Hello Ben & i2c people, Here is the patch to support I2C bus on Freescale STMP378x platform; any comments are welcome Signed-off-by: Dmitrij Frasenyak <d.frasenyak@xxxxxxxxxxxxxxxxx> Signed-off-by: dmitry pervishin <dpervushin@xxxxxxxxxxxxxxxxx> --- drivers/i2c/busses/Kconfig | 7 drivers/i2c/busses/Makefile | 1 drivers/i2c/busses/i2c-stmp378x.c | 440 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 448 insertions(+) Index: linux-2.6-arm/drivers/i2c/busses/Kconfig =================================================================== --- linux-2.6-arm.orig/drivers/i2c/busses/Kconfig +++ linux-2.6-arm/drivers/i2c/busses/Kconfig @@ -513,6 +513,13 @@ config I2C_SIMTEC This driver can also be built as a module. If so, the module will be called i2c-simtec. +config I2C_STMP378X + tristate "STMP378x I2C adapter" + depends on ARCH_STMP378X + help + Include support of I2C adapter on Freescale STMP378x board; to build + the driver as a module, say M here - module will be called i2c-stmp378x. + config I2C_VERSATILE tristate "ARM Versatile/Realview I2C bus support" depends on ARCH_VERSATILE || ARCH_REALVIEW Index: linux-2.6-arm/drivers/i2c/busses/Makefile =================================================================== --- linux-2.6-arm.orig/drivers/i2c/busses/Makefile +++ linux-2.6-arm/drivers/i2c/busses/Makefile @@ -48,6 +48,7 @@ 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 +obj-$(CONFIG_I2C_STMP378X) += i2c-stmp378x.o obj-$(CONFIG_I2C_VERSATILE) += i2c-versatile.o # External I2C/SMBus adapter drivers Index: linux-2.6-arm/drivers/i2c/busses/i2c-stmp378x.c =================================================================== --- /dev/null +++ linux-2.6-arm/drivers/i2c/busses/i2c-stmp378x.c @@ -0,0 +1,440 @@ +/* + * Freescale STMP378X I2C bus driver + * + * Author: Dmitrij Frasenyak <sed@xxxxxxxxxxxxxxxxx> + * + * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/completion.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/dma-mapping.h> + +#include <mach/platform.h> +#include <mach/stmp3xxx.h> +#include <mach/dma.h> +#include <mach/regs-i2c.h> +#include <mach/regs-apbx.h> + +static const u32 I2C_READ = 1, + I2C_WRITE = 0; + +static const struct stmp3xxx_dma_command cmd_i2c_select = { + .cmd = BF(1, APBX_CHn_CMD_XFER_COUNT) | + BF(1, APBX_CHn_CMD_CMDWORDS) | + BM_APBX_CHn_CMD_WAIT4ENDCMD | + BM_APBX_CHn_CMD_CHAIN | + BF(BV_APBX_CHn_CMD_COMMAND__DMA_READ, APBX_CHn_CMD_COMMAND), + .pio_words = { + [0] = BM_I2C_CTRL0_RETAIN_CLOCK | + BM_I2C_CTRL0_PRE_SEND_START | + BM_I2C_CTRL0_MASTER_MODE | + BM_I2C_CTRL0_DIRECTION | + BF(1, I2C_CTRL0_XFER_COUNT), + }, +}; + +static const struct stmp3xxx_dma_command cmd_i2c_write = { + .cmd = BM_APBX_CHn_CMD_SEMAPHORE | + BF(1, APBX_CHn_CMD_CMDWORDS) | + BM_APBX_CHn_CMD_WAIT4ENDCMD | + BM_APBX_CHn_CMD_IRQONCMPLT | + BF(BV_APBX_CHn_CMD_COMMAND__DMA_READ, APBX_CHn_CMD_COMMAND), + .pio_words = { + [0] = BM_I2C_CTRL0_PRE_SEND_START | + BM_I2C_CTRL0_MASTER_MODE | + BM_I2C_CTRL0_DIRECTION, + }, +}; + +static const struct stmp3xxx_dma_command cmd_i2c_read = { + .cmd = BM_APBX_CHn_CMD_SEMAPHORE | + BF(1, APBX_CHn_CMD_CMDWORDS) | + BM_APBX_CHn_CMD_WAIT4ENDCMD | + BM_APBX_CHn_CMD_IRQONCMPLT | + BF(BV_APBX_CHn_CMD_COMMAND__DMA_WRITE, APBX_CHn_CMD_COMMAND), + .pio_words = { + [0] = BM_I2C_CTRL0_SEND_NAK_ON_LAST | + BM_I2C_CTRL0_MASTER_MODE, + }, +}; + +struct stmp378x_i2c_dev { + struct device *dev; + int irq; + struct completion cmd_complete; + u32 cmd_err; + struct i2c_adapter adapter; + void __iomem *io; + unsigned dma; + + dma_addr_t bufp; + unsigned char *bufv; + + struct stmp3xxx_dma_descriptor i2c_dma_read[2], + i2c_dma_write; +}; + +static int stmp378x_i2c_init(struct stmp378x_i2c_dev *dev) +{ + int err; + + err = stmp3xxx_dma_request(dev->dma, dev->dev, dev_name(dev->dev)); + if (err) + goto out; + stmp3xxx_reset_block(dev->io, true); + + dev->bufv = dma_alloc_coherent(dev->dev, PAGE_SIZE, + &dev->bufp, GFP_KERNEL); + if (dma_mapping_error(dev->dev, dev->bufp)) + goto out_dma; + + err = stmp3xxx_request_pin_group(dev->dev->platform_data, + dev_name(dev->dev)); + if (err) + goto out_free; + + err = stmp3xxx_dma_allocate_command(dev->dma, &dev->i2c_dma_read[0]); + if (err) + goto out_rd0; + err = stmp3xxx_dma_allocate_command(dev->dma, &dev->i2c_dma_read[1]); + if (err) + goto out_rd1; + err = stmp3xxx_dma_allocate_command(dev->dma, &dev->i2c_dma_write); + if (err) + goto out_wr; + + stmp3xxx_dma_reset_channel(dev->dma); + stmp3xxx_dma_clear_interrupt(dev->dma); + stmp3xxx_dma_enable_interrupt(dev->dma); + + return 0; +/* + stmp3xxx_dma_free_command(dev->dma, &dev->i2c_dma_write); +*/ +out_wr: + stmp3xxx_dma_free_command(dev->dma, &dev->i2c_dma_read[1]); +out_rd1: + stmp3xxx_dma_free_command(dev->dma, &dev->i2c_dma_read[0]); +out_rd0: + stmp3xxx_release_pin_group(dev->dev->platform_data, dev_name(dev->dev)); +out_free: + dma_free_coherent(dev->dev, PAGE_SIZE, dev->bufv, dev->bufp); +out_dma: + stmp3xxx_dma_release(dev->dma); +out: + return err; +} + +static void stmp378x_i2c_release(struct stmp378x_i2c_dev *dev) +{ + stmp3xxx_dma_free_command(dev->dma, &dev->i2c_dma_write); + stmp3xxx_dma_free_command(dev->dma, &dev->i2c_dma_read[1]); + stmp3xxx_dma_free_command(dev->dma, &dev->i2c_dma_read[0]); + dma_free_coherent(dev->dev, PAGE_SIZE, dev->bufv, dev->bufp); + stmp3xxx_release_pin_group(dev->dev->platform_data, dev_name(dev->dev)); + stmp3xxx_dma_release(dev->dma); +} +/* + * Low level master read/write transaction. + */ +static int stmp378x_i2c_xfer_msg(struct i2c_adapter *adap, + struct i2c_msg *msg, int stop) +{ + struct stmp378x_i2c_dev *dev = i2c_get_adapdata(adap); + int err; + struct stmp3xxx_dma_descriptor *c, *r; + + init_completion(&dev->cmd_complete); + dev->cmd_err = 0; + + dev_dbg(dev->dev, "start XFER\n"); + dev_dbg(dev->dev, "addr: 0x%04x, len: %d, flags: 0x%x, stop: %d\n", + msg->addr, msg->len, msg->flags, stop); + + if ((msg->len == 0) || (msg->len > (PAGE_SIZE - 1))) + return -EINVAL; + + if (msg->flags & I2C_M_RD) { + + c = &dev->i2c_dma_read[0]; + r = &dev->i2c_dma_read[1]; + + /* first, setup the SELECT command */ + memcpy(c->command, &cmd_i2c_select, sizeof(cmd_i2c_select)); + c->command->buf_ptr = dev->bufp; + dev->bufv[0] = msg->addr | I2C_READ; + + /* then, setup the READ command */ + memcpy(r->command, &cmd_i2c_read, sizeof(cmd_i2c_read)); + r->command->cmd |= BF(msg->len, APBX_CHn_CMD_XFER_COUNT); + r->command->pio_words[0] |= BF(msg->len, I2C_CTRL0_XFER_COUNT); + if (stop) + r->command->pio_words[0] |= BM_I2C_CTRL0_POST_SEND_STOP; + /* + * yes, STMP controller can DMA transfer the data from any + * byte boundary + */ + r->command->buf_ptr = dev->bufp + 1; + + /* and chain them together */ + c->command->next = r->handle; + + } else { + + c = &dev->i2c_dma_write; + memcpy(c->command, &cmd_i2c_write, sizeof(cmd_i2c_write)); + c->command->cmd |= BF(msg->len + 1, APBX_CHn_CMD_XFER_COUNT); + c->command->pio_words[0] |= + BF(msg->len + 1, I2C_CTRL0_XFER_COUNT); + if (stop) + c->command->pio_words[0] |= + BM_I2C_CTRL0_POST_SEND_STOP; + dev->bufv[0] = msg->addr | I2C_WRITE; + memcpy(dev->bufv + 1, msg->buf, msg->len); + } + + stmp3xxx_dma_go(dev->dma, c, 1); + + err = wait_for_completion_interruptible_timeout(&dev->cmd_complete, + msecs_to_jiffies(1000)); + + if (err < 0) { + dev_dbg(dev->dev, "controler is timed out\n"); + return -ETIMEDOUT; + } + if (!dev->cmd_err && (msg->flags & I2C_M_RD)) + memcpy(msg->buf, dev->bufv + 1, msg->len); + + dev_dbg(dev->dev, "done with err=%d\n", dev->cmd_err); + + return dev->cmd_err; +} + + +static int +stmp378x_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) +{ + int i; + int err; + + if (!msgs->len) + return -EINVAL; + + for (i = 0; i < num; i++) { + err = stmp378x_i2c_xfer_msg(adap, &msgs[i], (i == (num - 1))); + if (err) + return err; + } + return num; +} + +static u32 +stmp378x_i2c_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK); +} + +#define I2C_IRQ_MASK 0x000000FF +static irqreturn_t +stmp378x_i2c_isr(int this_irq, void *dev_id) +{ + struct stmp378x_i2c_dev *dev = dev_id; + u32 stat; + u32 done_mask = BM_I2C_CTRL1_DATA_ENGINE_CMPLT_IRQ | + BM_I2C_CTRL1_BUS_FREE_IRQ ; + + stat = readl(HW_I2C_CTRL1 + dev->io) & I2C_IRQ_MASK; + if (!stat) + return IRQ_NONE; + + if (stat & BM_I2C_CTRL1_NO_SLAVE_ACK_IRQ) { + dev->cmd_err = -EREMOTEIO; + + /* + * Stop DMA + * Clear NAK + */ + stmp3xxx_setl(BM_I2C_CTRL1_CLR_GOT_A_NAK, + HW_I2C_CTRL1 + dev->io); + stmp3xxx_dma_reset_channel(dev->dma); + stmp3xxx_dma_clear_interrupt(dev->dma); + + complete(&dev->cmd_complete); + + goto done; + } + + /* Don't care about BM_I2C_CTRL1_OVERSIZE_XFER_TERM_IRQ */ + if (stat & (BM_I2C_CTRL1_EARLY_TERM_IRQ | + BM_I2C_CTRL1_MASTER_LOSS_IRQ | + BM_I2C_CTRL1_SLAVE_STOP_IRQ | + BM_I2C_CTRL1_SLAVE_IRQ)) { + dev->cmd_err = -EIO; + complete(&dev->cmd_complete); + goto done; + } + + if ((stat & done_mask) == done_mask) + complete(&dev->cmd_complete); + +done: + stmp3xxx_clearl(stat, dev->io + HW_I2C_CTRL1); + return IRQ_HANDLED; +} + +static const struct i2c_algorithm stmp378x_i2c_algo = { + .master_xfer = stmp378x_i2c_xfer, + .functionality = stmp378x_i2c_func, +}; + + +static int __devinit +stmp378x_i2c_probe(struct platform_device *pdev) +{ + struct stmp378x_i2c_dev *dev; + struct i2c_adapter *adap; + struct resource *mem, *dma; + int err = 0; + + /* NOTE: driver uses the static register mapping */ + dev = kzalloc(sizeof(struct stmp378x_i2c_dev), GFP_KERNEL); + if (!dev) { + dev_err(&pdev->dev, "no mem \n"); + return -ENOMEM; + } + + dev->irq = platform_get_irq(pdev, 0); /* Error */ + if (dev->irq < 0) { + dev_err(&pdev->dev, "no err_irq resource\n"); + err = -ENODEV; + goto nores; + } + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&pdev->dev, "no memory window\n"); + err = -ENODEV; + goto nores; + } + + dma = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!dma) { + dev_err(&pdev->dev, "no dma channel\n"); + err = -ENODEV; + goto nores; + } + dev->dma = dma->start; + + dev->dev = &pdev->dev; + dev->io = ioremap(mem->start, resource_size(mem)); + if (!dev->io) { + dev_err(&pdev->dev, "cannot ioremap\n"); + err = -ENXIO; + goto nores; + } + + err = request_irq(dev->irq, stmp378x_i2c_isr, 0, pdev->name, dev); + if (err) { + dev_err(&pdev->dev, "Can't get IRQ\n"); + goto no_err_irq; + } + + err = stmp378x_i2c_init(dev); + if (err) { + dev_err(&pdev->dev, "HW Init failed\n"); + goto init_failed; + } + + stmp3xxx_setl(0xFF00, dev->io + HW_I2C_CTRL1); + + adap = &dev->adapter; + i2c_set_adapdata(adap, dev); + adap->owner = THIS_MODULE; + adap->class = I2C_CLASS_HWMON; + strncpy(adap->name, "378x I2C adapter", sizeof(adap->name)); + adap->algo = &stmp378x_i2c_algo; + adap->dev.parent = &pdev->dev; + + adap->nr = pdev->id; + err = i2c_add_numbered_adapter(adap); + if (err) { + dev_err(&pdev->dev, "Failed to add adapter\n"); + goto no_i2c_adapter; + + } + + return 0; + +no_i2c_adapter: + stmp378x_i2c_release(dev); +init_failed: + free_irq(dev->irq, dev); +no_err_irq: + iounmap(dev->io); +nores: + kfree(dev); + return err; +} + +static int __devexit +stmp378x_i2c_remove(struct platform_device *pdev) +{ + struct stmp378x_i2c_dev *dev = platform_get_drvdata(pdev); + int res; + + res = i2c_del_adapter(&dev->adapter); + if (res) + return -EBUSY; + + stmp378x_i2c_release(dev); + + platform_set_drvdata(pdev, NULL); + + free_irq(dev->irq, dev); + iounmap(dev->io); + + kfree(dev); + return 0; +} + +static struct platform_driver stmp378x_i2c_driver = { + .probe = stmp378x_i2c_probe, + .remove = __devexit_p(stmp378x_i2c_remove), + .driver = { + .name = "i2c_stmp", + .owner = THIS_MODULE, + }, +}; + +static int __init stmp378x_i2c_init_driver(void) +{ + return platform_driver_register(&stmp378x_i2c_driver); +} +module_init(stmp378x_i2c_init_driver); + +static void __exit stmp378x_i2c_exit_driver(void) +{ + platform_driver_unregister(&stmp378x_i2c_driver); +} +module_exit(stmp378x_i2c_exit_driver); + +MODULE_AUTHOR("d.frasenyak@xxxxxxxxxxxxxxxxx"); +MODULE_DESCRIPTION("I2C for Freescale STMP378x"); +MODULE_LICENSE("GPL"); -- To unsubscribe from this list: send the line "unsubscribe linux-i2c" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html