From: Po-Yu Chuang <ratbert@xxxxxxxxxxxxxxxx> Support for Faraday FTIIC010 I2C controller. It is used on Faraday A320, A369 and some other ARM SoC's. Signed-off-by: Po-Yu Chuang <ratbert@xxxxxxxxxxxxxxxx> --- drivers/i2c/busses/Kconfig | 7 + drivers/i2c/busses/Makefile | 1 + drivers/i2c/busses/i2c-ftiic010.c | 434 +++++++++++++++++++++++++++++++++++++ drivers/i2c/busses/i2c-ftiic010.h | 155 +++++++++++++ 4 files changed, 597 insertions(+), 0 deletions(-) create mode 100644 drivers/i2c/busses/i2c-ftiic010.c create mode 100644 drivers/i2c/busses/i2c-ftiic010.h diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 326652f..612c2b1 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -358,6 +358,13 @@ config I2C_DESIGNWARE This driver can also be built as a module. If so, the module will be called i2c-designware. +config I2C_FTIIC010 + tristate "Faraday FTIIC010 I2C controller" + depends on HAVE_CLK + help + Support for Faraday FTIIC010 I2C controller. It is used on + Faraday A320, A369 and some other ARM SoC's. + config I2C_GPIO tristate "GPIO-based bitbanging I2C" depends on GENERIC_GPIO diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index e6cf294..c49570d 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -34,6 +34,7 @@ obj-$(CONFIG_I2C_BLACKFIN_TWI) += i2c-bfin-twi.o obj-$(CONFIG_I2C_CPM) += i2c-cpm.o obj-$(CONFIG_I2C_DAVINCI) += i2c-davinci.o obj-$(CONFIG_I2C_DESIGNWARE) += i2c-designware.o +obj-$(CONFIG_I2C_FTIIC010) += i2c-ftiic010.o obj-$(CONFIG_I2C_GPIO) += i2c-gpio.o obj-$(CONFIG_I2C_HIGHLANDER) += i2c-highlander.o obj-$(CONFIG_I2C_IBM_IIC) += i2c-ibm_iic.o diff --git a/drivers/i2c/busses/i2c-ftiic010.c b/drivers/i2c/busses/i2c-ftiic010.c new file mode 100644 index 0000000..ed86f06 --- /dev/null +++ b/drivers/i2c/busses/i2c-ftiic010.c @@ -0,0 +1,434 @@ +/* + * Faraday FTIIC010 I2C Controller + * + * (C) Copyright 2010-2011 Faraday Technology + * Po-Yu Chuang <ratbert@xxxxxxxxxxxxxxxx> + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include "i2c-ftiic010.h" + +#define SCL_SPEED (100 * 1000) +#define MAX_RETRY 1000 + +#define GSR 5 +#define TSR 5 + +struct ftiic010 { + struct resource *res; + void __iomem *base; + int irq; + struct clk *clk; + struct i2c_adapter adapter; +}; + +/****************************************************************************** + * internal functions + *****************************************************************************/ +static void ftiic010_reset(struct ftiic010 *ftiic010) +{ + int cr = FTIIC010_CR_I2C_RST; + + iowrite32(cr, ftiic010->base + FTIIC010_OFFSET_CR); + + /* wait until reset bit cleared by hw */ + while (ioread32(ftiic010->base + FTIIC010_OFFSET_CR) & FTIIC010_CR_I2C_RST) + ; +} + +static void ftiic010_set_clock_speed(struct ftiic010 *ftiic010, int hz) +{ + unsigned long pclk; + int cdr; + + pclk = clk_get_rate(ftiic010->clk); + + cdr = (pclk / hz - GSR) / 2 - 2; + cdr &= FTIIC010_CDR_MASK; + + dev_dbg(&ftiic010->adapter.dev, " [CDR] = %08x\n", cdr); + iowrite32(cdr, ftiic010->base + FTIIC010_OFFSET_CDR); +} + +static void ftiic010_set_tgsr(struct ftiic010 *ftiic010, int tsr, int gsr) +{ + int tgsr; + + tgsr = FTIIC010_TGSR_TSR(tsr); + tgsr |= FTIIC010_TGSR_GSR(gsr); + + dev_dbg(&ftiic010->adapter.dev, " [TGSR] = %08x\n", tgsr); + iowrite32(tgsr, ftiic010->base + FTIIC010_OFFSET_TGSR); +} + +static void ftiic010_hw_init(struct ftiic010 *ftiic010) +{ + ftiic010_reset(ftiic010); + ftiic010_set_tgsr(ftiic010, TSR, GSR); + ftiic010_set_clock_speed(ftiic010, SCL_SPEED); +} + +static inline void ftiic010_set_cr(struct ftiic010 *ftiic010, int start, + int stop, int nak) +{ + int cr; + + cr = FTIIC010_CR_I2C_EN + | FTIIC010_CR_SCL_EN + | FTIIC010_CR_TB_EN + | FTIIC010_CR_BERRI_EN + | FTIIC010_CR_ALI_EN; + + if (start) + cr |= FTIIC010_CR_START; + + if (stop) + cr |= FTIIC010_CR_STOP; + + if (nak) + cr |= FTIIC010_CR_NAK; + + iowrite32(cr, ftiic010->base + FTIIC010_OFFSET_CR); +} + +static int ftiic010_tx_byte(struct ftiic010 *ftiic010, __u8 data, int start, + int stop) +{ + struct i2c_adapter *adapter = &ftiic010->adapter; + int i; + + iowrite32(data, ftiic010->base + FTIIC010_OFFSET_DR); + ftiic010_set_cr(ftiic010, start, stop, 0); + + for (i = 0; i < MAX_RETRY; i++) { + unsigned int status; + + status = ioread32(ftiic010->base + FTIIC010_OFFSET_SR); + if (status & FTIIC010_SR_DT) + return 0; + + udelay(1); + } + + dev_err(&adapter->dev, "Failed to transmit\n"); + ftiic010_reset(ftiic010); + return -EIO; +} + +static int ftiic010_rx_byte(struct ftiic010 *ftiic010, int stop, int nak) +{ + struct i2c_adapter *adapter = &ftiic010->adapter; + int i; + + ftiic010_set_cr(ftiic010, 0, stop, nak); + + for (i = 0; i < MAX_RETRY; i++) { + unsigned int status; + + status = ioread32(ftiic010->base + FTIIC010_OFFSET_SR); + if (status & FTIIC010_SR_DR) { + return ioread32(ftiic010->base + FTIIC010_OFFSET_DR) + & FTIIC010_DR_MASK; + } + + udelay(1); + } + + dev_err(&adapter->dev, "Failed to receive\n"); + ftiic010_reset(ftiic010); + return -EIO; +} + +static int ftiic010_tx_msg(struct ftiic010 *ftiic010, + struct i2c_msg *msg, int last) +{ + __u8 data; + int ret; + int i; + + data = (msg->addr & 0x7f) << 1; + ret = ftiic010_tx_byte(ftiic010, data, 1, 0); + if (ret < 0) + return ret; + + for (i = 0; i < msg->len; i++) { + int stop = 0; + + if (last && i + 1 == msg->len) + stop = 1; + + ret = ftiic010_tx_byte(ftiic010, msg->buf[i], 0, stop); + if (ret < 0) + return ret; + } + + return 0; +} + +static int ftiic010_rx_msg(struct ftiic010 *ftiic010, + struct i2c_msg *msg, int last) +{ + __u8 data; + int ret; + int i; + + data = (msg->addr & 0x7f) << 1 | 1; + ret = ftiic010_tx_byte(ftiic010, data, 1, 0); + if (ret < 0) + return ret; + + for (i = 0; i < msg->len; i++) { + int nak = 0; + + if (i + 1 == msg->len) + nak = 1; + + ret = ftiic010_rx_byte(ftiic010, last, nak); + if (ret < 0) + return ret; + + msg->buf[i] = ret; + } + + return 0; +} + +static int ftiic010_do_msg(struct ftiic010 *ftiic010, + struct i2c_msg *msg, int last) +{ + if (msg->flags & I2C_M_RD) + return ftiic010_rx_msg(ftiic010, msg, last); + else + return ftiic010_tx_msg(ftiic010, msg, last); +} + +/****************************************************************************** + * interrupt handler + *****************************************************************************/ +static irqreturn_t ftiic010_interrupt(int irq, void *dev_id) +{ + struct ftiic010 *ftiic010 = dev_id; + struct i2c_adapter *adapter = &ftiic010->adapter; + int sr; + + sr = ioread32(ftiic010->base + FTIIC010_OFFSET_SR); + + if (sr & FTIIC010_SR_BERR) { + dev_err(&adapter->dev, "NAK!\n"); + ftiic010_reset(ftiic010); + } + + if (sr & FTIIC010_SR_AL) { + dev_err(&adapter->dev, "arbitration lost!\n"); + ftiic010_reset(ftiic010); + } + + return IRQ_HANDLED; +} + +/****************************************************************************** + * struct i2c_algorithm functions + *****************************************************************************/ +static int ftiic010_master_xfer(struct i2c_adapter *adapter, + struct i2c_msg *msgs, int num) +{ + struct ftiic010 *ftiic010 = i2c_get_adapdata(adapter); + int i; + + for (i = 0; i < num; i++) { + int last = 0; + int ret; + + if (i == num - 1) + last = 1; + + ret = ftiic010_do_msg(ftiic010, &msgs[i], last); + if (ret) + return ret; + } + + return num; +} + +static u32 ftiic010_functionality(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static struct i2c_algorithm ftiic010_algorithm = { + .master_xfer = ftiic010_master_xfer, + .functionality = ftiic010_functionality, +}; + +/****************************************************************************** + * struct platform_driver functions + *****************************************************************************/ +static int ftiic010_probe(struct platform_device *pdev) +{ + struct ftiic010 *ftiic010; + struct resource *res; + struct clk *clk; + int irq; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENXIO; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ftiic010 = kzalloc(sizeof(*ftiic010), GFP_KERNEL); + if (!ftiic010) { + dev_err(&pdev->dev, "Could not allocate private data\n"); + ret = -ENOMEM; + goto err_alloc; + } + + ftiic010->res = request_mem_region(res->start, resource_size(res), + dev_name(&pdev->dev)); + if (!ftiic010->res) { + dev_err(&pdev->dev, "Could not reserve memory region\n"); + ret = -ENOMEM; + goto err_req_mem; + } + + ftiic010->base = ioremap(res->start, resource_size(res)); + if (!ftiic010->base) { + dev_err(&pdev->dev, "Failed to ioremap\n"); + ret = -ENOMEM; + goto err_ioremap; + } + + ret = request_irq(irq, ftiic010_interrupt, IRQF_SHARED, pdev->name, + ftiic010); + if (ret) { + dev_err(&pdev->dev, "Failed to request irq %d\n", irq); + goto err_req_irq; + } + + ftiic010->irq = irq; + + clk = clk_get(NULL, "pclk"); + if (!clk) { + dev_err(&pdev->dev, "Failed to get pclk\n"); + goto err_clk; + } + + clk_enable(clk); + ftiic010->clk = clk; + + /* + * initialize i2c adapter + */ + ftiic010->adapter.owner = THIS_MODULE; + ftiic010->adapter.algo = &ftiic010_algorithm; + ftiic010->adapter.timeout = 1; + ftiic010->adapter.dev.parent = &pdev->dev; + + /* + * If "dev->id" is negative we consider it as zero. + * The reason to do so is to avoid sysfs names that only make + * sense when there are multiple adapters. + */ + ftiic010->adapter.nr = pdev->id != -1 ? pdev->id : 0; + strcpy(ftiic010->adapter.name, "ftiic010 adapter"); + + i2c_set_adapdata(&ftiic010->adapter, ftiic010); + + ftiic010_hw_init(ftiic010); + + ret = i2c_add_numbered_adapter(&ftiic010->adapter); + if (ret) { + dev_err(&pdev->dev, "Failed to add i2c adapter\n"); + goto err_add_adapter; + } + + platform_set_drvdata(pdev, ftiic010); + + dev_info(&pdev->dev, "irq %d, mapped at %p\n", irq, ftiic010->base); + + return 0; + +err_add_adapter: + clk_disable(clk); + clk_put(clk); +err_clk: + free_irq(ftiic010->irq, ftiic010); +err_req_irq: + iounmap(ftiic010->base); +err_ioremap: + release_resource(ftiic010->res); +err_req_mem: + kfree(ftiic010); +err_alloc: + return ret; +}; + +static int __devexit ftiic010_remove(struct platform_device *pdev) +{ + struct ftiic010 *ftiic010 = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + i2c_del_adapter(&ftiic010->adapter); + clk_disable(ftiic010->clk); + clk_put(ftiic010->clk); + free_irq(ftiic010->irq, ftiic010); + iounmap(ftiic010->base); + release_resource(ftiic010->res); + kfree(ftiic010); + return 0; +}; + + +static struct platform_driver ftiic010_driver = { + .probe = ftiic010_probe, + .remove = __devexit_p(ftiic010_remove), + .driver = { + .name = "ftiic010", + .owner = THIS_MODULE, + }, +}; + +/****************************************************************************** + * initialization / finalization + *****************************************************************************/ +static int __init ftiic010_init(void) +{ + return platform_driver_register(&ftiic010_driver); +} + +static void __exit ftiic010_exit(void) +{ + platform_driver_unregister(&ftiic010_driver); +} + +module_init(ftiic010_init); +module_exit(ftiic010_exit); + +MODULE_AUTHOR("Po-Yu Chuang <ratbert@xxxxxxxxxxxxxxxx>"); +MODULE_DESCRIPTION("FTIIC010 I2C bus adapter"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/busses/i2c-ftiic010.h b/drivers/i2c/busses/i2c-ftiic010.h new file mode 100644 index 0000000..c1f4752 --- /dev/null +++ b/drivers/i2c/busses/i2c-ftiic010.h @@ -0,0 +1,155 @@ +/* + * Faraday FTIIC010 I2C Controller + * + * (C) Copyright 2010-2011 Faraday Technology + * Po-Yu Chuang <ratbert@xxxxxxxxxxxxxxxx> + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __FTIIC010_H +#define __FTIIC010_H + +#define FTIIC010_OFFSET_CR 0x00 +#define FTIIC010_OFFSET_SR 0x04 +#define FTIIC010_OFFSET_CDR 0x08 +#define FTIIC010_OFFSET_DR 0x0c +#define FTIIC010_OFFSET_SAR 0x10 +#define FTIIC010_OFFSET_TGSR 0x14 +#define FTIIC010_OFFSET_BMR 0x18 +#define FTIIC010_OFFSET_SMCR 0x1c +#define FTIIC010_OFFSET_MAXTR 0x20 +#define FTIIC010_OFFSET_MINTR 0x24 +#define FTIIC010_OFFSET_METR 0x28 +#define FTIIC010_OFFSET_SETR 0x2c +#define FTIIC010_OFFSET_REV 0x30 +#define FTIIC010_OFFSET_FEAT 0x34 + +/* + * Control Register + */ +#define FTIIC010_CR_I2C_RST (1 << 0) +#define FTIIC010_CR_I2C_EN (1 << 1) +#define FTIIC010_CR_SCL_EN (1 << 2) +#define FTIIC010_CR_GC_EN (1 << 3) +#define FTIIC010_CR_START (1 << 4) +#define FTIIC010_CR_STOP (1 << 5) +#define FTIIC010_CR_NAK (1 << 6) +#define FTIIC010_CR_TB_EN (1 << 7) +#define FTIIC010_CR_DTI_EN (1 << 8) +#define FTIIC010_CR_DRI_EN (1 << 9) +#define FTIIC010_CR_BERRI_EN (1 << 10) +#define FTIIC010_CR_STOPI_EN (1 << 11) +#define FTIIC010_CR_SAMI_EN (1 << 12) +#define FTIIC010_CR_ALI_EN (1 << 13) /* arbitration lost */ +#define FTIIC010_CR_STARTI_EN (1 << 14) +#define FTIIC010_CR_SCL_LOW (1 << 15) +#define FTIIC010_CR_SDA_LOW (1 << 16) +#define FTIIC010_CR_TESTMODE (1 << 17) + +/* + * Status Register + */ +#define FTIIC010_SR_RW (1 << 0) +#define FTIIC010_SR_ACK (1 << 1) +#define FTIIC010_SR_I2CBUSY (1 << 2) +#define FTIIC010_SR_BUSBUSY (1 << 3) +#define FTIIC010_SR_DT (1 << 4) +#define FTIIC010_SR_DR (1 << 5) +#define FTIIC010_SR_BERR (1 << 6) +#define FTIIC010_SR_STOP (1 << 7) +#define FTIIC010_SR_SAM (1 << 8) +#define FTIIC010_SR_GC (1 << 9) +#define FTIIC010_SR_AL (1 << 10) +#define FTIIC010_SR_START (1 << 11) +#define FTIIC010_SR_SEXT (1 << 12) +#define FTIIC010_SR_MEXT (1 << 13) +#define FTIIC010_SR_MINTIMEOUT (1 << 14) +#define FTIIC010_SR_MAXTIMEOUT (1 << 15) +#define FTIIC010_SR_ALERT (1 << 16) +#define FTIIC010_SR_SUSPEND (1 << 17) +#define FTIIC010_SR_RESUME (1 << 18) +#define FTIIC010_SR_ARA (1 << 19) +#define FTIIC010_SR_DDA (1 << 20) +#define FTIIC010_SR_SAL (1 << 21) + +/* + * Clock Division Register + */ +#define FTIIC010_CDR_MASK 0x3ffff + +/* + * Data Register + */ +#define FTIIC010_DR_MASK 0xff + +/* + * Slave Address Register + */ +#define FTIIC010_SAR_MASK 0x3ff +#define FTIIC010_SAR_EN10 (1 << 31) + +/* + * Set/Hold Time Glitch Suppression Setting Register + */ +#define FTIIC010_TGSR_TSR(x) ((x) & 0x3ff) /* should not be zero */ +#define FTIIC010_TGSR_GSR(x) (((x) & 0x7) << 10) + +/* + * Bus Monitor Register + */ +#define FTIIC010_BMR_SDA_IN (1 << 0) +#define FTIIC010_BMR_SCL_IN (1 << 1) + +/* + * SM Control Register + */ +#define FTIIC010_SMCR_SEXT_EN (1 << 0) +#define FTIIC010_SMCR_MEXT_EN (1 << 1) +#define FTIIC010_SMCR_TOUT_EN (1 << 2) +#define FTIIC010_SMCR_ALERT_EN (1 << 3) +#define FTIIC010_SMCR_SUS_EN (1 << 4) +#define FTIIC010_SMCR_RSM_EN (1 << 5) +#define FTIIC010_SMCR_SAL_EN (1 << 8) +#define FTIIC010_SMCR_ALERT_LOW (1 << 9) +#define FTIIC010_SMCR_SUS_LOW (1 << 10) +#define FTIIC010_SMCR_SUSOUT_EN (1 << 11) + +/* + * SM Maximum Timeout Register + */ +#define FTIIC010_MAXTR_MASK 0x3fffff + +/* + * SM Minimum Timeout Register + */ +#define FTIIC010_MINTR_MASK 0x3fffff + +/* + * SM Master Extend Time Register + */ +#define FTIIC010_METR_MASK 0xfffff + +/* + * SM Slave Extend Time Register + */ +#define FTIIC010_SETR_MASK 0x1fffff + +/* + * Feature Register + */ +#define FTIIC010_FEAT_SMBUS (1 << 0) + +#endif /* __FTIIC010_H */ -- 1.6.3.3 -- 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