* [patch 1/5] i2c: xtensa s6000 i2c driver
@ 2009-03-23 15:05 Daniel Glöckner
2009-03-23 15:05 ` [patch 2/5] xtensa: s6105 specific configuration for the s6000 i2c host Daniel Glöckner
` (4 more replies)
0 siblings, 5 replies; 8+ messages in thread
From: Daniel Glöckner @ 2009-03-23 15:05 UTC (permalink / raw)
To: Chris Zankel, Ben Dooks
Cc: linux-i2c, linux-kernel, Oskar Schirmer, Daniel Glöckner
From: Oskar Schirmer <os@emlix.com>
Support for the s6000 on-chip i2c controller.
Signed-off-by: Oskar Schirmer <os@emlix.com>
Signed-off-by: Daniel Glöckner <dg@emlix.com>
---
drivers/i2c/busses/Kconfig | 10 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-s6000.c | 380 ++++++++++++++++++++++++++++++++++++++++
drivers/i2c/busses/i2c-s6000.h | 79 +++++++++
include/linux/i2c/s6000.h | 10 +
5 files changed, 480 insertions(+), 0 deletions(-)
create mode 100644 drivers/i2c/busses/i2c-s6000.c
create mode 100644 drivers/i2c/busses/i2c-s6000.h
create mode 100644 include/linux/i2c/s6000.h
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 7f95905..904def4 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -461,6 +461,16 @@ config I2C_S3C2410
Say Y here to include support for I2C controller in the
Samsung S3C2410 based System-on-Chip devices.
+config I2C_S6000
+ tristate "S6000 I2C support"
+ depends on XTENSA_VARIANT_S6000
+ help
+ This driver supports the on chip I2C device on the
+ S6000 xtensa processor family.
+
+ To compile this driver as a module, choose M here. The module
+ will be called i2c-s6000.
+
config I2C_SH7760
tristate "Renesas SH7760 I2C Controller"
depends on CPU_SUBTYPE_SH7760
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 0c2c4b2..f7989d1 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -43,6 +43,7 @@ obj-$(CONFIG_I2C_PASEMI) += i2c-pasemi.o
obj-$(CONFIG_I2C_PNX) += i2c-pnx.o
obj-$(CONFIG_I2C_PXA) += i2c-pxa.o
obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o
+obj-$(CONFIG_I2C_S6000) += i2c-s6000.o
obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o
obj-$(CONFIG_I2C_SH_MOBILE) += i2c-sh_mobile.o
obj-$(CONFIG_I2C_SIMTEC) += i2c-simtec.o
diff --git a/drivers/i2c/busses/i2c-s6000.c b/drivers/i2c/busses/i2c-s6000.c
new file mode 100644
index 0000000..88e3fdc
--- /dev/null
+++ b/drivers/i2c/busses/i2c-s6000.c
@@ -0,0 +1,380 @@
+/*
+ * drivers/i2c/busses/i2c-s6000.c
+ *
+ * Description: Driver for S6000 Family I2C Interface
+ * (c) 2008 emlix GmbH <info@emlix.com>
+ * Author: Oskar Schirmer <os@emlix.com>
+ *
+ * Partially based on i2c-bfin-twi.c driver by <sonic.zhang@analog.com>
+ * Copyright (c) 2005-2007 Analog Devices, Inc.
+ *
+ * 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/clk.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/i2c/s6000.h>
+#include <linux/timer.h>
+#include <linux/spinlock.h>
+#include <linux/completion.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+
+#include <asm/io.h>
+#include "i2c-s6000.h"
+
+#define POLL_TIMEOUT (2 * HZ)
+
+struct s6i2c_if {
+ u8 __iomem *reg;
+ int irq;
+ spinlock_t lock;
+ struct i2c_msg *msgs;
+ int msgs_num, msgs_push, msgs_done;
+ unsigned push, done;
+ int timeout_count;
+ struct timer_list timeout_timer;
+ struct i2c_adapter adap;
+ struct completion complete;
+ struct clk *clk;
+ struct resource *res;
+};
+
+#define I2C_RD16(iface, n) readw(iface->reg + (n))
+#define I2C_WR16(iface, n, v) writew(v, iface->reg + (n))
+#define I2C_RD32(iface, n) readl(iface->reg + (n))
+#define I2C_WR32(iface, n, v) writel(v, iface->reg + (n))
+
+static struct s6i2c_if s6i2c_if;
+
+static void s6i2c_handle_interrupt(struct s6i2c_if *iface)
+{
+ if (I2C_RD16(iface, S6_I2C_INTRSTAT) & (1 << S6_I2C_INTR_TXABRT)) {
+ I2C_RD16(iface, S6_I2C_CLRTXABRT);
+ I2C_WR16(iface, S6_I2C_INTRMASK, 0);
+ complete(&iface->complete);
+ return;
+ }
+ if (iface->msgs_done >= iface->msgs_num) {
+ printk(KERN_DEBUG "spurious I2C irq: %x\n",
+ I2C_RD16(iface, S6_I2C_INTRSTAT));
+ I2C_WR16(iface, S6_I2C_INTRMASK, 0);
+ return;
+ }
+ while ((iface->msgs_push < iface->msgs_num)
+ && (I2C_RD16(iface, S6_I2C_STATUS) & (1 << S6_I2C_STATUS_TFNF))) {
+ struct i2c_msg *m = &iface->msgs[iface->msgs_push];
+ if (!(m->flags & I2C_M_RD))
+ I2C_WR16(iface, S6_I2C_DATACMD, m->buf[iface->push]);
+ else
+ I2C_WR16(iface, S6_I2C_DATACMD,
+ 1 << S6_I2C_DATACMD_READ);
+ if (++iface->push >= m->len) {
+ iface->push = 0;
+ iface->msgs_push += 1;
+ }
+ }
+ do {
+ struct i2c_msg *m = &iface->msgs[iface->msgs_done];
+ if (!(m->flags & I2C_M_RD)) {
+ if (iface->msgs_done < iface->msgs_push)
+ iface->msgs_done += 1;
+ else
+ break;
+ } else if (I2C_RD16(iface, S6_I2C_STATUS)
+ & (1 << S6_I2C_STATUS_RFNE)) {
+ m->buf[iface->done] = I2C_RD16(iface, S6_I2C_DATACMD);
+ if (++iface->done >= m->len) {
+ iface->done = 0;
+ iface->msgs_done += 1;
+ }
+ } else{
+ break;
+ }
+ } while (iface->msgs_done < iface->msgs_num);
+ if (iface->msgs_done >= iface->msgs_num) {
+ I2C_WR16(iface, S6_I2C_INTRMASK, 1 << S6_I2C_INTR_TXABRT);
+ complete(&iface->complete);
+ } else if (iface->msgs_push >= iface->msgs_num) {
+ I2C_WR16(iface, S6_I2C_INTRMASK, (1 << S6_I2C_INTR_TXABRT) |
+ (1 << S6_I2C_INTR_RXFULL));
+ } else {
+ I2C_WR16(iface, S6_I2C_INTRMASK, (1 << S6_I2C_INTR_TXABRT) |
+ (1 << S6_I2C_INTR_TXEMPTY) |
+ (1 << S6_I2C_INTR_RXFULL));
+ }
+}
+
+static irqreturn_t s6i2c_interrupt_entry(int irq, void *dev_id)
+{
+ struct s6i2c_if *iface = dev_id;
+ if (!(I2C_RD16(iface, S6_I2C_STATUS) & ((1 << S6_I2C_INTR_RXUNDER)
+ | (1 << S6_I2C_INTR_RXOVER)
+ | (1 << S6_I2C_INTR_RXFULL)
+ | (1 << S6_I2C_INTR_TXOVER)
+ | (1 << S6_I2C_INTR_TXEMPTY)
+ | (1 << S6_I2C_INTR_RDREQ)
+ | (1 << S6_I2C_INTR_TXABRT)
+ | (1 << S6_I2C_INTR_RXDONE)
+ | (1 << S6_I2C_INTR_ACTIVITY)
+ | (1 << S6_I2C_INTR_STOPDET)
+ | (1 << S6_I2C_INTR_STARTDET)
+ | (1 << S6_I2C_INTR_GENCALL))))
+ return IRQ_NONE;
+
+ spin_lock(&iface->lock);
+ del_timer(&iface->timeout_timer);
+ s6i2c_handle_interrupt(iface);
+ spin_unlock(&iface->lock);
+ return IRQ_HANDLED;
+}
+
+static void s6i2c_timeout(unsigned long data)
+{
+ struct s6i2c_if *iface = (struct s6i2c_if *)data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&iface->lock, flags);
+ s6i2c_handle_interrupt(iface);
+ if (--iface->timeout_count > 0) {
+ iface->timeout_timer.expires = jiffies + POLL_TIMEOUT;
+ add_timer(&iface->timeout_timer);
+ } else {
+ complete(&iface->complete);
+ I2C_WR16(iface, S6_I2C_INTRMASK, 0);
+ }
+ spin_unlock_irqrestore(&iface->lock, flags);
+}
+
+static int s6i2c_master_xfer(struct i2c_adapter *adap,
+ struct i2c_msg *msgs, int num)
+{
+ struct s6i2c_if *iface = adap->algo_data;
+ int i;
+ if (num == 0)
+ return 0;
+ if (I2C_RD16(iface, S6_I2C_STATUS) & (1 << S6_I2C_STATUS_ACTIVITY))
+ yield();
+ I2C_WR16(iface, S6_I2C_INTRMASK, 0);
+ I2C_RD16(iface, S6_I2C_CLRINTR);
+ for (i = 0; i < num; i++) {
+ if (msgs[i].flags & I2C_M_TEN) {
+ dev_err(&(adap->dev),
+ "s6i2c: 10 bits addr not supported\n");
+ return -EINVAL;
+ }
+ if (msgs[i].len == 0) {
+ dev_err(&(adap->dev),
+ "s6i2c: zero length message not supported\n");
+ return -EINVAL;
+ }
+ if (msgs[i].addr != msgs[0].addr) {
+ dev_err(&(adap->dev),
+ "s6i2c: multiple xfer cannot change target\n");
+ return -EINVAL;
+ }
+ }
+ iface->msgs = msgs;
+ iface->msgs_num = num;
+ iface->msgs_push = 0;
+ iface->msgs_done = 0;
+ iface->push = 0;
+ iface->done = 0;
+ iface->timeout_count = 10;
+ I2C_WR16(iface, S6_I2C_TAR, msgs[0].addr);
+ I2C_WR16(iface, S6_I2C_ENABLE, 1);
+ I2C_WR16(iface, S6_I2C_INTRMASK, (1 << S6_I2C_INTR_TXEMPTY) |
+ (1 << S6_I2C_INTR_TXABRT));
+ iface->timeout_timer.expires = jiffies + POLL_TIMEOUT;
+ add_timer(&iface->timeout_timer);
+ wait_for_completion(&iface->complete);
+ del_timer_sync(&iface->timeout_timer);
+ while (I2C_RD32(iface, S6_I2C_TXFLR) > 0)
+ schedule();
+ while (I2C_RD16(iface, S6_I2C_STATUS) & (1 << S6_I2C_STATUS_ACTIVITY))
+ schedule();
+ I2C_WR16(iface, S6_I2C_INTRMASK, 0);
+ I2C_WR16(iface, S6_I2C_ENABLE, 0);
+ return iface->msgs_done;
+}
+
+static u32 s6i2c_functionality(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static struct i2c_algorithm s6i2c_algorithm = {
+ .master_xfer = s6i2c_master_xfer,
+ .functionality = s6i2c_functionality,
+};
+
+static u16 __devinit nanoseconds_on_clk(struct s6i2c_if *iface, u32 ns)
+{
+ u64 dividend = (u64)ns * clk_get_rate(iface->clk);
+ do_div(dividend, 1000000000);
+ if (dividend > 0xffff)
+ return 0xffff;
+ return dividend;
+}
+
+static int __devinit s6i2c_probe(struct platform_device *dev)
+{
+ struct s6i2c_if *iface = &s6i2c_if;
+ struct i2c_adapter *p_adap;
+ const char *clock;
+ int bus_num, rc;
+ spin_lock_init(&(iface->lock));
+ init_completion(&(iface->complete));
+ iface->irq = platform_get_irq(dev, 0);
+ if (iface->irq < 0) {
+ rc = iface->irq;
+ goto err_out;
+ }
+ iface->res = platform_get_resource(dev, IORESOURCE_MEM, 0);
+ if (!iface->res) {
+ rc = -ENXIO;
+ goto err_out;
+ }
+ iface->res = request_mem_region(iface->res->start,
+ iface->res->end - iface->res->start + 1,
+ dev->dev.bus_id);
+ if (!iface->res) {
+ rc = -EBUSY;
+ goto err_out;
+ }
+ iface->reg = ioremap_nocache(iface->res->start,
+ iface->res->end - iface->res->start + 1);
+ if (!iface->reg) {
+ rc = -ENOMEM;
+ goto err_reg;
+ }
+ clock = 0;
+ bus_num = -1;
+ if (dev->dev.platform_data) {
+ struct s6_i2c_platform_data *pdata = dev->dev.platform_data;
+ bus_num = pdata->bus_num;
+ clock = pdata->clock;
+ }
+ iface->clk = clk_get(&dev->dev, clock);
+ if (IS_ERR(iface->clk)) {
+ rc = PTR_ERR(iface->clk);
+ goto err_map;
+ }
+ rc = clk_enable(iface->clk);
+ if (rc < 0)
+ goto err_clk_put;
+ init_timer(&(iface->timeout_timer));
+ iface->timeout_timer.function = s6i2c_timeout;
+ iface->timeout_timer.data = (unsigned long)iface;
+ p_adap = &iface->adap;
+ strlcpy(p_adap->name, dev->name, sizeof(p_adap->name));
+ p_adap->algo = &s6i2c_algorithm;
+ p_adap->algo_data = iface;
+ p_adap->nr = bus_num;
+ p_adap->class = 0;
+ p_adap->dev.parent = &dev->dev;
+ I2C_WR16(iface, S6_I2C_INTRMASK, 0);
+ rc = request_irq(iface->irq, s6i2c_interrupt_entry,
+ IRQF_SHARED, dev->name, iface);
+ if (rc) {
+ dev_err(&(p_adap->dev), "s6i2c: cant get IRQ %d\n", iface->irq);
+ goto err_clk_dis;
+ }
+ I2C_WR16(iface, S6_I2C_ENABLE, 0);
+ udelay(1);
+ I2C_WR32(iface, S6_I2C_SRESET, 1 << S6_I2C_SRESET_IC_SRST);
+ I2C_WR16(iface, S6_I2C_CLRTXABRT, 1);
+ I2C_WR16(iface, S6_I2C_CON,
+ (1 << S6_I2C_CON_MASTER) |
+ (S6_I2C_CON_SPEED_NORMAL << S6_I2C_CON_SPEED) |
+ (0 << S6_I2C_CON_10BITSLAVE) |
+ (0 << S6_I2C_CON_10BITMASTER) |
+ (1 << S6_I2C_CON_RESTARTENA) |
+ (1 << S6_I2C_CON_SLAVEDISABLE));
+ I2C_WR16(iface, S6_I2C_SSHCNT, nanoseconds_on_clk(iface, 4000));
+ I2C_WR16(iface, S6_I2C_SSLCNT, nanoseconds_on_clk(iface, 4700));
+ I2C_WR16(iface, S6_I2C_FSHCNT, nanoseconds_on_clk(iface, 600));
+ I2C_WR16(iface, S6_I2C_FSLCNT, nanoseconds_on_clk(iface, 1300));
+ I2C_WR16(iface, S6_I2C_RXTL, 0);
+ I2C_WR16(iface, S6_I2C_TXTL, 0);
+ platform_set_drvdata(dev, iface);
+ if (bus_num < 0)
+ rc = i2c_add_adapter(p_adap);
+ else
+ rc = i2c_add_numbered_adapter(p_adap);
+ if (rc)
+ goto err_irq_free;
+ return 0;
+
+err_irq_free:
+ free_irq(iface->irq, iface);
+err_clk_dis:
+ clk_disable(iface->clk);
+err_clk_put:
+ clk_put(iface->clk);
+err_map:
+ iounmap(iface->reg);
+err_reg:
+ release_mem_region(iface->res->start,
+ iface->res->end - iface->res->start + 1);
+err_out:
+ return rc;
+}
+
+static int __devexit s6i2c_remove(struct platform_device *pdev)
+{
+ struct s6i2c_if *iface = platform_get_drvdata(pdev);
+ I2C_WR16(iface, S6_I2C_ENABLE, 0);
+ platform_set_drvdata(pdev, NULL);
+ i2c_del_adapter(&(iface->adap));
+ free_irq(iface->irq, iface);
+ clk_disable(iface->clk);
+ clk_put(iface->clk);
+ iounmap(iface->reg);
+ release_mem_region(iface->res->start,
+ iface->res->end - iface->res->start + 1);
+ return 0;
+}
+
+static struct platform_driver s6i2c_driver = {
+ .probe = s6i2c_probe,
+ .remove = __devexit_p(s6i2c_remove),
+ .driver = {
+ .name = "i2c-s6000",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init s6i2c_init(void)
+{
+ pr_info("I2C: S6000 I2C driver\n");
+ return platform_driver_register(&s6i2c_driver);
+}
+
+static void __exit s6i2c_exit(void)
+{
+ platform_driver_unregister(&s6i2c_driver);
+}
+
+MODULE_DESCRIPTION("I2C-Bus adapter routines for S6000 I2C");
+MODULE_LICENSE("GPL");
+
+subsys_initcall(s6i2c_init);
+module_exit(s6i2c_exit);
diff --git a/drivers/i2c/busses/i2c-s6000.h b/drivers/i2c/busses/i2c-s6000.h
new file mode 100644
index 0000000..ff23b81
--- /dev/null
+++ b/drivers/i2c/busses/i2c-s6000.h
@@ -0,0 +1,79 @@
+/*
+ * drivers/i2c/busses/i2c-s6000.h
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 2008 Emlix GmbH <info@emlix.com>
+ * Author: Oskar Schirmer <os@emlix.com>
+ */
+
+#ifndef __DRIVERS_I2C_BUSSES_I2C_S6000_H
+#define __DRIVERS_I2C_BUSSES_I2C_S6000_H
+
+#define S6_I2C_CON 0x000
+#define S6_I2C_CON_MASTER 0
+#define S6_I2C_CON_SPEED 1
+#define S6_I2C_CON_SPEED_NORMAL 1
+#define S6_I2C_CON_SPEED_FAST 2
+#define S6_I2C_CON_SPEED_MASK 3
+#define S6_I2C_CON_10BITSLAVE 3
+#define S6_I2C_CON_10BITMASTER 4
+#define S6_I2C_CON_RESTARTENA 5
+#define S6_I2C_CON_SLAVEDISABLE 6
+#define S6_I2C_TAR 0x004
+#define S6_I2C_TAR_GCORSTART 10
+#define S6_I2C_TAR_SPECIAL 11
+#define S6_I2C_SAR 0x008
+#define S6_I2C_HSMADDR 0x00C
+#define S6_I2C_DATACMD 0x010
+#define S6_I2C_DATACMD_READ 8
+#define S6_I2C_SSHCNT 0x014
+#define S6_I2C_SSLCNT 0x018
+#define S6_I2C_FSHCNT 0x01C
+#define S6_I2C_FSLCNT 0x020
+#define S6_I2C_INTRSTAT 0x02C
+#define S6_I2C_INTRMASK 0x030
+#define S6_I2C_RAWINTR 0x034
+#define S6_I2C_INTR_RXUNDER 0
+#define S6_I2C_INTR_RXOVER 1
+#define S6_I2C_INTR_RXFULL 2
+#define S6_I2C_INTR_TXOVER 3
+#define S6_I2C_INTR_TXEMPTY 4
+#define S6_I2C_INTR_RDREQ 5
+#define S6_I2C_INTR_TXABRT 6
+#define S6_I2C_INTR_RXDONE 7
+#define S6_I2C_INTR_ACTIVITY 8
+#define S6_I2C_INTR_STOPDET 9
+#define S6_I2C_INTR_STARTDET 10
+#define S6_I2C_INTR_GENCALL 11
+#define S6_I2C_RXTL 0x038
+#define S6_I2C_TXTL 0x03C
+#define S6_I2C_CLRINTR 0x040
+#define S6_I2C_CLRRXUNDER 0x044
+#define S6_I2C_CLRRXOVER 0x048
+#define S6_I2C_CLRTXOVER 0x04C
+#define S6_I2C_CLRRDREQ 0x050
+#define S6_I2C_CLRTXABRT 0x054
+#define S6_I2C_CLRRXDONE 0x058
+#define S6_I2C_CLRACTIVITY 0x05C
+#define S6_I2C_CLRSTOPDET 0x060
+#define S6_I2C_CLRSTARTDET 0x064
+#define S6_I2C_CLRGENCALL 0x068
+#define S6_I2C_ENABLE 0x06C
+#define S6_I2C_STATUS 0x070
+#define S6_I2C_STATUS_ACTIVITY 0
+#define S6_I2C_STATUS_TFNF 1
+#define S6_I2C_STATUS_TFE 2
+#define S6_I2C_STATUS_RFNE 3
+#define S6_I2C_STATUS_RFF 4
+#define S6_I2C_TXFLR 0x074
+#define S6_I2C_RXFLR 0x078
+#define S6_I2C_SRESET 0x07C
+#define S6_I2C_SRESET_IC_SRST 0
+#define S6_I2C_SRESET_IC_MASTER_SRST 1
+#define S6_I2C_SRESET_IC_SLAVE_SRST 2
+#define S6_I2C_TXABRTSOURCE 0x080
+
+#endif
diff --git a/include/linux/i2c/s6000.h b/include/linux/i2c/s6000.h
new file mode 100644
index 0000000..0b6b0c6
--- /dev/null
+++ b/include/linux/i2c/s6000.h
@@ -0,0 +1,10 @@
+#ifndef __LINUX_I2C_S6000_H
+#define __LINUX_I2C_S6000_H
+
+struct s6_i2c_platform_data {
+ const char *clock;
+ int bus_num;
+};
+
+#endif
+
--
1.6.2.107.ge47ee
^ permalink raw reply [flat|nested] 8+ messages in thread* [patch 2/5] xtensa: s6105 specific configuration for the s6000 i2c host 2009-03-23 15:05 [patch 1/5] i2c: xtensa s6000 i2c driver Daniel Glöckner @ 2009-03-23 15:05 ` Daniel Glöckner 2009-03-23 15:05 ` [patch 3/5] xtensa: add at24hc02b to s6105 platform Daniel Glöckner ` (3 subsequent siblings) 4 siblings, 0 replies; 8+ messages in thread From: Daniel Glöckner @ 2009-03-23 15:05 UTC (permalink / raw) To: Chris Zankel, Ben Dooks Cc: linux-i2c, linux-kernel, Daniel Glöckner, Oskar Schirmer Signed-off-by: Daniel Glöckner <dg@emlix.com> Signed-off-by: Oskar Schirmer <os@emlix.com> --- arch/xtensa/platforms/s6105/device.c | 51 ++++++++++++++++++++++++++++++++++ 1 files changed, 51 insertions(+), 0 deletions(-) diff --git a/arch/xtensa/platforms/s6105/device.c b/arch/xtensa/platforms/s6105/device.c index 963634a..00f52ee 100644 --- a/arch/xtensa/platforms/s6105/device.c +++ b/arch/xtensa/platforms/s6105/device.c @@ -6,6 +6,8 @@ #include <linux/kernel.h> #include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/i2c/s6000.h> #include <linux/init.h> #include <linux/irq.h> #include <linux/phy.h> @@ -21,6 +23,7 @@ #define GPIO3_INTNUM 3 #define UART_INTNUM 4 #define GMAC_INTNUM 5 +#define I2C_INTNUM 6 static const signed char gpio3_irq_mappings[] = { S6_INTC_GPIO(3), @@ -41,10 +44,16 @@ static const signed char gmac_irq_mappings[] = { -1 }; +static const signed char i2c_irq_mappings[] = { + S6_INTC_I2C, + -1 +}; + const signed char *platform_irq_mappings[NR_IRQS] = { [GPIO3_INTNUM] = gpio3_irq_mappings, [UART_INTNUM] = uart_irq_mappings, [GMAC_INTNUM] = gmac_irq_mappings, + [I2C_INTNUM] = i2c_irq_mappings, }; static struct plat_serial8250_port serial_platform_data[] = { @@ -129,6 +138,38 @@ fail: return PHY_POLL; } +#define S6_I2C_BUS_NUM 0 + +static __devinitdata struct s6_i2c_platform_data s6_i2c_pdata = { + .bus_num = S6_I2C_BUS_NUM, + .clock = "PCLK", +}; + +static struct resource s6_i2c_resource[] = { + { + .name = "mem", + .start = (resource_size_t)S6_REG_I2C, + .end = (resource_size_t)S6_REG_I2C + 0x1000 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "irq", + .start = (resource_size_t)I2C_INTNUM, + .flags = IORESOURCE_IRQ, + }, +}; + +#define S6I2C_ADDR_TLV320AIC31 0x18 +#define S6I2C_ADDR_SAA7121 0x44 +#define S6I2C_ADDR_MT9D131 0x48 +#define S6I2C_ADDR_AT24HC02B 0x57 +#define S6I2C_ADDR_M41T62 0x68 +#define S6I2C_ADDR_CDCE906 0x69 +#define S6I2C_ADDR_PCA9543 0x73 + +static struct i2c_board_info __initdata s6_i2c_devices[] = { +}; + static struct platform_device platform_devices[] = { { .name = "serial8250", @@ -143,12 +184,22 @@ static struct platform_device platform_devices[] = { .resource = s6_gmac_resource, .num_resources = ARRAY_SIZE(s6_gmac_resource), }, + { + .name = "i2c-s6000", + .resource = s6_i2c_resource, + .num_resources = ARRAY_SIZE(s6_i2c_resource), + .dev = { + .platform_data = &s6_i2c_pdata, + }, + }, }; static int __init device_init(void) { int i; + i2c_register_board_info(S6_I2C_BUS_NUM, s6_i2c_devices, + ARRAY_SIZE(s6_i2c_devices)); s6_gmac_resource[5].start = prepare_phy_irq(GPIO_PHY_IRQ); for (i = 0; i < ARRAY_SIZE(platform_devices); i++) -- 1.6.2.107.ge47ee ^ permalink raw reply [flat|nested] 8+ messages in thread
* [patch 3/5] xtensa: add at24hc02b to s6105 platform 2009-03-23 15:05 [patch 1/5] i2c: xtensa s6000 i2c driver Daniel Glöckner 2009-03-23 15:05 ` [patch 2/5] xtensa: s6105 specific configuration for the s6000 i2c host Daniel Glöckner @ 2009-03-23 15:05 ` Daniel Glöckner 2009-03-23 15:05 ` [patch 4/5] xtensa: enable s6000 i2c host driver in s6105_defconfig Daniel Glöckner ` (2 subsequent siblings) 4 siblings, 0 replies; 8+ messages in thread From: Daniel Glöckner @ 2009-03-23 15:05 UTC (permalink / raw) To: Chris Zankel, Ben Dooks; +Cc: linux-i2c, linux-kernel, Daniel Glöckner This is particularly used to read the MAC address of the ethernet controller from an EEPROM. Signed-off-by: Daniel Glöckner <dg@emlix.com> --- arch/xtensa/platforms/s6105/device.c | 3 +++ 1 files changed, 3 insertions(+), 0 deletions(-) diff --git a/arch/xtensa/platforms/s6105/device.c b/arch/xtensa/platforms/s6105/device.c index 00f52ee..9394e6f 100644 --- a/arch/xtensa/platforms/s6105/device.c +++ b/arch/xtensa/platforms/s6105/device.c @@ -168,6 +168,9 @@ static struct resource s6_i2c_resource[] = { #define S6I2C_ADDR_PCA9543 0x73 static struct i2c_board_info __initdata s6_i2c_devices[] = { + { + I2C_BOARD_INFO("24c02", S6I2C_ADDR_AT24HC02B), + }, }; static struct platform_device platform_devices[] = { -- 1.6.2.107.ge47ee ^ permalink raw reply [flat|nested] 8+ messages in thread
* [patch 4/5] xtensa: enable s6000 i2c host driver in s6105_defconfig 2009-03-23 15:05 [patch 1/5] i2c: xtensa s6000 i2c driver Daniel Glöckner 2009-03-23 15:05 ` [patch 2/5] xtensa: s6105 specific configuration for the s6000 i2c host Daniel Glöckner 2009-03-23 15:05 ` [patch 3/5] xtensa: add at24hc02b to s6105 platform Daniel Glöckner @ 2009-03-23 15:05 ` Daniel Glöckner 2009-03-23 15:05 ` [patch 5/5] xtensa: enable at24 " Daniel Glöckner 2009-03-31 22:52 ` [patch 1/5] i2c: xtensa s6000 i2c driver Ben Dooks 4 siblings, 0 replies; 8+ messages in thread From: Daniel Glöckner @ 2009-03-23 15:05 UTC (permalink / raw) To: Chris Zankel, Ben Dooks; +Cc: linux-i2c, linux-kernel, Daniel Glöckner Signed-off-by: Daniel Glöckner <dg@emlix.com> --- arch/xtensa/configs/s6105_defconfig | 54 +++++++++++++++++++++++++++++++++- 1 files changed, 52 insertions(+), 2 deletions(-) diff --git a/arch/xtensa/configs/s6105_defconfig b/arch/xtensa/configs/s6105_defconfig index a130c8d..a9a0de9 100644 --- a/arch/xtensa/configs/s6105_defconfig +++ b/arch/xtensa/configs/s6105_defconfig @@ -1,7 +1,7 @@ # # Automatically generated make config: don't edit # Linux kernel version: 2.6.29-rc7-s6 -# Tue Mar 10 11:09:26 2009 +# Wed Mar 11 13:38:31 2009 # # CONFIG_FRAME_POINTER is not set CONFIG_ZONE_DMA=y @@ -357,7 +357,48 @@ CONFIG_UNIX98_PTYS=y # CONFIG_R3964 is not set # CONFIG_RAW_DRIVER is not set # CONFIG_TCG_TPM is not set -# CONFIG_I2C is not set +CONFIG_I2C=y +CONFIG_I2C_BOARDINFO=y +# CONFIG_I2C_CHARDEV is not set +CONFIG_I2C_HELPER_AUTO=y + +# +# I2C Hardware Bus support +# + +# +# I2C system bus drivers (mostly embedded / system-on-chip) +# +# CONFIG_I2C_GPIO is not set +# CONFIG_I2C_OCORES is not set +CONFIG_I2C_S6000=y +# CONFIG_I2C_SIMTEC is not set + +# +# External I2C/SMBus adapter drivers +# +# CONFIG_I2C_PARPORT_LIGHT is not set +# CONFIG_I2C_TAOS_EVM is not set + +# +# Other I2C/SMBus bus drivers +# +# CONFIG_I2C_PCA_PLATFORM is not set + +# +# Miscellaneous I2C Chip support +# +# CONFIG_DS1682 is not set +# CONFIG_SENSORS_PCF8574 is not set +# CONFIG_PCF8575 is not set +# CONFIG_SENSORS_PCA9539 is not set +# CONFIG_SENSORS_PCF8591 is not set +# CONFIG_SENSORS_MAX6875 is not set +# CONFIG_SENSORS_TSL2550 is not set +# CONFIG_I2C_DEBUG_CORE is not set +# CONFIG_I2C_DEBUG_ALGO is not set +# CONFIG_I2C_DEBUG_BUS is not set +# CONFIG_I2C_DEBUG_CHIP is not set # CONFIG_SPI is not set CONFIG_ARCH_REQUIRE_GPIOLIB=y CONFIG_GPIOLIB=y @@ -371,6 +412,9 @@ CONFIG_GPIOLIB=y # # I2C GPIO expanders: # +# CONFIG_GPIO_MAX732X is not set +# CONFIG_GPIO_PCA953X is not set +# CONFIG_GPIO_PCF857X is not set # # PCI GPIO expanders: @@ -398,7 +442,13 @@ CONFIG_SSB_POSSIBLE=y # CONFIG_MFD_CORE is not set # CONFIG_MFD_SM501 is not set # CONFIG_HTC_PASIC3 is not set +# CONFIG_TPS65010 is not set +# CONFIG_TWL4030_CORE is not set # CONFIG_MFD_TMIO is not set +# CONFIG_PMIC_DA903X is not set +# CONFIG_MFD_WM8400 is not set +# CONFIG_MFD_WM8350_I2C is not set +# CONFIG_MFD_PCF50633 is not set # CONFIG_REGULATOR is not set # -- 1.6.2.107.ge47ee ^ permalink raw reply [flat|nested] 8+ messages in thread
* [patch 5/5] xtensa: enable at24 driver in s6105_defconfig 2009-03-23 15:05 [patch 1/5] i2c: xtensa s6000 i2c driver Daniel Glöckner ` (2 preceding siblings ...) 2009-03-23 15:05 ` [patch 4/5] xtensa: enable s6000 i2c host driver in s6105_defconfig Daniel Glöckner @ 2009-03-23 15:05 ` Daniel Glöckner 2009-03-31 22:52 ` [patch 1/5] i2c: xtensa s6000 i2c driver Ben Dooks 4 siblings, 0 replies; 8+ messages in thread From: Daniel Glöckner @ 2009-03-23 15:05 UTC (permalink / raw) To: Chris Zankel, Ben Dooks; +Cc: linux-i2c, linux-kernel, Daniel Glöckner Signed-off-by: Daniel Glöckner <dg@emlix.com> --- arch/xtensa/configs/s6105_defconfig | 12 +++++++++++- 1 files changed, 11 insertions(+), 1 deletions(-) diff --git a/arch/xtensa/configs/s6105_defconfig b/arch/xtensa/configs/s6105_defconfig index a9a0de9..e398710 100644 --- a/arch/xtensa/configs/s6105_defconfig +++ b/arch/xtensa/configs/s6105_defconfig @@ -251,7 +251,17 @@ CONFIG_BLK_DEV_RAM_SIZE=4096 # CONFIG_CDROM_PKTCDVD is not set # CONFIG_ATA_OVER_ETH is not set # CONFIG_BLK_DEV_HD is not set -# CONFIG_MISC_DEVICES is not set +CONFIG_MISC_DEVICES=y +# CONFIG_ICS932S401 is not set +# CONFIG_ENCLOSURE_SERVICES is not set +# CONFIG_C2PORT is not set + +# +# EEPROM support +# +CONFIG_EEPROM_AT24=y +# CONFIG_EEPROM_LEGACY is not set +# CONFIG_EEPROM_93CX6 is not set CONFIG_HAVE_IDE=y # CONFIG_IDE is not set -- 1.6.2.107.ge47ee ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [patch 1/5] i2c: xtensa s6000 i2c driver 2009-03-23 15:05 [patch 1/5] i2c: xtensa s6000 i2c driver Daniel Glöckner ` (3 preceding siblings ...) 2009-03-23 15:05 ` [patch 5/5] xtensa: enable at24 " Daniel Glöckner @ 2009-03-31 22:52 ` Ben Dooks 2009-03-31 23:01 ` Ben Dooks 4 siblings, 1 reply; 8+ messages in thread From: Ben Dooks @ 2009-03-31 22:52 UTC (permalink / raw) To: Daniel Gl??ckner Cc: Chris Zankel, Ben Dooks, linux-i2c, linux-kernel, Oskar Schirmer On Mon, Mar 23, 2009 at 04:05:14PM +0100, Daniel Gl??ckner wrote: > From: Oskar Schirmer <os@emlix.com> > > Support for the s6000 on-chip i2c controller. > > Signed-off-by: Oskar Schirmer <os@emlix.com> > Signed-off-by: Daniel Gl??ckner <dg@emlix.com> Not going to merge this on my first pull request to linus, there's a few things that I'd like to get answered before. > --- > drivers/i2c/busses/Kconfig | 10 + > drivers/i2c/busses/Makefile | 1 + > drivers/i2c/busses/i2c-s6000.c | 380 ++++++++++++++++++++++++++++++++++++++++ > drivers/i2c/busses/i2c-s6000.h | 79 +++++++++ > include/linux/i2c/s6000.h | 10 + > 5 files changed, 480 insertions(+), 0 deletions(-) > create mode 100644 drivers/i2c/busses/i2c-s6000.c > create mode 100644 drivers/i2c/busses/i2c-s6000.h > create mode 100644 include/linux/i2c/s6000.h > > diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig > index 7f95905..904def4 100644 > --- a/drivers/i2c/busses/Kconfig > +++ b/drivers/i2c/busses/Kconfig > @@ -461,6 +461,16 @@ config I2C_S3C2410 > Say Y here to include support for I2C controller in the > Samsung S3C2410 based System-on-Chip devices. > > +config I2C_S6000 > + tristate "S6000 I2C support" > + depends on XTENSA_VARIANT_S6000 > + help > + This driver supports the on chip I2C device on the > + S6000 xtensa processor family. > + > + To compile this driver as a module, choose M here. The module > + will be called i2c-s6000. > + > config I2C_SH7760 > tristate "Renesas SH7760 I2C Controller" > depends on CPU_SUBTYPE_SH7760 > diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile > index 0c2c4b2..f7989d1 100644 > --- a/drivers/i2c/busses/Makefile > +++ b/drivers/i2c/busses/Makefile > @@ -43,6 +43,7 @@ obj-$(CONFIG_I2C_PASEMI) += i2c-pasemi.o > obj-$(CONFIG_I2C_PNX) += i2c-pnx.o > obj-$(CONFIG_I2C_PXA) += i2c-pxa.o > obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o > +obj-$(CONFIG_I2C_S6000) += i2c-s6000.o > obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o > obj-$(CONFIG_I2C_SH_MOBILE) += i2c-sh_mobile.o > obj-$(CONFIG_I2C_SIMTEC) += i2c-simtec.o > diff --git a/drivers/i2c/busses/i2c-s6000.c b/drivers/i2c/busses/i2c-s6000.c > new file mode 100644 > index 0000000..88e3fdc > --- /dev/null > +++ b/drivers/i2c/busses/i2c-s6000.c > @@ -0,0 +1,380 @@ > +/* > + * drivers/i2c/busses/i2c-s6000.c > + * > + * Description: Driver for S6000 Family I2C Interface > + * (c) 2008 emlix GmbH <info@emlix.com> Note, I think you need Copyright here as well, (c) needs to be a proper c in circle i've been informed. > + * Author: Oskar Schirmer <os@emlix.com> > + * > + * Partially based on i2c-bfin-twi.c driver by <sonic.zhang@analog.com> > + * Copyright (c) 2005-2007 Analog Devices, Inc. > + * > + * 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/clk.h> > +#include <linux/err.h> > +#include <linux/module.h> > +#include <linux/kernel.h> > +#include <linux/init.h> > +#include <linux/delay.h> > +#include <linux/i2c.h> > +#include <linux/i2c/s6000.h> > +#include <linux/timer.h> > +#include <linux/spinlock.h> > +#include <linux/completion.h> > +#include <linux/interrupt.h> > +#include <linux/platform_device.h> > + > +#include <asm/io.h> > +#include "i2c-s6000.h" > + > +#define POLL_TIMEOUT (2 * HZ) > + would be nice to have some documentation for each of the members of this. > +struct s6i2c_if { > + u8 __iomem *reg; > + int irq; > + spinlock_t lock; > + struct i2c_msg *msgs; > + int msgs_num, msgs_push, msgs_done; > + unsigned push, done; > + int timeout_count; > + struct timer_list timeout_timer; > + struct i2c_adapter adap; > + struct completion complete; > + struct clk *clk; > + struct resource *res; > +}; > + > +#define I2C_RD16(iface, n) readw(iface->reg + (n)) > +#define I2C_WR16(iface, n, v) writew(v, iface->reg + (n)) > +#define I2C_RD32(iface, n) readl(iface->reg + (n)) > +#define I2C_WR32(iface, n, v) writel(v, iface->reg + (n)) tried inline functions for these? > + > +static struct s6i2c_if s6i2c_if; > + > +static void s6i2c_handle_interrupt(struct s6i2c_if *iface) > +{ > + if (I2C_RD16(iface, S6_I2C_INTRSTAT) & (1 << S6_I2C_INTR_TXABRT)) { > + I2C_RD16(iface, S6_I2C_CLRTXABRT); > + I2C_WR16(iface, S6_I2C_INTRMASK, 0); > + complete(&iface->complete); > + return; > + } > + if (iface->msgs_done >= iface->msgs_num) { > + printk(KERN_DEBUG "spurious I2C irq: %x\n", > + I2C_RD16(iface, S6_I2C_INTRSTAT)); > + I2C_WR16(iface, S6_I2C_INTRMASK, 0); > + return; > + } > + while ((iface->msgs_push < iface->msgs_num) > + && (I2C_RD16(iface, S6_I2C_STATUS) & (1 << S6_I2C_STATUS_TFNF))) { > + struct i2c_msg *m = &iface->msgs[iface->msgs_push]; > + if (!(m->flags & I2C_M_RD)) > + I2C_WR16(iface, S6_I2C_DATACMD, m->buf[iface->push]); > + else > + I2C_WR16(iface, S6_I2C_DATACMD, > + 1 << S6_I2C_DATACMD_READ); > + if (++iface->push >= m->len) { > + iface->push = 0; > + iface->msgs_push += 1; > + } > + } > + do { > + struct i2c_msg *m = &iface->msgs[iface->msgs_done]; > + if (!(m->flags & I2C_M_RD)) { > + if (iface->msgs_done < iface->msgs_push) > + iface->msgs_done += 1; > + else > + break; > + } else if (I2C_RD16(iface, S6_I2C_STATUS) > + & (1 << S6_I2C_STATUS_RFNE)) { > + m->buf[iface->done] = I2C_RD16(iface, S6_I2C_DATACMD); > + if (++iface->done >= m->len) { > + iface->done = 0; > + iface->msgs_done += 1; > + } > + } else{ > + break; > + } > + } while (iface->msgs_done < iface->msgs_num); > + if (iface->msgs_done >= iface->msgs_num) { > + I2C_WR16(iface, S6_I2C_INTRMASK, 1 << S6_I2C_INTR_TXABRT); > + complete(&iface->complete); > + } else if (iface->msgs_push >= iface->msgs_num) { > + I2C_WR16(iface, S6_I2C_INTRMASK, (1 << S6_I2C_INTR_TXABRT) | > + (1 << S6_I2C_INTR_RXFULL)); > + } else { > + I2C_WR16(iface, S6_I2C_INTRMASK, (1 << S6_I2C_INTR_TXABRT) | > + (1 << S6_I2C_INTR_TXEMPTY) | > + (1 << S6_I2C_INTR_RXFULL)); > + } > +} > + > +static irqreturn_t s6i2c_interrupt_entry(int irq, void *dev_id) > +{ > + struct s6i2c_if *iface = dev_id; > + if (!(I2C_RD16(iface, S6_I2C_STATUS) & ((1 << S6_I2C_INTR_RXUNDER) > + | (1 << S6_I2C_INTR_RXOVER) > + | (1 << S6_I2C_INTR_RXFULL) > + | (1 << S6_I2C_INTR_TXOVER) > + | (1 << S6_I2C_INTR_TXEMPTY) > + | (1 << S6_I2C_INTR_RDREQ) > + | (1 << S6_I2C_INTR_TXABRT) > + | (1 << S6_I2C_INTR_RXDONE) > + | (1 << S6_I2C_INTR_ACTIVITY) > + | (1 << S6_I2C_INTR_STOPDET) > + | (1 << S6_I2C_INTR_STARTDET) > + | (1 << S6_I2C_INTR_GENCALL)))) > + return IRQ_NONE; > + > + spin_lock(&iface->lock); > + del_timer(&iface->timeout_timer); > + s6i2c_handle_interrupt(iface); > + spin_unlock(&iface->lock); > + return IRQ_HANDLED; > +} > + > +static void s6i2c_timeout(unsigned long data) > +{ > + struct s6i2c_if *iface = (struct s6i2c_if *)data; > + unsigned long flags; > + > + spin_lock_irqsave(&iface->lock, flags); > + s6i2c_handle_interrupt(iface); > + if (--iface->timeout_count > 0) { > + iface->timeout_timer.expires = jiffies + POLL_TIMEOUT; > + add_timer(&iface->timeout_timer); > + } else { > + complete(&iface->complete); > + I2C_WR16(iface, S6_I2C_INTRMASK, 0); > + } > + spin_unlock_irqrestore(&iface->lock, flags); > +} > + > +static int s6i2c_master_xfer(struct i2c_adapter *adap, > + struct i2c_msg *msgs, int num) > +{ > + struct s6i2c_if *iface = adap->algo_data; > + int i; > + if (num == 0) > + return 0; > + if (I2C_RD16(iface, S6_I2C_STATUS) & (1 << S6_I2C_STATUS_ACTIVITY)) > + yield(); > + I2C_WR16(iface, S6_I2C_INTRMASK, 0); > + I2C_RD16(iface, S6_I2C_CLRINTR); > + for (i = 0; i < num; i++) { > + if (msgs[i].flags & I2C_M_TEN) { > + dev_err(&(adap->dev), > + "s6i2c: 10 bits addr not supported\n"); > + return -EINVAL; > + } > + if (msgs[i].len == 0) { > + dev_err(&(adap->dev), > + "s6i2c: zero length message not supported\n"); > + return -EINVAL; > + } > + if (msgs[i].addr != msgs[0].addr) { > + dev_err(&(adap->dev), > + "s6i2c: multiple xfer cannot change target\n"); > + return -EINVAL; > + } > + } > + iface->msgs = msgs; > + iface->msgs_num = num; > + iface->msgs_push = 0; > + iface->msgs_done = 0; > + iface->push = 0; > + iface->done = 0; > + iface->timeout_count = 10; > + I2C_WR16(iface, S6_I2C_TAR, msgs[0].addr); > + I2C_WR16(iface, S6_I2C_ENABLE, 1); > + I2C_WR16(iface, S6_I2C_INTRMASK, (1 << S6_I2C_INTR_TXEMPTY) | > + (1 << S6_I2C_INTR_TXABRT)); > + iface->timeout_timer.expires = jiffies + POLL_TIMEOUT; > + add_timer(&iface->timeout_timer); > + wait_for_completion(&iface->complete); > + del_timer_sync(&iface->timeout_timer); > + while (I2C_RD32(iface, S6_I2C_TXFLR) > 0) > + schedule(); > + while (I2C_RD16(iface, S6_I2C_STATUS) & (1 << S6_I2C_STATUS_ACTIVITY)) > + schedule(); > + I2C_WR16(iface, S6_I2C_INTRMASK, 0); > + I2C_WR16(iface, S6_I2C_ENABLE, 0); > + return iface->msgs_done; > +} > + > +static u32 s6i2c_functionality(struct i2c_adapter *adap) > +{ > + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; > +} > + > +static struct i2c_algorithm s6i2c_algorithm = { > + .master_xfer = s6i2c_master_xfer, > + .functionality = s6i2c_functionality, > +}; > + > +static u16 __devinit nanoseconds_on_clk(struct s6i2c_if *iface, u32 ns) > +{ > + u64 dividend = (u64)ns * clk_get_rate(iface->clk); > + do_div(dividend, 1000000000); > + if (dividend > 0xffff) > + return 0xffff; > + return dividend; > +} do you desperately need to be _that_ accurate? > +static int __devinit s6i2c_probe(struct platform_device *dev) > +{ > + struct s6i2c_if *iface = &s6i2c_if; > + struct i2c_adapter *p_adap; > + const char *clock; > + int bus_num, rc; > + spin_lock_init(&(iface->lock)); > + init_completion(&(iface->complete)); please don't do &(variable) it is fugly. > + iface->irq = platform_get_irq(dev, 0); > + if (iface->irq < 0) { > + rc = iface->irq; > + goto err_out; > + } > + iface->res = platform_get_resource(dev, IORESOURCE_MEM, 0); > + if (!iface->res) { > + rc = -ENXIO; > + goto err_out; > + } > + iface->res = request_mem_region(iface->res->start, > + iface->res->end - iface->res->start + 1, > + dev->dev.bus_id); > + if (!iface->res) { > + rc = -EBUSY; > + goto err_out; > + } > + iface->reg = ioremap_nocache(iface->res->start, > + iface->res->end - iface->res->start + 1); > + if (!iface->reg) { > + rc = -ENOMEM; > + goto err_reg; > + } > + clock = 0; > + bus_num = -1; > + if (dev->dev.platform_data) { > + struct s6_i2c_platform_data *pdata = dev->dev.platform_data; > + bus_num = pdata->bus_num; > + clock = pdata->clock; > + } > + iface->clk = clk_get(&dev->dev, clock); > + if (IS_ERR(iface->clk)) { > + rc = PTR_ERR(iface->clk); > + goto err_map; > + } passing the name in the platform device is a bit nasty, if there's only one clock for the device then it should have been dealt with at the arch level and doing clk_get(&dev->dev, NULL) should be acceptable. > + rc = clk_enable(iface->clk); > + if (rc < 0) > + goto err_clk_put; > + init_timer(&(iface->timeout_timer)); > + iface->timeout_timer.function = s6i2c_timeout; > + iface->timeout_timer.data = (unsigned long)iface; > + p_adap = &iface->adap; > + strlcpy(p_adap->name, dev->name, sizeof(p_adap->name)); > + p_adap->algo = &s6i2c_algorithm; > + p_adap->algo_data = iface; > + p_adap->nr = bus_num; > + p_adap->class = 0; > + p_adap->dev.parent = &dev->dev; some spacing for readability would be nice. > + I2C_WR16(iface, S6_I2C_INTRMASK, 0); > + rc = request_irq(iface->irq, s6i2c_interrupt_entry, > + IRQF_SHARED, dev->name, iface); > + if (rc) { > + dev_err(&(p_adap->dev), "s6i2c: cant get IRQ %d\n", iface->irq); > + goto err_clk_dis; > + } > + I2C_WR16(iface, S6_I2C_ENABLE, 0); > + udelay(1); > + I2C_WR32(iface, S6_I2C_SRESET, 1 << S6_I2C_SRESET_IC_SRST); > + I2C_WR16(iface, S6_I2C_CLRTXABRT, 1); > + I2C_WR16(iface, S6_I2C_CON, > + (1 << S6_I2C_CON_MASTER) | > + (S6_I2C_CON_SPEED_NORMAL << S6_I2C_CON_SPEED) | > + (0 << S6_I2C_CON_10BITSLAVE) | > + (0 << S6_I2C_CON_10BITMASTER) | > + (1 << S6_I2C_CON_RESTARTENA) | > + (1 << S6_I2C_CON_SLAVEDISABLE)); > + I2C_WR16(iface, S6_I2C_SSHCNT, nanoseconds_on_clk(iface, 4000)); > + I2C_WR16(iface, S6_I2C_SSLCNT, nanoseconds_on_clk(iface, 4700)); > + I2C_WR16(iface, S6_I2C_FSHCNT, nanoseconds_on_clk(iface, 600)); > + I2C_WR16(iface, S6_I2C_FSLCNT, nanoseconds_on_clk(iface, 1300)); > + I2C_WR16(iface, S6_I2C_RXTL, 0); > + I2C_WR16(iface, S6_I2C_TXTL, 0); > + platform_set_drvdata(dev, iface); > + if (bus_num < 0) > + rc = i2c_add_adapter(p_adap); > + else > + rc = i2c_add_numbered_adapter(p_adap); > + if (rc) > + goto err_irq_free; > + return 0; > + > +err_irq_free: > + free_irq(iface->irq, iface); > +err_clk_dis: > + clk_disable(iface->clk); > +err_clk_put: > + clk_put(iface->clk); > +err_map: > + iounmap(iface->reg); > +err_reg: > + release_mem_region(iface->res->start, > + iface->res->end - iface->res->start + 1); > +err_out: > + return rc; > +} > + > +static int __devexit s6i2c_remove(struct platform_device *pdev) > +{ > + struct s6i2c_if *iface = platform_get_drvdata(pdev); > + I2C_WR16(iface, S6_I2C_ENABLE, 0); > + platform_set_drvdata(pdev, NULL); > + i2c_del_adapter(&(iface->adap)); > + free_irq(iface->irq, iface); > + clk_disable(iface->clk); > + clk_put(iface->clk); > + iounmap(iface->reg); > + release_mem_region(iface->res->start, > + iface->res->end - iface->res->start + 1); resource_size() would be good here, and elsewhere in the driver. > + return 0; > +} > + > +static struct platform_driver s6i2c_driver = { > + .probe = s6i2c_probe, > + .remove = __devexit_p(s6i2c_remove), > + .driver = { > + .name = "i2c-s6000", > + .owner = THIS_MODULE, > + }, > +}; > + > +static int __init s6i2c_init(void) > +{ > + pr_info("I2C: S6000 I2C driver\n"); > + return platform_driver_register(&s6i2c_driver); > +} > + > +static void __exit s6i2c_exit(void) > +{ > + platform_driver_unregister(&s6i2c_driver); > +} > + > +MODULE_DESCRIPTION("I2C-Bus adapter routines for S6000 I2C"); > +MODULE_LICENSE("GPL"); > + > +subsys_initcall(s6i2c_init); > +module_exit(s6i2c_exit); as a note, the code is quite dense, a few blank lines would be nice. > diff --git a/drivers/i2c/busses/i2c-s6000.h b/drivers/i2c/busses/i2c-s6000.h > new file mode 100644 > index 0000000..ff23b81 > --- /dev/null > +++ b/drivers/i2c/busses/i2c-s6000.h > @@ -0,0 +1,79 @@ > +/* > + * drivers/i2c/busses/i2c-s6000.h > + * > + * This file is subject to the terms and conditions of the GNU General Public > + * License. See the file "COPYING" in the main directory of this archive > + * for more details. > + * > + * Copyright (C) 2008 Emlix GmbH <info@emlix.com> > + * Author: Oskar Schirmer <os@emlix.com> > + */ > + > +#ifndef __DRIVERS_I2C_BUSSES_I2C_S6000_H > +#define __DRIVERS_I2C_BUSSES_I2C_S6000_H > + > +#define S6_I2C_CON 0x000 > +#define S6_I2C_CON_MASTER 0 > +#define S6_I2C_CON_SPEED 1 > +#define S6_I2C_CON_SPEED_NORMAL 1 > +#define S6_I2C_CON_SPEED_FAST 2 > +#define S6_I2C_CON_SPEED_MASK 3 > +#define S6_I2C_CON_10BITSLAVE 3 > +#define S6_I2C_CON_10BITMASTER 4 > +#define S6_I2C_CON_RESTARTENA 5 > +#define S6_I2C_CON_SLAVEDISABLE 6 > +#define S6_I2C_TAR 0x004 > +#define S6_I2C_TAR_GCORSTART 10 > +#define S6_I2C_TAR_SPECIAL 11 > +#define S6_I2C_SAR 0x008 > +#define S6_I2C_HSMADDR 0x00C > +#define S6_I2C_DATACMD 0x010 > +#define S6_I2C_DATACMD_READ 8 > +#define S6_I2C_SSHCNT 0x014 > +#define S6_I2C_SSLCNT 0x018 > +#define S6_I2C_FSHCNT 0x01C > +#define S6_I2C_FSLCNT 0x020 > +#define S6_I2C_INTRSTAT 0x02C > +#define S6_I2C_INTRMASK 0x030 > +#define S6_I2C_RAWINTR 0x034 > +#define S6_I2C_INTR_RXUNDER 0 > +#define S6_I2C_INTR_RXOVER 1 > +#define S6_I2C_INTR_RXFULL 2 > +#define S6_I2C_INTR_TXOVER 3 > +#define S6_I2C_INTR_TXEMPTY 4 > +#define S6_I2C_INTR_RDREQ 5 > +#define S6_I2C_INTR_TXABRT 6 > +#define S6_I2C_INTR_RXDONE 7 > +#define S6_I2C_INTR_ACTIVITY 8 > +#define S6_I2C_INTR_STOPDET 9 > +#define S6_I2C_INTR_STARTDET 10 > +#define S6_I2C_INTR_GENCALL 11 > +#define S6_I2C_RXTL 0x038 > +#define S6_I2C_TXTL 0x03C > +#define S6_I2C_CLRINTR 0x040 > +#define S6_I2C_CLRRXUNDER 0x044 > +#define S6_I2C_CLRRXOVER 0x048 > +#define S6_I2C_CLRTXOVER 0x04C > +#define S6_I2C_CLRRDREQ 0x050 > +#define S6_I2C_CLRTXABRT 0x054 > +#define S6_I2C_CLRRXDONE 0x058 > +#define S6_I2C_CLRACTIVITY 0x05C > +#define S6_I2C_CLRSTOPDET 0x060 > +#define S6_I2C_CLRSTARTDET 0x064 > +#define S6_I2C_CLRGENCALL 0x068 > +#define S6_I2C_ENABLE 0x06C > +#define S6_I2C_STATUS 0x070 > +#define S6_I2C_STATUS_ACTIVITY 0 > +#define S6_I2C_STATUS_TFNF 1 > +#define S6_I2C_STATUS_TFE 2 > +#define S6_I2C_STATUS_RFNE 3 > +#define S6_I2C_STATUS_RFF 4 > +#define S6_I2C_TXFLR 0x074 > +#define S6_I2C_RXFLR 0x078 > +#define S6_I2C_SRESET 0x07C > +#define S6_I2C_SRESET_IC_SRST 0 > +#define S6_I2C_SRESET_IC_MASTER_SRST 1 > +#define S6_I2C_SRESET_IC_SLAVE_SRST 2 > +#define S6_I2C_TXABRTSOURCE 0x080 > + > +#endif > diff --git a/include/linux/i2c/s6000.h b/include/linux/i2c/s6000.h and again. > new file mode 100644 > index 0000000..0b6b0c6 > --- /dev/null > +++ b/include/linux/i2c/s6000.h > @@ -0,0 +1,10 @@ > +#ifndef __LINUX_I2C_S6000_H > +#define __LINUX_I2C_S6000_H > + documentation comments would be nice here too. > +struct s6_i2c_platform_data { > + const char *clock; > + int bus_num; > +}; > + > +#endif > + > -- > 1.6.2.107.ge47ee > > -- > To unsubscribe from this list: send the line "unsubscribe linux-i2c" in > the body of a message to majordomo@vger.kernel.org > More majordomo info at http://vger.kernel.org/majordomo-info.html -- Ben (ben@fluff.org, http://www.fluff.org/) 'a smiley only costs 4 bytes' ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [patch 1/5] i2c: xtensa s6000 i2c driver 2009-03-31 22:52 ` [patch 1/5] i2c: xtensa s6000 i2c driver Ben Dooks @ 2009-03-31 23:01 ` Ben Dooks 2009-04-02 11:19 ` [patch v2] " Oskar Schirmer 0 siblings, 1 reply; 8+ messages in thread From: Ben Dooks @ 2009-03-31 23:01 UTC (permalink / raw) To: Ben Dooks Cc: Daniel Gl??ckner, Chris Zankel, linux-i2c, linux-kernel, Oskar Schirmer On Tue, Mar 31, 2009 at 11:52:58PM +0100, Ben Dooks wrote: > On Mon, Mar 23, 2009 at 04:05:14PM +0100, Daniel Gl??ckner wrote: > > From: Oskar Schirmer <os@emlix.com> > > > > Support for the s6000 on-chip i2c controller. > > > > Signed-off-by: Oskar Schirmer <os@emlix.com> > > Signed-off-by: Daniel Gl??ckner <dg@emlix.com> > > Not going to merge this on my first pull request to linus, there's > a few things that I'd like to get answered before. and a few other things i've just noticed: > > --- > > drivers/i2c/busses/Kconfig | 10 + > > drivers/i2c/busses/Makefile | 1 + > > drivers/i2c/busses/i2c-s6000.c | 380 ++++++++++++++++++++++++++++++++++++++++ > > drivers/i2c/busses/i2c-s6000.h | 79 +++++++++ > > include/linux/i2c/s6000.h | 10 + > > 5 files changed, 480 insertions(+), 0 deletions(-) > > create mode 100644 drivers/i2c/busses/i2c-s6000.c > > create mode 100644 drivers/i2c/busses/i2c-s6000.h > > create mode 100644 include/linux/i2c/s6000.h > > > > diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig > > index 7f95905..904def4 100644 > > --- a/drivers/i2c/busses/Kconfig > > +++ b/drivers/i2c/busses/Kconfig > > @@ -461,6 +461,16 @@ config I2C_S3C2410 > > Say Y here to include support for I2C controller in the > > Samsung S3C2410 based System-on-Chip devices. > > > > +config I2C_S6000 > > + tristate "S6000 I2C support" > > + depends on XTENSA_VARIANT_S6000 > > + help > > + This driver supports the on chip I2C device on the > > + S6000 xtensa processor family. > > + > > + To compile this driver as a module, choose M here. The module > > + will be called i2c-s6000. > > + > > config I2C_SH7760 > > tristate "Renesas SH7760 I2C Controller" > > depends on CPU_SUBTYPE_SH7760 > > diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile > > index 0c2c4b2..f7989d1 100644 > > --- a/drivers/i2c/busses/Makefile > > +++ b/drivers/i2c/busses/Makefile > > @@ -43,6 +43,7 @@ obj-$(CONFIG_I2C_PASEMI) += i2c-pasemi.o > > obj-$(CONFIG_I2C_PNX) += i2c-pnx.o > > obj-$(CONFIG_I2C_PXA) += i2c-pxa.o > > obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o > > +obj-$(CONFIG_I2C_S6000) += i2c-s6000.o > > obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o > > obj-$(CONFIG_I2C_SH_MOBILE) += i2c-sh_mobile.o > > obj-$(CONFIG_I2C_SIMTEC) += i2c-simtec.o > > diff --git a/drivers/i2c/busses/i2c-s6000.c b/drivers/i2c/busses/i2c-s6000.c > > new file mode 100644 > > index 0000000..88e3fdc > > --- /dev/null > > +++ b/drivers/i2c/busses/i2c-s6000.c > > @@ -0,0 +1,380 @@ > > +/* > > + * drivers/i2c/busses/i2c-s6000.c > > + * > > + * Description: Driver for S6000 Family I2C Interface > > + * (c) 2008 emlix GmbH <info@emlix.com> > > Note, I think you need Copyright here as well, (c) needs to be a > proper c in circle i've been informed. > > > + * Author: Oskar Schirmer <os@emlix.com> > > + * > > + * Partially based on i2c-bfin-twi.c driver by <sonic.zhang@analog.com> > > + * Copyright (c) 2005-2007 Analog Devices, Inc. > > + * > > + * 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/clk.h> > > +#include <linux/err.h> > > +#include <linux/module.h> > > +#include <linux/kernel.h> > > +#include <linux/init.h> > > +#include <linux/delay.h> > > +#include <linux/i2c.h> > > +#include <linux/i2c/s6000.h> > > +#include <linux/timer.h> > > +#include <linux/spinlock.h> > > +#include <linux/completion.h> > > +#include <linux/interrupt.h> > > +#include <linux/platform_device.h> > > + > > +#include <asm/io.h> > > +#include "i2c-s6000.h" > > + > > +#define POLL_TIMEOUT (2 * HZ) > > + > > would be nice to have some documentation for each of the members > of this. > > > +struct s6i2c_if { > > + u8 __iomem *reg; > > + int irq; > > + spinlock_t lock; > > + struct i2c_msg *msgs; > > + int msgs_num, msgs_push, msgs_done; > > + unsigned push, done; > > + int timeout_count; > > + struct timer_list timeout_timer; > > + struct i2c_adapter adap; > > + struct completion complete; > > + struct clk *clk; > > + struct resource *res; > > +}; > > + > > +#define I2C_RD16(iface, n) readw(iface->reg + (n)) > > +#define I2C_WR16(iface, n, v) writew(v, iface->reg + (n)) > > +#define I2C_RD32(iface, n) readl(iface->reg + (n)) > > +#define I2C_WR32(iface, n, v) writel(v, iface->reg + (n)) > > tried inline functions for these? > > > + > > +static struct s6i2c_if s6i2c_if; > > + > > +static void s6i2c_handle_interrupt(struct s6i2c_if *iface) > > +{ > > + if (I2C_RD16(iface, S6_I2C_INTRSTAT) & (1 << S6_I2C_INTR_TXABRT)) { > > + I2C_RD16(iface, S6_I2C_CLRTXABRT); > > + I2C_WR16(iface, S6_I2C_INTRMASK, 0); > > + complete(&iface->complete); > > + return; > > + } > > + if (iface->msgs_done >= iface->msgs_num) { > > + printk(KERN_DEBUG "spurious I2C irq: %x\n", > > + I2C_RD16(iface, S6_I2C_INTRSTAT)); storing the device and using dev_ functions would be much better. > > + I2C_WR16(iface, S6_I2C_INTRMASK, 0); > > + return; > > + } > > + while ((iface->msgs_push < iface->msgs_num) > > + && (I2C_RD16(iface, S6_I2C_STATUS) & (1 << S6_I2C_STATUS_TFNF))) { > > + struct i2c_msg *m = &iface->msgs[iface->msgs_push]; > > + if (!(m->flags & I2C_M_RD)) > > + I2C_WR16(iface, S6_I2C_DATACMD, m->buf[iface->push]); > > + else > > + I2C_WR16(iface, S6_I2C_DATACMD, > > + 1 << S6_I2C_DATACMD_READ); > > + if (++iface->push >= m->len) { > > + iface->push = 0; > > + iface->msgs_push += 1; > > + } > > + } > > + do { > > + struct i2c_msg *m = &iface->msgs[iface->msgs_done]; > > + if (!(m->flags & I2C_M_RD)) { > > + if (iface->msgs_done < iface->msgs_push) > > + iface->msgs_done += 1; > > + else > > + break; > > + } else if (I2C_RD16(iface, S6_I2C_STATUS) > > + & (1 << S6_I2C_STATUS_RFNE)) { > > + m->buf[iface->done] = I2C_RD16(iface, S6_I2C_DATACMD); > > + if (++iface->done >= m->len) { > > + iface->done = 0; > > + iface->msgs_done += 1; > > + } > > + } else{ > > + break; > > + } > > + } while (iface->msgs_done < iface->msgs_num); > > + if (iface->msgs_done >= iface->msgs_num) { > > + I2C_WR16(iface, S6_I2C_INTRMASK, 1 << S6_I2C_INTR_TXABRT); > > + complete(&iface->complete); > > + } else if (iface->msgs_push >= iface->msgs_num) { > > + I2C_WR16(iface, S6_I2C_INTRMASK, (1 << S6_I2C_INTR_TXABRT) | > > + (1 << S6_I2C_INTR_RXFULL)); > > + } else { > > + I2C_WR16(iface, S6_I2C_INTRMASK, (1 << S6_I2C_INTR_TXABRT) | > > + (1 << S6_I2C_INTR_TXEMPTY) | > > + (1 << S6_I2C_INTR_RXFULL)); > > + } > > +} > > + > > +static irqreturn_t s6i2c_interrupt_entry(int irq, void *dev_id) > > +{ > > + struct s6i2c_if *iface = dev_id; > > + if (!(I2C_RD16(iface, S6_I2C_STATUS) & ((1 << S6_I2C_INTR_RXUNDER) > > + | (1 << S6_I2C_INTR_RXOVER) > > + | (1 << S6_I2C_INTR_RXFULL) > > + | (1 << S6_I2C_INTR_TXOVER) > > + | (1 << S6_I2C_INTR_TXEMPTY) > > + | (1 << S6_I2C_INTR_RDREQ) > > + | (1 << S6_I2C_INTR_TXABRT) > > + | (1 << S6_I2C_INTR_RXDONE) > > + | (1 << S6_I2C_INTR_ACTIVITY) > > + | (1 << S6_I2C_INTR_STOPDET) > > + | (1 << S6_I2C_INTR_STARTDET) > > + | (1 << S6_I2C_INTR_GENCALL)))) > > + return IRQ_NONE; > > + > > + spin_lock(&iface->lock); > > + del_timer(&iface->timeout_timer); > > + s6i2c_handle_interrupt(iface); > > + spin_unlock(&iface->lock); > > + return IRQ_HANDLED; > > +} > > + > > +static void s6i2c_timeout(unsigned long data) > > +{ > > + struct s6i2c_if *iface = (struct s6i2c_if *)data; > > + unsigned long flags; > > + > > + spin_lock_irqsave(&iface->lock, flags); > > + s6i2c_handle_interrupt(iface); > > + if (--iface->timeout_count > 0) { > > + iface->timeout_timer.expires = jiffies + POLL_TIMEOUT; > > + add_timer(&iface->timeout_timer); > > + } else { > > + complete(&iface->complete); > > + I2C_WR16(iface, S6_I2C_INTRMASK, 0); > > + } > > + spin_unlock_irqrestore(&iface->lock, flags); > > +} > > + > > +static int s6i2c_master_xfer(struct i2c_adapter *adap, > > + struct i2c_msg *msgs, int num) > > +{ > > + struct s6i2c_if *iface = adap->algo_data; > > + int i; > > + if (num == 0) > > + return 0; > > + if (I2C_RD16(iface, S6_I2C_STATUS) & (1 << S6_I2C_STATUS_ACTIVITY)) > > + yield(); > > + I2C_WR16(iface, S6_I2C_INTRMASK, 0); > > + I2C_RD16(iface, S6_I2C_CLRINTR); > > + for (i = 0; i < num; i++) { > > + if (msgs[i].flags & I2C_M_TEN) { > > + dev_err(&(adap->dev), > > + "s6i2c: 10 bits addr not supported\n"); > > + return -EINVAL; > > + } > > + if (msgs[i].len == 0) { > > + dev_err(&(adap->dev), > > + "s6i2c: zero length message not supported\n"); > > + return -EINVAL; > > + } > > + if (msgs[i].addr != msgs[0].addr) { > > + dev_err(&(adap->dev), > > + "s6i2c: multiple xfer cannot change target\n"); > > + return -EINVAL; > > + } > > + } > > + iface->msgs = msgs; > > + iface->msgs_num = num; > > + iface->msgs_push = 0; > > + iface->msgs_done = 0; > > + iface->push = 0; > > + iface->done = 0; > > + iface->timeout_count = 10; > > + I2C_WR16(iface, S6_I2C_TAR, msgs[0].addr); > > + I2C_WR16(iface, S6_I2C_ENABLE, 1); > > + I2C_WR16(iface, S6_I2C_INTRMASK, (1 << S6_I2C_INTR_TXEMPTY) | > > + (1 << S6_I2C_INTR_TXABRT)); > > + iface->timeout_timer.expires = jiffies + POLL_TIMEOUT; > > + add_timer(&iface->timeout_timer); > > + wait_for_completion(&iface->complete); > > + del_timer_sync(&iface->timeout_timer); > > + while (I2C_RD32(iface, S6_I2C_TXFLR) > 0) > > + schedule(); > > + while (I2C_RD16(iface, S6_I2C_STATUS) & (1 << S6_I2C_STATUS_ACTIVITY)) > > + schedule(); > > + I2C_WR16(iface, S6_I2C_INTRMASK, 0); > > + I2C_WR16(iface, S6_I2C_ENABLE, 0); > > + return iface->msgs_done; > > +} > > + > > +static u32 s6i2c_functionality(struct i2c_adapter *adap) > > +{ > > + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; > > +} > > + > > +static struct i2c_algorithm s6i2c_algorithm = { > > + .master_xfer = s6i2c_master_xfer, > > + .functionality = s6i2c_functionality, > > +}; > > + > > +static u16 __devinit nanoseconds_on_clk(struct s6i2c_if *iface, u32 ns) > > +{ > > + u64 dividend = (u64)ns * clk_get_rate(iface->clk); > > + do_div(dividend, 1000000000); > > + if (dividend > 0xffff) > > + return 0xffff; > > + return dividend; > > +} > > do you desperately need to be _that_ accurate? > > > +static int __devinit s6i2c_probe(struct platform_device *dev) > > +{ > > + struct s6i2c_if *iface = &s6i2c_if; > > + struct i2c_adapter *p_adap; > > + const char *clock; > > + int bus_num, rc; > > + spin_lock_init(&(iface->lock)); > > + init_completion(&(iface->complete)); > > please don't do &(variable) it is fugly. > > > + iface->irq = platform_get_irq(dev, 0); > > + if (iface->irq < 0) { > > + rc = iface->irq; > > + goto err_out; > > + } > > + iface->res = platform_get_resource(dev, IORESOURCE_MEM, 0); > > + if (!iface->res) { > > + rc = -ENXIO; > > + goto err_out; > > + } > > + iface->res = request_mem_region(iface->res->start, > > + iface->res->end - iface->res->start + 1, > > + dev->dev.bus_id); resource_size() is your friend here too, and in various others bits of the code. > > + if (!iface->res) { > > + rc = -EBUSY; > > + goto err_out; > > + } > > + iface->reg = ioremap_nocache(iface->res->start, > > + iface->res->end - iface->res->start + 1); > > + if (!iface->reg) { > > + rc = -ENOMEM; > > + goto err_reg; > > + } > > + clock = 0; > > + bus_num = -1; > > + if (dev->dev.platform_data) { > > + struct s6_i2c_platform_data *pdata = dev->dev.platform_data; > > + bus_num = pdata->bus_num; > > + clock = pdata->clock; > > + } > > + iface->clk = clk_get(&dev->dev, clock); > > + if (IS_ERR(iface->clk)) { > > + rc = PTR_ERR(iface->clk); > > + goto err_map; > > + } > > passing the name in the platform device is a bit nasty, if there's > only one clock for the device then it should have been dealt with > at the arch level and doing clk_get(&dev->dev, NULL) should be > acceptable. > > > + rc = clk_enable(iface->clk); > > + if (rc < 0) > > + goto err_clk_put; > > + init_timer(&(iface->timeout_timer)); > > + iface->timeout_timer.function = s6i2c_timeout; > > + iface->timeout_timer.data = (unsigned long)iface; > > + p_adap = &iface->adap; > > + strlcpy(p_adap->name, dev->name, sizeof(p_adap->name)); > > + p_adap->algo = &s6i2c_algorithm; > > + p_adap->algo_data = iface; > > + p_adap->nr = bus_num; > > + p_adap->class = 0; > > + p_adap->dev.parent = &dev->dev; > > some spacing for readability would be nice. > > > + I2C_WR16(iface, S6_I2C_INTRMASK, 0); > > + rc = request_irq(iface->irq, s6i2c_interrupt_entry, > > + IRQF_SHARED, dev->name, iface); > > + if (rc) { > > + dev_err(&(p_adap->dev), "s6i2c: cant get IRQ %d\n", iface->irq); > > + goto err_clk_dis; > > + } > > + I2C_WR16(iface, S6_I2C_ENABLE, 0); > > + udelay(1); > > + I2C_WR32(iface, S6_I2C_SRESET, 1 << S6_I2C_SRESET_IC_SRST); > > + I2C_WR16(iface, S6_I2C_CLRTXABRT, 1); > > + I2C_WR16(iface, S6_I2C_CON, > > + (1 << S6_I2C_CON_MASTER) | > > + (S6_I2C_CON_SPEED_NORMAL << S6_I2C_CON_SPEED) | > > + (0 << S6_I2C_CON_10BITSLAVE) | > > + (0 << S6_I2C_CON_10BITMASTER) | > > + (1 << S6_I2C_CON_RESTARTENA) | > > + (1 << S6_I2C_CON_SLAVEDISABLE)); > > + I2C_WR16(iface, S6_I2C_SSHCNT, nanoseconds_on_clk(iface, 4000)); > > + I2C_WR16(iface, S6_I2C_SSLCNT, nanoseconds_on_clk(iface, 4700)); > > + I2C_WR16(iface, S6_I2C_FSHCNT, nanoseconds_on_clk(iface, 600)); > > + I2C_WR16(iface, S6_I2C_FSLCNT, nanoseconds_on_clk(iface, 1300)); > > + I2C_WR16(iface, S6_I2C_RXTL, 0); > > + I2C_WR16(iface, S6_I2C_TXTL, 0); > > + platform_set_drvdata(dev, iface); > > + if (bus_num < 0) > > + rc = i2c_add_adapter(p_adap); > > + else > > + rc = i2c_add_numbered_adapter(p_adap); > > + if (rc) > > + goto err_irq_free; > > + return 0; > > + > > +err_irq_free: > > + free_irq(iface->irq, iface); > > +err_clk_dis: > > + clk_disable(iface->clk); > > +err_clk_put: > > + clk_put(iface->clk); > > +err_map: > > + iounmap(iface->reg); > > +err_reg: > > + release_mem_region(iface->res->start, > > + iface->res->end - iface->res->start + 1); > > +err_out: > > + return rc; > > +} > > + > > +static int __devexit s6i2c_remove(struct platform_device *pdev) > > +{ > > + struct s6i2c_if *iface = platform_get_drvdata(pdev); > > + I2C_WR16(iface, S6_I2C_ENABLE, 0); > > + platform_set_drvdata(pdev, NULL); > > + i2c_del_adapter(&(iface->adap)); > > + free_irq(iface->irq, iface); > > + clk_disable(iface->clk); > > + clk_put(iface->clk); > > + iounmap(iface->reg); > > + release_mem_region(iface->res->start, > > + iface->res->end - iface->res->start + 1); > > resource_size() would be good here, and elsewhere in the driver. > > > + return 0; > > +} > > + > > +static struct platform_driver s6i2c_driver = { > > + .probe = s6i2c_probe, > > + .remove = __devexit_p(s6i2c_remove), > > + .driver = { > > + .name = "i2c-s6000", > > + .owner = THIS_MODULE, > > + }, > > +}; > > + > > +static int __init s6i2c_init(void) > > +{ > > + pr_info("I2C: S6000 I2C driver\n"); > > + return platform_driver_register(&s6i2c_driver); > > +} > > + > > +static void __exit s6i2c_exit(void) > > +{ > > + platform_driver_unregister(&s6i2c_driver); > > +} > > + > > +MODULE_DESCRIPTION("I2C-Bus adapter routines for S6000 I2C"); > > +MODULE_LICENSE("GPL"); alias for module loading would also be useful. > > + > > +subsys_initcall(s6i2c_init); > > +module_exit(s6i2c_exit); > as a note, the code is quite dense, a few blank lines would be > nice. > > > diff --git a/drivers/i2c/busses/i2c-s6000.h b/drivers/i2c/busses/i2c-s6000.h > > new file mode 100644 > > index 0000000..ff23b81 > > --- /dev/null > > +++ b/drivers/i2c/busses/i2c-s6000.h > > @@ -0,0 +1,79 @@ > > +/* > > + * drivers/i2c/busses/i2c-s6000.h > > + * > > + * This file is subject to the terms and conditions of the GNU General Public > > + * License. See the file "COPYING" in the main directory of this archive > > + * for more details. > > + * > > + * Copyright (C) 2008 Emlix GmbH <info@emlix.com> > > + * Author: Oskar Schirmer <os@emlix.com> > > + */ > > + > > +#ifndef __DRIVERS_I2C_BUSSES_I2C_S6000_H > > +#define __DRIVERS_I2C_BUSSES_I2C_S6000_H > > + > > +#define S6_I2C_CON 0x000 > > +#define S6_I2C_CON_MASTER 0 > > +#define S6_I2C_CON_SPEED 1 > > +#define S6_I2C_CON_SPEED_NORMAL 1 > > +#define S6_I2C_CON_SPEED_FAST 2 > > +#define S6_I2C_CON_SPEED_MASK 3 > > +#define S6_I2C_CON_10BITSLAVE 3 > > +#define S6_I2C_CON_10BITMASTER 4 > > +#define S6_I2C_CON_RESTARTENA 5 > > +#define S6_I2C_CON_SLAVEDISABLE 6 > > +#define S6_I2C_TAR 0x004 > > +#define S6_I2C_TAR_GCORSTART 10 > > +#define S6_I2C_TAR_SPECIAL 11 > > +#define S6_I2C_SAR 0x008 > > +#define S6_I2C_HSMADDR 0x00C > > +#define S6_I2C_DATACMD 0x010 > > +#define S6_I2C_DATACMD_READ 8 > > +#define S6_I2C_SSHCNT 0x014 > > +#define S6_I2C_SSLCNT 0x018 > > +#define S6_I2C_FSHCNT 0x01C > > +#define S6_I2C_FSLCNT 0x020 > > +#define S6_I2C_INTRSTAT 0x02C > > +#define S6_I2C_INTRMASK 0x030 > > +#define S6_I2C_RAWINTR 0x034 > > +#define S6_I2C_INTR_RXUNDER 0 > > +#define S6_I2C_INTR_RXOVER 1 > > +#define S6_I2C_INTR_RXFULL 2 > > +#define S6_I2C_INTR_TXOVER 3 > > +#define S6_I2C_INTR_TXEMPTY 4 > > +#define S6_I2C_INTR_RDREQ 5 > > +#define S6_I2C_INTR_TXABRT 6 > > +#define S6_I2C_INTR_RXDONE 7 > > +#define S6_I2C_INTR_ACTIVITY 8 > > +#define S6_I2C_INTR_STOPDET 9 > > +#define S6_I2C_INTR_STARTDET 10 > > +#define S6_I2C_INTR_GENCALL 11 > > +#define S6_I2C_RXTL 0x038 > > +#define S6_I2C_TXTL 0x03C > > +#define S6_I2C_CLRINTR 0x040 > > +#define S6_I2C_CLRRXUNDER 0x044 > > +#define S6_I2C_CLRRXOVER 0x048 > > +#define S6_I2C_CLRTXOVER 0x04C > > +#define S6_I2C_CLRRDREQ 0x050 > > +#define S6_I2C_CLRTXABRT 0x054 > > +#define S6_I2C_CLRRXDONE 0x058 > > +#define S6_I2C_CLRACTIVITY 0x05C > > +#define S6_I2C_CLRSTOPDET 0x060 > > +#define S6_I2C_CLRSTARTDET 0x064 > > +#define S6_I2C_CLRGENCALL 0x068 > > +#define S6_I2C_ENABLE 0x06C > > +#define S6_I2C_STATUS 0x070 > > +#define S6_I2C_STATUS_ACTIVITY 0 > > +#define S6_I2C_STATUS_TFNF 1 > > +#define S6_I2C_STATUS_TFE 2 > > +#define S6_I2C_STATUS_RFNE 3 > > +#define S6_I2C_STATUS_RFF 4 > > +#define S6_I2C_TXFLR 0x074 > > +#define S6_I2C_RXFLR 0x078 > > +#define S6_I2C_SRESET 0x07C > > +#define S6_I2C_SRESET_IC_SRST 0 > > +#define S6_I2C_SRESET_IC_MASTER_SRST 1 > > +#define S6_I2C_SRESET_IC_SLAVE_SRST 2 > > +#define S6_I2C_TXABRTSOURCE 0x080 > > + > > +#endif > > diff --git a/include/linux/i2c/s6000.h b/include/linux/i2c/s6000.h > > and again. > > > new file mode 100644 > > index 0000000..0b6b0c6 > > --- /dev/null > > +++ b/include/linux/i2c/s6000.h > > @@ -0,0 +1,10 @@ > > +#ifndef __LINUX_I2C_S6000_H > > +#define __LINUX_I2C_S6000_H > > + > > documentation comments would be nice here too. > > > +struct s6_i2c_platform_data { > > + const char *clock; > > + int bus_num; > > +}; > > + > > +#endif > > + > > -- > > 1.6.2.107.ge47ee > > > > -- > > To unsubscribe from this list: send the line "unsubscribe linux-i2c" in > > the body of a message to majordomo@vger.kernel.org > > More majordomo info at http://vger.kernel.org/majordomo-info.html > > -- > Ben (ben@fluff.org, http://www.fluff.org/) > > 'a smiley only costs 4 bytes' > -- > To unsubscribe from this list: send the line "unsubscribe linux-i2c" in > the body of a message to majordomo@vger.kernel.org > More majordomo info at http://vger.kernel.org/majordomo-info.html -- Ben (ben@fluff.org, http://www.fluff.org/) 'a smiley only costs 4 bytes' ^ permalink raw reply [flat|nested] 8+ messages in thread
* [patch v2] i2c: xtensa s6000 i2c driver 2009-03-31 23:01 ` Ben Dooks @ 2009-04-02 11:19 ` Oskar Schirmer 0 siblings, 0 replies; 8+ messages in thread From: Oskar Schirmer @ 2009-04-02 11:19 UTC (permalink / raw) To: Ben Dooks Cc: Daniel Gloeckner, Chris Zankel, linux-i2c, linux-kernel, Oskar Schirmer Support for the s6000 on-chip i2c controller. Signed-off-by: Oskar Schirmer <os@emlix.com> Signed-off-by: Daniel Glöckner <dg@emlix.com> --- drivers/i2c/busses/Kconfig | 10 + drivers/i2c/busses/Makefile | 1 + drivers/i2c/busses/i2c-s6000.c | 407 ++++++++++++++++++++++++++++++++++++++++ drivers/i2c/busses/i2c-s6000.h | 79 ++++++++ include/linux/i2c/s6000.h | 10 + 5 files changed, 507 insertions(+), 0 deletions(-) create mode 100644 drivers/i2c/busses/i2c-s6000.c create mode 100644 drivers/i2c/busses/i2c-s6000.h create mode 100644 include/linux/i2c/s6000.h Version 2: o fix copyright notice o document interface state structure o translate io helpers from cpp to c o use dev_printk() o dropped parens in '&(expression)' o use resource_size() o added common i2c module alias o newlines between logically separate blocks o clock-to-ns calculation: less accurate, much less object code Ben, we left the clk-by-name thing in there. The clk API is an abstraction of the platform and some implementations don't even support passing NULL as a name (sh, some arms, avr32, mips, ...). Passing in a string works everywhere so we are on the safe side. Any objections? diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 7f95905..904def4 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -461,6 +461,16 @@ config I2C_S3C2410 Say Y here to include support for I2C controller in the Samsung S3C2410 based System-on-Chip devices. +config I2C_S6000 + tristate "S6000 I2C support" + depends on XTENSA_VARIANT_S6000 + help + This driver supports the on chip I2C device on the + S6000 xtensa processor family. + + To compile this driver as a module, choose M here. The module + will be called i2c-s6000. + config I2C_SH7760 tristate "Renesas SH7760 I2C Controller" depends on CPU_SUBTYPE_SH7760 diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index 0c2c4b2..f7989d1 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -43,6 +43,7 @@ obj-$(CONFIG_I2C_PASEMI) += i2c-pasemi.o obj-$(CONFIG_I2C_PNX) += i2c-pnx.o obj-$(CONFIG_I2C_PXA) += i2c-pxa.o obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o +obj-$(CONFIG_I2C_S6000) += i2c-s6000.o obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o obj-$(CONFIG_I2C_SH_MOBILE) += i2c-sh_mobile.o obj-$(CONFIG_I2C_SIMTEC) += i2c-simtec.o diff --git a/drivers/i2c/busses/i2c-s6000.c b/drivers/i2c/busses/i2c-s6000.c new file mode 100644 index 0000000..c91359f --- /dev/null +++ b/drivers/i2c/busses/i2c-s6000.c @@ -0,0 +1,407 @@ +/* + * drivers/i2c/busses/i2c-s6000.c + * + * Description: Driver for S6000 Family I2C Interface + * Copyright (c) 2008 emlix GmbH + * Author: Oskar Schirmer <os@emlix.com> + * + * Partially based on i2c-bfin-twi.c driver by <sonic.zhang@analog.com> + * Copyright (c) 2005-2007 Analog Devices, Inc. + * + * 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/clk.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/i2c/s6000.h> +#include <linux/timer.h> +#include <linux/spinlock.h> +#include <linux/completion.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> + +#include <asm/io.h> +#include "i2c-s6000.h" + +#define DRV_NAME "i2c-s6000" + +#define POLL_TIMEOUT (2 * HZ) + +struct s6i2c_if { + u8 __iomem *reg; /* memory mapped registers */ + int irq; + spinlock_t lock; + struct i2c_msg *msgs; /* messages currently handled */ + int msgs_num; /* nb of msgs to do */ + int msgs_push; /* nb of msgs read/written */ + int msgs_done; /* nb of msgs finally handled */ + unsigned push; /* nb of bytes read/written in msg */ + unsigned done; /* nb of bytes finally handled */ + int timeout_count; /* timeout retries left */ + struct timer_list timeout_timer; + struct i2c_adapter adap; + struct completion complete; + struct clk *clk; + struct resource *res; +}; + +static inline u16 i2c_rd16(struct s6i2c_if *iface, unsigned n) +{ + return readw(iface->reg + (n)); +} + +static inline void i2c_wr16(struct s6i2c_if *iface, unsigned n, u16 v) +{ + writew(v, iface->reg + (n)); +} + +static inline u32 i2c_rd32(struct s6i2c_if *iface, unsigned n) +{ + return readl(iface->reg + (n)); +} + +static inline void i2c_wr32(struct s6i2c_if *iface, unsigned n, u32 v) +{ + writel(v, iface->reg + (n)); +} + +static struct s6i2c_if s6i2c_if; + +static void s6i2c_handle_interrupt(struct s6i2c_if *iface) +{ + if (i2c_rd16(iface, S6_I2C_INTRSTAT) & (1 << S6_I2C_INTR_TXABRT)) { + i2c_rd16(iface, S6_I2C_CLRTXABRT); + i2c_wr16(iface, S6_I2C_INTRMASK, 0); + complete(&iface->complete); + return; + } + if (iface->msgs_done >= iface->msgs_num) { + dev_err(&iface->adap.dev, "s6i2c: spurious I2C irq: %04x\n", + i2c_rd16(iface, S6_I2C_INTRSTAT)); + i2c_wr16(iface, S6_I2C_INTRMASK, 0); + return; + } + while ((iface->msgs_push < iface->msgs_num) + && (i2c_rd16(iface, S6_I2C_STATUS) & (1 << S6_I2C_STATUS_TFNF))) { + struct i2c_msg *m = &iface->msgs[iface->msgs_push]; + if (!(m->flags & I2C_M_RD)) + i2c_wr16(iface, S6_I2C_DATACMD, m->buf[iface->push]); + else + i2c_wr16(iface, S6_I2C_DATACMD, + 1 << S6_I2C_DATACMD_READ); + if (++iface->push >= m->len) { + iface->push = 0; + iface->msgs_push += 1; + } + } + do { + struct i2c_msg *m = &iface->msgs[iface->msgs_done]; + if (!(m->flags & I2C_M_RD)) { + if (iface->msgs_done < iface->msgs_push) + iface->msgs_done += 1; + else + break; + } else if (i2c_rd16(iface, S6_I2C_STATUS) + & (1 << S6_I2C_STATUS_RFNE)) { + m->buf[iface->done] = i2c_rd16(iface, S6_I2C_DATACMD); + if (++iface->done >= m->len) { + iface->done = 0; + iface->msgs_done += 1; + } + } else{ + break; + } + } while (iface->msgs_done < iface->msgs_num); + if (iface->msgs_done >= iface->msgs_num) { + i2c_wr16(iface, S6_I2C_INTRMASK, 1 << S6_I2C_INTR_TXABRT); + complete(&iface->complete); + } else if (iface->msgs_push >= iface->msgs_num) { + i2c_wr16(iface, S6_I2C_INTRMASK, (1 << S6_I2C_INTR_TXABRT) | + (1 << S6_I2C_INTR_RXFULL)); + } else { + i2c_wr16(iface, S6_I2C_INTRMASK, (1 << S6_I2C_INTR_TXABRT) | + (1 << S6_I2C_INTR_TXEMPTY) | + (1 << S6_I2C_INTR_RXFULL)); + } +} + +static irqreturn_t s6i2c_interrupt_entry(int irq, void *dev_id) +{ + struct s6i2c_if *iface = dev_id; + if (!(i2c_rd16(iface, S6_I2C_STATUS) & ((1 << S6_I2C_INTR_RXUNDER) + | (1 << S6_I2C_INTR_RXOVER) + | (1 << S6_I2C_INTR_RXFULL) + | (1 << S6_I2C_INTR_TXOVER) + | (1 << S6_I2C_INTR_TXEMPTY) + | (1 << S6_I2C_INTR_RDREQ) + | (1 << S6_I2C_INTR_TXABRT) + | (1 << S6_I2C_INTR_RXDONE) + | (1 << S6_I2C_INTR_ACTIVITY) + | (1 << S6_I2C_INTR_STOPDET) + | (1 << S6_I2C_INTR_STARTDET) + | (1 << S6_I2C_INTR_GENCALL)))) + return IRQ_NONE; + + spin_lock(&iface->lock); + del_timer(&iface->timeout_timer); + s6i2c_handle_interrupt(iface); + spin_unlock(&iface->lock); + return IRQ_HANDLED; +} + +static void s6i2c_timeout(unsigned long data) +{ + struct s6i2c_if *iface = (struct s6i2c_if *)data; + unsigned long flags; + + spin_lock_irqsave(&iface->lock, flags); + s6i2c_handle_interrupt(iface); + if (--iface->timeout_count > 0) { + iface->timeout_timer.expires = jiffies + POLL_TIMEOUT; + add_timer(&iface->timeout_timer); + } else { + complete(&iface->complete); + i2c_wr16(iface, S6_I2C_INTRMASK, 0); + } + spin_unlock_irqrestore(&iface->lock, flags); +} + +static int s6i2c_master_xfer(struct i2c_adapter *adap, + struct i2c_msg *msgs, int num) +{ + struct s6i2c_if *iface = adap->algo_data; + int i; + if (num == 0) + return 0; + if (i2c_rd16(iface, S6_I2C_STATUS) & (1 << S6_I2C_STATUS_ACTIVITY)) + yield(); + i2c_wr16(iface, S6_I2C_INTRMASK, 0); + i2c_rd16(iface, S6_I2C_CLRINTR); + for (i = 0; i < num; i++) { + if (msgs[i].flags & I2C_M_TEN) { + dev_err(&adap->dev, + "s6i2c: 10 bits addr not supported\n"); + return -EINVAL; + } + if (msgs[i].len == 0) { + dev_err(&adap->dev, + "s6i2c: zero length message not supported\n"); + return -EINVAL; + } + if (msgs[i].addr != msgs[0].addr) { + dev_err(&adap->dev, + "s6i2c: multiple xfer cannot change target\n"); + return -EINVAL; + } + } + + iface->msgs = msgs; + iface->msgs_num = num; + iface->msgs_push = 0; + iface->msgs_done = 0; + iface->push = 0; + iface->done = 0; + iface->timeout_count = 10; + i2c_wr16(iface, S6_I2C_TAR, msgs[0].addr); + i2c_wr16(iface, S6_I2C_ENABLE, 1); + i2c_wr16(iface, S6_I2C_INTRMASK, (1 << S6_I2C_INTR_TXEMPTY) | + (1 << S6_I2C_INTR_TXABRT)); + + iface->timeout_timer.expires = jiffies + POLL_TIMEOUT; + add_timer(&iface->timeout_timer); + wait_for_completion(&iface->complete); + del_timer_sync(&iface->timeout_timer); + while (i2c_rd32(iface, S6_I2C_TXFLR) > 0) + schedule(); + while (i2c_rd16(iface, S6_I2C_STATUS) & (1 << S6_I2C_STATUS_ACTIVITY)) + schedule(); + + i2c_wr16(iface, S6_I2C_INTRMASK, 0); + i2c_wr16(iface, S6_I2C_ENABLE, 0); + return iface->msgs_done; +} + +static u32 s6i2c_functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static struct i2c_algorithm s6i2c_algorithm = { + .master_xfer = s6i2c_master_xfer, + .functionality = s6i2c_functionality, +}; + +static u16 __devinit nanoseconds_on_clk(struct s6i2c_if *iface, u32 ns) +{ + u32 dividend = ((clk_get_rate(iface->clk) / 1000) * ns) / 1000000; + if (dividend > 0xffff) + return 0xffff; + return dividend; +} + +static int __devinit s6i2c_probe(struct platform_device *dev) +{ + struct s6i2c_if *iface = &s6i2c_if; + struct i2c_adapter *p_adap; + const char *clock; + int bus_num, rc; + spin_lock_init(&iface->lock); + init_completion(&iface->complete); + iface->irq = platform_get_irq(dev, 0); + if (iface->irq < 0) { + rc = iface->irq; + goto err_out; + } + iface->res = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (!iface->res) { + rc = -ENXIO; + goto err_out; + } + iface->res = request_mem_region(iface->res->start, + resource_size(iface->res), + dev->dev.bus_id); + if (!iface->res) { + rc = -EBUSY; + goto err_out; + } + iface->reg = ioremap_nocache(iface->res->start, + resource_size(iface->res)); + if (!iface->reg) { + rc = -ENOMEM; + goto err_reg; + } + + clock = 0; + bus_num = -1; + if (dev->dev.platform_data) { + struct s6_i2c_platform_data *pdata = dev->dev.platform_data; + bus_num = pdata->bus_num; + clock = pdata->clock; + } + iface->clk = clk_get(&dev->dev, clock); + if (IS_ERR(iface->clk)) { + rc = PTR_ERR(iface->clk); + goto err_map; + } + rc = clk_enable(iface->clk); + if (rc < 0) + goto err_clk_put; + init_timer(&iface->timeout_timer); + iface->timeout_timer.function = s6i2c_timeout; + iface->timeout_timer.data = (unsigned long)iface; + + p_adap = &iface->adap; + strlcpy(p_adap->name, dev->name, sizeof(p_adap->name)); + p_adap->algo = &s6i2c_algorithm; + p_adap->algo_data = iface; + p_adap->nr = bus_num; + p_adap->class = 0; + p_adap->dev.parent = &dev->dev; + i2c_wr16(iface, S6_I2C_INTRMASK, 0); + rc = request_irq(iface->irq, s6i2c_interrupt_entry, + IRQF_SHARED, dev->name, iface); + if (rc) { + dev_err(&p_adap->dev, "s6i2c: cant get IRQ %d\n", iface->irq); + goto err_clk_dis; + } + + i2c_wr16(iface, S6_I2C_ENABLE, 0); + udelay(1); + i2c_wr32(iface, S6_I2C_SRESET, 1 << S6_I2C_SRESET_IC_SRST); + i2c_wr16(iface, S6_I2C_CLRTXABRT, 1); + i2c_wr16(iface, S6_I2C_CON, + (1 << S6_I2C_CON_MASTER) | + (S6_I2C_CON_SPEED_NORMAL << S6_I2C_CON_SPEED) | + (0 << S6_I2C_CON_10BITSLAVE) | + (0 << S6_I2C_CON_10BITMASTER) | + (1 << S6_I2C_CON_RESTARTENA) | + (1 << S6_I2C_CON_SLAVEDISABLE)); + i2c_wr16(iface, S6_I2C_SSHCNT, nanoseconds_on_clk(iface, 4000)); + i2c_wr16(iface, S6_I2C_SSLCNT, nanoseconds_on_clk(iface, 4700)); + i2c_wr16(iface, S6_I2C_FSHCNT, nanoseconds_on_clk(iface, 600)); + i2c_wr16(iface, S6_I2C_FSLCNT, nanoseconds_on_clk(iface, 1300)); + i2c_wr16(iface, S6_I2C_RXTL, 0); + i2c_wr16(iface, S6_I2C_TXTL, 0); + + platform_set_drvdata(dev, iface); + if (bus_num < 0) + rc = i2c_add_adapter(p_adap); + else + rc = i2c_add_numbered_adapter(p_adap); + if (rc) + goto err_irq_free; + return 0; + +err_irq_free: + free_irq(iface->irq, iface); +err_clk_dis: + clk_disable(iface->clk); +err_clk_put: + clk_put(iface->clk); +err_map: + iounmap(iface->reg); +err_reg: + release_mem_region(iface->res->start, + resource_size(iface->res)); +err_out: + return rc; +} + +static int __devexit s6i2c_remove(struct platform_device *pdev) +{ + struct s6i2c_if *iface = platform_get_drvdata(pdev); + i2c_wr16(iface, S6_I2C_ENABLE, 0); + platform_set_drvdata(pdev, NULL); + i2c_del_adapter(&iface->adap); + free_irq(iface->irq, iface); + clk_disable(iface->clk); + clk_put(iface->clk); + iounmap(iface->reg); + release_mem_region(iface->res->start, + resource_size(iface->res)); + return 0; +} + +static struct platform_driver s6i2c_driver = { + .probe = s6i2c_probe, + .remove = __devexit_p(s6i2c_remove), + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init s6i2c_init(void) +{ + pr_info("I2C: S6000 I2C driver\n"); + return platform_driver_register(&s6i2c_driver); +} + +static void __exit s6i2c_exit(void) +{ + platform_driver_unregister(&s6i2c_driver); +} + +MODULE_DESCRIPTION("I2C-Bus adapter routines for S6000 I2C"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); + +subsys_initcall(s6i2c_init); +module_exit(s6i2c_exit); diff --git a/drivers/i2c/busses/i2c-s6000.h b/drivers/i2c/busses/i2c-s6000.h new file mode 100644 index 0000000..ff23b81 --- /dev/null +++ b/drivers/i2c/busses/i2c-s6000.h @@ -0,0 +1,79 @@ +/* + * drivers/i2c/busses/i2c-s6000.h + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2008 Emlix GmbH <info@emlix.com> + * Author: Oskar Schirmer <os@emlix.com> + */ + +#ifndef __DRIVERS_I2C_BUSSES_I2C_S6000_H +#define __DRIVERS_I2C_BUSSES_I2C_S6000_H + +#define S6_I2C_CON 0x000 +#define S6_I2C_CON_MASTER 0 +#define S6_I2C_CON_SPEED 1 +#define S6_I2C_CON_SPEED_NORMAL 1 +#define S6_I2C_CON_SPEED_FAST 2 +#define S6_I2C_CON_SPEED_MASK 3 +#define S6_I2C_CON_10BITSLAVE 3 +#define S6_I2C_CON_10BITMASTER 4 +#define S6_I2C_CON_RESTARTENA 5 +#define S6_I2C_CON_SLAVEDISABLE 6 +#define S6_I2C_TAR 0x004 +#define S6_I2C_TAR_GCORSTART 10 +#define S6_I2C_TAR_SPECIAL 11 +#define S6_I2C_SAR 0x008 +#define S6_I2C_HSMADDR 0x00C +#define S6_I2C_DATACMD 0x010 +#define S6_I2C_DATACMD_READ 8 +#define S6_I2C_SSHCNT 0x014 +#define S6_I2C_SSLCNT 0x018 +#define S6_I2C_FSHCNT 0x01C +#define S6_I2C_FSLCNT 0x020 +#define S6_I2C_INTRSTAT 0x02C +#define S6_I2C_INTRMASK 0x030 +#define S6_I2C_RAWINTR 0x034 +#define S6_I2C_INTR_RXUNDER 0 +#define S6_I2C_INTR_RXOVER 1 +#define S6_I2C_INTR_RXFULL 2 +#define S6_I2C_INTR_TXOVER 3 +#define S6_I2C_INTR_TXEMPTY 4 +#define S6_I2C_INTR_RDREQ 5 +#define S6_I2C_INTR_TXABRT 6 +#define S6_I2C_INTR_RXDONE 7 +#define S6_I2C_INTR_ACTIVITY 8 +#define S6_I2C_INTR_STOPDET 9 +#define S6_I2C_INTR_STARTDET 10 +#define S6_I2C_INTR_GENCALL 11 +#define S6_I2C_RXTL 0x038 +#define S6_I2C_TXTL 0x03C +#define S6_I2C_CLRINTR 0x040 +#define S6_I2C_CLRRXUNDER 0x044 +#define S6_I2C_CLRRXOVER 0x048 +#define S6_I2C_CLRTXOVER 0x04C +#define S6_I2C_CLRRDREQ 0x050 +#define S6_I2C_CLRTXABRT 0x054 +#define S6_I2C_CLRRXDONE 0x058 +#define S6_I2C_CLRACTIVITY 0x05C +#define S6_I2C_CLRSTOPDET 0x060 +#define S6_I2C_CLRSTARTDET 0x064 +#define S6_I2C_CLRGENCALL 0x068 +#define S6_I2C_ENABLE 0x06C +#define S6_I2C_STATUS 0x070 +#define S6_I2C_STATUS_ACTIVITY 0 +#define S6_I2C_STATUS_TFNF 1 +#define S6_I2C_STATUS_TFE 2 +#define S6_I2C_STATUS_RFNE 3 +#define S6_I2C_STATUS_RFF 4 +#define S6_I2C_TXFLR 0x074 +#define S6_I2C_RXFLR 0x078 +#define S6_I2C_SRESET 0x07C +#define S6_I2C_SRESET_IC_SRST 0 +#define S6_I2C_SRESET_IC_MASTER_SRST 1 +#define S6_I2C_SRESET_IC_SLAVE_SRST 2 +#define S6_I2C_TXABRTSOURCE 0x080 + +#endif diff --git a/include/linux/i2c/s6000.h b/include/linux/i2c/s6000.h new file mode 100644 index 0000000..d9b34bf --- /dev/null +++ b/include/linux/i2c/s6000.h @@ -0,0 +1,10 @@ +#ifndef __LINUX_I2C_S6000_H +#define __LINUX_I2C_S6000_H + +struct s6_i2c_platform_data { + const char *clock; /* the clock to use */ + int bus_num; /* the bus number to register */ +}; + +#endif + -- 1.6.2.107.ge47ee ^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2009-04-02 11:18 UTC | newest] Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2009-03-23 15:05 [patch 1/5] i2c: xtensa s6000 i2c driver Daniel Glöckner 2009-03-23 15:05 ` [patch 2/5] xtensa: s6105 specific configuration for the s6000 i2c host Daniel Glöckner 2009-03-23 15:05 ` [patch 3/5] xtensa: add at24hc02b to s6105 platform Daniel Glöckner 2009-03-23 15:05 ` [patch 4/5] xtensa: enable s6000 i2c host driver in s6105_defconfig Daniel Glöckner 2009-03-23 15:05 ` [patch 5/5] xtensa: enable at24 " Daniel Glöckner 2009-03-31 22:52 ` [patch 1/5] i2c: xtensa s6000 i2c driver Ben Dooks 2009-03-31 23:01 ` Ben Dooks 2009-04-02 11:19 ` [patch v2] " Oskar Schirmer
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox
Powered by JetHome