From: Nobuhiro Iwamatsu <nobuhiro.iwamatsu.yj@renesas.com> To: linux-i2c@vger.kernel.org Cc: linux-sh@vger.kernel.org, Nobuhiro Iwamatsu <nobuhiro.iwamatsu.yj@renesas.com> Subject: [PATCH] i2c: Add support SuperH SH7734 I2C bus controller Date: Wed, 11 Apr 2012 23:57:21 +0000 [thread overview] Message-ID: <1334188641-14641-1-git-send-email-nobuhiro.iwamatsu.yj@renesas.com> (raw) 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
WARNING: multiple messages have this Message-ID (diff)
From: Nobuhiro Iwamatsu <nobuhiro.iwamatsu.yj@renesas.com> To: linux-i2c@vger.kernel.org Cc: linux-sh@vger.kernel.org, Nobuhiro Iwamatsu <nobuhiro.iwamatsu.yj@renesas.com> Subject: [PATCH] i2c: Add support SuperH SH7734 I2C bus controller Date: Thu, 12 Apr 2012 08:57:21 +0900 [thread overview] Message-ID: <1334188641-14641-1-git-send-email-nobuhiro.iwamatsu.yj@renesas.com> (raw) 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
next reply other threads:[~2012-04-11 23:57 UTC|newest] Thread overview: 20+ messages / expand[flat|nested] mbox.gz Atom feed top 2012-04-11 23:57 Nobuhiro Iwamatsu [this message] 2012-04-11 23:57 ` [PATCH] i2c: Add support SuperH SH7734 I2C bus controller Nobuhiro Iwamatsu 2012-04-12 4:18 ` Kuninori Morimoto 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-12 8:18 ` Nobuhiro Iwamatsu 2012-04-13 9:35 ` Kuninori Morimoto 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-13 11:05 ` Nobuhiro Iwamatsu 2012-04-16 0:09 ` Kuninori Morimoto 2012-04-16 0:09 ` Kuninori Morimoto 2012-04-16 0:59 ` Nobuhiro Iwamatsu 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 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-16 11:19 ` Shubhrajyoti Datta 2012-04-19 2:10 ` Nobuhiro Iwamatsu 2012-04-19 2:10 ` Nobuhiro Iwamatsu
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=1334188641-14641-1-git-send-email-nobuhiro.iwamatsu.yj@renesas.com \ --to=nobuhiro.iwamatsu.yj@renesas.com \ --cc=linux-i2c@vger.kernel.org \ --cc=linux-sh@vger.kernel.org \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: linkBe sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.