Re: [PATCH] i2c: add Faraday FTIIC010 I2C controller support

From: Po-Yu Chuang
Date: Wed Jun 29 2011 - 02:41:11 EST


Hi Ben,

On Tue, Jun 28, 2011 at 5:59 AM, Ben Dooks <ben-i2c@xxxxxxxxx> wrote:
> On Tue, Jun 14, 2011 at 02:19:29PM +0800, Po-Yu Chuang wrote:
>> 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.
>> +
>
> is there a better depends line for this? is there an ARCH_xxx or some
> other way of ensuring this doesn't get shown for all systems that
> happen to support CLK?

OK, will fix this.

>
>> Â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)
>> + Â Â Â Â Â Â ;
>
> how about calls to cpu_relax() during these loops?
> also, do we ever expect timeout?

OK, will fix this.

>
>> +}
>
> I think iowrite32/ioread32 are ok on these.

Not sure what do you mean here.

>> +
>> +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);
>> +}
>
> maybe this could be merged into the parent.

Do you mean merge the content of this function into ftiic010_master_xfer?
Well, I prefer the current small function.

>
> is there any way of using the interrupt handler to do the
> actual transfers, this seems to involve a lot of busy-waiting
> for the hardware.

Originally I used interrupt-driven data transfer, but some users told me that
the latency of interrupt handling is too long for their application.
That's why I use polling now...

>
>> +/******************************************************************************
>> + * 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;
>
> error message, -ENXIO IIRC gets silently discarded by the bus
> layer.

OK, will add it.

>
>> +
>> + Â Â irq = platform_get_irq(pdev, 0);
>> + Â Â if (irq < 0)
>> + Â Â Â Â Â Â return irq;
>
> error print would be useful.

OK

>
>> + Â Â 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;
>> + Â Â }
>
> do you really need a named clock? sounds like the clk_ usage on
> this platform needs some work to it.

Let me think about this.

[snip]

Thanks for your review.
I will fix the mentioned issues and resubmit later.

Best regards,
Po-Yu Chuang
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/