[PATCH 2/3] Freescale I2C driver
Renaud Barbier
renaud.barbier at ge.com
Wed Aug 22 11:08:52 EDT 2012
This patch introduces the Freescale I2C driver along with its configuration
and build files.
The header file asm/fsl_i2c.h is updated with register offsets and the
structure mapping the i2c registers is removed.
Signed-off-by: Renaud Barbier <renaud.barbier at ge.com>
---
arch/ppc/include/asm/fsl_i2c.h | 80 ++++------
drivers/i2c/busses/Kconfig | 4 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-fsl.c | 356 ++++++++++++++++++++++++++++++++++++++++
4 files changed, 393 insertions(+), 48 deletions(-)
create mode 100644 drivers/i2c/busses/i2c-fsl.c
diff --git a/arch/ppc/include/asm/fsl_i2c.h b/arch/ppc/include/asm/fsl_i2c.h
index 4f71341..62b077d 100644
--- a/arch/ppc/include/asm/fsl_i2c.h
+++ b/arch/ppc/include/asm/fsl_i2c.h
@@ -1,6 +1,7 @@
/*
* Freescale I2C Controller
*
+ * Copyright 2012 GE Intelligent Platform, Inc.
* Copyright 2006 Freescale Semiconductor, Inc.
*
* Based on earlier versions by Gleb Natapov <gnatapov at mrv.com>,
@@ -32,55 +33,38 @@
#include <asm/types.h>
-typedef struct fsl_i2c {
+#define I2C1_BASE_ADDR (CFG_IMMR + 0x3000)
+#define I2C2_BASE_ADDR (CFG_IMMR + 0x3100)
- u8 adr; /* I2C slave address */
- u8 res0[3];
-#define I2C_ADR 0xFE
-#define I2C_ADR_SHIFT 1
-#define I2C_ADR_RES ~(I2C_ADR)
+#define FSL_I2C_ADR 0x00
+#define FSL_I2C_FDR 0x04
+#define FSL_I2C_CR 0x08
+#define I2C_CR_MEN 0x80
+#define I2C_CR_MIEN 0x40
+#define I2C_CR_MSTA 0x20
+#define I2C_CR_MTX 0x10
+#define I2C_CR_TXAK 0x08
+#define I2C_CR_RSTA 0x04
+#define I2C_CR_BCST 0x01
+#define FSL_I2C_SR 0x0C
+#define I2C_SR_MCF 0x80
+#define I2C_SR_MAAS 0x40
+#define I2C_SR_MBB 0x20
+#define I2C_SR_MAL 0x10
+#define I2C_SR_BCSTM 0x08
+#define I2C_SR_SRW 0x04
+#define I2C_SR_MIF 0x02
+#define I2C_SR_RXAK 0x01
+#define FSL_I2C_DR 0x10
+#define FSL_I2C_DFSRR 0x14
- u8 fdr; /* I2C frequency divider register */
- u8 res1[3];
-#define IC2_FDR 0x3F
-#define IC2_FDR_SHIFT 0
-#define IC2_FDR_RES ~(IC2_FDR)
-
- u8 cr; /* I2C control redister */
- u8 res2[3];
-#define I2C_CR_MEN 0x80
-#define I2C_CR_MIEN 0x40
-#define I2C_CR_MSTA 0x20
-#define I2C_CR_MTX 0x10
-#define I2C_CR_TXAK 0x08
-#define I2C_CR_RSTA 0x04
-#define I2C_CR_BCST 0x01
-
- u8 sr; /* I2C status register */
- u8 res3[3];
-#define I2C_SR_MCF 0x80
-#define I2C_SR_MAAS 0x40
-#define I2C_SR_MBB 0x20
-#define I2C_SR_MAL 0x10
-#define I2C_SR_BCSTM 0x08
-#define I2C_SR_SRW 0x04
-#define I2C_SR_MIF 0x02
-#define I2C_SR_RXAK 0x01
-
- u8 dr; /* I2C data register */
- u8 res4[3];
-#define I2C_DR 0xFF
-#define I2C_DR_SHIFT 0
-#define I2C_DR_RES ~(I2C_DR)
-
- u8 dfsrr; /* I2C digital filter sampling rate register */
- u8 res5[3];
-#define I2C_DFSRR 0x3F
-#define I2C_DFSRR_SHIFT 0
-#define I2C_DFSRR_RES ~(I2C_DR)
-
- /* Fill out the reserved block */
- u8 res6[0xE8];
-} fsl_i2c_t;
+/*
+ * Platform data
+ */
+struct fsl_i2c_plat {
+ int bus_num;
+ int slaveaddr;
+ int speed;
+};
#endif /* _ASM_I2C_H_ */
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 1ce5c00..d8d677a 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -12,4 +12,8 @@ config I2C_OMAP
bool "OMAP I2C Master driver"
depends on ARCH_OMAP
+config I2C_FSL
+ bool "FSL I2C Master driver"
+ depends on ARCH_MPC85XX
+
endmenu
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index e4c5125..dd4b88d 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -1,2 +1,3 @@
obj-$(CONFIG_I2C_IMX) += i2c-imx.o
obj-$(CONFIG_I2C_OMAP) += i2c-omap.o
+obj-$(CONFIG_I2C_FSL) += i2c-fsl.o
diff --git a/drivers/i2c/busses/i2c-fsl.c b/drivers/i2c/busses/i2c-fsl.c
new file mode 100644
index 0000000..17ac7da
--- /dev/null
+++ b/drivers/i2c/busses/i2c-fsl.c
@@ -0,0 +1,356 @@
+/*
+ * Copyright 2012 GE Intelligent Platforms, Inc.
+ * Copyright 2006,2009 Freescale Semiconductor, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * Version 2 as published by the Free Software Foundation.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ *
+ * This I2C driver is derived from U-boot. It different from the original
+ * code as it does byte-write so that page boundary can be crossed.
+ * A version closer to the Linux driver should be developed.
+ */
+
+#include <common.h>
+#include <clock.h>
+#include <config.h>
+#include <driver.h>
+#include <init.h>
+#include <malloc.h>
+#include <command.h>
+#include <i2c/i2c.h>
+#include <linux/err.h>
+#include <asm/io.h>
+#include <asm/fsl_i2c.h>
+#include <mach/clocks.h>
+
+#define I2C_READ_BIT 1
+#define I2C_WRITE_BIT 0
+
+struct fsl_i2c_dev {
+ int bus_num;
+ int slaveaddr;
+ int speed;
+ void __iomem *regs;
+ struct i2c_adapter adapter;
+};
+#define to_fsl_i2c_struct(a) container_of(a, struct fsl_i2c_dev, adapter)
+
+/*
+ * Set the I2C bus speed for a given I2C device
+ *
+ * param dev: the I2C device
+ * i2c_clk: I2C bus clock frequency
+ * speed: the desired speed of the bus
+ *
+ * The I2C device must be stopped before calling this function.
+ *
+ * The return value is the actual bus speed that is set.
+ */
+static unsigned int fsl_set_i2c_bus_speed(const void __iomem *dev,
+ unsigned int i2c_clk, unsigned int speed)
+{
+ unsigned short divider = min((unsigned short)(i2c_clk / speed),
+ (unsigned short) -1);
+
+ /*
+ * We want to choose an FDR/DFSR that generates an I2C bus speed that
+ * is equal to or lower than the requested speed. That means that we
+ * want the first divider that is equal to or greater than the
+ * calculated divider.
+ */
+ u8 dfsr, fdr = 0x31; /* Default if no FDR found */
+ /* a, b and dfsr matches identifiers A,B and C respectively in AN2919 */
+ unsigned short a, b, ga, gb;
+ unsigned long c_div, est_div;
+
+ /*
+ * Condition 1: dfsr <= 50ns/T (T=period of I2C source clock in ns).
+ * or (dfsr * T) <= 50ns.
+ * Translate to dfsr = 5 * Frequency / 100,000,000
+ */
+ dfsr = (5 * (i2c_clk / 1000)) / 100000;
+ debug("Requested speed:%d, i3c_clk:%d\n", speed, i2c_clk);
+ if (!dfsr)
+ dfsr = 1;
+
+ est_div = ~0;
+ for (ga = 0x4, a = 10; a <= 30; ga++, a += 2) {
+ for (gb = 0; gb < 8; gb++) {
+ b = 16 << gb;
+ c_div = b * (a + ((3*dfsr)/b)*2);
+ if ((c_div > divider) && (c_div < est_div)) {
+ unsigned short bin_gb, bin_ga;
+
+ est_div = c_div;
+ bin_gb = gb << 2;
+ bin_ga = (ga & 0x3) | ((ga & 0x4) << 3);
+ fdr = bin_gb | bin_ga;
+ speed = i2c_clk / est_div;
+ debug("FDR:0x%.2x, div:%ld, ga:0x%x, gb:0x%x, "
+ "a:%d, b:%d, speed:%d\n",
+ fdr, est_div, ga, gb, a, b, speed);
+ /* Condition 2 not accounted for */
+ debug("Tr <= %d ns\n",
+ (b - 3 * dfsr) * 1000000 /
+ (i2c_clk / 1000));
+ }
+ }
+ if (a == 20)
+ a += 2;
+ if (a == 24)
+ a += 4;
+ }
+ debug("divider:%d, est_div:%ld, DFSR:%d\n", divider, est_div, dfsr);
+ debug("FDR:0x%.2x, speed:%d\n", fdr, speed);
+ writeb(dfsr, dev + FSL_I2C_DFSRR); /* set default filter */
+ writeb(fdr, dev + FSL_I2C_FDR); /* set bus speed */
+
+ return speed;
+}
+
+static void fsl_i2c_init(struct fsl_i2c_dev *i2c_dev)
+{
+ void __iomem *dev = i2c_dev->regs;
+ unsigned int i2c_clk;
+
+ i2c_clk = fsl_get_i2c_freq();
+
+ writeb(0, dev + FSL_I2C_CR); /* stop I2C controller */
+ udelay(5);
+ fsl_set_i2c_bus_speed(dev, i2c_clk, i2c_dev->speed);
+ writeb(i2c_dev->slaveaddr << 1, dev + FSL_I2C_ADR);
+ writeb(0x0, dev + FSL_I2C_SR); /* clear status register */
+ writeb(I2C_CR_MEN, dev + FSL_I2C_CR); /* start I2C controller */
+}
+
+static int i2c_wait4bus(void __iomem *i2c)
+{
+ uint64_t start;
+
+ start = get_time_ns();
+ while (readb(i2c + FSL_I2C_SR) & I2C_SR_MBB) {
+ if (is_timeout(start, 100 * MSECOND)) {
+ debug("i2c_wait4bus timeout for I2C bus\n");
+ return -EIO;
+ }
+ }
+
+ return 0;
+}
+
+static inline int i2c_wait(void __iomem *i2c, int write)
+{
+ int csr;
+ uint64_t start;
+
+ start = get_time_ns();
+ while (1) {
+ if (is_timeout(start, 100 * MSECOND)) {
+ debug("timeout waiting for I2C bus\n");
+ return -EIO;
+ }
+
+ csr = readb(i2c + FSL_I2C_SR);
+ if (!(csr & I2C_SR_MIF))
+ continue;
+ /* Read again to allow register to stabilise */
+ csr = readb(i2c + FSL_I2C_SR);
+ writeb(0x0, i2c + FSL_I2C_SR);
+
+ if (csr & I2C_SR_MAL) {
+ debug("i2c_wait: MAL\n");
+ return -EIO;
+ }
+
+ if (!(csr & I2C_SR_MCF)) {
+ debug("i2c_wait: unfinished\n");
+ return -EIO;
+ }
+
+ if (write == I2C_WRITE_BIT && (csr & I2C_SR_RXAK)) {
+ debug("i2c_wait: No RXACK\n");
+ return -EIO;
+ }
+
+ return 0;
+ }
+
+ return -EIO;
+}
+
+static inline int i2c_write_addr(void __iomem *i2c, u8 dev, u8 dir, int rsta)
+{
+ writeb(I2C_CR_MEN | I2C_CR_MSTA | I2C_CR_MTX
+ | (rsta ? I2C_CR_RSTA : 0), i2c + FSL_I2C_CR);
+
+ writeb((dev << 1) | dir, i2c + FSL_I2C_DR);
+
+ if (i2c_wait(i2c, I2C_WRITE_BIT) < 0) {
+ debug("i2c_write_addr failed i2c_wait\n");
+ return 0;
+ }
+
+ return 1;
+}
+
+static inline int __i2c_write(void __iomem *i2c, u8 *data, int length)
+{
+ int i;
+
+ for (i = 0; i < length; i++) {
+ writeb(data[i], i2c + FSL_I2C_DR);
+
+ if (i2c_wait(i2c, I2C_WRITE_BIT) < 0)
+ break;
+ }
+
+ return i;
+}
+
+static inline int __i2c_read(void __iomem *i2c, u8 *data, int length)
+{
+ int i;
+
+ writeb(I2C_CR_MEN | I2C_CR_MSTA | ((length == 1) ? I2C_CR_TXAK : 0),
+ i2c + FSL_I2C_CR);
+
+ /* dummy read */
+ readb(i2c + FSL_I2C_DR);
+
+ for (i = 0; i < length; i++) {
+ if (i2c_wait(i2c, I2C_READ_BIT) < 0)
+ break;
+
+ /* Generate ack on last next to last byte */
+ if (i == (length - 2))
+ writeb(I2C_CR_MEN | I2C_CR_MSTA | I2C_CR_TXAK,
+ i2c + FSL_I2C_CR);
+
+ /* Do not generate stop on last byte */
+ if (i == (length - 1))
+ writeb(I2C_CR_MEN | I2C_CR_MSTA | I2C_CR_MTX,
+ i2c + FSL_I2C_CR);
+
+ data[i] = readb(i2c + FSL_I2C_DR);
+ }
+
+ return i;
+}
+
+static inline void fsl_i2c_stop(void __iomem *i2c)
+{
+ writeb(I2C_CR_MEN, i2c + FSL_I2C_CR);
+}
+
+static int fsl_i2c_read(void __iomem *i2c, u8 dev, u8 *data, int len, int rsta)
+{
+ int l = 0;
+
+ if (i2c_write_addr(i2c, dev, I2C_READ_BIT, rsta) != 0)
+ l = __i2c_read(i2c, data, len);
+
+ if (l == len)
+ return 0;
+
+ return -EIO;
+}
+
+static int fsl_i2c_write(void __iomem *i2c, u8 dev, u8 *data, int len, int rsta)
+{
+ if ((i2c_wait4bus(i2c) >= 0) &&
+ (i2c_write_addr(i2c, dev, I2C_WRITE_BIT, rsta) != 0) &&
+ (__i2c_write(i2c, data, len) == len)) {
+ return 0;
+ } else
+ return -EIO;
+}
+
+static int fsl_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
+{
+ struct fsl_i2c_dev *i2cdev = to_fsl_i2c_struct(adap);
+ void __iomem *i2c = i2cdev->regs;
+ struct i2c_msg *pmsg;
+ int ix;
+ int ret = 0;
+
+ for (ix = 0; ret >= 0 && ix < num; ix++) {
+ pmsg = &msgs[ix];
+
+ debug("%s %d bytes to 0x%02x - %d of %d messages\n",
+ pmsg->flags & I2C_M_RD ? "Read" : "Write",
+ pmsg->len, pmsg->addr, ix + 1, num);
+
+ if (pmsg->len == 0)
+ return -EINVAL;
+
+ if (pmsg->flags & I2C_M_RD)
+ ret = fsl_i2c_read(i2c, pmsg->addr, pmsg->buf,
+ pmsg->len, ix);
+ else
+ ret = fsl_i2c_write(i2c, pmsg->addr, pmsg->buf,
+ pmsg->len, ix);
+ }
+
+ fsl_i2c_stop(i2c);
+
+ return (ret < 0) ? ret : num;
+}
+
+static int fsl_i2c_probe(struct device_d *pdev)
+{
+ struct fsl_i2c_dev *i2cdev;
+ struct fsl_i2c_plat *pdata = (struct fsl_i2c_plat *)pdev->platform_data;
+ int ret = 0;
+
+ i2cdev = kzalloc(sizeof(struct fsl_i2c_dev), GFP_KERNEL);
+ if (!i2cdev)
+ return -ENOMEM;
+
+ i2cdev->bus_num = pdata->bus_num;
+ i2cdev->regs = dev_request_mem_region(pdev, 0);
+ i2cdev->speed = pdata->speed;
+ i2cdev->slaveaddr = pdata->slaveaddr;
+
+ debug(pdev, "bus %d at %d Hz\n", i2cdev->bus_num, i2cdev->speed);
+
+ fsl_i2c_init(i2cdev);
+
+ i2cdev->adapter.master_xfer = fsl_i2c_xfer,
+ i2cdev->adapter.nr = i2cdev->bus_num;
+ i2cdev->adapter.dev = pdev;
+
+ /* Add I2C adapter */
+ ret = i2c_add_numbered_adapter(&i2cdev->adapter);
+ if (ret < 0) {
+ dev_err(pdev, "registration failed\n");
+ goto fail;
+ }
+
+ return 0;
+fail:
+ kfree(i2cdev);
+ return ret;
+}
+
+static struct driver_d i2c_fsl_driver = {
+ .probe = fsl_i2c_probe,
+ .name = "fsl_i2c",
+};
+
+static int __init fsl_i2c_init_driver(void)
+{
+ return register_driver(&i2c_fsl_driver);
+}
+
+device_initcall(fsl_i2c_init_driver);
--
1.7.1
More information about the barebox
mailing list