From: Jiang Lu <lu.ji...@windriver.com> I2C driver for the controller on ACP platform. The EEPROM attached on the bus can be accessed.
Signed-off-by: Jiang Lu <lu.ji...@windriver.com> --- drivers/i2c/busses/Kconfig | 7 + drivers/i2c/busses/Makefile | 3 +- drivers/i2c/busses/acp3400-i2c.c | 510 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 519 insertions(+), 1 deletion(-) create mode 100644 drivers/i2c/busses/acp3400-i2c.c diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 4b3a7ca..a1ff6b0 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -934,4 +934,11 @@ config I2C_AXXIA If you don't know, say Y. +config ACP3400_I2C + tristate "ACP3400 I2C support" + depends on ACP + default y + help + I2C adapter for acp476 based boards + endmenu diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index 39f9f4f..b325ee6 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -92,4 +92,5 @@ obj-$(CONFIG_SCx200_I2C) += scx200_i2c.o ccflags-$(CONFIG_I2C_DEBUG_BUS) := -DDEBUG -obj-y += ai2c/ +obj-$(CONFIG_I2C_AXXIA) += ai2c/ +obj-$(CONFIG_ACP3400_I2C) += acp3400-i2c.o diff --git a/drivers/i2c/busses/acp3400-i2c.c b/drivers/i2c/busses/acp3400-i2c.c new file mode 100644 index 0000000..4a536db --- /dev/null +++ b/drivers/i2c/busses/acp3400-i2c.c @@ -0,0 +1,510 @@ +/* + * ACP3400 I2C adapter + * + * Based on DU-TS I2C Adapter Driver + * + * 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. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/init.h> +#include <linux/of_platform.h> +#include <linux/of_i2c.h> +#include <linux/slab.h> + +#include <linux/io.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/delay.h> + +#define DRV_NAME "acp3400-i2c" + +struct acp3400_i2c_regs { + unsigned int txconfig; + unsigned int rxconfig; + unsigned int txstatus; + unsigned int rxstatus; + unsigned int irqenable; + unsigned int irqclear; + unsigned int irqstatus; + unsigned int clkconfig; + unsigned int startsetup; + unsigned int stopsetup; + unsigned int datasetup; + unsigned int bypassmode; + unsigned int slaveaddr; + unsigned int txdata0; + unsigned int txdata1; + unsigned int rxdata0; + unsigned int rxdata1; +}; + +struct acp3400_i2c_timer_regs { + unsigned int loadval; /* 0x20 */ + unsigned int val; /* 0x24 */ + unsigned int control; /* 0x28 */ + unsigned int irq_clr; /* 0x2c */ + unsigned int irq_stat_raw; /* 0x30 */ + unsigned int irq_stat; /* 0x34 */ + unsigned int bg_loadval; /* 0x38 */ +}; + + +/* Master Clock Configuration */ +/* Configured clock frequency i2c_freq = 100kHz. */ + +/* I2C register values */ +#define ACPI2C_CLK_100KHZ (1000 | (1000 << 16)) +#define ACPI2C_MSTSHC (940 | (800 << 16)) +#define ACPI2C_MSPSHC (800 | (0 << 16)) +#define ACPI2C_MDSHC (255 | (127 << 16)) + +#define ACPI2C_XFER_START 0x00000001 +#define ACPI2C_XFER_DONE 0x00000001 +#define ACPI2C_READ_MODE 0x00000200 +#define ACPI2C_STOP_MODE 0x20000000 +#define ACPI2C_MASTER_MODE 0x00000100 +#define ACPI2C_MASTER_OP_CLEAR 0x00000400 +#define ACPI2C_10BIT_ADDR 0x00000080 +#define ACPI2C_CLEAR_IRQ 0x0000000F +#define ACPI2C_DELAY 500 /* us */ +#define ACPI2C_RETRIES 100 +#define ACPI2C_REG_BSIZE 4 /* bytes */ +#define ACPI2C_DATA_REGS 2 + +struct acp3400_i2c { + struct device *dev; + struct i2c_adapter adap; + struct acp3400_i2c_regs __iomem *i2c_regs; + struct acp3400_i2c_timer_regs __iomem *timer_regs; + struct mutex i2c_lock; +}; + +#ifdef ACP3400_I2C_DEBUG +static void dump_regs(struct acp3400_i2c *i2c) +{ + pr_info("i2c-reg: txconfig %8.8x\n", + in_le32(&i2c->i2c_regs->txconfig)); + pr_info("i2c-reg: rxconfig %8.8x\n", + in_le32(&i2c->i2c_regs->rxconfig)); + pr_info("i2c-reg: txstatus %8.8x\n", + in_le32(&i2c->i2c_regs->txstatus)); + pr_info("i2c-reg: rxstatus %8.8x\n", + in_le32(&i2c->i2c_regs->rxstatus)); + pr_info("i2c-reg: irqenable %8.8x\n", + in_le32(&i2c->i2c_regs->irqenable)); + pr_info("i2c-reg: irqclear %8.8x\n", + in_le32(&i2c->i2c_regs->irqclear)); + pr_info("i2c-reg: irqstatus %8.8x\n", + in_le32(&i2c->i2c_regs->irqstatus)); + pr_info("i2c-reg: clkconfig %8.8x\n", + in_le32(&i2c->i2c_regs->clkconfig)); + pr_info("i2c-reg: startsetup %8.8x\n", + in_le32(&i2c->i2c_regs->startsetup)); + pr_info("i2c-reg: stopsetup %8.8x\n", + in_le32(&i2c->i2c_regs->stopsetup)); + pr_info("i2c-reg: datasetup %8.8x\n", + in_le32(&i2c->i2c_regs->datasetup)); + pr_info("i2c-reg: bypassmode %8.8x\n", + in_le32(&i2c->i2c_regs->bypassmode)); + pr_info("i2c-reg: slaveaddr %8.8x\n", + in_le32(&i2c->i2c_regs->slaveaddr)); + pr_info("i2c-reg: txdata0 %8.8x\n", + in_le32(&i2c->i2c_regs->txdata0)); + pr_info("i2c-reg: txdata1 %8.8x\n", + in_le32(&i2c->i2c_regs->txdata1)); + pr_info("i2c-reg: rxdata0 %8.8x\n", + in_le32(&i2c->i2c_regs->rxdata0)); + pr_info("i2c-reg: rxdata1 %8.8x\n", + in_le32(&i2c->i2c_regs->rxdata1)); + pr_info("i2c-timer-reg: loadval %8.8x\n", + in_le32(&i2c->timer_regs->loadval)); + pr_info("i2c-timer-reg: val %8.8x\n", + in_le32(&i2c->timer_regs->val)); + pr_info("i2c-timer-reg: control %8.8x\n", + in_le32(&i2c->timer_regs->control)); +} +#endif +/* + * Low level write routine + */ +static int acp3400_i2c_write_bytes(struct acp3400_i2c *i2c, + struct i2c_msg *msgs) +{ + unsigned char *bufp = msgs->buf; + unsigned int reg_value, data[2] = {0, 0}; + int cnt, ret = 0; + + if (msgs->len > (ACPI2C_REG_BSIZE * ACPI2C_DATA_REGS)) + msgs->len = ACPI2C_REG_BSIZE * ACPI2C_DATA_REGS; + + /* Set message */ + for (cnt = 0; cnt < msgs->len; cnt++) { + data[1] <<= 8; + data[1] |= ((data[0] >> 24) & 0xFF); + data[0] <<= 8; + data[0] |= bufp[cnt]; + } + out_le32(&i2c->i2c_regs->txdata0, data[0]); + out_le32(&i2c->i2c_regs->txdata1, data[1]); + + /* setup and start a transmission */ + reg_value = ACPI2C_MASTER_MODE | ACPI2C_STOP_MODE; + reg_value |= (msgs->len << 1) & 0x1e; + if (msgs->flags & I2C_M_TEN) { + reg_value |= ACPI2C_10BIT_ADDR; + /* TODO update slave address accordingly */ + } + out_le32(&i2c->i2c_regs->txconfig, reg_value); + + reg_value &= ~ACPI2C_STOP_MODE; + out_le32(&i2c->i2c_regs->txconfig, reg_value); + + reg_value |= ACPI2C_XFER_START; + out_le32(&i2c->i2c_regs->txconfig, reg_value); + + /* Check if the message has been sent + * Wait a totally of 1 s for the transmission */ + reg_value = cnt = 0; + while (0 == reg_value && cnt++ < ACPI2C_RETRIES) { + udelay(ACPI2C_DELAY); + /* Read transmission status */ + reg_value = in_le32(&i2c->i2c_regs->txstatus); + } +#ifdef ACP3400_I2C_DEBUG + if (ACPI2C_XFER_DONE != reg_value) + dump_regs(i2c); +#endif + /* Clear registers */ + out_le32(&i2c->i2c_regs->irqclear, ACPI2C_CLEAR_IRQ); + out_le32(&i2c->i2c_regs->txconfig, ACPI2C_MASTER_OP_CLEAR); + + out_le32(&i2c->i2c_regs->txconfig, ACPI2C_STOP_MODE); + + if (ACPI2C_XFER_DONE == reg_value) + ret = msgs->len; + else + ret = -EIO; + + return ret; +} + +/* + * Low level read routine + */ + +static int acp3400_i2c_read_bytes(struct acp3400_i2c *i2c, + struct i2c_msg *msgs) +{ + unsigned char *bufp = msgs->buf; + unsigned int reg_value, data[2]; + int cnt, ret = 0; + + if (msgs->len > (ACPI2C_REG_BSIZE * ACPI2C_DATA_REGS)) + msgs->len = ACPI2C_REG_BSIZE * ACPI2C_DATA_REGS; + + /* Setup a reception */ + reg_value = (msgs->len << 1) & 0x1e; + if (msgs->flags & I2C_M_TEN) { + reg_value |= ACPI2C_10BIT_ADDR; + /* TODO update slave address accordingly */ + } + out_le32(&i2c->i2c_regs->rxconfig, reg_value); + + /* set read mode and start clock */ + reg_value |= ACPI2C_XFER_START; + out_le32(&i2c->i2c_regs->rxconfig, reg_value); + + reg_value = ACPI2C_STOP_MODE | ACPI2C_MASTER_MODE | ACPI2C_READ_MODE; + out_le32(&i2c->i2c_regs->txconfig, reg_value); + + reg_value &= ~ACPI2C_STOP_MODE; + out_le32(&i2c->i2c_regs->txconfig, reg_value); + + reg_value |= ACPI2C_XFER_START; + out_le32(&i2c->i2c_regs->txconfig, reg_value); + + /* Check if the message has been received + * Wait a totally of 1 s for the reception */ + reg_value = cnt = 0; + while (0 == (ACPI2C_XFER_DONE & reg_value) && + cnt++ < ACPI2C_RETRIES) { + udelay(ACPI2C_DELAY); + /* Read transmission status */ + reg_value = in_le32(&i2c->i2c_regs->rxstatus); + } + + /* get message */ + data[0] = in_le32(&i2c->i2c_regs->rxdata0); + data[1] = in_le32(&i2c->i2c_regs->rxdata1); + for (cnt = 0; cnt < msgs->len; cnt++) { + if (cnt < ACPI2C_REG_BSIZE) + bufp[cnt] = data[0] >> ((8 * cnt) & 0xFF); + else + bufp[cnt] = data[1] >> + ((8 * (cnt - ACPI2C_REG_BSIZE)) & 0xFF); + } +#ifdef ACP3400_I2C_DEBUG + if (ACPI2C_XFER_DONE != (reg_value & 0x03)) + dump_regs(i2c); +#endif + /* clear registers */ + out_le32(&i2c->i2c_regs->irqclear, ACPI2C_CLEAR_IRQ); + out_le32(&i2c->i2c_regs->txconfig, ACPI2C_MASTER_OP_CLEAR); + + out_le32(&i2c->i2c_regs->txconfig, ACPI2C_STOP_MODE); + + if (ACPI2C_XFER_DONE == (reg_value & 0x03)) + ret = msgs->len; + else + ret = -EIO; + + return ret; +} + +/* + * I2C timer setup + */ +static void acp3400_i2c_timer_setup(struct acp3400_i2c *i2c) +{ + /* disable timer 1 */ + out_le32(&i2c->timer_regs->control, 0); + /* Program the Timer1 Load Value register with a value that sets + * the timer period to 250 ns (that is, 4 MHz frequency). When you + * configure the ACP peripheral clock (clk_per) for 200 MHz, the + * Timer1 Load Value is 0x31. */ + out_le32(&i2c->timer_regs->loadval, 0x31); + out_le32(&i2c->timer_regs->bg_loadval, 0x31); + + /* Configure and enable Timer1 for periodic wrapping mode + * with a prescaler of 1 by writing 0xc0 to the Timer1 Control + * Register. */ + out_le32(&i2c->timer_regs->control, 0xc0); +} +/* + * Low level master transfer routine + */ +static int acp3400_i2c_xfer_bytes(struct acp3400_i2c *i2c, + struct i2c_msg *msgs) +{ + int ret = 0; + + mutex_lock(&i2c->i2c_lock); + /* Prepare ACP3400 I2C for a transaction */ + out_le32(&i2c->i2c_regs->txconfig, + ACPI2C_MASTER_OP_CLEAR | ACPI2C_MASTER_MODE); + + out_le32(&i2c->i2c_regs->txconfig, + ACPI2C_MASTER_MODE | ACPI2C_STOP_MODE); + + /* I2C clock frequency and duty cycle */ + out_le32(&i2c->i2c_regs->clkconfig, ACPI2C_CLK_100KHZ); + /* The setup and hold durations for the START condition. */ + out_le32(&i2c->i2c_regs->startsetup, ACPI2C_MSTSHC); + /* The setup and hold durations for the STOP condition. */ + out_le32(&i2c->i2c_regs->stopsetup, ACPI2C_MSPSHC); + /* The setup and hold durations for the data bits. */ + out_le32(&i2c->i2c_regs->datasetup, ACPI2C_MDSHC); + /* Set Slave Address */ + out_le32(&i2c->i2c_regs->slaveaddr, msgs->addr); + /* Disable the actions for which the host requires to be interrupted */ + out_le32(&i2c->i2c_regs->irqenable, 0); + + /* Send/Receive Data */ + if (msgs->flags & I2C_M_RD) + ret = acp3400_i2c_read_bytes(i2c, msgs); + else + ret = acp3400_i2c_write_bytes(i2c, msgs); + + mutex_unlock(&i2c->i2c_lock); + return ret; +} +static void acp3400_i2c_dummy_xfer(struct acp3400_i2c *i2c) +{ + /* Prepare ACP3400 I2C for a transaction */ + out_le32(&i2c->i2c_regs->txconfig, + ACPI2C_MASTER_OP_CLEAR | ACPI2C_MASTER_MODE); + + out_le32(&i2c->i2c_regs->txconfig, + ACPI2C_MASTER_MODE | ACPI2C_STOP_MODE); + + /* I2C clock frequency and duty cycle */ + out_le32(&i2c->i2c_regs->clkconfig, ACPI2C_CLK_100KHZ); + /* The setup and hold durations for the START condition. */ + out_le32(&i2c->i2c_regs->startsetup, ACPI2C_MSTSHC); + /* The setup and hold durations for the STOP condition. */ + out_le32(&i2c->i2c_regs->stopsetup, ACPI2C_MSPSHC); + /* The setup and hold durations for the data bits. */ + out_le32(&i2c->i2c_regs->datasetup, ACPI2C_MDSHC); + /* Set Dummy Slave Address */ + out_le32(&i2c->i2c_regs->slaveaddr, 0x7f); + /* Disable the actions for which the host requires to be interrupted */ + out_le32(&i2c->i2c_regs->irqenable, 0); + /* Number of bytes 0, clear stop mode */ + out_le32(&i2c->i2c_regs->txconfig, ACPI2C_MASTER_MODE); + + /* Set Transmit Ready - triggers the transmit transaction */ + out_le32(&i2c->i2c_regs->txconfig, + (ACPI2C_XFER_START | ACPI2C_MASTER_MODE)); + +} + +/* + * Generic master transfer entrypoint. + * Returns the number of processed messages or error (<0) + */ +static int acp3400_i2c_xfer(struct i2c_adapter *adap, + struct i2c_msg *msgs, int num) +{ + struct acp3400_i2c *i2c = i2c_get_adapdata(adap); + int msg_cnt, ret = 0; + + if (!num) + return 0; + +#ifdef ACP3400_I2C_DEBUG + if (num == 1 && msgs[0].addr == 0x7f && msgs[0].len == 0) { + mutex_lock(&i2c->i2c_lock); + acp3400_i2c_dummy_xfer(i2c); + mutex_unlock(&i2c->i2c_lock); + return 0; + } +#endif + /* + * Check the sanity of the passed messages. + * Uhh, generic i2c layer is more suitable place for such code... + */ + if ((msgs[0].addr > 0x3ff) || + (!(msgs[0].flags & I2C_M_TEN) && (msgs[0].addr > 0x7f))) + return -EINVAL; + + for (msg_cnt = 0; msg_cnt < num; ++msg_cnt) { + if (msgs[msg_cnt].len <= 0) + return -EINVAL; + if ((msgs[0].addr != msgs[msg_cnt].addr) || + ((msgs[0].flags & I2C_M_TEN) != + (msgs[msg_cnt].flags & I2C_M_TEN))) + return -EINVAL; + } + + /* Do real transfer */ + for (msg_cnt = 0; msg_cnt < num; msg_cnt++) + ret = acp3400_i2c_xfer_bytes(i2c, &msgs[msg_cnt]); + return ret < 0 ? ret : num; +} + +static u32 acp3400_i2c_functionality(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C | I2C_FUNC_10BIT_ADDR; +} + +static const struct i2c_algorithm acp3400_i2c_algo = { + .master_xfer = acp3400_i2c_xfer, + .functionality = acp3400_i2c_functionality, +}; + +static struct i2c_adapter acp3400_i2c_ops = { + .owner = THIS_MODULE, + .name = "ACP3400 adapter", + .class = I2C_CLASS_HWMON | I2C_CLASS_SPD, + .algo = &acp3400_i2c_algo, + .timeout = HZ, +}; + +static int acp34xx_i2c_probe(struct platform_device *dev) +{ + struct device_node *np = dev->dev.of_node; + + struct acp3400_i2c *i2c; + int result = -ENODEV; + + i2c = kzalloc(sizeof(*i2c), GFP_KERNEL); + if (!i2c) + goto err; + + i2c->i2c_regs = of_iomap(np, 0); + i2c->timer_regs = of_iomap(np, 1); + if (!i2c->i2c_regs || !i2c->timer_regs) { + pr_err("%s: failed to map I/O\n", np->full_name); + goto err; + } + + i2c->adap = acp3400_i2c_ops; + i2c_set_adapdata(&i2c->adap, i2c); + i2c->adap.dev.of_node = of_node_get(np); + mutex_init(&i2c->i2c_lock); + + /* I2C timer setup */ + acp3400_i2c_timer_setup(i2c); + acp3400_i2c_dummy_xfer(i2c); + result = i2c_add_adapter(&i2c->adap); + if (result < 0) { + pr_err("%s: failed to add adapter\n", + np->full_name); + goto err; + } + + pr_info("%s: adapter has been added\n", np->full_name); + + of_i2c_register_devices(&i2c->adap); + + dev_set_drvdata(&dev->dev, i2c); + return 0; +err: + if (i2c) { + if (i2c->i2c_regs) + iounmap(i2c->i2c_regs); + if (i2c->timer_regs) + iounmap(i2c->timer_regs); + kfree(i2c); + } + + return result; +} + + +static int acp34xx_i2c_remove(struct platform_device *dev) +{ + struct acp3400_i2c *i2c = dev_get_drvdata(&dev->dev); + + i2c_del_adapter(&i2c->adap); + kfree(i2c); + + return 0; +} + +static struct of_device_id acp_i2c_match[] = { + { + .compatible = "acp-i2c", + }, + { + .compatible = "acp,acp3400-i2c", + }, + { /* end of list */ }, +}; + +static struct platform_driver acp_i2c_driver = { + .driver = { + .name = "acp-i2c", + .owner = THIS_MODULE, + .of_match_table = acp_i2c_match, + }, + .probe = acp34xx_i2c_probe, + .remove = acp34xx_i2c_remove, +}; + +module_platform_driver(acp_i2c_driver); + +MODULE_AUTHOR("Andrey Panteleev <andrey.xx.pantel...@ericsson.com>"); +MODULE_DESCRIPTION("I2C adapter for ACP3400"); +MODULE_LICENSE("GPL"); -- 1.8.3 _______________________________________________ linux-yocto mailing list linux-yocto@yoctoproject.org https://lists.yoctoproject.org/listinfo/linux-yocto