* [PATCH] i2c: Add support SuperH SH7734 I2C bus controller @ 2012-04-11 23:57 Nobuhiro Iwamatsu 2012-04-12 4:18 ` Kuninori Morimoto [not found] ` <1334188641-14641-1-git-send-email-nobuhiro.iwamatsu.yj-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org> 0 siblings, 2 replies; 10+ messages in thread From: Nobuhiro Iwamatsu @ 2012-04-11 23:57 UTC (permalink / raw) To: linux-i2c; +Cc: linux-sh, Nobuhiro Iwamatsu This is the SuperH SH7734 I2C Controller Driver. A simple Master only support driver for the I2C included in processors SH7734 only. Signed-off-by: Nobuhiro Iwamatsu <nobuhiro.iwamatsu.yj@renesas.com> --- drivers/i2c/busses/Kconfig | 9 + drivers/i2c/busses/Makefile | 1 + drivers/i2c/busses/i2c-sh7734.c | 721 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 731 insertions(+), 0 deletions(-) create mode 100644 drivers/i2c/busses/i2c-sh7734.c diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index d2c5095..68d3181 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -634,6 +634,15 @@ config I2C_SH_MOBILE This driver can also be built as a module. If so, the module will be called i2c-sh_mobile. +config I2C_SH7734 + tristate "Renesas SH7734 I2C Controller" + depends on CPU_SUBTYPE_SH7734 + help + This driver supports the I2C interfaces on the Renesas SH7734. + + This driver can also be built as a module. If so, the module + will be called i2c-sh7734. + config I2C_SIMTEC tristate "Simtec Generic I2C interface" select I2C_ALGOBIT diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index 569567b..b7057f5 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -63,6 +63,7 @@ obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o 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_SH7734) += i2c-sh7734.o obj-$(CONFIG_I2C_SIMTEC) += i2c-simtec.o obj-$(CONFIG_I2C_SIRF) += i2c-sirf.o obj-$(CONFIG_I2C_STU300) += i2c-stu300.o diff --git a/drivers/i2c/busses/i2c-sh7734.c b/drivers/i2c/busses/i2c-sh7734.c new file mode 100644 index 0000000..89e37d0 --- /dev/null +++ b/drivers/i2c/busses/i2c-sh7734.c @@ -0,0 +1,721 @@ +/* + * SuperH SH7734 I2C Controller + * + * Copyright (C) 2008 Magnus Damm + * Copyright (C) 2012 Nobuhiro Iwamatsu <nobuhiro.iwamatsu.yj@renesas.com> + * Copyright (C) 2012 Renesas Solutions Corp. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Based on drivers/i2c/busses/i2c-sh_mobile.c. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/i2c/i2c-sh_mobile.h> + +enum sh7734_i2c_op { + OP_START_TRS = 0, + OP_START_SCP, + OP_TX_FIRST, + OP_TX_FIRST_END, + OP_TX, + OP_TX_END, + OP_TX_STOP, + OP_TX_STOP_CHK, + OP_TX_FINISH, + OP_RX, + OP_RX_BBSY_CLR, + OP_RX_LAST_DATA, + OP_RX_FINISH, + OP_RX_STOP, + OP_RX_STOP_CHK, +}; + +struct sh7734_i2c_data { + struct device *dev; + void __iomem *reg; + struct i2c_adapter adap; + unsigned long bus_speed; + struct clk *clk; + u_int8_t nf2cyc; + u_int8_t nf2cyc_clk; + u_int8_t iccr1_clk; + spinlock_t lock; + wait_queue_head_t wait; + struct i2c_msg *msg; + int pos; + int sr; + int irq; + int ackbr; + int state; + int data_num; +}; + +#define NORMAL_SPEED 100000 /* FAST_SPEED 400000 */ + +/* Register offsets */ +#define ICCR1 0x0 +#define ICCR2 0x1 +#define ICMR 0x2 +#define ICIER 0x3 +#define ICSR 0x4 +#define SAR 0x5 +#define ICDRT 0x6 +#define ICDRR 0x7 +#define NF2CYC 0x8 + +#define ICCR1_ICE 0x80 +#define ICCR1_RCVD 0x40 +#define ICCR1_MST 0x20 +#define ICCR1_TRS 0x10 + +#define SW_DONE 0x100 +#define SW_ERROR 0x200 + +#define ICCR2_BBSY 0x80 +#define ICCR2_SCP 0x40 +#define ICCR2_SDAO 0x20 +#define ICCR2_SDAOP 0x10 +#define ICCR2_SCLO 0x08 +#define ICCR2_I2CRST 0x02 + +#define ICSR_TDRE 0x80 +#define ICSR_TEND 0x40 +#define ICSR_RDRF 0x20 +#define ICSR_NACKF 0x10 +#define ICSR_STOP 0x08 +#define ICSR_AL 0x04 +#define ICSR_AAS 0x02 +#define ICSR_ADZ 0x01 + +#define ICIER_TIE 0x80 /* Transfer interrputs */ +#define ICIER_TEIE 0x40 /* Transfer end interrupts */ +#define ICIER_RIE 0x20 /* Receive interrupts */ +#define ICIER_NAKIE 0x10 /* NACK interrupts */ +#define ICIER_STIE 0x08 /* Stop interrupts */ +#define ICIER_ACKE 0x04 /* ACKbit check*/ +#define ICIER_ACKBR 0x02 /* set ACKbit / Receive */ +#define ICIER_ACKBT 0x01 /* set ACKbit / Transfer */ +#define ICIER_RX (ICIER_RIE|ICIER_TEIE|ICIER_TIE|ICIER_NAKIE|ICIER_STIE) +#define ICIER_TX (ICIER_TIE|ICIER_TEIE|ICIER_NAKIE|ICIER_STIE) + +#define I2C0_INTMASK 0xFF804288 +#define I2C1_INTMASK 0xFF80428C +#define I2C0_INTMASK_CLR 0xFF8042A8 +#define I2C1_INTMASK_CLR 0xFF8042AC +#define I2C0_INT 0xFF8040AC +#define I2C1_INT 0xFF8040D8 + +#define I2C_INT_TXI (1 << 4) +#define I2C_INT_TEI (1 << 3) +#define I2C_INT_RXI (1 << 2) +#define I2C_INT_NAKI (1 << 1) +#define I2C_INT_STPI (1 << 0) +#define I2C_INT_ALL \ + (I2C_INT_TXI|I2C_INT_TEI|I2C_INT_RXI|I2C_INT_NAKI|I2C_INT_STPI) + +static void +iic_wr(struct sh7734_i2c_data *pd, int offs, unsigned char data) +{ + iowrite8(data, pd->reg + offs); +} + +static unsigned char iic_rd(struct sh7734_i2c_data *pd, int offs) +{ + return ioread8(pd->reg + offs); +} + +static void iic_set_clr(struct sh7734_i2c_data *pd, int offs, + unsigned char set, unsigned char clr) +{ + iic_wr(pd, offs, (iic_rd(pd, offs) | set) & ~clr); +} + +static int clk_denom_tbl[] = { + 44, 52, 64, 72, 84, 92, 100, 108, 176, + 208, 256, 288, 336, 368, 400, 432, 352, 416, + 512, 576, 672, 736, 800, 864, 704, 832, 1024, + 1152, 1344, 1472, 1600, 1728 }; + +static void activate_ch(struct sh7734_i2c_data *pd, int num) +{ + unsigned long pclk; + unsigned long mode, mode_tmp; + unsigned int cks_bit; + long denom, speed; + unsigned int i; + + pclk = clk_get_rate(pd->clk) / 1000; + mode = speed = pd->bus_speed / 1000; + + for (i = 0; i < ARRAY_SIZE(clk_denom_tbl); i++) { + denom = (unsigned int)(pclk / clk_denom_tbl[i]); + if (denom > (speed * 2)) + continue; + else if ((speed - denom) > 100) + continue; + mode_tmp = (unsigned int)(denom % speed); + + if (mode_tmp < speed) { + if (mode_tmp < mode) { + mode = mode_tmp; + cks_bit = i; + } + } + } + + pd->nf2cyc = 1; + pd->nf2cyc_clk = (u_int8_t)(cks_bit & 0x10); + pd->iccr1_clk = (u_int8_t)(cks_bit & 0x0F); + pd->state = OP_START_TRS; + pd->ackbr = 0; + pd->data_num = num; + + /* Mask clear interrupts */ + if (pd->adap.nr == 0) + __raw_writel(I2C_INT_ALL, I2C0_INTMASK_CLR); + else + __raw_writel(I2C_INT_ALL, I2C1_INTMASK_CLR); +} + +static void deactivate_ch(struct sh7734_i2c_data *pd) +{ + /* Reset controller */ + iic_set_clr(pd, ICCR2, ICCR2_I2CRST, 0); + udelay(10); + iic_set_clr(pd, ICCR2, 0, ICCR2_I2CRST); + + /* Clear/disable interrupts */ + iic_wr(pd, ICSR, 0); + iic_wr(pd, ICIER, 0); + + /* Mask interrupts */ + if (pd->adap.nr == 0) + __raw_writel(I2C_INT_ALL, I2C0_INTMASK); + else + __raw_writel(I2C_INT_ALL, I2C1_INTMASK); + + /* Disable channel */ + iic_set_clr(pd, ICCR1, 0, ICCR1_ICE); + + pd->ackbr = 0; + /* Reset controller */ + iic_set_clr(pd, ICCR2, ICCR2_I2CRST, 0); + udelay(10); + iic_set_clr(pd, ICCR2, 0, ICCR2_I2CRST); +} + +static int i2c_op(struct sh7734_i2c_data *pd) +{ + int ret = 0; + unsigned char data; + unsigned long flags; + + spin_lock_irqsave(&pd->lock, flags); + + switch (pd->state) { + case OP_START_TRS: + if (!(iic_rd(pd, ICCR2) & ICCR2_BBSY)) { + /* set to master transfer mode */ + iic_set_clr(pd, ICCR1, ICCR1_MST | ICCR1_TRS, 0); + pd->state = OP_TX_FIRST; + } else { + dev_dbg(pd->dev, "Device busy\n"); + pd->state = OP_TX_STOP; + break; + } + + iic_set_clr(pd, ICCR2, ICCR2_BBSY, ICCR2_SCP); + break; + + case OP_START_SCP: + /* Send TIE, Set TDRE */ + iic_set_clr(pd, ICCR2, ICCR2_BBSY, ICCR2_SCP); + pd->state = OP_TX_FIRST; + break; + + case OP_TX_FIRST: + if (iic_rd(pd, ICSR) & ICSR_TDRE) { + /* Create command */ + data = (pd->msg->addr & 0x7f) << 1; + data |= (pd->msg->flags & I2C_M_RD) ? 1 : 0; + + /* write data with clear TDRE bit */ + iic_wr(pd, ICDRT, data); + dev_dbg(pd->dev, "Write data (ICDRT): %02X\n", data); + pd->state = OP_TX_FIRST_END; + } else + dev_dbg(pd->dev, "ICSR/TDRE is not set\n"); + + break; + + case OP_TX_FIRST_END: + if (!(iic_rd(pd, ICSR) & ICSR_TEND)) { + dev_dbg(pd->dev, "ICSR/TEND is not set\n"); + break; + } + + /* Don't clear TEND */ + if (pd->ackbr) { + if (pd->msg->flags & I2C_M_RD) { + iic_set_clr(pd, ICSR, 0, ICSR_TEND); + iic_set_clr(pd, ICCR1, ICCR1_MST, ICCR1_TRS); + iic_set_clr(pd, ICSR, 0, ICSR_TDRE); + + if (pd->msg->len == 1) { + iic_set_clr(pd, ICIER, ICIER_ACKBT, 0); + iic_set_clr(pd, ICCR1, ICCR1_RCVD, 0); + + data = iic_rd(pd, ICDRR); + /* If pd->msg->len == 1, + this read data is dummy. */ + dev_dbg(pd->dev, "Last - 1 read data %02X\n", + data); + + pd->state = OP_RX_STOP_CHK; + } else { + iic_set_clr(pd, ICIER, 0, ICIER_ACKBT); + data = iic_rd(pd, ICDRR); + dev_dbg(pd->dev, + "Dummy read data %02X\n", data); + + pd->state = OP_RX; + } + } else + pd->state = OP_TX_STOP; + + break; + } + + if (iic_rd(pd, ICIER) & ICIER_ACKBR) { + dev_err(pd->dev, "ACKBR Error...\n"); + pd->sr |= SW_ERROR; /* Save Error state */ + + pd->state = OP_TX_STOP; + break; + } else { + pd->ackbr = 1; + pd->state = OP_TX; + } + + /* Through to OP_TX */ + + case OP_TX: /* send data */ + if (iic_rd(pd, ICSR) & ICSR_TDRE) { + data = pd->msg->buf[pd->pos]; + /* write data with clear TDRE bit */ + iic_wr(pd, ICDRT, data); + dev_dbg(pd->dev, "Write data (ICDRT): %02X\n", data); + pd->pos++; + + pd->state = OP_TX_END; + } else + dev_dbg(pd->dev, "Retry Data translate\n"); + + break; + + case OP_TX_END: + if (!(iic_rd(pd, ICSR) & ICSR_TEND)) { + dev_dbg(pd->dev, "TEND bit was not set (%02X)\n", + iic_rd(pd, ICSR)); + break; + } + + dev_dbg(pd->dev, "pd->pos %d, pd->msg->len %d, pd->data_num %d\n", + pd->pos, pd->msg->len, pd->data_num); + if (pd->pos == pd->msg->len) { + /* If last data, need check ICSR_TDRE and ICSR_TEND. */ + if (!(iic_rd(pd, ICSR) & ICSR_TDRE)) + break; + + /* Last data / Last Packet */ + if (pd->data_num == 1) { + pd->state = OP_TX_STOP; + /* Through to OP_TX_STOP */ + } else if (pd->data_num > 1) { + /* Change Transfer to Receive */ + iic_wr(pd, ICIER, 0x0); /* Interrupts disable */ + pd->state = OP_TX_FINISH; + ret = 1; + break; + } + } else { + pd->state = OP_TX; + break; + } + /* Through to OP_TX_STOP */ + + case OP_TX_STOP: + iic_set_clr(pd, ICSR, 0, ICSR_TEND); + iic_set_clr(pd, ICSR, 0, ICSR_STOP); + iic_set_clr(pd, ICCR2, 0, ICCR2_BBSY | ICCR2_SCP); + udelay(10); + pd->state = OP_TX_STOP_CHK; + /* Through to OP_TX_STOP_CHK */ + + case OP_TX_STOP_CHK: + if (!(iic_rd(pd, ICSR) | ICSR_STOP)) { + dev_dbg(pd->dev, "ICSR/STOP is not set\n"); + break; + } + iic_set_clr(pd, ICCR1, 0, ICCR1_MST | ICCR1_TRS); + iic_set_clr(pd, ICSR, 0, ICSR_TDRE); + + /* Interrupts disable */ + iic_wr(pd, ICIER, 0x0); + pd->state = OP_TX_FINISH; + ret = 1; + + break; + + case OP_RX: + if (!(iic_rd(pd, ICSR) & ICSR_RDRF)) + break; + + data = iic_rd(pd, ICDRR); + pd->msg->buf[pd->pos] = data; + pd->pos++; + + dev_dbg(pd->dev, "Read data %02X\n", data); + + if (pd->pos == pd->msg->len) + pd->state = OP_RX_STOP; + + break; + + case OP_RX_STOP: /* enable DTE interrupt, issue stop */ + iic_set_clr(pd, ICIER, ICIER_ACKBT, 0); + iic_set_clr(pd, ICCR1, ICCR1_RCVD, 0); + pd->state = OP_RX_BBSY_CLR; + + break; + + case OP_RX_BBSY_CLR: + data = iic_rd(pd, ICDRR); + dev_dbg(pd->dev, "Last - 1 read data %02X\n", data); + pd->msg->buf[pd->pos] = data; + pd->pos++; + pd->state = OP_RX_STOP_CHK; + + break; + + case OP_RX_STOP_CHK: + if (!(iic_rd(pd, ICSR) & ICSR_RDRF)) { + dev_dbg(pd->dev, "ICSR/RDRF bit 0\n"); + break; + } + + iic_set_clr(pd, ICSR, 0, ICSR_STOP); + iic_set_clr(pd, ICCR2, 0, ICCR2_BBSY|ICCR2_SCP); + + pd->state = OP_RX_LAST_DATA; + /* Through to OP_RX_LAST_DATA */ + + case OP_RX_LAST_DATA: + if (iic_rd(pd, ICCR2) && ICCR2_BBSY) + iic_set_clr(pd, ICCR2, 0, ICCR2_BBSY|ICCR2_SCP); + + if (!(iic_rd(pd, ICSR) & ICSR_STOP)) { + dev_dbg(pd->dev, "ICSR/STOP is not set\n"); + break; + } + + iic_set_clr(pd, ICSR, 0, ICSR_STOP); + + data = iic_rd(pd, ICDRR); + pd->msg->buf[pd->pos] = data; + + dev_dbg(pd->dev, "Last read data %02X\n", data); + + pd->pos++; + + iic_set_clr(pd, ICCR1, 0, ICCR1_MST); + iic_set_clr(pd, ICSR, 0, ICSR_TDRE); + iic_set_clr(pd, ICCR1, 0, ICCR1_RCVD); + + iic_wr(pd, ICIER, 0x00); + pd->state = OP_RX_FINISH; + ret = 1; + + break; + } + + spin_unlock_irqrestore(&pd->lock, flags); + + return ret; +} + +static irqreturn_t sh7734_i2c_isr(int irq, void *dev_id) +{ + struct platform_device *dev = dev_id; + struct sh7734_i2c_data *pd = platform_get_drvdata(dev); + unsigned char sr; + int wakeup; + + sr = iic_rd(pd, ICSR); + pd->sr |= sr; /* remember state */ + + dev_dbg(pd->dev, "%s ICSR: 0x%02x sr: 0x%02x %s %d %d!\n", + __func__, sr, pd->sr, + (pd->msg->flags & I2C_M_RD) ? "read" : "write", + pd->pos, pd->msg->len); + + if (sr & (ICSR_AL)) { + /* error / abitration */ + iic_wr(pd, ICSR, sr & ~ICSR_AL); + wakeup = 0; + } else { + wakeup = i2c_op(pd); + } + + if (wakeup) { + pd->sr |= SW_DONE; + wake_up(&pd->wait); + } + + return IRQ_HANDLED; +} + +static int start_ch(struct sh7734_i2c_data *pd, struct i2c_msg *usr_msg) +{ + unsigned long flags; + + if (usr_msg->len == 0 && (usr_msg->flags & I2C_M_RD)) { + dev_err(pd->dev, "Unsupported zero length i2c read\n"); + return -EIO; + } + + if (pd->ackbr == 0) { + iic_wr(pd, ICCR1, pd->iccr1_clk | ICCR1_ICE); + iic_wr(pd, NF2CYC, pd->nf2cyc | pd->nf2cyc_clk); + } + + spin_lock_irqsave(&pd->lock, flags); + + pd->msg = usr_msg; + pd->pos = 0; + pd->sr = 0; + + /* Enable interrupts */ + if (usr_msg->flags & I2C_M_RD) { + if ((pd->ackbr) && (pd->state == OP_TX_FINISH)) + pd->state = OP_START_SCP; + + iic_wr(pd, ICIER, ICIER_RX); + } else { + if (pd->state == OP_RX) + pd->state = OP_TX; + + iic_wr(pd, ICIER, ICIER_TX); + } + spin_unlock_irqrestore(&pd->lock, flags); + + return 0; +} + +static int sh7734_i2c_xfer(struct i2c_adapter *adapter, + struct i2c_msg *msgs, + int num) +{ + struct sh7734_i2c_data *pd = i2c_get_adapdata(adapter); + struct i2c_msg *msg; + int err = 0, i, k; + u_int8_t val; + + activate_ch(pd, num); + + /* Process all messages */ + for (i = 0; i < num; i++) { + msg = &msgs[i]; + + err = start_ch(pd, msg); + if (err) + break; + + i2c_op(pd); + + k = wait_event_timeout(pd->wait, + pd->sr & (SW_DONE|SW_ERROR), + 5 * HZ); + if (!k) { + dev_err(pd->dev, "Transfer request timed out\n"); + err = -EIO; + dev_err(pd->dev, "Polling timed out\n"); + break; + } + + val = iic_rd(pd, ICSR); + /* handle missing acknowledge and arbitration lost */ + if (((val | pd->sr) & (ICSR_AL | SW_ERROR))) { + dev_err(pd->dev, "I2C I/O error\n"); + err = -EIO; + break; + } + } + + deactivate_ch(pd); + + if (!err) + err = num; + + return err; +} + +static u32 sh7734_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static struct i2c_algorithm sh7734_i2c_algorithm = { + .functionality = sh7734_i2c_func, + .master_xfer = sh7734_i2c_xfer, +}; + +static int sh7734_i2c_probe(struct platform_device *pdev) +{ + struct i2c_sh_mobile_platform_data *pdata = pdev->dev.platform_data; + struct sh7734_i2c_data *pd; + struct i2c_adapter *adap; + struct resource *res; + int size; + int ret = 0; + + pd = kzalloc(sizeof(struct sh7734_i2c_data), GFP_KERNEL); + if (pd == NULL) { + dev_err(&pdev->dev, "cannot allocate private data\n"); + return -ENOMEM; + } + + /* I2C of SH7734 base clock is pll clock */ + pd->clk = clk_get(NULL, "peripheral_clk"); + if (IS_ERR(pd->clk)) { + dev_err(&pdev->dev, "cannot get clock\n"); + ret = PTR_ERR(pd->clk); + goto err; + } + + pd->irq = platform_get_irq(pdev, 0); + if (pd->irq < 0) { + dev_err(&pdev->dev, "failed to get irq\n"); + goto err_clk; + } + + ret = request_irq(pd->irq, sh7734_i2c_isr, + 0, pdev->name, pdev); + if (ret) { + dev_err(&pdev->dev, "cannot request IRQ\n"); + goto err_clk; + } + + pd->dev = &pdev->dev; + platform_set_drvdata(pdev, pd); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "cannot find IO resource\n"); + ret = -ENOENT; + goto err_irq; + } + + size = resource_size(res); + + pd->reg = ioremap(res->start, size); + if (pd->reg == NULL) { + dev_err(&pdev->dev, "cannot map IO\n"); + ret = -ENXIO; + goto err_irq; + } + + /* Use platformd data bus speed or NORMAL_SPEED */ + pd->bus_speed = NORMAL_SPEED; + if (pdata && pdata->bus_speed) + pd->bus_speed = pdata->bus_speed; + + /* setup the private data */ + adap = &pd->adap; + i2c_set_adapdata(adap, pd); + + adap->owner = THIS_MODULE; + adap->algo = &sh7734_i2c_algorithm; + adap->dev.parent = &pdev->dev; + adap->retries = 5; + adap->nr = pdev->id; + + strlcpy(adap->name, pdev->name, sizeof(adap->name)); + + spin_lock_init(&pd->lock); + init_waitqueue_head(&pd->wait); + + ret = i2c_add_numbered_adapter(adap); + if (ret < 0) { + dev_err(&pdev->dev, "cannot add numbered adapter\n"); + goto err_all; + } + + dev_info(&pdev->dev, "I2C adapter %d with bus speed %lu Hz\n", + adap->nr, pd->bus_speed); + + return 0; + +err_all: + iounmap(pd->reg); +err_irq: + free_irq(pd->irq, pd); +err_clk: + clk_put(pd->clk); +err: + kfree(pd); + + return ret; +} + +static int sh7734_i2c_remove(struct platform_device *pdev) +{ + struct sh7734_i2c_data *pd = platform_get_drvdata(pdev); + + i2c_del_adapter(&pd->adap); + iounmap(pd->reg); + free_irq(pd->irq, pdev); + clk_put(pd->clk); + kfree(pd); + return 0; +} + +static struct platform_driver sh7734_i2c_driver = { + .driver = { + .name = "i2c-sh7734", + .owner = THIS_MODULE, + }, + .probe = sh7734_i2c_probe, + .remove = sh7734_i2c_remove, +}; + +static int __init sh7734_i2c_adap_init(void) +{ + return platform_driver_register(&sh7734_i2c_driver); +} + +static void __exit sh7734_i2c_adap_exit(void) +{ + platform_driver_unregister(&sh7734_i2c_driver); +} + +subsys_initcall(sh7734_i2c_adap_init); +module_exit(sh7734_i2c_adap_exit); + +MODULE_DESCRIPTION("SuperH SH7734 I2C Bus Controller driver"); +MODULE_AUTHOR("Nobuhiro Iwamatsu <nobuhiro.iwamatsu.yj@renesas.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:i2c-sh7734"); -- 1.7.9.1 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH] i2c: Add support SuperH SH7734 I2C bus controller 2012-04-11 23:57 [PATCH] i2c: Add support SuperH SH7734 I2C bus controller Nobuhiro Iwamatsu @ 2012-04-12 4:18 ` Kuninori Morimoto [not found] ` <87d37d37ml.wl%kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org> [not found] ` <1334188641-14641-1-git-send-email-nobuhiro.iwamatsu.yj-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org> 1 sibling, 1 reply; 10+ messages in thread From: Kuninori Morimoto @ 2012-04-12 4:18 UTC (permalink / raw) To: iwamatsu (RSO); +Cc: Magnus, Linux-SH, Linux-I2C Hi Iwamatsu-san > This is the SuperH SH7734 I2C Controller Driver. > A simple Master only support driver for the I2C included in processors SH7734 only. > > Signed-off-by: Nobuhiro Iwamatsu <nobuhiro.iwamatsu.yj@renesas.com> > --- I guess we can use this I2C driver in other SuperH Navi series. (not only SH7734 CPU) Best regards --- Kuninori Morimoto ^ permalink raw reply [flat|nested] 10+ messages in thread
[parent not found: <87d37d37ml.wl%kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org>]
* Re: [PATCH] i2c: Add support SuperH SH7734 I2C bus controller [not found] ` <87d37d37ml.wl%kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org> @ 2012-04-12 8:18 ` Nobuhiro Iwamatsu 2012-04-13 9:35 ` Kuninori Morimoto 0 siblings, 1 reply; 10+ messages in thread From: Nobuhiro Iwamatsu @ 2012-04-12 8:18 UTC (permalink / raw) To: Kuninori Morimoto; +Cc: Magnus, Linux-SH, Linux-I2C 2012/4/12 Kuninori Morimoto <kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org>: > > Hi Iwamatsu-san > >> This is the SuperH SH7734 I2C Controller Driver. >> A simple Master only support driver for the I2C included in processors SH7734 only. >> >> Signed-off-by: Nobuhiro Iwamatsu <nobuhiro.iwamatsu.yj-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org> >> --- > > I guess we can use this I2C driver in other SuperH Navi series. > (not only SH7734 CPU) > Well, the SH-NAVI series hss I2C. However, I assume it support only for SH7734 because bus width and register constitution are different. If you point it out about R-Car, I will examine R-Car. (R-Car is not SH-NAVI). Best regards, Nobuhiro -- Nobuhiro Iwamatsu ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] i2c: Add support SuperH SH7734 I2C bus controller 2012-04-12 8:18 ` Nobuhiro Iwamatsu @ 2012-04-13 9:35 ` Kuninori Morimoto [not found] ` <87y5q02cv2.wl%kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org> 0 siblings, 1 reply; 10+ messages in thread From: Kuninori Morimoto @ 2012-04-13 9:35 UTC (permalink / raw) To: Nobuhiro Iwamatsu; +Cc: Magnus, Linux-SH, Linux-I2C Hi Iwamatsu-san > Well, the SH-NAVI series hss I2C. > However, I assume it support only for SH7734 because bus width and > register constitution are different. > If you point it out about R-Car, I will examine R-Car. (R-Car is not SH-NAVI). I guess R-Car is not same as this I2C. And sorry, SH7734 seems not same as SH-NAVI. Now I'm checking the series. Please wait. Best regards --- Kuninori Morimoto ^ permalink raw reply [flat|nested] 10+ messages in thread
[parent not found: <87y5q02cv2.wl%kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org>]
* Re: [PATCH] i2c: Add support SuperH SH7734 I2C bus controller [not found] ` <87y5q02cv2.wl%kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org> @ 2012-04-13 11:05 ` Nobuhiro Iwamatsu 2012-04-16 0:09 ` Kuninori Morimoto 0 siblings, 1 reply; 10+ messages in thread From: Nobuhiro Iwamatsu @ 2012-04-13 11:05 UTC (permalink / raw) To: Kuninori Morimoto; +Cc: Magnus, Linux-SH, Linux-I2C Hi, 2012/4/13 Kuninori Morimoto <kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org>: > > Hi Iwamatsu-san > >> Well, the SH-NAVI series hss I2C. >> However, I assume it support only for SH7734 because bus width and >> register constitution are different. >> If you point it out about R-Car, I will examine R-Car. (R-Car is not SH-NAVI). > > I guess R-Car is not same as this I2C. > And sorry, SH7734 seems not same as SH-NAVI. > Now I'm checking the series. > Please wait. > I already checked SH7775 and SH7776. These are SH-NAVI series. But these CPU's are constitution unlike I2C of SH7734. Best regards, Nobuhiro -- Nobuhiro Iwamatsu ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] i2c: Add support SuperH SH7734 I2C bus controller 2012-04-13 11:05 ` Nobuhiro Iwamatsu @ 2012-04-16 0:09 ` Kuninori Morimoto 2012-04-16 1:04 ` Nobuhiro Iwamatsu 0 siblings, 1 reply; 10+ messages in thread From: Kuninori Morimoto @ 2012-04-16 0:09 UTC (permalink / raw) To: Nobuhiro Iwamatsu; +Cc: Magnus, Linux-SH, Linux-I2C Hi Iwamatsu-san > > I guess R-Car is not same as this I2C. > > And sorry, SH7734 seems not same as SH-NAVI. > > Now I'm checking the series. > > Please wait. > > > > I already checked SH7775 and SH7776. These are SH-NAVI series. > But these CPU's are constitution unlike I2C of SH7734. I got the information about it this morning. SH7734 I2C came from SH7266, and it came from SH-NaviJ3. Best regards --- Kuninori Morimoto ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] i2c: Add support SuperH SH7734 I2C bus controller 2012-04-16 0:09 ` Kuninori Morimoto @ 2012-04-16 1:04 ` Nobuhiro Iwamatsu [not found] ` <OF7FE4858F.5425E857-ON80257A3F.002C2DA0-80257A3F.002E3111@eu.necel.com> 0 siblings, 1 reply; 10+ messages in thread From: Nobuhiro Iwamatsu @ 2012-04-16 1:04 UTC (permalink / raw) To: Kuninori Morimoto; +Cc: Magnus, Linux-SH, Linux-I2C Kuninori Morimoto さんは書きました: > Hi Iwamatsu-san > >>> I guess R-Car is not same as this I2C. >>> And sorry, SH7734 seems not same as SH-NAVI. >>> Now I'm checking the series. >>> Please wait. >>> >> I already checked SH7775 and SH7776. These are SH-NAVI series. >> But these CPU's are constitution unlike I2C of SH7734. > > I got the information about it this morning. > SH7734 I2C came from SH7266, and it came from SH-NaviJ3. > I comfirmed these, thanks. OK, I will rename file and update description. Best regards, Nobuhiro ^ permalink raw reply [flat|nested] 10+ messages in thread
[parent not found: <OF7FE4858F.5425E857-ON80257A3F.002C2DA0-80257A3F.002E3111@eu.necel.com>]
[parent not found: <OF7FE4858F.5425E857-ON80257A3F.002C2DA0-80257A3F.002E3111-mWMTcI9IYFFWk0Htik3J/w@public.gmane.org>]
* Re: [PATCH] i2c: Add support SuperH SH7734 I2C bus controller [not found] ` <OF7FE4858F.5425E857-ON80257A3F.002C2DA0-80257A3F.002E3111-mWMTcI9IYFFWk0Htik3J/w@public.gmane.org> @ 2012-07-20 0:57 ` Nobuhiro Iwamatsu 0 siblings, 0 replies; 10+ messages in thread From: Nobuhiro Iwamatsu @ 2012-07-20 0:57 UTC (permalink / raw) To: phil.edworthy-zM6kxYcvzFBBDgjK7y7TUQ Cc: Kuninori Morimoto, Linux-I2C, Linux-SH, linux-sh-owner-u79uwXL29TY76Z2rM5mHXA, Magnus Hi, Phil. phil.edworthy-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org さんは書きました: > Hi Iwamatsu-san, > >> Date: 16/04/2012 01:58 >> Subject: Re: [PATCH] i2c: Add support SuperH SH7734 I2C bus controller >> Sent by: linux-sh-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org >> >> Kuninori Morimoto さんは書きました: >>> Hi Iwamatsu-san >>> >>>>> I guess R-Car is not same as this I2C. >>>>> And sorry, SH7734 seems not same as SH-NAVI. >>>>> Now I'm checking the series. >>>>> Please wait. >>>>> >>>> I already checked SH7775 and SH7776. These are SH-NAVI series. >>>> But these CPU's are constitution unlike I2C of SH7734. >>> I got the information about it this morning. >>> SH7734 I2C came from SH7266, and it came from SH-NaviJ3. >>> >> I comfirmed these, thanks. >> OK, I will rename file and update description. > > I noticed your sh7734 i2c patch and realised that the registers are the same as sh7269 (sh2a). I tried this driver on sh7269, but I had to make some > changes to use it. > > The following code is not suitable for sh7269: > + /* Mask clear interrupts */ > + if (pd->adap.nr == 0) > + __raw_writel(I2C_INT_ALL, I2C0_INTMASK_CLR); > + else if (pd->adap.nr == 1) > + __raw_writel(I2C_INT_ALL, I2C1_INTMASK_CLR); > > + /* Mask interrupts */ > + if (pd->adap.nr == 0) > + __raw_writel(I2C_INT_ALL, I2C0_INTMASK); > + else if (pd->adap.nr == 1) > + __raw_writel(I2C_INT_ALL, I2C1_INTMASK); > > Do you plan to send this driver again? If yes, maybe add #ifdef CPU_SUBTYPE_SH7734 around this code, or add platform data to enable it. I will resend patch with some fixes and your point. Best regards, Nobuhiro ^ permalink raw reply [flat|nested] 10+ messages in thread
[parent not found: <1334188641-14641-1-git-send-email-nobuhiro.iwamatsu.yj-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org>]
* Re: [PATCH] i2c: Add support SuperH SH7734 I2C bus controller [not found] ` <1334188641-14641-1-git-send-email-nobuhiro.iwamatsu.yj-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org> @ 2012-04-16 11:07 ` Shubhrajyoti Datta 2012-04-19 2:10 ` Nobuhiro Iwamatsu 0 siblings, 1 reply; 10+ messages in thread From: Shubhrajyoti Datta @ 2012-04-16 11:07 UTC (permalink / raw) To: Nobuhiro Iwamatsu Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA, linux-sh-u79uwXL29TY76Z2rM5mHXA Hi , Few comments, On Thu, Apr 12, 2012 at 5:27 AM, Nobuhiro Iwamatsu <nobuhiro.iwamatsu.yj-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org> wrote: > This is the SuperH SH7734 I2C Controller Driver. > A simple Master only support driver for the I2C included in processors SH7734 only. > > Signed-off-by: Nobuhiro Iwamatsu <nobuhiro.iwamatsu.yj-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org> > --- > drivers/i2c/busses/Kconfig | 9 + > drivers/i2c/busses/Makefile | 1 + > drivers/i2c/busses/i2c-sh7734.c | 721 +++++++++++++++++++++++++++++++++++++++ > 3 files changed, 731 insertions(+), 0 deletions(-) > create mode 100644 drivers/i2c/busses/i2c-sh7734.c > > diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig > index d2c5095..68d3181 100644 > --- a/drivers/i2c/busses/Kconfig > +++ b/drivers/i2c/busses/Kconfig > @@ -634,6 +634,15 @@ config I2C_SH_MOBILE > This driver can also be built as a module. If so, the module > will be called i2c-sh_mobile. > > +config I2C_SH7734 > + tristate "Renesas SH7734 I2C Controller" > + depends on CPU_SUBTYPE_SH7734 > + help > + This driver supports the I2C interfaces on the Renesas SH7734. > + > + This driver can also be built as a module. If so, the module > + will be called i2c-sh7734. > + > config I2C_SIMTEC > tristate "Simtec Generic I2C interface" > select I2C_ALGOBIT > diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile > index 569567b..b7057f5 100644 > --- a/drivers/i2c/busses/Makefile > +++ b/drivers/i2c/busses/Makefile > @@ -63,6 +63,7 @@ obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o > 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_SH7734) += i2c-sh7734.o > obj-$(CONFIG_I2C_SIMTEC) += i2c-simtec.o > obj-$(CONFIG_I2C_SIRF) += i2c-sirf.o > obj-$(CONFIG_I2C_STU300) += i2c-stu300.o > diff --git a/drivers/i2c/busses/i2c-sh7734.c b/drivers/i2c/busses/i2c-sh7734.c > new file mode 100644 > index 0000000..89e37d0 > --- /dev/null > +++ b/drivers/i2c/busses/i2c-sh7734.c > @@ -0,0 +1,721 @@ > +/* > + * SuperH SH7734 I2C Controller > + * > + * Copyright (C) 2008 Magnus Damm > + * Copyright (C) 2012 Nobuhiro Iwamatsu <nobuhiro.iwamatsu.yj@renesas.com> > + * Copyright (C) 2012 Renesas Solutions Corp. > + * > + * This file is subject to the terms and conditions of the GNU General Public > + * License. See the file "COPYING" in the main directory of this archive > + * for more details. > + * > + * Based on drivers/i2c/busses/i2c-sh_mobile.c. > + */ > + > +#include <linux/kernel.h> > +#include <linux/module.h> > +#include <linux/init.h> > +#include <linux/delay.h> > +#include <linux/platform_device.h> > +#include <linux/interrupt.h> > +#include <linux/i2c.h> > +#include <linux/err.h> > +#include <linux/clk.h> > +#include <linux/io.h> > +#include <linux/slab.h> > +#include <linux/i2c/i2c-sh_mobile.h> > + > +enum sh7734_i2c_op { > + OP_START_TRS = 0, > + OP_START_SCP, > + OP_TX_FIRST, > + OP_TX_FIRST_END, > + OP_TX, > + OP_TX_END, > + OP_TX_STOP, > + OP_TX_STOP_CHK, > + OP_TX_FINISH, > + OP_RX, > + OP_RX_BBSY_CLR, > + OP_RX_LAST_DATA, > + OP_RX_FINISH, > + OP_RX_STOP, > + OP_RX_STOP_CHK, > +}; > + > +struct sh7734_i2c_data { > + struct device *dev; > + void __iomem *reg; > + struct i2c_adapter adap; > + unsigned long bus_speed; > + struct clk *clk; > + u_int8_t nf2cyc; > + u_int8_t nf2cyc_clk; > + u_int8_t iccr1_clk; > + spinlock_t lock; > + wait_queue_head_t wait; > + struct i2c_msg *msg; > + int pos; > + int sr; > + int irq; > + int ackbr; > + int state; > + int data_num; > +}; > + > +#define NORMAL_SPEED 100000 /* FAST_SPEED 400000 */ > + > +/* Register offsets */ > +#define ICCR1 0x0 > +#define ICCR2 0x1 > +#define ICMR 0x2 > +#define ICIER 0x3 > +#define ICSR 0x4 > +#define SAR 0x5 > +#define ICDRT 0x6 > +#define ICDRR 0x7 > +#define NF2CYC 0x8 > + > +#define ICCR1_ICE 0x80 > +#define ICCR1_RCVD 0x40 > +#define ICCR1_MST 0x20 > +#define ICCR1_TRS 0x10 > + > +#define SW_DONE 0x100 > +#define SW_ERROR 0x200 > + > +#define ICCR2_BBSY 0x80 > +#define ICCR2_SCP 0x40 > +#define ICCR2_SDAO 0x20 > +#define ICCR2_SDAOP 0x10 > +#define ICCR2_SCLO 0x08 > +#define ICCR2_I2CRST 0x02 > + > +#define ICSR_TDRE 0x80 > +#define ICSR_TEND 0x40 > +#define ICSR_RDRF 0x20 > +#define ICSR_NACKF 0x10 > +#define ICSR_STOP 0x08 > +#define ICSR_AL 0x04 > +#define ICSR_AAS 0x02 > +#define ICSR_ADZ 0x01 > + > +#define ICIER_TIE 0x80 /* Transfer interrputs */ > +#define ICIER_TEIE 0x40 /* Transfer end interrupts */ > +#define ICIER_RIE 0x20 /* Receive interrupts */ > +#define ICIER_NAKIE 0x10 /* NACK interrupts */ > +#define ICIER_STIE 0x08 /* Stop interrupts */ > +#define ICIER_ACKE 0x04 /* ACKbit check*/ > +#define ICIER_ACKBR 0x02 /* set ACKbit / Receive */ > +#define ICIER_ACKBT 0x01 /* set ACKbit / Transfer */ > +#define ICIER_RX (ICIER_RIE|ICIER_TEIE|ICIER_TIE|ICIER_NAKIE|ICIER_STIE) > +#define ICIER_TX (ICIER_TIE|ICIER_TEIE|ICIER_NAKIE|ICIER_STIE) > + > +#define I2C0_INTMASK 0xFF804288 > +#define I2C1_INTMASK 0xFF80428C > +#define I2C0_INTMASK_CLR 0xFF8042A8 > +#define I2C1_INTMASK_CLR 0xFF8042AC > +#define I2C0_INT 0xFF8040AC > +#define I2C1_INT 0xFF8040D8 > + > +#define I2C_INT_TXI (1 << 4) > +#define I2C_INT_TEI (1 << 3) > +#define I2C_INT_RXI (1 << 2) > +#define I2C_INT_NAKI (1 << 1) > +#define I2C_INT_STPI (1 << 0) > +#define I2C_INT_ALL \ > + (I2C_INT_TXI|I2C_INT_TEI|I2C_INT_RXI|I2C_INT_NAKI|I2C_INT_STPI) > + > +static void > +iic_wr(struct sh7734_i2c_data *pd, int offs, unsigned char data) > +{ > + iowrite8(data, pd->reg + offs); > +} > + > +static unsigned char iic_rd(struct sh7734_i2c_data *pd, int offs) > +{ > + return ioread8(pd->reg + offs); > +} > + > +static void iic_set_clr(struct sh7734_i2c_data *pd, int offs, > + unsigned char set, unsigned char clr) > +{ > + iic_wr(pd, offs, (iic_rd(pd, offs) | set) & ~clr); > +} > + > +static int clk_denom_tbl[] = { > + 44, 52, 64, 72, 84, 92, 100, 108, 176, > + 208, 256, 288, 336, 368, 400, 432, 352, 416, > + 512, 576, 672, 736, 800, 864, 704, 832, 1024, > + 1152, 1344, 1472, 1600, 1728 }; > + > +static void activate_ch(struct sh7734_i2c_data *pd, int num) > +{ > + unsigned long pclk; > + unsigned long mode, mode_tmp; > + unsigned int cks_bit; > + long denom, speed; > + unsigned int i; > + > + pclk = clk_get_rate(pd->clk) / 1000; > + mode = speed = pd->bus_speed / 1000; > + > + for (i = 0; i < ARRAY_SIZE(clk_denom_tbl); i++) { > + denom = (unsigned int)(pclk / clk_denom_tbl[i]); > + if (denom > (speed * 2)) > + continue; > + else if ((speed - denom) > 100) > + continue; > + mode_tmp = (unsigned int)(denom % speed); > + > + if (mode_tmp < speed) { > + if (mode_tmp < mode) { > + mode = mode_tmp; > + cks_bit = i; > + } > + } > + } > + > + pd->nf2cyc = 1; > + pd->nf2cyc_clk = (u_int8_t)(cks_bit & 0x10); > + pd->iccr1_clk = (u_int8_t)(cks_bit & 0x0F); > + pd->state = OP_START_TRS; > + pd->ackbr = 0; > + pd->data_num = num; > + > + /* Mask clear interrupts */ > + if (pd->adap.nr == 0) > + __raw_writel(I2C_INT_ALL, I2C0_INTMASK_CLR); > + else Nit : Can the else be dropped? > + __raw_writel(I2C_INT_ALL, I2C1_INTMASK_CLR); > +} > + > +static void deactivate_ch(struct sh7734_i2c_data *pd) > +{ > + /* Reset controller */ > + iic_set_clr(pd, ICCR2, ICCR2_I2CRST, 0); > + udelay(10); > + iic_set_clr(pd, ICCR2, 0, ICCR2_I2CRST); > + > + /* Clear/disable interrupts */ Why is the interrupts disabled? > + iic_wr(pd, ICSR, 0); > + iic_wr(pd, ICIER, 0); > + > + /* Mask interrupts */ > + if (pd->adap.nr == 0) > + __raw_writel(I2C_INT_ALL, I2C0_INTMASK); > + else > + __raw_writel(I2C_INT_ALL, I2C1_INTMASK); > + > + /* Disable channel */ > + iic_set_clr(pd, ICCR1, 0, ICCR1_ICE); > + > + pd->ackbr = 0; > + /* Reset controller */ > + iic_set_clr(pd, ICCR2, ICCR2_I2CRST, 0); > + udelay(10); > + iic_set_clr(pd, ICCR2, 0, ICCR2_I2CRST); > +} > + > +static int i2c_op(struct sh7734_i2c_data *pd) > +{ > + int ret = 0; > + unsigned char data; > + unsigned long flags; > + > + spin_lock_irqsave(&pd->lock, flags); > + > + switch (pd->state) { > + case OP_START_TRS: > + if (!(iic_rd(pd, ICCR2) & ICCR2_BBSY)) { > + /* set to master transfer mode */ > + iic_set_clr(pd, ICCR1, ICCR1_MST | ICCR1_TRS, 0); > + pd->state = OP_TX_FIRST; > + } else { > + dev_dbg(pd->dev, "Device busy\n"); > + pd->state = OP_TX_STOP; > + break; > + } > + > + iic_set_clr(pd, ICCR2, ICCR2_BBSY, ICCR2_SCP); > + break; > + > + case OP_START_SCP: > + /* Send TIE, Set TDRE */ > + iic_set_clr(pd, ICCR2, ICCR2_BBSY, ICCR2_SCP); > + pd->state = OP_TX_FIRST; > + break; > + > + case OP_TX_FIRST: > + if (iic_rd(pd, ICSR) & ICSR_TDRE) { > + /* Create command */ > + data = (pd->msg->addr & 0x7f) << 1; > + data |= (pd->msg->flags & I2C_M_RD) ? 1 : 0; > + > + /* write data with clear TDRE bit */ > + iic_wr(pd, ICDRT, data); > + dev_dbg(pd->dev, "Write data (ICDRT): %02X\n", data); > + pd->state = OP_TX_FIRST_END; > + } else > + dev_dbg(pd->dev, "ICSR/TDRE is not set\n"); > + > + break; > + > + case OP_TX_FIRST_END: > + if (!(iic_rd(pd, ICSR) & ICSR_TEND)) { > + dev_dbg(pd->dev, "ICSR/TEND is not set\n"); > + break; > + } > + > + /* Don't clear TEND */ > + if (pd->ackbr) { > + if (pd->msg->flags & I2C_M_RD) { > + iic_set_clr(pd, ICSR, 0, ICSR_TEND); > + iic_set_clr(pd, ICCR1, ICCR1_MST, ICCR1_TRS); > + iic_set_clr(pd, ICSR, 0, ICSR_TDRE); > + > + if (pd->msg->len == 1) { > + iic_set_clr(pd, ICIER, ICIER_ACKBT, 0); > + iic_set_clr(pd, ICCR1, ICCR1_RCVD, 0); > + > + data = iic_rd(pd, ICDRR); > + /* If pd->msg->len == 1, > + this read data is dummy. */ > + dev_dbg(pd->dev, "Last - 1 read data %02X\n", > + data); > + > + pd->state = OP_RX_STOP_CHK; > + } else { > + iic_set_clr(pd, ICIER, 0, ICIER_ACKBT); > + data = iic_rd(pd, ICDRR); > + dev_dbg(pd->dev, > + "Dummy read data %02X\n", data); > + > + pd->state = OP_RX; > + } > + } else > + pd->state = OP_TX_STOP; > + > + break; > + } > + > + if (iic_rd(pd, ICIER) & ICIER_ACKBR) { > + dev_err(pd->dev, "ACKBR Error...\n"); > + pd->sr |= SW_ERROR; /* Save Error state */ > + > + pd->state = OP_TX_STOP; > + break; > + } else { > + pd->ackbr = 1; > + pd->state = OP_TX; > + } > + > + /* Through to OP_TX */ > + > + case OP_TX: /* send data */ > + if (iic_rd(pd, ICSR) & ICSR_TDRE) { > + data = pd->msg->buf[pd->pos]; > + /* write data with clear TDRE bit */ > + iic_wr(pd, ICDRT, data); > + dev_dbg(pd->dev, "Write data (ICDRT): %02X\n", data); > + pd->pos++; > + > + pd->state = OP_TX_END; > + } else > + dev_dbg(pd->dev, "Retry Data translate\n"); > + > + break; > + > + case OP_TX_END: > + if (!(iic_rd(pd, ICSR) & ICSR_TEND)) { > + dev_dbg(pd->dev, "TEND bit was not set (%02X)\n", > + iic_rd(pd, ICSR)); > + break; > + } > + > + dev_dbg(pd->dev, "pd->pos %d, pd->msg->len %d, pd->data_num %d\n", > + pd->pos, pd->msg->len, pd->data_num); > + if (pd->pos == pd->msg->len) { > + /* If last data, need check ICSR_TDRE and ICSR_TEND. */ > + if (!(iic_rd(pd, ICSR) & ICSR_TDRE)) > + break; > + > + /* Last data / Last Packet */ > + if (pd->data_num == 1) { > + pd->state = OP_TX_STOP; > + /* Through to OP_TX_STOP */ > + } else if (pd->data_num > 1) { > + /* Change Transfer to Receive */ > + iic_wr(pd, ICIER, 0x0); /* Interrupts disable */ > + pd->state = OP_TX_FINISH; > + ret = 1; > + break; > + } > + } else { > + pd->state = OP_TX; > + break; > + } > + /* Through to OP_TX_STOP */ > + > + case OP_TX_STOP: > + iic_set_clr(pd, ICSR, 0, ICSR_TEND); > + iic_set_clr(pd, ICSR, 0, ICSR_STOP); > + iic_set_clr(pd, ICCR2, 0, ICCR2_BBSY | ICCR2_SCP); > + udelay(10); > + pd->state = OP_TX_STOP_CHK; > + /* Through to OP_TX_STOP_CHK */ > + > + case OP_TX_STOP_CHK: > + if (!(iic_rd(pd, ICSR) | ICSR_STOP)) { > + dev_dbg(pd->dev, "ICSR/STOP is not set\n"); > + break; > + } > + iic_set_clr(pd, ICCR1, 0, ICCR1_MST | ICCR1_TRS); > + iic_set_clr(pd, ICSR, 0, ICSR_TDRE); > + > + /* Interrupts disable */ > + iic_wr(pd, ICIER, 0x0); > + pd->state = OP_TX_FINISH; > + ret = 1; > + > + break; > + > + case OP_RX: > + if (!(iic_rd(pd, ICSR) & ICSR_RDRF)) > + break; > + > + data = iic_rd(pd, ICDRR); > + pd->msg->buf[pd->pos] = data; > + pd->pos++; > + > + dev_dbg(pd->dev, "Read data %02X\n", data); > + > + if (pd->pos == pd->msg->len) > + pd->state = OP_RX_STOP; > + > + break; > + > + case OP_RX_STOP: /* enable DTE interrupt, issue stop */ > + iic_set_clr(pd, ICIER, ICIER_ACKBT, 0); > + iic_set_clr(pd, ICCR1, ICCR1_RCVD, 0); > + pd->state = OP_RX_BBSY_CLR; > + > + break; > + > + case OP_RX_BBSY_CLR: > + data = iic_rd(pd, ICDRR); > + dev_dbg(pd->dev, "Last - 1 read data %02X\n", data); > + pd->msg->buf[pd->pos] = data; > + pd->pos++; > + pd->state = OP_RX_STOP_CHK; > + > + break; > + > + case OP_RX_STOP_CHK: > + if (!(iic_rd(pd, ICSR) & ICSR_RDRF)) { > + dev_dbg(pd->dev, "ICSR/RDRF bit 0\n"); > + break; > + } > + > + iic_set_clr(pd, ICSR, 0, ICSR_STOP); > + iic_set_clr(pd, ICCR2, 0, ICCR2_BBSY|ICCR2_SCP); > + > + pd->state = OP_RX_LAST_DATA; > + /* Through to OP_RX_LAST_DATA */ > + > + case OP_RX_LAST_DATA: > + if (iic_rd(pd, ICCR2) && ICCR2_BBSY) > + iic_set_clr(pd, ICCR2, 0, ICCR2_BBSY|ICCR2_SCP); > + > + if (!(iic_rd(pd, ICSR) & ICSR_STOP)) { > + dev_dbg(pd->dev, "ICSR/STOP is not set\n"); > + break; > + } > + > + iic_set_clr(pd, ICSR, 0, ICSR_STOP); > + > + data = iic_rd(pd, ICDRR); > + pd->msg->buf[pd->pos] = data; > + > + dev_dbg(pd->dev, "Last read data %02X\n", data); > + > + pd->pos++; > + > + iic_set_clr(pd, ICCR1, 0, ICCR1_MST); > + iic_set_clr(pd, ICSR, 0, ICSR_TDRE); > + iic_set_clr(pd, ICCR1, 0, ICCR1_RCVD); > + > + iic_wr(pd, ICIER, 0x00); > + pd->state = OP_RX_FINISH; > + ret = 1; > + > + break; > + } > + > + spin_unlock_irqrestore(&pd->lock, flags); > + > + return ret; > +} > + > +static irqreturn_t sh7734_i2c_isr(int irq, void *dev_id) > +{ > + struct platform_device *dev = dev_id; > + struct sh7734_i2c_data *pd = platform_get_drvdata(dev); > + unsigned char sr; > + int wakeup; > + > + sr = iic_rd(pd, ICSR); > + pd->sr |= sr; /* remember state */ > + > + dev_dbg(pd->dev, "%s ICSR: 0x%02x sr: 0x%02x %s %d %d!\n", > + __func__, sr, pd->sr, > + (pd->msg->flags & I2C_M_RD) ? "read" : "write", > + pd->pos, pd->msg->len); > + > + if (sr & (ICSR_AL)) { > + /* error / abitration */ > + iic_wr(pd, ICSR, sr & ~ICSR_AL); > + wakeup = 0; > + } else { > + wakeup = i2c_op(pd); > + } > + > + if (wakeup) { > + pd->sr |= SW_DONE; > + wake_up(&pd->wait); > + } > + > + return IRQ_HANDLED; > +} > + > +static int start_ch(struct sh7734_i2c_data *pd, struct i2c_msg *usr_msg) > +{ > + unsigned long flags; > + > + if (usr_msg->len == 0 && (usr_msg->flags & I2C_M_RD)) { > + dev_err(pd->dev, "Unsupported zero length i2c read\n"); > + return -EIO; > + } > + > + if (pd->ackbr == 0) { > + iic_wr(pd, ICCR1, pd->iccr1_clk | ICCR1_ICE); > + iic_wr(pd, NF2CYC, pd->nf2cyc | pd->nf2cyc_clk); > + } > + > + spin_lock_irqsave(&pd->lock, flags); > + > + pd->msg = usr_msg; > + pd->pos = 0; > + pd->sr = 0; > + > + /* Enable interrupts */ > + if (usr_msg->flags & I2C_M_RD) { > + if ((pd->ackbr) && (pd->state == OP_TX_FINISH)) > + pd->state = OP_START_SCP; > + > + iic_wr(pd, ICIER, ICIER_RX); > + } else { > + if (pd->state == OP_RX) > + pd->state = OP_TX; > + > + iic_wr(pd, ICIER, ICIER_TX); > + } > + spin_unlock_irqrestore(&pd->lock, flags); > + > + return 0; > +} > + > +static int sh7734_i2c_xfer(struct i2c_adapter *adapter, > + struct i2c_msg *msgs, > + int num) > +{ > + struct sh7734_i2c_data *pd = i2c_get_adapdata(adapter); > + struct i2c_msg *msg; > + int err = 0, i, k; > + u_int8_t val; > + > + activate_ch(pd, num); > + > + /* Process all messages */ > + for (i = 0; i < num; i++) { > + msg = &msgs[i]; > + > + err = start_ch(pd, msg); > + if (err) > + break; > + > + i2c_op(pd); > + > + k = wait_event_timeout(pd->wait, > + pd->sr & (SW_DONE|SW_ERROR), > + 5 * HZ); > + if (!k) { > + dev_err(pd->dev, "Transfer request timed out\n"); > + err = -EIO; > + dev_err(pd->dev, "Polling timed out\n"); > + break; > + } > + > + val = iic_rd(pd, ICSR); > + /* handle missing acknowledge and arbitration lost */ > + if (((val | pd->sr) & (ICSR_AL | SW_ERROR))) { What about checking for other errors? > + dev_err(pd->dev, "I2C I/O error\n"); > + err = -EIO; > + break; > + } > + } > + > + deactivate_ch(pd); > + > + if (!err) > + err = num; > + > + return err; > +} > + > +static u32 sh7734_i2c_func(struct i2c_adapter *adapter) > +{ > + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; > +} > + > +static struct i2c_algorithm sh7734_i2c_algorithm = { > + .functionality = sh7734_i2c_func, > + .master_xfer = sh7734_i2c_xfer, > +}; > + > +static int sh7734_i2c_probe(struct platform_device *pdev) > +{ > + struct i2c_sh_mobile_platform_data *pdata = pdev->dev.platform_data; > + struct sh7734_i2c_data *pd; > + struct i2c_adapter *adap; > + struct resource *res; > + int size; > + int ret = 0; > + > + pd = kzalloc(sizeof(struct sh7734_i2c_data), GFP_KERNEL); > + if (pd == NULL) { > + dev_err(&pdev->dev, "cannot allocate private data\n"); > + return -ENOMEM; > + } > + > + /* I2C of SH7734 base clock is pll clock */ > + pd->clk = clk_get(NULL, "peripheral_clk"); > + if (IS_ERR(pd->clk)) { > + dev_err(&pdev->dev, "cannot get clock\n"); > + ret = PTR_ERR(pd->clk); > + goto err; > + } > + > + pd->irq = platform_get_irq(pdev, 0); > + if (pd->irq < 0) { > + dev_err(&pdev->dev, "failed to get irq\n"); > + goto err_clk; > + } > + > + ret = request_irq(pd->irq, sh7734_i2c_isr, > + 0, pdev->name, pdev); > + if (ret) { > + dev_err(&pdev->dev, "cannot request IRQ\n"); > + goto err_clk; > + } > + > + pd->dev = &pdev->dev; > + platform_set_drvdata(pdev, pd); > + > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + if (res == NULL) { > + dev_err(&pdev->dev, "cannot find IO resource\n"); > + ret = -ENOENT; > + goto err_irq; > + } > + > + size = resource_size(res); > + > + pd->reg = ioremap(res->start, size); > + if (pd->reg == NULL) { > + dev_err(&pdev->dev, "cannot map IO\n"); > + ret = -ENXIO; > + goto err_irq; > + } > + > + /* Use platformd data bus speed or NORMAL_SPEED */ > + pd->bus_speed = NORMAL_SPEED; > + if (pdata && pdata->bus_speed) > + pd->bus_speed = pdata->bus_speed; > + > + /* setup the private data */ > + adap = &pd->adap; > + i2c_set_adapdata(adap, pd); > + > + adap->owner = THIS_MODULE; > + adap->algo = &sh7734_i2c_algorithm; > + adap->dev.parent = &pdev->dev; > + adap->retries = 5; > + adap->nr = pdev->id; > + > + strlcpy(adap->name, pdev->name, sizeof(adap->name)); > + > + spin_lock_init(&pd->lock); > + init_waitqueue_head(&pd->wait); > + > + ret = i2c_add_numbered_adapter(adap); > + if (ret < 0) { > + dev_err(&pdev->dev, "cannot add numbered adapter\n"); > + goto err_all; > + } > + > + dev_info(&pdev->dev, "I2C adapter %d with bus speed %lu Hz\n", > + adap->nr, pd->bus_speed); > + > + return 0; > + > +err_all: > + iounmap(pd->reg); > +err_irq: > + free_irq(pd->irq, pd); > +err_clk: > + clk_put(pd->clk); > +err: > + kfree(pd); > + > + return ret; > +} > + > +static int sh7734_i2c_remove(struct platform_device *pdev) Can these be in the __devexit ?? > +{ > + struct sh7734_i2c_data *pd = platform_get_drvdata(pdev); > + Should we release resource here?? May be devm* could be considered? > + i2c_del_adapter(&pd->adap); > + iounmap(pd->reg); > + free_irq(pd->irq, pdev); > + clk_put(pd->clk); > + kfree(pd); > + return 0; > +} > + > +static struct platform_driver sh7734_i2c_driver = { > + .driver = { > + .name = "i2c-sh7734", > + .owner = THIS_MODULE, > + }, > + .probe = sh7734_i2c_probe, > + .remove = sh7734_i2c_remove, > +}; > + > +static int __init sh7734_i2c_adap_init(void) > +{ > + return platform_driver_register(&sh7734_i2c_driver); > +} > + > +static void __exit sh7734_i2c_adap_exit(void) > +{ > + platform_driver_unregister(&sh7734_i2c_driver); > +} > + > +subsys_initcall(sh7734_i2c_adap_init); > +module_exit(sh7734_i2c_adap_exit); > + > +MODULE_DESCRIPTION("SuperH SH7734 I2C Bus Controller driver"); > +MODULE_AUTHOR("Nobuhiro Iwamatsu <nobuhiro.iwamatsu.yj-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org>"); > +MODULE_LICENSE("GPL v2"); > +MODULE_ALIAS("platform:i2c-sh7734"); > -- > 1.7.9.1 > > -- > To unsubscribe from this list: send the line "unsubscribe linux-i2c" in > the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org > More majordomo info at http://vger.kernel.org/majordomo-info.html ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] i2c: Add support SuperH SH7734 I2C bus controller 2012-04-16 11:07 ` Shubhrajyoti Datta @ 2012-04-19 2:10 ` Nobuhiro Iwamatsu 0 siblings, 0 replies; 10+ messages in thread From: Nobuhiro Iwamatsu @ 2012-04-19 2:10 UTC (permalink / raw) To: Shubhrajyoti Datta; +Cc: linux-i2c, linux-sh Hi, Thanks for your review. 2012/4/16 Shubhrajyoti Datta <omaplinuxkernel@gmail.com>: > Hi , > Few comments, > > > On Thu, Apr 12, 2012 at 5:27 AM, Nobuhiro Iwamatsu > <nobuhiro.iwamatsu.yj@renesas.com> wrote: >> This is the SuperH SH7734 I2C Controller Driver. >> A simple Master only support driver for the I2C included in processors SH7734 only. >> >> Signed-off-by: Nobuhiro Iwamatsu <nobuhiro.iwamatsu.yj@renesas.com> >> --- >> drivers/i2c/busses/Kconfig | 9 + >> drivers/i2c/busses/Makefile | 1 + >> drivers/i2c/busses/i2c-sh7734.c | 721 +++++++++++++++++++++++++++++++++++++++ >> 3 files changed, 731 insertions(+), 0 deletions(-) >> create mode 100644 drivers/i2c/busses/i2c-sh7734.c >> >> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig >> index d2c5095..68d3181 100644 >> --- a/drivers/i2c/busses/Kconfig >> +++ b/drivers/i2c/busses/Kconfig >> @@ -634,6 +634,15 @@ config I2C_SH_MOBILE >> This driver can also be built as a module. If so, the module >> will be called i2c-sh_mobile. >> >> +config I2C_SH7734 >> + tristate "Renesas SH7734 I2C Controller" >> + depends on CPU_SUBTYPE_SH7734 >> + help >> + This driver supports the I2C interfaces on the Renesas SH7734. >> + >> + This driver can also be built as a module. If so, the module >> + will be called i2c-sh7734. >> + >> config I2C_SIMTEC >> tristate "Simtec Generic I2C interface" >> select I2C_ALGOBIT >> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile >> index 569567b..b7057f5 100644 >> --- a/drivers/i2c/busses/Makefile >> +++ b/drivers/i2c/busses/Makefile >> @@ -63,6 +63,7 @@ obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o >> 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_SH7734) += i2c-sh7734.o >> obj-$(CONFIG_I2C_SIMTEC) += i2c-simtec.o >> obj-$(CONFIG_I2C_SIRF) += i2c-sirf.o >> obj-$(CONFIG_I2C_STU300) += i2c-stu300.o >> diff --git a/drivers/i2c/busses/i2c-sh7734.c b/drivers/i2c/busses/i2c-sh7734.c >> new file mode 100644 >> index 0000000..89e37d0 >> --- /dev/null >> +++ b/drivers/i2c/busses/i2c-sh7734.c >> @@ -0,0 +1,721 @@ >> +/* >> + * SuperH SH7734 I2C Controller >> + * >> + * Copyright (C) 2008 Magnus Damm >> + * Copyright (C) 2012 Nobuhiro Iwamatsu <nobuhiro.iwamatsu.yj@renesas.com> >> + * Copyright (C) 2012 Renesas Solutions Corp. >> + * >> + * This file is subject to the terms and conditions of the GNU General Public >> + * License. See the file "COPYING" in the main directory of this archive >> + * for more details. >> + * >> + * Based on drivers/i2c/busses/i2c-sh_mobile.c. >> + */ >> + >> +#include <linux/kernel.h> >> +#include <linux/module.h> >> +#include <linux/init.h> >> +#include <linux/delay.h> >> +#include <linux/platform_device.h> >> +#include <linux/interrupt.h> >> +#include <linux/i2c.h> >> +#include <linux/err.h> >> +#include <linux/clk.h> >> +#include <linux/io.h> >> +#include <linux/slab.h> >> +#include <linux/i2c/i2c-sh_mobile.h> >> + >> +enum sh7734_i2c_op { >> + OP_START_TRS = 0, >> + OP_START_SCP, >> + OP_TX_FIRST, >> + OP_TX_FIRST_END, >> + OP_TX, >> + OP_TX_END, >> + OP_TX_STOP, >> + OP_TX_STOP_CHK, >> + OP_TX_FINISH, >> + OP_RX, >> + OP_RX_BBSY_CLR, >> + OP_RX_LAST_DATA, >> + OP_RX_FINISH, >> + OP_RX_STOP, >> + OP_RX_STOP_CHK, >> +}; >> + >> +struct sh7734_i2c_data { >> + struct device *dev; >> + void __iomem *reg; >> + struct i2c_adapter adap; >> + unsigned long bus_speed; >> + struct clk *clk; >> + u_int8_t nf2cyc; >> + u_int8_t nf2cyc_clk; >> + u_int8_t iccr1_clk; >> + spinlock_t lock; >> + wait_queue_head_t wait; >> + struct i2c_msg *msg; >> + int pos; >> + int sr; >> + int irq; >> + int ackbr; >> + int state; >> + int data_num; >> +}; >> + >> +#define NORMAL_SPEED 100000 /* FAST_SPEED 400000 */ >> + >> +/* Register offsets */ >> +#define ICCR1 0x0 >> +#define ICCR2 0x1 >> +#define ICMR 0x2 >> +#define ICIER 0x3 >> +#define ICSR 0x4 >> +#define SAR 0x5 >> +#define ICDRT 0x6 >> +#define ICDRR 0x7 >> +#define NF2CYC 0x8 >> + >> +#define ICCR1_ICE 0x80 >> +#define ICCR1_RCVD 0x40 >> +#define ICCR1_MST 0x20 >> +#define ICCR1_TRS 0x10 >> + >> +#define SW_DONE 0x100 >> +#define SW_ERROR 0x200 >> + >> +#define ICCR2_BBSY 0x80 >> +#define ICCR2_SCP 0x40 >> +#define ICCR2_SDAO 0x20 >> +#define ICCR2_SDAOP 0x10 >> +#define ICCR2_SCLO 0x08 >> +#define ICCR2_I2CRST 0x02 >> + >> +#define ICSR_TDRE 0x80 >> +#define ICSR_TEND 0x40 >> +#define ICSR_RDRF 0x20 >> +#define ICSR_NACKF 0x10 >> +#define ICSR_STOP 0x08 >> +#define ICSR_AL 0x04 >> +#define ICSR_AAS 0x02 >> +#define ICSR_ADZ 0x01 >> + >> +#define ICIER_TIE 0x80 /* Transfer interrputs */ >> +#define ICIER_TEIE 0x40 /* Transfer end interrupts */ >> +#define ICIER_RIE 0x20 /* Receive interrupts */ >> +#define ICIER_NAKIE 0x10 /* NACK interrupts */ >> +#define ICIER_STIE 0x08 /* Stop interrupts */ >> +#define ICIER_ACKE 0x04 /* ACKbit check*/ >> +#define ICIER_ACKBR 0x02 /* set ACKbit / Receive */ >> +#define ICIER_ACKBT 0x01 /* set ACKbit / Transfer */ >> +#define ICIER_RX (ICIER_RIE|ICIER_TEIE|ICIER_TIE|ICIER_NAKIE|ICIER_STIE) >> +#define ICIER_TX (ICIER_TIE|ICIER_TEIE|ICIER_NAKIE|ICIER_STIE) >> + >> +#define I2C0_INTMASK 0xFF804288 >> +#define I2C1_INTMASK 0xFF80428C >> +#define I2C0_INTMASK_CLR 0xFF8042A8 >> +#define I2C1_INTMASK_CLR 0xFF8042AC >> +#define I2C0_INT 0xFF8040AC >> +#define I2C1_INT 0xFF8040D8 >> + >> +#define I2C_INT_TXI (1 << 4) >> +#define I2C_INT_TEI (1 << 3) >> +#define I2C_INT_RXI (1 << 2) >> +#define I2C_INT_NAKI (1 << 1) >> +#define I2C_INT_STPI (1 << 0) >> +#define I2C_INT_ALL \ >> + (I2C_INT_TXI|I2C_INT_TEI|I2C_INT_RXI|I2C_INT_NAKI|I2C_INT_STPI) >> + >> +static void >> +iic_wr(struct sh7734_i2c_data *pd, int offs, unsigned char data) >> +{ >> + iowrite8(data, pd->reg + offs); >> +} >> + >> +static unsigned char iic_rd(struct sh7734_i2c_data *pd, int offs) >> +{ >> + return ioread8(pd->reg + offs); >> +} >> + >> +static void iic_set_clr(struct sh7734_i2c_data *pd, int offs, >> + unsigned char set, unsigned char clr) >> +{ >> + iic_wr(pd, offs, (iic_rd(pd, offs) | set) & ~clr); >> +} >> + >> +static int clk_denom_tbl[] = { >> + 44, 52, 64, 72, 84, 92, 100, 108, 176, >> + 208, 256, 288, 336, 368, 400, 432, 352, 416, >> + 512, 576, 672, 736, 800, 864, 704, 832, 1024, >> + 1152, 1344, 1472, 1600, 1728 }; >> + >> +static void activate_ch(struct sh7734_i2c_data *pd, int num) >> +{ >> + unsigned long pclk; >> + unsigned long mode, mode_tmp; >> + unsigned int cks_bit; >> + long denom, speed; >> + unsigned int i; >> + >> + pclk = clk_get_rate(pd->clk) / 1000; >> + mode = speed = pd->bus_speed / 1000; >> + >> + for (i = 0; i < ARRAY_SIZE(clk_denom_tbl); i++) { >> + denom = (unsigned int)(pclk / clk_denom_tbl[i]); >> + if (denom > (speed * 2)) >> + continue; >> + else if ((speed - denom) > 100) >> + continue; >> + mode_tmp = (unsigned int)(denom % speed); >> + >> + if (mode_tmp < speed) { >> + if (mode_tmp < mode) { >> + mode = mode_tmp; >> + cks_bit = i; >> + } >> + } >> + } >> + >> + pd->nf2cyc = 1; >> + pd->nf2cyc_clk = (u_int8_t)(cks_bit & 0x10); >> + pd->iccr1_clk = (u_int8_t)(cks_bit & 0x0F); >> + pd->state = OP_START_TRS; >> + pd->ackbr = 0; >> + pd->data_num = num; >> + >> + /* Mask clear interrupts */ >> + if (pd->adap.nr == 0) >> + __raw_writel(I2C_INT_ALL, I2C0_INTMASK_CLR); >> + else > Nit : Can the else be dropped? If pd->adap.nr is 0, write value to I2C*0*_INTMASK_CLR. In differing, it writes a value to I2C*1*_INTMASK_CLR. Not "else" but "else if" (pd->adap.nr == 1) is right here. It corrects. > >> + __raw_writel(I2C_INT_ALL, I2C1_INTMASK_CLR); >> +} >> + >> +static void deactivate_ch(struct sh7734_i2c_data *pd) >> +{ >> + /* Reset controller */ >> + iic_set_clr(pd, ICCR2, ICCR2_I2CRST, 0); >> + udelay(10); >> + iic_set_clr(pd, ICCR2, 0, ICCR2_I2CRST); >> + >> + /* Clear/disable interrupts */ > Why is the interrupts disabled? Permission of various interruption factors is repealed here. These are repealed whenever communication finishes. > >> + iic_wr(pd, ICSR, 0); >> + iic_wr(pd, ICIER, 0); >> + >> + /* Mask interrupts */ >> + if (pd->adap.nr == 0) >> + __raw_writel(I2C_INT_ALL, I2C0_INTMASK); >> + else >> + __raw_writel(I2C_INT_ALL, I2C1_INTMASK); >> + >> + /* Disable channel */ >> + iic_set_clr(pd, ICCR1, 0, ICCR1_ICE); >> + >> + pd->ackbr = 0; >> + /* Reset controller */ >> + iic_set_clr(pd, ICCR2, ICCR2_I2CRST, 0); >> + udelay(10); >> + iic_set_clr(pd, ICCR2, 0, ICCR2_I2CRST); >> +} >> + >> +static int i2c_op(struct sh7734_i2c_data *pd) >> +{ >> + int ret = 0; >> + unsigned char data; >> + unsigned long flags; >> + >> + spin_lock_irqsave(&pd->lock, flags); >> + >> + switch (pd->state) { >> + case OP_START_TRS: >> + if (!(iic_rd(pd, ICCR2) & ICCR2_BBSY)) { >> + /* set to master transfer mode */ >> + iic_set_clr(pd, ICCR1, ICCR1_MST | ICCR1_TRS, 0); >> + pd->state = OP_TX_FIRST; >> + } else { >> + dev_dbg(pd->dev, "Device busy\n"); >> + pd->state = OP_TX_STOP; >> + break; >> + } >> + >> + iic_set_clr(pd, ICCR2, ICCR2_BBSY, ICCR2_SCP); >> + break; >> + >> + case OP_START_SCP: >> + /* Send TIE, Set TDRE */ >> + iic_set_clr(pd, ICCR2, ICCR2_BBSY, ICCR2_SCP); >> + pd->state = OP_TX_FIRST; >> + break; >> + >> + case OP_TX_FIRST: >> + if (iic_rd(pd, ICSR) & ICSR_TDRE) { >> + /* Create command */ >> + data = (pd->msg->addr & 0x7f) << 1; >> + data |= (pd->msg->flags & I2C_M_RD) ? 1 : 0; >> + >> + /* write data with clear TDRE bit */ >> + iic_wr(pd, ICDRT, data); >> + dev_dbg(pd->dev, "Write data (ICDRT): %02X\n", data); >> + pd->state = OP_TX_FIRST_END; >> + } else >> + dev_dbg(pd->dev, "ICSR/TDRE is not set\n"); >> + >> + break; >> + >> + case OP_TX_FIRST_END: >> + if (!(iic_rd(pd, ICSR) & ICSR_TEND)) { >> + dev_dbg(pd->dev, "ICSR/TEND is not set\n"); >> + break; >> + } >> + >> + /* Don't clear TEND */ >> + if (pd->ackbr) { >> + if (pd->msg->flags & I2C_M_RD) { >> + iic_set_clr(pd, ICSR, 0, ICSR_TEND); >> + iic_set_clr(pd, ICCR1, ICCR1_MST, ICCR1_TRS); >> + iic_set_clr(pd, ICSR, 0, ICSR_TDRE); >> + >> + if (pd->msg->len == 1) { >> + iic_set_clr(pd, ICIER, ICIER_ACKBT, 0); >> + iic_set_clr(pd, ICCR1, ICCR1_RCVD, 0); >> + >> + data = iic_rd(pd, ICDRR); >> + /* If pd->msg->len == 1, >> + this read data is dummy. */ >> + dev_dbg(pd->dev, "Last - 1 read data %02X\n", >> + data); >> + >> + pd->state = OP_RX_STOP_CHK; >> + } else { >> + iic_set_clr(pd, ICIER, 0, ICIER_ACKBT); >> + data = iic_rd(pd, ICDRR); >> + dev_dbg(pd->dev, >> + "Dummy read data %02X\n", data); >> + >> + pd->state = OP_RX; >> + } >> + } else >> + pd->state = OP_TX_STOP; >> + >> + break; >> + } >> + >> + if (iic_rd(pd, ICIER) & ICIER_ACKBR) { >> + dev_err(pd->dev, "ACKBR Error...\n"); >> + pd->sr |= SW_ERROR; /* Save Error state */ >> + >> + pd->state = OP_TX_STOP; >> + break; >> + } else { >> + pd->ackbr = 1; >> + pd->state = OP_TX; >> + } >> + >> + /* Through to OP_TX */ >> + >> + case OP_TX: /* send data */ >> + if (iic_rd(pd, ICSR) & ICSR_TDRE) { >> + data = pd->msg->buf[pd->pos]; >> + /* write data with clear TDRE bit */ >> + iic_wr(pd, ICDRT, data); >> + dev_dbg(pd->dev, "Write data (ICDRT): %02X\n", data); >> + pd->pos++; >> + >> + pd->state = OP_TX_END; >> + } else >> + dev_dbg(pd->dev, "Retry Data translate\n"); >> + >> + break; >> + >> + case OP_TX_END: >> + if (!(iic_rd(pd, ICSR) & ICSR_TEND)) { >> + dev_dbg(pd->dev, "TEND bit was not set (%02X)\n", >> + iic_rd(pd, ICSR)); >> + break; >> + } >> + >> + dev_dbg(pd->dev, "pd->pos %d, pd->msg->len %d, pd->data_num %d\n", >> + pd->pos, pd->msg->len, pd->data_num); >> + if (pd->pos == pd->msg->len) { >> + /* If last data, need check ICSR_TDRE and ICSR_TEND. */ >> + if (!(iic_rd(pd, ICSR) & ICSR_TDRE)) >> + break; >> + >> + /* Last data / Last Packet */ >> + if (pd->data_num == 1) { >> + pd->state = OP_TX_STOP; >> + /* Through to OP_TX_STOP */ >> + } else if (pd->data_num > 1) { >> + /* Change Transfer to Receive */ >> + iic_wr(pd, ICIER, 0x0); /* Interrupts disable */ >> + pd->state = OP_TX_FINISH; >> + ret = 1; >> + break; >> + } >> + } else { >> + pd->state = OP_TX; >> + break; >> + } >> + /* Through to OP_TX_STOP */ >> + >> + case OP_TX_STOP: >> + iic_set_clr(pd, ICSR, 0, ICSR_TEND); >> + iic_set_clr(pd, ICSR, 0, ICSR_STOP); >> + iic_set_clr(pd, ICCR2, 0, ICCR2_BBSY | ICCR2_SCP); >> + udelay(10); >> + pd->state = OP_TX_STOP_CHK; >> + /* Through to OP_TX_STOP_CHK */ >> + >> + case OP_TX_STOP_CHK: >> + if (!(iic_rd(pd, ICSR) | ICSR_STOP)) { >> + dev_dbg(pd->dev, "ICSR/STOP is not set\n"); >> + break; >> + } >> + iic_set_clr(pd, ICCR1, 0, ICCR1_MST | ICCR1_TRS); >> + iic_set_clr(pd, ICSR, 0, ICSR_TDRE); >> + >> + /* Interrupts disable */ >> + iic_wr(pd, ICIER, 0x0); >> + pd->state = OP_TX_FINISH; >> + ret = 1; >> + >> + break; >> + >> + case OP_RX: >> + if (!(iic_rd(pd, ICSR) & ICSR_RDRF)) >> + break; >> + >> + data = iic_rd(pd, ICDRR); >> + pd->msg->buf[pd->pos] = data; >> + pd->pos++; >> + >> + dev_dbg(pd->dev, "Read data %02X\n", data); >> + >> + if (pd->pos == pd->msg->len) >> + pd->state = OP_RX_STOP; >> + >> + break; >> + >> + case OP_RX_STOP: /* enable DTE interrupt, issue stop */ >> + iic_set_clr(pd, ICIER, ICIER_ACKBT, 0); >> + iic_set_clr(pd, ICCR1, ICCR1_RCVD, 0); >> + pd->state = OP_RX_BBSY_CLR; >> + >> + break; >> + >> + case OP_RX_BBSY_CLR: >> + data = iic_rd(pd, ICDRR); >> + dev_dbg(pd->dev, "Last - 1 read data %02X\n", data); >> + pd->msg->buf[pd->pos] = data; >> + pd->pos++; >> + pd->state = OP_RX_STOP_CHK; >> + >> + break; >> + >> + case OP_RX_STOP_CHK: >> + if (!(iic_rd(pd, ICSR) & ICSR_RDRF)) { >> + dev_dbg(pd->dev, "ICSR/RDRF bit 0\n"); >> + break; >> + } >> + >> + iic_set_clr(pd, ICSR, 0, ICSR_STOP); >> + iic_set_clr(pd, ICCR2, 0, ICCR2_BBSY|ICCR2_SCP); >> + >> + pd->state = OP_RX_LAST_DATA; >> + /* Through to OP_RX_LAST_DATA */ >> + >> + case OP_RX_LAST_DATA: >> + if (iic_rd(pd, ICCR2) && ICCR2_BBSY) >> + iic_set_clr(pd, ICCR2, 0, ICCR2_BBSY|ICCR2_SCP); >> + >> + if (!(iic_rd(pd, ICSR) & ICSR_STOP)) { >> + dev_dbg(pd->dev, "ICSR/STOP is not set\n"); >> + break; >> + } >> + >> + iic_set_clr(pd, ICSR, 0, ICSR_STOP); >> + >> + data = iic_rd(pd, ICDRR); >> + pd->msg->buf[pd->pos] = data; >> + >> + dev_dbg(pd->dev, "Last read data %02X\n", data); >> + >> + pd->pos++; >> + >> + iic_set_clr(pd, ICCR1, 0, ICCR1_MST); >> + iic_set_clr(pd, ICSR, 0, ICSR_TDRE); >> + iic_set_clr(pd, ICCR1, 0, ICCR1_RCVD); >> + >> + iic_wr(pd, ICIER, 0x00); >> + pd->state = OP_RX_FINISH; >> + ret = 1; >> + >> + break; >> + } >> + >> + spin_unlock_irqrestore(&pd->lock, flags); >> + >> + return ret; >> +} >> + >> +static irqreturn_t sh7734_i2c_isr(int irq, void *dev_id) >> +{ >> + struct platform_device *dev = dev_id; >> + struct sh7734_i2c_data *pd = platform_get_drvdata(dev); >> + unsigned char sr; >> + int wakeup; >> + >> + sr = iic_rd(pd, ICSR); >> + pd->sr |= sr; /* remember state */ >> + >> + dev_dbg(pd->dev, "%s ICSR: 0x%02x sr: 0x%02x %s %d %d!\n", >> + __func__, sr, pd->sr, >> + (pd->msg->flags & I2C_M_RD) ? "read" : "write", >> + pd->pos, pd->msg->len); >> + >> + if (sr & (ICSR_AL)) { >> + /* error / abitration */ >> + iic_wr(pd, ICSR, sr & ~ICSR_AL); >> + wakeup = 0; >> + } else { >> + wakeup = i2c_op(pd); >> + } >> + >> + if (wakeup) { >> + pd->sr |= SW_DONE; >> + wake_up(&pd->wait); >> + } >> + >> + return IRQ_HANDLED; >> +} >> + >> +static int start_ch(struct sh7734_i2c_data *pd, struct i2c_msg *usr_msg) >> +{ >> + unsigned long flags; >> + >> + if (usr_msg->len == 0 && (usr_msg->flags & I2C_M_RD)) { >> + dev_err(pd->dev, "Unsupported zero length i2c read\n"); >> + return -EIO; >> + } >> + >> + if (pd->ackbr == 0) { >> + iic_wr(pd, ICCR1, pd->iccr1_clk | ICCR1_ICE); >> + iic_wr(pd, NF2CYC, pd->nf2cyc | pd->nf2cyc_clk); >> + } >> + >> + spin_lock_irqsave(&pd->lock, flags); >> + >> + pd->msg = usr_msg; >> + pd->pos = 0; >> + pd->sr = 0; >> + >> + /* Enable interrupts */ >> + if (usr_msg->flags & I2C_M_RD) { >> + if ((pd->ackbr) && (pd->state == OP_TX_FINISH)) >> + pd->state = OP_START_SCP; >> + >> + iic_wr(pd, ICIER, ICIER_RX); >> + } else { >> + if (pd->state == OP_RX) >> + pd->state = OP_TX; >> + >> + iic_wr(pd, ICIER, ICIER_TX); >> + } >> + spin_unlock_irqrestore(&pd->lock, flags); >> + >> + return 0; >> +} >> + >> +static int sh7734_i2c_xfer(struct i2c_adapter *adapter, >> + struct i2c_msg *msgs, >> + int num) >> +{ >> + struct sh7734_i2c_data *pd = i2c_get_adapdata(adapter); >> + struct i2c_msg *msg; >> + int err = 0, i, k; >> + u_int8_t val; >> + >> + activate_ch(pd, num); >> + >> + /* Process all messages */ >> + for (i = 0; i < num; i++) { >> + msg = &msgs[i]; >> + >> + err = start_ch(pd, msg); >> + if (err) >> + break; >> + >> + i2c_op(pd); >> + >> + k = wait_event_timeout(pd->wait, >> + pd->sr & (SW_DONE|SW_ERROR), >> + 5 * HZ); >> + if (!k) { >> + dev_err(pd->dev, "Transfer request timed out\n"); >> + err = -EIO; >> + dev_err(pd->dev, "Polling timed out\n"); >> + break; >> + } >> + >> + val = iic_rd(pd, ICSR); >> + /* handle missing acknowledge and arbitration lost */ >> + if (((val | pd->sr) & (ICSR_AL | SW_ERROR))) { > What about checking for other errors? OK, I will update about error check. > >> + dev_err(pd->dev, "I2C I/O error\n"); >> + err = -EIO; >> + break; >> + } >> + } >> + >> + deactivate_ch(pd); >> + >> + if (!err) >> + err = num; >> + >> + return err; >> +} >> + >> +static u32 sh7734_i2c_func(struct i2c_adapter *adapter) >> +{ >> + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; >> +} >> + >> +static struct i2c_algorithm sh7734_i2c_algorithm = { >> + .functionality = sh7734_i2c_func, >> + .master_xfer = sh7734_i2c_xfer, >> +}; >> + >> +static int sh7734_i2c_probe(struct platform_device *pdev) >> +{ >> + struct i2c_sh_mobile_platform_data *pdata = pdev->dev.platform_data; >> + struct sh7734_i2c_data *pd; >> + struct i2c_adapter *adap; >> + struct resource *res; >> + int size; >> + int ret = 0; >> + >> + pd = kzalloc(sizeof(struct sh7734_i2c_data), GFP_KERNEL); >> + if (pd == NULL) { >> + dev_err(&pdev->dev, "cannot allocate private data\n"); >> + return -ENOMEM; >> + } >> + >> + /* I2C of SH7734 base clock is pll clock */ >> + pd->clk = clk_get(NULL, "peripheral_clk"); >> + if (IS_ERR(pd->clk)) { >> + dev_err(&pdev->dev, "cannot get clock\n"); >> + ret = PTR_ERR(pd->clk); >> + goto err; >> + } >> + >> + pd->irq = platform_get_irq(pdev, 0); >> + if (pd->irq < 0) { >> + dev_err(&pdev->dev, "failed to get irq\n"); >> + goto err_clk; >> + } >> + >> + ret = request_irq(pd->irq, sh7734_i2c_isr, >> + 0, pdev->name, pdev); >> + if (ret) { >> + dev_err(&pdev->dev, "cannot request IRQ\n"); >> + goto err_clk; >> + } >> + >> + pd->dev = &pdev->dev; >> + platform_set_drvdata(pdev, pd); >> + >> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); >> + if (res == NULL) { >> + dev_err(&pdev->dev, "cannot find IO resource\n"); >> + ret = -ENOENT; >> + goto err_irq; >> + } >> + >> + size = resource_size(res); >> + >> + pd->reg = ioremap(res->start, size); >> + if (pd->reg == NULL) { >> + dev_err(&pdev->dev, "cannot map IO\n"); >> + ret = -ENXIO; >> + goto err_irq; >> + } >> + >> + /* Use platformd data bus speed or NORMAL_SPEED */ >> + pd->bus_speed = NORMAL_SPEED; >> + if (pdata && pdata->bus_speed) >> + pd->bus_speed = pdata->bus_speed; >> + >> + /* setup the private data */ >> + adap = &pd->adap; >> + i2c_set_adapdata(adap, pd); >> + >> + adap->owner = THIS_MODULE; >> + adap->algo = &sh7734_i2c_algorithm; >> + adap->dev.parent = &pdev->dev; >> + adap->retries = 5; >> + adap->nr = pdev->id; >> + >> + strlcpy(adap->name, pdev->name, sizeof(adap->name)); >> + >> + spin_lock_init(&pd->lock); >> + init_waitqueue_head(&pd->wait); >> + >> + ret = i2c_add_numbered_adapter(adap); >> + if (ret < 0) { >> + dev_err(&pdev->dev, "cannot add numbered adapter\n"); >> + goto err_all; >> + } >> + >> + dev_info(&pdev->dev, "I2C adapter %d with bus speed %lu Hz\n", >> + adap->nr, pd->bus_speed); >> + >> + return 0; >> + >> +err_all: >> + iounmap(pd->reg); >> +err_irq: >> + free_irq(pd->irq, pd); >> +err_clk: >> + clk_put(pd->clk); >> +err: >> + kfree(pd); >> + >> + return ret; >> +} >> + >> +static int sh7734_i2c_remove(struct platform_device *pdev) > > Can these be in the __devexit ?? Thanks, I will fix. > >> +{ >> + struct sh7734_i2c_data *pd = platform_get_drvdata(pdev); >> + > Should we release resource here?? > > May be devm* could be considered? OK, I wll update to using devm*. > >> + i2c_del_adapter(&pd->adap); >> + iounmap(pd->reg); >> + free_irq(pd->irq, pdev); >> + clk_put(pd->clk); >> + kfree(pd); >> + return 0; >> +} >> + >> +static struct platform_driver sh7734_i2c_driver = { >> + .driver = { >> + .name = "i2c-sh7734", >> + .owner = THIS_MODULE, >> + }, >> + .probe = sh7734_i2c_probe, >> + .remove = sh7734_i2c_remove, >> +}; >> + >> +static int __init sh7734_i2c_adap_init(void) >> +{ >> + return platform_driver_register(&sh7734_i2c_driver); >> +} >> + >> +static void __exit sh7734_i2c_adap_exit(void) >> +{ >> + platform_driver_unregister(&sh7734_i2c_driver); >> +} >> + >> +subsys_initcall(sh7734_i2c_adap_init); >> +module_exit(sh7734_i2c_adap_exit); >> + >> +MODULE_DESCRIPTION("SuperH SH7734 I2C Bus Controller driver"); >> +MODULE_AUTHOR("Nobuhiro Iwamatsu <nobuhiro.iwamatsu.yj@renesas.com>"); >> +MODULE_LICENSE("GPL v2"); >> +MODULE_ALIAS("platform:i2c-sh7734"); >> -- >> 1.7.9.1 >> Best regards, Nobuhiro -- Nobuhiro Iwamatsu ^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2012-07-20 0:57 UTC | newest] Thread overview: 10+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2012-04-11 23:57 [PATCH] i2c: Add support SuperH SH7734 I2C bus controller Nobuhiro Iwamatsu 2012-04-12 4:18 ` Kuninori Morimoto [not found] ` <87d37d37ml.wl%kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org> 2012-04-12 8:18 ` Nobuhiro Iwamatsu 2012-04-13 9:35 ` Kuninori Morimoto [not found] ` <87y5q02cv2.wl%kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org> 2012-04-13 11:05 ` Nobuhiro Iwamatsu 2012-04-16 0:09 ` Kuninori Morimoto 2012-04-16 1:04 ` Nobuhiro Iwamatsu [not found] ` <OF7FE4858F.5425E857-ON80257A3F.002C2DA0-80257A3F.002E3111@eu.necel.com> [not found] ` <OF7FE4858F.5425E857-ON80257A3F.002C2DA0-80257A3F.002E3111-mWMTcI9IYFFWk0Htik3J/w@public.gmane.org> 2012-07-20 0:57 ` Nobuhiro Iwamatsu [not found] ` <1334188641-14641-1-git-send-email-nobuhiro.iwamatsu.yj-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org> 2012-04-16 11:07 ` Shubhrajyoti Datta 2012-04-19 2:10 ` Nobuhiro Iwamatsu
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).