From e5d89837a365a810b3cc5ce58d273a7c49029f36 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sun, 5 Sep 2010 03:19:10 +0200 Subject: [PATCH] i2c: Add i2c driver for JZ47XX SoCs This patch adds a driver for the i2c controller found in Ingenic JZ47XX based SoCs. Signed-off-by: Lars-Peter Clausen --- drivers/i2c/busses/Kconfig | 10 + drivers/i2c/busses/Makefile | 1 + drivers/i2c/busses/i2c-jz47xx.c | 424 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 435 insertions(+), 0 deletions(-) create mode 100644 drivers/i2c/busses/i2c-jz47xx.c --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -420,6 +420,16 @@ config I2C_IXP2000 This driver is deprecated and will be dropped soon. Use i2c-gpio instead. +config I2C_JZ47XX + tristate "JZ4740 I2C Interface" + depends on MACH_JZ4740 + help + Say Y here if you want support for the I2C controller found on Ingenic + JZ47XX based SoCs. + + This driver can also be built as a module. If so, the module will be + called i2c-jz47xx. + config I2C_MPC tristate "MPC107/824x/85xx/512x/52xx/83xx/86xx" depends on PPC32 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -40,6 +40,7 @@ obj-$(CONFIG_I2C_IBM_IIC) += i2c-ibm_iic obj-$(CONFIG_I2C_IMX) += i2c-imx.o obj-$(CONFIG_I2C_IOP3XX) += i2c-iop3xx.o obj-$(CONFIG_I2C_IXP2000) += i2c-ixp2000.o +obj-$(CONFIG_I2C_JZ47XX) += i2c-jz47xx.o obj-$(CONFIG_I2C_MPC) += i2c-mpc.o obj-$(CONFIG_I2C_MV64XXX) += i2c-mv64xxx.o obj-$(CONFIG_I2C_NOMADIK) += i2c-nomadik.o --- /dev/null +++ b/drivers/i2c/busses/i2c-jz47xx.c @@ -0,0 +1,424 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define JZ47XX_REG_I2C_DATA 0x00 +#define JZ47XX_REG_I2C_CTRL 0x04 +#define JZ47XX_REG_I2C_STATUS 0x08 +#define JZ47XX_REG_I2C_CLOCK 0x0C + +#define JZ47XX_I2C_STATUS_FIFO_FULL BIT(4) +#define JZ47XX_I2C_STATUS_BUSY BIT(3) +#define JZ47XX_I2C_STATUS_TEND BIT(2) +#define JZ47XX_I2C_STATUS_DATA_VALID BIT(1) +#define JZ47XX_I2C_STATUS_NACK BIT(0) + +#define JZ47XX_I2C_CTRL_IRQ_ENABLE BIT(4) +#define JZ47XX_I2C_CTRL_START BIT(3) +#define JZ47XX_I2C_CTRL_STOP BIT(2) +#define JZ47XX_I2C_CTRL_NACK BIT(1) +#define JZ47XX_I2C_CTRL_ENABLE BIT(0) + +struct jz47xx_i2c { + struct resource *mem; + void __iomem *base; + int irq; + struct clk *clk; + + struct i2c_adapter adapter; + + wait_queue_head_t wait_queue; +}; + +static inline struct jz47xx_i2c *adapter_to_jz47xx_i2c(struct i2c_adapter *adap) +{ + return container_of(adap, struct jz47xx_i2c, adapter); +} + +static inline void jz47xx_i2c_set_ctrl(struct jz47xx_i2c *jz47xx_i2c, + uint8_t mask, uint8_t value) +{ + uint8_t ctrl; + ctrl = readb(jz47xx_i2c->base + JZ47XX_REG_I2C_CTRL); + ctrl &= ~mask; + ctrl |= value; + printk("ctrl: %x\n", ctrl); + writeb(ctrl, jz47xx_i2c->base + JZ47XX_REG_I2C_CTRL); +} + +static irqreturn_t jz47xx_i2c_irq_handler(int irq, void *devid) +{ + struct jz47xx_i2c *jz47xx_i2c = devid; + + printk("IRQ\n"); + + wake_up(&jz47xx_i2c->wait_queue); + + jz47xx_i2c_set_ctrl(jz47xx_i2c, JZ47XX_I2C_CTRL_IRQ_ENABLE, 0); + + return IRQ_HANDLED; +} + +static inline void jz47xx_i2c_set_data_valid(struct jz47xx_i2c *jz47xx_i2c, + bool valid) +{ + uint8_t val; + val = readb(jz47xx_i2c->base + JZ47XX_REG_I2C_STATUS); + if (valid) + val |= JZ47XX_I2C_STATUS_DATA_VALID; + else + val &= ~JZ47XX_I2C_STATUS_DATA_VALID; + writeb(val, jz47xx_i2c->base + JZ47XX_REG_I2C_STATUS); +} + +static int jz47xx_i2c_test_event(struct jz47xx_i2c *jz47xx_i2c, uint8_t mask, uint8_t value) +{ + uint8_t status; + + mask |= JZ47XX_I2C_STATUS_NACK; + value |= JZ47XX_I2C_STATUS_NACK; + + status = readb(jz47xx_i2c->base + JZ47XX_REG_I2C_STATUS); + printk("status: %x %x %x %x\n", status, mask, value, (status & mask) ^ + value); + if (((status & mask) ^ value) == mask) { + jz47xx_i2c_set_ctrl(jz47xx_i2c, JZ47XX_I2C_CTRL_IRQ_ENABLE, + JZ47XX_I2C_CTRL_IRQ_ENABLE); + return 0; + } + return 1; +} + +static int jz47xx_i2c_wait_event_or_nack(struct jz47xx_i2c *jz47xx_i2c, uint8_t +mask, uint8_t value) +{ + int ret; + + ret = wait_event_interruptible_timeout(jz47xx_i2c->wait_queue, + jz47xx_i2c_test_event(jz47xx_i2c, mask, value), 30 * HZ); + +/* while (!jz47xx_i2c_test_event(jz47xx_i2c, mask, value)); + + ret = 1;*/ + + printk("wait event or nack: %d %x\n", ret, readb(jz47xx_i2c->base + + JZ47XX_REG_I2C_STATUS)); + + if (ret == 0) + ret = -ETIMEDOUT; + else if(ret > 0) { + if (readb(jz47xx_i2c->base + JZ47XX_REG_I2C_STATUS) & JZ47XX_I2C_STATUS_NACK) + ret = -EIO; + else + ret = 0; + } + + return ret; +} + +static int jz47xx_i2c_wait_event(struct jz47xx_i2c *jz47xx_i2c, uint8_t event) +{ + int ret; + + ret = wait_event_interruptible_timeout(jz47xx_i2c->wait_queue, + jz47xx_i2c_test_event(jz47xx_i2c, event, event), 30 * HZ); + + if (ret == 0) + ret = -ETIMEDOUT; + else if(ret > 0) + ret = 0; + + return ret; +} + + +static int jz47xx_i2c_write_msg(struct jz47xx_i2c *jz47xx_i2c, + struct i2c_msg *msg) +{ + int ret; + int i; + + printk("%s:%s[%d]\n", __FILE__, __func__, __LINE__); + for (i = 0; i < msg->len; ++i) { + writeb(msg->buf[i], jz47xx_i2c->base + JZ47XX_REG_I2C_DATA); + jz47xx_i2c_set_data_valid(jz47xx_i2c, true); + ret = jz47xx_i2c_wait_event_or_nack(jz47xx_i2c, + JZ47XX_I2C_STATUS_DATA_VALID, 0); + if (ret) + break; + } + jz47xx_i2c_set_ctrl(jz47xx_i2c, JZ47XX_I2C_CTRL_STOP, + JZ47XX_I2C_CTRL_STOP); + + if (!ret) + ret = jz47xx_i2c_wait_event_or_nack(jz47xx_i2c, JZ47XX_I2C_STATUS_TEND, + JZ47XX_I2C_STATUS_TEND); + + return ret; +} + +static int jz47xx_i2c_read_msg(struct jz47xx_i2c *jz47xx_i2c, + struct i2c_msg *msg) +{ + int i; + int ret; + printk("%s:%s[%d]\n", __FILE__, __func__, __LINE__); + + jz47xx_i2c_set_ctrl(jz47xx_i2c, JZ47XX_I2C_CTRL_NACK, + msg->len == 1 ? JZ47XX_I2C_CTRL_NACK : 0); + + for (i = 0; i < msg->len; ++i) { + ret = jz47xx_i2c_wait_event(jz47xx_i2c, JZ47XX_I2C_STATUS_DATA_VALID); + if (ret) { + jz47xx_i2c_set_ctrl(jz47xx_i2c, JZ47XX_I2C_CTRL_NACK, + JZ47XX_I2C_CTRL_NACK); + break; + } + + if (i == msg->len - 2) { + jz47xx_i2c_set_ctrl(jz47xx_i2c, JZ47XX_I2C_CTRL_NACK, + JZ47XX_I2C_CTRL_NACK); + } + + msg->buf[i] = readb(jz47xx_i2c->base + JZ47XX_REG_I2C_DATA); + printk("read: %x\n", msg->buf[i]); + jz47xx_i2c_set_data_valid(jz47xx_i2c, false); + } + + jz47xx_i2c_set_ctrl(jz47xx_i2c, JZ47XX_I2C_CTRL_STOP, + JZ47XX_I2C_CTRL_STOP); + + return ret; +} + +static int jz47xx_i2c_xfer_msg(struct jz47xx_i2c *jz47xx_i2c, + struct i2c_msg *msg) +{ + uint8_t addr; + int ret; + + addr = msg->addr << 1; + if (msg->flags & I2C_M_RD) + addr |= 1; + + jz47xx_i2c_set_ctrl(jz47xx_i2c, JZ47XX_I2C_CTRL_START, + JZ47XX_I2C_CTRL_START); + writeb(addr, jz47xx_i2c->base + JZ47XX_REG_I2C_DATA); + jz47xx_i2c_set_data_valid(jz47xx_i2c, true); + + if (msg->flags & I2C_M_RD) { + printk("%s:%s[%d]\n", __FILE__, __func__, __LINE__); + ret = jz47xx_i2c_wait_event_or_nack(jz47xx_i2c, + JZ47XX_I2C_STATUS_TEND, JZ47XX_I2C_STATUS_TEND); + if (!ret) + ret = jz47xx_i2c_read_msg(jz47xx_i2c, msg); + } else { + printk("%s:%s[%d]\n", __FILE__, __func__, __LINE__); + ret = jz47xx_i2c_wait_event_or_nack(jz47xx_i2c, + JZ47XX_I2C_STATUS_DATA_VALID, 0); + if (!ret) + ret = jz47xx_i2c_write_msg(jz47xx_i2c, msg); + } + + return ret; +} + +static int jz47xx_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int +num) +{ + struct jz47xx_i2c *jz47xx_i2c = adapter_to_jz47xx_i2c(adap); + int ret = 0; + int i; + int mask = JZ47XX_I2C_CTRL_ENABLE; + + printk("xfer: %d %x\n", num, readb(jz47xx_i2c->base + + JZ47XX_REG_I2C_STATUS)); + + clk_enable(jz47xx_i2c->clk); + jz47xx_i2c_set_ctrl(jz47xx_i2c, mask, mask); + + for (i = 0; i < num; ++i) { + ret = jz47xx_i2c_xfer_msg(jz47xx_i2c, &msgs[i]); + if (ret) + break; + } + + jz47xx_i2c_set_ctrl(jz47xx_i2c, mask, 0); + clk_disable(jz47xx_i2c->clk); + + printk("xfer ret: %d\n", ret); + + return ret; +} + +static u32 jz47xx_i2c_functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm jz47xx_i2c_algorithm = { + .master_xfer = jz47xx_i2c_xfer, + .functionality = jz47xx_i2c_functionality, +}; + +const static struct jz_gpio_bulk_request jz47xx_i2c_pins[] = { + JZ_GPIO_BULK_PIN(I2C_SDA), + JZ_GPIO_BULK_PIN(I2C_SCK), +}; + +static int __devinit jz47xx_i2c_probe(struct platform_device *pdev) +{ + struct jz47xx_i2c *jz47xx_i2c; + struct resource *mem; + void __iomem *base; + struct clk *clk; + int irq; + int ret; + + irq = platform_get_irq(pdev, 0); + if (!irq) { + dev_err(&pdev->dev, "Failed to get IRQ: %d\n", irq); + return irq; + } + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&pdev->dev, "Failed to get iomem region\n"); + return -ENXIO; + } + + mem = request_mem_region(mem->start, resource_size(mem), pdev->name); + if (!mem) { + dev_err(&pdev->dev, "Failed to request iomem region\n"); + return -EBUSY; + } + + base = ioremap(mem->start, resource_size(mem)); + if (!base) { + dev_err(&pdev->dev, "Failed to ioremap iomem\n"); + ret = -EBUSY; + goto err_release_mem_region; + } + + clk = clk_get(&pdev->dev, "i2c"); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + goto err_iounmap; + } + + jz47xx_i2c = kzalloc(sizeof(*jz47xx_i2c), GFP_KERNEL); + if (!jz47xx_i2c) { + ret = -ENOMEM; + goto err_clk_put; + } + + jz47xx_i2c->adapter.owner = THIS_MODULE; + jz47xx_i2c->adapter.algo = &jz47xx_i2c_algorithm; + jz47xx_i2c->adapter.dev.parent = &pdev->dev; + jz47xx_i2c->adapter.nr = pdev->id < 0 ?: 0; + strcpy(jz47xx_i2c->adapter.name, pdev->name); + + jz47xx_i2c->mem = mem; + jz47xx_i2c->base = base; + jz47xx_i2c->clk = clk; + jz47xx_i2c->irq = irq; + + init_waitqueue_head(&jz47xx_i2c->wait_queue); + + ret = request_irq(irq, jz47xx_i2c_irq_handler, 0, pdev->name, jz47xx_i2c); + if (ret) { + dev_err(&pdev->dev, "Failed to request irq: %d\n", ret); + goto err_free; + } + + ret = jz_gpio_bulk_request(jz47xx_i2c_pins, ARRAY_SIZE(jz47xx_i2c_pins)); + if (ret) { + dev_err(&pdev->dev, "Failed to request i2c pins: %d\n", ret); + goto err_free_irq; + } + + writew(0x10, jz47xx_i2c->base + JZ47XX_REG_I2C_CLOCK); + + ret = i2c_add_numbered_adapter(&jz47xx_i2c->adapter); + if (ret) { + dev_err(&pdev->dev, "Failed to add i2c adapter: %d\n", ret); + goto err_free_gpios; + } + + platform_set_drvdata(pdev, jz47xx_i2c); + + printk("JZ4740 I2C\n"); + + return 0; + +err_free_gpios: + jz_gpio_bulk_free(jz47xx_i2c_pins, ARRAY_SIZE(jz47xx_i2c_pins)); +err_free_irq: + free_irq(irq, jz47xx_i2c); +err_free: + kfree(jz47xx_i2c); +err_clk_put: + clk_put(clk); +err_iounmap: + iounmap(base); +err_release_mem_region: + release_mem_region(mem->start, resource_size(mem)); + return ret; +} + +static int __devexit jz47xx_i2c_remove(struct platform_device *pdev) +{ + struct jz47xx_i2c *jz47xx_i2c = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + i2c_del_adapter(&jz47xx_i2c->adapter); + + jz_gpio_bulk_free(jz47xx_i2c_pins, ARRAY_SIZE(jz47xx_i2c_pins)); + + free_irq(jz47xx_i2c->irq, jz47xx_i2c); + clk_put(jz47xx_i2c->clk); + + iounmap(jz47xx_i2c->base); + release_mem_region(jz47xx_i2c->mem->start, resource_size(jz47xx_i2c->mem)); + + kfree(jz47xx_i2c); + + return 0; +} + +static struct platform_driver jz47xx_i2c_driver = { + .probe = jz47xx_i2c_probe, + .remove = jz47xx_i2c_remove, + .driver = { + .name = "jz47xx-i2c", + .owner = THIS_MODULE, + }, +}; + +static int __init jz47xx_i2c_init(void) +{ + return platform_driver_register(&jz47xx_i2c_driver); +} +module_init(jz47xx_i2c_init); + +static void jz47xx_i2c_exit(void) +{ + platform_driver_unregister(&jz47xx_i2c_driver); +} +module_exit(jz47xx_i2c_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("I2C adapter driver for JZ47XX SoCs"); +MODULE_ALIAS("platform:jz47xx-i2c"); +