From patchwork Sun Jul 12 22:47:21 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Joachim Eastwood X-Patchwork-Id: 6773391 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork1.web.kernel.org (Postfix) with ESMTP id 16B5B9F2F0 for ; Sun, 12 Jul 2015 22:51:52 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 72440205F9 for ; Sun, 12 Jul 2015 22:51:50 +0000 (UTC) Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.9]) (using TLSv1.2 with cipher AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id D424C205F3 for ; Sun, 12 Jul 2015 22:51:48 +0000 (UTC) Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1ZEQ3Y-0002j0-TW; Sun, 12 Jul 2015 22:49:08 +0000 Received: from mail-la0-x229.google.com ([2a00:1450:4010:c03::229]) by bombadil.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1ZEQ3K-0002fl-4B for linux-arm-kernel@lists.infradead.org; Sun, 12 Jul 2015 22:48:55 +0000 Received: by laar3 with SMTP id r3so291568859laa.0 for ; Sun, 12 Jul 2015 15:48:32 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=WEulrhFPIRl/DvDIUrgdisSEfz3Y4z4Uen/QlXFlJmQ=; b=S//YyxzHk6KNTlo8adObGybWNGnzENSXD4s7582Q/B7imXinTZiGHSeVePZ2wk1X4B hzdtyZVptcHrPTYKY/+5juVDQWCja9Bi2eqJ2Skzm4hsoV+ozNiUcL0MkPZEeX1ngcY0 ALxPqGLvNBSHM23TObSVOD38yMYNCCpxgd3ktS1f78hiWZMCvlQKx3XdRZFRK0Dxcy/E pFzyD3go/e990EiBvYQo9GiUZkB/q3PKkyS2Y9G1pSM/mKdinVo00Dbdfw3u8KQzfdk7 kha6uHY/4zxNIXCNcMcZkshJ7NGpYDFYC1Y63mkf2neMV9+iN9AzTIOjAPQ6ZH+ZEt6c 3gbQ== X-Received: by 10.112.50.148 with SMTP id c20mr29266727lbo.27.1436741312317; Sun, 12 Jul 2015 15:48:32 -0700 (PDT) Received: from localhost.localdomain (141.89-11-213.nextgentel.com. [89.11.213.141]) by smtp.gmail.com with ESMTPSA id ba6sm4208321lab.26.2015.07.12.15.48.31 (version=TLSv1.1 cipher=ECDHE-RSA-RC4-SHA bits=128/128); Sun, 12 Jul 2015 15:48:31 -0700 (PDT) From: Joachim Eastwood To: wsa@the-dreams.de Subject: [PATCH 1/2] i2c: add i2c-lpc2k driver Date: Mon, 13 Jul 2015 00:47:21 +0200 Message-Id: <1436741242-13489-2-git-send-email-manabian@gmail.com> X-Mailer: git-send-email 1.8.0 In-Reply-To: <1436741242-13489-1-git-send-email-manabian@gmail.com> References: <1436741242-13489-1-git-send-email-manabian@gmail.com> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20150712_154854_572270_602A4DE8 X-CRM114-Status: GOOD ( 30.41 ) X-Spam-Score: -2.7 (--) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: devicetree@vger.kernel.org, Joachim Eastwood , linux-i2c@vger.kernel.org, linux-arm-kernel@lists.infradead.org MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Spam-Status: No, score=-5.5 required=5.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED, DKIM_SIGNED, FREEMAIL_FROM, RCVD_IN_DNSWL_MED, RP_MATCHES_RCVD, T_DKIM_INVALID, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Add support for the I2C controller found on several NXP devices including LPC2xxx, LPC178x/7x and LPC18xx/43xx. The controller is implemented as a state machine and the driver act upon the state changes when the bus is accessed. The I2C controller supports master/slave operation, bus arbitration, programmable clock rate, and speeds up to 1 Mbit/s. Signed-off-by: Joachim Eastwood --- drivers/i2c/busses/Kconfig | 10 + drivers/i2c/busses/Makefile | 1 + drivers/i2c/busses/i2c-lpc2k.c | 551 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 562 insertions(+) create mode 100644 drivers/i2c/busses/i2c-lpc2k.c diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 35ac23768ce9..a880c82bed3d 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -612,6 +612,16 @@ config I2C_KEMPLD This driver can also be built as a module. If so, the module will be called i2c-kempld. +config I2C_LPC2K + tristate "I2C bus support for NXP LPC2K/LPC178x/18xx/43xx" + depends on OF && (ARCH_LPC18XX || COMPILE_TEST) + help + This driver supports the I2C interface found several NXP + devices including LPC2xxx, LPC178x/7x and LPC18xx/43xx. + + This driver can also be built as a module. If so, the module + will be called i2c-lpc2k. + config I2C_MESON tristate "Amlogic Meson I2C controller" depends on ARCH_MESON diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index e5f537c80da0..65b84097e665 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -58,6 +58,7 @@ obj-$(CONFIG_I2C_IMX) += i2c-imx.o obj-$(CONFIG_I2C_IOP3XX) += i2c-iop3xx.o obj-$(CONFIG_I2C_JZ4780) += i2c-jz4780.o obj-$(CONFIG_I2C_KEMPLD) += i2c-kempld.o +obj-$(CONFIG_I2C_LPC2K) += i2c-lpc2k.o obj-$(CONFIG_I2C_MESON) += i2c-meson.o obj-$(CONFIG_I2C_MPC) += i2c-mpc.o obj-$(CONFIG_I2C_MT65XX) += i2c-mt65xx.o diff --git a/drivers/i2c/busses/i2c-lpc2k.c b/drivers/i2c/busses/i2c-lpc2k.c new file mode 100644 index 000000000000..7bcbecc8dabe --- /dev/null +++ b/drivers/i2c/busses/i2c-lpc2k.c @@ -0,0 +1,551 @@ +/* + * Copyright (C) 2011 NXP Semiconductors + * + * Code portions referenced from the i2x-pxa and i2c-pnx drivers + * + * Make SMBus byte and word transactions work on LPC178x/7x + * Copyright (c) 2012 + * Alexander Potashev, Emcraft Systems, aspotashev@emcraft.com + * Anton Protopopov, Emcraft Systems, antonp@emcraft.com + * + * Copyright (C) 2015 Joachim Eastwood + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* LPC24xx register offsets and bits */ +#define LPC24XX_I2CONSET 0x00 +#define LPC24XX_I2STAT 0x04 +#define LPC24XX_I2DAT 0x08 +#define LPC24XX_I2ADDR 0x0c +#define LPC24XX_I2SCLH 0x10 +#define LPC24XX_I2SCLL 0x14 +#define LPC24XX_I2CONCLR 0x18 + +#define LPC24XX_AA BIT(2) +#define LPC24XX_SI BIT(3) +#define LPC24XX_STO BIT(4) +#define LPC24XX_STA BIT(5) +#define LPC24XX_I2EN BIT(6) + +#define LPC24XX_CLEAR_ALL (LPC24XX_AA | LPC24XX_SI | LPC24XX_STO | \ + LPC24XX_STA | LPC24XX_I2EN) + +/* I2C SCL clock has different duty cycle depending on mode */ +#define I2C_STD_MODE_DUTY 46 +#define I2C_FAST_MODE_DUTY 32 +#define I2C_FAST_MODE_PLUS_DUTY 34 + +/* + * 26 possible I2C status codes, but codes applicable only + * to master are listed here and used in this driver + */ +enum { + M_BUS_ERROR = 0x00, + M_START = 0x08, + M_REPSTART = 0x10, + MX_ADDR_W_ACK = 0x18, + MX_ADDR_W_NACK = 0x20, + MX_DATA_W_ACK = 0x28, + MX_DATA_W_NACK = 0x30, + M_DATA_ARB_LOST = 0x38, + MR_ADDR_R_ACK = 0x40, + MR_ADDR_R_NACK = 0x48, + MR_DATA_R_ACK = 0x50, + MR_DATA_R_NACK = 0x58, + M_I2C_IDLE = 0xf8, +}; + +struct lpc2k_i2c { + void __iomem *reg_base; + struct clk *clk; + int irq; + wait_queue_head_t wait; + struct i2c_adapter adap; + struct i2c_msg *msg; + int msg_idx; + int msg_status; + int is_last; +}; + +static void i2c_lpc2k_reset(struct lpc2k_i2c *i2c) +{ + /* Will force clear all statuses */ + writel(LPC24XX_CLEAR_ALL, i2c->reg_base + LPC24XX_I2CONCLR); + writel(0, i2c->reg_base + LPC24XX_I2ADDR); + writel(LPC24XX_I2EN, i2c->reg_base + LPC24XX_I2CONSET); +} + +static int i2c_lpc2k_clear_arb(struct lpc2k_i2c *i2c) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(1000); + + /* + * If the transfer needs to abort for some reason, we'll try to + * force a stop condition to clear any pending bus conditions + */ + writel(LPC24XX_STO, i2c->reg_base + LPC24XX_I2CONSET); + + /* Wait for status change */ + while (readl(i2c->reg_base + LPC24XX_I2STAT) != M_I2C_IDLE) { + if (time_after(jiffies, timeout)) { + /* Bus was not idle, try to reset adapter */ + i2c_lpc2k_reset(i2c); + return -EBUSY; + } + + cpu_relax(); + } + + return 0; +} + +static void i2c_lpc2k_pump_msg(struct lpc2k_i2c *i2c) +{ + unsigned char data; + u32 status; + + /* + * I2C in the LPC2xxx series is basically a state machine. + * Just run through the steps based on the current status. + */ + status = readl(i2c->reg_base + LPC24XX_I2STAT); + + switch (status) { + case M_START: + case M_REPSTART: + /* Start bit was just sent out, send out addr and dir */ + data = (i2c->msg->addr << 1); + if (i2c->msg->flags & I2C_M_RD) + data |= 1; + + writel(data, i2c->reg_base + LPC24XX_I2DAT); + writel(LPC24XX_STA, i2c->reg_base + LPC24XX_I2CONCLR); + + dev_dbg(&i2c->adap.dev, "Start sent with addr 0x%02x\n", data); + break; + + case MX_ADDR_W_ACK: + case MX_DATA_W_ACK: + /* + * Address or data was sent out with an ACK. If there is more + * data to send, send it now + */ + if (i2c->msg_idx < i2c->msg->len) { + writel(i2c->msg->buf[i2c->msg_idx], + i2c->reg_base + LPC24XX_I2DAT); + dev_dbg(&i2c->adap.dev, "ACK ok, sending (0x%02x)\n", + i2c->msg->buf[i2c->msg_idx]); + } else if (i2c->is_last) { + /* Last message, send stop */ + writel(LPC24XX_STO | LPC24XX_AA, + i2c->reg_base + LPC24XX_I2CONSET); + writel(LPC24XX_SI, i2c->reg_base + LPC24XX_I2CONCLR); + i2c->msg_status = 0; + dev_dbg(&i2c->adap.dev, "ACK ok, sending stop\n"); + disable_irq_nosync(i2c->irq); + } else { + i2c->msg_status = 0; + dev_dbg(&i2c->adap.dev, + "ACK ok, idling until next message start\n"); + disable_irq_nosync(i2c->irq); + } + + i2c->msg_idx++; + break; + + case MR_ADDR_R_ACK: + /* Receive first byte from slave */ + if (i2c->msg->len == 1) { + /* Last byte, return NACK */ + writel(LPC24XX_AA, i2c->reg_base + LPC24XX_I2CONCLR); + } else { + /* Not last byte, return ACK */ + writel(LPC24XX_AA, i2c->reg_base + LPC24XX_I2CONSET); + } + + writel(LPC24XX_STA, i2c->reg_base + LPC24XX_I2CONCLR); + break; + + case MR_DATA_R_NACK: + /* + * The I2C shows NACK status on reads, so we need to accept + * the NACK as an ACK here. This should be ok, as the real + * BACK would of been caught on the address write. + */ + case MR_DATA_R_ACK: + /* Data was received */ + if (i2c->msg_idx < i2c->msg->len) { + i2c->msg->buf[i2c->msg_idx] = readl(i2c->reg_base + LPC24XX_I2DAT); + dev_dbg(&i2c->adap.dev, "ACK ok, received (0x%02x)\n", + i2c->msg->buf[i2c->msg_idx]); + } + + /* If transfer is done, send STOP */ + if (i2c->msg_idx >= i2c->msg->len - 1 && i2c->is_last) { + writel(LPC24XX_STO | LPC24XX_AA, i2c->reg_base + LPC24XX_I2CONSET); + writel(LPC24XX_SI, i2c->reg_base + LPC24XX_I2CONCLR); + i2c->msg_status = 0; + dev_dbg(&i2c->adap.dev, "ACK ok, sending stop\n"); + } + + /* Message is done */ + if (i2c->msg_idx >= i2c->msg->len - 1) { + i2c->msg_status = 0; + dev_dbg(&i2c->adap.dev, + "ACK ok, idling until next message start\n"); + disable_irq_nosync(i2c->irq); + } + + /* + * One pre-last data input, send NACK to tell the slave that + * this is going to be the last data byte to be transferred. + */ + if (i2c->msg_idx >= i2c->msg->len - 2) { + /* One byte left to receive - NACK */ + writel(LPC24XX_AA, i2c->reg_base + LPC24XX_I2CONCLR); + } else { + /* More than one byte left to receive - ACK */ + writel(LPC24XX_AA, i2c->reg_base + LPC24XX_I2CONSET); + } + + writel(LPC24XX_STA, i2c->reg_base + LPC24XX_I2CONCLR); + i2c->msg_idx++; + break; + + case MX_ADDR_W_NACK: + case MX_DATA_W_NACK: + case MR_ADDR_R_NACK: + /* NACK processing is done */ + writel(LPC24XX_STO | LPC24XX_AA, i2c->reg_base + LPC24XX_I2CONSET); + i2c->msg_status = -ENODEV; + dev_dbg(&i2c->adap.dev, "Device NACKed, error\n"); + disable_irq_nosync(i2c->irq); + break; + + case M_DATA_ARB_LOST: + /* Arbitration lost */ + i2c->msg_status = -EIO; + dev_dbg(&i2c->adap.dev, "Arbitration lost, error\n"); + + /* Release the I2C bus */ + writel(LPC24XX_STA | LPC24XX_STO, i2c->reg_base + LPC24XX_I2CONCLR); + disable_irq_nosync(i2c->irq); + break; + + default: + /* Unexpected statuses */ + i2c->msg_status = -EIO; + dev_err(&i2c->adap.dev, "Unexpected status, error (0x%x)\n", + status); + disable_irq_nosync(i2c->irq); + break; + } + + /* Exit on failure or all bytes transferred */ + if (i2c->msg_status != -EBUSY) + wake_up(&i2c->wait); + + /* + * If `msg_status` is zero, then `lpc2k_process_msg()` + * is responsible for clearing the SI flag. + */ + if (i2c->msg_status != 0) + writel(LPC24XX_SI, i2c->reg_base + LPC24XX_I2CONCLR); +} + +static int lpc2k_process_msg(struct lpc2k_i2c *i2c, int msgidx) +{ + int ret; + + dev_dbg(&i2c->adap.dev, "Processing message %d (len=%d) (flags=%x)\n", + msgidx, i2c->msg->len, i2c->msg->flags); + + /* A new transfer is kicked off by initiating a start condition */ + if (!msgidx) { + dev_dbg(&i2c->adap.dev, "Start sent\n"); + writel(LPC24XX_STA, i2c->reg_base + LPC24XX_I2CONSET); + } else { + /* + * A multi-message I2C transfer continues where the + * previous I2C transfer left off and uses the + * current condition of the I2C adapter. + */ + if (unlikely(i2c->msg->flags & I2C_M_NOSTART)) { + WARN_ON(i2c->msg->len == 0); + + if (!(i2c->msg->flags & I2C_M_RD)) { + /* Start transmit of data */ + writel(i2c->msg->buf[0], i2c->reg_base + LPC24XX_I2DAT); + i2c->msg_idx++; + dev_dbg(&i2c->adap.dev, "New data sent\n"); + } else { + dev_dbg(&i2c->adap.dev, "New data incoming\n"); + } + } else { + /* Start or repeated start */ + dev_dbg(&i2c->adap.dev, "Repeated start sent\n"); + writel(LPC24XX_STA, i2c->reg_base + LPC24XX_I2CONSET); + } + + writel(LPC24XX_SI, i2c->reg_base + LPC24XX_I2CONCLR); + } + + enable_irq(i2c->irq); + + /* Wait for transfer completion */ + if (wait_event_timeout(i2c->wait, i2c->msg_status != -EBUSY, + msecs_to_jiffies(1000)) == 0) { + disable_irq_nosync(i2c->irq); + dev_dbg(&i2c->adap.dev, "Transfer timed out!\n"); + + return -ETIMEDOUT; + } + + ret = i2c->msg_status; + if (ret == 0) + dev_dbg(&i2c->adap.dev, "Transfer successful\n"); + else + dev_dbg(&i2c->adap.dev, "Transfer failed (%d)\n", ret); + + return ret; +} + +static int i2c_lpc2k_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, + int msg_num) +{ + struct lpc2k_i2c *i2c = i2c_get_adapdata(adap); + int ret, i; + u32 stat; + + /* Check for bus idle condition */ + stat = readl(i2c->reg_base + LPC24XX_I2STAT); + if (stat != M_I2C_IDLE) { + /* Something is holding the bus, try to clear it */ + ret = i2c_lpc2k_clear_arb(i2c); + if (ret) { + dev_warn(&i2c->adap.dev, "Bus is not idle\n"); + return ret; + } + } + + dev_dbg(&i2c->adap.dev, "Processing total messages = %d\n", msg_num); + + /* Process a single message at a time */ + for (i = 0; i < msg_num; i++) { + /* Save message pointer and current message data index */ + i2c->msg = &msgs[i]; + i2c->msg_idx = 0; + i2c->msg_status = -EBUSY; + i2c->is_last = (i == (msg_num - 1)); + + ret = lpc2k_process_msg(i2c, i); + if (ret) + return ret; + } + + return msg_num; +} + +static irqreturn_t i2c_lpc2k_handler(int irq, void *dev_id) +{ + struct lpc2k_i2c *i2c = dev_id; + + if (readl(i2c->reg_base + LPC24XX_I2CONSET) & LPC24XX_SI) { + i2c_lpc2k_pump_msg(i2c); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static u32 i2c_lpc2k_functionality(struct i2c_adapter *adap) +{ + /* Only emulated SMBus for now */ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm i2c_lpc2k_algorithm = { + .master_xfer = i2c_lpc2k_xfer, + .functionality = i2c_lpc2k_functionality, +}; + +static int i2c_lpc2k_probe(struct platform_device *pdev) +{ + struct lpc2k_i2c *i2c; + struct resource *res; + u32 bus_clk_rate; + u32 scl_high; + u32 clkrate; + int ret; + + i2c = devm_kzalloc(&pdev->dev, sizeof(*i2c), GFP_KERNEL); + if (!i2c) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + i2c->reg_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(i2c->reg_base)) + return PTR_ERR(i2c->reg_base); + + i2c->irq = platform_get_irq(pdev, 0); + if (i2c->irq < 0) { + dev_err(&pdev->dev, "can't get interrupt resource\n"); + return i2c->irq; + } + + init_waitqueue_head(&i2c->wait); + + i2c->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(i2c->clk)) { + dev_err(&pdev->dev, "error getting clock\n"); + return PTR_ERR(i2c->clk); + } + + ret = clk_prepare_enable(i2c->clk); + if (ret) { + dev_err(&pdev->dev, "unable to enable clock.\n"); + return ret; + } + + ret = devm_request_irq(&pdev->dev, i2c->irq, i2c_lpc2k_handler, 0, + dev_name(&pdev->dev), i2c); + if (ret < 0) { + dev_err(&pdev->dev, "can't request interrupt.\n"); + goto fail_clk; + } + + disable_irq_nosync(i2c->irq); + + /* Place controller is a known state */ + i2c_lpc2k_reset(i2c); + + ret = of_property_read_u32(pdev->dev.of_node, "clock-frequency", + &bus_clk_rate); + if (ret) + bus_clk_rate = 100000; /* 100 kHz default clock rate */ + + clkrate = clk_get_rate(i2c->clk); + if (clkrate == 0) { + dev_err(&pdev->dev, "can't get I2C base clock\n"); + ret = -EINVAL; + goto fail_clk; + } + + /* Setup I2C dividers to generate clock with proper duty cycle */ + clkrate = clkrate / bus_clk_rate; + if (bus_clk_rate <= 100000) + scl_high = (clkrate * I2C_STD_MODE_DUTY) / 100; + else if (bus_clk_rate <= 400000) + scl_high = (clkrate * I2C_FAST_MODE_DUTY) / 100; + else + scl_high = (clkrate * I2C_FAST_MODE_PLUS_DUTY) / 100; + + writel(scl_high, i2c->reg_base + LPC24XX_I2SCLH); + writel(clkrate - scl_high, i2c->reg_base + LPC24XX_I2SCLL); + + platform_set_drvdata(pdev, i2c); + + i2c_set_adapdata(&i2c->adap, i2c); + i2c->adap.owner = THIS_MODULE; + strlcpy(i2c->adap.name, "LPC2K I2C adapter", sizeof(i2c->adap.name)); + i2c->adap.algo = &i2c_lpc2k_algorithm; + i2c->adap.dev.parent = &pdev->dev; + i2c->adap.dev.of_node = pdev->dev.of_node; + i2c->adap.nr = pdev->id; + + ret = i2c_add_numbered_adapter(&i2c->adap); + if (ret < 0) { + dev_err(&pdev->dev, "failed to add bus!\n"); + goto fail_clk; + } + + dev_info(&pdev->dev, "LPC2K I2C adapter\n"); + + return 0; + +fail_clk: + clk_disable_unprepare(i2c->clk); + return ret; +} + +static int i2c_lpc2k_remove(struct platform_device *dev) +{ + struct lpc2k_i2c *i2c = platform_get_drvdata(dev); + + i2c_del_adapter(&i2c->adap); + clk_disable_unprepare(i2c->clk); + + return 0; +} + +#ifdef CONFIG_PM +static int i2c_lpc2k_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lpc2k_i2c *i2c = platform_get_drvdata(pdev); + + clk_disable(i2c->clk); + + return 0; +} + +static int i2c_lpc2k_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lpc2k_i2c *i2c = platform_get_drvdata(pdev); + + clk_enable(i2c->clk); + i2c_lpc2k_reset(i2c); + + return 0; +} + +static const struct dev_pm_ops i2c_lpc2k_dev_pm_ops = { + .suspend_noirq = i2c_lpc2k_suspend, + .resume_noirq = i2c_lpc2k_resume, +}; + +#define I2C_LPC2K_DEV_PM_OPS (&i2c_lpc2k_dev_pm_ops) +#else +#define I2C_LPC2K_DEV_PM_OPS NULL +#endif + +static const struct of_device_id lpc2k_i2c_match[] = { + { .compatible = "nxp,lpc1788-i2c" }, + {}, +}; +MODULE_DEVICE_TABLE(of, lpc2k_i2c_match); + +static struct platform_driver i2c_lpc2k_driver = { + .probe = i2c_lpc2k_probe, + .remove = i2c_lpc2k_remove, + .driver = { + .name = "lpc2k-i2c", + .pm = I2C_LPC2K_DEV_PM_OPS, + .of_match_table = lpc2k_i2c_match, + }, +}; +module_platform_driver(i2c_lpc2k_driver); + +MODULE_AUTHOR("Kevin Wells "); +MODULE_DESCRIPTION("I2C driver for LPC2xxx devices"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:lpc2k-i2c");