From: Manuel L. <ma...@ro...> - 2007-10-25 18:23:36
|
Good evening, Here's a i2c bus driver for the 2 I2C interfaces of the SH7760. I've been using this driver for 2 years now, and it seems to be very solid. Tested with I2C touchscreens, I2S codecs, lots of eeproms and a MOST controller. Patch is againts latest git, comments appreciated. Thanks! Manuel Lauss --- >From b122c7a16f2a8bf664e2d478562e490cd3df6141 Mon Sep 17 00:00:00 2001 From: Manuel Lauss <ma...@ro...> Date: Thu, 25 Oct 2007 20:05:44 +0200 Subject: [PATCH] i2c-sh7760: I2C bus driver for SH7760 SoC Signed-off-by: Manuel Lauss <ma...@ro...> --- drivers/i2c/busses/Kconfig | 11 + drivers/i2c/busses/Makefile | 1 + drivers/i2c/busses/i2c-sh7760.c | 598 +++++++++++++++++++++++++++++++++++++++ include/asm-sh/i2c-sh7760.h | 23 ++ 4 files changed, 633 insertions(+), 0 deletions(-) create mode 100644 drivers/i2c/busses/i2c-sh7760.c create mode 100644 include/asm-sh/i2c-sh7760.h diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index c466c6c..94d6cc9 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -675,4 +675,15 @@ config I2C_PMCMSP This driver can also be built as module. If so, the module will be called i2c-pmcmsp. +config I2C_SH7760 + tristate "Renesas SH7760 I2C Controller" + depends on I2C && CPU_SUBTYPE_SH7760 + default n + help + If you say yes to this option, support will be included for the + built-in I2C interface of the Renesas SH7760 processor. + + This driver can also be built as a module. If so, the module + will be called i2c-sh7760. + endmenu diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index 81d43c2..548839e 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -43,6 +43,7 @@ obj-$(CONFIG_I2C_SIMTEC) += i2c-simtec.o obj-$(CONFIG_I2C_SIS5595) += i2c-sis5595.o obj-$(CONFIG_I2C_SIS630) += i2c-sis630.o obj-$(CONFIG_I2C_SIS96X) += i2c-sis96x.o +obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o obj-$(CONFIG_I2C_STUB) += i2c-stub.o obj-$(CONFIG_I2C_TAOS_EVM) += i2c-taos-evm.o obj-$(CONFIG_I2C_TINY_USB) += i2c-tiny-usb.o diff --git a/drivers/i2c/busses/i2c-sh7760.c b/drivers/i2c/busses/i2c-sh7760.c new file mode 100644 index 0000000..d901656 --- /dev/null +++ b/drivers/i2c/busses/i2c-sh7760.c @@ -0,0 +1,598 @@ +/* + * I2C bus driver for the SH7760 I2C Interfaces. + * + * (c) 2005-2007 MSC Vertriebsges.m.b.H. + * <ml...@ms...>., <ma...@ro...> + * + * licensed under the terms outlined in the file COPYING. + * + */ + +/* NOTE: SMBus QUICK Probe feature is "emulated" by reading 1 byte from + * the slave address, because the SH7760 I2C cannot be programmed + * to send a stop immediately after receiving the slave ACK/NACK. + * If this behavior is not wanted, set platform_data.noquick to 1. + */ + +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/types.h> + +#include <asm/clock.h> +#include <asm/i2c-sh7760.h> +#include <asm/io.h> + +/* register offsets */ +#define I2CSCR 0x0 /* slave ctrl */ +#define I2CMCR 0x4 /* master ctrl */ +#define I2CSSR 0x8 /* slave status */ +#define I2CMSR 0xC /* master status */ +#define I2CSIER 0x10 /* slave irq enable */ +#define I2CMIER 0x14 /* master irq enable */ +#define I2CCCR 0x18 /* clock dividers */ +#define I2CSAR 0x1c /* slave address */ +#define I2CMAR 0x20 /* master address */ +#define I2CRXTX 0x24 /* data port */ +#define I2CFCR 0x28 /* fifo control */ +#define I2CFSR 0x2C /* fifo status */ +#define I2CFIER 0x30 /* fifo irq enable */ +#define I2CRFDR 0x34 /* rx fifo count */ +#define I2CTFDR 0x38 /* tx fifo count */ + +#define REGSIZE 0x3C + +#define MCR_MDBS 0x80 +#define MCR_FSCL 0x40 +#define MCR_FSDA 0x20 +#define MCR_OBPC 0x10 +#define MCR_MIE 0x08 +#define MCR_TSBE 0x04 +#define MCR_FSB 0x02 +#define MCR_ESG 0x01 + +#define MSR_MNR 0x40 +#define MSR_MAL 0x20 +#define MSR_MST 0x10 +#define MSR_MDE 0x08 +#define MSR_MDT 0x04 +#define MSR_MDR 0x02 +#define MSR_MAT 0x01 + +#define MIE_MNRE 0x40 +#define MIE_MALE 0x20 +#define MIE_MSTE 0x10 +#define MIE_MDEE 0x08 +#define MIE_MDTE 0x04 +#define MIE_MDRE 0x02 +#define MIE_MATE 0x01 + +#define FCR_RFRST 0x02 +#define FCR_TFRST 0x01 + +#define FSR_TEND 0x04 +#define FSR_RDF 0x02 +#define FSR_TDFE 0x01 + +#define FIER_TEIE 0x04 +#define FIER_RXIE 0x02 +#define FIER_TXIE 0x01 + +#define FIFO_SIZE 16 + +struct cami2c { + void __iomem *iobase; /* channel base address */ + struct i2c_adapter adap; + u32 func; /* supported functions */ + int irq; /* IRQ vector */ + int chan; /* channel number (0 or 1) */ + + /* message processing */ + struct i2c_msg *msg; +#define IDF_SEND 1 +#define IDF_RECV 2 +#define IDF_STOP 4 + int flags; + +#define IDS_DONE 1 +#define IDS_ARBLOST 2 +#define IDS_NACK 4 + int status; + struct completion xfer_done; +}; + +static inline void OUT32(struct cami2c *cam, int reg, unsigned long val) +{ + ctrl_outl(val, (unsigned long)cam->iobase + reg); +} + +static inline unsigned long IN32(struct cami2c *cam, int reg) +{ + return ctrl_inl((unsigned long)cam->iobase + reg); +} + +static irqreturn_t sh7760_i2c_irq(int this_irq, void *ptr) +{ + struct cami2c *id = ptr; + struct i2c_msg *msg; + unsigned long msr, fsr, fier, len; + char *data; + + msg = id->msg; + data = msg->buf; + + msr = IN32(id, I2CMSR); + fsr = IN32(id, I2CFSR); + + /* arbitration lost */ + if (msr & MSR_MAL) { + OUT32(id, I2CMCR, 0); + OUT32(id, I2CSCR, 0); + OUT32(id, I2CSAR, 0); + id->status |= IDS_DONE | IDS_ARBLOST; + goto out; + } + + if (msr & MSR_MNR) { + /* NACK handling is very screwed up. After receiving a + * NAK IRQ one has to wait a bit before writing to any + * registers, or the ctl will lock up. After that delay + * do a normal i2c stop. Then wait at least 1 ms before + * attempting another xfer or risk another ctl hang. + */ + udelay(100); /* wait or risk ctl hang */ + OUT32(id, I2CFCR, FCR_RFRST | FCR_TFRST); + OUT32(id, I2CMCR, MCR_MIE | MCR_FSB); + OUT32(id, I2CFIER, 0); + OUT32(id, I2CMIER, MIE_MSTE); + OUT32(id, I2CSCR, 0); + OUT32(id, I2CSAR, 0); + id->status |= IDS_NACK; + msr &= ~MSR_MAT; + fsr = 0; + /* In some cases the MST bit is also set; so don't go + * to the end of the handler + */ + } + + /* i2c-stop was sent */ + if (msr & MSR_MST) { + id->status |= IDS_DONE; + goto out; + } + + /* i2c slave addr was sent */ + if (msr & MSR_MAT) + OUT32(id, I2CMCR, MCR_MIE); + + fier = IN32(id, I2CFIER); + + if (fsr & FSR_RDF) { + /* there is data in the receive fifo */ + len = IN32(id, I2CRFDR); + if (msg->len <= len) { + /* all data received, stop if required */ + if (id->flags & IDF_STOP) { + OUT32(id, I2CMCR, MCR_MIE | MCR_FSB); + OUT32(id, I2CFIER, 0); + /* manual says: wait >= 0.5 SCL times */ + udelay(5); + /* next int should contain MST */ + } else { + id->status |= IDS_DONE; + /* keep the RDF bit: ctrl holds SCL low + * until the setup for the next i2c_msg + * clears this bit and ctl resumes work. + */ + fsr &= ~FSR_RDF; + } + } + /* read fifo */ + while (msg->len && len) { + *data++ = IN32(id, I2CRXTX); + msg->len--; + len--; + } + + if (msg->len) { + /* (re)adjust rx-fifo trigger */ + if (msg->len >= FIFO_SIZE) + len = FIFO_SIZE - 1; + else + len = msg->len - 1; + + OUT32(id, I2CFCR, FCR_TFRST | ((len & 0xf) << 4)); + } + + } else if (id->flags & IDF_SEND) { + /* transfer has ended, and no more data to send */ + if ((fsr & FSR_TEND) && (msg->len < 1)) { + if (id->flags & IDF_STOP) { + OUT32(id, I2CMCR, MCR_MIE | MCR_FSB); + } else { + id->status |= IDS_DONE; + /* keep the TEND bit: ctl holds SCL low + * until the setup for the next i2c_msg + * clears this bit and ctl resumes work. + */ + fsr &= ~FSR_TEND; + } + } + + /* there is an opening in the TX FIFO */ + if (fsr & FSR_TDFE) { + while (msg->len && (IN32(id, I2CTFDR) < FIFO_SIZE)) { + OUT32(id, I2CRXTX, *data++); + (msg->len)--; + } + + if (msg->len < 1) { + fier &= ~FIER_TXIE; + OUT32(id, I2CFIER, fier); + } else { + /* adjust tx-fifo trigger */ + if (msg->len >= FIFO_SIZE) + len = 2; + else + len = 0; + + OUT32(id, I2CFCR, + FCR_RFRST | ((len & 3) << 2)); + } + } + } +out: + if (id->status & IDS_DONE) { + OUT32(id, I2CMIER, 0); + OUT32(id, I2CFIER, 0); + id->msg = NULL; + complete(&id->xfer_done); + } + /* clear status flags and ctrl resumes work */ + OUT32(id, I2CMSR, ~msr); + OUT32(id, I2CFSR, ~fsr); + OUT32(id, I2CSSR, 0); + + return IRQ_HANDLED; +} + +static inline int sh7760_i2c_bsy_check(struct cami2c *id) +{ + return (IN32(id, I2CMCR) & MCR_FSDA); +} + +/* prepare and start a master receive operation */ +static void sh7760_i2c_mrecv(struct cami2c *id) +{ + int len; + + /* set the slave addr reg; otherwise rcv wont work! */ + OUT32(id, I2CSAR, 0xfe); + OUT32(id, I2CMAR, (id->msg->addr << 1) | 1); + + /* adjust rx fifo trigger */ + if (id->msg->len >= FIFO_SIZE) + len = FIFO_SIZE - 1; /* trigger at fifo full */ + else + len = id->msg->len - 1; /* trigger before all received */ + + OUT32(id, I2CFCR, FCR_RFRST | FCR_TFRST); + OUT32(id, I2CFCR, FCR_TFRST | ((len & 0xF) << 4)); + + id->flags |= IDF_RECV; + + OUT32(id, I2CMSR, 0); + if (id->msg->flags & I2C_M_NOSTART) + OUT32(id, I2CMCR, MCR_MIE); + else + OUT32(id, I2CMCR, MCR_MIE | MCR_ESG); + + OUT32(id, I2CMIER, MIE_MNRE | MIE_MALE | MIE_MSTE | MIE_MATE); + OUT32(id, I2CFIER, FIER_RXIE); +} + +/* prepare and start a master send operation */ +static void sh7760_i2c_msend(struct cami2c *id) +{ + int len; + + /* set the slave addr reg; otherwise xmit wont work! */ + OUT32(id, I2CSAR, 0xfe); + OUT32(id, I2CMAR, (id->msg->addr << 1) | 0); + + /* adjust tx fifo trigger */ + if (id->msg->len >= FIFO_SIZE) + len = 2; /* trig: 2 bytes left in TX fifo */ + else + len = 0; /* trig: 8 bytes left in TX fifo */ + + OUT32(id, I2CFCR, FCR_RFRST | FCR_TFRST); + OUT32(id, I2CFCR, FCR_RFRST | ((len & 3) << 2)); + + while (id->msg->len && IN32(id, I2CTFDR) < FIFO_SIZE) { + OUT32( id, I2CRXTX, *(id->msg->buf)); + (id->msg->len)--; + (id->msg->buf)++; + } + + id->flags |= IDF_SEND; + + OUT32(id, I2CMSR, 0); + if (id->msg->flags & I2C_M_NOSTART) + OUT32(id, I2CMCR, MCR_MIE); + else + OUT32(id, I2CMCR, MCR_MIE | MCR_ESG); + + OUT32(id, I2CFSR, 0); + + OUT32(id, I2CMIER, MIE_MNRE | MIE_MALE | MIE_MSTE | MIE_MATE); + OUT32(id, I2CFIER, FIER_TEIE | (id->msg->len ? FIER_TXIE : 0)); +} + +static int sh7760_i2c_master_xfer(struct i2c_adapter *adap, + struct i2c_msg *msgs, + int num) +{ + struct cami2c *id = adap->algo_data; + struct i2c_msg *msg = msgs, qmsg; + int i, addr, retr; + unsigned char buf; /* dummy for qmsg */ + + if (sh7760_i2c_bsy_check(id)) { + pr_debug("sh7760-i2c%d: bus is busy!\n", id->chan); + num = -EBUSY; + } + + i = 0; + while ((i < num) && (num > 0)) { + addr = msg->addr << 1; + if (msg->len < 1) { + /* SMBus Quick "emulation": + * setup a dummy msg to read 1 byte from addr. + */ + qmsg.addr = msg->addr; + qmsg.len = 1; + qmsg.flags = msg->flags; + qmsg.buf = &buf; + addr |= 1; + id->msg = &qmsg; + } else { + if (msg->flags & I2C_M_RD) + addr |= 1; + if (msg->flags & I2C_M_REV_DIR_ADDR) + addr ^= 1; + msg->addr = addr >> 1; + id->msg = msg; + } + retr = adap->retries; + +retry: + id->flags = ((i == (num-1)) ? IDF_STOP : 0); + id->status = 0; + init_completion(&id->xfer_done); + + if (addr & 1) + sh7760_i2c_mrecv(id); + else + sh7760_i2c_msend(id); + + wait_for_completion(&id->xfer_done); + + if (id->status == 0) { + num = -EIO; + break; + } + + if (id->status & IDS_NACK) { + /* little delay. Without it, subsequent transfers + * hang the i2c ctl... */ + mdelay(1); + num = -EREMOTEIO; + break; + } + + if (id->status & IDS_ARBLOST) { + if (retr--) { + mdelay(2); + goto retry; + } + num = -EREMOTEIO; + break; + } + + /* message successfully processed. YAY! */ + msg++; + i++; + } + + id->msg = NULL; + id->flags = 0; + id->status = 0; + + OUT32(id, I2CMCR, 0); + OUT32(id, I2CMSR, 0); + OUT32(id, I2CMIER, 0); + OUT32(id, I2CFIER, 0); + + /* important: reset slave regs too: master mode enables slave + * module for receive ops (ack, data). Without this reset, + * eternal bus activity might be reported after NACK / ARBLOST. + */ + OUT32(id, I2CSCR, 0); + OUT32(id, I2CSAR, 0); + OUT32(id, I2CSSR, 0); + + return num; +} + +static u32 sh7760_i2c_func(struct i2c_adapter *adap) +{ + struct cami2c *id = adap->algo_data; + return id->func; +} + +static struct i2c_algorithm sh7760_i2c_algo = { + .master_xfer = sh7760_i2c_master_xfer, + .functionality = sh7760_i2c_func, +}; + +/* calculate CCR register setting for a desired scl clock */ +static int __devinit calc_CCR(unsigned long scl_freq) +{ + struct clk *mclk; + unsigned long mck, m1, dff, odff, iclk; + signed char cdf, cdfm = 0; + int scgd, scgdm; + + mclk = clk_get(NULL, "module_clk"); + if (!mclk) { + mck = CONFIG_SH_PCLK_FREQ; + } else { + mck = mclk->rate; + clk_put(mclk); + } + + /* pclock -> CDF -> i2c_module clock (iclk) -> SCGD -> SCL */ + odff = scl_freq; + scgdm = cdfm = m1 = 0; + for (cdf = 3; cdf >= 0; cdf--) { + iclk = mck / (1 + cdf); + /* iclk must not be > 20MHz */ + if (iclk >= 20000000) + continue; + for (scgd = 0; scgd < 63; scgd++) { + m1 = iclk / (20 + (scgd << 3)); + dff = abs(scl_freq - m1); + if (dff < odff) { + odff = dff; + cdfm = cdf; + scgdm = scgd; + } + } + } + /* create a CCR register value */ + m1 = ((scgdm & 63) << 2) | (cdfm & 3); + + return m1; +} + +/* register a channel with the i2c core */ +static int __devinit sh7760_i2c_probe(struct platform_device *pdev) +{ + struct sh7760_i2c_platdata *pd; + struct resource *res; + struct cami2c *id; + int ret; + + pd = pdev->dev.platform_data; + if (!pd) { + dev_err(&pdev->dev, "no platform_data!\n"); + ret = -ENODEV; + goto out0; + } + + id = kzalloc(sizeof(struct cami2c), GFP_KERNEL); + if (!id) { + ret = -ENOMEM; + goto out0; + } + + ret = -ENODEV; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + goto out1; + + id->iobase = (void __iomem *)res->start; + id->irq = platform_get_irq(pdev, 0); + id->chan = pdev->id; + id->func = I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; + if (pd->noquick) + id->func &= ~I2C_FUNC_SMBUS_QUICK; + + id->adap.algo = &sh7760_i2c_algo; + id->adap.class = I2C_CLASS_ALL; + id->adap.timeout = 100; + id->adap.retries = 3; + id->adap.algo_data = id; + id->adap.dev.parent = &pdev->dev; + strcpy(id->adap.name, SH7760_I2C_DEVNAME); + + OUT32(id, I2CMCR, 0); + OUT32(id, I2CMSR, 0); + OUT32(id, I2CMIER, 0); + OUT32(id, I2CMAR, 0); + OUT32(id, I2CSIER, 0); + OUT32(id, I2CSAR, 0); + OUT32(id, I2CSCR, 0); + OUT32(id, I2CSSR, 0); + OUT32(id, I2CFIER, 0); + OUT32(id, I2CFCR, FCR_RFRST | FCR_TFRST); + OUT32(id, I2CFSR, 0); + OUT32(id, I2CCCR, calc_CCR(pd->speed_khz * 1000)); + + if (request_irq(id->irq, sh7760_i2c_irq, IRQF_DISABLED, + SH7760_I2C_DEVNAME, id)) { + dev_err(&pdev->dev, "cannot get irq %d\n", id->irq); + ret = -ENODEV; + goto out1; + } + + ret = i2c_add_adapter(&id->adap); + if (ret < 0) { + dev_err(&pdev->dev, "i2c_add_adapter failed: %d\n", ret); + goto out2; + } + + platform_set_drvdata(pdev, id); + + printk(KERN_INFO "SH7760 I2C-%d, %d kHz, mmio %p, irq %d\n", + id->chan, pd->speed_khz, id->iobase, id->irq); + + return 0; + +out2: free_irq(id->irq, (void *)id); +out1: kfree(id); +out0: return ret; +} + +static int __devexit sh7760_i2c_remove(struct platform_device *pdev) +{ + struct cami2c *id = platform_get_drvdata(pdev); + + if (id) { + i2c_del_adapter(&id->adap); + free_irq(id->irq, (void *)id); + kfree(id); + } + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver sh7760_i2c_drv = { + .driver = { + .name = SH7760_I2C_DEVNAME, + .owner = THIS_MODULE, + }, + .probe = sh7760_i2c_probe, + .remove = __devexit_p(sh7760_i2c_remove), +}; + +static int __init sh7760_i2c_init(void) +{ + return platform_driver_register(&sh7760_i2c_drv); +} + +static void __exit sh7760_i2c_exit(void) +{ + platform_driver_unregister(&sh7760_i2c_drv); +} + +module_init(sh7760_i2c_init); +module_exit(sh7760_i2c_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("SH7760 I2C bus driver"); +MODULE_AUTHOR("Manuel Lauss <ma...@ro...>"); diff --git a/include/asm-sh/i2c-sh7760.h b/include/asm-sh/i2c-sh7760.h new file mode 100644 index 0000000..d2a86bd --- /dev/null +++ b/include/asm-sh/i2c-sh7760.h @@ -0,0 +1,23 @@ +/* + * MMIO/IRQ and platform data for SH7760 I2C channels + */ + +#ifndef _I2C_SH7760_H_ +#define _I2C_SH7760_H_ + +#define SH7760_I2C_DEVNAME "i2c-sh7760" + +#define SH7760_I2C0_MMIO 0xFE140000 +#define SH7760_I2C0_MMIOEND 0xFE14003B +#define SH7760_I2C0_IRQ 62 + +#define SH7760_I2C1_MMIO 0xFE150000 +#define SH7760_I2C1_MMIOEND 0xFE15003B +#define SH7760_I2C1_IRQ 63 + +struct sh7760_i2c_platdata { + unsigned int speed_khz; + unsigned int noquick; +}; + +#endif -- 1.5.3.4 |