2010-01-11 00:00:55

by Steven King

[permalink] [raw]
Subject: [PATCH] m68knommu: driver for Freescale Coldfire I2C controller.

Add support for the I2C controller used on Freescale/Motorola Coldfire MCUs.

Signed-off-by: Steven King <[email protected]>

drivers/i2c/busses/Kconfig | 11 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-mcf.c | 463
++++++++++++++++++++++++++++++++++++++++++
3 files changed, 475 insertions(+), 0 deletions(-)

diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 5f318ce..6caf7a0 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -429,6 +429,17 @@ config I2C_MPC
This driver can also be built as a module. If so, the module
will be called i2c-mpc.

+config I2C_MCF
+ tristate "Freescale Coldfire I2C driver"
+ depends on (M5206 || M5206e || M520x || M523x || M5249 || M527x || M528x ||
M5307 || M532x || M5407)
+ help
+ This driver supports the I2C interface availible on some Freescale
+ Coldfire processors (M520x, M523x, M5249, M5271, M5275, M528x,
+ M5307, M532x, M5407).
+
+ This driver can be built as a module. If so, the module
+ will be called i2c-mcf.
+
config I2C_MV64XXX
tristate "Marvell mv64xxx I2C Controller"
depends on (MV64X60 || PLAT_ORION) && EXPERIMENTAL
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 302c551..c71349e 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -41,6 +41,7 @@ obj-$(CONFIG_I2C_IMX) += i2c-imx.o
obj-$(CONFIG_I2C_IOP3XX) += i2c-iop3xx.o
obj-$(CONFIG_I2C_IXP2000) += i2c-ixp2000.o
obj-$(CONFIG_I2C_MPC) += i2c-mpc.o
+obj-$(CONFIG_I2C_MCF) += i2c-mcf.o
obj-$(CONFIG_I2C_MV64XXX) += i2c-mv64xxx.o
obj-$(CONFIG_I2C_OCORES) += i2c-ocores.o
obj-$(CONFIG_I2C_OMAP) += i2c-omap.o
diff --git a/drivers/i2c/busses/i2c-mcf.c b/drivers/i2c/busses/i2c-mcf.c
new file mode 100644
index 0000000..27f0d0b
--- /dev/null
+++ b/drivers/i2c/busses/i2c-mcf.c
@@ -0,0 +1,463 @@
+/* Freescale/Motorola Coldfire I2C driver.
+ *
+ * Copyright 2010 Steven King <[email protected]>
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/errno.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+
+#include <asm/mcfi2c.h>
+
+#define DRIVER_NAME "mcfi2c"
+
+#define MCFI2C_ADR 0x00
+#define MCFI2C_FDR 0x04
+#define MCFI2C_CR 0x08
+#define MCFI2C_CR_IEN 0x80
+#define MCFI2C_CR_IIEN 0x40
+#define MCFI2C_CR_MSTA 0x20
+#define MCFI2C_CR_MTX 0x10
+#define MCFI2C_CR_TXAK 0x08
+#define MCFI2C_CR_RSTA 0x04
+#define MCFI2C_DR 0x10
+#define MCFI2C_SR 0x0C
+#define MCFI2C_SR_ICF 0x80
+#define MCFI2C_SR_IAAS 0x40
+#define MCFI2C_SR_IBB 0x20
+#define MCFI2C_SR_IAL 0x10
+#define MCFI2C_SR_SRW 0x04
+#define MCFI2C_SR_IIF 0x02
+#define MCFI2C_SR_RXAK 0x01
+
+#define DEFAULT_I2C_BUS_SPEED 100000
+
+struct mcfi2c {
+ struct i2c_adapter adapter;
+ void __iomem *iobase;
+ int irq;
+ struct clk *clk;
+ struct completion completion;
+};
+
+static u8 mcfi2c_rd_cr(struct mcfi2c *mcfi2c)
+{
+ return readb(mcfi2c->iobase + MCFI2C_CR);
+}
+
+static void mcfi2c_wr_cr(struct mcfi2c *mcfi2c, u8 val)
+{
+ writeb(val, mcfi2c->iobase + MCFI2C_CR);
+}
+
+static u8 mcfi2c_rd_sr(struct mcfi2c *mcfi2c)
+{
+ return readb(mcfi2c->iobase + MCFI2C_SR);
+}
+
+static void mcfi2c_wr_sr(struct mcfi2c *mcfi2c, u8 val)
+{
+ writeb(val, mcfi2c->iobase + MCFI2C_SR);
+}
+
+static u8 mcfi2c_rd_dr(struct mcfi2c *mcfi2c)
+{
+ return readb(mcfi2c->iobase + MCFI2C_DR);
+}
+
+static void mcfi2c_wr_dr(struct mcfi2c *mcfi2c, u8 val)
+{
+ writeb(val, mcfi2c->iobase + MCFI2C_DR);
+}
+
+static void mcfi2c_start(struct mcfi2c *mcfi2c)
+{
+ mcfi2c_wr_cr(mcfi2c, MCFI2C_CR_IEN | MCFI2C_CR_IIEN | MCFI2C_CR_MSTA |
+ MCFI2C_CR_MTX);
+}
+
+static void mcfi2c_repeat_start(struct mcfi2c *mcfi2c)
+{
+ mcfi2c_wr_cr(mcfi2c, MCFI2C_CR_IEN | MCFI2C_CR_IIEN | MCFI2C_CR_MSTA |
+ MCFI2C_CR_MTX | MCFI2C_CR_RSTA);
+}
+
+static void mcfi2c_stop(struct mcfi2c *mcfi2c)
+{
+ mcfi2c_wr_cr(mcfi2c, MCFI2C_CR_IEN);
+}
+
+static void mcfi2c_tx_ack(struct mcfi2c *mcfi2c)
+{
+ mcfi2c_wr_cr(mcfi2c, MCFI2C_CR_IEN | MCFI2C_CR_IIEN | MCFI2C_CR_MSTA);
+}
+
+static void mcfi2c_tx_nak(struct mcfi2c *mcfi2c)
+{
+ mcfi2c_wr_cr(mcfi2c, MCFI2C_CR_IEN | MCFI2C_CR_IIEN | MCFI2C_CR_MSTA |
+ MCFI2C_CR_TXAK);
+}
+
+static irqreturn_t mcfi2c_irq_handler(int this_irq, void *dev_id)
+{
+ struct mcfi2c *mcfi2c = dev_id;
+
+ /* clear interrupt */
+ mcfi2c_wr_sr(mcfi2c, 0);
+ complete(&mcfi2c->completion);
+
+ return IRQ_HANDLED;
+}
+
+static void mcfi2c_reset(struct mcfi2c *mcfi2c)
+{
+ mcfi2c_wr_cr(mcfi2c, 0);
+ mcfi2c_wr_cr(mcfi2c, MCFI2C_CR_IEN | MCFI2C_CR_MSTA);
+ mcfi2c_rd_dr(mcfi2c);
+ mcfi2c_wr_sr(mcfi2c, 0);
+ mcfi2c_wr_cr(mcfi2c, 0);
+ mcfi2c_wr_cr(mcfi2c, MCFI2C_CR_IEN);
+}
+
+static void mcfi2c_wait_for_bus_idle(struct mcfi2c *mcfi2c)
+{
+ if (mcfi2c_rd_sr(mcfi2c) & MCFI2C_SR_IBB) {
+ unsigned long timeout = jiffies + HZ / 2;
+ do {
+ cond_resched();
+ if (time_after(jiffies, timeout)) {
+ mcfi2c_reset(mcfi2c);
+ break;
+ }
+ } while (mcfi2c_rd_sr(mcfi2c) & MCFI2C_SR_IBB);
+ }
+}
+
+static int mcfi2c_wait_for_bus_busy(struct mcfi2c *mcfi2c)
+{
+ u8 sr;
+ while (!((sr = mcfi2c_rd_sr(mcfi2c)) & MCFI2C_SR_IBB))
+ if (sr & MCFI2C_SR_IAL) {
+ mcfi2c_reset(mcfi2c);
+ return -EIO;
+ }
+ return 0;
+}
+
+static int mcfi2c_xmit(struct mcfi2c *mcfi2c, u16 addr, u16 flags, u8 *buf,
+ u16 len, int timeout, int more)
+{
+ if (!(mcfi2c_rd_cr(mcfi2c) & MCFI2C_CR_MSTA)) {
+ mcfi2c_wait_for_bus_idle(mcfi2c);
+
+ INIT_COMPLETION(mcfi2c->completion);
+ mcfi2c_start(mcfi2c);
+
+ if (mcfi2c_wait_for_bus_busy(mcfi2c))
+ return -EIO;
+ }
+
+ mcfi2c_wr_dr(mcfi2c, (addr << 1) | (flags & I2C_M_RD));
+ while (wait_for_completion_timeout(&mcfi2c->completion, timeout)) {
+ u8 sr = mcfi2c_rd_sr(mcfi2c);
+ if (sr & MCFI2C_SR_IAL) {
+ mcfi2c_wr_sr(mcfi2c, ~MCFI2C_SR_IAL);
+ return -EIO;
+ } else if (mcfi2c_rd_cr(mcfi2c) & MCFI2C_CR_MTX) {
+ if (sr & MCFI2C_SR_RXAK) {
+ mcfi2c_stop(mcfi2c);
+ return -EIO;
+ } else if (flags & I2C_M_RD) {
+ if (len > 1)
+ mcfi2c_tx_ack(mcfi2c);
+ else
+ mcfi2c_tx_nak(mcfi2c);
+ /* dummy read */
+ mcfi2c_rd_dr(mcfi2c);
+ } else if (len--) {
+ mcfi2c_wr_dr(mcfi2c, *buf++);
+ } else {
+ if (more)
+ mcfi2c_repeat_start(mcfi2c);
+ else
+ mcfi2c_stop(mcfi2c);
+ return 0;
+ }
+ } else if (--len) {
+ if (!(len > 1))
+ mcfi2c_tx_nak(mcfi2c);
+ *buf++ = mcfi2c_rd_dr(mcfi2c);
+ } else {
+ if (more)
+ mcfi2c_repeat_start(mcfi2c);
+ else
+ mcfi2c_stop(mcfi2c);
+ *buf++ = mcfi2c_rd_dr(mcfi2c);
+ return 0;
+ }
+ }
+ mcfi2c_stop(mcfi2c);
+
+ return -ETIMEDOUT;
+}
+
+static int mcfi2c_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs,
+ int num)
+{
+ struct mcfi2c *mcfi2c = i2c_get_adapdata(adapter);
+ int cnt = 0;
+ int status;
+ int retries;
+
+ while (num--) {
+ retries = adapter->retries;
+ if (msgs->flags & ~I2C_M_RD)
+ return -EINVAL;
+ do {
+ status = mcfi2c_xmit(mcfi2c, msgs->addr, msgs->flags,
+ msgs->buf, msgs->len,
+ adapter->timeout, num);
+ } while (status && retries--);
+ if (status)
+ return status;
+ ++cnt;
+ ++msgs;
+ }
+
+ return cnt;
+}
+
+static u32 mcfi2c_func(struct i2c_adapter *adapter)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm mcfi2c_algo = {
+ .master_xfer = mcfi2c_xfer,
+ .functionality = mcfi2c_func,
+};
+
+static const u16 mcfi2c_fdr[] = {
+ 28, 30, 34, 40, 44, 48, 56, 68,
+ 80, 88, 104, 128, 144, 160, 192, 240,
+ 288, 320, 384, 480, 576, 640, 768, 960,
+ 1152, 1280, 1536, 1920, 2304, 2560, 3072, 3840,
+ 20, 22, 24, 26, 28, 32, 36, 40,
+ 48, 56, 64, 72, 80, 96, 112, 128,
+ 160, 192, 224, 256, 320, 384, 448, 512,
+ 640, 768, 896, 1024, 1280, 1536, 1792, 2048
+};
+
+static u8 __devinit mcfi2c_calc_fdr(struct mcfi2c *mcfi2c,
+ struct mcfi2c_platform_data *pdata)
+{
+ u32 bitrate = (pdata && pdata->bitrate) ?
+ pdata->bitrate : DEFAULT_I2C_BUS_SPEED;
+ int div = clk_get_rate(mcfi2c->clk)/bitrate;
+ int r = 0, i = 0;
+
+ do
+ if (abs(mcfi2c_fdr[i] - div) < abs(mcfi2c_fdr[r] - div))
+ r = i;
+ while (++i < ARRAY_SIZE(mcfi2c_fdr));
+
+ return r;
+}
+
+static int __devinit mcfi2c_probe(struct platform_device *pdev)
+{
+ struct mcfi2c *mcfi2c;
+ struct resource *res;
+ int status;
+
+ mcfi2c = kzalloc(sizeof(*mcfi2c), GFP_KERNEL);
+ if (!mcfi2c) {
+ dev_dbg(&pdev->dev, "kzalloc failed\n");
+
+ return -ENOMEM;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_dbg(&pdev->dev, "platform_get_resource failed\n");
+ status = -ENXIO;
+ goto fail0;
+ }
+
+ if (!request_mem_region(res->start, resource_size(res), pdev->name)) {
+ dev_dbg(&pdev->dev, "request_mem_region failed\n");
+ status = -EBUSY;
+ goto fail0;
+ }
+
+ mcfi2c->iobase = ioremap(res->start, resource_size(res));
+ if (!mcfi2c->iobase) {
+ dev_dbg(&pdev->dev, "ioremap failed\n");
+ status = -ENOMEM;
+ goto fail1;
+ }
+
+ mcfi2c->irq = platform_get_irq(pdev, 0);
+ if (mcfi2c->irq < 0) {
+ dev_dbg(&pdev->dev, "platform_get_irq failed\n");
+ status = -ENXIO;
+ goto fail2;
+ }
+ status = request_irq(mcfi2c->irq, mcfi2c_irq_handler, IRQF_DISABLED,
+ pdev->name, mcfi2c);
+ if (status) {
+ dev_dbg(&pdev->dev, "request_irq failed\n");
+ goto fail2;
+ }
+
+ mcfi2c->clk = clk_get(&pdev->dev, "i2c_clk");
+ if (IS_ERR(mcfi2c->clk)) {
+ dev_dbg(&pdev->dev, "clk_get failed\n");
+ status = PTR_ERR(mcfi2c->clk);
+ goto fail3;
+ }
+ clk_enable(mcfi2c->clk);
+
+ platform_set_drvdata(pdev, mcfi2c);
+
+ init_completion(&mcfi2c->completion);
+
+ writeb(mcfi2c_calc_fdr(mcfi2c, pdev->dev.platform_data),
+ mcfi2c->iobase + MCFI2C_FDR);
+
+ writeb(0x00, mcfi2c->iobase + MCFI2C_ADR);
+
+ mcfi2c_wr_cr(mcfi2c, MCFI2C_CR_IEN);
+
+ /* if the bus busy (IBB) is set, reset the controller */
+ if (mcfi2c_rd_sr(mcfi2c) & MCFI2C_SR_IBB)
+ mcfi2c_reset(mcfi2c);
+
+ mcfi2c->adapter.algo = &mcfi2c_algo;
+ mcfi2c->adapter.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
+ mcfi2c->adapter.dev.parent = &pdev->dev;
+ mcfi2c->adapter.nr = pdev->id;
+ mcfi2c->adapter.retries = 2;
+ snprintf(mcfi2c->adapter.name, sizeof(mcfi2c->adapter.name),
+ DRIVER_NAME ".%d", pdev->id);
+
+ i2c_set_adapdata(&mcfi2c->adapter, mcfi2c);
+
+ status = i2c_add_numbered_adapter(&mcfi2c->adapter);
+ if (status < 0) {
+ dev_dbg(&pdev->dev, "i2c_add_numbered_adapter failed\n");
+ goto fail4;
+ }
+ dev_info(&pdev->dev, "Coldfire I2C bus driver\n");
+
+ return 0;
+
+fail4:
+ clk_disable(mcfi2c->clk);
+ clk_put(mcfi2c->clk);
+fail3:
+ free_irq(mcfi2c->irq, mcfi2c);
+fail2:
+ iounmap(mcfi2c->iobase);
+fail1:
+ release_mem_region(res->start, resource_size(res));
+fail0:
+ kfree(mcfi2c);
+
+ return status;
+}
+
+static int __devexit mcfi2c_remove(struct platform_device *pdev)
+{
+ struct mcfi2c *mcfi2c = platform_get_drvdata(pdev);
+ struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+ /* disable the hardware */
+ mcfi2c_wr_cr(mcfi2c, 0);
+
+ platform_set_drvdata(pdev, NULL);
+ i2c_del_adapter(&mcfi2c->adapter);
+ clk_disable(mcfi2c->clk);
+ clk_put(mcfi2c->clk);
+ free_irq(mcfi2c->irq, mcfi2c);
+ iounmap(mcfi2c->iobase);
+ release_mem_region(res->start, resource_size(res));
+ kfree(mcfi2c);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int mcfi2c_suspend(struct device *dev)
+{
+ struct mcfi2c *mcfi2c = platform_get_drvdata(to_platform_device(dev));
+
+ mcfi2c_wr_cr(mcfi2c, 0);
+ clk_disable(mcfi2c->clk);
+
+ return 0;
+}
+
+static int mcfi2c_resume(struct device *dev)
+{
+ struct mcfi2c *mcfi2c = platform_get_drvdata(to_platform_device(dev));
+
+ clk_enable(mcfi2c->clk);
+ mcfi2c_wr_cr(mcfi2c, MCFI2C_CR_IEN);
+
+ return 0;
+}
+
+static struct dev_pm_ops mcfi2c_dev_pm_ops = {
+ .suspend = mcfi2c_suspend,
+ .resume = mcfi2c_resume,
+};
+
+#define MCFI2C_DEV_PM_OPS (&mcfi2c_dev_pm_ops)
+#else
+#define MCFI2C_DEV_PM_OPS NULL
+#endif
+
+static struct platform_driver mcfi2c_driver = {
+ .driver.name = DRIVER_NAME,
+ .driver.owner = THIS_MODULE,
+ .driver.pm = MCFI2C_DEV_PM_OPS,
+ .remove = __devexit_p(mcfi2c_remove),
+};
+
+static int __init mcfi2c_init(void)
+{
+ return platform_driver_probe(&mcfi2c_driver, mcfi2c_probe);
+}
+module_init(mcfi2c_init);
+
+static void __exit mcfi2c_exit(void)
+{
+ platform_driver_unregister(&mcfi2c_driver);
+}
+module_exit(mcfi2c_exit);
+
+MODULE_AUTHOR("Steven King <[email protected]>");
+MODULE_DESCRIPTION("I2C-Bus support for Freescale Coldfire processors");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRIVER_NAME);


2010-01-11 08:37:35

by Jean Delvare

[permalink] [raw]
Subject: Re: [PATCH] m68knommu: driver for Freescale Coldfire I2C controller.

On Sun, 10 Jan 2010 16:00:41 -0800, Steven King wrote:
> Add support for the I2C controller used on Freescale/Motorola Coldfire MCUs.
>
> Signed-off-by: Steven King <[email protected]>
>
> drivers/i2c/busses/Kconfig | 11 +
> drivers/i2c/busses/Makefile | 1 +
> drivers/i2c/busses/i2c-mcf.c | 463 ++++++++++++++++++++++++++++++++++++++++++

Please come up with a longer name. i2c-coldfire, for example.

--
Jean Delvare

2010-01-11 18:24:10

by Steven King

[permalink] [raw]
Subject: [PATCH v2] m68knommu: driver for Freescale Coldfire I2C controller.

Changes for this version:
rename drivers/i2c/busses/i2c-mcf.c to drivers/i2c/busses/i2c-coldfire.c
use I2C_COLDFIRE in drivers/i2c/busses/{Kconfig,Makefile}

------

Add support for the I2C controller used on Freescale/Motorola Coldfire MCUs.

Signed-off-by: Steven King <[email protected]>

drivers/i2c/busses/Kconfig | 11 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-coldfire.c | 463 +++++++++++++++++++++++++++++++++++++
3 files changed, 475 insertions(+), 0 deletions(-)

diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 5f318ce..83c2904 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -320,6 +320,17 @@ config I2C_BLACKFIN_TWI_CLK_KHZ
help
The unit of the TWI clock is kHz.

+config I2C_COLDFIRE
+ tristate "Freescale Coldfire I2C driver"
+ depends on (M5206 || M5206e || M520x || M523x || M5249 || M527x || M528x || M5307 || M532x || M5407)
+ help
+ This driver supports the I2C interface available on some Freescale
+ Coldfire processors (M520x, M523x, M5249, M5271, M5275, M528x,
+ M5307, M532x, M5407).
+
+ This driver can be built as a module. If so, the module
+ will be called i2c-coldfire.
+
config I2C_CPM
tristate "Freescale CPM1 or CPM2 (MPC8xx/826x)"
depends on (CPM1 || CPM2) && OF_I2C
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 302c551..0185333 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -31,6 +31,7 @@ obj-$(CONFIG_I2C_POWERMAC) += i2c-powermac.o
obj-$(CONFIG_I2C_AT91) += i2c-at91.o
obj-$(CONFIG_I2C_AU1550) += i2c-au1550.o
obj-$(CONFIG_I2C_BLACKFIN_TWI) += i2c-bfin-twi.o
+obj-$(CONFIG_I2C_COLDFIRE) += i2c-coldfire.o
obj-$(CONFIG_I2C_CPM) += i2c-cpm.o
obj-$(CONFIG_I2C_DAVINCI) += i2c-davinci.o
obj-$(CONFIG_I2C_DESIGNWARE) += i2c-designware.o
diff --git a/drivers/i2c/busses/i2c-coldfire.c b/drivers/i2c/busses/i2c-coldfire.c
new file mode 100644
index 0000000..27f0d0b
--- /dev/null
+++ b/drivers/i2c/busses/i2c-coldfire.c
@@ -0,0 +1,463 @@
+/* Freescale/Motorola Coldfire I2C driver.
+ *
+ * Copyright 2010 Steven King <[email protected]>
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/errno.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+
+#include <asm/mcfi2c.h>
+
+#define DRIVER_NAME "mcfi2c"
+
+#define MCFI2C_ADR 0x00
+#define MCFI2C_FDR 0x04
+#define MCFI2C_CR 0x08
+#define MCFI2C_CR_IEN 0x80
+#define MCFI2C_CR_IIEN 0x40
+#define MCFI2C_CR_MSTA 0x20
+#define MCFI2C_CR_MTX 0x10
+#define MCFI2C_CR_TXAK 0x08
+#define MCFI2C_CR_RSTA 0x04
+#define MCFI2C_DR 0x10
+#define MCFI2C_SR 0x0C
+#define MCFI2C_SR_ICF 0x80
+#define MCFI2C_SR_IAAS 0x40
+#define MCFI2C_SR_IBB 0x20
+#define MCFI2C_SR_IAL 0x10
+#define MCFI2C_SR_SRW 0x04
+#define MCFI2C_SR_IIF 0x02
+#define MCFI2C_SR_RXAK 0x01
+
+#define DEFAULT_I2C_BUS_SPEED 100000
+
+struct mcfi2c {
+ struct i2c_adapter adapter;
+ void __iomem *iobase;
+ int irq;
+ struct clk *clk;
+ struct completion completion;
+};
+
+static u8 mcfi2c_rd_cr(struct mcfi2c *mcfi2c)
+{
+ return readb(mcfi2c->iobase + MCFI2C_CR);
+}
+
+static void mcfi2c_wr_cr(struct mcfi2c *mcfi2c, u8 val)
+{
+ writeb(val, mcfi2c->iobase + MCFI2C_CR);
+}
+
+static u8 mcfi2c_rd_sr(struct mcfi2c *mcfi2c)
+{
+ return readb(mcfi2c->iobase + MCFI2C_SR);
+}
+
+static void mcfi2c_wr_sr(struct mcfi2c *mcfi2c, u8 val)
+{
+ writeb(val, mcfi2c->iobase + MCFI2C_SR);
+}
+
+static u8 mcfi2c_rd_dr(struct mcfi2c *mcfi2c)
+{
+ return readb(mcfi2c->iobase + MCFI2C_DR);
+}
+
+static void mcfi2c_wr_dr(struct mcfi2c *mcfi2c, u8 val)
+{
+ writeb(val, mcfi2c->iobase + MCFI2C_DR);
+}
+
+static void mcfi2c_start(struct mcfi2c *mcfi2c)
+{
+ mcfi2c_wr_cr(mcfi2c, MCFI2C_CR_IEN | MCFI2C_CR_IIEN | MCFI2C_CR_MSTA |
+ MCFI2C_CR_MTX);
+}
+
+static void mcfi2c_repeat_start(struct mcfi2c *mcfi2c)
+{
+ mcfi2c_wr_cr(mcfi2c, MCFI2C_CR_IEN | MCFI2C_CR_IIEN | MCFI2C_CR_MSTA |
+ MCFI2C_CR_MTX | MCFI2C_CR_RSTA);
+}
+
+static void mcfi2c_stop(struct mcfi2c *mcfi2c)
+{
+ mcfi2c_wr_cr(mcfi2c, MCFI2C_CR_IEN);
+}
+
+static void mcfi2c_tx_ack(struct mcfi2c *mcfi2c)
+{
+ mcfi2c_wr_cr(mcfi2c, MCFI2C_CR_IEN | MCFI2C_CR_IIEN | MCFI2C_CR_MSTA);
+}
+
+static void mcfi2c_tx_nak(struct mcfi2c *mcfi2c)
+{
+ mcfi2c_wr_cr(mcfi2c, MCFI2C_CR_IEN | MCFI2C_CR_IIEN | MCFI2C_CR_MSTA |
+ MCFI2C_CR_TXAK);
+}
+
+static irqreturn_t mcfi2c_irq_handler(int this_irq, void *dev_id)
+{
+ struct mcfi2c *mcfi2c = dev_id;
+
+ /* clear interrupt */
+ mcfi2c_wr_sr(mcfi2c, 0);
+ complete(&mcfi2c->completion);
+
+ return IRQ_HANDLED;
+}
+
+static void mcfi2c_reset(struct mcfi2c *mcfi2c)
+{
+ mcfi2c_wr_cr(mcfi2c, 0);
+ mcfi2c_wr_cr(mcfi2c, MCFI2C_CR_IEN | MCFI2C_CR_MSTA);
+ mcfi2c_rd_dr(mcfi2c);
+ mcfi2c_wr_sr(mcfi2c, 0);
+ mcfi2c_wr_cr(mcfi2c, 0);
+ mcfi2c_wr_cr(mcfi2c, MCFI2C_CR_IEN);
+}
+
+static void mcfi2c_wait_for_bus_idle(struct mcfi2c *mcfi2c)
+{
+ if (mcfi2c_rd_sr(mcfi2c) & MCFI2C_SR_IBB) {
+ unsigned long timeout = jiffies + HZ / 2;
+ do {
+ cond_resched();
+ if (time_after(jiffies, timeout)) {
+ mcfi2c_reset(mcfi2c);
+ break;
+ }
+ } while (mcfi2c_rd_sr(mcfi2c) & MCFI2C_SR_IBB);
+ }
+}
+
+static int mcfi2c_wait_for_bus_busy(struct mcfi2c *mcfi2c)
+{
+ u8 sr;
+ while (!((sr = mcfi2c_rd_sr(mcfi2c)) & MCFI2C_SR_IBB))
+ if (sr & MCFI2C_SR_IAL) {
+ mcfi2c_reset(mcfi2c);
+ return -EIO;
+ }
+ return 0;
+}
+
+static int mcfi2c_xmit(struct mcfi2c *mcfi2c, u16 addr, u16 flags, u8 *buf,
+ u16 len, int timeout, int more)
+{
+ if (!(mcfi2c_rd_cr(mcfi2c) & MCFI2C_CR_MSTA)) {
+ mcfi2c_wait_for_bus_idle(mcfi2c);
+
+ INIT_COMPLETION(mcfi2c->completion);
+ mcfi2c_start(mcfi2c);
+
+ if (mcfi2c_wait_for_bus_busy(mcfi2c))
+ return -EIO;
+ }
+
+ mcfi2c_wr_dr(mcfi2c, (addr << 1) | (flags & I2C_M_RD));
+ while (wait_for_completion_timeout(&mcfi2c->completion, timeout)) {
+ u8 sr = mcfi2c_rd_sr(mcfi2c);
+ if (sr & MCFI2C_SR_IAL) {
+ mcfi2c_wr_sr(mcfi2c, ~MCFI2C_SR_IAL);
+ return -EIO;
+ } else if (mcfi2c_rd_cr(mcfi2c) & MCFI2C_CR_MTX) {
+ if (sr & MCFI2C_SR_RXAK) {
+ mcfi2c_stop(mcfi2c);
+ return -EIO;
+ } else if (flags & I2C_M_RD) {
+ if (len > 1)
+ mcfi2c_tx_ack(mcfi2c);
+ else
+ mcfi2c_tx_nak(mcfi2c);
+ /* dummy read */
+ mcfi2c_rd_dr(mcfi2c);
+ } else if (len--) {
+ mcfi2c_wr_dr(mcfi2c, *buf++);
+ } else {
+ if (more)
+ mcfi2c_repeat_start(mcfi2c);
+ else
+ mcfi2c_stop(mcfi2c);
+ return 0;
+ }
+ } else if (--len) {
+ if (!(len > 1))
+ mcfi2c_tx_nak(mcfi2c);
+ *buf++ = mcfi2c_rd_dr(mcfi2c);
+ } else {
+ if (more)
+ mcfi2c_repeat_start(mcfi2c);
+ else
+ mcfi2c_stop(mcfi2c);
+ *buf++ = mcfi2c_rd_dr(mcfi2c);
+ return 0;
+ }
+ }
+ mcfi2c_stop(mcfi2c);
+
+ return -ETIMEDOUT;
+}
+
+static int mcfi2c_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs,
+ int num)
+{
+ struct mcfi2c *mcfi2c = i2c_get_adapdata(adapter);
+ int cnt = 0;
+ int status;
+ int retries;
+
+ while (num--) {
+ retries = adapter->retries;
+ if (msgs->flags & ~I2C_M_RD)
+ return -EINVAL;
+ do {
+ status = mcfi2c_xmit(mcfi2c, msgs->addr, msgs->flags,
+ msgs->buf, msgs->len,
+ adapter->timeout, num);
+ } while (status && retries--);
+ if (status)
+ return status;
+ ++cnt;
+ ++msgs;
+ }
+
+ return cnt;
+}
+
+static u32 mcfi2c_func(struct i2c_adapter *adapter)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm mcfi2c_algo = {
+ .master_xfer = mcfi2c_xfer,
+ .functionality = mcfi2c_func,
+};
+
+static const u16 mcfi2c_fdr[] = {
+ 28, 30, 34, 40, 44, 48, 56, 68,
+ 80, 88, 104, 128, 144, 160, 192, 240,
+ 288, 320, 384, 480, 576, 640, 768, 960,
+ 1152, 1280, 1536, 1920, 2304, 2560, 3072, 3840,
+ 20, 22, 24, 26, 28, 32, 36, 40,
+ 48, 56, 64, 72, 80, 96, 112, 128,
+ 160, 192, 224, 256, 320, 384, 448, 512,
+ 640, 768, 896, 1024, 1280, 1536, 1792, 2048
+};
+
+static u8 __devinit mcfi2c_calc_fdr(struct mcfi2c *mcfi2c,
+ struct mcfi2c_platform_data *pdata)
+{
+ u32 bitrate = (pdata && pdata->bitrate) ?
+ pdata->bitrate : DEFAULT_I2C_BUS_SPEED;
+ int div = clk_get_rate(mcfi2c->clk)/bitrate;
+ int r = 0, i = 0;
+
+ do
+ if (abs(mcfi2c_fdr[i] - div) < abs(mcfi2c_fdr[r] - div))
+ r = i;
+ while (++i < ARRAY_SIZE(mcfi2c_fdr));
+
+ return r;
+}
+
+static int __devinit mcfi2c_probe(struct platform_device *pdev)
+{
+ struct mcfi2c *mcfi2c;
+ struct resource *res;
+ int status;
+
+ mcfi2c = kzalloc(sizeof(*mcfi2c), GFP_KERNEL);
+ if (!mcfi2c) {
+ dev_dbg(&pdev->dev, "kzalloc failed\n");
+
+ return -ENOMEM;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_dbg(&pdev->dev, "platform_get_resource failed\n");
+ status = -ENXIO;
+ goto fail0;
+ }
+
+ if (!request_mem_region(res->start, resource_size(res), pdev->name)) {
+ dev_dbg(&pdev->dev, "request_mem_region failed\n");
+ status = -EBUSY;
+ goto fail0;
+ }
+
+ mcfi2c->iobase = ioremap(res->start, resource_size(res));
+ if (!mcfi2c->iobase) {
+ dev_dbg(&pdev->dev, "ioremap failed\n");
+ status = -ENOMEM;
+ goto fail1;
+ }
+
+ mcfi2c->irq = platform_get_irq(pdev, 0);
+ if (mcfi2c->irq < 0) {
+ dev_dbg(&pdev->dev, "platform_get_irq failed\n");
+ status = -ENXIO;
+ goto fail2;
+ }
+ status = request_irq(mcfi2c->irq, mcfi2c_irq_handler, IRQF_DISABLED,
+ pdev->name, mcfi2c);
+ if (status) {
+ dev_dbg(&pdev->dev, "request_irq failed\n");
+ goto fail2;
+ }
+
+ mcfi2c->clk = clk_get(&pdev->dev, "i2c_clk");
+ if (IS_ERR(mcfi2c->clk)) {
+ dev_dbg(&pdev->dev, "clk_get failed\n");
+ status = PTR_ERR(mcfi2c->clk);
+ goto fail3;
+ }
+ clk_enable(mcfi2c->clk);
+
+ platform_set_drvdata(pdev, mcfi2c);
+
+ init_completion(&mcfi2c->completion);
+
+ writeb(mcfi2c_calc_fdr(mcfi2c, pdev->dev.platform_data),
+ mcfi2c->iobase + MCFI2C_FDR);
+
+ writeb(0x00, mcfi2c->iobase + MCFI2C_ADR);
+
+ mcfi2c_wr_cr(mcfi2c, MCFI2C_CR_IEN);
+
+ /* if the bus busy (IBB) is set, reset the controller */
+ if (mcfi2c_rd_sr(mcfi2c) & MCFI2C_SR_IBB)
+ mcfi2c_reset(mcfi2c);
+
+ mcfi2c->adapter.algo = &mcfi2c_algo;
+ mcfi2c->adapter.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
+ mcfi2c->adapter.dev.parent = &pdev->dev;
+ mcfi2c->adapter.nr = pdev->id;
+ mcfi2c->adapter.retries = 2;
+ snprintf(mcfi2c->adapter.name, sizeof(mcfi2c->adapter.name),
+ DRIVER_NAME ".%d", pdev->id);
+
+ i2c_set_adapdata(&mcfi2c->adapter, mcfi2c);
+
+ status = i2c_add_numbered_adapter(&mcfi2c->adapter);
+ if (status < 0) {
+ dev_dbg(&pdev->dev, "i2c_add_numbered_adapter failed\n");
+ goto fail4;
+ }
+ dev_info(&pdev->dev, "Coldfire I2C bus driver\n");
+
+ return 0;
+
+fail4:
+ clk_disable(mcfi2c->clk);
+ clk_put(mcfi2c->clk);
+fail3:
+ free_irq(mcfi2c->irq, mcfi2c);
+fail2:
+ iounmap(mcfi2c->iobase);
+fail1:
+ release_mem_region(res->start, resource_size(res));
+fail0:
+ kfree(mcfi2c);
+
+ return status;
+}
+
+static int __devexit mcfi2c_remove(struct platform_device *pdev)
+{
+ struct mcfi2c *mcfi2c = platform_get_drvdata(pdev);
+ struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+ /* disable the hardware */
+ mcfi2c_wr_cr(mcfi2c, 0);
+
+ platform_set_drvdata(pdev, NULL);
+ i2c_del_adapter(&mcfi2c->adapter);
+ clk_disable(mcfi2c->clk);
+ clk_put(mcfi2c->clk);
+ free_irq(mcfi2c->irq, mcfi2c);
+ iounmap(mcfi2c->iobase);
+ release_mem_region(res->start, resource_size(res));
+ kfree(mcfi2c);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int mcfi2c_suspend(struct device *dev)
+{
+ struct mcfi2c *mcfi2c = platform_get_drvdata(to_platform_device(dev));
+
+ mcfi2c_wr_cr(mcfi2c, 0);
+ clk_disable(mcfi2c->clk);
+
+ return 0;
+}
+
+static int mcfi2c_resume(struct device *dev)
+{
+ struct mcfi2c *mcfi2c = platform_get_drvdata(to_platform_device(dev));
+
+ clk_enable(mcfi2c->clk);
+ mcfi2c_wr_cr(mcfi2c, MCFI2C_CR_IEN);
+
+ return 0;
+}
+
+static struct dev_pm_ops mcfi2c_dev_pm_ops = {
+ .suspend = mcfi2c_suspend,
+ .resume = mcfi2c_resume,
+};
+
+#define MCFI2C_DEV_PM_OPS (&mcfi2c_dev_pm_ops)
+#else
+#define MCFI2C_DEV_PM_OPS NULL
+#endif
+
+static struct platform_driver mcfi2c_driver = {
+ .driver.name = DRIVER_NAME,
+ .driver.owner = THIS_MODULE,
+ .driver.pm = MCFI2C_DEV_PM_OPS,
+ .remove = __devexit_p(mcfi2c_remove),
+};
+
+static int __init mcfi2c_init(void)
+{
+ return platform_driver_probe(&mcfi2c_driver, mcfi2c_probe);
+}
+module_init(mcfi2c_init);
+
+static void __exit mcfi2c_exit(void)
+{
+ platform_driver_unregister(&mcfi2c_driver);
+}
+module_exit(mcfi2c_exit);
+
+MODULE_AUTHOR("Steven King <[email protected]>");
+MODULE_DESCRIPTION("I2C-Bus support for Freescale Coldfire processors");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRIVER_NAME);

2010-01-24 15:15:41

by Ben Dooks

[permalink] [raw]
Subject: Re: [PATCH v2] m68knommu: driver for Freescale Coldfire I2C controller.

On Mon, Jan 11, 2010 at 10:24:05AM -0800, Steven King wrote:
> Changes for this version:
> rename drivers/i2c/busses/i2c-mcf.c to drivers/i2c/busses/i2c-coldfire.c
> use I2C_COLDFIRE in drivers/i2c/busses/{Kconfig,Makefile}
>
> ------
>
> Add support for the I2C controller used on Freescale/Motorola Coldfire MCUs.
>
> Signed-off-by: Steven King <[email protected]>

The commit messsage should go first, the changelog and other stuff
that won't go in should go beflore the --- line.

> drivers/i2c/busses/Kconfig | 11 +
> drivers/i2c/busses/Makefile | 1 +
> drivers/i2c/busses/i2c-coldfire.c | 463 +++++++++++++++++++++++++++++++++++++
> 3 files changed, 475 insertions(+), 0 deletions(-)
>
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index 5f318ce..83c2904 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -320,6 +320,17 @@ config I2C_BLACKFIN_TWI_CLK_KHZ
> help
> The unit of the TWI clock is kHz.
>
> +config I2C_COLDFIRE
> + tristate "Freescale Coldfire I2C driver"
> + depends on (M5206 || M5206e || M520x || M523x || M5249 || M527x || M528x || M5307 || M532x || M5407)
> + help
> + This driver supports the I2C interface available on some Freescale
> + Coldfire processors (M520x, M523x, M5249, M5271, M5275, M528x,
> + M5307, M532x, M5407).
> +
> + This driver can be built as a module. If so, the module
> + will be called i2c-coldfire.
> +
> config I2C_CPM
> tristate "Freescale CPM1 or CPM2 (MPC8xx/826x)"
> depends on (CPM1 || CPM2) && OF_I2C
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index 302c551..0185333 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -31,6 +31,7 @@ obj-$(CONFIG_I2C_POWERMAC) += i2c-powermac.o
> obj-$(CONFIG_I2C_AT91) += i2c-at91.o
> obj-$(CONFIG_I2C_AU1550) += i2c-au1550.o
> obj-$(CONFIG_I2C_BLACKFIN_TWI) += i2c-bfin-twi.o
> +obj-$(CONFIG_I2C_COLDFIRE) += i2c-coldfire.o
> obj-$(CONFIG_I2C_CPM) += i2c-cpm.o
> obj-$(CONFIG_I2C_DAVINCI) += i2c-davinci.o
> obj-$(CONFIG_I2C_DESIGNWARE) += i2c-designware.o
> diff --git a/drivers/i2c/busses/i2c-coldfire.c b/drivers/i2c/busses/i2c-coldfire.c
> new file mode 100644
> index 0000000..27f0d0b
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-coldfire.c
> @@ -0,0 +1,463 @@
> +/* Freescale/Motorola Coldfire I2C driver.
> + *
> + * Copyright 2010 Steven King <[email protected]>
> + *
> + * 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.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/interrupt.h>
> +#include <linux/errno.h>
> +#include <linux/platform_device.h>
> +#include <linux/io.h>
> +#include <linux/clk.h>
> +#include <linux/err.h>
> +#include <linux/i2c.h>
> +
> +#include <asm/mcfi2c.h>
> +
> +#define DRIVER_NAME "mcfi2c"
> +
> +#define MCFI2C_ADR 0x00
> +#define MCFI2C_FDR 0x04
> +#define MCFI2C_CR 0x08
> +#define MCFI2C_CR_IEN 0x80
> +#define MCFI2C_CR_IIEN 0x40
> +#define MCFI2C_CR_MSTA 0x20
> +#define MCFI2C_CR_MTX 0x10
> +#define MCFI2C_CR_TXAK 0x08
> +#define MCFI2C_CR_RSTA 0x04
> +#define MCFI2C_DR 0x10
> +#define MCFI2C_SR 0x0C
> +#define MCFI2C_SR_ICF 0x80
> +#define MCFI2C_SR_IAAS 0x40
> +#define MCFI2C_SR_IBB 0x20
> +#define MCFI2C_SR_IAL 0x10
> +#define MCFI2C_SR_SRW 0x04
> +#define MCFI2C_SR_IIF 0x02
> +#define MCFI2C_SR_RXAK 0x01
> +
> +#define DEFAULT_I2C_BUS_SPEED 100000
> +
> +struct mcfi2c {
> + struct i2c_adapter adapter;
> + void __iomem *iobase;
> + int irq;
> + struct clk *clk;
> + struct completion completion;
> +};
> +
> +static u8 mcfi2c_rd_cr(struct mcfi2c *mcfi2c)
> +{
> + return readb(mcfi2c->iobase + MCFI2C_CR);
> +}
> +
> +static void mcfi2c_wr_cr(struct mcfi2c *mcfi2c, u8 val)
> +{
> + writeb(val, mcfi2c->iobase + MCFI2C_CR);
> +}
> +
> +static u8 mcfi2c_rd_sr(struct mcfi2c *mcfi2c)
> +{
> + return readb(mcfi2c->iobase + MCFI2C_SR);
> +}
> +
> +static void mcfi2c_wr_sr(struct mcfi2c *mcfi2c, u8 val)
> +{
> + writeb(val, mcfi2c->iobase + MCFI2C_SR);
> +}
> +
> +static u8 mcfi2c_rd_dr(struct mcfi2c *mcfi2c)
> +{
> + return readb(mcfi2c->iobase + MCFI2C_DR);
> +}
> +
> +static void mcfi2c_wr_dr(struct mcfi2c *mcfi2c, u8 val)
> +{
> + writeb(val, mcfi2c->iobase + MCFI2C_DR);
> +}

not entirely sure why you bother wrapping these accesses, but I'm not
going to block this driver just becuase I don't like it.

> +static void mcfi2c_start(struct mcfi2c *mcfi2c)
> +{
> + mcfi2c_wr_cr(mcfi2c, MCFI2C_CR_IEN | MCFI2C_CR_IIEN | MCFI2C_CR_MSTA |
> + MCFI2C_CR_MTX);
> +}
> +
> +static void mcfi2c_repeat_start(struct mcfi2c *mcfi2c)
> +{
> + mcfi2c_wr_cr(mcfi2c, MCFI2C_CR_IEN | MCFI2C_CR_IIEN | MCFI2C_CR_MSTA |
> + MCFI2C_CR_MTX | MCFI2C_CR_RSTA);
> +}
> +
> +static void mcfi2c_stop(struct mcfi2c *mcfi2c)
> +{
> + mcfi2c_wr_cr(mcfi2c, MCFI2C_CR_IEN);
> +}
> +
> +static void mcfi2c_tx_ack(struct mcfi2c *mcfi2c)
> +{
> + mcfi2c_wr_cr(mcfi2c, MCFI2C_CR_IEN | MCFI2C_CR_IIEN | MCFI2C_CR_MSTA);
> +}
> +
> +static void mcfi2c_tx_nak(struct mcfi2c *mcfi2c)
> +{
> + mcfi2c_wr_cr(mcfi2c, MCFI2C_CR_IEN | MCFI2C_CR_IIEN | MCFI2C_CR_MSTA |
> + MCFI2C_CR_TXAK);
> +}
> +
> +static irqreturn_t mcfi2c_irq_handler(int this_irq, void *dev_id)
> +{
> + struct mcfi2c *mcfi2c = dev_id;
> +
> + /* clear interrupt */
> + mcfi2c_wr_sr(mcfi2c, 0);
> + complete(&mcfi2c->completion);
> +
> + return IRQ_HANDLED;
> +}

I'm interested in why you don't just handle the interrupt here and
wake the thread once all the data is handled?

> +static void mcfi2c_reset(struct mcfi2c *mcfi2c)
> +{
> + mcfi2c_wr_cr(mcfi2c, 0);
> + mcfi2c_wr_cr(mcfi2c, MCFI2C_CR_IEN | MCFI2C_CR_MSTA);
> + mcfi2c_rd_dr(mcfi2c);
> + mcfi2c_wr_sr(mcfi2c, 0);
> + mcfi2c_wr_cr(mcfi2c, 0);
> + mcfi2c_wr_cr(mcfi2c, MCFI2C_CR_IEN);
> +}
> +
> +static void mcfi2c_wait_for_bus_idle(struct mcfi2c *mcfi2c)
> +{
> + if (mcfi2c_rd_sr(mcfi2c) & MCFI2C_SR_IBB) {
> + unsigned long timeout = jiffies + HZ / 2;
> + do {
> + cond_resched();
> + if (time_after(jiffies, timeout)) {
> + mcfi2c_reset(mcfi2c);
> + break;
> + }
> + } while (mcfi2c_rd_sr(mcfi2c) & MCFI2C_SR_IBB);
> + }
> +}
> +
> +static int mcfi2c_wait_for_bus_busy(struct mcfi2c *mcfi2c)
> +{
> + u8 sr;
> + while (!((sr = mcfi2c_rd_sr(mcfi2c)) & MCFI2C_SR_IBB))
> + if (sr & MCFI2C_SR_IAL) {
> + mcfi2c_reset(mcfi2c);
> + return -EIO;
> + }
> + return 0;
> +}
> +
> +static int mcfi2c_xmit(struct mcfi2c *mcfi2c, u16 addr, u16 flags, u8 *buf,
> + u16 len, int timeout, int more)
> +{
> + if (!(mcfi2c_rd_cr(mcfi2c) & MCFI2C_CR_MSTA)) {
> + mcfi2c_wait_for_bus_idle(mcfi2c);
> +
> + INIT_COMPLETION(mcfi2c->completion);
> + mcfi2c_start(mcfi2c);
> +
> + if (mcfi2c_wait_for_bus_busy(mcfi2c))
> + return -EIO;
> + }
> +
> + mcfi2c_wr_dr(mcfi2c, (addr << 1) | (flags & I2C_M_RD));
> + while (wait_for_completion_timeout(&mcfi2c->completion, timeout)) {
> + u8 sr = mcfi2c_rd_sr(mcfi2c);
> + if (sr & MCFI2C_SR_IAL) {
> + mcfi2c_wr_sr(mcfi2c, ~MCFI2C_SR_IAL);
> + return -EIO;
> + } else if (mcfi2c_rd_cr(mcfi2c) & MCFI2C_CR_MTX) {
> + if (sr & MCFI2C_SR_RXAK) {
> + mcfi2c_stop(mcfi2c);
> + return -EIO;
> + } else if (flags & I2C_M_RD) {
> + if (len > 1)
> + mcfi2c_tx_ack(mcfi2c);
> + else
> + mcfi2c_tx_nak(mcfi2c);
> + /* dummy read */
> + mcfi2c_rd_dr(mcfi2c);
> + } else if (len--) {
> + mcfi2c_wr_dr(mcfi2c, *buf++);
> + } else {
> + if (more)
> + mcfi2c_repeat_start(mcfi2c);
> + else
> + mcfi2c_stop(mcfi2c);
> + return 0;
> + }
> + } else if (--len) {
> + if (!(len > 1))
> + mcfi2c_tx_nak(mcfi2c);
> + *buf++ = mcfi2c_rd_dr(mcfi2c);
> + } else {
> + if (more)
> + mcfi2c_repeat_start(mcfi2c);
> + else
> + mcfi2c_stop(mcfi2c);
> + *buf++ = mcfi2c_rd_dr(mcfi2c);
> + return 0;
> + }
> + }
> + mcfi2c_stop(mcfi2c);
> +
> + return -ETIMEDOUT;
> +}
> +
> +static int mcfi2c_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs,
> + int num)
> +{
> + struct mcfi2c *mcfi2c = i2c_get_adapdata(adapter);
> + int cnt = 0;
> + int status;
> + int retries;
> +
> + while (num--) {
> + retries = adapter->retries;
> + if (msgs->flags & ~I2C_M_RD)
> + return -EINVAL;
> + do {
> + status = mcfi2c_xmit(mcfi2c, msgs->addr, msgs->flags,
> + msgs->buf, msgs->len,
> + adapter->timeout, num);
> + } while (status && retries--);
> + if (status)
> + return status;
> + ++cnt;
> + ++msgs;
> + }
> +
> + return cnt;
> +}
> +
> +static u32 mcfi2c_func(struct i2c_adapter *adapter)
> +{
> + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
> +}
> +
> +static const struct i2c_algorithm mcfi2c_algo = {
> + .master_xfer = mcfi2c_xfer,
> + .functionality = mcfi2c_func,
> +};
> +
> +static const u16 mcfi2c_fdr[] = {
> + 28, 30, 34, 40, 44, 48, 56, 68,
> + 80, 88, 104, 128, 144, 160, 192, 240,
> + 288, 320, 384, 480, 576, 640, 768, 960,
> + 1152, 1280, 1536, 1920, 2304, 2560, 3072, 3840,
> + 20, 22, 24, 26, 28, 32, 36, 40,
> + 48, 56, 64, 72, 80, 96, 112, 128,
> + 160, 192, 224, 256, 320, 384, 448, 512,
> + 640, 768, 896, 1024, 1280, 1536, 1792, 2048
> +};
> +
> +static u8 __devinit mcfi2c_calc_fdr(struct mcfi2c *mcfi2c,
> + struct mcfi2c_platform_data *pdata)
> +{
> + u32 bitrate = (pdata && pdata->bitrate) ?
> + pdata->bitrate : DEFAULT_I2C_BUS_SPEED;
> + int div = clk_get_rate(mcfi2c->clk)/bitrate;
> + int r = 0, i = 0;
> +
> + do
> + if (abs(mcfi2c_fdr[i] - div) < abs(mcfi2c_fdr[r] - div))
> + r = i;
> + while (++i < ARRAY_SIZE(mcfi2c_fdr));
> +
> + return r;
> +}
> +
> +static int __devinit mcfi2c_probe(struct platform_device *pdev)
> +{
> + struct mcfi2c *mcfi2c;
> + struct resource *res;
> + int status;
> +
> + mcfi2c = kzalloc(sizeof(*mcfi2c), GFP_KERNEL);
> + if (!mcfi2c) {
> + dev_dbg(&pdev->dev, "kzalloc failed\n");
> +
> + return -ENOMEM;
> + }
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (!res) {
> + dev_dbg(&pdev->dev, "platform_get_resource failed\n");
> + status = -ENXIO;
> + goto fail0;
> + }
> +
> + if (!request_mem_region(res->start, resource_size(res), pdev->name)) {
> + dev_dbg(&pdev->dev, "request_mem_region failed\n");
> + status = -EBUSY;
> + goto fail0;
> + }
> +
> + mcfi2c->iobase = ioremap(res->start, resource_size(res));
> + if (!mcfi2c->iobase) {
> + dev_dbg(&pdev->dev, "ioremap failed\n");
> + status = -ENOMEM;
> + goto fail1;
> + }
> +
> + mcfi2c->irq = platform_get_irq(pdev, 0);
> + if (mcfi2c->irq < 0) {
> + dev_dbg(&pdev->dev, "platform_get_irq failed\n");
> + status = -ENXIO;
> + goto fail2;
> + }
> + status = request_irq(mcfi2c->irq, mcfi2c_irq_handler, IRQF_DISABLED,
> + pdev->name, mcfi2c);

do you really need IRQF_DISABLED here? your irq handler hardly does
anything.

> + if (status) {
> + dev_dbg(&pdev->dev, "request_irq failed\n");
> + goto fail2;
> + }
> +
> + mcfi2c->clk = clk_get(&pdev->dev, "i2c_clk");

hmm, think the default device clock should be findable by clk_get(dev,
NULL).

> + if (IS_ERR(mcfi2c->clk)) {
> + dev_dbg(&pdev->dev, "clk_get failed\n");
> + status = PTR_ERR(mcfi2c->clk);
> + goto fail3;
> + }
> + clk_enable(mcfi2c->clk);
> +
> + platform_set_drvdata(pdev, mcfi2c);
> +
> + init_completion(&mcfi2c->completion);
> +
> + writeb(mcfi2c_calc_fdr(mcfi2c, pdev->dev.platform_data),
> + mcfi2c->iobase + MCFI2C_FDR);
> +
> + writeb(0x00, mcfi2c->iobase + MCFI2C_ADR);
> +
> + mcfi2c_wr_cr(mcfi2c, MCFI2C_CR_IEN);
> +
> + /* if the bus busy (IBB) is set, reset the controller */
> + if (mcfi2c_rd_sr(mcfi2c) & MCFI2C_SR_IBB)
> + mcfi2c_reset(mcfi2c);
> +
> + mcfi2c->adapter.algo = &mcfi2c_algo;
> + mcfi2c->adapter.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
> + mcfi2c->adapter.dev.parent = &pdev->dev;
> + mcfi2c->adapter.nr = pdev->id;
> + mcfi2c->adapter.retries = 2;
> + snprintf(mcfi2c->adapter.name, sizeof(mcfi2c->adapter.name),
> + DRIVER_NAME ".%d", pdev->id);
> +
> + i2c_set_adapdata(&mcfi2c->adapter, mcfi2c);
> +
> + status = i2c_add_numbered_adapter(&mcfi2c->adapter);
> + if (status < 0) {
> + dev_dbg(&pdev->dev, "i2c_add_numbered_adapter failed\n");
> + goto fail4;
> + }
> + dev_info(&pdev->dev, "Coldfire I2C bus driver\n");
> +
> + return 0;
> +
> +fail4:
> + clk_disable(mcfi2c->clk);
> + clk_put(mcfi2c->clk);
> +fail3:
> + free_irq(mcfi2c->irq, mcfi2c);
> +fail2:
> + iounmap(mcfi2c->iobase);
> +fail1:
> + release_mem_region(res->start, resource_size(res));
> +fail0:
> + kfree(mcfi2c);
> +
> + return status;
> +}
> +
> +static int __devexit mcfi2c_remove(struct platform_device *pdev)
> +{
> + struct mcfi2c *mcfi2c = platform_get_drvdata(pdev);
> + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +
> + /* disable the hardware */
> + mcfi2c_wr_cr(mcfi2c, 0);
> +
> + platform_set_drvdata(pdev, NULL);
> + i2c_del_adapter(&mcfi2c->adapter);
> + clk_disable(mcfi2c->clk);
> + clk_put(mcfi2c->clk);
> + free_irq(mcfi2c->irq, mcfi2c);
> + iounmap(mcfi2c->iobase);
> + release_mem_region(res->start, resource_size(res));
> + kfree(mcfi2c);
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int mcfi2c_suspend(struct device *dev)
> +{
> + struct mcfi2c *mcfi2c = platform_get_drvdata(to_platform_device(dev));
> +
> + mcfi2c_wr_cr(mcfi2c, 0);
> + clk_disable(mcfi2c->clk);
> +
> + return 0;
> +}
> +
> +static int mcfi2c_resume(struct device *dev)
> +{
> + struct mcfi2c *mcfi2c = platform_get_drvdata(to_platform_device(dev));
> +
> + clk_enable(mcfi2c->clk);
> + mcfi2c_wr_cr(mcfi2c, MCFI2C_CR_IEN);
> +
> + return 0;
> +}
> +
> +static struct dev_pm_ops mcfi2c_dev_pm_ops = {
> + .suspend = mcfi2c_suspend,
> + .resume = mcfi2c_resume,
> +};
> +
> +#define MCFI2C_DEV_PM_OPS (&mcfi2c_dev_pm_ops)
> +#else
> +#define MCFI2C_DEV_PM_OPS NULL
> +#endif
> +
> +static struct platform_driver mcfi2c_driver = {
> + .driver.name = DRIVER_NAME,
> + .driver.owner = THIS_MODULE,
> + .driver.pm = MCFI2C_DEV_PM_OPS,
> + .remove = __devexit_p(mcfi2c_remove),
> +};
> +
> +static int __init mcfi2c_init(void)
> +{
> + return platform_driver_probe(&mcfi2c_driver, mcfi2c_probe);
> +}
> +module_init(mcfi2c_init);
> +
> +static void __exit mcfi2c_exit(void)
> +{
> + platform_driver_unregister(&mcfi2c_driver);
> +}
> +module_exit(mcfi2c_exit);
> +
> +MODULE_AUTHOR("Steven King <[email protected]>");
> +MODULE_DESCRIPTION("I2C-Bus support for Freescale Coldfire processors");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:" DRIVER_NAME);


--
Ben ([email protected], http://www.fluff.org/)

'a smiley only costs 4 bytes'

2010-01-25 20:03:15

by Steven King

[permalink] [raw]
Subject: Re: [PATCH v2] m68knommu: driver for Freescale Coldfire I2C controller.

On Sunday 24 January 2010 07:15:19 you wrote:
> On Mon, Jan 11, 2010 at 10:24:05AM -0800, Steven King wrote:
> > Changes for this version:
> > rename drivers/i2c/busses/i2c-mcf.c to drivers/i2c/busses/i2c-coldfire.c
> > use I2C_COLDFIRE in drivers/i2c/busses/{Kconfig,Makefile}
> >
> > ------
> >
> > Add support for the I2C controller used on Freescale/Motorola Coldfire
> > MCUs.
> >
> > Signed-off-by: Steven King <[email protected]>
>
> The commit messsage should go first, the changelog and other stuff
> that won't go in should go beflore the --- line.

My bad, I think I was paying more attention to making sure this mailer didn't
line wrap on me. I can repost if you want.

> > +static irqreturn_t mcfi2c_irq_handler(int this_irq, void *dev_id)
> > +{
> > + struct mcfi2c *mcfi2c = dev_id;
> > +
> > + /* clear interrupt */
> > + mcfi2c_wr_sr(mcfi2c, 0);
> > + complete(&mcfi2c->completion);
> > +
> > + return IRQ_HANDLED;
> > +}
>
> I'm interested in why you don't just handle the interrupt here and
> wake the thread once all the data is handled?

No particular reason; when I started working on this I looked at how some of
the other drivers in drivers/i2c/busses were implemented, found one whose
workings I could understand without knowing anything about that particular
SoC (which one I don't remember) and used it as a model to adapt the i2c code
I use with other Freescale/Motorola MCUs that don't run linux (there is very
little difference in the i2c controller on an 8-bit hc08, 16 bit hc11/12,
32bit Coldfire v1 and the Coldfire v2[+] that are currently supported). So
other than having easy to understand (for me atleast) code that shared a lot
of similarity with the code I use for the some of the other systems I code
for, no real reason. That and I find statefull irq handlers disconcerting.

> > + status = request_irq(mcfi2c->irq, mcfi2c_irq_handler, IRQF_DISABLED,
> > + pdev->name, mcfi2c);
>
> do you really need IRQF_DISABLED here? your irq handler hardly does
> anything.

Yes, without it, I was getting a spurious interrupt (been there, done that).

> > + if (status) {
> > + dev_dbg(&pdev->dev, "request_irq failed\n");
> > + goto fail2;
> > + }
> > +
> > + mcfi2c->clk = clk_get(&pdev->dev, "i2c_clk");
>
> hmm, think the default device clock should be findable by clk_get(dev,
> NULL).

Interesting. Again, I had looked at the existing code in drivers/i2c/busses
to see what the other drivers were doing and most were passing an id string.

Its a trivial change so its no big deal, but I'm curious, absent anything in
Documentation, if the bulk of the existing code isn't documentation for the
correct use of an api, then what is?

--
Steven King -- sfking at fdwdc dot com