[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-ID: <BANLkTinHsg1FWRdAE2dxsJ6Shxrh7+HUeA@mail.gmail.com>
Date: Wed, 29 Jun 2011 14:39:38 +0800
From: Po-Yu Chuang <ratbert.chuang@...il.com>
To: Ben Dooks <ben-i2c@...ff.org>
Cc: linux-i2c@...r.kernel.org, linux-kernel@...r.kernel.org,
khali@...ux-fr.org, ben-linux@...ff.org,
Po-Yu Chuang <ratbert@...aday-tech.com>
Subject: Re: [PATCH] i2c: add Faraday FTIIC010 I2C controller support
Hi Ben,
On Tue, Jun 28, 2011 at 5:59 AM, Ben Dooks <ben-i2c@...ff.org> wrote:
> On Tue, Jun 14, 2011 at 02:19:29PM +0800, Po-Yu Chuang wrote:
>> From: Po-Yu Chuang <ratbert@...aday-tech.com>
>>
>> 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@...aday-tech.com>
>> ---
>> 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@...aday-tech.com>
>> + *
>> + * 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@...r.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Powered by blists - more mailing lists