diff options
author | Timothy Pearson <tpearson@raptorengineering.com> | 2017-08-23 14:45:25 -0500 |
---|---|---|
committer | Timothy Pearson <tpearson@raptorengineering.com> | 2017-08-23 14:45:25 -0500 |
commit | fcbb27b0ec6dcbc5a5108cb8fb19eae64593d204 (patch) | |
tree | 22962a4387943edc841c72a4e636a068c66d58fd /drivers/i2c | |
download | ast2050-linux-kernel-fcbb27b0ec6dcbc5a5108cb8fb19eae64593d204.zip ast2050-linux-kernel-fcbb27b0ec6dcbc5a5108cb8fb19eae64593d204.tar.gz |
Initial import of modified Linux 2.6.28 tree
Original upstream URL:
git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git | branch linux-2.6.28.y
Diffstat (limited to 'drivers/i2c')
88 files changed, 39067 insertions, 0 deletions
diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig new file mode 100644 index 0000000..711ca08 --- /dev/null +++ b/drivers/i2c/Kconfig @@ -0,0 +1,90 @@ +# +# I2C subsystem configuration +# + +menuconfig I2C + tristate "I2C support" + depends on HAS_IOMEM + ---help--- + I2C (pronounce: I-square-C) is a slow serial bus protocol used in + many micro controller applications and developed by Philips. SMBus, + or System Management Bus is a subset of the I2C protocol. More + information is contained in the directory <file:Documentation/i2c/>, + especially in the file called "summary" there. + + Both I2C and SMBus are supported here. You will need this for + hardware sensors support, and also for Video For Linux support. + + If you want I2C support, you should say Y here and also to the + specific driver for your bus adapter(s) below. + + This I2C support can also be built as a module. If so, the module + will be called i2c-core. + +if I2C + +config I2C_BOARDINFO + boolean + default y + +config I2C_CHARDEV + tristate "I2C device interface" + help + Say Y here to use i2c-* device files, usually found in the /dev + directory on your system. They make it possible to have user-space + programs use the I2C bus. Information on how to do this is + contained in the file <file:Documentation/i2c/dev-interface>. + + This support is also available as a module. If so, the module + will be called i2c-dev. + +config I2C_HELPER_AUTO + bool "Autoselect pertinent helper modules" + default y + help + Some I2C bus drivers require so-called "I2C algorithm" modules + to work. These are basically software-only abstractions of generic + I2C interfaces. This option will autoselect them so that you don't + have to care. + + Unselect this only if you need to enable additional helper + modules, for example for use with external I2C bus drivers. + + In doubt, say Y. + +source drivers/i2c/algos/Kconfig +source drivers/i2c/busses/Kconfig +source drivers/i2c/chips/Kconfig + +config I2C_DEBUG_CORE + bool "I2C Core debugging messages" + help + Say Y here if you want the I2C core to produce a bunch of debug + messages to the system log. Select this if you are having a + problem with I2C support and want to see more of what is going on. + +config I2C_DEBUG_ALGO + bool "I2C Algorithm debugging messages" + help + Say Y here if you want the I2C algorithm drivers to produce a bunch + of debug messages to the system log. Select this if you are having + a problem with I2C support and want to see more of what is going + on. + +config I2C_DEBUG_BUS + bool "I2C Bus debugging messages" + help + Say Y here if you want the I2C bus drivers to produce a bunch of + debug messages to the system log. Select this if you are having + a problem with I2C support and want to see more of what is going + on. + +config I2C_DEBUG_CHIP + bool "I2C Chip debugging messages" + help + Say Y here if you want the I2C chip drivers to produce a bunch of + debug messages to the system log. Select this if you are having + a problem with I2C support and want to see more of what is going + on. + +endif # I2C diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile new file mode 100644 index 0000000..ba26e6c --- /dev/null +++ b/drivers/i2c/Makefile @@ -0,0 +1,12 @@ +# +# Makefile for the i2c core. +# + +obj-$(CONFIG_I2C_BOARDINFO) += i2c-boardinfo.o +obj-$(CONFIG_I2C) += i2c-core.o +obj-$(CONFIG_I2C_CHARDEV) += i2c-dev.o +obj-y += busses/ chips/ algos/ + +ifeq ($(CONFIG_I2C_DEBUG_CORE),y) +EXTRA_CFLAGS += -DDEBUG +endif diff --git a/drivers/i2c/algos/Kconfig b/drivers/i2c/algos/Kconfig new file mode 100644 index 0000000..b788579 --- /dev/null +++ b/drivers/i2c/algos/Kconfig @@ -0,0 +1,21 @@ +# +# I2C algorithm drivers configuration +# + +menu "I2C Algorithms" + depends on !I2C_HELPER_AUTO + +config I2C_ALGOBIT + tristate "I2C bit-banging interfaces" + +config I2C_ALGOPCF + tristate "I2C PCF 8584 interfaces" + +config I2C_ALGOPCA + tristate "I2C PCA 9564 interfaces" + +config I2C_ALGO_SGI + tristate + depends on SGI_IP22 || SGI_IP32 || X86_VISWS + +endmenu diff --git a/drivers/i2c/algos/Makefile b/drivers/i2c/algos/Makefile new file mode 100644 index 0000000..cac1051 --- /dev/null +++ b/drivers/i2c/algos/Makefile @@ -0,0 +1,12 @@ +# +# Makefile for the i2c algorithms +# + +obj-$(CONFIG_I2C_ALGOBIT) += i2c-algo-bit.o +obj-$(CONFIG_I2C_ALGOPCF) += i2c-algo-pcf.o +obj-$(CONFIG_I2C_ALGOPCA) += i2c-algo-pca.o +obj-$(CONFIG_I2C_ALGO_SGI) += i2c-algo-sgi.o + +ifeq ($(CONFIG_I2C_DEBUG_ALGO),y) +EXTRA_CFLAGS += -DDEBUG +endif diff --git a/drivers/i2c/algos/i2c-algo-bit.c b/drivers/i2c/algos/i2c-algo-bit.c new file mode 100644 index 0000000..eb8f72c --- /dev/null +++ b/drivers/i2c/algos/i2c-algo-bit.c @@ -0,0 +1,640 @@ +/* ------------------------------------------------------------------------- + * i2c-algo-bit.c i2c driver algorithms for bit-shift adapters + * ------------------------------------------------------------------------- + * Copyright (C) 1995-2000 Simon G. Vogl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * ------------------------------------------------------------------------- */ + +/* With some changes from Frodo Looijaard <frodol@dds.nl>, Kyösti Mälkki + <kmalkki@cc.hut.fi> and Jean Delvare <khali@linux-fr.org> */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> + + +/* ----- global defines ----------------------------------------------- */ + +#ifdef DEBUG +#define bit_dbg(level, dev, format, args...) \ + do { \ + if (i2c_debug >= level) \ + dev_dbg(dev, format, ##args); \ + } while (0) +#else +#define bit_dbg(level, dev, format, args...) \ + do {} while (0) +#endif /* DEBUG */ + +/* ----- global variables --------------------------------------------- */ + +static int bit_test; /* see if the line-setting functions work */ +module_param(bit_test, bool, 0); +MODULE_PARM_DESC(bit_test, "Test the lines of the bus to see if it is stuck"); + +#ifdef DEBUG +static int i2c_debug = 1; +module_param(i2c_debug, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(i2c_debug, + "debug level - 0 off; 1 normal; 2 verbose; 3 very verbose"); +#endif + +/* --- setting states on the bus with the right timing: --------------- */ + +#define setsda(adap, val) adap->setsda(adap->data, val) +#define setscl(adap, val) adap->setscl(adap->data, val) +#define getsda(adap) adap->getsda(adap->data) +#define getscl(adap) adap->getscl(adap->data) + +static inline void sdalo(struct i2c_algo_bit_data *adap) +{ + setsda(adap, 0); + udelay((adap->udelay + 1) / 2); +} + +static inline void sdahi(struct i2c_algo_bit_data *adap) +{ + setsda(adap, 1); + udelay((adap->udelay + 1) / 2); +} + +static inline void scllo(struct i2c_algo_bit_data *adap) +{ + setscl(adap, 0); + udelay(adap->udelay / 2); +} + +/* + * Raise scl line, and do checking for delays. This is necessary for slower + * devices. + */ +static int sclhi(struct i2c_algo_bit_data *adap) +{ + unsigned long start; + + setscl(adap, 1); + + /* Not all adapters have scl sense line... */ + if (!adap->getscl) + goto done; + + start = jiffies; + while (!getscl(adap)) { + /* This hw knows how to read the clock line, so we wait + * until it actually gets high. This is safer as some + * chips may hold it low ("clock stretching") while they + * are processing data internally. + */ + if (time_after_eq(jiffies, start + adap->timeout)) + return -ETIMEDOUT; + cond_resched(); + } +#ifdef DEBUG + if (jiffies != start && i2c_debug >= 3) + pr_debug("i2c-algo-bit: needed %ld jiffies for SCL to go " + "high\n", jiffies - start); +#endif + +done: + udelay(adap->udelay); + return 0; +} + + +/* --- other auxiliary functions -------------------------------------- */ +static void i2c_start(struct i2c_algo_bit_data *adap) +{ + /* assert: scl, sda are high */ + setsda(adap, 0); + udelay(adap->udelay); + scllo(adap); +} + +static void i2c_repstart(struct i2c_algo_bit_data *adap) +{ + /* assert: scl is low */ + sdahi(adap); + sclhi(adap); + setsda(adap, 0); + udelay(adap->udelay); + scllo(adap); +} + + +static void i2c_stop(struct i2c_algo_bit_data *adap) +{ + /* assert: scl is low */ + sdalo(adap); + sclhi(adap); + setsda(adap, 1); + udelay(adap->udelay); +} + + + +/* send a byte without start cond., look for arbitration, + check ackn. from slave */ +/* returns: + * 1 if the device acknowledged + * 0 if the device did not ack + * -ETIMEDOUT if an error occurred (while raising the scl line) + */ +static int i2c_outb(struct i2c_adapter *i2c_adap, unsigned char c) +{ + int i; + int sb; + int ack; + struct i2c_algo_bit_data *adap = i2c_adap->algo_data; + + /* assert: scl is low */ + for (i = 7; i >= 0; i--) { + sb = (c >> i) & 1; + setsda(adap, sb); + udelay((adap->udelay + 1) / 2); + if (sclhi(adap) < 0) { /* timed out */ + bit_dbg(1, &i2c_adap->dev, "i2c_outb: 0x%02x, " + "timeout at bit #%d\n", (int)c, i); + return -ETIMEDOUT; + } + /* FIXME do arbitration here: + * if (sb && !getsda(adap)) -> ouch! Get out of here. + * + * Report a unique code, so higher level code can retry + * the whole (combined) message and *NOT* issue STOP. + */ + scllo(adap); + } + sdahi(adap); + if (sclhi(adap) < 0) { /* timeout */ + bit_dbg(1, &i2c_adap->dev, "i2c_outb: 0x%02x, " + "timeout at ack\n", (int)c); + return -ETIMEDOUT; + } + + /* read ack: SDA should be pulled down by slave, or it may + * NAK (usually to report problems with the data we wrote). + */ + ack = !getsda(adap); /* ack: sda is pulled low -> success */ + bit_dbg(2, &i2c_adap->dev, "i2c_outb: 0x%02x %s\n", (int)c, + ack ? "A" : "NA"); + + scllo(adap); + return ack; + /* assert: scl is low (sda undef) */ +} + + +static int i2c_inb(struct i2c_adapter *i2c_adap) +{ + /* read byte via i2c port, without start/stop sequence */ + /* acknowledge is sent in i2c_read. */ + int i; + unsigned char indata = 0; + struct i2c_algo_bit_data *adap = i2c_adap->algo_data; + + /* assert: scl is low */ + sdahi(adap); + for (i = 0; i < 8; i++) { + if (sclhi(adap) < 0) { /* timeout */ + bit_dbg(1, &i2c_adap->dev, "i2c_inb: timeout at bit " + "#%d\n", 7 - i); + return -ETIMEDOUT; + } + indata *= 2; + if (getsda(adap)) + indata |= 0x01; + setscl(adap, 0); + udelay(i == 7 ? adap->udelay / 2 : adap->udelay); + } + /* assert: scl is low */ + return indata; +} + +/* + * Sanity check for the adapter hardware - check the reaction of + * the bus lines only if it seems to be idle. + */ +static int test_bus(struct i2c_algo_bit_data *adap, char *name) +{ + int scl, sda; + + if (adap->getscl == NULL) + pr_info("%s: Testing SDA only, SCL is not readable\n", name); + + sda = getsda(adap); + scl = (adap->getscl == NULL) ? 1 : getscl(adap); + if (!scl || !sda) { + printk(KERN_WARNING "%s: bus seems to be busy\n", name); + goto bailout; + } + + sdalo(adap); + sda = getsda(adap); + scl = (adap->getscl == NULL) ? 1 : getscl(adap); + if (sda) { + printk(KERN_WARNING "%s: SDA stuck high!\n", name); + goto bailout; + } + if (!scl) { + printk(KERN_WARNING "%s: SCL unexpected low " + "while pulling SDA low!\n", name); + goto bailout; + } + + sdahi(adap); + sda = getsda(adap); + scl = (adap->getscl == NULL) ? 1 : getscl(adap); + if (!sda) { + printk(KERN_WARNING "%s: SDA stuck low!\n", name); + goto bailout; + } + if (!scl) { + printk(KERN_WARNING "%s: SCL unexpected low " + "while pulling SDA high!\n", name); + goto bailout; + } + + scllo(adap); + sda = getsda(adap); + scl = (adap->getscl == NULL) ? 0 : getscl(adap); + if (scl) { + printk(KERN_WARNING "%s: SCL stuck high!\n", name); + goto bailout; + } + if (!sda) { + printk(KERN_WARNING "%s: SDA unexpected low " + "while pulling SCL low!\n", name); + goto bailout; + } + + sclhi(adap); + sda = getsda(adap); + scl = (adap->getscl == NULL) ? 1 : getscl(adap); + if (!scl) { + printk(KERN_WARNING "%s: SCL stuck low!\n", name); + goto bailout; + } + if (!sda) { + printk(KERN_WARNING "%s: SDA unexpected low " + "while pulling SCL high!\n", name); + goto bailout; + } + pr_info("%s: Test OK\n", name); + return 0; +bailout: + sdahi(adap); + sclhi(adap); + return -ENODEV; +} + +/* ----- Utility functions + */ + +/* try_address tries to contact a chip for a number of + * times before it gives up. + * return values: + * 1 chip answered + * 0 chip did not answer + * -x transmission error + */ +static int try_address(struct i2c_adapter *i2c_adap, + unsigned char addr, int retries) +{ + struct i2c_algo_bit_data *adap = i2c_adap->algo_data; + int i, ret = 0; + + for (i = 0; i <= retries; i++) { + ret = i2c_outb(i2c_adap, addr); + if (ret == 1 || i == retries) + break; + bit_dbg(3, &i2c_adap->dev, "emitting stop condition\n"); + i2c_stop(adap); + udelay(adap->udelay); + yield(); + bit_dbg(3, &i2c_adap->dev, "emitting start condition\n"); + i2c_start(adap); + } + if (i && ret) + bit_dbg(1, &i2c_adap->dev, "Used %d tries to %s client at " + "0x%02x: %s\n", i + 1, + addr & 1 ? "read from" : "write to", addr >> 1, + ret == 1 ? "success" : "failed, timeout?"); + return ret; +} + +static int sendbytes(struct i2c_adapter *i2c_adap, struct i2c_msg *msg) +{ + const unsigned char *temp = msg->buf; + int count = msg->len; + unsigned short nak_ok = msg->flags & I2C_M_IGNORE_NAK; + int retval; + int wrcount = 0; + + while (count > 0) { + retval = i2c_outb(i2c_adap, *temp); + + /* OK/ACK; or ignored NAK */ + if ((retval > 0) || (nak_ok && (retval == 0))) { + count--; + temp++; + wrcount++; + + /* A slave NAKing the master means the slave didn't like + * something about the data it saw. For example, maybe + * the SMBus PEC was wrong. + */ + } else if (retval == 0) { + dev_err(&i2c_adap->dev, "sendbytes: NAK bailout.\n"); + return -EIO; + + /* Timeout; or (someday) lost arbitration + * + * FIXME Lost ARB implies retrying the transaction from + * the first message, after the "winning" master issues + * its STOP. As a rule, upper layer code has no reason + * to know or care about this ... it is *NOT* an error. + */ + } else { + dev_err(&i2c_adap->dev, "sendbytes: error %d\n", + retval); + return retval; + } + } + return wrcount; +} + +static int acknak(struct i2c_adapter *i2c_adap, int is_ack) +{ + struct i2c_algo_bit_data *adap = i2c_adap->algo_data; + + /* assert: sda is high */ + if (is_ack) /* send ack */ + setsda(adap, 0); + udelay((adap->udelay + 1) / 2); + if (sclhi(adap) < 0) { /* timeout */ + dev_err(&i2c_adap->dev, "readbytes: ack/nak timeout\n"); + return -ETIMEDOUT; + } + scllo(adap); + return 0; +} + +static int readbytes(struct i2c_adapter *i2c_adap, struct i2c_msg *msg) +{ + int inval; + int rdcount = 0; /* counts bytes read */ + unsigned char *temp = msg->buf; + int count = msg->len; + const unsigned flags = msg->flags; + + while (count > 0) { + inval = i2c_inb(i2c_adap); + if (inval >= 0) { + *temp = inval; + rdcount++; + } else { /* read timed out */ + break; + } + + temp++; + count--; + + /* Some SMBus transactions require that we receive the + transaction length as the first read byte. */ + if (rdcount == 1 && (flags & I2C_M_RECV_LEN)) { + if (inval <= 0 || inval > I2C_SMBUS_BLOCK_MAX) { + if (!(flags & I2C_M_NO_RD_ACK)) + acknak(i2c_adap, 0); + dev_err(&i2c_adap->dev, "readbytes: invalid " + "block length (%d)\n", inval); + return -EREMOTEIO; + } + /* The original count value accounts for the extra + bytes, that is, either 1 for a regular transaction, + or 2 for a PEC transaction. */ + count += inval; + msg->len += inval; + } + + bit_dbg(2, &i2c_adap->dev, "readbytes: 0x%02x %s\n", + inval, + (flags & I2C_M_NO_RD_ACK) + ? "(no ack/nak)" + : (count ? "A" : "NA")); + + if (!(flags & I2C_M_NO_RD_ACK)) { + inval = acknak(i2c_adap, count); + if (inval < 0) + return inval; + } + } + return rdcount; +} + +/* doAddress initiates the transfer by generating the start condition (in + * try_address) and transmits the address in the necessary format to handle + * reads, writes as well as 10bit-addresses. + * returns: + * 0 everything went okay, the chip ack'ed, or IGNORE_NAK flag was set + * -x an error occurred (like: -EREMOTEIO if the device did not answer, or + * -ETIMEDOUT, for example if the lines are stuck...) + */ +static int bit_doAddress(struct i2c_adapter *i2c_adap, struct i2c_msg *msg) +{ + unsigned short flags = msg->flags; + unsigned short nak_ok = msg->flags & I2C_M_IGNORE_NAK; + struct i2c_algo_bit_data *adap = i2c_adap->algo_data; + + unsigned char addr; + int ret, retries; + + retries = nak_ok ? 0 : i2c_adap->retries; + + if (flags & I2C_M_TEN) { + /* a ten bit address */ + addr = 0xf0 | ((msg->addr >> 7) & 0x03); + bit_dbg(2, &i2c_adap->dev, "addr0: %d\n", addr); + /* try extended address code...*/ + ret = try_address(i2c_adap, addr, retries); + if ((ret != 1) && !nak_ok) { + dev_err(&i2c_adap->dev, + "died at extended address code\n"); + return -EREMOTEIO; + } + /* the remaining 8 bit address */ + ret = i2c_outb(i2c_adap, msg->addr & 0x7f); + if ((ret != 1) && !nak_ok) { + /* the chip did not ack / xmission error occurred */ + dev_err(&i2c_adap->dev, "died at 2nd address code\n"); + return -EREMOTEIO; + } + if (flags & I2C_M_RD) { + bit_dbg(3, &i2c_adap->dev, "emitting repeated " + "start condition\n"); + i2c_repstart(adap); + /* okay, now switch into reading mode */ + addr |= 0x01; + ret = try_address(i2c_adap, addr, retries); + if ((ret != 1) && !nak_ok) { + dev_err(&i2c_adap->dev, + "died at repeated address code\n"); + return -EREMOTEIO; + } + } + } else { /* normal 7bit address */ + addr = msg->addr << 1; + if (flags & I2C_M_RD) + addr |= 1; + if (flags & I2C_M_REV_DIR_ADDR) + addr ^= 1; + ret = try_address(i2c_adap, addr, retries); + if ((ret != 1) && !nak_ok) + return -ENXIO; + } + + return 0; +} + +static int bit_xfer(struct i2c_adapter *i2c_adap, + struct i2c_msg msgs[], int num) +{ + struct i2c_msg *pmsg; + struct i2c_algo_bit_data *adap = i2c_adap->algo_data; + int i, ret; + unsigned short nak_ok; + + bit_dbg(3, &i2c_adap->dev, "emitting start condition\n"); + i2c_start(adap); + for (i = 0; i < num; i++) { + pmsg = &msgs[i]; + nak_ok = pmsg->flags & I2C_M_IGNORE_NAK; + if (!(pmsg->flags & I2C_M_NOSTART)) { + if (i) { + bit_dbg(3, &i2c_adap->dev, "emitting " + "repeated start condition\n"); + i2c_repstart(adap); + } + ret = bit_doAddress(i2c_adap, pmsg); + if ((ret != 0) && !nak_ok) { + bit_dbg(1, &i2c_adap->dev, "NAK from " + "device addr 0x%02x msg #%d\n", + msgs[i].addr, i); + goto bailout; + } + } + if (pmsg->flags & I2C_M_RD) { + /* read bytes into buffer*/ + ret = readbytes(i2c_adap, pmsg); + if (ret >= 1) + bit_dbg(2, &i2c_adap->dev, "read %d byte%s\n", + ret, ret == 1 ? "" : "s"); + if (ret < pmsg->len) { + if (ret >= 0) + ret = -EREMOTEIO; + goto bailout; + } + } else { + /* write bytes from buffer */ + ret = sendbytes(i2c_adap, pmsg); + if (ret >= 1) + bit_dbg(2, &i2c_adap->dev, "wrote %d byte%s\n", + ret, ret == 1 ? "" : "s"); + if (ret < pmsg->len) { + if (ret >= 0) + ret = -EREMOTEIO; + goto bailout; + } + } + } + ret = i; + +bailout: + bit_dbg(3, &i2c_adap->dev, "emitting stop condition\n"); + i2c_stop(adap); + return ret; +} + +static u32 bit_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | + I2C_FUNC_SMBUS_READ_BLOCK_DATA | + I2C_FUNC_SMBUS_BLOCK_PROC_CALL | + I2C_FUNC_10BIT_ADDR | I2C_FUNC_PROTOCOL_MANGLING; +} + + +/* -----exported algorithm data: ------------------------------------- */ + +static const struct i2c_algorithm i2c_bit_algo = { + .master_xfer = bit_xfer, + .functionality = bit_func, +}; + +/* + * registering functions to load algorithms at runtime + */ +static int i2c_bit_prepare_bus(struct i2c_adapter *adap) +{ + struct i2c_algo_bit_data *bit_adap = adap->algo_data; + + if (bit_test) { + int ret = test_bus(bit_adap, adap->name); + if (ret < 0) + return -ENODEV; + } + + /* register new adapter to i2c module... */ + adap->algo = &i2c_bit_algo; + + adap->timeout = 100; /* default values, should */ + adap->retries = 3; /* be replaced by defines */ + + return 0; +} + +int i2c_bit_add_bus(struct i2c_adapter *adap) +{ + int err; + + err = i2c_bit_prepare_bus(adap); + if (err) + return err; + + return i2c_add_adapter(adap); +} +EXPORT_SYMBOL(i2c_bit_add_bus); + +int i2c_bit_add_numbered_bus(struct i2c_adapter *adap) +{ + int err; + + err = i2c_bit_prepare_bus(adap); + if (err) + return err; + + return i2c_add_numbered_adapter(adap); +} +EXPORT_SYMBOL(i2c_bit_add_numbered_bus); + +MODULE_AUTHOR("Simon G. Vogl <simon@tk.uni-linz.ac.at>"); +MODULE_DESCRIPTION("I2C-Bus bit-banging algorithm"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/algos/i2c-algo-pca.c b/drivers/i2c/algos/i2c-algo-pca.c new file mode 100644 index 0000000..d50b329 --- /dev/null +++ b/drivers/i2c/algos/i2c-algo-pca.c @@ -0,0 +1,390 @@ +/* + * i2c-algo-pca.c i2c driver algorithms for PCA9564 adapters + * Copyright (C) 2004 Arcom Control Systems + * Copyright (C) 2008 Pengutronix + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-pca.h> + +#define DEB1(fmt, args...) do { if (i2c_debug>=1) printk(fmt, ## args); } while(0) +#define DEB2(fmt, args...) do { if (i2c_debug>=2) printk(fmt, ## args); } while(0) +#define DEB3(fmt, args...) do { if (i2c_debug>=3) printk(fmt, ## args); } while(0) + +static int i2c_debug; + +#define pca_outw(adap, reg, val) adap->write_byte(adap->data, reg, val) +#define pca_inw(adap, reg) adap->read_byte(adap->data, reg) + +#define pca_status(adap) pca_inw(adap, I2C_PCA_STA) +#define pca_clock(adap) adap->i2c_clock +#define pca_set_con(adap, val) pca_outw(adap, I2C_PCA_CON, val) +#define pca_get_con(adap) pca_inw(adap, I2C_PCA_CON) +#define pca_wait(adap) adap->wait_for_completion(adap->data) +#define pca_reset(adap) adap->reset_chip(adap->data) + +/* + * Generate a start condition on the i2c bus. + * + * returns after the start condition has occurred + */ +static void pca_start(struct i2c_algo_pca_data *adap) +{ + int sta = pca_get_con(adap); + DEB2("=== START\n"); + sta |= I2C_PCA_CON_STA; + sta &= ~(I2C_PCA_CON_STO|I2C_PCA_CON_SI); + pca_set_con(adap, sta); + pca_wait(adap); +} + +/* + * Generate a repeated start condition on the i2c bus + * + * return after the repeated start condition has occurred + */ +static void pca_repeated_start(struct i2c_algo_pca_data *adap) +{ + int sta = pca_get_con(adap); + DEB2("=== REPEATED START\n"); + sta |= I2C_PCA_CON_STA; + sta &= ~(I2C_PCA_CON_STO|I2C_PCA_CON_SI); + pca_set_con(adap, sta); + pca_wait(adap); +} + +/* + * Generate a stop condition on the i2c bus + * + * returns after the stop condition has been generated + * + * STOPs do not generate an interrupt or set the SI flag, since the + * part returns the idle state (0xf8). Hence we don't need to + * pca_wait here. + */ +static void pca_stop(struct i2c_algo_pca_data *adap) +{ + int sta = pca_get_con(adap); + DEB2("=== STOP\n"); + sta |= I2C_PCA_CON_STO; + sta &= ~(I2C_PCA_CON_STA|I2C_PCA_CON_SI); + pca_set_con(adap, sta); +} + +/* + * Send the slave address and R/W bit + * + * returns after the address has been sent + */ +static void pca_address(struct i2c_algo_pca_data *adap, + struct i2c_msg *msg) +{ + int sta = pca_get_con(adap); + int addr; + + addr = ( (0x7f & msg->addr) << 1 ); + if (msg->flags & I2C_M_RD ) + addr |= 1; + DEB2("=== SLAVE ADDRESS %#04x+%c=%#04x\n", + msg->addr, msg->flags & I2C_M_RD ? 'R' : 'W', addr); + + pca_outw(adap, I2C_PCA_DAT, addr); + + sta &= ~(I2C_PCA_CON_STO|I2C_PCA_CON_STA|I2C_PCA_CON_SI); + pca_set_con(adap, sta); + + pca_wait(adap); +} + +/* + * Transmit a byte. + * + * Returns after the byte has been transmitted + */ +static void pca_tx_byte(struct i2c_algo_pca_data *adap, + __u8 b) +{ + int sta = pca_get_con(adap); + DEB2("=== WRITE %#04x\n", b); + pca_outw(adap, I2C_PCA_DAT, b); + + sta &= ~(I2C_PCA_CON_STO|I2C_PCA_CON_STA|I2C_PCA_CON_SI); + pca_set_con(adap, sta); + + pca_wait(adap); +} + +/* + * Receive a byte + * + * returns immediately. + */ +static void pca_rx_byte(struct i2c_algo_pca_data *adap, + __u8 *b, int ack) +{ + *b = pca_inw(adap, I2C_PCA_DAT); + DEB2("=== READ %#04x %s\n", *b, ack ? "ACK" : "NACK"); +} + +/* + * Setup ACK or NACK for next received byte and wait for it to arrive. + * + * Returns after next byte has arrived. + */ +static void pca_rx_ack(struct i2c_algo_pca_data *adap, + int ack) +{ + int sta = pca_get_con(adap); + + sta &= ~(I2C_PCA_CON_STO|I2C_PCA_CON_STA|I2C_PCA_CON_SI|I2C_PCA_CON_AA); + + if ( ack ) + sta |= I2C_PCA_CON_AA; + + pca_set_con(adap, sta); + pca_wait(adap); +} + +static int pca_xfer(struct i2c_adapter *i2c_adap, + struct i2c_msg *msgs, + int num) +{ + struct i2c_algo_pca_data *adap = i2c_adap->algo_data; + struct i2c_msg *msg = NULL; + int curmsg; + int numbytes = 0; + int state; + int ret; + int timeout = i2c_adap->timeout; + + while ((state = pca_status(adap)) != 0xf8 && timeout--) { + msleep(10); + } + if (state != 0xf8) { + dev_dbg(&i2c_adap->dev, "bus is not idle. status is %#04x\n", state); + return -EAGAIN; + } + + DEB1("{{{ XFER %d messages\n", num); + + if (i2c_debug>=2) { + for (curmsg = 0; curmsg < num; curmsg++) { + int addr, i; + msg = &msgs[curmsg]; + + addr = (0x7f & msg->addr) ; + + if (msg->flags & I2C_M_RD ) + printk(KERN_INFO " [%02d] RD %d bytes from %#02x [%#02x, ...]\n", + curmsg, msg->len, addr, (addr<<1) | 1); + else { + printk(KERN_INFO " [%02d] WR %d bytes to %#02x [%#02x%s", + curmsg, msg->len, addr, addr<<1, + msg->len == 0 ? "" : ", "); + for(i=0; i < msg->len; i++) + printk("%#04x%s", msg->buf[i], i == msg->len - 1 ? "" : ", "); + printk("]\n"); + } + } + } + + curmsg = 0; + ret = -EREMOTEIO; + while (curmsg < num) { + state = pca_status(adap); + + DEB3("STATE is 0x%02x\n", state); + msg = &msgs[curmsg]; + + switch (state) { + case 0xf8: /* On reset or stop the bus is idle */ + pca_start(adap); + break; + + case 0x08: /* A START condition has been transmitted */ + case 0x10: /* A repeated start condition has been transmitted */ + pca_address(adap, msg); + break; + + case 0x18: /* SLA+W has been transmitted; ACK has been received */ + case 0x28: /* Data byte in I2CDAT has been transmitted; ACK has been received */ + if (numbytes < msg->len) { + pca_tx_byte(adap, msg->buf[numbytes]); + numbytes++; + break; + } + curmsg++; numbytes = 0; + if (curmsg == num) + pca_stop(adap); + else + pca_repeated_start(adap); + break; + + case 0x20: /* SLA+W has been transmitted; NOT ACK has been received */ + DEB2("NOT ACK received after SLA+W\n"); + pca_stop(adap); + goto out; + + case 0x40: /* SLA+R has been transmitted; ACK has been received */ + pca_rx_ack(adap, msg->len > 1); + break; + + case 0x50: /* Data bytes has been received; ACK has been returned */ + if (numbytes < msg->len) { + pca_rx_byte(adap, &msg->buf[numbytes], 1); + numbytes++; + pca_rx_ack(adap, numbytes < msg->len - 1); + break; + } + curmsg++; numbytes = 0; + if (curmsg == num) + pca_stop(adap); + else + pca_repeated_start(adap); + break; + + case 0x48: /* SLA+R has been transmitted; NOT ACK has been received */ + DEB2("NOT ACK received after SLA+R\n"); + pca_stop(adap); + goto out; + + case 0x30: /* Data byte in I2CDAT has been transmitted; NOT ACK has been received */ + DEB2("NOT ACK received after data byte\n"); + goto out; + + case 0x38: /* Arbitration lost during SLA+W, SLA+R or data bytes */ + DEB2("Arbitration lost\n"); + goto out; + + case 0x58: /* Data byte has been received; NOT ACK has been returned */ + if ( numbytes == msg->len - 1 ) { + pca_rx_byte(adap, &msg->buf[numbytes], 0); + curmsg++; numbytes = 0; + if (curmsg == num) + pca_stop(adap); + else + pca_repeated_start(adap); + } else { + DEB2("NOT ACK sent after data byte received. " + "Not final byte. numbytes %d. len %d\n", + numbytes, msg->len); + pca_stop(adap); + goto out; + } + break; + case 0x70: /* Bus error - SDA stuck low */ + DEB2("BUS ERROR - SDA Stuck low\n"); + pca_reset(adap); + goto out; + case 0x90: /* Bus error - SCL stuck low */ + DEB2("BUS ERROR - SCL Stuck low\n"); + pca_reset(adap); + goto out; + case 0x00: /* Bus error during master or slave mode due to illegal START or STOP condition */ + DEB2("BUS ERROR - Illegal START or STOP\n"); + pca_reset(adap); + goto out; + default: + dev_err(&i2c_adap->dev, "unhandled SIO state 0x%02x\n", state); + break; + } + + } + + ret = curmsg; + out: + DEB1(KERN_CRIT "}}} transfered %d/%d messages. " + "status is %#04x. control is %#04x\n", + curmsg, num, pca_status(adap), + pca_get_con(adap)); + return ret; +} + +static u32 pca_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm pca_algo = { + .master_xfer = pca_xfer, + .functionality = pca_func, +}; + +static int pca_init(struct i2c_adapter *adap) +{ + static int freqs[] = {330,288,217,146,88,59,44,36}; + int clock; + struct i2c_algo_pca_data *pca_data = adap->algo_data; + + if (pca_data->i2c_clock > 7) { + printk(KERN_WARNING "%s: Invalid I2C clock speed selected. Trying default.\n", + adap->name); + pca_data->i2c_clock = I2C_PCA_CON_59kHz; + } + + adap->algo = &pca_algo; + + pca_reset(pca_data); + + clock = pca_clock(pca_data); + DEB1(KERN_INFO "%s: Clock frequency is %dkHz\n", adap->name, freqs[clock]); + + pca_set_con(pca_data, I2C_PCA_CON_ENSIO | clock); + udelay(500); /* 500 us for oscilator to stabilise */ + + return 0; +} + +/* + * registering functions to load algorithms at runtime + */ +int i2c_pca_add_bus(struct i2c_adapter *adap) +{ + int rval; + + rval = pca_init(adap); + if (rval) + return rval; + + return i2c_add_adapter(adap); +} +EXPORT_SYMBOL(i2c_pca_add_bus); + +int i2c_pca_add_numbered_bus(struct i2c_adapter *adap) +{ + int rval; + + rval = pca_init(adap); + if (rval) + return rval; + + return i2c_add_numbered_adapter(adap); +} +EXPORT_SYMBOL(i2c_pca_add_numbered_bus); + +MODULE_AUTHOR("Ian Campbell <icampbell@arcom.com>, " + "Wolfram Sang <w.sang@pengutronix.de>"); +MODULE_DESCRIPTION("I2C-Bus PCA9564 algorithm"); +MODULE_LICENSE("GPL"); + +module_param(i2c_debug, int, 0); diff --git a/drivers/i2c/algos/i2c-algo-pcf.c b/drivers/i2c/algos/i2c-algo-pcf.c new file mode 100644 index 0000000..3e01992 --- /dev/null +++ b/drivers/i2c/algos/i2c-algo-pcf.c @@ -0,0 +1,461 @@ +/* ------------------------------------------------------------------------- */ +/* i2c-algo-pcf.c i2c driver algorithms for PCF8584 adapters */ +/* ------------------------------------------------------------------------- */ +/* Copyright (C) 1995-1997 Simon G. Vogl + 1998-2000 Hans Berglund + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +/* ------------------------------------------------------------------------- */ + +/* With some changes from Kyösti Mälkki <kmalkki@cc.hut.fi> and + Frodo Looijaard <frodol@dds.nl> ,and also from Martin Bailey + <mbailey@littlefeet-inc.com> */ + +/* Partially rewriten by Oleg I. Vdovikin <vdovikin@jscc.ru> to handle multiple + messages, proper stop/repstart signaling during receive, + added detect code */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-pcf.h> +#include "i2c-algo-pcf.h" + + +#define DEB2(x) if (i2c_debug>=2) x +#define DEB3(x) if (i2c_debug>=3) x /* print several statistical values*/ +#define DEBPROTO(x) if (i2c_debug>=9) x; + /* debug the protocol by showing transferred bits */ +#define DEF_TIMEOUT 16 + +/* module parameters: + */ +static int i2c_debug; + +/* --- setting states on the bus with the right timing: --------------- */ + +#define set_pcf(adap, ctl, val) adap->setpcf(adap->data, ctl, val) +#define get_pcf(adap, ctl) adap->getpcf(adap->data, ctl) +#define get_own(adap) adap->getown(adap->data) +#define get_clock(adap) adap->getclock(adap->data) +#define i2c_outb(adap, val) adap->setpcf(adap->data, 0, val) +#define i2c_inb(adap) adap->getpcf(adap->data, 0) + +/* --- other auxiliary functions -------------------------------------- */ + +static void i2c_start(struct i2c_algo_pcf_data *adap) +{ + DEBPROTO(printk("S ")); + set_pcf(adap, 1, I2C_PCF_START); +} + +static void i2c_repstart(struct i2c_algo_pcf_data *adap) +{ + DEBPROTO(printk(" Sr ")); + set_pcf(adap, 1, I2C_PCF_REPSTART); +} + + +static void i2c_stop(struct i2c_algo_pcf_data *adap) +{ + DEBPROTO(printk("P\n")); + set_pcf(adap, 1, I2C_PCF_STOP); +} + +static void handle_lab(struct i2c_algo_pcf_data *adap, const int *status) +{ + DEB2(printk(KERN_INFO + "i2c-algo-pcf.o: lost arbitration (CSR 0x%02x)\n", + *status)); + + /* Cleanup from LAB -- reset and enable ESO. + * This resets the PCF8584; since we've lost the bus, no + * further attempts should be made by callers to clean up + * (no i2c_stop() etc.) + */ + set_pcf(adap, 1, I2C_PCF_PIN); + set_pcf(adap, 1, I2C_PCF_ESO); + + /* We pause for a time period sufficient for any running + * I2C transaction to complete -- the arbitration logic won't + * work properly until the next START is seen. + * It is assumed the bus driver or client has set a proper value. + * + * REVISIT: should probably use msleep instead of mdelay if we + * know we can sleep. + */ + if (adap->lab_mdelay) + mdelay(adap->lab_mdelay); + + DEB2(printk(KERN_INFO + "i2c-algo-pcf.o: reset LAB condition (CSR 0x%02x)\n", + get_pcf(adap, 1))); +} + +static int wait_for_bb(struct i2c_algo_pcf_data *adap) { + + int timeout = DEF_TIMEOUT; + int status; + + status = get_pcf(adap, 1); +#ifndef STUB_I2C + while (timeout-- && !(status & I2C_PCF_BB)) { + udelay(100); /* wait for 100 us */ + status = get_pcf(adap, 1); + } +#endif + if (timeout <= 0) { + printk(KERN_ERR "Timeout waiting for Bus Busy\n"); + } + + return (timeout<=0); +} + + +static int wait_for_pin(struct i2c_algo_pcf_data *adap, int *status) { + + int timeout = DEF_TIMEOUT; + + *status = get_pcf(adap, 1); +#ifndef STUB_I2C + while (timeout-- && (*status & I2C_PCF_PIN)) { + adap->waitforpin(adap->data); + *status = get_pcf(adap, 1); + } + if (*status & I2C_PCF_LAB) { + handle_lab(adap, status); + return(-EINTR); + } +#endif + if (timeout <= 0) + return(-1); + else + return(0); +} + +/* + * This should perform the 'PCF8584 initialization sequence' as described + * in the Philips IC12 data book (1995, Aug 29). + * There should be a 30 clock cycle wait after reset, I assume this + * has been fulfilled. + * There should be a delay at the end equal to the longest I2C message + * to synchronize the BB-bit (in multimaster systems). How long is + * this? I assume 1 second is always long enough. + * + * vdovikin: added detect code for PCF8584 + */ +static int pcf_init_8584 (struct i2c_algo_pcf_data *adap) +{ + unsigned char temp; + + DEB3(printk(KERN_DEBUG "i2c-algo-pcf.o: PCF state 0x%02x\n", get_pcf(adap, 1))); + + /* S1=0x80: S0 selected, serial interface off */ + set_pcf(adap, 1, I2C_PCF_PIN); + /* check to see S1 now used as R/W ctrl - + PCF8584 does that when ESO is zero */ + if (((temp = get_pcf(adap, 1)) & 0x7f) != (0)) { + DEB2(printk(KERN_ERR "i2c-algo-pcf.o: PCF detection failed -- can't select S0 (0x%02x).\n", temp)); + return -ENXIO; /* definetly not PCF8584 */ + } + + /* load own address in S0, effective address is (own << 1) */ + i2c_outb(adap, get_own(adap)); + /* check it's really written */ + if ((temp = i2c_inb(adap)) != get_own(adap)) { + DEB2(printk(KERN_ERR "i2c-algo-pcf.o: PCF detection failed -- can't set S0 (0x%02x).\n", temp)); + return -ENXIO; + } + + /* S1=0xA0, next byte in S2 */ + set_pcf(adap, 1, I2C_PCF_PIN | I2C_PCF_ES1); + /* check to see S2 now selected */ + if (((temp = get_pcf(adap, 1)) & 0x7f) != I2C_PCF_ES1) { + DEB2(printk(KERN_ERR "i2c-algo-pcf.o: PCF detection failed -- can't select S2 (0x%02x).\n", temp)); + return -ENXIO; + } + + /* load clock register S2 */ + i2c_outb(adap, get_clock(adap)); + /* check it's really written, the only 5 lowest bits does matter */ + if (((temp = i2c_inb(adap)) & 0x1f) != get_clock(adap)) { + DEB2(printk(KERN_ERR "i2c-algo-pcf.o: PCF detection failed -- can't set S2 (0x%02x).\n", temp)); + return -ENXIO; + } + + /* Enable serial interface, idle, S0 selected */ + set_pcf(adap, 1, I2C_PCF_IDLE); + + /* check to see PCF is really idled and we can access status register */ + if ((temp = get_pcf(adap, 1)) != (I2C_PCF_PIN | I2C_PCF_BB)) { + DEB2(printk(KERN_ERR "i2c-algo-pcf.o: PCF detection failed -- can't select S1` (0x%02x).\n", temp)); + return -ENXIO; + } + + printk(KERN_DEBUG "i2c-algo-pcf.o: detected and initialized PCF8584.\n"); + + return 0; +} + + +/* ----- Utility functions + */ + +static int pcf_sendbytes(struct i2c_adapter *i2c_adap, const char *buf, + int count, int last) +{ + struct i2c_algo_pcf_data *adap = i2c_adap->algo_data; + int wrcount, status, timeout; + + for (wrcount=0; wrcount<count; ++wrcount) { + DEB2(dev_dbg(&i2c_adap->dev, "i2c_write: writing %2.2X\n", + buf[wrcount]&0xff)); + i2c_outb(adap, buf[wrcount]); + timeout = wait_for_pin(adap, &status); + if (timeout) { + if (timeout == -EINTR) { + /* arbitration lost */ + return -EINTR; + } + i2c_stop(adap); + dev_err(&i2c_adap->dev, "i2c_write: error - timeout.\n"); + return -EREMOTEIO; /* got a better one ?? */ + } +#ifndef STUB_I2C + if (status & I2C_PCF_LRB) { + i2c_stop(adap); + dev_err(&i2c_adap->dev, "i2c_write: error - no ack.\n"); + return -EREMOTEIO; /* got a better one ?? */ + } +#endif + } + if (last) { + i2c_stop(adap); + } + else { + i2c_repstart(adap); + } + + return (wrcount); +} + + +static int pcf_readbytes(struct i2c_adapter *i2c_adap, char *buf, + int count, int last) +{ + int i, status; + struct i2c_algo_pcf_data *adap = i2c_adap->algo_data; + int wfp; + + /* increment number of bytes to read by one -- read dummy byte */ + for (i = 0; i <= count; i++) { + + if ((wfp = wait_for_pin(adap, &status))) { + if (wfp == -EINTR) { + /* arbitration lost */ + return -EINTR; + } + i2c_stop(adap); + dev_err(&i2c_adap->dev, "pcf_readbytes timed out.\n"); + return (-1); + } + +#ifndef STUB_I2C + if ((status & I2C_PCF_LRB) && (i != count)) { + i2c_stop(adap); + dev_err(&i2c_adap->dev, "i2c_read: i2c_inb, No ack.\n"); + return (-1); + } +#endif + + if (i == count - 1) { + set_pcf(adap, 1, I2C_PCF_ESO); + } else + if (i == count) { + if (last) { + i2c_stop(adap); + } else { + i2c_repstart(adap); + } + }; + + if (i) { + buf[i - 1] = i2c_inb(adap); + } else { + i2c_inb(adap); /* dummy read */ + } + } + + return (i - 1); +} + + +static int pcf_doAddress(struct i2c_algo_pcf_data *adap, + struct i2c_msg *msg) +{ + unsigned short flags = msg->flags; + unsigned char addr; + + addr = msg->addr << 1; + if (flags & I2C_M_RD) + addr |= 1; + if (flags & I2C_M_REV_DIR_ADDR) + addr ^= 1; + i2c_outb(adap, addr); + + return 0; +} + +static int pcf_xfer(struct i2c_adapter *i2c_adap, + struct i2c_msg *msgs, + int num) +{ + struct i2c_algo_pcf_data *adap = i2c_adap->algo_data; + struct i2c_msg *pmsg; + int i; + int ret=0, timeout, status; + + if (adap->xfer_begin) + adap->xfer_begin(adap->data); + + /* Check for bus busy */ + timeout = wait_for_bb(adap); + if (timeout) { + DEB2(printk(KERN_ERR "i2c-algo-pcf.o: " + "Timeout waiting for BB in pcf_xfer\n");) + i = -EIO; + goto out; + } + + for (i = 0;ret >= 0 && i < num; i++) { + pmsg = &msgs[i]; + + DEB2(printk(KERN_DEBUG "i2c-algo-pcf.o: Doing %s %d bytes to 0x%02x - %d of %d messages\n", + pmsg->flags & I2C_M_RD ? "read" : "write", + pmsg->len, pmsg->addr, i + 1, num);) + + ret = pcf_doAddress(adap, pmsg); + + /* Send START */ + if (i == 0) { + i2c_start(adap); + } + + /* Wait for PIN (pending interrupt NOT) */ + timeout = wait_for_pin(adap, &status); + if (timeout) { + if (timeout == -EINTR) { + /* arbitration lost */ + i = -EINTR; + goto out; + } + i2c_stop(adap); + DEB2(printk(KERN_ERR "i2c-algo-pcf.o: Timeout waiting " + "for PIN(1) in pcf_xfer\n");) + i = -EREMOTEIO; + goto out; + } + +#ifndef STUB_I2C + /* Check LRB (last rcvd bit - slave ack) */ + if (status & I2C_PCF_LRB) { + i2c_stop(adap); + DEB2(printk(KERN_ERR "i2c-algo-pcf.o: No LRB(1) in pcf_xfer\n");) + i = -EREMOTEIO; + goto out; + } +#endif + + DEB3(printk(KERN_DEBUG "i2c-algo-pcf.o: Msg %d, addr=0x%x, flags=0x%x, len=%d\n", + i, msgs[i].addr, msgs[i].flags, msgs[i].len);) + + /* Read */ + if (pmsg->flags & I2C_M_RD) { + /* read bytes into buffer*/ + ret = pcf_readbytes(i2c_adap, pmsg->buf, pmsg->len, + (i + 1 == num)); + + if (ret != pmsg->len) { + DEB2(printk(KERN_DEBUG "i2c-algo-pcf.o: fail: " + "only read %d bytes.\n",ret)); + } else { + DEB2(printk(KERN_DEBUG "i2c-algo-pcf.o: read %d bytes.\n",ret)); + } + } else { /* Write */ + ret = pcf_sendbytes(i2c_adap, pmsg->buf, pmsg->len, + (i + 1 == num)); + + if (ret != pmsg->len) { + DEB2(printk(KERN_DEBUG "i2c-algo-pcf.o: fail: " + "only wrote %d bytes.\n",ret)); + } else { + DEB2(printk(KERN_DEBUG "i2c-algo-pcf.o: wrote %d bytes.\n",ret)); + } + } + } + +out: + if (adap->xfer_end) + adap->xfer_end(adap->data); + return (i); +} + +static u32 pcf_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | + I2C_FUNC_PROTOCOL_MANGLING; +} + +/* -----exported algorithm data: ------------------------------------- */ + +static const struct i2c_algorithm pcf_algo = { + .master_xfer = pcf_xfer, + .functionality = pcf_func, +}; + +/* + * registering functions to load algorithms at runtime + */ +int i2c_pcf_add_bus(struct i2c_adapter *adap) +{ + struct i2c_algo_pcf_data *pcf_adap = adap->algo_data; + int rval; + + DEB2(dev_dbg(&adap->dev, "hw routines registered.\n")); + + /* register new adapter to i2c module... */ + adap->algo = &pcf_algo; + adap->timeout = 100; + + if ((rval = pcf_init_8584(pcf_adap))) + return rval; + + rval = i2c_add_adapter(adap); + + return rval; +} +EXPORT_SYMBOL(i2c_pcf_add_bus); + +MODULE_AUTHOR("Hans Berglund <hb@spacetec.no>"); +MODULE_DESCRIPTION("I2C-Bus PCF8584 algorithm"); +MODULE_LICENSE("GPL"); + +module_param(i2c_debug, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(i2c_debug, + "debug level - 0 off; 1 normal; 2,3 more verbose; 9 pcf-protocol"); diff --git a/drivers/i2c/algos/i2c-algo-pcf.h b/drivers/i2c/algos/i2c-algo-pcf.h new file mode 100644 index 0000000..5263a9e --- /dev/null +++ b/drivers/i2c/algos/i2c-algo-pcf.h @@ -0,0 +1,76 @@ +/* -------------------------------------------------------------------- */ +/* i2c-pcf8584.h: PCF 8584 global defines */ +/* -------------------------------------------------------------------- */ +/* Copyright (C) 1996 Simon G. Vogl + 1999 Hans Berglund + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +/* -------------------------------------------------------------------- */ + +/* With some changes from Frodo Looijaard <frodol@dds.nl> */ + +#ifndef I2C_PCF8584_H +#define I2C_PCF8584_H 1 + +/* ----- Control register bits ---------------------------------------- */ +#define I2C_PCF_PIN 0x80 +#define I2C_PCF_ESO 0x40 +#define I2C_PCF_ES1 0x20 +#define I2C_PCF_ES2 0x10 +#define I2C_PCF_ENI 0x08 +#define I2C_PCF_STA 0x04 +#define I2C_PCF_STO 0x02 +#define I2C_PCF_ACK 0x01 + +#define I2C_PCF_START (I2C_PCF_PIN | I2C_PCF_ESO | I2C_PCF_STA | I2C_PCF_ACK) +#define I2C_PCF_STOP (I2C_PCF_PIN | I2C_PCF_ESO | I2C_PCF_STO | I2C_PCF_ACK) +#define I2C_PCF_REPSTART ( I2C_PCF_ESO | I2C_PCF_STA | I2C_PCF_ACK) +#define I2C_PCF_IDLE (I2C_PCF_PIN | I2C_PCF_ESO | I2C_PCF_ACK) + +/* ----- Status register bits ----------------------------------------- */ +/*#define I2C_PCF_PIN 0x80 as above*/ + +#define I2C_PCF_INI 0x40 /* 1 if not initialized */ +#define I2C_PCF_STS 0x20 +#define I2C_PCF_BER 0x10 +#define I2C_PCF_AD0 0x08 +#define I2C_PCF_LRB 0x08 +#define I2C_PCF_AAS 0x04 +#define I2C_PCF_LAB 0x02 +#define I2C_PCF_BB 0x01 + +/* ----- Chip clock frequencies --------------------------------------- */ +#define I2C_PCF_CLK3 0x00 +#define I2C_PCF_CLK443 0x10 +#define I2C_PCF_CLK6 0x14 +#define I2C_PCF_CLK 0x18 +#define I2C_PCF_CLK12 0x1c + +/* ----- transmission frequencies ------------------------------------- */ +#define I2C_PCF_TRNS90 0x00 /* 90 kHz */ +#define I2C_PCF_TRNS45 0x01 /* 45 kHz */ +#define I2C_PCF_TRNS11 0x02 /* 11 kHz */ +#define I2C_PCF_TRNS15 0x03 /* 1.5 kHz */ + + +/* ----- Access to internal registers according to ES1,ES2 ------------ */ +/* they are mapped to the data port ( a0 = 0 ) */ +/* available when ESO == 0 : */ + +#define I2C_PCF_OWNADR 0 +#define I2C_PCF_INTREG I2C_PCF_ES2 +#define I2C_PCF_CLKREG I2C_PCF_ES1 + +#endif /* I2C_PCF8584_H */ diff --git a/drivers/i2c/algos/i2c-algo-sgi.c b/drivers/i2c/algos/i2c-algo-sgi.c new file mode 100644 index 0000000..6eaf145 --- /dev/null +++ b/drivers/i2c/algos/i2c-algo-sgi.c @@ -0,0 +1,179 @@ +/* + * i2c-algo-sgi.c: i2c driver algorithm used by the VINO (SGI Indy) and + * MACE (SGI O2) chips. + * + * This file is subject to the terms and conditions of the GNU General Public + * License version 2 as published by the Free Software Foundation. + * + * Copyright (C) 2003 Ladislav Michl <ladis@linux-mips.org> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/delay.h> + +#include <linux/i2c.h> +#include <linux/i2c-algo-sgi.h> + + +#define SGI_I2C_FORCE_IDLE (0 << 0) +#define SGI_I2C_NOT_IDLE (1 << 0) +#define SGI_I2C_WRITE (0 << 1) +#define SGI_I2C_READ (1 << 1) +#define SGI_I2C_RELEASE_BUS (0 << 2) +#define SGI_I2C_HOLD_BUS (1 << 2) +#define SGI_I2C_XFER_DONE (0 << 4) +#define SGI_I2C_XFER_BUSY (1 << 4) +#define SGI_I2C_ACK (0 << 5) +#define SGI_I2C_NACK (1 << 5) +#define SGI_I2C_BUS_OK (0 << 7) +#define SGI_I2C_BUS_ERR (1 << 7) + +#define get_control() adap->getctrl(adap->data) +#define set_control(val) adap->setctrl(adap->data, val) +#define read_data() adap->rdata(adap->data) +#define write_data(val) adap->wdata(adap->data, val) + + +static int wait_xfer_done(struct i2c_algo_sgi_data *adap) +{ + int i; + + for (i = 0; i < adap->xfer_timeout; i++) { + if ((get_control() & SGI_I2C_XFER_BUSY) == 0) + return 0; + udelay(1); + } + + return -ETIMEDOUT; +} + +static int wait_ack(struct i2c_algo_sgi_data *adap) +{ + int i; + + if (wait_xfer_done(adap)) + return -ETIMEDOUT; + for (i = 0; i < adap->ack_timeout; i++) { + if ((get_control() & SGI_I2C_NACK) == 0) + return 0; + udelay(1); + } + + return -ETIMEDOUT; +} + +static int force_idle(struct i2c_algo_sgi_data *adap) +{ + int i; + + set_control(SGI_I2C_FORCE_IDLE); + for (i = 0; i < adap->xfer_timeout; i++) { + if ((get_control() & SGI_I2C_NOT_IDLE) == 0) + goto out; + udelay(1); + } + return -ETIMEDOUT; +out: + if (get_control() & SGI_I2C_BUS_ERR) + return -EIO; + return 0; +} + +static int do_address(struct i2c_algo_sgi_data *adap, unsigned int addr, + int rd) +{ + if (rd) + set_control(SGI_I2C_NOT_IDLE); + /* Check if bus is idle, eventually force it to do so */ + if (get_control() & SGI_I2C_NOT_IDLE) + if (force_idle(adap)) + return -EIO; + /* Write out the i2c chip address and specify operation */ + set_control(SGI_I2C_HOLD_BUS | SGI_I2C_WRITE | SGI_I2C_NOT_IDLE); + if (rd) + addr |= 1; + write_data(addr); + if (wait_ack(adap)) + return -EIO; + return 0; +} + +static int i2c_read(struct i2c_algo_sgi_data *adap, unsigned char *buf, + unsigned int len) +{ + int i; + + set_control(SGI_I2C_HOLD_BUS | SGI_I2C_READ | SGI_I2C_NOT_IDLE); + for (i = 0; i < len; i++) { + if (wait_xfer_done(adap)) + return -EIO; + buf[i] = read_data(); + } + set_control(SGI_I2C_RELEASE_BUS | SGI_I2C_FORCE_IDLE); + + return 0; + +} + +static int i2c_write(struct i2c_algo_sgi_data *adap, unsigned char *buf, + unsigned int len) +{ + int i; + + /* We are already in write state */ + for (i = 0; i < len; i++) { + write_data(buf[i]); + if (wait_ack(adap)) + return -EIO; + } + return 0; +} + +static int sgi_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg *msgs, + int num) +{ + struct i2c_algo_sgi_data *adap = i2c_adap->algo_data; + struct i2c_msg *p; + int i, err = 0; + + for (i = 0; !err && i < num; i++) { + p = &msgs[i]; + err = do_address(adap, p->addr, p->flags & I2C_M_RD); + if (err || !p->len) + continue; + if (p->flags & I2C_M_RD) + err = i2c_read(adap, p->buf, p->len); + else + err = i2c_write(adap, p->buf, p->len); + } + + return (err < 0) ? err : i; +} + +static u32 sgi_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm sgi_algo = { + .master_xfer = sgi_xfer, + .functionality = sgi_func, +}; + +/* + * registering functions to load algorithms at runtime + */ +int i2c_sgi_add_bus(struct i2c_adapter *adap) +{ + adap->algo = &sgi_algo; + + return i2c_add_adapter(adap); +} +EXPORT_SYMBOL(i2c_sgi_add_bus); + +MODULE_AUTHOR("Ladislav Michl <ladis@linux-mips.org>"); +MODULE_DESCRIPTION("I2C-Bus SGI algorithm"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig new file mode 100644 index 0000000..2ed0928 --- /dev/null +++ b/drivers/i2c/busses/Kconfig @@ -0,0 +1,763 @@ +# +# Sensor device configuration +# + +menu "I2C Hardware Bus support" + +comment "PC SMBus host controller drivers" + depends on PCI + +config I2C_ALI1535 + tristate "ALI 1535" + depends on PCI + help + If you say yes to this option, support will be included for the SMB + Host controller on Acer Labs Inc. (ALI) M1535 South Bridges. The SMB + controller is part of the 7101 device, which is an ACPI-compliant + Power Management Unit (PMU). + + This driver can also be built as a module. If so, the module + will be called i2c-ali1535. + +config I2C_ALI1563 + tristate "ALI 1563" + depends on PCI && EXPERIMENTAL + help + If you say yes to this option, support will be included for the SMB + Host controller on Acer Labs Inc. (ALI) M1563 South Bridges. The SMB + controller is part of the 7101 device, which is an ACPI-compliant + Power Management Unit (PMU). + + This driver can also be built as a module. If so, the module + will be called i2c-ali1563. + +config I2C_ALI15X3 + tristate "ALI 15x3" + depends on PCI + help + If you say yes to this option, support will be included for the + Acer Labs Inc. (ALI) M1514 and M1543 motherboard I2C interfaces. + + This driver can also be built as a module. If so, the module + will be called i2c-ali15x3. + +config I2C_AMD756 + tristate "AMD 756/766/768/8111 and nVidia nForce" + depends on PCI + help + If you say yes to this option, support will be included for the AMD + 756/766/768 mainboard I2C interfaces. The driver also includes + support for the first (SMBus 1.0) I2C interface of the AMD 8111 and + the nVidia nForce I2C interface. + + This driver can also be built as a module. If so, the module + will be called i2c-amd756. + +config I2C_AMD756_S4882 + tristate "SMBus multiplexing on the Tyan S4882" + depends on I2C_AMD756 && X86 && EXPERIMENTAL + help + Enabling this option will add specific SMBus support for the Tyan + S4882 motherboard. On this 4-CPU board, the SMBus is multiplexed + over 8 different channels, where the various memory module EEPROMs + and temperature sensors live. Saying yes here will give you access + to these in addition to the trunk. + + This driver can also be built as a module. If so, the module + will be called i2c-amd756-s4882. + +config I2C_AMD8111 + tristate "AMD 8111" + depends on PCI + help + If you say yes to this option, support will be included for the + second (SMBus 2.0) AMD 8111 mainboard I2C interface. + + This driver can also be built as a module. If so, the module + will be called i2c-amd8111. + +config I2C_I801 + tristate "Intel 82801 (ICH)" + depends on PCI + help + If you say yes to this option, support will be included for the Intel + 801 family of mainboard I2C interfaces. Specifically, the following + versions of the chipset are supported: + 82801AA + 82801AB + 82801BA + 82801CA/CAM + 82801DB + 82801EB/ER (ICH5/ICH5R) + 6300ESB + ICH6 + ICH7 + ESB2 + ICH8 + ICH9 + Tolapai + ICH10 + PCH + + This driver can also be built as a module. If so, the module + will be called i2c-i801. + +config I2C_ISCH + tristate "Intel SCH SMBus 1.0" + depends on PCI + help + Say Y here if you want to use SMBus controller on the Intel SCH + based systems. + + This driver can also be built as a module. If so, the module + will be called i2c-isch. + +config I2C_PIIX4 + tristate "Intel PIIX4 and compatible (ATI/Serverworks/Broadcom/SMSC)" + depends on PCI + help + If you say yes to this option, support will be included for the Intel + PIIX4 family of mainboard I2C interfaces. Specifically, the following + versions of the chipset are supported (note that Serverworks is part + of Broadcom): + Intel PIIX4 + Intel 440MX + ATI IXP200 + ATI IXP300 + ATI IXP400 + ATI SB600 + ATI SB700 + ATI SB800 + Serverworks OSB4 + Serverworks CSB5 + Serverworks CSB6 + Serverworks HT-1000 + SMSC Victory66 + + This driver can also be built as a module. If so, the module + will be called i2c-piix4. + +config I2C_NFORCE2 + tristate "Nvidia nForce2, nForce3 and nForce4" + depends on PCI + help + If you say yes to this option, support will be included for the Nvidia + nForce2, nForce3 and nForce4 families of mainboard I2C interfaces. + + This driver can also be built as a module. If so, the module + will be called i2c-nforce2. + +config I2C_NFORCE2_S4985 + tristate "SMBus multiplexing on the Tyan S4985" + depends on I2C_NFORCE2 && X86 && EXPERIMENTAL + help + Enabling this option will add specific SMBus support for the Tyan + S4985 motherboard. On this 4-CPU board, the SMBus is multiplexed + over 4 different channels, where the various memory module EEPROMs + live. Saying yes here will give you access to these in addition + to the trunk. + + This driver can also be built as a module. If so, the module + will be called i2c-nforce2-s4985. + +config I2C_SIS5595 + tristate "SiS 5595" + depends on PCI + help + If you say yes to this option, support will be included for the + SiS5595 SMBus (a subset of I2C) interface. + + This driver can also be built as a module. If so, the module + will be called i2c-sis5595. + +config I2C_SIS630 + tristate "SiS 630/730" + depends on PCI + help + If you say yes to this option, support will be included for the + SiS630 and SiS730 SMBus (a subset of I2C) interface. + + This driver can also be built as a module. If so, the module + will be called i2c-sis630. + +config I2C_SIS96X + tristate "SiS 96x" + depends on PCI + help + If you say yes to this option, support will be included for the SiS + 96x SMBus (a subset of I2C) interfaces. Specifically, the following + chipsets are supported: + 645/961 + 645DX/961 + 645DX/962 + 648/961 + 650/961 + 735 + 745 + + This driver can also be built as a module. If so, the module + will be called i2c-sis96x. + +config I2C_VIA + tristate "VIA VT82C586B" + depends on PCI && EXPERIMENTAL + select I2C_ALGOBIT + help + If you say yes to this option, support will be included for the VIA + 82C586B I2C interface + + This driver can also be built as a module. If so, the module + will be called i2c-via. + +config I2C_VIAPRO + tristate "VIA VT82C596/82C686/82xx and CX700/VX800/VX820" + depends on PCI + help + If you say yes to this option, support will be included for the VIA + VT82C596 and later SMBus interface. Specifically, the following + chipsets are supported: + VT82C596A/B + VT82C686A/B + VT8231 + VT8233/A + VT8235 + VT8237R/A/S + VT8251 + CX700 + VX800 + VX820 + + This driver can also be built as a module. If so, the module + will be called i2c-viapro. + +comment "Mac SMBus host controller drivers" + depends on PPC_CHRP || PPC_PMAC + +config I2C_HYDRA + tristate "CHRP Apple Hydra Mac I/O I2C interface" + depends on PCI && PPC_CHRP && EXPERIMENTAL + select I2C_ALGOBIT + help + This supports the use of the I2C interface in the Apple Hydra Mac + I/O chip on some CHRP machines (e.g. the LongTrail). Say Y if you + have such a machine. + + This support is also available as a module. If so, the module + will be called i2c-hydra. + +config I2C_POWERMAC + tristate "Powermac I2C interface" + depends on PPC_PMAC + default y + help + This exposes the various PowerMac i2c interfaces to the linux i2c + layer and to userland. It is used by various drivers on the PowerMac + platform, and should generally be enabled. + + This support is also available as a module. If so, the module + will be called i2c-powermac. + +comment "I2C system bus drivers (mostly embedded / system-on-chip)" + +config I2C_AT91 + tristate "Atmel AT91 I2C Two-Wire interface (TWI)" + depends on ARCH_AT91 && EXPERIMENTAL && BROKEN + help + This supports the use of the I2C interface on Atmel AT91 + processors. + + This driver is BROKEN because the controller which it uses + will easily trigger RX overrun and TX underrun errors. Using + low I2C clock rates may partially work around those issues + on some systems. Another serious problem is that there is no + documented way to issue repeated START conditions, as needed + to support combined I2C messages. Use the i2c-gpio driver + unless your system can cope with those limitations. + +config I2C_AU1550 + tristate "Au1550/Au1200 SMBus interface" + depends on SOC_AU1550 || SOC_AU1200 + help + If you say yes to this option, support will be included for the + Au1550 and Au1200 SMBus interface. + + This driver can also be built as a module. If so, the module + will be called i2c-au1550. + +config I2C_BLACKFIN_TWI + tristate "Blackfin TWI I2C support" + depends on BLACKFIN + depends on !BF561 && !BF531 && !BF532 && !BF533 + help + This is the I2C bus driver for Blackfin on-chip TWI interface. + + This driver can also be built as a module. If so, the module + will be called i2c-bfin-twi. + +config I2C_BLACKFIN_TWI_CLK_KHZ + int "Blackfin TWI I2C clock (kHz)" + depends on I2C_BLACKFIN_TWI + range 10 400 + default 50 + help + The unit of the TWI clock is kHz. + +config I2C_CPM + tristate "Freescale CPM1 or CPM2 (MPC8xx/826x)" + depends on (CPM1 || CPM2) && OF_I2C + help + This supports the use of the I2C interface on Freescale + processors with CPM1 or CPM2. + + This driver can also be built as a module. If so, the module + will be called i2c-cpm. + +config I2C_DAVINCI + tristate "DaVinci I2C driver" + depends on ARCH_DAVINCI + help + Support for TI DaVinci I2C controller driver. + + This driver can also be built as a module. If so, the module + will be called i2c-davinci. + + Please note that this driver might be needed to bring up other + devices such as DaVinci NIC. + For details please see http://www.ti.com/davinci + +config I2C_GPIO + tristate "GPIO-based bitbanging I2C" + depends on GENERIC_GPIO + select I2C_ALGOBIT + help + This is a very simple bitbanging I2C driver utilizing the + arch-neutral GPIO API to control the SCL and SDA lines. + +config I2C_HIGHLANDER + tristate "Highlander FPGA SMBus interface" + depends on SH_HIGHLANDER + help + If you say yes to this option, support will be included for + the SMBus interface located in the FPGA on various Highlander + boards, particularly the R0P7780LC0011RL and R0P7785LC0011RL + FPGAs. This is wholly unrelated to the SoC I2C. + + This driver can also be built as a module. If so, the module + will be called i2c-highlander. + +config I2C_IBM_IIC + tristate "IBM PPC 4xx on-chip I2C interface" + depends on 4xx + help + Say Y here if you want to use IIC peripheral found on + embedded IBM PPC 4xx based systems. + + This driver can also be built as a module. If so, the module + will be called i2c-ibm_iic. + +config I2C_IOP3XX + tristate "Intel IOPx3xx and IXP4xx on-chip I2C interface" + depends on ARCH_IOP32X || ARCH_IOP33X || ARCH_IXP4XX || ARCH_IOP13XX + help + Say Y here if you want to use the IIC bus controller on + the Intel IOPx3xx I/O Processors or IXP4xx Network Processors. + + This driver can also be built as a module. If so, the module + will be called i2c-iop3xx. + +config I2C_IXP2000 + tristate "IXP2000 GPIO-Based I2C Interface (DEPRECATED)" + depends on ARCH_IXP2000 + select I2C_ALGOBIT + help + Say Y here if you have an Intel IXP2000 (2400, 2800, 2850) based + system and are using GPIO lines for an I2C bus. + + This support is also available as a module. If so, the module + will be called i2c-ixp2000. + + This driver is deprecated and will be dropped soon. Use i2c-gpio + instead. + +config I2C_MPC + tristate "MPC107/824x/85xx/52xx/86xx" + depends on PPC32 + help + If you say yes to this option, support will be included for the + built-in I2C interface on the MPC107/Tsi107/MPC8240/MPC8245 and + MPC85xx/MPC8641 family processors. The driver may also work on 52xx + family processors, though interrupts are known not to work. + + This driver can also be built as a module. If so, the module + will be called i2c-mpc. + +config I2C_MV64XXX + tristate "Marvell mv64xxx I2C Controller" + depends on (MV64X60 || PLAT_ORION) && EXPERIMENTAL + help + If you say yes to this option, support will be included for the + built-in I2C interface on the Marvell 64xxx line of host bridges. + + This driver can also be built as a module. If so, the module + will be called i2c-mv64xxx. + +config I2C_OCORES + tristate "OpenCores I2C Controller" + depends on EXPERIMENTAL + help + If you say yes to this option, support will be included for the + OpenCores I2C controller. For details see + http://www.opencores.org/projects.cgi/web/i2c/overview + + This driver can also be built as a module. If so, the module + will be called i2c-ocores. + +config I2C_OMAP + tristate "OMAP I2C adapter" + depends on ARCH_OMAP + default y if MACH_OMAP_H3 || MACH_OMAP_OSK + help + If you say yes to this option, support will be included for the + I2C interface on the Texas Instruments OMAP1/2 family of processors. + Like OMAP1510/1610/1710/5912 and OMAP242x. + For details see http://www.ti.com/omap. + +config I2C_PASEMI + tristate "PA Semi SMBus interface" + depends on PPC_PASEMI && PCI + help + Supports the PA Semi PWRficient on-chip SMBus interfaces. + +config I2C_PNX + tristate "I2C bus support for Philips PNX targets" + depends on ARCH_PNX4008 + help + This driver supports the Philips IP3204 I2C IP block master and/or + slave controller + + This driver can also be built as a module. If so, the module + will be called i2c-pnx. + +config I2C_PXA + tristate "Intel PXA2XX I2C adapter (EXPERIMENTAL)" + depends on EXPERIMENTAL && ARCH_PXA + help + If you have devices in the PXA I2C bus, say yes to this option. + This driver can also be built as a module. If so, the module + will be called i2c-pxa. + +config I2C_PXA_SLAVE + bool "Intel PXA2XX I2C Slave comms support" + depends on I2C_PXA + help + Support I2C slave mode communications on the PXA I2C bus. This + is necessary for systems where the PXA may be a target on the + I2C bus. + +config I2C_AST + tristate "ASPEED AST I2C adapter " +# depends on ARCH_ASPEED + help + If you have devices in the AST I2C bus, say yes to this option. + This driver can also be built as a module. If so, the module + will be called i2c-ast. + +config I2C_AST1070 + tristate "ASPEED AST1070 I2C adapter " + depends on ARCH_AST1070 + help + If you have devices in the AST1070 I2C bus, say yes to this option. + This driver can also be built as a module. If so, the module + will be called i2c-ast. + +config AST_I2C_SLAVE_MODE + bool "AST I2C Slave mode" + depends on I2C_AST + +if AST_I2C_SLAVE_MODE + +choice + prompt "I2C slave config" + default AST_I2C_SLAVE_EEPROM + +config AST_I2C_SLAVE_EEPROM + bool "10 byte EEPROM Device" + help + Support I2C slave mode communications on the AST I2C bus. This + is necessary for systems where the AST may be a target on the + I2C bus. + +config AST_I2C_SLAVE_RDWR + bool "I2C Slave RD/WR via ioctl" + + help + Support I2C slave mode communications on the AST I2C bus. This + is necessary for systems where the AST may be a target on the + I2C bus. + +endchoice + +endif + +config I2C_S3C2410 + tristate "S3C2410 I2C Driver" + depends on ARCH_S3C2410 + help + Say Y here to include support for I2C controller in the + Samsung S3C2410 based System-on-Chip devices. + +config I2C_SH7760 + tristate "Renesas SH7760 I2C Controller" + depends on CPU_SUBTYPE_SH7760 + help + This driver supports the 2 I2C interfaces on the Renesas SH7760. + + This driver can also be built as a module. If so, the module + will be called i2c-sh7760. + +config I2C_SH_MOBILE + tristate "SuperH Mobile I2C Controller" + depends on SUPERH + help + If you say yes to this option, support will be included for the + built-in I2C interface on the Renesas SH-Mobile processor. + + This driver can also be built as a module. If so, the module + will be called i2c-sh_mobile. + +config I2C_SIMTEC + tristate "Simtec Generic I2C interface" + select I2C_ALGOBIT + help + If you say yes to this option, support will be included for + the Simtec Generic I2C interface. This driver is for the + simple I2C bus used on newer Simtec products for general + I2C, such as DDC on the Simtec BBD2016A. + + This driver can also be built as a module. If so, the module + will be called i2c-simtec. + +config I2C_VERSATILE + tristate "ARM Versatile/Realview I2C bus support" + depends on ARCH_VERSATILE || ARCH_REALVIEW + select I2C_ALGOBIT + help + Say yes if you want to support the I2C serial bus on ARMs Versatile + range of platforms. + + This driver can also be built as a module. If so, the module + will be called i2c-versatile. + +comment "External I2C/SMBus adapter drivers" + +config I2C_PARPORT + tristate "Parallel port adapter" + depends on PARPORT + select I2C_ALGOBIT + help + This supports parallel port I2C adapters such as the ones made by + Philips or Velleman, Analog Devices evaluation boards, and more. + Basically any adapter using the parallel port as an I2C bus with + no extra chipset is supported by this driver, or could be. + + This driver is a replacement for (and was inspired by) an older + driver named i2c-philips-par. The new driver supports more devices, + and makes it easier to add support for new devices. + + An adapter type parameter is now mandatory. Please read the file + Documentation/i2c/busses/i2c-parport for details. + + Another driver exists, named i2c-parport-light, which doesn't depend + on the parport driver. This is meant for embedded systems. Don't say + Y here if you intend to say Y or M there. + + This support is also available as a module. If so, the module + will be called i2c-parport. + +config I2C_PARPORT_LIGHT + tristate "Parallel port adapter (light)" + select I2C_ALGOBIT + help + This supports parallel port I2C adapters such as the ones made by + Philips or Velleman, Analog Devices evaluation boards, and more. + Basically any adapter using the parallel port as an I2C bus with + no extra chipset is supported by this driver, or could be. + + This driver is a light version of i2c-parport. It doesn't depend + on the parport driver, and uses direct I/O access instead. This + might be preferred on embedded systems where wasting memory for + the clean but heavy parport handling is not an option. The + drawback is a reduced portability and the impossibility to + daisy-chain other parallel port devices. + + Don't say Y here if you said Y or M to i2c-parport. Saying M to + both is possible but both modules should not be loaded at the same + time. + + This support is also available as a module. If so, the module + will be called i2c-parport-light. + +config I2C_TAOS_EVM + tristate "TAOS evaluation module" + depends on EXPERIMENTAL + select SERIO + select SERIO_SERPORT + default n + help + This supports TAOS evaluation modules on serial port. In order to + use this driver, you will need the inputattach tool, which is part + of the input-utils package. + + If unsure, say N. + + This support is also available as a module. If so, the module + will be called i2c-taos-evm. + +config I2C_TINY_USB + tristate "Tiny-USB adapter" + depends on USB + help + If you say yes to this option, support will be included for the + i2c-tiny-usb, a simple do-it-yourself USB to I2C interface. See + http://www.harbaum.org/till/i2c_tiny_usb for hardware details. + + This driver can also be built as a module. If so, the module + will be called i2c-tiny-usb. + +comment "Graphics adapter I2C/DDC channel drivers" + depends on PCI + +config I2C_VOODOO3 + tristate "Voodoo 3" + depends on PCI + select I2C_ALGOBIT + help + If you say yes to this option, support will be included for the + Voodoo 3 I2C interface. + + This driver can also be built as a module. If so, the module + will be called i2c-voodoo3. + +comment "Other I2C/SMBus bus drivers" + +config I2C_ACORN + tristate "Acorn IOC/IOMD I2C bus support" + depends on ARCH_ACORN + default y + select I2C_ALGOBIT + help + Say yes if you want to support the I2C bus on Acorn platforms. + + If you don't know, say Y. + +config I2C_ELEKTOR + tristate "Elektor ISA card" + depends on ISA && BROKEN_ON_SMP + select I2C_ALGOPCF + help + This supports the PCF8584 ISA bus I2C adapter. Say Y if you own + such an adapter. + + This support is also available as a module. If so, the module + will be called i2c-elektor. + +config I2C_PCA_ISA + tristate "PCA9564 on an ISA bus" + depends on ISA + select I2C_ALGOPCA + default n + help + This driver supports ISA boards using the Philips PCA9564 + parallel bus to I2C bus controller. + + This driver can also be built as a module. If so, the module + will be called i2c-pca-isa. + + This device is almost undetectable and using this driver on a + system which doesn't have this device will result in long + delays when I2C/SMBus chip drivers are loaded (e.g. at boot + time). If unsure, say N. + +config I2C_PCA_PLATFORM + tristate "PCA9564 as platform device" + select I2C_ALGOPCA + default n + help + This driver supports a memory mapped Philips PCA9564 + parallel bus to I2C bus controller. + + This driver can also be built as a module. If so, the module + will be called i2c-pca-platform. + +config I2C_PMCMSP + tristate "PMC MSP I2C TWI Controller" + depends on PMC_MSP + help + This driver supports the PMC TWI controller on MSP devices. + + This driver can also be built as module. If so, the module + will be called i2c-pmcmsp. + +config I2C_SIBYTE + tristate "SiByte SMBus interface" + depends on SIBYTE_SB1xxx_SOC + help + Supports the SiByte SOC on-chip I2C interfaces (2 channels). + +config I2C_STUB + tristate "I2C/SMBus Test Stub" + depends on EXPERIMENTAL && m + default 'n' + help + This module may be useful to developers of SMBus client drivers, + especially for certain kinds of sensor chips. + + If you do build this module, be sure to read the notes and warnings + in <file:Documentation/i2c/i2c-stub>. + + If you don't know what to do here, definitely say N. + +config SCx200_I2C + tristate "NatSemi SCx200 I2C using GPIO pins (DEPRECATED)" + depends on SCx200_GPIO + select I2C_ALGOBIT + help + Enable the use of two GPIO pins of a SCx200 processor as an I2C bus. + + If you don't know what to do here, say N. + + This support is also available as a module. If so, the module + will be called scx200_i2c. + + This driver is deprecated and will be dropped soon. Use i2c-gpio + (or scx200_acb) instead. + +config SCx200_I2C_SCL + int "GPIO pin used for SCL" + depends on SCx200_I2C + default "12" + help + Enter the GPIO pin number used for the SCL signal. This value can + also be specified with a module parameter. + +config SCx200_I2C_SDA + int "GPIO pin used for SDA" + depends on SCx200_I2C + default "13" + help + Enter the GPIO pin number used for the SSA signal. This value can + also be specified with a module parameter. + +config SCx200_ACB + tristate "Geode ACCESS.bus support" + depends on X86_32 && PCI + help + Enable the use of the ACCESS.bus controllers on the Geode SCx200 and + SC1100 processors and the CS5535 and CS5536 Geode companion devices. + + If you don't know what to do here, say N. + + This support is also available as a module. If so, the module + will be called scx200_acb. + +endmenu diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile new file mode 100644 index 0000000..a3b523e --- /dev/null +++ b/drivers/i2c/busses/Makefile @@ -0,0 +1,74 @@ +# +# Makefile for the i2c bus drivers. +# + +# PC SMBus host controller drivers +obj-$(CONFIG_I2C_ALI1535) += i2c-ali1535.o +obj-$(CONFIG_I2C_ALI1563) += i2c-ali1563.o +obj-$(CONFIG_I2C_ALI15X3) += i2c-ali15x3.o +obj-$(CONFIG_I2C_AMD756) += i2c-amd756.o +obj-$(CONFIG_I2C_AMD756_S4882) += i2c-amd756-s4882.o +obj-$(CONFIG_I2C_AMD8111) += i2c-amd8111.o +obj-$(CONFIG_I2C_I801) += i2c-i801.o +obj-$(CONFIG_I2C_ISCH) += i2c-isch.o +obj-$(CONFIG_I2C_NFORCE2) += i2c-nforce2.o +obj-$(CONFIG_I2C_NFORCE2_S4985) += i2c-nforce2-s4985.o +obj-$(CONFIG_I2C_PIIX4) += i2c-piix4.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_VIA) += i2c-via.o +obj-$(CONFIG_I2C_VIAPRO) += i2c-viapro.o + +# Mac SMBus host controller drivers +obj-$(CONFIG_I2C_HYDRA) += i2c-hydra.o +obj-$(CONFIG_I2C_POWERMAC) += i2c-powermac.o + +# Embebbed system I2C/SMBus host controller drivers +obj-$(CONFIG_I2C_AT91) += i2c-at91.o +obj-$(CONFIG_I2C_AU1550) += i2c-au1550.o +obj-$(CONFIG_I2C_BLACKFIN_TWI) += i2c-bfin-twi.o +obj-$(CONFIG_I2C_CPM) += i2c-cpm.o +obj-$(CONFIG_I2C_DAVINCI) += i2c-davinci.o +obj-$(CONFIG_I2C_GPIO) += i2c-gpio.o +obj-$(CONFIG_I2C_HIGHLANDER) += i2c-highlander.o +obj-$(CONFIG_I2C_IBM_IIC) += i2c-ibm_iic.o +obj-$(CONFIG_I2C_IOP3XX) += i2c-iop3xx.o +obj-$(CONFIG_I2C_IXP2000) += i2c-ixp2000.o +obj-$(CONFIG_I2C_MPC) += i2c-mpc.o +obj-$(CONFIG_I2C_MV64XXX) += i2c-mv64xxx.o +obj-$(CONFIG_I2C_OCORES) += i2c-ocores.o +obj-$(CONFIG_I2C_OMAP) += i2c-omap.o +obj-$(CONFIG_I2C_PASEMI) += i2c-pasemi.o +obj-$(CONFIG_I2C_PNX) += i2c-pnx.o +obj-$(CONFIG_I2C_PXA) += i2c-pxa.o +obj-$(CONFIG_I2C_AST) += i2c-ast.o +obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o +obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o +obj-$(CONFIG_I2C_SH_MOBILE) += i2c-sh_mobile.o +obj-$(CONFIG_I2C_SIMTEC) += i2c-simtec.o +obj-$(CONFIG_I2C_VERSATILE) += i2c-versatile.o + +# External I2C/SMBus adapter drivers +obj-$(CONFIG_I2C_PARPORT) += i2c-parport.o +obj-$(CONFIG_I2C_PARPORT_LIGHT) += i2c-parport-light.o +obj-$(CONFIG_I2C_TAOS_EVM) += i2c-taos-evm.o +obj-$(CONFIG_I2C_TINY_USB) += i2c-tiny-usb.o + +# Graphics adapter I2C/DDC channel drivers +obj-$(CONFIG_I2C_VOODOO3) += i2c-voodoo3.o + +# Other I2C/SMBus bus drivers +obj-$(CONFIG_I2C_ACORN) += i2c-acorn.o +obj-$(CONFIG_I2C_ELEKTOR) += i2c-elektor.o +obj-$(CONFIG_I2C_PCA_ISA) += i2c-pca-isa.o +obj-$(CONFIG_I2C_PCA_PLATFORM) += i2c-pca-platform.o +obj-$(CONFIG_I2C_PMCMSP) += i2c-pmcmsp.o +obj-$(CONFIG_I2C_SIBYTE) += i2c-sibyte.o +obj-$(CONFIG_I2C_STUB) += i2c-stub.o +obj-$(CONFIG_SCx200_ACB) += scx200_acb.o +obj-$(CONFIG_SCx200_I2C) += scx200_i2c.o + +ifeq ($(CONFIG_I2C_DEBUG_BUS),y) +EXTRA_CFLAGS += -DDEBUG +endif diff --git a/drivers/i2c/busses/i2c-acorn.c b/drivers/i2c/busses/i2c-acorn.c new file mode 100644 index 0000000..c9a8d6f --- /dev/null +++ b/drivers/i2c/busses/i2c-acorn.c @@ -0,0 +1,98 @@ +/* + * linux/drivers/acorn/char/i2c.c + * + * Copyright (C) 2000 Russell King + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * ARM IOC/IOMD i2c driver. + * + * On Acorn machines, the following i2c devices are on the bus: + * - PCF8583 real time clock & static RAM + */ +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> + +#include <mach/hardware.h> +#include <asm/io.h> +#include <asm/hardware/ioc.h> +#include <asm/system.h> + +#define FORCE_ONES 0xdc +#define SCL 0x02 +#define SDA 0x01 + +/* + * We must preserve all non-i2c output bits in IOC_CONTROL. + * Note also that we need to preserve the value of SCL and + * SDA outputs as well (which may be different from the + * values read back from IOC_CONTROL). + */ +static u_int force_ones; + +static void ioc_setscl(void *data, int state) +{ + u_int ioc_control = ioc_readb(IOC_CONTROL) & ~(SCL | SDA); + u_int ones = force_ones; + + if (state) + ones |= SCL; + else + ones &= ~SCL; + + force_ones = ones; + + ioc_writeb(ioc_control | ones, IOC_CONTROL); +} + +static void ioc_setsda(void *data, int state) +{ + u_int ioc_control = ioc_readb(IOC_CONTROL) & ~(SCL | SDA); + u_int ones = force_ones; + + if (state) + ones |= SDA; + else + ones &= ~SDA; + + force_ones = ones; + + ioc_writeb(ioc_control | ones, IOC_CONTROL); +} + +static int ioc_getscl(void *data) +{ + return (ioc_readb(IOC_CONTROL) & SCL) != 0; +} + +static int ioc_getsda(void *data) +{ + return (ioc_readb(IOC_CONTROL) & SDA) != 0; +} + +static struct i2c_algo_bit_data ioc_data = { + .setsda = ioc_setsda, + .setscl = ioc_setscl, + .getsda = ioc_getsda, + .getscl = ioc_getscl, + .udelay = 80, + .timeout = 100 +}; + +static struct i2c_adapter ioc_ops = { + .id = I2C_HW_B_IOC, + .nr = 0, + .algo_data = &ioc_data, +}; + +static int __init i2c_ioc_init(void) +{ + force_ones = FORCE_ONES | SCL | SDA; + + return i2c_bit_add_numbered_bus(&ioc_ops); +} + +module_init(i2c_ioc_init); diff --git a/drivers/i2c/busses/i2c-ali1535.c b/drivers/i2c/busses/i2c-ali1535.c new file mode 100644 index 0000000..9cead9b --- /dev/null +++ b/drivers/i2c/busses/i2c-ali1535.c @@ -0,0 +1,538 @@ +/* + Copyright (c) 2000 Frodo Looijaard <frodol@dds.nl>, + Philip Edelbrock <phil@netroedge.com>, + Mark D. Studebaker <mdsxyz123@yahoo.com>, + Dan Eaton <dan.eaton@rocketlogix.com> and + Stephen Rousset<stephen.rousset@rocketlogix.com> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* + This is the driver for the SMB Host controller on + Acer Labs Inc. (ALI) M1535 South Bridge. + + The M1535 is a South bridge for portable systems. + It is very similar to the M15x3 South bridges also produced + by Acer Labs Inc. Some of the registers within the part + have moved and some have been redefined slightly. Additionally, + the sequencing of the SMBus transactions has been modified + to be more consistent with the sequencing recommended by + the manufacturer and observed through testing. These + changes are reflected in this driver and can be identified + by comparing this driver to the i2c-ali15x3 driver. + For an overview of these chips see http://www.acerlabs.com + + The SMB controller is part of the 7101 device, which is an + ACPI-compliant Power Management Unit (PMU). + + The whole 7101 device has to be enabled for the SMB to work. + You can't just enable the SMB alone. + The SMB and the ACPI have separate I/O spaces. + We make sure that the SMB is enabled. We leave the ACPI alone. + + This driver controls the SMB Host only. + + This driver does not use interrupts. +*/ + + +/* Note: we assume there can only be one ALI1535, with one SMBus interface */ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/kernel.h> +#include <linux/stddef.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/acpi.h> +#include <asm/io.h> + + +/* ALI1535 SMBus address offsets */ +#define SMBHSTSTS (0 + ali1535_smba) +#define SMBHSTTYP (1 + ali1535_smba) +#define SMBHSTPORT (2 + ali1535_smba) +#define SMBHSTCMD (7 + ali1535_smba) +#define SMBHSTADD (3 + ali1535_smba) +#define SMBHSTDAT0 (4 + ali1535_smba) +#define SMBHSTDAT1 (5 + ali1535_smba) +#define SMBBLKDAT (6 + ali1535_smba) + +/* PCI Address Constants */ +#define SMBCOM 0x004 +#define SMBREV 0x008 +#define SMBCFG 0x0D1 +#define SMBBA 0x0E2 +#define SMBHSTCFG 0x0F0 +#define SMBCLK 0x0F2 + +/* Other settings */ +#define MAX_TIMEOUT 500 /* times 1/100 sec */ +#define ALI1535_SMB_IOSIZE 32 + +#define ALI1535_SMB_DEFAULTBASE 0x8040 + +/* ALI1535 address lock bits */ +#define ALI1535_LOCK 0x06 /* dwe */ + +/* ALI1535 command constants */ +#define ALI1535_QUICK 0x00 +#define ALI1535_BYTE 0x10 +#define ALI1535_BYTE_DATA 0x20 +#define ALI1535_WORD_DATA 0x30 +#define ALI1535_BLOCK_DATA 0x40 +#define ALI1535_I2C_READ 0x60 + +#define ALI1535_DEV10B_EN 0x80 /* Enable 10-bit addressing in */ + /* I2C read */ +#define ALI1535_T_OUT 0x08 /* Time-out Command (write) */ +#define ALI1535_A_HIGH_BIT9 0x08 /* Bit 9 of 10-bit address in */ + /* Alert-Response-Address */ + /* (read) */ +#define ALI1535_KILL 0x04 /* Kill Command (write) */ +#define ALI1535_A_HIGH_BIT8 0x04 /* Bit 8 of 10-bit address in */ + /* Alert-Response-Address */ + /* (read) */ + +#define ALI1535_D_HI_MASK 0x03 /* Mask for isolating bits 9-8 */ + /* of 10-bit address in I2C */ + /* Read Command */ + +/* ALI1535 status register bits */ +#define ALI1535_STS_IDLE 0x04 +#define ALI1535_STS_BUSY 0x08 /* host busy */ +#define ALI1535_STS_DONE 0x10 /* transaction complete */ +#define ALI1535_STS_DEV 0x20 /* device error */ +#define ALI1535_STS_BUSERR 0x40 /* bus error */ +#define ALI1535_STS_FAIL 0x80 /* failed bus transaction */ +#define ALI1535_STS_ERR 0xE0 /* all the bad error bits */ + +#define ALI1535_BLOCK_CLR 0x04 /* reset block data index */ + +/* ALI1535 device address register bits */ +#define ALI1535_RD_ADDR 0x01 /* Read/Write Bit in Device */ + /* Address field */ + /* -> Write = 0 */ + /* -> Read = 1 */ +#define ALI1535_SMBIO_EN 0x04 /* SMB I/O Space enable */ + +static struct pci_driver ali1535_driver; +static unsigned short ali1535_smba; + +/* Detect whether a ALI1535 can be found, and initialize it, where necessary. + Note the differences between kernels with the old PCI BIOS interface and + newer kernels with the real PCI interface. In compat.h some things are + defined to make the transition easier. */ +static int ali1535_setup(struct pci_dev *dev) +{ + int retval = -ENODEV; + unsigned char temp; + + /* Check the following things: + - SMB I/O address is initialized + - Device is enabled + - We can use the addresses + */ + + /* Determine the address of the SMBus area */ + pci_read_config_word(dev, SMBBA, &ali1535_smba); + ali1535_smba &= (0xffff & ~(ALI1535_SMB_IOSIZE - 1)); + if (ali1535_smba == 0) { + dev_warn(&dev->dev, + "ALI1535_smb region uninitialized - upgrade BIOS?\n"); + goto exit; + } + + retval = acpi_check_region(ali1535_smba, ALI1535_SMB_IOSIZE, + ali1535_driver.name); + if (retval) + goto exit; + + if (!request_region(ali1535_smba, ALI1535_SMB_IOSIZE, + ali1535_driver.name)) { + dev_err(&dev->dev, "ALI1535_smb region 0x%x already in use!\n", + ali1535_smba); + goto exit; + } + + /* check if whole device is enabled */ + pci_read_config_byte(dev, SMBCFG, &temp); + if ((temp & ALI1535_SMBIO_EN) == 0) { + dev_err(&dev->dev, "SMB device not enabled - upgrade BIOS?\n"); + goto exit_free; + } + + /* Is SMB Host controller enabled? */ + pci_read_config_byte(dev, SMBHSTCFG, &temp); + if ((temp & 1) == 0) { + dev_err(&dev->dev, "SMBus controller not enabled - upgrade BIOS?\n"); + goto exit_free; + } + + /* set SMB clock to 74KHz as recommended in data sheet */ + pci_write_config_byte(dev, SMBCLK, 0x20); + + /* + The interrupt routing for SMB is set up in register 0x77 in the + 1533 ISA Bridge device, NOT in the 7101 device. + Don't bother with finding the 1533 device and reading the register. + if ((....... & 0x0F) == 1) + dev_dbg(&dev->dev, "ALI1535 using Interrupt 9 for SMBus.\n"); + */ + pci_read_config_byte(dev, SMBREV, &temp); + dev_dbg(&dev->dev, "SMBREV = 0x%X\n", temp); + dev_dbg(&dev->dev, "ALI1535_smba = 0x%X\n", ali1535_smba); + + retval = 0; +exit: + return retval; + +exit_free: + release_region(ali1535_smba, ALI1535_SMB_IOSIZE); + return retval; +} + +static int ali1535_transaction(struct i2c_adapter *adap) +{ + int temp; + int result = 0; + int timeout = 0; + + dev_dbg(&adap->dev, "Transaction (pre): STS=%02x, TYP=%02x, " + "CMD=%02x, ADD=%02x, DAT0=%02x, DAT1=%02x\n", + inb_p(SMBHSTSTS), inb_p(SMBHSTTYP), inb_p(SMBHSTCMD), + inb_p(SMBHSTADD), inb_p(SMBHSTDAT0), inb_p(SMBHSTDAT1)); + + /* get status */ + temp = inb_p(SMBHSTSTS); + + /* Make sure the SMBus host is ready to start transmitting */ + /* Check the busy bit first */ + if (temp & ALI1535_STS_BUSY) { + /* If the host controller is still busy, it may have timed out + * in the previous transaction, resulting in a "SMBus Timeout" + * printk. I've tried the following to reset a stuck busy bit. + * 1. Reset the controller with an KILL command. (this + * doesn't seem to clear the controller if an external + * device is hung) + * 2. Reset the controller and the other SMBus devices with a + * T_OUT command. (this clears the host busy bit if an + * external device is hung, but it comes back upon a new + * access to a device) + * 3. Disable and reenable the controller in SMBHSTCFG. Worst + * case, nothing seems to work except power reset. + */ + + /* Try resetting entire SMB bus, including other devices - This + * may not work either - it clears the BUSY bit but then the + * BUSY bit may come back on when you try and use the chip + * again. If that's the case you are stuck. + */ + dev_info(&adap->dev, + "Resetting entire SMB Bus to clear busy condition (%02x)\n", + temp); + outb_p(ALI1535_T_OUT, SMBHSTTYP); + temp = inb_p(SMBHSTSTS); + } + + /* now check the error bits and the busy bit */ + if (temp & (ALI1535_STS_ERR | ALI1535_STS_BUSY)) { + /* do a clear-on-write */ + outb_p(0xFF, SMBHSTSTS); + if ((temp = inb_p(SMBHSTSTS)) & + (ALI1535_STS_ERR | ALI1535_STS_BUSY)) { + /* This is probably going to be correctable only by a + * power reset as one of the bits now appears to be + * stuck */ + /* This may be a bus or device with electrical problems. */ + dev_err(&adap->dev, + "SMBus reset failed! (0x%02x) - controller or " + "device on bus is probably hung\n", temp); + return -EBUSY; + } + } else { + /* check and clear done bit */ + if (temp & ALI1535_STS_DONE) { + outb_p(temp, SMBHSTSTS); + } + } + + /* start the transaction by writing anything to the start register */ + outb_p(0xFF, SMBHSTPORT); + + /* We will always wait for a fraction of a second! */ + timeout = 0; + do { + msleep(1); + temp = inb_p(SMBHSTSTS); + } while (((temp & ALI1535_STS_BUSY) && !(temp & ALI1535_STS_IDLE)) + && (timeout++ < MAX_TIMEOUT)); + + /* If the SMBus is still busy, we give up */ + if (timeout >= MAX_TIMEOUT) { + result = -ETIMEDOUT; + dev_err(&adap->dev, "SMBus Timeout!\n"); + } + + if (temp & ALI1535_STS_FAIL) { + result = -EIO; + dev_dbg(&adap->dev, "Error: Failed bus transaction\n"); + } + + /* Unfortunately the ALI SMB controller maps "no response" and "bus + * collision" into a single bit. No reponse is the usual case so don't + * do a printk. This means that bus collisions go unreported. + */ + if (temp & ALI1535_STS_BUSERR) { + result = -ENXIO; + dev_dbg(&adap->dev, + "Error: no response or bus collision ADD=%02x\n", + inb_p(SMBHSTADD)); + } + + /* haven't ever seen this */ + if (temp & ALI1535_STS_DEV) { + result = -EIO; + dev_err(&adap->dev, "Error: device error\n"); + } + + /* check to see if the "command complete" indication is set */ + if (!(temp & ALI1535_STS_DONE)) { + result = -ETIMEDOUT; + dev_err(&adap->dev, "Error: command never completed\n"); + } + + dev_dbg(&adap->dev, "Transaction (post): STS=%02x, TYP=%02x, " + "CMD=%02x, ADD=%02x, DAT0=%02x, DAT1=%02x\n", + inb_p(SMBHSTSTS), inb_p(SMBHSTTYP), inb_p(SMBHSTCMD), + inb_p(SMBHSTADD), inb_p(SMBHSTDAT0), inb_p(SMBHSTDAT1)); + + /* take consequent actions for error conditions */ + if (!(temp & ALI1535_STS_DONE)) { + /* issue "kill" to reset host controller */ + outb_p(ALI1535_KILL,SMBHSTTYP); + outb_p(0xFF,SMBHSTSTS); + } else if (temp & ALI1535_STS_ERR) { + /* issue "timeout" to reset all devices on bus */ + outb_p(ALI1535_T_OUT,SMBHSTTYP); + outb_p(0xFF,SMBHSTSTS); + } + + return result; +} + +/* Return negative errno on error. */ +static s32 ali1535_access(struct i2c_adapter *adap, u16 addr, + unsigned short flags, char read_write, u8 command, + int size, union i2c_smbus_data *data) +{ + int i, len; + int temp; + int timeout; + s32 result = 0; + + /* make sure SMBus is idle */ + temp = inb_p(SMBHSTSTS); + for (timeout = 0; + (timeout < MAX_TIMEOUT) && !(temp & ALI1535_STS_IDLE); + timeout++) { + msleep(1); + temp = inb_p(SMBHSTSTS); + } + if (timeout >= MAX_TIMEOUT) + dev_warn(&adap->dev, "Idle wait Timeout! STS=0x%02x\n", temp); + + /* clear status register (clear-on-write) */ + outb_p(0xFF, SMBHSTSTS); + + switch (size) { + case I2C_SMBUS_QUICK: + outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), + SMBHSTADD); + size = ALI1535_QUICK; + outb_p(size, SMBHSTTYP); /* output command */ + break; + case I2C_SMBUS_BYTE: + outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), + SMBHSTADD); + size = ALI1535_BYTE; + outb_p(size, SMBHSTTYP); /* output command */ + if (read_write == I2C_SMBUS_WRITE) + outb_p(command, SMBHSTCMD); + break; + case I2C_SMBUS_BYTE_DATA: + outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), + SMBHSTADD); + size = ALI1535_BYTE_DATA; + outb_p(size, SMBHSTTYP); /* output command */ + outb_p(command, SMBHSTCMD); + if (read_write == I2C_SMBUS_WRITE) + outb_p(data->byte, SMBHSTDAT0); + break; + case I2C_SMBUS_WORD_DATA: + outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), + SMBHSTADD); + size = ALI1535_WORD_DATA; + outb_p(size, SMBHSTTYP); /* output command */ + outb_p(command, SMBHSTCMD); + if (read_write == I2C_SMBUS_WRITE) { + outb_p(data->word & 0xff, SMBHSTDAT0); + outb_p((data->word & 0xff00) >> 8, SMBHSTDAT1); + } + break; + case I2C_SMBUS_BLOCK_DATA: + outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), + SMBHSTADD); + size = ALI1535_BLOCK_DATA; + outb_p(size, SMBHSTTYP); /* output command */ + outb_p(command, SMBHSTCMD); + if (read_write == I2C_SMBUS_WRITE) { + len = data->block[0]; + if (len < 0) { + len = 0; + data->block[0] = len; + } + if (len > 32) { + len = 32; + data->block[0] = len; + } + outb_p(len, SMBHSTDAT0); + /* Reset SMBBLKDAT */ + outb_p(inb_p(SMBHSTTYP) | ALI1535_BLOCK_CLR, SMBHSTTYP); + for (i = 1; i <= len; i++) + outb_p(data->block[i], SMBBLKDAT); + } + break; + default: + dev_warn(&adap->dev, "Unsupported transaction %d\n", size); + result = -EOPNOTSUPP; + goto EXIT; + } + + result = ali1535_transaction(adap); + if (result) + goto EXIT; + + if ((read_write == I2C_SMBUS_WRITE) || (size == ALI1535_QUICK)) { + result = 0; + goto EXIT; + } + + switch (size) { + case ALI1535_BYTE: /* Result put in SMBHSTDAT0 */ + data->byte = inb_p(SMBHSTDAT0); + break; + case ALI1535_BYTE_DATA: + data->byte = inb_p(SMBHSTDAT0); + break; + case ALI1535_WORD_DATA: + data->word = inb_p(SMBHSTDAT0) + (inb_p(SMBHSTDAT1) << 8); + break; + case ALI1535_BLOCK_DATA: + len = inb_p(SMBHSTDAT0); + if (len > 32) + len = 32; + data->block[0] = len; + /* Reset SMBBLKDAT */ + outb_p(inb_p(SMBHSTTYP) | ALI1535_BLOCK_CLR, SMBHSTTYP); + for (i = 1; i <= data->block[0]; i++) { + data->block[i] = inb_p(SMBBLKDAT); + dev_dbg(&adap->dev, "Blk: len=%d, i=%d, data=%02x\n", + len, i, data->block[i]); + } + break; + } +EXIT: + return result; +} + + +static u32 ali1535_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_BLOCK_DATA; +} + +static const struct i2c_algorithm smbus_algorithm = { + .smbus_xfer = ali1535_access, + .functionality = ali1535_func, +}; + +static struct i2c_adapter ali1535_adapter = { + .owner = THIS_MODULE, + .id = I2C_HW_SMBUS_ALI1535, + .class = I2C_CLASS_HWMON | I2C_CLASS_SPD, + .algo = &smbus_algorithm, +}; + +static struct pci_device_id ali1535_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M7101) }, + { }, +}; + +MODULE_DEVICE_TABLE (pci, ali1535_ids); + +static int __devinit ali1535_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + if (ali1535_setup(dev)) { + dev_warn(&dev->dev, + "ALI1535 not detected, module not inserted.\n"); + return -ENODEV; + } + + /* set up the sysfs linkage to our parent device */ + ali1535_adapter.dev.parent = &dev->dev; + + snprintf(ali1535_adapter.name, sizeof(ali1535_adapter.name), + "SMBus ALI1535 adapter at %04x", ali1535_smba); + return i2c_add_adapter(&ali1535_adapter); +} + +static void __devexit ali1535_remove(struct pci_dev *dev) +{ + i2c_del_adapter(&ali1535_adapter); + release_region(ali1535_smba, ALI1535_SMB_IOSIZE); +} + +static struct pci_driver ali1535_driver = { + .name = "ali1535_smbus", + .id_table = ali1535_ids, + .probe = ali1535_probe, + .remove = __devexit_p(ali1535_remove), +}; + +static int __init i2c_ali1535_init(void) +{ + return pci_register_driver(&ali1535_driver); +} + +static void __exit i2c_ali1535_exit(void) +{ + pci_unregister_driver(&ali1535_driver); +} + +MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl>, " + "Philip Edelbrock <phil@netroedge.com>, " + "Mark D. Studebaker <mdsxyz123@yahoo.com> " + "and Dan Eaton <dan.eaton@rocketlogix.com>"); +MODULE_DESCRIPTION("ALI1535 SMBus driver"); +MODULE_LICENSE("GPL"); + +module_init(i2c_ali1535_init); +module_exit(i2c_ali1535_exit); diff --git a/drivers/i2c/busses/i2c-ali1563.c b/drivers/i2c/busses/i2c-ali1563.c new file mode 100644 index 0000000..fc3e5b0 --- /dev/null +++ b/drivers/i2c/busses/i2c-ali1563.c @@ -0,0 +1,449 @@ +/** + * i2c-ali1563.c - i2c driver for the ALi 1563 Southbridge + * + * Copyright (C) 2004 Patrick Mochel + * 2005 Rudolf Marek <r.marek@assembler.cz> + * + * The 1563 southbridge is deceptively similar to the 1533, with a + * few notable exceptions. One of those happens to be the fact they + * upgraded the i2c core to be 2.0 compliant, and happens to be almost + * identical to the i2c controller found in the Intel 801 south + * bridges. + * + * This driver is based on a mix of the 15x3, 1535, and i801 drivers, + * with a little help from the ALi 1563 spec. + * + * This file is released under the GPLv2 + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/pci.h> +#include <linux/init.h> +#include <linux/acpi.h> + +#define ALI1563_MAX_TIMEOUT 500 +#define ALI1563_SMBBA 0x80 +#define ALI1563_SMB_IOEN 1 +#define ALI1563_SMB_HOSTEN 2 +#define ALI1563_SMB_IOSIZE 16 + +#define SMB_HST_STS (ali1563_smba + 0) +#define SMB_HST_CNTL1 (ali1563_smba + 1) +#define SMB_HST_CNTL2 (ali1563_smba + 2) +#define SMB_HST_CMD (ali1563_smba + 3) +#define SMB_HST_ADD (ali1563_smba + 4) +#define SMB_HST_DAT0 (ali1563_smba + 5) +#define SMB_HST_DAT1 (ali1563_smba + 6) +#define SMB_BLK_DAT (ali1563_smba + 7) + +#define HST_STS_BUSY 0x01 +#define HST_STS_INTR 0x02 +#define HST_STS_DEVERR 0x04 +#define HST_STS_BUSERR 0x08 +#define HST_STS_FAIL 0x10 +#define HST_STS_DONE 0x80 +#define HST_STS_BAD 0x1c + + +#define HST_CNTL1_TIMEOUT 0x80 +#define HST_CNTL1_LAST 0x40 + +#define HST_CNTL2_KILL 0x04 +#define HST_CNTL2_START 0x40 +#define HST_CNTL2_QUICK 0x00 +#define HST_CNTL2_BYTE 0x01 +#define HST_CNTL2_BYTE_DATA 0x02 +#define HST_CNTL2_WORD_DATA 0x03 +#define HST_CNTL2_BLOCK 0x05 + + +#define HST_CNTL2_SIZEMASK 0x38 + +static struct pci_driver ali1563_pci_driver; +static unsigned short ali1563_smba; + +static int ali1563_transaction(struct i2c_adapter * a, int size) +{ + u32 data; + int timeout; + int status = -EIO; + + dev_dbg(&a->dev, "Transaction (pre): STS=%02x, CNTL1=%02x, " + "CNTL2=%02x, CMD=%02x, ADD=%02x, DAT0=%02x, DAT1=%02x\n", + inb_p(SMB_HST_STS), inb_p(SMB_HST_CNTL1), inb_p(SMB_HST_CNTL2), + inb_p(SMB_HST_CMD), inb_p(SMB_HST_ADD), inb_p(SMB_HST_DAT0), + inb_p(SMB_HST_DAT1)); + + data = inb_p(SMB_HST_STS); + if (data & HST_STS_BAD) { + dev_err(&a->dev, "ali1563: Trying to reset busy device\n"); + outb_p(data | HST_STS_BAD,SMB_HST_STS); + data = inb_p(SMB_HST_STS); + if (data & HST_STS_BAD) + return -EBUSY; + } + outb_p(inb_p(SMB_HST_CNTL2) | HST_CNTL2_START, SMB_HST_CNTL2); + + timeout = ALI1563_MAX_TIMEOUT; + do + msleep(1); + while (((data = inb_p(SMB_HST_STS)) & HST_STS_BUSY) && --timeout); + + dev_dbg(&a->dev, "Transaction (post): STS=%02x, CNTL1=%02x, " + "CNTL2=%02x, CMD=%02x, ADD=%02x, DAT0=%02x, DAT1=%02x\n", + inb_p(SMB_HST_STS), inb_p(SMB_HST_CNTL1), inb_p(SMB_HST_CNTL2), + inb_p(SMB_HST_CMD), inb_p(SMB_HST_ADD), inb_p(SMB_HST_DAT0), + inb_p(SMB_HST_DAT1)); + + if (timeout && !(data & HST_STS_BAD)) + return 0; + + if (!timeout) { + dev_err(&a->dev, "Timeout - Trying to KILL transaction!\n"); + /* Issue 'kill' to host controller */ + outb_p(HST_CNTL2_KILL,SMB_HST_CNTL2); + data = inb_p(SMB_HST_STS); + status = -ETIMEDOUT; + } + + /* device error - no response, ignore the autodetection case */ + if (data & HST_STS_DEVERR) { + if (size != HST_CNTL2_QUICK) + dev_err(&a->dev, "Device error!\n"); + status = -ENXIO; + } + /* bus collision */ + if (data & HST_STS_BUSERR) { + dev_err(&a->dev, "Bus collision!\n"); + /* Issue timeout, hoping it helps */ + outb_p(HST_CNTL1_TIMEOUT,SMB_HST_CNTL1); + } + + if (data & HST_STS_FAIL) { + dev_err(&a->dev, "Cleaning fail after KILL!\n"); + outb_p(0x0,SMB_HST_CNTL2); + } + + return status; +} + +static int ali1563_block_start(struct i2c_adapter * a) +{ + u32 data; + int timeout; + int status = -EIO; + + dev_dbg(&a->dev, "Block (pre): STS=%02x, CNTL1=%02x, " + "CNTL2=%02x, CMD=%02x, ADD=%02x, DAT0=%02x, DAT1=%02x\n", + inb_p(SMB_HST_STS), inb_p(SMB_HST_CNTL1), inb_p(SMB_HST_CNTL2), + inb_p(SMB_HST_CMD), inb_p(SMB_HST_ADD), inb_p(SMB_HST_DAT0), + inb_p(SMB_HST_DAT1)); + + data = inb_p(SMB_HST_STS); + if (data & HST_STS_BAD) { + dev_warn(&a->dev,"ali1563: Trying to reset busy device\n"); + outb_p(data | HST_STS_BAD,SMB_HST_STS); + data = inb_p(SMB_HST_STS); + if (data & HST_STS_BAD) + return -EBUSY; + } + + /* Clear byte-ready bit */ + outb_p(data | HST_STS_DONE, SMB_HST_STS); + + /* Start transaction and wait for byte-ready bit to be set */ + outb_p(inb_p(SMB_HST_CNTL2) | HST_CNTL2_START, SMB_HST_CNTL2); + + timeout = ALI1563_MAX_TIMEOUT; + do + msleep(1); + while (!((data = inb_p(SMB_HST_STS)) & HST_STS_DONE) && --timeout); + + dev_dbg(&a->dev, "Block (post): STS=%02x, CNTL1=%02x, " + "CNTL2=%02x, CMD=%02x, ADD=%02x, DAT0=%02x, DAT1=%02x\n", + inb_p(SMB_HST_STS), inb_p(SMB_HST_CNTL1), inb_p(SMB_HST_CNTL2), + inb_p(SMB_HST_CMD), inb_p(SMB_HST_ADD), inb_p(SMB_HST_DAT0), + inb_p(SMB_HST_DAT1)); + + if (timeout && !(data & HST_STS_BAD)) + return 0; + + if (timeout == 0) + status = -ETIMEDOUT; + + if (data & HST_STS_DEVERR) + status = -ENXIO; + + dev_err(&a->dev, "SMBus Error: %s%s%s%s%s\n", + timeout ? "" : "Timeout ", + data & HST_STS_FAIL ? "Transaction Failed " : "", + data & HST_STS_BUSERR ? "No response or Bus Collision " : "", + data & HST_STS_DEVERR ? "Device Error " : "", + !(data & HST_STS_DONE) ? "Transaction Never Finished " : ""); + return status; +} + +static int ali1563_block(struct i2c_adapter * a, union i2c_smbus_data * data, u8 rw) +{ + int i, len; + int error = 0; + + /* Do we need this? */ + outb_p(HST_CNTL1_LAST,SMB_HST_CNTL1); + + if (rw == I2C_SMBUS_WRITE) { + len = data->block[0]; + if (len < 1) + len = 1; + else if (len > 32) + len = 32; + outb_p(len,SMB_HST_DAT0); + outb_p(data->block[1],SMB_BLK_DAT); + } else + len = 32; + + outb_p(inb_p(SMB_HST_CNTL2) | HST_CNTL2_BLOCK, SMB_HST_CNTL2); + + for (i = 0; i < len; i++) { + if (rw == I2C_SMBUS_WRITE) { + outb_p(data->block[i + 1], SMB_BLK_DAT); + if ((error = ali1563_block_start(a))) + break; + } else { + if ((error = ali1563_block_start(a))) + break; + if (i == 0) { + len = inb_p(SMB_HST_DAT0); + if (len < 1) + len = 1; + else if (len > 32) + len = 32; + } + data->block[i+1] = inb_p(SMB_BLK_DAT); + } + } + /* Do we need this? */ + outb_p(HST_CNTL1_LAST,SMB_HST_CNTL1); + return error; +} + +static s32 ali1563_access(struct i2c_adapter * a, u16 addr, + unsigned short flags, char rw, u8 cmd, + int size, union i2c_smbus_data * data) +{ + int error = 0; + int timeout; + u32 reg; + + for (timeout = ALI1563_MAX_TIMEOUT; timeout; timeout--) { + if (!(reg = inb_p(SMB_HST_STS) & HST_STS_BUSY)) + break; + } + if (!timeout) + dev_warn(&a->dev,"SMBus not idle. HST_STS = %02x\n",reg); + outb_p(0xff,SMB_HST_STS); + + /* Map the size to what the chip understands */ + switch (size) { + case I2C_SMBUS_QUICK: + size = HST_CNTL2_QUICK; + break; + case I2C_SMBUS_BYTE: + size = HST_CNTL2_BYTE; + break; + case I2C_SMBUS_BYTE_DATA: + size = HST_CNTL2_BYTE_DATA; + break; + case I2C_SMBUS_WORD_DATA: + size = HST_CNTL2_WORD_DATA; + break; + case I2C_SMBUS_BLOCK_DATA: + size = HST_CNTL2_BLOCK; + break; + default: + dev_warn(&a->dev, "Unsupported transaction %d\n", size); + error = -EOPNOTSUPP; + goto Done; + } + + outb_p(((addr & 0x7f) << 1) | (rw & 0x01), SMB_HST_ADD); + outb_p((inb_p(SMB_HST_CNTL2) & ~HST_CNTL2_SIZEMASK) | (size << 3), SMB_HST_CNTL2); + + /* Write the command register */ + + switch(size) { + case HST_CNTL2_BYTE: + if (rw== I2C_SMBUS_WRITE) + /* Beware it uses DAT0 register and not CMD! */ + outb_p(cmd, SMB_HST_DAT0); + break; + case HST_CNTL2_BYTE_DATA: + outb_p(cmd, SMB_HST_CMD); + if (rw == I2C_SMBUS_WRITE) + outb_p(data->byte, SMB_HST_DAT0); + break; + case HST_CNTL2_WORD_DATA: + outb_p(cmd, SMB_HST_CMD); + if (rw == I2C_SMBUS_WRITE) { + outb_p(data->word & 0xff, SMB_HST_DAT0); + outb_p((data->word & 0xff00) >> 8, SMB_HST_DAT1); + } + break; + case HST_CNTL2_BLOCK: + outb_p(cmd, SMB_HST_CMD); + error = ali1563_block(a,data,rw); + goto Done; + } + + if ((error = ali1563_transaction(a, size))) + goto Done; + + if ((rw == I2C_SMBUS_WRITE) || (size == HST_CNTL2_QUICK)) + goto Done; + + switch (size) { + case HST_CNTL2_BYTE: /* Result put in SMBHSTDAT0 */ + data->byte = inb_p(SMB_HST_DAT0); + break; + case HST_CNTL2_BYTE_DATA: + data->byte = inb_p(SMB_HST_DAT0); + break; + case HST_CNTL2_WORD_DATA: + data->word = inb_p(SMB_HST_DAT0) + (inb_p(SMB_HST_DAT1) << 8); + break; + } +Done: + return error; +} + +static u32 ali1563_func(struct i2c_adapter * a) +{ + return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_BLOCK_DATA; +} + + +static int __devinit ali1563_setup(struct pci_dev * dev) +{ + u16 ctrl; + + pci_read_config_word(dev,ALI1563_SMBBA,&ctrl); + + /* SMB I/O Base in high 12 bits and must be aligned with the + * size of the I/O space. */ + ali1563_smba = ctrl & ~(ALI1563_SMB_IOSIZE - 1); + if (!ali1563_smba) { + dev_warn(&dev->dev,"ali1563_smba Uninitialized\n"); + goto Err; + } + + /* Check if device is enabled */ + if (!(ctrl & ALI1563_SMB_HOSTEN)) { + dev_warn(&dev->dev, "Host Controller not enabled\n"); + goto Err; + } + if (!(ctrl & ALI1563_SMB_IOEN)) { + dev_warn(&dev->dev, "I/O space not enabled, trying manually\n"); + pci_write_config_word(dev, ALI1563_SMBBA, + ctrl | ALI1563_SMB_IOEN); + pci_read_config_word(dev, ALI1563_SMBBA, &ctrl); + if (!(ctrl & ALI1563_SMB_IOEN)) { + dev_err(&dev->dev, "I/O space still not enabled, " + "giving up\n"); + goto Err; + } + } + + if (acpi_check_region(ali1563_smba, ALI1563_SMB_IOSIZE, + ali1563_pci_driver.name)) + goto Err; + + if (!request_region(ali1563_smba, ALI1563_SMB_IOSIZE, + ali1563_pci_driver.name)) { + dev_err(&dev->dev, "Could not allocate I/O space at 0x%04x\n", + ali1563_smba); + goto Err; + } + dev_info(&dev->dev, "Found ALi1563 SMBus at 0x%04x\n", ali1563_smba); + + return 0; +Err: + return -ENODEV; +} + +static void ali1563_shutdown(struct pci_dev *dev) +{ + release_region(ali1563_smba,ALI1563_SMB_IOSIZE); +} + +static const struct i2c_algorithm ali1563_algorithm = { + .smbus_xfer = ali1563_access, + .functionality = ali1563_func, +}; + +static struct i2c_adapter ali1563_adapter = { + .owner = THIS_MODULE, + .id = I2C_HW_SMBUS_ALI1563, + .class = I2C_CLASS_HWMON | I2C_CLASS_SPD, + .algo = &ali1563_algorithm, +}; + +static int __devinit ali1563_probe(struct pci_dev * dev, + const struct pci_device_id * id_table) +{ + int error; + + if ((error = ali1563_setup(dev))) + goto exit; + ali1563_adapter.dev.parent = &dev->dev; + sprintf(ali1563_adapter.name,"SMBus ALi 1563 Adapter @ %04x", + ali1563_smba); + if ((error = i2c_add_adapter(&ali1563_adapter))) + goto exit_shutdown; + return 0; + +exit_shutdown: + ali1563_shutdown(dev); +exit: + dev_warn(&dev->dev, "ALi1563 SMBus probe failed (%d)\n", error); + return error; +} + +static void __devexit ali1563_remove(struct pci_dev * dev) +{ + i2c_del_adapter(&ali1563_adapter); + ali1563_shutdown(dev); +} + +static struct pci_device_id __devinitdata ali1563_id_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M1563) }, + {}, +}; + +MODULE_DEVICE_TABLE (pci, ali1563_id_table); + +static struct pci_driver ali1563_pci_driver = { + .name = "ali1563_smbus", + .id_table = ali1563_id_table, + .probe = ali1563_probe, + .remove = __devexit_p(ali1563_remove), +}; + +static int __init ali1563_init(void) +{ + return pci_register_driver(&ali1563_pci_driver); +} + +module_init(ali1563_init); + +static void __exit ali1563_exit(void) +{ + pci_unregister_driver(&ali1563_pci_driver); +} + +module_exit(ali1563_exit); + +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/busses/i2c-ali15x3.c b/drivers/i2c/busses/i2c-ali15x3.c new file mode 100644 index 0000000..234fdde --- /dev/null +++ b/drivers/i2c/busses/i2c-ali15x3.c @@ -0,0 +1,534 @@ +/* + Copyright (c) 1999 Frodo Looijaard <frodol@dds.nl> and + Philip Edelbrock <phil@netroedge.com> and + Mark D. Studebaker <mdsxyz123@yahoo.com> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* + This is the driver for the SMB Host controller on + Acer Labs Inc. (ALI) M1541 and M1543C South Bridges. + + The M1543C is a South bridge for desktop systems. + The M1533 is a South bridge for portable systems. + They are part of the following ALI chipsets: + "Aladdin Pro 2": Includes the M1621 Slot 1 North bridge + with AGP and 100MHz CPU Front Side bus + "Aladdin V": Includes the M1541 Socket 7 North bridge + with AGP and 100MHz CPU Front Side bus + "Aladdin IV": Includes the M1541 Socket 7 North bridge + with host bus up to 83.3 MHz. + For an overview of these chips see http://www.acerlabs.com + + The M1533/M1543C devices appear as FOUR separate devices + on the PCI bus. An output of lspci will show something similar + to the following: + + 00:02.0 USB Controller: Acer Laboratories Inc. M5237 + 00:03.0 Bridge: Acer Laboratories Inc. M7101 + 00:07.0 ISA bridge: Acer Laboratories Inc. M1533 + 00:0f.0 IDE interface: Acer Laboratories Inc. M5229 + + The SMB controller is part of the 7101 device, which is an + ACPI-compliant Power Management Unit (PMU). + + The whole 7101 device has to be enabled for the SMB to work. + You can't just enable the SMB alone. + The SMB and the ACPI have separate I/O spaces. + We make sure that the SMB is enabled. We leave the ACPI alone. + + This driver controls the SMB Host only. + The SMB Slave controller on the M15X3 is not enabled. + + This driver does not use interrupts. +*/ + +/* Note: we assume there can only be one ALI15X3, with one SMBus interface */ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/kernel.h> +#include <linux/stddef.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/acpi.h> +#include <asm/io.h> + +/* ALI15X3 SMBus address offsets */ +#define SMBHSTSTS (0 + ali15x3_smba) +#define SMBHSTCNT (1 + ali15x3_smba) +#define SMBHSTSTART (2 + ali15x3_smba) +#define SMBHSTCMD (7 + ali15x3_smba) +#define SMBHSTADD (3 + ali15x3_smba) +#define SMBHSTDAT0 (4 + ali15x3_smba) +#define SMBHSTDAT1 (5 + ali15x3_smba) +#define SMBBLKDAT (6 + ali15x3_smba) + +/* PCI Address Constants */ +#define SMBCOM 0x004 +#define SMBBA 0x014 +#define SMBATPC 0x05B /* used to unlock xxxBA registers */ +#define SMBHSTCFG 0x0E0 +#define SMBSLVC 0x0E1 +#define SMBCLK 0x0E2 +#define SMBREV 0x008 + +/* Other settings */ +#define MAX_TIMEOUT 200 /* times 1/100 sec */ +#define ALI15X3_SMB_IOSIZE 32 + +/* this is what the Award 1004 BIOS sets them to on a ASUS P5A MB. + We don't use these here. If the bases aren't set to some value we + tell user to upgrade BIOS and we fail. +*/ +#define ALI15X3_SMB_DEFAULTBASE 0xE800 + +/* ALI15X3 address lock bits */ +#define ALI15X3_LOCK 0x06 + +/* ALI15X3 command constants */ +#define ALI15X3_ABORT 0x02 +#define ALI15X3_T_OUT 0x04 +#define ALI15X3_QUICK 0x00 +#define ALI15X3_BYTE 0x10 +#define ALI15X3_BYTE_DATA 0x20 +#define ALI15X3_WORD_DATA 0x30 +#define ALI15X3_BLOCK_DATA 0x40 +#define ALI15X3_BLOCK_CLR 0x80 + +/* ALI15X3 status register bits */ +#define ALI15X3_STS_IDLE 0x04 +#define ALI15X3_STS_BUSY 0x08 +#define ALI15X3_STS_DONE 0x10 +#define ALI15X3_STS_DEV 0x20 /* device error */ +#define ALI15X3_STS_COLL 0x40 /* collision or no response */ +#define ALI15X3_STS_TERM 0x80 /* terminated by abort */ +#define ALI15X3_STS_ERR 0xE0 /* all the bad error bits */ + + +/* If force_addr is set to anything different from 0, we forcibly enable + the device at the given address. */ +static u16 force_addr; +module_param(force_addr, ushort, 0); +MODULE_PARM_DESC(force_addr, + "Initialize the base address of the i2c controller"); + +static struct pci_driver ali15x3_driver; +static unsigned short ali15x3_smba; + +static int ali15x3_setup(struct pci_dev *ALI15X3_dev) +{ + u16 a; + unsigned char temp; + + /* Check the following things: + - SMB I/O address is initialized + - Device is enabled + - We can use the addresses + */ + + /* Unlock the register. + The data sheet says that the address registers are read-only + if the lock bits are 1, but in fact the address registers + are zero unless you clear the lock bits. + */ + pci_read_config_byte(ALI15X3_dev, SMBATPC, &temp); + if (temp & ALI15X3_LOCK) { + temp &= ~ALI15X3_LOCK; + pci_write_config_byte(ALI15X3_dev, SMBATPC, temp); + } + + /* Determine the address of the SMBus area */ + pci_read_config_word(ALI15X3_dev, SMBBA, &ali15x3_smba); + ali15x3_smba &= (0xffff & ~(ALI15X3_SMB_IOSIZE - 1)); + if (ali15x3_smba == 0 && force_addr == 0) { + dev_err(&ALI15X3_dev->dev, "ALI15X3_smb region uninitialized " + "- upgrade BIOS or use force_addr=0xaddr\n"); + return -ENODEV; + } + + if(force_addr) + ali15x3_smba = force_addr & ~(ALI15X3_SMB_IOSIZE - 1); + + if (acpi_check_region(ali15x3_smba, ALI15X3_SMB_IOSIZE, + ali15x3_driver.name)) + return -EBUSY; + + if (!request_region(ali15x3_smba, ALI15X3_SMB_IOSIZE, + ali15x3_driver.name)) { + dev_err(&ALI15X3_dev->dev, + "ALI15X3_smb region 0x%x already in use!\n", + ali15x3_smba); + return -ENODEV; + } + + if(force_addr) { + dev_info(&ALI15X3_dev->dev, "forcing ISA address 0x%04X\n", + ali15x3_smba); + if (PCIBIOS_SUCCESSFUL != pci_write_config_word(ALI15X3_dev, + SMBBA, + ali15x3_smba)) + goto error; + if (PCIBIOS_SUCCESSFUL != pci_read_config_word(ALI15X3_dev, + SMBBA, &a)) + goto error; + if ((a & ~(ALI15X3_SMB_IOSIZE - 1)) != ali15x3_smba) { + /* make sure it works */ + dev_err(&ALI15X3_dev->dev, + "force address failed - not supported?\n"); + goto error; + } + } + /* check if whole device is enabled */ + pci_read_config_byte(ALI15X3_dev, SMBCOM, &temp); + if ((temp & 1) == 0) { + dev_info(&ALI15X3_dev->dev, "enabling SMBus device\n"); + pci_write_config_byte(ALI15X3_dev, SMBCOM, temp | 0x01); + } + + /* Is SMB Host controller enabled? */ + pci_read_config_byte(ALI15X3_dev, SMBHSTCFG, &temp); + if ((temp & 1) == 0) { + dev_info(&ALI15X3_dev->dev, "enabling SMBus controller\n"); + pci_write_config_byte(ALI15X3_dev, SMBHSTCFG, temp | 0x01); + } + + /* set SMB clock to 74KHz as recommended in data sheet */ + pci_write_config_byte(ALI15X3_dev, SMBCLK, 0x20); + + /* + The interrupt routing for SMB is set up in register 0x77 in the + 1533 ISA Bridge device, NOT in the 7101 device. + Don't bother with finding the 1533 device and reading the register. + if ((....... & 0x0F) == 1) + dev_dbg(&ALI15X3_dev->dev, "ALI15X3 using Interrupt 9 for SMBus.\n"); + */ + pci_read_config_byte(ALI15X3_dev, SMBREV, &temp); + dev_dbg(&ALI15X3_dev->dev, "SMBREV = 0x%X\n", temp); + dev_dbg(&ALI15X3_dev->dev, "iALI15X3_smba = 0x%X\n", ali15x3_smba); + + return 0; +error: + release_region(ali15x3_smba, ALI15X3_SMB_IOSIZE); + return -ENODEV; +} + +/* Another internally used function */ +static int ali15x3_transaction(struct i2c_adapter *adap) +{ + int temp; + int result = 0; + int timeout = 0; + + dev_dbg(&adap->dev, "Transaction (pre): STS=%02x, CNT=%02x, CMD=%02x, " + "ADD=%02x, DAT0=%02x, DAT1=%02x\n", inb_p(SMBHSTSTS), + inb_p(SMBHSTCNT), inb_p(SMBHSTCMD), inb_p(SMBHSTADD), + inb_p(SMBHSTDAT0), inb_p(SMBHSTDAT1)); + + /* get status */ + temp = inb_p(SMBHSTSTS); + + /* Make sure the SMBus host is ready to start transmitting */ + /* Check the busy bit first */ + if (temp & ALI15X3_STS_BUSY) { + /* + If the host controller is still busy, it may have timed out in the + previous transaction, resulting in a "SMBus Timeout" Dev. + I've tried the following to reset a stuck busy bit. + 1. Reset the controller with an ABORT command. + (this doesn't seem to clear the controller if an external + device is hung) + 2. Reset the controller and the other SMBus devices with a + T_OUT command. (this clears the host busy bit if an + external device is hung, but it comes back upon a new access + to a device) + 3. Disable and reenable the controller in SMBHSTCFG + Worst case, nothing seems to work except power reset. + */ + /* Abort - reset the host controller */ + /* + Try resetting entire SMB bus, including other devices - + This may not work either - it clears the BUSY bit but + then the BUSY bit may come back on when you try and use the chip again. + If that's the case you are stuck. + */ + dev_info(&adap->dev, "Resetting entire SMB Bus to " + "clear busy condition (%02x)\n", temp); + outb_p(ALI15X3_T_OUT, SMBHSTCNT); + temp = inb_p(SMBHSTSTS); + } + + /* now check the error bits and the busy bit */ + if (temp & (ALI15X3_STS_ERR | ALI15X3_STS_BUSY)) { + /* do a clear-on-write */ + outb_p(0xFF, SMBHSTSTS); + if ((temp = inb_p(SMBHSTSTS)) & + (ALI15X3_STS_ERR | ALI15X3_STS_BUSY)) { + /* this is probably going to be correctable only by a power reset + as one of the bits now appears to be stuck */ + /* This may be a bus or device with electrical problems. */ + dev_err(&adap->dev, "SMBus reset failed! (0x%02x) - " + "controller or device on bus is probably hung\n", + temp); + return -EBUSY; + } + } else { + /* check and clear done bit */ + if (temp & ALI15X3_STS_DONE) { + outb_p(temp, SMBHSTSTS); + } + } + + /* start the transaction by writing anything to the start register */ + outb_p(0xFF, SMBHSTSTART); + + /* We will always wait for a fraction of a second! */ + timeout = 0; + do { + msleep(1); + temp = inb_p(SMBHSTSTS); + } while ((!(temp & (ALI15X3_STS_ERR | ALI15X3_STS_DONE))) + && (timeout++ < MAX_TIMEOUT)); + + /* If the SMBus is still busy, we give up */ + if (timeout >= MAX_TIMEOUT) { + result = -ETIMEDOUT; + dev_err(&adap->dev, "SMBus Timeout!\n"); + } + + if (temp & ALI15X3_STS_TERM) { + result = -EIO; + dev_dbg(&adap->dev, "Error: Failed bus transaction\n"); + } + + /* + Unfortunately the ALI SMB controller maps "no response" and "bus + collision" into a single bit. No reponse is the usual case so don't + do a printk. + This means that bus collisions go unreported. + */ + if (temp & ALI15X3_STS_COLL) { + result = -ENXIO; + dev_dbg(&adap->dev, + "Error: no response or bus collision ADD=%02x\n", + inb_p(SMBHSTADD)); + } + + /* haven't ever seen this */ + if (temp & ALI15X3_STS_DEV) { + result = -EIO; + dev_err(&adap->dev, "Error: device error\n"); + } + dev_dbg(&adap->dev, "Transaction (post): STS=%02x, CNT=%02x, CMD=%02x, " + "ADD=%02x, DAT0=%02x, DAT1=%02x\n", inb_p(SMBHSTSTS), + inb_p(SMBHSTCNT), inb_p(SMBHSTCMD), inb_p(SMBHSTADD), + inb_p(SMBHSTDAT0), inb_p(SMBHSTDAT1)); + return result; +} + +/* Return negative errno on error. */ +static s32 ali15x3_access(struct i2c_adapter * adap, u16 addr, + unsigned short flags, char read_write, u8 command, + int size, union i2c_smbus_data * data) +{ + int i, len; + int temp; + int timeout; + + /* clear all the bits (clear-on-write) */ + outb_p(0xFF, SMBHSTSTS); + /* make sure SMBus is idle */ + temp = inb_p(SMBHSTSTS); + for (timeout = 0; + (timeout < MAX_TIMEOUT) && !(temp & ALI15X3_STS_IDLE); + timeout++) { + msleep(1); + temp = inb_p(SMBHSTSTS); + } + if (timeout >= MAX_TIMEOUT) { + dev_err(&adap->dev, "Idle wait Timeout! STS=0x%02x\n", temp); + } + + switch (size) { + case I2C_SMBUS_QUICK: + outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), + SMBHSTADD); + size = ALI15X3_QUICK; + break; + case I2C_SMBUS_BYTE: + outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), + SMBHSTADD); + if (read_write == I2C_SMBUS_WRITE) + outb_p(command, SMBHSTCMD); + size = ALI15X3_BYTE; + break; + case I2C_SMBUS_BYTE_DATA: + outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), + SMBHSTADD); + outb_p(command, SMBHSTCMD); + if (read_write == I2C_SMBUS_WRITE) + outb_p(data->byte, SMBHSTDAT0); + size = ALI15X3_BYTE_DATA; + break; + case I2C_SMBUS_WORD_DATA: + outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), + SMBHSTADD); + outb_p(command, SMBHSTCMD); + if (read_write == I2C_SMBUS_WRITE) { + outb_p(data->word & 0xff, SMBHSTDAT0); + outb_p((data->word & 0xff00) >> 8, SMBHSTDAT1); + } + size = ALI15X3_WORD_DATA; + break; + case I2C_SMBUS_BLOCK_DATA: + outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), + SMBHSTADD); + outb_p(command, SMBHSTCMD); + if (read_write == I2C_SMBUS_WRITE) { + len = data->block[0]; + if (len < 0) { + len = 0; + data->block[0] = len; + } + if (len > 32) { + len = 32; + data->block[0] = len; + } + outb_p(len, SMBHSTDAT0); + /* Reset SMBBLKDAT */ + outb_p(inb_p(SMBHSTCNT) | ALI15X3_BLOCK_CLR, SMBHSTCNT); + for (i = 1; i <= len; i++) + outb_p(data->block[i], SMBBLKDAT); + } + size = ALI15X3_BLOCK_DATA; + break; + default: + dev_warn(&adap->dev, "Unsupported transaction %d\n", size); + return -EOPNOTSUPP; + } + + outb_p(size, SMBHSTCNT); /* output command */ + + temp = ali15x3_transaction(adap); + if (temp) + return temp; + + if ((read_write == I2C_SMBUS_WRITE) || (size == ALI15X3_QUICK)) + return 0; + + + switch (size) { + case ALI15X3_BYTE: /* Result put in SMBHSTDAT0 */ + data->byte = inb_p(SMBHSTDAT0); + break; + case ALI15X3_BYTE_DATA: + data->byte = inb_p(SMBHSTDAT0); + break; + case ALI15X3_WORD_DATA: + data->word = inb_p(SMBHSTDAT0) + (inb_p(SMBHSTDAT1) << 8); + break; + case ALI15X3_BLOCK_DATA: + len = inb_p(SMBHSTDAT0); + if (len > 32) + len = 32; + data->block[0] = len; + /* Reset SMBBLKDAT */ + outb_p(inb_p(SMBHSTCNT) | ALI15X3_BLOCK_CLR, SMBHSTCNT); + for (i = 1; i <= data->block[0]; i++) { + data->block[i] = inb_p(SMBBLKDAT); + dev_dbg(&adap->dev, "Blk: len=%d, i=%d, data=%02x\n", + len, i, data->block[i]); + } + break; + } + return 0; +} + +static u32 ali15x3_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_BLOCK_DATA; +} + +static const struct i2c_algorithm smbus_algorithm = { + .smbus_xfer = ali15x3_access, + .functionality = ali15x3_func, +}; + +static struct i2c_adapter ali15x3_adapter = { + .owner = THIS_MODULE, + .id = I2C_HW_SMBUS_ALI15X3, + .class = I2C_CLASS_HWMON | I2C_CLASS_SPD, + .algo = &smbus_algorithm, +}; + +static struct pci_device_id ali15x3_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M7101) }, + { 0, } +}; + +MODULE_DEVICE_TABLE (pci, ali15x3_ids); + +static int __devinit ali15x3_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + if (ali15x3_setup(dev)) { + dev_err(&dev->dev, + "ALI15X3 not detected, module not inserted.\n"); + return -ENODEV; + } + + /* set up the sysfs linkage to our parent device */ + ali15x3_adapter.dev.parent = &dev->dev; + + snprintf(ali15x3_adapter.name, sizeof(ali15x3_adapter.name), + "SMBus ALI15X3 adapter at %04x", ali15x3_smba); + return i2c_add_adapter(&ali15x3_adapter); +} + +static void __devexit ali15x3_remove(struct pci_dev *dev) +{ + i2c_del_adapter(&ali15x3_adapter); + release_region(ali15x3_smba, ALI15X3_SMB_IOSIZE); +} + +static struct pci_driver ali15x3_driver = { + .name = "ali15x3_smbus", + .id_table = ali15x3_ids, + .probe = ali15x3_probe, + .remove = __devexit_p(ali15x3_remove), +}; + +static int __init i2c_ali15x3_init(void) +{ + return pci_register_driver(&ali15x3_driver); +} + +static void __exit i2c_ali15x3_exit(void) +{ + pci_unregister_driver(&ali15x3_driver); +} + +MODULE_AUTHOR ("Frodo Looijaard <frodol@dds.nl>, " + "Philip Edelbrock <phil@netroedge.com>, " + "and Mark D. Studebaker <mdsxyz123@yahoo.com>"); +MODULE_DESCRIPTION("ALI15X3 SMBus driver"); +MODULE_LICENSE("GPL"); + +module_init(i2c_ali15x3_init); +module_exit(i2c_ali15x3_exit); diff --git a/drivers/i2c/busses/i2c-amd756-s4882.c b/drivers/i2c/busses/i2c-amd756-s4882.c new file mode 100644 index 0000000..8ba2bcf --- /dev/null +++ b/drivers/i2c/busses/i2c-amd756-s4882.c @@ -0,0 +1,262 @@ +/* + * i2c-amd756-s4882.c - i2c-amd756 extras for the Tyan S4882 motherboard + * + * Copyright (C) 2004, 2008 Jean Delvare <khali@linux-fr.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * We select the channels by sending commands to the Philips + * PCA9556 chip at I2C address 0x18. The main adapter is used for + * the non-multiplexed part of the bus, and 4 virtual adapters + * are defined for the multiplexed addresses: 0x50-0x53 (memory + * module EEPROM) located on channels 1-4, and 0x4c (LM63) + * located on multiplexed channels 0 and 5-7. We define one + * virtual adapter per CPU, which corresponds to two multiplexed + * channels: + * CPU0: virtual adapter 1, channels 1 and 0 + * CPU1: virtual adapter 2, channels 2 and 5 + * CPU2: virtual adapter 3, channels 3 and 6 + * CPU3: virtual adapter 4, channels 4 and 7 + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/mutex.h> + +extern struct i2c_adapter amd756_smbus; + +static struct i2c_adapter *s4882_adapter; +static struct i2c_algorithm *s4882_algo; + +/* Wrapper access functions for multiplexed SMBus */ +static DEFINE_MUTEX(amd756_lock); + +static s32 amd756_access_virt0(struct i2c_adapter * adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, + union i2c_smbus_data * data) +{ + int error; + + /* We exclude the multiplexed addresses */ + if (addr == 0x4c || (addr & 0xfc) == 0x50 || (addr & 0xfc) == 0x30 + || addr == 0x18) + return -ENXIO; + + mutex_lock(&amd756_lock); + + error = amd756_smbus.algo->smbus_xfer(adap, addr, flags, read_write, + command, size, data); + + mutex_unlock(&amd756_lock); + + return error; +} + +/* We remember the last used channels combination so as to only switch + channels when it is really needed. This greatly reduces the SMBus + overhead, but also assumes that nobody will be writing to the PCA9556 + in our back. */ +static u8 last_channels; + +static inline s32 amd756_access_channel(struct i2c_adapter * adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, + union i2c_smbus_data * data, + u8 channels) +{ + int error; + + /* We exclude the non-multiplexed addresses */ + if (addr != 0x4c && (addr & 0xfc) != 0x50 && (addr & 0xfc) != 0x30) + return -ENXIO; + + mutex_lock(&amd756_lock); + + if (last_channels != channels) { + union i2c_smbus_data mplxdata; + mplxdata.byte = channels; + + error = amd756_smbus.algo->smbus_xfer(adap, 0x18, 0, + I2C_SMBUS_WRITE, 0x01, + I2C_SMBUS_BYTE_DATA, + &mplxdata); + if (error) + goto UNLOCK; + last_channels = channels; + } + error = amd756_smbus.algo->smbus_xfer(adap, addr, flags, read_write, + command, size, data); + +UNLOCK: + mutex_unlock(&amd756_lock); + return error; +} + +static s32 amd756_access_virt1(struct i2c_adapter * adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, + union i2c_smbus_data * data) +{ + /* CPU0: channels 1 and 0 enabled */ + return amd756_access_channel(adap, addr, flags, read_write, command, + size, data, 0x03); +} + +static s32 amd756_access_virt2(struct i2c_adapter * adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, + union i2c_smbus_data * data) +{ + /* CPU1: channels 2 and 5 enabled */ + return amd756_access_channel(adap, addr, flags, read_write, command, + size, data, 0x24); +} + +static s32 amd756_access_virt3(struct i2c_adapter * adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, + union i2c_smbus_data * data) +{ + /* CPU2: channels 3 and 6 enabled */ + return amd756_access_channel(adap, addr, flags, read_write, command, + size, data, 0x48); +} + +static s32 amd756_access_virt4(struct i2c_adapter * adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, + union i2c_smbus_data * data) +{ + /* CPU3: channels 4 and 7 enabled */ + return amd756_access_channel(adap, addr, flags, read_write, command, + size, data, 0x90); +} + +static int __init amd756_s4882_init(void) +{ + int i, error; + union i2c_smbus_data ioconfig; + + if (!amd756_smbus.dev.parent) + return -ENODEV; + + /* Configure the PCA9556 multiplexer */ + ioconfig.byte = 0x00; /* All I/O to output mode */ + error = i2c_smbus_xfer(&amd756_smbus, 0x18, 0, I2C_SMBUS_WRITE, 0x03, + I2C_SMBUS_BYTE_DATA, &ioconfig); + if (error) { + dev_err(&amd756_smbus.dev, "PCA9556 configuration failed\n"); + error = -EIO; + goto ERROR0; + } + + /* Unregister physical bus */ + error = i2c_del_adapter(&amd756_smbus); + if (error) { + dev_err(&amd756_smbus.dev, "Physical bus removal failed\n"); + goto ERROR0; + } + + printk(KERN_INFO "Enabling SMBus multiplexing for Tyan S4882\n"); + /* Define the 5 virtual adapters and algorithms structures */ + if (!(s4882_adapter = kzalloc(5 * sizeof(struct i2c_adapter), + GFP_KERNEL))) { + error = -ENOMEM; + goto ERROR1; + } + if (!(s4882_algo = kzalloc(5 * sizeof(struct i2c_algorithm), + GFP_KERNEL))) { + error = -ENOMEM; + goto ERROR2; + } + + /* Fill in the new structures */ + s4882_algo[0] = *(amd756_smbus.algo); + s4882_algo[0].smbus_xfer = amd756_access_virt0; + s4882_adapter[0] = amd756_smbus; + s4882_adapter[0].algo = s4882_algo; + s4882_adapter[0].dev.parent = amd756_smbus.dev.parent; + for (i = 1; i < 5; i++) { + s4882_algo[i] = *(amd756_smbus.algo); + s4882_adapter[i] = amd756_smbus; + sprintf(s4882_adapter[i].name, + "SMBus 8111 adapter (CPU%d)", i-1); + s4882_adapter[i].algo = s4882_algo+i; + s4882_adapter[i].dev.parent = amd756_smbus.dev.parent; + } + s4882_algo[1].smbus_xfer = amd756_access_virt1; + s4882_algo[2].smbus_xfer = amd756_access_virt2; + s4882_algo[3].smbus_xfer = amd756_access_virt3; + s4882_algo[4].smbus_xfer = amd756_access_virt4; + + /* Register virtual adapters */ + for (i = 0; i < 5; i++) { + error = i2c_add_adapter(s4882_adapter+i); + if (error) { + printk(KERN_ERR "i2c-amd756-s4882: " + "Virtual adapter %d registration " + "failed, module not inserted\n", i); + for (i--; i >= 0; i--) + i2c_del_adapter(s4882_adapter+i); + goto ERROR3; + } + } + + return 0; + +ERROR3: + kfree(s4882_algo); + s4882_algo = NULL; +ERROR2: + kfree(s4882_adapter); + s4882_adapter = NULL; +ERROR1: + /* Restore physical bus */ + i2c_add_adapter(&amd756_smbus); +ERROR0: + return error; +} + +static void __exit amd756_s4882_exit(void) +{ + if (s4882_adapter) { + int i; + + for (i = 0; i < 5; i++) + i2c_del_adapter(s4882_adapter+i); + kfree(s4882_adapter); + s4882_adapter = NULL; + } + kfree(s4882_algo); + s4882_algo = NULL; + + /* Restore physical bus */ + if (i2c_add_adapter(&amd756_smbus)) + printk(KERN_ERR "i2c-amd756-s4882: " + "Physical bus restoration failed\n"); +} + +MODULE_AUTHOR("Jean Delvare <khali@linux-fr.org>"); +MODULE_DESCRIPTION("S4882 SMBus multiplexing"); +MODULE_LICENSE("GPL"); + +module_init(amd756_s4882_init); +module_exit(amd756_s4882_exit); diff --git a/drivers/i2c/busses/i2c-amd756.c b/drivers/i2c/busses/i2c-amd756.c new file mode 100644 index 0000000..424dad6 --- /dev/null +++ b/drivers/i2c/busses/i2c-amd756.c @@ -0,0 +1,430 @@ +/* + Copyright (c) 1999-2002 Merlin Hughes <merlin@merlin.org> + + Shamelessly ripped from i2c-piix4.c: + + Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl> and + Philip Edelbrock <phil@netroedge.com> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* + 2002-04-08: Added nForce support. (Csaba Halasz) + 2002-10-03: Fixed nForce PnP I/O port. (Michael Steil) + 2002-12-28: Rewritten into something that resembles a Linux driver (hch) + 2003-11-29: Added back AMD8111 removed by the previous rewrite. + (Philip Pokorny) +*/ + +/* + Supports AMD756, AMD766, AMD768, AMD8111 and nVidia nForce + Note: we assume there can only be one device, with one SMBus interface. +*/ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/stddef.h> +#include <linux/ioport.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/acpi.h> +#include <asm/io.h> + +/* AMD756 SMBus address offsets */ +#define SMB_ADDR_OFFSET 0xE0 +#define SMB_IOSIZE 16 +#define SMB_GLOBAL_STATUS (0x0 + amd756_ioport) +#define SMB_GLOBAL_ENABLE (0x2 + amd756_ioport) +#define SMB_HOST_ADDRESS (0x4 + amd756_ioport) +#define SMB_HOST_DATA (0x6 + amd756_ioport) +#define SMB_HOST_COMMAND (0x8 + amd756_ioport) +#define SMB_HOST_BLOCK_DATA (0x9 + amd756_ioport) +#define SMB_HAS_DATA (0xA + amd756_ioport) +#define SMB_HAS_DEVICE_ADDRESS (0xC + amd756_ioport) +#define SMB_HAS_HOST_ADDRESS (0xE + amd756_ioport) +#define SMB_SNOOP_ADDRESS (0xF + amd756_ioport) + +/* PCI Address Constants */ + +/* address of I/O space */ +#define SMBBA 0x058 /* mh */ +#define SMBBANFORCE 0x014 + +/* general configuration */ +#define SMBGCFG 0x041 /* mh */ + +/* silicon revision code */ +#define SMBREV 0x008 + +/* Other settings */ +#define MAX_TIMEOUT 500 + +/* AMD756 constants */ +#define AMD756_QUICK 0x00 +#define AMD756_BYTE 0x01 +#define AMD756_BYTE_DATA 0x02 +#define AMD756_WORD_DATA 0x03 +#define AMD756_PROCESS_CALL 0x04 +#define AMD756_BLOCK_DATA 0x05 + +static struct pci_driver amd756_driver; +static unsigned short amd756_ioport; + +/* + SMBUS event = I/O 28-29 bit 11 + see E0 for the status bits and enabled in E2 + +*/ +#define GS_ABRT_STS (1 << 0) +#define GS_COL_STS (1 << 1) +#define GS_PRERR_STS (1 << 2) +#define GS_HST_STS (1 << 3) +#define GS_HCYC_STS (1 << 4) +#define GS_TO_STS (1 << 5) +#define GS_SMB_STS (1 << 11) + +#define GS_CLEAR_STS (GS_ABRT_STS | GS_COL_STS | GS_PRERR_STS | \ + GS_HCYC_STS | GS_TO_STS ) + +#define GE_CYC_TYPE_MASK (7) +#define GE_HOST_STC (1 << 3) +#define GE_ABORT (1 << 5) + + +static int amd756_transaction(struct i2c_adapter *adap) +{ + int temp; + int result = 0; + int timeout = 0; + + dev_dbg(&adap->dev, "Transaction (pre): GS=%04x, GE=%04x, ADD=%04x, " + "DAT=%04x\n", inw_p(SMB_GLOBAL_STATUS), + inw_p(SMB_GLOBAL_ENABLE), inw_p(SMB_HOST_ADDRESS), + inb_p(SMB_HOST_DATA)); + + /* Make sure the SMBus host is ready to start transmitting */ + if ((temp = inw_p(SMB_GLOBAL_STATUS)) & (GS_HST_STS | GS_SMB_STS)) { + dev_dbg(&adap->dev, "SMBus busy (%04x). Waiting...\n", temp); + do { + msleep(1); + temp = inw_p(SMB_GLOBAL_STATUS); + } while ((temp & (GS_HST_STS | GS_SMB_STS)) && + (timeout++ < MAX_TIMEOUT)); + /* If the SMBus is still busy, we give up */ + if (timeout >= MAX_TIMEOUT) { + dev_dbg(&adap->dev, "Busy wait timeout (%04x)\n", temp); + goto abort; + } + timeout = 0; + } + + /* start the transaction by setting the start bit */ + outw_p(inw(SMB_GLOBAL_ENABLE) | GE_HOST_STC, SMB_GLOBAL_ENABLE); + + /* We will always wait for a fraction of a second! */ + do { + msleep(1); + temp = inw_p(SMB_GLOBAL_STATUS); + } while ((temp & GS_HST_STS) && (timeout++ < MAX_TIMEOUT)); + + /* If the SMBus is still busy, we give up */ + if (timeout >= MAX_TIMEOUT) { + dev_dbg(&adap->dev, "Completion timeout!\n"); + goto abort; + } + + if (temp & GS_PRERR_STS) { + result = -ENXIO; + dev_dbg(&adap->dev, "SMBus Protocol error (no response)!\n"); + } + + if (temp & GS_COL_STS) { + result = -EIO; + dev_warn(&adap->dev, "SMBus collision!\n"); + } + + if (temp & GS_TO_STS) { + result = -ETIMEDOUT; + dev_dbg(&adap->dev, "SMBus protocol timeout!\n"); + } + + if (temp & GS_HCYC_STS) + dev_dbg(&adap->dev, "SMBus protocol success!\n"); + + outw_p(GS_CLEAR_STS, SMB_GLOBAL_STATUS); + +#ifdef DEBUG + if (((temp = inw_p(SMB_GLOBAL_STATUS)) & GS_CLEAR_STS) != 0x00) { + dev_dbg(&adap->dev, + "Failed reset at end of transaction (%04x)\n", temp); + } +#endif + + dev_dbg(&adap->dev, + "Transaction (post): GS=%04x, GE=%04x, ADD=%04x, DAT=%04x\n", + inw_p(SMB_GLOBAL_STATUS), inw_p(SMB_GLOBAL_ENABLE), + inw_p(SMB_HOST_ADDRESS), inb_p(SMB_HOST_DATA)); + + return result; + + abort: + dev_warn(&adap->dev, "Sending abort\n"); + outw_p(inw(SMB_GLOBAL_ENABLE) | GE_ABORT, SMB_GLOBAL_ENABLE); + msleep(100); + outw_p(GS_CLEAR_STS, SMB_GLOBAL_STATUS); + return -EIO; +} + +/* Return negative errno on error. */ +static s32 amd756_access(struct i2c_adapter * adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, union i2c_smbus_data * data) +{ + int i, len; + int status; + + switch (size) { + case I2C_SMBUS_QUICK: + outw_p(((addr & 0x7f) << 1) | (read_write & 0x01), + SMB_HOST_ADDRESS); + size = AMD756_QUICK; + break; + case I2C_SMBUS_BYTE: + outw_p(((addr & 0x7f) << 1) | (read_write & 0x01), + SMB_HOST_ADDRESS); + if (read_write == I2C_SMBUS_WRITE) + outb_p(command, SMB_HOST_DATA); + size = AMD756_BYTE; + break; + case I2C_SMBUS_BYTE_DATA: + outw_p(((addr & 0x7f) << 1) | (read_write & 0x01), + SMB_HOST_ADDRESS); + outb_p(command, SMB_HOST_COMMAND); + if (read_write == I2C_SMBUS_WRITE) + outw_p(data->byte, SMB_HOST_DATA); + size = AMD756_BYTE_DATA; + break; + case I2C_SMBUS_WORD_DATA: + outw_p(((addr & 0x7f) << 1) | (read_write & 0x01), + SMB_HOST_ADDRESS); + outb_p(command, SMB_HOST_COMMAND); + if (read_write == I2C_SMBUS_WRITE) + outw_p(data->word, SMB_HOST_DATA); /* TODO: endian???? */ + size = AMD756_WORD_DATA; + break; + case I2C_SMBUS_BLOCK_DATA: + outw_p(((addr & 0x7f) << 1) | (read_write & 0x01), + SMB_HOST_ADDRESS); + outb_p(command, SMB_HOST_COMMAND); + if (read_write == I2C_SMBUS_WRITE) { + len = data->block[0]; + if (len < 0) + len = 0; + if (len > 32) + len = 32; + outw_p(len, SMB_HOST_DATA); + /* i = inw_p(SMBHSTCNT); Reset SMBBLKDAT */ + for (i = 1; i <= len; i++) + outb_p(data->block[i], + SMB_HOST_BLOCK_DATA); + } + size = AMD756_BLOCK_DATA; + break; + default: + dev_warn(&adap->dev, "Unsupported transaction %d\n", size); + return -EOPNOTSUPP; + } + + /* How about enabling interrupts... */ + outw_p(size & GE_CYC_TYPE_MASK, SMB_GLOBAL_ENABLE); + + status = amd756_transaction(adap); + if (status) + return status; + + if ((read_write == I2C_SMBUS_WRITE) || (size == AMD756_QUICK)) + return 0; + + + switch (size) { + case AMD756_BYTE: + data->byte = inw_p(SMB_HOST_DATA); + break; + case AMD756_BYTE_DATA: + data->byte = inw_p(SMB_HOST_DATA); + break; + case AMD756_WORD_DATA: + data->word = inw_p(SMB_HOST_DATA); /* TODO: endian???? */ + break; + case AMD756_BLOCK_DATA: + data->block[0] = inw_p(SMB_HOST_DATA) & 0x3f; + if(data->block[0] > 32) + data->block[0] = 32; + /* i = inw_p(SMBHSTCNT); Reset SMBBLKDAT */ + for (i = 1; i <= data->block[0]; i++) + data->block[i] = inb_p(SMB_HOST_BLOCK_DATA); + break; + } + + return 0; +} + +static u32 amd756_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_BLOCK_DATA; +} + +static const struct i2c_algorithm smbus_algorithm = { + .smbus_xfer = amd756_access, + .functionality = amd756_func, +}; + +struct i2c_adapter amd756_smbus = { + .owner = THIS_MODULE, + .id = I2C_HW_SMBUS_AMD756, + .class = I2C_CLASS_HWMON | I2C_CLASS_SPD, + .algo = &smbus_algorithm, +}; + +enum chiptype { AMD756, AMD766, AMD768, NFORCE, AMD8111 }; +static const char* chipname[] = { + "AMD756", "AMD766", "AMD768", + "nVidia nForce", "AMD8111", +}; + +static struct pci_device_id amd756_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_VIPER_740B), + .driver_data = AMD756 }, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_VIPER_7413), + .driver_data = AMD766 }, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_OPUS_7443), + .driver_data = AMD768 }, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_8111_SMBUS), + .driver_data = AMD8111 }, + { PCI_DEVICE(PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_SMBUS), + .driver_data = NFORCE }, + { 0, } +}; + +MODULE_DEVICE_TABLE (pci, amd756_ids); + +static int __devinit amd756_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + int nforce = (id->driver_data == NFORCE); + int error; + u8 temp; + + if (amd756_ioport) { + dev_err(&pdev->dev, "Only one device supported " + "(you have a strange motherboard, btw)\n"); + return -ENODEV; + } + + if (nforce) { + if (PCI_FUNC(pdev->devfn) != 1) + return -ENODEV; + + pci_read_config_word(pdev, SMBBANFORCE, &amd756_ioport); + amd756_ioport &= 0xfffc; + } else { /* amd */ + if (PCI_FUNC(pdev->devfn) != 3) + return -ENODEV; + + pci_read_config_byte(pdev, SMBGCFG, &temp); + if ((temp & 128) == 0) { + dev_err(&pdev->dev, + "Error: SMBus controller I/O not enabled!\n"); + return -ENODEV; + } + + /* Determine the address of the SMBus areas */ + /* Technically it is a dword but... */ + pci_read_config_word(pdev, SMBBA, &amd756_ioport); + amd756_ioport &= 0xff00; + amd756_ioport += SMB_ADDR_OFFSET; + } + + error = acpi_check_region(amd756_ioport, SMB_IOSIZE, + amd756_driver.name); + if (error) + return error; + + if (!request_region(amd756_ioport, SMB_IOSIZE, amd756_driver.name)) { + dev_err(&pdev->dev, "SMB region 0x%x already in use!\n", + amd756_ioport); + return -ENODEV; + } + + pci_read_config_byte(pdev, SMBREV, &temp); + dev_dbg(&pdev->dev, "SMBREV = 0x%X\n", temp); + dev_dbg(&pdev->dev, "AMD756_smba = 0x%X\n", amd756_ioport); + + /* set up the sysfs linkage to our parent device */ + amd756_smbus.dev.parent = &pdev->dev; + + sprintf(amd756_smbus.name, "SMBus %s adapter at %04x", + chipname[id->driver_data], amd756_ioport); + + error = i2c_add_adapter(&amd756_smbus); + if (error) { + dev_err(&pdev->dev, + "Adapter registration failed, module not inserted\n"); + goto out_err; + } + + return 0; + + out_err: + release_region(amd756_ioport, SMB_IOSIZE); + return error; +} + +static void __devexit amd756_remove(struct pci_dev *dev) +{ + i2c_del_adapter(&amd756_smbus); + release_region(amd756_ioport, SMB_IOSIZE); +} + +static struct pci_driver amd756_driver = { + .name = "amd756_smbus", + .id_table = amd756_ids, + .probe = amd756_probe, + .remove = __devexit_p(amd756_remove), +}; + +static int __init amd756_init(void) +{ + return pci_register_driver(&amd756_driver); +} + +static void __exit amd756_exit(void) +{ + pci_unregister_driver(&amd756_driver); +} + +MODULE_AUTHOR("Merlin Hughes <merlin@merlin.org>"); +MODULE_DESCRIPTION("AMD756/766/768/8111 and nVidia nForce SMBus driver"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(amd756_smbus); + +module_init(amd756_init) +module_exit(amd756_exit) diff --git a/drivers/i2c/busses/i2c-amd8111.c b/drivers/i2c/busses/i2c-amd8111.c new file mode 100644 index 0000000..c688237 --- /dev/null +++ b/drivers/i2c/busses/i2c-amd8111.c @@ -0,0 +1,440 @@ +/* + * SMBus 2.0 driver for AMD-8111 IO-Hub. + * + * Copyright (c) 2002 Vojtech Pavlik + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation version 2. + */ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/kernel.h> +#include <linux/stddef.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/acpi.h> +#include <asm/io.h> + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR ("Vojtech Pavlik <vojtech@suse.cz>"); +MODULE_DESCRIPTION("AMD8111 SMBus 2.0 driver"); + +struct amd_smbus { + struct pci_dev *dev; + struct i2c_adapter adapter; + int base; + int size; +}; + +static struct pci_driver amd8111_driver; + +/* + * AMD PCI control registers definitions. + */ + +#define AMD_PCI_MISC 0x48 + +#define AMD_PCI_MISC_SCI 0x04 /* deliver SCI */ +#define AMD_PCI_MISC_INT 0x02 /* deliver PCI IRQ */ +#define AMD_PCI_MISC_SPEEDUP 0x01 /* 16x clock speedup */ + +/* + * ACPI 2.0 chapter 13 PCI interface definitions. + */ + +#define AMD_EC_DATA 0x00 /* data register */ +#define AMD_EC_SC 0x04 /* status of controller */ +#define AMD_EC_CMD 0x04 /* command register */ +#define AMD_EC_ICR 0x08 /* interrupt control register */ + +#define AMD_EC_SC_SMI 0x04 /* smi event pending */ +#define AMD_EC_SC_SCI 0x02 /* sci event pending */ +#define AMD_EC_SC_BURST 0x01 /* burst mode enabled */ +#define AMD_EC_SC_CMD 0x08 /* byte in data reg is command */ +#define AMD_EC_SC_IBF 0x02 /* data ready for embedded controller */ +#define AMD_EC_SC_OBF 0x01 /* data ready for host */ + +#define AMD_EC_CMD_RD 0x80 /* read EC */ +#define AMD_EC_CMD_WR 0x81 /* write EC */ +#define AMD_EC_CMD_BE 0x82 /* enable burst mode */ +#define AMD_EC_CMD_BD 0x83 /* disable burst mode */ +#define AMD_EC_CMD_QR 0x84 /* query EC */ + +/* + * ACPI 2.0 chapter 13 access of registers of the EC + */ + +static unsigned int amd_ec_wait_write(struct amd_smbus *smbus) +{ + int timeout = 500; + + while ((inb(smbus->base + AMD_EC_SC) & AMD_EC_SC_IBF) && --timeout) + udelay(1); + + if (!timeout) { + dev_warn(&smbus->dev->dev, + "Timeout while waiting for IBF to clear\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static unsigned int amd_ec_wait_read(struct amd_smbus *smbus) +{ + int timeout = 500; + + while ((~inb(smbus->base + AMD_EC_SC) & AMD_EC_SC_OBF) && --timeout) + udelay(1); + + if (!timeout) { + dev_warn(&smbus->dev->dev, + "Timeout while waiting for OBF to set\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static unsigned int amd_ec_read(struct amd_smbus *smbus, unsigned char address, + unsigned char *data) +{ + int status; + + status = amd_ec_wait_write(smbus); + if (status) + return status; + outb(AMD_EC_CMD_RD, smbus->base + AMD_EC_CMD); + + status = amd_ec_wait_write(smbus); + if (status) + return status; + outb(address, smbus->base + AMD_EC_DATA); + + status = amd_ec_wait_read(smbus); + if (status) + return status; + *data = inb(smbus->base + AMD_EC_DATA); + + return 0; +} + +static unsigned int amd_ec_write(struct amd_smbus *smbus, unsigned char address, + unsigned char data) +{ + int status; + + status = amd_ec_wait_write(smbus); + if (status) + return status; + outb(AMD_EC_CMD_WR, smbus->base + AMD_EC_CMD); + + status = amd_ec_wait_write(smbus); + if (status) + return status; + outb(address, smbus->base + AMD_EC_DATA); + + status = amd_ec_wait_write(smbus); + if (status) + return status; + outb(data, smbus->base + AMD_EC_DATA); + + return 0; +} + +/* + * ACPI 2.0 chapter 13 SMBus 2.0 EC register model + */ + +#define AMD_SMB_PRTCL 0x00 /* protocol, PEC */ +#define AMD_SMB_STS 0x01 /* status */ +#define AMD_SMB_ADDR 0x02 /* address */ +#define AMD_SMB_CMD 0x03 /* command */ +#define AMD_SMB_DATA 0x04 /* 32 data registers */ +#define AMD_SMB_BCNT 0x24 /* number of data bytes */ +#define AMD_SMB_ALRM_A 0x25 /* alarm address */ +#define AMD_SMB_ALRM_D 0x26 /* 2 bytes alarm data */ + +#define AMD_SMB_STS_DONE 0x80 +#define AMD_SMB_STS_ALRM 0x40 +#define AMD_SMB_STS_RES 0x20 +#define AMD_SMB_STS_STATUS 0x1f + +#define AMD_SMB_STATUS_OK 0x00 +#define AMD_SMB_STATUS_FAIL 0x07 +#define AMD_SMB_STATUS_DNAK 0x10 +#define AMD_SMB_STATUS_DERR 0x11 +#define AMD_SMB_STATUS_CMD_DENY 0x12 +#define AMD_SMB_STATUS_UNKNOWN 0x13 +#define AMD_SMB_STATUS_ACC_DENY 0x17 +#define AMD_SMB_STATUS_TIMEOUT 0x18 +#define AMD_SMB_STATUS_NOTSUP 0x19 +#define AMD_SMB_STATUS_BUSY 0x1A +#define AMD_SMB_STATUS_PEC 0x1F + +#define AMD_SMB_PRTCL_WRITE 0x00 +#define AMD_SMB_PRTCL_READ 0x01 +#define AMD_SMB_PRTCL_QUICK 0x02 +#define AMD_SMB_PRTCL_BYTE 0x04 +#define AMD_SMB_PRTCL_BYTE_DATA 0x06 +#define AMD_SMB_PRTCL_WORD_DATA 0x08 +#define AMD_SMB_PRTCL_BLOCK_DATA 0x0a +#define AMD_SMB_PRTCL_PROC_CALL 0x0c +#define AMD_SMB_PRTCL_BLOCK_PROC_CALL 0x0d +#define AMD_SMB_PRTCL_I2C_BLOCK_DATA 0x4a +#define AMD_SMB_PRTCL_PEC 0x80 + + +static s32 amd8111_access(struct i2c_adapter * adap, u16 addr, + unsigned short flags, char read_write, u8 command, int size, + union i2c_smbus_data * data) +{ + struct amd_smbus *smbus = adap->algo_data; + unsigned char protocol, len, pec, temp[2]; + int i; + + protocol = (read_write == I2C_SMBUS_READ) ? AMD_SMB_PRTCL_READ + : AMD_SMB_PRTCL_WRITE; + pec = (flags & I2C_CLIENT_PEC) ? AMD_SMB_PRTCL_PEC : 0; + + switch (size) { + case I2C_SMBUS_QUICK: + protocol |= AMD_SMB_PRTCL_QUICK; + read_write = I2C_SMBUS_WRITE; + break; + + case I2C_SMBUS_BYTE: + if (read_write == I2C_SMBUS_WRITE) + amd_ec_write(smbus, AMD_SMB_CMD, command); + protocol |= AMD_SMB_PRTCL_BYTE; + break; + + case I2C_SMBUS_BYTE_DATA: + amd_ec_write(smbus, AMD_SMB_CMD, command); + if (read_write == I2C_SMBUS_WRITE) + amd_ec_write(smbus, AMD_SMB_DATA, data->byte); + protocol |= AMD_SMB_PRTCL_BYTE_DATA; + break; + + case I2C_SMBUS_WORD_DATA: + amd_ec_write(smbus, AMD_SMB_CMD, command); + if (read_write == I2C_SMBUS_WRITE) { + amd_ec_write(smbus, AMD_SMB_DATA, + data->word & 0xff); + amd_ec_write(smbus, AMD_SMB_DATA + 1, + data->word >> 8); + } + protocol |= AMD_SMB_PRTCL_WORD_DATA | pec; + break; + + case I2C_SMBUS_BLOCK_DATA: + amd_ec_write(smbus, AMD_SMB_CMD, command); + if (read_write == I2C_SMBUS_WRITE) { + len = min_t(u8, data->block[0], + I2C_SMBUS_BLOCK_MAX); + amd_ec_write(smbus, AMD_SMB_BCNT, len); + for (i = 0; i < len; i++) + amd_ec_write(smbus, AMD_SMB_DATA + i, + data->block[i + 1]); + } + protocol |= AMD_SMB_PRTCL_BLOCK_DATA | pec; + break; + + case I2C_SMBUS_I2C_BLOCK_DATA: + len = min_t(u8, data->block[0], + I2C_SMBUS_BLOCK_MAX); + amd_ec_write(smbus, AMD_SMB_CMD, command); + amd_ec_write(smbus, AMD_SMB_BCNT, len); + if (read_write == I2C_SMBUS_WRITE) + for (i = 0; i < len; i++) + amd_ec_write(smbus, AMD_SMB_DATA + i, + data->block[i + 1]); + protocol |= AMD_SMB_PRTCL_I2C_BLOCK_DATA; + break; + + case I2C_SMBUS_PROC_CALL: + amd_ec_write(smbus, AMD_SMB_CMD, command); + amd_ec_write(smbus, AMD_SMB_DATA, data->word & 0xff); + amd_ec_write(smbus, AMD_SMB_DATA + 1, data->word >> 8); + protocol = AMD_SMB_PRTCL_PROC_CALL | pec; + read_write = I2C_SMBUS_READ; + break; + + case I2C_SMBUS_BLOCK_PROC_CALL: + len = min_t(u8, data->block[0], + I2C_SMBUS_BLOCK_MAX - 1); + amd_ec_write(smbus, AMD_SMB_CMD, command); + amd_ec_write(smbus, AMD_SMB_BCNT, len); + for (i = 0; i < len; i++) + amd_ec_write(smbus, AMD_SMB_DATA + i, + data->block[i + 1]); + protocol = AMD_SMB_PRTCL_BLOCK_PROC_CALL | pec; + read_write = I2C_SMBUS_READ; + break; + + default: + dev_warn(&adap->dev, "Unsupported transaction %d\n", size); + return -EOPNOTSUPP; + } + + amd_ec_write(smbus, AMD_SMB_ADDR, addr << 1); + amd_ec_write(smbus, AMD_SMB_PRTCL, protocol); + + /* FIXME this discards status from ec_read(); so temp[0] will + * hold stack garbage ... the rest of this routine will act + * nonsensically. Ignored ec_write() status might explain + * some such failures... + */ + amd_ec_read(smbus, AMD_SMB_STS, temp + 0); + + if (~temp[0] & AMD_SMB_STS_DONE) { + udelay(500); + amd_ec_read(smbus, AMD_SMB_STS, temp + 0); + } + + if (~temp[0] & AMD_SMB_STS_DONE) { + msleep(1); + amd_ec_read(smbus, AMD_SMB_STS, temp + 0); + } + + if ((~temp[0] & AMD_SMB_STS_DONE) || (temp[0] & AMD_SMB_STS_STATUS)) + return -EIO; + + if (read_write == I2C_SMBUS_WRITE) + return 0; + + switch (size) { + case I2C_SMBUS_BYTE: + case I2C_SMBUS_BYTE_DATA: + amd_ec_read(smbus, AMD_SMB_DATA, &data->byte); + break; + + case I2C_SMBUS_WORD_DATA: + case I2C_SMBUS_PROC_CALL: + amd_ec_read(smbus, AMD_SMB_DATA, temp + 0); + amd_ec_read(smbus, AMD_SMB_DATA + 1, temp + 1); + data->word = (temp[1] << 8) | temp[0]; + break; + + case I2C_SMBUS_BLOCK_DATA: + case I2C_SMBUS_BLOCK_PROC_CALL: + amd_ec_read(smbus, AMD_SMB_BCNT, &len); + len = min_t(u8, len, I2C_SMBUS_BLOCK_MAX); + case I2C_SMBUS_I2C_BLOCK_DATA: + for (i = 0; i < len; i++) + amd_ec_read(smbus, AMD_SMB_DATA + i, + data->block + i + 1); + data->block[0] = len; + break; + } + + return 0; +} + + +static u32 amd8111_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BLOCK_DATA | + I2C_FUNC_SMBUS_PROC_CALL | I2C_FUNC_SMBUS_BLOCK_PROC_CALL | + I2C_FUNC_SMBUS_I2C_BLOCK | I2C_FUNC_SMBUS_PEC; +} + +static const struct i2c_algorithm smbus_algorithm = { + .smbus_xfer = amd8111_access, + .functionality = amd8111_func, +}; + + +static struct pci_device_id amd8111_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_8111_SMBUS2) }, + { 0, } +}; + +MODULE_DEVICE_TABLE (pci, amd8111_ids); + +static int __devinit amd8111_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + struct amd_smbus *smbus; + int error; + + if (!(pci_resource_flags(dev, 0) & IORESOURCE_IO)) + return -ENODEV; + + smbus = kzalloc(sizeof(struct amd_smbus), GFP_KERNEL); + if (!smbus) + return -ENOMEM; + + smbus->dev = dev; + smbus->base = pci_resource_start(dev, 0); + smbus->size = pci_resource_len(dev, 0); + + error = acpi_check_resource_conflict(&dev->resource[0]); + if (error) + goto out_kfree; + + if (!request_region(smbus->base, smbus->size, amd8111_driver.name)) { + error = -EBUSY; + goto out_kfree; + } + + smbus->adapter.owner = THIS_MODULE; + snprintf(smbus->adapter.name, sizeof(smbus->adapter.name), + "SMBus2 AMD8111 adapter at %04x", smbus->base); + smbus->adapter.id = I2C_HW_SMBUS_AMD8111; + smbus->adapter.class = I2C_CLASS_HWMON | I2C_CLASS_SPD; + smbus->adapter.algo = &smbus_algorithm; + smbus->adapter.algo_data = smbus; + + /* set up the sysfs linkage to our parent device */ + smbus->adapter.dev.parent = &dev->dev; + + pci_write_config_dword(smbus->dev, AMD_PCI_MISC, 0); + error = i2c_add_adapter(&smbus->adapter); + if (error) + goto out_release_region; + + pci_set_drvdata(dev, smbus); + return 0; + + out_release_region: + release_region(smbus->base, smbus->size); + out_kfree: + kfree(smbus); + return error; +} + +static void __devexit amd8111_remove(struct pci_dev *dev) +{ + struct amd_smbus *smbus = pci_get_drvdata(dev); + + i2c_del_adapter(&smbus->adapter); + release_region(smbus->base, smbus->size); + kfree(smbus); +} + +static struct pci_driver amd8111_driver = { + .name = "amd8111_smbus2", + .id_table = amd8111_ids, + .probe = amd8111_probe, + .remove = __devexit_p(amd8111_remove), +}; + +static int __init i2c_amd8111_init(void) +{ + return pci_register_driver(&amd8111_driver); +} + +static void __exit i2c_amd8111_exit(void) +{ + pci_unregister_driver(&amd8111_driver); +} + +module_init(i2c_amd8111_init); +module_exit(i2c_amd8111_exit); diff --git a/drivers/i2c/busses/i2c-ast.c b/drivers/i2c/busses/i2c-ast.c new file mode 100644 index 0000000..22ce167 --- /dev/null +++ b/drivers/i2c/busses/i2c-ast.c @@ -0,0 +1,1995 @@ +/* + * i2c_adap_ast.c + * + * I2C adapter for the ASPEED I2C bus access. + * + * Copyright (C) 2012-2020 ASPEED Technology Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * History: + * 2012.07.26: Initial version [Ryan Chen] + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/i2c-id.h> +#include <linux/init.h> +#include <linux/time.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/completion.h> + +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/clk.h> + +#include <linux/dma-mapping.h> + +#include <asm/irq.h> +#include <asm/io.h> + +#if defined(CONFIG_COLDFIRE) +#include <asm/arch/regs-iic.h> +#include <asm/arch/ast_i2c.h> +#else +#include <plat/regs-iic.h> +#include <plat/ast_i2c.h> +#endif + +//AST2400 buffer mode issue , force I2C slave write use byte mode , read use buffer mode +/* Use platform_data instead of module parameters */ +/* Fast Mode = 400 kHz, Standard = 100 kHz */ +//static int clock = 100; /* Default: 100 kHz */ + + +/***************************************************************************/ + +#ifdef CONFIG_AST_I2C_SLAVE_RDWR +#define I2C_S_BUF_SIZE 256 +#define I2C_S_RX_BUF_NUM 20 +#define BUFF_FULL 0xff00 +#define BUFF_ONGOING 1 +#endif + +#define AST_LOCKUP_DETECTED (0x1 << 15) + +// Enable SCL/SDA pull LOW detection for Yosemite platform +#ifdef CONFIG_YOSEMITE +#define AST_I2C_LOW_TIMEOUT 0x07 +#else +#define AST_I2C_LOW_TIMEOUT 0x00 +#endif //CONFIG_YOSEMITE + + +struct ast_i2c_dev { + struct ast_i2c_driver_data *ast_i2c_data; + struct device *dev; + void __iomem *reg_base; /* virtual */ + int irq; //I2C IRQ number + u32 bus_id; //for i2c dev# IRQ number check + u32 state; //I2C xfer mode state matchine + struct i2c_adapter adap; + struct buf_page *req_page; +//dma or buff mode needed + unsigned char *dma_buf; + dma_addr_t dma_addr; + +//master + int xfer_last; //cur xfer is last msgs for stop msgs + struct i2c_msg *master_msgs; //cur xfer msgs + int master_xfer_len; //cur xfer len + int master_xfer_cnt; //total xfer count + u32 master_xfer_mode; //cur xfer mode ... 0 : no_op , master: 1 byte , 2 : buffer , 3: dma , slave : xxxx + struct completion cmd_complete; + int cmd_err; + u8 blk_r_flag; //for smbus block read + void (*do_master_xfer)(struct ast_i2c_dev *i2c_dev); + spinlock_t master_lock; +//Slave structure + u8 slave_operation; + u8 slave_event; + struct i2c_msg *slave_msgs; //cur slave xfer msgs + int slave_xfer_len; + int slave_xfer_cnt; + u32 slave_xfer_mode; //cur xfer mode ... 0 : no_op , master: 1 byte , 2 : buffer , 3: dma , slave : xxxx + void (*do_slave_xfer)(struct ast_i2c_dev *i2c_dev); +#ifdef CONFIG_AST_I2C_SLAVE_RDWR + struct i2c_msg slave_rx_msg[I2C_S_RX_BUF_NUM + 1]; + struct i2c_msg slave_tx_msg; + spinlock_t slave_rx_lock; +#endif +}; + + + +static inline void +ast_i2c_write(struct ast_i2c_dev *i2c_dev, u32 val, u32 reg) +{ +// dev_dbg(i2c_dev->dev, "ast_i2c_write : val: %x , reg : %x \n",val,reg); + writel(val, i2c_dev->reg_base+ reg); +} + +static inline u32 +ast_i2c_read(struct ast_i2c_dev *i2c_dev, u32 reg) +{ +#if 0 + u32 val = readl(i2c_dev->reg_base + reg); + printk("R : reg %x , val: %x \n",reg, val); + return val; +#else + return readl(i2c_dev->reg_base + reg); +#endif +} + +static u32 select_i2c_clock(struct ast_i2c_dev *i2c_dev) +{ + + unsigned int clk, inc = 0, div, divider_ratio; + u32 SCL_Low, SCL_High, data; + + clk = i2c_dev->ast_i2c_data->get_i2c_clock(); +// printk("pclk = %d \n",clk); + divider_ratio = clk / i2c_dev->ast_i2c_data->bus_clk; + for (div = 0; divider_ratio >= 16; div++) + { + inc |= (divider_ratio & 1); + divider_ratio >>= 1; + } + divider_ratio += inc; + SCL_Low = (divider_ratio >> 1) - 1; + SCL_High = divider_ratio - SCL_Low - 2; + data = 0x77700300 | (SCL_High << 16) | (SCL_Low << 12) | div; +// printk("I2CD04 for %d = %08X\n", target_speed, data); + return data; +} + +#ifdef CONFIG_AST_I2C_SLAVE_MODE +/* AST I2C Slave mode */ +static void ast_slave_issue_alert(struct ast_i2c_dev *i2c_dev, u8 enable) +{ + //only support dev0~3 + if(i2c_dev->bus_id > 3) + return; + else { + if(enable) + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_CMD_REG) | AST_I2CD_S_ALT_EN, I2C_CMD_REG); + else + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_CMD_REG) & ~AST_I2CD_S_ALT_EN, I2C_CMD_REG); + } +} + +static void ast_slave_mode_enable(struct ast_i2c_dev *i2c_dev, struct i2c_msg *msgs) +{ + if(msgs->buf[0] == 1) { + ast_i2c_write(i2c_dev, msgs->addr, I2C_DEV_ADDR_REG); + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_FUN_CTRL_REG) | AST_I2CD_SLAVE_EN, I2C_FUN_CTRL_REG); + } else + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_FUN_CTRL_REG) & ~AST_I2CD_SLAVE_EN, I2C_FUN_CTRL_REG); +} + +#endif + +static void ast_i2c_dev_init(struct ast_i2c_dev *i2c_dev) +{ + //I2CG Reset + ast_i2c_write(i2c_dev, 0, I2C_FUN_CTRL_REG); + +#ifdef CONFIG_AST_I2C_SLAVE_MODE +#ifdef CONFIG_AST_I2C_SLAVE_EEPROM + i2c_dev->ast_i2c_data->slave_init(&(i2c_dev->slave_msgs)); + ast_slave_mode_enable(i2c_dev, i2c_dev->slave_msgs); +#else + i2c_dev->slave_msgs = i2c_dev->slave_rx_msg; +#endif +#endif + + //Enable Master Mode + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev, I2C_FUN_CTRL_REG) | AST_I2CD_MASTER_EN, I2C_FUN_CTRL_REG); + + + /* Set AC Timing */ +#if defined(CONFIG_ARCH_AST2400) + if(i2c_dev->ast_i2c_data->bus_clk/1000 > 400) { + printk("high speed mode enable clk [%dkhz]\n",i2c_dev->ast_i2c_data->bus_clk/1000); + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev, I2C_FUN_CTRL_REG) | + AST_I2CD_M_HIGH_SPEED_EN | + AST_I2CD_M_SDA_DRIVE_1T_EN | + AST_I2CD_SDA_DRIVE_1T_EN + , I2C_FUN_CTRL_REG); + + /* Set AC Timing */ + ast_i2c_write(i2c_dev, AST_I2C_LOW_TIMEOUT, I2C_AC_TIMING_REG2); + ast_i2c_write(i2c_dev, select_i2c_clock(i2c_dev), I2C_AC_TIMING_REG1); + }else { + /* target apeed is xxKhz*/ + ast_i2c_write(i2c_dev, select_i2c_clock(i2c_dev), I2C_AC_TIMING_REG1); + ast_i2c_write(i2c_dev, AST_I2C_LOW_TIMEOUT, I2C_AC_TIMING_REG2); + } +#else + /* target apeed is xxKhz*/ + ast_i2c_write(i2c_dev, select_i2c_clock(i2c_dev), I2C_AC_TIMING_REG1); + ast_i2c_write(i2c_dev, AST_NO_TIMEOUT_CTRL, I2C_AC_TIMING_REG2); +#endif +// ast_i2c_write(i2c_dev, 0x77743335, I2C_AC_TIMING_REG1); +///// + + + //Clear Interrupt + ast_i2c_write(i2c_dev, 0xfffffff, I2C_INTR_STS_REG); + + //TODO +// ast_i2c_write(i2c_dev, 0xAF, I2C_INTR_CTRL_REG); + //Enable Interrupt, STOP Interrupt has bug in AST2000 + + /* Set interrupt generation of I2C controller */ + ast_i2c_write(i2c_dev, + AST_I2CD_SDA_DL_TO_INTR_EN | + AST_I2CD_BUS_RECOVER_INTR_EN | + AST_I2CD_SMBUS_ALT_INTR_EN | +// AST_I2CD_SLAVE_MATCH_INTR_EN | + AST_I2CD_SCL_TO_INTR_EN | + AST_I2CD_ABNORMAL_INTR_EN | + AST_I2CD_NORMAL_STOP_INTR_EN | + AST_I2CD_ARBIT_LOSS_INTR_EN | + AST_I2CD_RX_DOWN_INTR_EN | + AST_I2CD_TX_NAK_INTR_EN | + AST_I2CD_TX_ACK_INTR_EN, + I2C_INTR_CTRL_REG); + +} + +#ifdef CONFIG_AST_I2C_SLAVE_RDWR +//for memory buffer initial +static void ast_i2c_slave_buff_init(struct ast_i2c_dev *i2c_dev) +{ + int i; + //Tx buf 1 + i2c_dev->slave_tx_msg.len = I2C_S_BUF_SIZE; + i2c_dev->slave_tx_msg.buf = kzalloc(I2C_S_BUF_SIZE, GFP_KERNEL); + //Rx buf 4 + for(i=0; i<I2C_S_RX_BUF_NUM+1; i++) { + i2c_dev->slave_rx_msg[i].addr = ~BUFF_ONGOING; + i2c_dev->slave_rx_msg[i].flags = 0; //mean empty buffer + i2c_dev->slave_rx_msg[i].len = I2C_S_BUF_SIZE; + i2c_dev->slave_rx_msg[i].buf = kzalloc(I2C_S_BUF_SIZE, GFP_KERNEL); + } +} + +static void ast_i2c_slave_rdwr_xfer(struct ast_i2c_dev *i2c_dev) +{ + int i; + int count = 0; + unsigned long flags; + + spin_lock_irqsave(&i2c_dev->slave_rx_lock, flags); + + switch(i2c_dev->slave_event) { + case I2C_SLAVE_EVENT_START_WRITE: + for(i=0; i<I2C_S_RX_BUF_NUM; i++) { + if((i2c_dev->slave_rx_msg[i].flags == 0) && (i2c_dev->slave_rx_msg[i].addr != BUFF_ONGOING)) { + i2c_dev->slave_rx_msg[i].addr = BUFF_ONGOING; + break; + } + } + if(i == I2C_S_RX_BUF_NUM) { + // dev_err(i2c_dev->dev, "RX buffer full ........use tmp msgs buff \n"); + for(i=0; i<I2C_S_RX_BUF_NUM; i++) { + if((i2c_dev->slave_rx_msg[i].flags == 0) && (i2c_dev->slave_rx_msg[i].addr == BUFF_ONGOING)) { + count++; + i2c_dev->slave_rx_msg[i].addr = 0; + } + } + + if (count) { + dev_err(i2c_dev->dev, "Cleared slave ongoing buffers of count: %d\n", count); + } + + for(i=0; i<I2C_S_RX_BUF_NUM; i++) { + if((i2c_dev->slave_rx_msg[i].flags == 0) && (i2c_dev->slave_rx_msg[i].addr != BUFF_ONGOING)) { + i2c_dev->slave_rx_msg[i].addr = BUFF_ONGOING; + break; + } + } + } + //printk("I2C_SLAVE_EVENT_START_WRITE ... %d \n", i); + + i2c_dev->slave_msgs = &i2c_dev->slave_rx_msg[i]; + break; + case I2C_SLAVE_EVENT_START_READ: + // printk("I2C_SLAVE_EVENT_START_READ ERROR .. not imple \n"); + i2c_dev->slave_msgs = &i2c_dev->slave_tx_msg; + break; + case I2C_SLAVE_EVENT_WRITE: + //printk("I2C_SLAVE_EVENT_WRITE next write ERROR ...\n"); + i2c_dev->slave_msgs = &i2c_dev->slave_tx_msg; + break; + case I2C_SLAVE_EVENT_READ: + printk("I2C_SLAVE_EVENT_READ ERROR ... \n"); + i2c_dev->slave_msgs = &i2c_dev->slave_tx_msg; + break; + case I2C_SLAVE_EVENT_NACK: + //printk("I2C_SLAVE_EVENT_NACK ERROR ... \n"); + i2c_dev->slave_msgs = &i2c_dev->slave_tx_msg; + break; + case I2C_SLAVE_EVENT_STOP: + //printk("I2C_SLAVE_EVENT_STOP \n"); + for(i=0; i<I2C_S_RX_BUF_NUM; i++) { + if(i2c_dev->slave_rx_msg[i].addr == BUFF_ONGOING) { + i2c_dev->slave_rx_msg[i].flags = BUFF_FULL; + i2c_dev->slave_rx_msg[i].addr = 0; + break; + } + } + + i2c_dev->slave_msgs = &i2c_dev->slave_tx_msg; + break; + } + spin_unlock_irqrestore(&i2c_dev->slave_rx_lock, flags); + +} + +static int ast_i2c_slave_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs) +{ + struct ast_i2c_dev *i2c_dev = adap->algo_data; + int ret=0, i; + unsigned long flags; + + switch(msgs->flags) { + case 0: +// printk("slave read \n"); + //cur_msg = get_free_msg; + spin_lock_irqsave(&i2c_dev->slave_rx_lock, flags); + + for(i=0; i<I2C_S_RX_BUF_NUM; i++) { + if((i2c_dev->slave_rx_msg[i].addr == 0) && (i2c_dev->slave_rx_msg[i].flags == BUFF_FULL)) { + memcpy(msgs->buf, i2c_dev->slave_rx_msg[i].buf, i2c_dev->slave_rx_msg[i].len); + msgs->len = i2c_dev->slave_rx_msg[i].len; + i2c_dev->slave_rx_msg[i].flags = 0; + i2c_dev->slave_rx_msg[i].len = 0; + break; + } + } + spin_unlock_irqrestore(&i2c_dev->slave_rx_lock, flags); + + if(i == I2C_S_RX_BUF_NUM) { + //printk("No buffer ........ \n"); + msgs->len = 0; + ret = -1; + } + break; + case I2C_M_RD: //slave write +// printk("slave write \n"); + memcpy(msgs->buf, i2c_dev->slave_tx_msg.buf, I2C_S_BUF_SIZE); + break; + case I2C_S_EN: + if((msgs->addr < 0x1) || (msgs->addr > 0xff)) { + ret = -1; + printk("addrsss not correct !! \n"); + return ret; + } + if(msgs->len != 1) printk("ERROR \n"); + ast_slave_mode_enable(i2c_dev, msgs); + break; + case I2C_S_ALT: +// printk("slave issue alt\n"); + if(msgs->len != 1) printk("ERROR \n"); + if(msgs->buf[0]==1) + ast_slave_issue_alert(i2c_dev, 1); + else + ast_slave_issue_alert(i2c_dev, 0); + break; + + default: + printk("slave xfer error \n"); + break; + + } + return ret; +} + + +#endif + +static u8 +ast_i2c_bus_error_recover(struct ast_i2c_dev *i2c_dev) +{ + u32 sts; + int r; + u32 i = 0; + + //Check 0x14's SDA and SCL status + sts = ast_i2c_read(i2c_dev,I2C_CMD_REG); + + if ((sts & AST_I2CD_SDA_LINE_STS) && (sts & AST_I2CD_SCL_LINE_STS)) { + //Means bus is idle. + dev_err(i2c_dev->dev, "I2C bus (%d) is idle. I2C slave doesn't exist?!\n", i2c_dev->bus_id); + return -1; + } + + dev_err(i2c_dev->dev, "ERROR!! I2C(%d) bus hanged, try to recovery it!\n", i2c_dev->bus_id); + + + if ((sts & AST_I2CD_SDA_LINE_STS) && !(sts & AST_I2CD_SCL_LINE_STS)) { + //if SDA == 1 and SCL == 0, it means the master is locking the bus. + //Send a stop command to unlock the bus. + dev_err(i2c_dev->dev, "I2C's master is locking the bus, try to stop it.\n"); +// + init_completion(&i2c_dev->cmd_complete); + i2c_dev->cmd_err = 0; + + ast_i2c_write(i2c_dev, AST_I2CD_M_STOP_CMD, I2C_CMD_REG); + + r = wait_for_completion_interruptible_timeout(&i2c_dev->cmd_complete, + i2c_dev->adap.timeout*HZ); + + if(i2c_dev->cmd_err && + i2c_dev->cmd_err != AST_I2CD_INTR_STS_NORMAL_STOP) { + dev_err(i2c_dev->dev, "recovery error \n"); + return -1; + } + + if (r == 0) { + dev_err(i2c_dev->dev, "recovery timed out\n"); + return -1; + } else { + dev_err(i2c_dev->dev, "Recovery successfully\n"); + return 0; + } + + + } else if (!(sts & AST_I2CD_SDA_LINE_STS)) { + //else if SDA == 0, the device is dead. We need to reset the bus + //And do the recovery command. + dev_err(i2c_dev->dev, "I2C's slave is dead, try to recover it\n"); + //Let's retry 10 times + for (i = 0; i < 10; i++) { + ast_i2c_dev_init(i2c_dev); + //Do the recovery command BIT11 + init_completion(&i2c_dev->cmd_complete); + i2c_dev->cmd_err = 0; + ast_i2c_write(i2c_dev, AST_I2CD_BUS_RECOVER_CMD_EN, I2C_CMD_REG); + + r = wait_for_completion_interruptible_timeout(&i2c_dev->cmd_complete, + i2c_dev->adap.timeout*HZ); + if (i2c_dev->cmd_err != 0 && + i2c_dev->cmd_err != AST_I2CD_INTR_STS_NORMAL_STOP) { + dev_err(i2c_dev->dev, "ERROR!! Failed to do recovery command(0x%08x)\n", i2c_dev->cmd_err); + return -1; + } + //Check 0x14's SDA and SCL status + sts = ast_i2c_read(i2c_dev,I2C_CMD_REG); + if (sts & AST_I2CD_SDA_LINE_STS) //Recover OK + break; + } + if (i == 10) { + dev_err(i2c_dev->dev, "ERROR!! recover failed\n"); + return -1; + } + } else { + dev_err(i2c_dev->dev, "Don't know how to handle this case?!\n"); + return -1; + } + dev_err(i2c_dev->dev, "Recovery successfully\n"); + return 0; +} + +static void ast_master_alert_recv(struct ast_i2c_dev *i2c_dev) +{ + printk("ast_master_alert_recv bus id %d, Disable Alt, Please Imple \n",i2c_dev->bus_id); +} + +static int ast_i2c_wait_bus_not_busy(struct ast_i2c_dev *i2c_dev) +{ + int timeout = 10; //TODO number + volatile u8 mode = 0; +// printk("ast_i2c_wait_bus_not_busy \n"); + + // Wait for slave transfer to finish + mode = i2c_dev->slave_operation; + while (mode == 1) { + if (timeout <= 0) { + break; + } + mode = i2c_dev->slave_operation; + timeout--; + msleep(1); + } + + if (timeout <= 0) { + //TODO: sometimes the slave operation flag is not reset properly so go ahead with checking bus busy signal + dev_err(i2c_dev->dev, "slave operation set, check busy status line\n"); + } + + // Wait for Bus to go IDLE + timeout = 10; + while (ast_i2c_read(i2c_dev,I2C_CMD_REG) & AST_I2CD_BUS_BUSY_STS) { + if(timeout<=0) { + break; + } + + timeout--; + msleep(1); + } + + if (timeout <=0) { + ast_i2c_bus_error_recover(i2c_dev); + return 0; + } + + // TODO: hack to reset slave operation flag manually + i2c_dev->slave_operation = 0; + return 0; +} + +static void ast_i2c_do_dma_xfer(struct ast_i2c_dev *i2c_dev) +{ + u32 cmd = 0; + int i; + + if(i2c_dev->slave_operation == 1) { + if(i2c_dev->slave_msgs->flags & I2C_M_RD) { + //DMA tx mode + if(i2c_dev->slave_msgs->len > AST_I2C_DMA_SIZE) + i2c_dev->slave_xfer_len = AST_I2C_DMA_SIZE; + else + i2c_dev->slave_xfer_len = i2c_dev->slave_msgs->len; + + dev_dbg(i2c_dev->dev, "(<--) slave tx DMA \n"); + for(i=0; i<i2c_dev->slave_xfer_len; i++) + i2c_dev->dma_buf[i] = i2c_dev->slave_msgs->buf[i2c_dev->slave_xfer_cnt + i]; + + ast_i2c_write(i2c_dev, i2c_dev->dma_addr, I2C_DMA_BASE_REG); + ast_i2c_write(i2c_dev, (i2c_dev->slave_xfer_len-1), I2C_DMA_LEN_REG); + ast_i2c_write(i2c_dev, AST_I2CD_TX_DMA_ENABLE | AST_I2CD_S_TX_CMD,I2C_CMD_REG); + } else { + //DMA prepare rx + dev_dbg(i2c_dev->dev, "(-->) slave rx DMA \n"); + ast_i2c_write(i2c_dev, i2c_dev->dma_addr, I2C_DMA_BASE_REG); + ast_i2c_write(i2c_dev, (AST_I2C_DMA_SIZE-1), I2C_DMA_LEN_REG); + ast_i2c_write(i2c_dev, AST_I2CD_RX_DMA_ENABLE, I2C_CMD_REG); + } + } else { + dev_dbg(i2c_dev->dev,"M cnt %d, xf len %d \n",i2c_dev->master_xfer_cnt, i2c_dev->master_msgs->len); + if(i2c_dev->master_xfer_cnt == -1) { + //send start + dev_dbg(i2c_dev->dev, " %sing %d byte%s %s 0x%02x\n", + i2c_dev->master_msgs->flags & I2C_M_RD ? "read" : "write", + i2c_dev->master_msgs->len, i2c_dev->master_msgs->len > 1 ? "s" : "", + i2c_dev->master_msgs->flags & I2C_M_RD ? "from" : "to", i2c_dev->master_msgs->addr); + + if(i2c_dev->master_msgs->flags & I2C_M_RD) { + //workaround .. HW can;t send start read addr with buff mode + cmd = AST_I2CD_M_START_CMD | AST_I2CD_M_TX_CMD; + ast_i2c_write(i2c_dev, (i2c_dev->master_msgs->addr <<1) |0x1, I2C_BYTE_BUF_REG); + +// tx_buf[0] = (i2c_dev->master_msgs->addr <<1); //+1 + i2c_dev->master_xfer_len = 1; + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_INTR_CTRL_REG) | + AST_I2CD_TX_ACK_INTR_EN, I2C_INTR_CTRL_REG); + } else { + //tx + cmd = AST_I2CD_M_START_CMD | AST_I2CD_M_TX_CMD | AST_I2CD_TX_DMA_ENABLE; + + i2c_dev->dma_buf[0] = (i2c_dev->master_msgs->addr <<1); //+1 + //next data write + if((i2c_dev->master_msgs->len + 1) > AST_I2C_DMA_SIZE) + i2c_dev->master_xfer_len = AST_I2C_DMA_SIZE; + else + i2c_dev->master_xfer_len = i2c_dev->master_msgs->len + 1; + + for(i = 1; i < i2c_dev->master_xfer_len; i++) + i2c_dev->dma_buf[i] = i2c_dev->master_msgs->buf[i2c_dev->master_xfer_cnt+i]; + + if (i2c_dev->xfer_last == 1) { + dev_dbg(i2c_dev->dev, "last stop \n"); + cmd |= AST_I2CD_M_STOP_CMD; + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_INTR_CTRL_REG) & + ~AST_I2CD_TX_ACK_INTR_EN, I2C_INTR_CTRL_REG); + + } else { + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_INTR_CTRL_REG) | + AST_I2CD_TX_ACK_INTR_EN, I2C_INTR_CTRL_REG); + } + ast_i2c_write(i2c_dev, i2c_dev->dma_addr, I2C_DMA_BASE_REG); + ast_i2c_write(i2c_dev, (i2c_dev->master_xfer_len-1), I2C_DMA_LEN_REG); + + } + ast_i2c_write(i2c_dev, cmd, I2C_CMD_REG); + dev_dbg(i2c_dev->dev, "txfer size %d , cmd = %x \n",i2c_dev->master_xfer_len, cmd); + + } else if (i2c_dev->master_xfer_cnt < i2c_dev->master_msgs->len){ + //Next send + if(i2c_dev->master_msgs->flags & I2C_M_RD) { + //Rx data + cmd = AST_I2CD_M_RX_CMD | AST_I2CD_RX_DMA_ENABLE; + + if((i2c_dev->master_msgs->len - i2c_dev->master_xfer_cnt) > AST_I2C_DMA_SIZE) { + i2c_dev->master_xfer_len = AST_I2C_DMA_SIZE; + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_INTR_CTRL_REG) | + AST_I2CD_RX_DOWN_INTR_EN, I2C_INTR_CTRL_REG); + + } else { + i2c_dev->master_xfer_len = i2c_dev->master_msgs->len - i2c_dev->master_xfer_cnt; + if((i2c_dev->master_msgs->flags & I2C_M_RECV_LEN) && (i2c_dev->blk_r_flag == 0)) { + dev_dbg(i2c_dev->dev, "I2C_M_RECV_LEN \n"); + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_INTR_CTRL_REG) | + AST_I2CD_RX_DOWN_INTR_EN, I2C_INTR_CTRL_REG); + } else { +#ifdef CONFIG_AST1010 + //Workaround for ast1010 can't send NACK + if((i2c_dev->master_xfer_len == 1) && (i2c_dev->xfer_last == 1)) { + //change to byte mode + cmd |= AST_I2CD_M_STOP_CMD | AST_I2CD_M_S_RX_CMD_LAST; + cmd &= ~AST_I2CD_RX_DMA_ENABLE; + i2c_dev->master_xfer_mode = BYTE_XFER; + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_INTR_CTRL_REG) & + ~AST_I2CD_RX_DOWN_INTR_EN, I2C_INTR_CTRL_REG); + + } else if (i2c_dev->master_xfer_len > 1) { + i2c_dev->master_xfer_len -=1; + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_INTR_CTRL_REG) | + AST_I2CD_RX_DOWN_INTR_EN, I2C_INTR_CTRL_REG); + } else { + printk(" Fix Me !! \n"); + } +#else + if(i2c_dev->xfer_last == 1) { + dev_dbg(i2c_dev->dev, "last stop \n"); + cmd |= AST_I2CD_M_STOP_CMD; + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_INTR_CTRL_REG) & + ~AST_I2CD_RX_DOWN_INTR_EN, I2C_INTR_CTRL_REG); + } else { + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_INTR_CTRL_REG) | + AST_I2CD_RX_DOWN_INTR_EN, I2C_INTR_CTRL_REG); + } + //TODO check.... + cmd |= AST_I2CD_M_S_RX_CMD_LAST; +#endif + } + + } + ast_i2c_write(i2c_dev, i2c_dev->dma_addr, I2C_DMA_BASE_REG); + ast_i2c_write(i2c_dev, i2c_dev->master_xfer_len-1, I2C_DMA_LEN_REG); + ast_i2c_write(i2c_dev, cmd, I2C_CMD_REG); + dev_dbg(i2c_dev->dev, "rxfer size %d , cmd = %x \n",i2c_dev->master_xfer_len, cmd); + } else { + //Tx data + //next data write + cmd = AST_I2CD_M_TX_CMD | AST_I2CD_TX_DMA_ENABLE; + if((i2c_dev->master_msgs->len - i2c_dev->master_xfer_cnt) > AST_I2C_DMA_SIZE) { + i2c_dev->master_xfer_len = AST_I2C_DMA_SIZE; + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_INTR_CTRL_REG) | + AST_I2CD_TX_ACK_INTR_EN, I2C_INTR_CTRL_REG); + + } else { + i2c_dev->master_xfer_len = i2c_dev->master_msgs->len - i2c_dev->master_xfer_cnt; + if(i2c_dev->xfer_last == 1) { + dev_dbg(i2c_dev->dev, "last stop \n"); + cmd |= AST_I2CD_M_STOP_CMD; + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_INTR_CTRL_REG) & + ~AST_I2CD_TX_ACK_INTR_EN, I2C_INTR_CTRL_REG); + + } else { + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_INTR_CTRL_REG) | + AST_I2CD_TX_ACK_INTR_EN, I2C_INTR_CTRL_REG); + } + } + + for(i = 0; i < i2c_dev->master_xfer_len; i++) + i2c_dev->dma_buf[i] = i2c_dev->master_msgs->buf[i2c_dev->master_xfer_cnt + i]; + + ast_i2c_write(i2c_dev, i2c_dev->dma_addr, I2C_DMA_BASE_REG); + ast_i2c_write(i2c_dev, (i2c_dev->master_xfer_len-1), I2C_DMA_LEN_REG); + ast_i2c_write(i2c_dev, cmd , I2C_CMD_REG); + dev_dbg(i2c_dev->dev, "txfer size %d , cmd = %x \n",i2c_dev->master_xfer_len, cmd); + + } + }else { + //should send next msg + if(i2c_dev->master_xfer_cnt != i2c_dev->master_msgs->len) + printk("complete rx ... bus=%d addr=0x%x (%d vs. %d) ERROR\n", + i2c_dev->bus_id, i2c_dev->master_msgs->addr, + i2c_dev->master_xfer_cnt, i2c_dev->master_msgs->len); + + dev_dbg(i2c_dev->dev, "ast_i2c_do_byte_xfer complete \n"); + i2c_dev->cmd_err = 0; + complete(&i2c_dev->cmd_complete); + } + + } + + +} + +static void ast_i2c_do_pool_xfer(struct ast_i2c_dev *i2c_dev) +{ + u32 cmd = 0; + int i; + u32 *tx_buf; + +#if defined(CONFIG_ARCH_AST2400) + ast_i2c_write(i2c_dev, + (ast_i2c_read(i2c_dev, I2C_FUN_CTRL_REG) & + ~AST_I2CD_BUFF_SEL_MASK) | + AST_I2CD_BUFF_SEL(i2c_dev->req_page->page_no), + I2C_FUN_CTRL_REG); +#endif + + tx_buf = (u32 *) i2c_dev->req_page->page_addr; + + + if(i2c_dev->slave_operation == 1) { + if(i2c_dev->slave_msgs->flags & I2C_M_RD) { + dev_dbg(i2c_dev->dev, "(<--) slave tx buf \n"); + + if(i2c_dev->slave_msgs->len > i2c_dev->req_page->page_size) + i2c_dev->slave_xfer_len = i2c_dev->req_page->page_size; + else + i2c_dev->slave_xfer_len = i2c_dev->slave_msgs->len; + + for(i = 0; i< i2c_dev->slave_xfer_len; i++) { + if(i%4 == 0) + tx_buf[i/4] = 0; + tx_buf[i/4] |= (i2c_dev->slave_msgs->buf[i2c_dev->slave_xfer_cnt + i] << ((i%4)*8)) ; + dev_dbg(i2c_dev->dev, "[%x] ",tx_buf[i/4]); + } + dev_dbg(i2c_dev->dev, "\n"); + + ast_i2c_write(i2c_dev, AST_I2CD_TX_DATA_BUF_END_SET((i2c_dev->slave_xfer_len-1)) | + AST_I2CD_BUF_BASE_ADDR_SET((i2c_dev->req_page->page_addr_point)), + I2C_BUF_CTRL_REG); + + ast_i2c_write(i2c_dev, AST_I2CD_TX_BUFF_ENABLE | AST_I2CD_S_TX_CMD, I2C_CMD_REG); + } else { + //prepare for new rx + dev_dbg(i2c_dev->dev, "(-->) slave prepare rx buf \n"); + ast_i2c_write(i2c_dev, + AST_I2CD_RX_BUF_END_ADDR_SET((i2c_dev->req_page->page_size-1)) | + AST_I2CD_BUF_BASE_ADDR_SET((i2c_dev->req_page->page_addr_point)), + I2C_BUF_CTRL_REG); + + ast_i2c_write(i2c_dev, AST_I2CD_RX_BUFF_ENABLE, I2C_CMD_REG); + + } + } else { + dev_dbg(i2c_dev->dev,"M cnt %d, xf len %d \n",i2c_dev->master_xfer_cnt, i2c_dev->master_msgs->len); + if(i2c_dev->master_xfer_cnt == -1) { + //send start + dev_dbg(i2c_dev->dev, " %sing %d byte%s %s 0x%02x\n", + i2c_dev->master_msgs->flags & I2C_M_RD ? "read" : "write", + i2c_dev->master_msgs->len, i2c_dev->master_msgs->len > 1 ? "s" : "", + i2c_dev->master_msgs->flags & I2C_M_RD ? "from" : "to", i2c_dev->master_msgs->addr); + + if(i2c_dev->master_msgs->flags & I2C_M_RD) { +//workaround .. HW can;t send start read addr with buff mode + cmd = AST_I2CD_M_START_CMD | AST_I2CD_M_TX_CMD; + ast_i2c_write(i2c_dev, (i2c_dev->master_msgs->addr <<1) |0x1, I2C_BYTE_BUF_REG); + +// tx_buf[0] = (i2c_dev->master_msgs->addr <<1); //+1 + i2c_dev->master_xfer_len = 1; + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_INTR_CTRL_REG) | + AST_I2CD_TX_ACK_INTR_EN, I2C_INTR_CTRL_REG); + } else { + cmd = AST_I2CD_M_START_CMD | AST_I2CD_M_TX_CMD | AST_I2CD_TX_BUFF_ENABLE; + tx_buf[0] = (i2c_dev->master_msgs->addr <<1); //+1 + //next data write + if((i2c_dev->master_msgs->len + 1) > i2c_dev->req_page->page_size) + i2c_dev->master_xfer_len = i2c_dev->req_page->page_size; + else + i2c_dev->master_xfer_len = i2c_dev->master_msgs->len + 1; + + for(i = 1; i < i2c_dev->master_xfer_len; i++) { + if(i%4 == 0) + tx_buf[i/4] = 0; + tx_buf[i/4] |= (i2c_dev->master_msgs->buf[i2c_dev->master_xfer_cnt + i] << ((i%4)*8)) ; + } + + if (i2c_dev->xfer_last == 1) { + dev_dbg(i2c_dev->dev, "last stop \n"); + cmd |= AST_I2CD_M_STOP_CMD; + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_INTR_CTRL_REG) & + ~AST_I2CD_TX_ACK_INTR_EN, I2C_INTR_CTRL_REG); + + } else { + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_INTR_CTRL_REG) | + AST_I2CD_TX_ACK_INTR_EN, I2C_INTR_CTRL_REG); + } + ast_i2c_write(i2c_dev, + AST_I2CD_TX_DATA_BUF_END_SET((i2c_dev->master_xfer_len - 1)) | + AST_I2CD_BUF_BASE_ADDR_SET(i2c_dev->req_page->page_addr_point), + I2C_BUF_CTRL_REG); + } + ast_i2c_write(i2c_dev, cmd, I2C_CMD_REG); + dev_dbg(i2c_dev->dev, "txfer size %d , cmd = %x \n",i2c_dev->master_xfer_len, cmd); + + } else if (i2c_dev->master_xfer_cnt < i2c_dev->master_msgs->len){ + //Next send + if(i2c_dev->master_msgs->flags & I2C_M_RD) { + //Rx data + cmd = AST_I2CD_M_RX_CMD | AST_I2CD_RX_BUFF_ENABLE; + + if((i2c_dev->master_msgs->len - i2c_dev->master_xfer_cnt) > i2c_dev->req_page->page_size) { + i2c_dev->master_xfer_len = i2c_dev->req_page->page_size; + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_INTR_CTRL_REG) | + AST_I2CD_RX_DOWN_INTR_EN, I2C_INTR_CTRL_REG); + } else { + i2c_dev->master_xfer_len = i2c_dev->master_msgs->len - i2c_dev->master_xfer_cnt; + if((i2c_dev->master_msgs->flags & I2C_M_RECV_LEN) && (i2c_dev->blk_r_flag == 0)) { + dev_dbg(i2c_dev->dev, "I2C_M_RECV_LEN \n"); + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_INTR_CTRL_REG) | + AST_I2CD_RX_DOWN_INTR_EN, I2C_INTR_CTRL_REG); + } else { + if(i2c_dev->xfer_last == 1) { + dev_dbg(i2c_dev->dev, "last stop \n"); + cmd |= AST_I2CD_M_STOP_CMD; + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_INTR_CTRL_REG) & + ~AST_I2CD_RX_DOWN_INTR_EN, I2C_INTR_CTRL_REG); + } else { + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_INTR_CTRL_REG) | + AST_I2CD_RX_DOWN_INTR_EN, I2C_INTR_CTRL_REG); + } + cmd |= AST_I2CD_M_S_RX_CMD_LAST; + } + } + ast_i2c_write(i2c_dev, + AST_I2CD_RX_BUF_END_ADDR_SET((i2c_dev->master_xfer_len-1))| + AST_I2CD_BUF_BASE_ADDR_SET((i2c_dev->req_page->page_addr_point)), + I2C_BUF_CTRL_REG); + ast_i2c_write(i2c_dev, cmd, I2C_CMD_REG); + dev_dbg(i2c_dev->dev, "rxfer size %d , cmd = %x \n",i2c_dev->master_xfer_len, cmd); + } else { + //Tx data + //next data write + cmd = AST_I2CD_M_TX_CMD | AST_I2CD_TX_BUFF_ENABLE; + if((i2c_dev->master_msgs->len - i2c_dev->master_xfer_cnt) > i2c_dev->req_page->page_size) { + i2c_dev->master_xfer_len = i2c_dev->req_page->page_size; + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_INTR_CTRL_REG) | + AST_I2CD_TX_ACK_INTR_EN, I2C_INTR_CTRL_REG); + + } else { + i2c_dev->master_xfer_len = i2c_dev->master_msgs->len - i2c_dev->master_xfer_cnt; + if(i2c_dev->xfer_last == 1) { + dev_dbg(i2c_dev->dev, "last stop \n"); + cmd |= AST_I2CD_M_STOP_CMD; + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_INTR_CTRL_REG) & + ~AST_I2CD_TX_ACK_INTR_EN, I2C_INTR_CTRL_REG); + + } else { + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_INTR_CTRL_REG) | + AST_I2CD_TX_ACK_INTR_EN, I2C_INTR_CTRL_REG); + } + } + + for(i = 0; i < i2c_dev->master_xfer_len; i++) { + if(i%4 == 0) + tx_buf[i/4] = 0; + tx_buf[i/4] |= (i2c_dev->master_msgs->buf[i2c_dev->master_xfer_cnt + i] << ((i%4)*8)) ; + } +// printk("count %x \n",ast_i2c_read(i2c_dev,I2C_CMD_REG)); + ast_i2c_write(i2c_dev, + AST_I2CD_TX_DATA_BUF_END_SET((i2c_dev->master_xfer_len - 1)) | + AST_I2CD_BUF_BASE_ADDR_SET(i2c_dev->req_page->page_addr_point), + I2C_BUF_CTRL_REG); + + ast_i2c_write(i2c_dev, cmd , I2C_CMD_REG); + dev_dbg(i2c_dev->dev, "txfer size %d , cmd = %x \n",i2c_dev->master_xfer_len, cmd); + } + } else { + //should send next msg + if(i2c_dev->master_xfer_cnt != i2c_dev->master_msgs->len) + printk("complete rx ... bus=%d addr=0x%x (%d vs. %d) ERROR\n", + i2c_dev->bus_id, i2c_dev->master_msgs->addr, + i2c_dev->master_xfer_cnt, i2c_dev->master_msgs->len); + + dev_dbg(i2c_dev->dev, "ast_i2c_do_byte_xfer complete \n"); + i2c_dev->cmd_err = 0; + complete(&i2c_dev->cmd_complete); + } + + } +} +static void ast_i2c_do_byte_xfer(struct ast_i2c_dev *i2c_dev) +{ + u8 *xfer_buf; + u32 cmd = 0; + + if(i2c_dev->slave_operation == 1) { + dev_dbg(i2c_dev->dev,"S cnt %d, xf len %d \n",i2c_dev->slave_xfer_cnt, i2c_dev->slave_msgs->len); + if(i2c_dev->slave_msgs->flags & I2C_M_RD) { + //READ <-- TX + dev_dbg(i2c_dev->dev, "(<--) slave(tx) buf %d [%x]\n", i2c_dev->slave_xfer_cnt, i2c_dev->slave_msgs->buf[i2c_dev->slave_xfer_cnt]); + ast_i2c_write(i2c_dev, i2c_dev->slave_msgs->buf[i2c_dev->slave_xfer_cnt], I2C_BYTE_BUF_REG); + ast_i2c_write(i2c_dev, AST_I2CD_S_TX_CMD, I2C_CMD_REG); + } else { + // Write -->Rx + //no need to handle in byte mode + dev_dbg(i2c_dev->dev, "(-->) slave(rx) BYTE do nothing\n"); + + } + } else { + dev_dbg(i2c_dev->dev,"M cnt %d, xf len %d \n",i2c_dev->master_xfer_cnt, i2c_dev->master_msgs->len); + if(i2c_dev->master_xfer_cnt == -1) { + //first start + dev_dbg(i2c_dev->dev, " %sing %d byte%s %s 0x%02x\n", + i2c_dev->master_msgs->flags & I2C_M_RD ? "read" : "write", + i2c_dev->master_msgs->len, i2c_dev->master_msgs->len > 1 ? "s" : "", + i2c_dev->master_msgs->flags & I2C_M_RD ? "from" : "to", i2c_dev->master_msgs->addr); + + + if(i2c_dev->master_msgs->flags & I2C_M_RD) + ast_i2c_write(i2c_dev, (i2c_dev->master_msgs->addr <<1) |0x1, I2C_BYTE_BUF_REG); + else + ast_i2c_write(i2c_dev, (i2c_dev->master_msgs->addr <<1), I2C_BYTE_BUF_REG); + + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_INTR_CTRL_REG) | + AST_I2CD_TX_ACK_INTR_EN, I2C_INTR_CTRL_REG); + + ast_i2c_write(i2c_dev, AST_I2CD_M_TX_CMD | AST_I2CD_M_START_CMD, I2C_CMD_REG); + + + } else if (i2c_dev->master_xfer_cnt < i2c_dev->master_msgs->len){ + xfer_buf = i2c_dev->master_msgs->buf; + if(i2c_dev->master_msgs->flags & I2C_M_RD) { + //Rx data + cmd = AST_I2CD_M_RX_CMD; + if((i2c_dev->master_msgs->flags & I2C_M_RECV_LEN) && (i2c_dev->master_xfer_cnt == 0)) { + dev_dbg(i2c_dev->dev, "I2C_M_RECV_LEN \n"); + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_INTR_CTRL_REG) | + AST_I2CD_RX_DOWN_INTR_EN, I2C_INTR_CTRL_REG); + + } else if((i2c_dev->xfer_last == 1) && (i2c_dev->master_xfer_cnt + 1 == i2c_dev->master_msgs->len)) { + cmd |= AST_I2CD_M_S_RX_CMD_LAST | AST_I2CD_M_STOP_CMD; + // disable rx_dwn isr + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_INTR_CTRL_REG) & + ~AST_I2CD_RX_DOWN_INTR_EN, I2C_INTR_CTRL_REG); + } else { + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_INTR_CTRL_REG) | + AST_I2CD_RX_DOWN_INTR_EN, I2C_INTR_CTRL_REG); + } + + dev_dbg(i2c_dev->dev, "(<--) rx byte, cmd = %x \n",cmd); + + ast_i2c_write(i2c_dev, cmd, I2C_CMD_REG); + + + } else { + //Tx data + dev_dbg(i2c_dev->dev, "(-->) xfer byte data index[%02x]:%02x \n",i2c_dev->master_xfer_cnt, *(xfer_buf + i2c_dev->master_xfer_cnt)); + ast_i2c_write(i2c_dev, *(xfer_buf + i2c_dev->master_xfer_cnt), I2C_BYTE_BUF_REG); + if((i2c_dev->xfer_last == 1) && (i2c_dev->master_xfer_cnt + 1 == i2c_dev->master_msgs->len)) { + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_INTR_CTRL_REG) & + ~AST_I2CD_TX_ACK_INTR_EN, I2C_INTR_CTRL_REG); + ast_i2c_write(i2c_dev, AST_I2CD_M_TX_CMD | AST_I2CD_M_STOP_CMD, I2C_CMD_REG); + } else { + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_INTR_CTRL_REG) | + AST_I2CD_TX_ACK_INTR_EN, I2C_INTR_CTRL_REG); + ast_i2c_write(i2c_dev, AST_I2CD_M_TX_CMD, I2C_CMD_REG); + } + } + + } else { + //should send next msg + if(i2c_dev->master_xfer_cnt != i2c_dev->master_msgs->len) + printk("CNT ERROR bus=%d addr=0x%x (%d vs. %d)\n", + i2c_dev->bus_id, i2c_dev->master_msgs->addr, + i2c_dev->master_xfer_cnt, i2c_dev->master_msgs->len); + + dev_dbg(i2c_dev->dev, "ast_i2c_do_byte_xfer complete \n"); + i2c_dev->cmd_err = 0; + complete(&i2c_dev->cmd_complete); + + } + } + +} + +static void ast_i2c_slave_xfer_done(struct ast_i2c_dev *i2c_dev) +{ + u32 xfer_len; + int i; + u8 *rx_buf; + unsigned long flags; + + dev_dbg(i2c_dev->dev, "ast_i2c_slave_xfer_done [%d]\n",i2c_dev->slave_xfer_mode); + + + if (i2c_dev->slave_msgs->flags & I2C_M_RD) { + //tx done , only check tx count ... + if(i2c_dev->slave_xfer_mode == BYTE_XFER) { + xfer_len = 1; + } else if (i2c_dev->slave_xfer_mode == BUFF_XFER) { + xfer_len = AST_I2CD_TX_DATA_BUF_GET(ast_i2c_read(i2c_dev, I2C_BUF_CTRL_REG)); + xfer_len++; + dev_dbg(i2c_dev->dev,"S tx buff done len %d \n",xfer_len); + } else { + //DMA mode + xfer_len = ast_i2c_read(i2c_dev, I2C_DMA_LEN_REG); + if(xfer_len == 0) + xfer_len = i2c_dev->slave_xfer_len; + else + xfer_len = i2c_dev->slave_xfer_len - xfer_len - 1; + + dev_dbg(i2c_dev->dev,"S tx rx dma done len %d \n",xfer_len); + } + + } else { + //rx done + if(i2c_dev->slave_xfer_mode == BYTE_XFER) { + //TODO + xfer_len = 1; + if(i2c_dev->slave_event == I2C_SLAVE_EVENT_STOP) { + i2c_dev->slave_msgs->buf[i2c_dev->slave_xfer_cnt] = 0; + i2c_dev->slave_msgs->len = i2c_dev->slave_xfer_cnt; + } else { + if (i2c_dev->slave_xfer_cnt == 0) + dev_err(i2c_dev->dev,"Possible first byte failure issue\n"); + i2c_dev->slave_msgs->buf[i2c_dev->slave_xfer_cnt] = ast_i2c_read(i2c_dev,I2C_BYTE_BUF_REG) >> 8; + } + dev_dbg(i2c_dev->dev,"rx buff %d, [%x] \n",i2c_dev->slave_xfer_cnt ,i2c_dev->slave_msgs->buf[i2c_dev->slave_xfer_cnt]); + } else if (i2c_dev->slave_xfer_mode == BUFF_XFER) { + xfer_len = AST_I2CD_RX_BUF_ADDR_GET(ast_i2c_read(i2c_dev, I2C_BUF_CTRL_REG)); + if(xfer_len == 0) + xfer_len = AST_I2C_PAGE_SIZE; + + dev_dbg(i2c_dev->dev,"rx buff done len %d \n",xfer_len); + + rx_buf = (u8 *)i2c_dev->req_page->page_addr; + + for(i=0;i<xfer_len;i++) { + i2c_dev->slave_msgs->buf[i2c_dev->slave_xfer_cnt+i] = rx_buf[i]; + dev_dbg(i2c_dev->dev,"%d, [%x] \n",i2c_dev->slave_xfer_cnt+i ,i2c_dev->slave_msgs->buf[i2c_dev->slave_xfer_cnt+i]); + } + + } else { + //RX DMA DOWN + xfer_len = ast_i2c_read(i2c_dev, I2C_DMA_LEN_REG); + if(xfer_len == 0) + xfer_len = i2c_dev->slave_xfer_len; + else + xfer_len = i2c_dev->slave_xfer_len - xfer_len - 1; + + dev_dbg(i2c_dev->dev, " rx dma done len %d \n", xfer_len); + + for(i=0;i<xfer_len;i++) { + i2c_dev->slave_msgs->buf[i2c_dev->slave_xfer_cnt+i] = i2c_dev->dma_buf[i]; + dev_dbg(i2c_dev->dev,"%d, [%x] \n",i2c_dev->slave_xfer_cnt+i ,i2c_dev->slave_msgs->buf[i2c_dev->slave_xfer_cnt+i]); + } + } + + } + + if(xfer_len !=i2c_dev->slave_xfer_len) { + //TODO.. + printk(" **slave xfer error ====\n"); + //should goto stop.... + } else + i2c_dev->slave_xfer_cnt += i2c_dev->slave_xfer_len; + + + if((i2c_dev->slave_event == I2C_SLAVE_EVENT_NACK) || (i2c_dev->slave_event == I2C_SLAVE_EVENT_STOP)) { +#ifdef CONFIG_AST_I2C_SLAVE_RDWR + ast_i2c_slave_rdwr_xfer(i2c_dev); +#else + i2c_dev->ast_i2c_data->slave_xfer(i2c_dev->slave_event, &(i2c_dev->slave_msgs)); +#endif + i2c_dev->slave_xfer_cnt = 0; + } else { + if(i2c_dev->slave_xfer_cnt == i2c_dev->slave_msgs->len) { + dev_err(i2c_dev->dev,"slave next msgs with len %d\n", i2c_dev->slave_xfer_cnt); +#ifdef CONFIG_AST_I2C_SLAVE_RDWR + ast_i2c_slave_rdwr_xfer(i2c_dev); +#else + i2c_dev->ast_i2c_data->slave_xfer(i2c_dev->slave_event, &(i2c_dev->slave_msgs)); +#endif + + i2c_dev->slave_xfer_cnt = 0; + } + i2c_dev->do_slave_xfer(i2c_dev); + } + + // Read the current state for clearing up the slave mode + i2c_dev->state = (ast_i2c_read(i2c_dev,I2C_CMD_REG) >> 19) & 0xf; + + if(AST_I2CD_IDLE == i2c_dev->state) { + dev_dbg(i2c_dev->dev,"** Slave go IDLE **\n"); + i2c_dev->slave_operation = 0; + + if(i2c_dev->slave_xfer_mode == BUFF_XFER) { + i2c_dev->ast_i2c_data->free_pool_buff_page(i2c_dev->req_page); + } + + } else if (i2c_dev->slave_event == I2C_SLAVE_EVENT_STOP) { + // TODO: hack to reset slave operation flag in case the stop is received + i2c_dev->slave_operation = 0; + } + +} + +//TX/Rx Done +static void ast_i2c_master_xfer_done(struct ast_i2c_dev *i2c_dev) +{ + u32 xfer_len; + int i; + u8 *pool_buf; + unsigned long flags; + + spin_lock_irqsave(&i2c_dev->master_lock, flags); + + /* + * This function shall be involked during interrupt handling. + * Since the interrupt could be fired at anytime, we will need to make sure + * we have the buffer (i2c_dev->master_msgs) to handle the results. + */ + if (!i2c_dev->master_msgs) { + goto unlock_out; + } + + dev_dbg(i2c_dev->dev, "ast_i2c_master_xfer_done mode[%d]\n",i2c_dev->master_xfer_mode); + + if (i2c_dev->master_msgs->flags & I2C_M_RD) { + if(i2c_dev->master_xfer_cnt == -1) { + xfer_len = 1; + goto next_xfer; + } + if(i2c_dev->master_xfer_mode == BYTE_XFER) { + if ((i2c_dev->master_msgs->flags & I2C_M_RECV_LEN) && (i2c_dev->blk_r_flag == 0)) { + i2c_dev->master_msgs->len += (ast_i2c_read(i2c_dev,I2C_BYTE_BUF_REG) & AST_I2CD_RX_BYTE_BUFFER) >> 8; + i2c_dev->blk_r_flag = 1; + dev_dbg(i2c_dev->dev, "I2C_M_RECV_LEN %d \n", i2c_dev->master_msgs->len -1); + } + xfer_len = 1; + i2c_dev->master_msgs->buf[i2c_dev->master_xfer_cnt] = (ast_i2c_read(i2c_dev,I2C_BYTE_BUF_REG) & AST_I2CD_RX_BYTE_BUFFER) >> 8; + } else if (i2c_dev->master_xfer_mode == BUFF_XFER) { + pool_buf = (u8 *)i2c_dev->req_page->page_addr; + xfer_len = AST_I2CD_RX_BUF_ADDR_GET(ast_i2c_read(i2c_dev, I2C_BUF_CTRL_REG)); + + if(xfer_len == 0) + xfer_len = AST_I2C_PAGE_SIZE; + + for(i = 0; i< xfer_len; i++) { + i2c_dev->master_msgs->buf[i2c_dev->master_xfer_cnt + i] = pool_buf[i]; + dev_dbg(i2c_dev->dev, "rx %d buff[%x]\n",i2c_dev->master_xfer_cnt+i, i2c_dev->master_msgs->buf[i2c_dev->master_xfer_cnt+i]); + } + + if ((i2c_dev->master_msgs->flags & I2C_M_RECV_LEN) && (i2c_dev->blk_r_flag == 0)) { + i2c_dev->master_msgs->len += pool_buf[0]; + i2c_dev->blk_r_flag = 1; + dev_dbg(i2c_dev->dev, "I2C_M_RECV_LEN %d \n", i2c_dev->master_msgs->len -1); + } + } else { + //DMA Mode + xfer_len = ast_i2c_read(i2c_dev, I2C_DMA_LEN_REG); + + if(xfer_len == 0) + xfer_len = i2c_dev->master_xfer_len; + else + xfer_len = i2c_dev->master_xfer_len - xfer_len - 1; + + for(i = 0; i < xfer_len; i++) { + i2c_dev->master_msgs->buf[i2c_dev->master_xfer_cnt + i] = i2c_dev->dma_buf[i]; + dev_dbg(i2c_dev->dev, "buf[%x] \n", i2c_dev->dma_buf[i]); + dev_dbg(i2c_dev->dev, "buf[%x] \n", i2c_dev->dma_buf[i+1]); + } + + if ((i2c_dev->master_msgs->flags & I2C_M_RECV_LEN) && (i2c_dev->blk_r_flag == 0)) { + i2c_dev->master_msgs->len += i2c_dev->dma_buf[0]; + i2c_dev->blk_r_flag = 1; + dev_dbg(i2c_dev->dev, "I2C_M_RECV_LEN %d \n", i2c_dev->master_msgs->len -1); + } + + } + + }else { + if(i2c_dev->master_xfer_mode == BYTE_XFER) { + xfer_len = 1; + } else if(i2c_dev->master_xfer_mode == BUFF_XFER) { + xfer_len = AST_I2CD_TX_DATA_BUF_GET(ast_i2c_read(i2c_dev, I2C_BUF_CTRL_REG)); + xfer_len++; + dev_dbg(i2c_dev->dev,"tx buff done len %d \n",xfer_len); + } else { + //DMA + xfer_len = ast_i2c_read(i2c_dev, I2C_DMA_LEN_REG); + if(xfer_len == 0) + xfer_len = i2c_dev->master_xfer_len; + else + xfer_len = i2c_dev->master_xfer_len - xfer_len - 1; + + dev_dbg(i2c_dev->dev,"tx dma done len %d \n",xfer_len); + } + } + +next_xfer: + + if(xfer_len !=i2c_dev->master_xfer_len) { + //TODO.. + printk(" ** xfer error bus=%d addr=0x%x (%d vs. %d)\n", + i2c_dev->bus_id, i2c_dev->master_msgs->addr, + xfer_len, i2c_dev->master_xfer_len); + //should goto stop.... + i2c_dev->cmd_err = 1; + goto done_out; + } else + i2c_dev->master_xfer_cnt += i2c_dev->master_xfer_len; + + if(i2c_dev->master_xfer_cnt != i2c_dev->master_msgs->len) { + dev_dbg(i2c_dev->dev,"do next cnt \n"); + i2c_dev->do_master_xfer(i2c_dev); + } else { +#if 0 + int i; + printk(" ===== \n"); + for(i=0;i<i2c_dev->master_msgs->len;i++) + printk("rx buf i,[%x]\n",i,i2c_dev->master_msgs->buf[i]); + printk(" ===== \n"); +#endif + i2c_dev->cmd_err = 0; + +done_out: + dev_dbg(i2c_dev->dev,"msgs complete \n"); + complete(&i2c_dev->cmd_complete); + } + +unlock_out: + spin_unlock_irqrestore(&i2c_dev->master_lock, flags); +} + +static void ast_i2c_slave_addr_match(struct ast_i2c_dev *i2c_dev) +{ + u8 match; + unsigned long flags; + + + i2c_dev->slave_operation = 1; + i2c_dev->slave_xfer_cnt = 0; + match = ast_i2c_read(i2c_dev,I2C_BYTE_BUF_REG) >> 8; + i2c_dev->slave_msgs->buf[0] = match; + dev_dbg(i2c_dev->dev, "S Start Addr match [%x] \n",match); + + if(match & 1) { + i2c_dev->slave_event = I2C_SLAVE_EVENT_START_READ; + } else { + i2c_dev->slave_event = I2C_SLAVE_EVENT_START_WRITE; + } + +#ifdef CONFIG_AST_I2C_SLAVE_RDWR + ast_i2c_slave_rdwr_xfer(i2c_dev); + i2c_dev->slave_msgs->buf[0] = match; + i2c_dev->slave_xfer_cnt = 1; + // Reset the length field as we have received new slave address match + i2c_dev->slave_msgs->len = 0x0; +#else + i2c_dev->ast_i2c_data->slave_xfer(i2c_dev->slave_event, &(i2c_dev->slave_msgs)); + i2c_dev->slave_xfer_cnt = 0; +#endif + + //request: set slave_xfer_mode properly based on slave_dma mode + if(i2c_dev->ast_i2c_data->slave_dma == BYTE_MODE) { + i2c_dev->do_slave_xfer = ast_i2c_do_byte_xfer; + i2c_dev->slave_xfer_mode = BYTE_XFER; + i2c_dev->slave_xfer_len = 1; + } else if (i2c_dev->ast_i2c_data->slave_dma == DMA_MODE) { + i2c_dev->do_slave_xfer = ast_i2c_do_dma_xfer; + i2c_dev->slave_xfer_mode = DMA_XFER; + } else { + if(i2c_dev->ast_i2c_data->request_pool_buff_page(&(i2c_dev->req_page)) == 0) { + i2c_dev->do_slave_xfer = ast_i2c_do_pool_xfer; + i2c_dev->slave_xfer_mode = BUFF_XFER; + } else { + i2c_dev->do_slave_xfer = ast_i2c_do_byte_xfer; + dev_err(i2c_dev->dev,"i2cdriver: pool request failed for slave\n"); + i2c_dev->slave_xfer_mode = BYTE_XFER; + i2c_dev->slave_xfer_len = 1; + } + } + + i2c_dev->do_slave_xfer(i2c_dev); + +} + +static irqreturn_t i2c_ast_handler(int this_irq, void *dev_id) +{ + u32 sts; + u32 temp; + + struct ast_i2c_dev *i2c_dev = dev_id; + u32 isr_sts = readl(i2c_dev->ast_i2c_data->reg_gr); + + if(!(isr_sts & (1<< i2c_dev->bus_id))) + return IRQ_NONE; + + i2c_dev->state = (ast_i2c_read(i2c_dev,I2C_CMD_REG) >> 19) & 0xf; + sts = ast_i2c_read(i2c_dev,I2C_INTR_STS_REG); +// printk("ISR : %x , sts [%x]\n",sts , xfer_sts); +// dev_dbg(i2c_dev->dev,"ISR : %x , sts [%x]\n",sts , xfer_sts); + +// dev_dbg(i2c_dev->dev,"sts machine %x, slave_op %d \n", xfer_sts,i2c_dev->slave_operation); + + if(AST_I2CD_INTR_STS_SMBUS_ALT & sts) { + dev_dbg(i2c_dev->dev, "M clear isr: AST_I2CD_INTR_STS_SMBUS_ALT= %x\n",sts); + //Disable ALT INT + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev, I2C_INTR_CTRL_REG) & + ~AST_I2CD_SMBUS_ALT_INTR_EN, + I2C_INTR_CTRL_REG); + ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_SMBUS_ALT, I2C_INTR_STS_REG); + ast_master_alert_recv(i2c_dev); + sts &= ~AST_I2CD_SMBUS_ALT_INTR_EN; + } + + if(AST_I2CD_INTR_STS_ABNORMAL & sts) { + // TODO: observed abnormal interrupt happening when the bus is stressed with traffic + dev_err(i2c_dev->dev, "abnormal interrupt happens with status: %x, slave mode: %d\n", sts, i2c_dev->slave_operation); + // Need to clear the interrupt + ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_ABNORMAL, I2C_INTR_STS_REG); + + i2c_dev->cmd_err |= AST_I2CD_INTR_STS_ABNORMAL; + complete(&i2c_dev->cmd_complete); + + return IRQ_HANDLED; + } + + switch(sts) { + case AST_I2CD_INTR_STS_TX_ACK: + if(i2c_dev->slave_operation == 1) { + i2c_dev->slave_event = I2C_SLAVE_EVENT_READ; + ast_i2c_slave_xfer_done(i2c_dev); + dev_dbg(i2c_dev->dev, "S clear isr: AST_I2CD_INTR_STS_TX_ACK = %x\n",sts); + ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_TX_ACK, I2C_INTR_STS_REG); + } else { + dev_dbg(i2c_dev->dev, "M clear isr: AST_I2CD_INTR_STS_TX_ACK = %x\n",sts); + ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_TX_ACK, I2C_INTR_STS_REG); + ast_i2c_master_xfer_done(i2c_dev); + } + break; + case AST_I2CD_INTR_STS_TX_ACK | AST_I2CD_INTR_STS_NORMAL_STOP: + if((i2c_dev->xfer_last == 1) && (i2c_dev->slave_operation == 0)) { + dev_dbg(i2c_dev->dev, "M clear isr: AST_I2CD_INTR_STS_TX_ACK | AST_I2CD_INTR_STS_NORMAL_STOP= %x\n",sts); + ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_TX_ACK | AST_I2CD_INTR_STS_NORMAL_STOP, I2C_INTR_STS_REG); + //take care + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_INTR_CTRL_REG) | + AST_I2CD_TX_ACK_INTR_EN, I2C_INTR_CTRL_REG); + ast_i2c_master_xfer_done(i2c_dev); + + } else { + dev_err(i2c_dev->dev,"ast_i2c: TX_ACK | NORMAL_STOP; xfer_last %d\n", i2c_dev->xfer_last); + ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_TX_ACK | AST_I2CD_INTR_STS_NORMAL_STOP, I2C_INTR_STS_REG); + uint32_t new_val = ast_i2c_read(i2c_dev,I2C_INTR_CTRL_REG) | + AST_I2CD_NORMAL_STOP_INTR_EN | + AST_I2CD_TX_ACK_INTR_EN; + ast_i2c_write(i2c_dev, new_val, I2C_INTR_CTRL_REG); + //take care + i2c_dev->cmd_err |= AST_LOCKUP_DETECTED; + complete(&i2c_dev->cmd_complete); + } + break; + case AST_I2CD_INTR_STS_TX_ACK | AST_I2CD_INTR_STS_NORMAL_STOP | AST_I2CD_INTR_STS_SLAVE_MATCH : + if((i2c_dev->xfer_last == 1) && (i2c_dev->slave_operation == 0)) { + dev_dbg(i2c_dev->dev, "M clear isr: AST_I2CD_INTR_STS_TX_ACK | AST_I2CD_INTR_STS_NORMAL_STOP | AST_I2CD_INTR_STS_SLAVE_MATCH= %x\n",sts); + ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_TX_ACK | AST_I2CD_INTR_STS_NORMAL_STOP, I2C_INTR_STS_REG); + //take care + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_INTR_CTRL_REG) | + AST_I2CD_TX_ACK_INTR_EN, I2C_INTR_CTRL_REG); + ast_i2c_master_xfer_done(i2c_dev); + + // Handle the new slave match interrupt + ast_i2c_slave_addr_match(i2c_dev); + ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_SLAVE_MATCH, I2C_INTR_STS_REG); + } else { + dev_err(i2c_dev->dev, "Slave TX_ACK | NORMAL_STOP | AST_I2CD_INTR_STS_SLAVE_MATCH; xfer_last %d\n", i2c_dev->xfer_last); + ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_TX_ACK | AST_I2CD_INTR_STS_NORMAL_STOP, I2C_INTR_STS_REG); + uint32_t new_val = ast_i2c_read(i2c_dev,I2C_INTR_CTRL_REG) | + AST_I2CD_NORMAL_STOP_INTR_EN | + AST_I2CD_TX_ACK_INTR_EN; + ast_i2c_write(i2c_dev, new_val, I2C_INTR_CTRL_REG); + //take care + i2c_dev->cmd_err |= AST_LOCKUP_DETECTED; + complete(&i2c_dev->cmd_complete); + + // stop previous slave transaction + i2c_dev->slave_event = I2C_SLAVE_EVENT_STOP; + ast_i2c_slave_xfer_done(i2c_dev); + + // Handle the new slave match interrupt + ast_i2c_slave_addr_match(i2c_dev); + ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_SLAVE_MATCH, I2C_INTR_STS_REG); + } + break; + + case AST_I2CD_INTR_STS_TX_NAK: + if(i2c_dev->slave_operation == 1) { + i2c_dev->slave_event = I2C_SLAVE_EVENT_NACK; + ast_i2c_slave_xfer_done(i2c_dev); + dev_err(i2c_dev->dev, "S clear isr: AST_I2CD_INTR_STS_TX_NAK = %x\n",sts); + ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_TX_NAK, I2C_INTR_STS_REG); + } else { + dev_dbg(i2c_dev->dev, "M clear isr: AST_I2CD_INTR_STS_TX_NAK = %x\n",sts); + ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_TX_NAK, I2C_INTR_STS_REG); + if (i2c_dev->master_msgs + && i2c_dev->master_msgs->flags & I2C_M_IGNORE_NAK) { + dev_dbg(i2c_dev->dev, "I2C_M_IGNORE_NAK next send\n"); + } else { + dev_dbg(i2c_dev->dev, "NAK error\n"); + i2c_dev->cmd_err |= AST_I2CD_INTR_STS_TX_NAK; + } + complete(&i2c_dev->cmd_complete); + } + break; + + case AST_I2CD_INTR_STS_TX_NAK | AST_I2CD_INTR_STS_NORMAL_STOP: + if(i2c_dev->slave_operation == 1) { + printk("SLAVE TODO .... \n"); + i2c_dev->slave_operation = 0; + ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_TX_NAK | AST_I2CD_INTR_STS_NORMAL_STOP, I2C_INTR_STS_REG); + i2c_dev->cmd_err |= AST_I2CD_INTR_STS_TX_NAK | AST_I2CD_INTR_STS_NORMAL_STOP; + complete(&i2c_dev->cmd_complete); + } else { + dev_dbg(i2c_dev->dev, "M clear isr: AST_I2CD_INTR_STS_TX_NAK| AST_I2CD_INTR_STS_NORMAL_STOP = %x\n",sts); + ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_TX_NAK | AST_I2CD_INTR_STS_NORMAL_STOP, I2C_INTR_STS_REG); + dev_dbg(i2c_dev->dev, "M TX NAK | NORMAL STOP \n"); + i2c_dev->cmd_err |= AST_I2CD_INTR_STS_TX_NAK | AST_I2CD_INTR_STS_NORMAL_STOP; + complete(&i2c_dev->cmd_complete); + } + break; + + //Issue : Workaround for I2C slave mode + case AST_I2CD_INTR_STS_TX_NAK | AST_I2CD_INTR_STS_SLAVE_MATCH: + if(i2c_dev->slave_operation == 1) { + dev_err(i2c_dev->dev, "i2cdriver: TX_NAK and Slave match sts = %x\n",sts); + ast_i2c_slave_addr_match(i2c_dev); + ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_TX_NAK | AST_I2CD_INTR_STS_SLAVE_MATCH , I2C_INTR_STS_REG); + } else { + printk("ERROR !!!!\n"); + ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_TX_NAK, I2C_INTR_STS_REG); + i2c_dev->cmd_err |= AST_I2CD_INTR_STS_TX_NAK; + complete(&i2c_dev->cmd_complete); + + ast_i2c_slave_addr_match(i2c_dev); + ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_SLAVE_MATCH , I2C_INTR_STS_REG); + } + break; + case AST_I2CD_INTR_STS_RX_DOWN | AST_I2CD_INTR_STS_SLAVE_MATCH: + ast_i2c_slave_addr_match(i2c_dev); + dev_dbg(i2c_dev->dev, "S clear isr: AST_I2CD_INTR_STS_RX_DOWN | AST_I2CD_INTR_STS_SLAVE_MATCH = %x\n",sts); + ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_RX_DOWN | AST_I2CD_INTR_STS_SLAVE_MATCH, I2C_INTR_STS_REG); + break; + case AST_I2CD_INTR_STS_RX_DOWN | AST_I2CD_INTR_STS_SLAVE_MATCH | AST_I2CD_INTR_STS_ARBIT_LOSS: + dev_err(i2c_dev->dev, "M clear isr: sts = %x\n",sts); + ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_ARBIT_LOSS, I2C_INTR_STS_REG); + i2c_dev->cmd_err |= AST_I2CD_INTR_STS_ARBIT_LOSS; + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_INTR_CTRL_REG) | + AST_I2CD_RX_DOWN_INTR_EN, I2C_INTR_CTRL_REG); + complete(&i2c_dev->cmd_complete); + + ast_i2c_slave_addr_match(i2c_dev); + ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_RX_DOWN | AST_I2CD_INTR_STS_SLAVE_MATCH, I2C_INTR_STS_REG); + break; + case AST_I2CD_INTR_STS_RX_DOWN: + if(i2c_dev->slave_operation == 1) { + i2c_dev->slave_event = I2C_SLAVE_EVENT_WRITE; + ast_i2c_slave_xfer_done(i2c_dev); + dev_dbg(i2c_dev->dev, "S clear isr: AST_I2CD_INTR_STS_RX_DOWN = %x\n",sts); + ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_RX_DOWN, I2C_INTR_STS_REG); + } else { + dev_dbg(i2c_dev->dev, "M clear isr: AST_I2CD_INTR_STS_RX_DOWN = %x\n",sts); + ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_RX_DOWN, I2C_INTR_STS_REG); + ast_i2c_master_xfer_done(i2c_dev); + } + break; + + case AST_I2CD_INTR_STS_NORMAL_STOP: + if(i2c_dev->slave_operation == 1) { + i2c_dev->slave_event = I2C_SLAVE_EVENT_STOP; + ast_i2c_slave_xfer_done(i2c_dev); + dev_dbg(i2c_dev->dev, "S clear isr: AST_I2CD_INTR_STS_NORMAL_STOP = %x\n",sts); + ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_NORMAL_STOP, I2C_INTR_STS_REG); + dev_dbg(i2c_dev->dev, "state [%x] \n",i2c_dev->state); + } else { + dev_dbg(i2c_dev->dev, "M clear isr: AST_I2CD_INTR_STS_NORMAL_STOP = %x\n",sts); + ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_NORMAL_STOP, I2C_INTR_STS_REG); + i2c_dev->cmd_err |= AST_I2CD_INTR_STS_NORMAL_STOP; + complete(&i2c_dev->cmd_complete); + } + break; + case AST_I2CD_INTR_STS_RX_DOWN | AST_I2CD_INTR_STS_NORMAL_STOP: + /* Whether or not we're done, the hardware thinks we're done, so bail. */ + if(i2c_dev->slave_operation == 0) { + dev_dbg(i2c_dev->dev, "M clear isr: AST_I2CD_INTR_STS_RX_DOWN | AST_I2CD_INTR_STS_NORMAL_STOP = %x\n",sts); + ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_RX_DOWN | AST_I2CD_INTR_STS_NORMAL_STOP, I2C_INTR_STS_REG); + //take care + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_INTR_CTRL_REG) | + AST_I2CD_RX_DOWN_INTR_EN, I2C_INTR_CTRL_REG); + ast_i2c_master_xfer_done(i2c_dev); + } else { + dev_err(i2c_dev->dev, "S clear isr: AST_I2CD_INTR_STS_RX_DOWN | AST_I2CD_INTR_STS_NORMAL_STOP = %x\n",sts); + i2c_dev->slave_event = I2C_SLAVE_EVENT_STOP; + ast_i2c_slave_xfer_done(i2c_dev); + ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_RX_DOWN | AST_I2CD_INTR_STS_NORMAL_STOP, I2C_INTR_STS_REG); + ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_INTR_CTRL_REG) | + AST_I2CD_RX_DOWN_INTR_EN, I2C_INTR_CTRL_REG); + ast_i2c_master_xfer_done(i2c_dev); + } + break; + case AST_I2CD_INTR_STS_ARBIT_LOSS: + dev_err(i2c_dev->dev, "M clear isr: AST_I2CD_INTR_STS_ARBIT_LOSS = %x\n",sts); + ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_ARBIT_LOSS, I2C_INTR_STS_REG); + i2c_dev->cmd_err |= AST_I2CD_INTR_STS_ARBIT_LOSS; + complete(&i2c_dev->cmd_complete); + break; + case AST_I2CD_INTR_STS_SCL_TO: + dev_err(i2c_dev->dev, "SCL LOW detected with sts = %x, slave mode: %x\n",sts, i2c_dev->slave_operation); + ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_SCL_TO, I2C_INTR_STS_REG); + i2c_dev->cmd_err |= AST_I2CD_INTR_STS_SCL_TO; + complete(&i2c_dev->cmd_complete); + + // Reset i2c controller + temp = ast_i2c_read(i2c_dev,I2C_FUN_CTRL_REG); + + ast_i2c_write(i2c_dev, temp & ~(AST_I2CD_SLAVE_EN | AST_I2CD_MASTER_EN), I2C_FUN_CTRL_REG); + + ast_i2c_write(i2c_dev, temp, I2C_FUN_CTRL_REG); + break; + case AST_I2CD_INTR_STS_SLAVE_MATCH | AST_I2CD_INTR_STS_SCL_TO: + dev_err(i2c_dev->dev, "SCL LOW detected with sts = %x, slave mode: %x\n",sts, i2c_dev->slave_operation); + ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_SCL_TO | AST_I2CD_INTR_STS_SLAVE_MATCH, I2C_INTR_STS_REG); + i2c_dev->cmd_err |= AST_I2CD_INTR_STS_SCL_TO; + complete(&i2c_dev->cmd_complete); + + // Reset i2c controller + temp = ast_i2c_read(i2c_dev,I2C_FUN_CTRL_REG); + + ast_i2c_write(i2c_dev, temp & ~(AST_I2CD_SLAVE_EN | AST_I2CD_MASTER_EN), I2C_FUN_CTRL_REG); + + ast_i2c_write(i2c_dev, temp, I2C_FUN_CTRL_REG); + break; + case AST_I2CD_INTR_STS_GCALL_ADDR: + i2c_dev->cmd_err |= AST_I2CD_INTR_STS_GCALL_ADDR; + complete(&i2c_dev->cmd_complete); + + break; + case AST_I2CD_INTR_STS_SMBUS_DEF_ADDR: + break; + case AST_I2CD_INTR_STS_SMBUS_DEV_ALT: + break; + + case AST_I2CD_INTR_STS_SMBUS_ARP_ADDR: + break; + case AST_I2CD_INTR_STS_SDA_DL_TO: + ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_SDA_DL_TO, I2C_INTR_STS_REG); + i2c_dev->cmd_err |= AST_I2CD_INTR_STS_SDA_DL_TO; + complete(&i2c_dev->cmd_complete); + break; + case AST_I2CD_INTR_STS_BUS_RECOVER: + dev_err(i2c_dev->dev, "Bus recover with sts= %x, slave mode: %x\n",sts, i2c_dev->slave_operation); + ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_BUS_RECOVER, I2C_INTR_STS_REG); + complete(&i2c_dev->cmd_complete); + break; + default: + printk("GR %x : Status : %x, bus_id %d\n",i2c_dev->ast_i2c_data->reg_gr, sts, i2c_dev->bus_id); + + // Handle Arbitration Loss + if (sts & AST_I2CD_INTR_STS_ARBIT_LOSS) { + ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_ARBIT_LOSS, I2C_INTR_STS_REG); + i2c_dev->cmd_err |= AST_I2CD_INTR_STS_ARBIT_LOSS; + complete(&i2c_dev->cmd_complete); + sts &= (~AST_I2CD_INTR_STS_ARBIT_LOSS); + } + + // Handle the write transaction ACK + if (sts & AST_I2CD_INTR_STS_TX_ACK) { + ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_TX_ACK, I2C_INTR_STS_REG); + ast_i2c_master_xfer_done(i2c_dev); + sts &= (~AST_I2CD_INTR_STS_TX_ACK); + } + + // Handle Normal Stop conditon + if (sts & AST_I2CD_INTR_STS_NORMAL_STOP) { + ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_TX_ACK, I2C_INTR_STS_REG); + sts &= (~AST_I2CD_INTR_STS_NORMAL_STOP); + i2c_dev->cmd_err |= AST_I2CD_INTR_STS_NORMAL_STOP; + complete(&i2c_dev->cmd_complete); + } + + // Handle the Slave address match + if (sts & AST_I2CD_INTR_STS_SLAVE_MATCH) { + ast_i2c_slave_addr_match(i2c_dev); + sts &= (~AST_I2CD_INTR_STS_SLAVE_MATCH); + ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_SLAVE_MATCH, I2C_INTR_STS_REG); + } + + + // TODO: Debug print for any unhandled condition + if(sts) { + printk("GR %x : Status : %x, bus_id %d\n",i2c_dev->ast_i2c_data->reg_gr, sts, i2c_dev->bus_id); + } + + //TODO: Clearing this interrupt for now, but needs to cleanup this ISR function + ast_i2c_write(i2c_dev, sts, I2C_INTR_STS_REG); + + return IRQ_HANDLED; + } + + return IRQ_HANDLED; +} + +static int ast_i2c_do_msgs_xfer(struct ast_i2c_dev *i2c_dev, struct i2c_msg *msgs, int num) +{ + int i; + int ret = 1; + unsigned long flags; + + spin_lock_irqsave(&i2c_dev->master_lock, flags); + + //request: update master_xfer_mode based on master_dma selection + if(i2c_dev->ast_i2c_data->master_dma == BYTE_MODE) { + i2c_dev->do_master_xfer = ast_i2c_do_byte_xfer; + i2c_dev->master_xfer_mode = BYTE_XFER; + i2c_dev->master_xfer_len = 1; + } else if (i2c_dev->ast_i2c_data->master_dma == DMA_MODE) { + i2c_dev->do_master_xfer = ast_i2c_do_dma_xfer; + i2c_dev->master_xfer_mode = DMA_XFER; + } else { + if(i2c_dev->ast_i2c_data->request_pool_buff_page(&(i2c_dev->req_page)) == 0) { + i2c_dev->do_master_xfer = ast_i2c_do_pool_xfer; + i2c_dev->master_xfer_mode = BUFF_XFER; + } else { + i2c_dev->do_master_xfer = ast_i2c_do_byte_xfer; + dev_err(i2c_dev->dev, "i2cdriver: pool request failed for master\n"); + i2c_dev->master_xfer_mode = BYTE_XFER; + i2c_dev->master_xfer_len = 1; + + } + } + +// printk("start xfer ret = %d \n",ret); + + for (i=0; i < num; i++) { + i2c_dev->blk_r_flag = 0; + i2c_dev->master_msgs = &msgs[i]; + if(num == i+1) + i2c_dev->xfer_last = 1; + else + i2c_dev->xfer_last = 0; + + i2c_dev->blk_r_flag = 0; + init_completion(&i2c_dev->cmd_complete); + i2c_dev->cmd_err = 0; + + if(i2c_dev->master_msgs->flags & I2C_M_NOSTART) + i2c_dev->master_xfer_cnt = 0; + else + i2c_dev->master_xfer_cnt = -1; + + i2c_dev->do_master_xfer(i2c_dev); + + spin_unlock_irqrestore(&i2c_dev->master_lock, flags); + + ret = wait_for_completion_interruptible_timeout(&i2c_dev->cmd_complete, + i2c_dev->adap.timeout*HZ); + + spin_lock_irqsave(&i2c_dev->master_lock, flags); + i2c_dev->master_msgs = NULL; + + if (ret == 0) { + dev_err(i2c_dev->dev, "controller timed out\n"); + i2c_dev->state = (ast_i2c_read(i2c_dev,I2C_CMD_REG) >> 19) & 0xf; +// printk("sts [%x], isr sts [%x] \n",i2c_dev->state, ast_i2c_read(i2c_dev,I2C_INTR_STS_REG)); + ret = -ETIMEDOUT; + spin_unlock_irqrestore(&i2c_dev->master_lock, flags); + goto stop; + } + + if(i2c_dev->cmd_err != 0 && + i2c_dev->cmd_err != AST_I2CD_INTR_STS_NORMAL_STOP) { + if (i2c_dev->cmd_err & AST_LOCKUP_DETECTED) { + printk("ast-i2c: error got unexpected STOP\n"); + // reset the bus + ast_i2c_bus_error_recover(i2c_dev); + } + ret = -EAGAIN; + spin_unlock_irqrestore(&i2c_dev->master_lock, flags); + goto stop; + } + } + + spin_unlock_irqrestore(&i2c_dev->master_lock, flags); + + if(i2c_dev->cmd_err == 0 || + i2c_dev->cmd_err == AST_I2CD_INTR_STS_NORMAL_STOP) { + ret = num; + goto out; + + } +stop: + if(i2c_dev->cmd_err & AST_I2CD_INTR_STS_NORMAL_STOP) + goto out; + init_completion(&i2c_dev->cmd_complete); + i2c_dev->cmd_err = 0; + ast_i2c_write(i2c_dev, AST_I2CD_M_STOP_CMD, I2C_CMD_REG); + wait_for_completion_interruptible_timeout(&i2c_dev->cmd_complete, + i2c_dev->adap.timeout*HZ); + +out: + //Free .. + if(i2c_dev->master_xfer_mode == BUFF_XFER) { + i2c_dev->ast_i2c_data->free_pool_buff_page(i2c_dev->req_page); + + } + dev_dbg(i2c_dev->dev, "end xfer ret = %d, xfer mode[%d]\n",ret, i2c_dev->master_xfer_mode); + return ret; + +} + +static int ast_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) +{ + struct ast_i2c_dev *i2c_dev = adap->algo_data; + int ret, i; + int sts; + + sts = ast_i2c_read(i2c_dev,I2C_CMD_REG); + dev_dbg(i2c_dev->dev, "state[%x],SCL[%d],SDA[%d],BUS[%d]\n", (sts >> 19) & 0xf, (sts >> 18) & 0x1,(sts >> 17) & 0x1,(sts >> 16) & 1); + /* + * Wait for the bus to become free. + */ + + ret = ast_i2c_wait_bus_not_busy(i2c_dev); + if (ret) { + dev_err(&i2c_dev->adap.dev, "i2c_ast: timeout waiting for bus free\n"); + goto out; + } + + for (i = adap->retries; i >= 0; i--) { + + ret = ast_i2c_do_msgs_xfer(i2c_dev, msgs, num); + if (ret != -EAGAIN) + goto out; + dev_dbg(&i2c_dev->adap.dev, "Retrying transmission [%d]\n",i); + udelay(100); + } + + ret = -EREMOTEIO; +out: + + return ret; +} + +static u32 ast_i2c_functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SMBUS_BLOCK_DATA; +} + +static const struct i2c_algorithm i2c_ast_algorithm = { + .master_xfer = ast_i2c_xfer, +#ifdef CONFIG_AST_I2C_SLAVE_RDWR + .slave_xfer = ast_i2c_slave_xfer, +#endif + .functionality = ast_i2c_functionality, +}; + +static int ast_i2c_probe(struct platform_device *pdev) +{ + struct ast_i2c_dev *i2c_dev; + struct resource *res; + int ret; + + dev_dbg(&pdev->dev, "ast_i2c_probe \n"); + + i2c_dev = kzalloc(sizeof(struct ast_i2c_dev), GFP_KERNEL); + if (!i2c_dev) { + ret = -ENOMEM; + goto err_no_mem; + } + + i2c_dev->ast_i2c_data = pdev->dev.platform_data; + if(i2c_dev->ast_i2c_data->master_dma == BUFF_MODE) { + dev_dbg(&pdev->dev, "use buffer pool mode 256\n"); + + } else if ((i2c_dev->ast_i2c_data->master_dma == DMA_MODE) || (i2c_dev->ast_i2c_data->slave_dma == DMA_MODE)) { + dev_dbg(&pdev->dev, "use dma mode \n"); + if (!i2c_dev->dma_buf) { + i2c_dev->dma_buf = dma_alloc_coherent(NULL, AST_I2C_DMA_SIZE, &i2c_dev->dma_addr, GFP_KERNEL); + if (!i2c_dev->dma_buf) { + printk("unable to allocate tx Buffer memory\n"); + ret = -ENOMEM; + goto err_no_dma; + } + if(i2c_dev->dma_addr%4 !=0) { + printk("not 4 byte boundary \n"); + ret = -ENOMEM; + goto err_no_dma; + } +// printk("dma_buf = [0x%x] dma_addr = [0x%x], please check 4byte boundary \n",i2c_dev->dma_buf,i2c_dev->dma_addr); + memset (i2c_dev->dma_buf, 0, AST_I2C_DMA_SIZE); + } + + } else { + //master_mode 0: use byte mode + dev_dbg(&pdev->dev, "use default byte mode \n"); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (NULL == res) { + dev_err(&pdev->dev, "cannot get IORESOURCE_MEM\n"); + ret = -ENOENT; + goto err_no_io_res; + } + if (!request_mem_region(res->start, resource_size(res), res->name)) { + dev_err(&pdev->dev, "cannot reserved region\n"); + ret = -ENXIO; + goto err_no_io_res; + } + + i2c_dev->reg_base = ioremap(res->start, resource_size(res)); + if (!i2c_dev->reg_base) { + ret = -EIO; + goto release_mem; + } + + i2c_dev->irq = platform_get_irq(pdev, 0); + if (i2c_dev->irq < 0) { + dev_err(&pdev->dev, "no irq specified\n"); + ret = -ENOENT; + goto ereqirq; + } + + i2c_dev->dev = &pdev->dev; + +#if defined (CONFIG_ARCH_AST1070) + if(i2c_dev->irq == IRQ_C0_I2C) { + i2c_dev->bus_id = pdev->id - NUM_BUS; + dev_dbg(&pdev->dev, "C0 :: pdev->id %d , i2c_dev->bus_id = %d, i2c_dev->irq =%d\n",pdev->id, i2c_dev->bus_id,i2c_dev->irq); +#if (CONFIG_AST1070_NR >= 2) + } else if(i2c_dev->irq == IRQ_C1_I2C) { + i2c_dev->bus_id = pdev->id - (NUM_BUS + 8); + dev_dbg(&pdev->dev, "C1 :: pdev->id %d , i2c_dev->bus_id = %d, i2c_dev->irq =%d\n",pdev->id, i2c_dev->bus_id,i2c_dev->irq); +#endif + } else { + i2c_dev->bus_id = pdev->id; + dev_dbg(&pdev->dev, "AST pdev->id %d , i2c_dev->bus_id = %d, i2c_dev->irq =%d\n",pdev->id, i2c_dev->bus_id,i2c_dev->irq); + } +#else + i2c_dev->bus_id = pdev->id; +#endif + + /* Initialize the I2C adapter */ + i2c_dev->adap.owner = THIS_MODULE; +//TODO + i2c_dev->adap.retries = 0; + +// i2c_dev->adap.retries = 3; + + i2c_dev->adap.timeout = 5; + + i2c_dev->master_xfer_mode = BYTE_XFER; + + /* + * If "pdev->id" is negative we consider it as zero. + * The reason to do so is to avoid sysfs names that only make + * sense when there are multiple adapters. + */ + i2c_dev->adap.nr = pdev->id != -1 ? pdev->id : 0; + snprintf(i2c_dev->adap.name, sizeof(i2c_dev->adap.name), "ast_i2c.%u", + i2c_dev->adap.nr); + + i2c_dev->slave_operation = 0; + i2c_dev->blk_r_flag = 0; + i2c_dev->adap.algo = &i2c_ast_algorithm; + + ast_i2c_dev_init(i2c_dev); + + ret = request_irq(i2c_dev->irq, i2c_ast_handler, IRQF_SHARED, + i2c_dev->adap.name, i2c_dev); + if (ret) { + printk(KERN_INFO "I2C: Failed request irq %d\n", i2c_dev->irq); + goto ereqirq; + } + + spin_lock_init(&i2c_dev->master_lock); + +#ifdef CONFIG_AST_I2C_SLAVE_RDWR + ast_i2c_slave_buff_init(i2c_dev); + spin_lock_init(&i2c_dev->slave_rx_lock); +#endif + + i2c_dev->adap.algo_data = i2c_dev; + i2c_dev->adap.dev.parent = &pdev->dev; + + i2c_dev->adap.id = pdev->id; + + ret = i2c_add_numbered_adapter(&i2c_dev->adap); + if (ret < 0) { + printk(KERN_INFO "I2C: Failed to add bus\n"); + goto eadapt; + } + + platform_set_drvdata(pdev, i2c_dev); + + printk(KERN_INFO "I2C: %s: AST I2C adapter [%d khz]\n", + i2c_dev->adap.dev.bus_id,i2c_dev->ast_i2c_data->bus_clk/1000); + + return 0; + +eadapt: + free_irq(i2c_dev->irq, i2c_dev); +ereqirq: + iounmap(i2c_dev->reg_base); + +release_mem: + release_mem_region(res->start, resource_size(res)); +err_no_io_res: +err_no_dma: + kfree(i2c_dev); + +err_no_mem: + return ret; +} + +static int ast_i2c_remove(struct platform_device *pdev) +{ + struct ast_i2c_dev *i2c_dev = platform_get_drvdata(pdev); + struct resource *res; + + platform_set_drvdata(pdev, NULL); + i2c_del_adapter(&i2c_dev->adap); + + free_irq(i2c_dev->irq, i2c_dev); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + iounmap(i2c_dev->reg_base); + release_mem_region(res->start, res->end - res->start + 1); + + kfree(i2c_dev); + + return 0; +} + +#ifdef CONFIG_PM +static int ast_i2c_suspend(struct platform_device *pdev, pm_message_t state) +{ + //TODO +// struct ast_i2c_dev *i2c_dev = platform_get_drvdata(pdev); + return 0; +} + +static int ast_i2c_resume(struct platform_device *pdev) +{ + //TODO +// struct ast_i2c_dev *i2c_dev = platform_get_drvdata(pdev); + //Should reset i2c ??? + return 0; +} +#else +#define ast_i2c_suspend NULL +#define ast_i2c_resume NULL +#endif + +static struct platform_driver i2c_ast_driver = { + .probe = ast_i2c_probe, + .remove = __devexit_p(ast_i2c_remove), + .suspend = ast_i2c_suspend, + .resume = ast_i2c_resume, + .driver = { + .name = "ast-i2c", + .owner = THIS_MODULE, + }, +}; + +static int __init ast_i2c_init(void) +{ + return platform_driver_register(&i2c_ast_driver); +} + +static void __exit ast_i2c_exit(void) +{ + platform_driver_unregister(&i2c_ast_driver); +} +//TODO : check module init sequence +module_init(ast_i2c_init); +module_exit(ast_i2c_exit); + +MODULE_AUTHOR("Ryan Chen <ryan_chen@aspeedtech.com>"); +MODULE_DESCRIPTION("ASPEED AST I2C Bus Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:ast_i2c"); diff --git a/drivers/i2c/busses/i2c-at91.c b/drivers/i2c/busses/i2c-at91.c new file mode 100644 index 0000000..9efb021 --- /dev/null +++ b/drivers/i2c/busses/i2c-at91.c @@ -0,0 +1,328 @@ +/* + i2c Support for Atmel's AT91 Two-Wire Interface (TWI) + + Copyright (C) 2004 Rick Bronson + Converted to 2.6 by Andrew Victor <andrew@sanpeople.com> + + Borrowed heavily from original work by: + Copyright (C) 2000 Philip Edelbrock <phil@stimpy.netroedge.com> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. +*/ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/clk.h> +#include <linux/platform_device.h> + +#include <asm/io.h> + +#include <mach/at91_twi.h> +#include <mach/board.h> +#include <mach/cpu.h> + +#define TWI_CLOCK 100000 /* Hz. max 400 Kbits/sec */ + + +static struct clk *twi_clk; +static void __iomem *twi_base; + +#define at91_twi_read(reg) __raw_readl(twi_base + (reg)) +#define at91_twi_write(reg, val) __raw_writel((val), twi_base + (reg)) + + +/* + * Initialize the TWI hardware registers. + */ +static void __devinit at91_twi_hwinit(void) +{ + unsigned long cdiv, ckdiv; + + at91_twi_write(AT91_TWI_IDR, 0xffffffff); /* Disable all interrupts */ + at91_twi_write(AT91_TWI_CR, AT91_TWI_SWRST); /* Reset peripheral */ + at91_twi_write(AT91_TWI_CR, AT91_TWI_MSEN); /* Set Master mode */ + + /* Calcuate clock dividers */ + cdiv = (clk_get_rate(twi_clk) / (2 * TWI_CLOCK)) - 3; + cdiv = cdiv + 1; /* round up */ + ckdiv = 0; + while (cdiv > 255) { + ckdiv++; + cdiv = cdiv >> 1; + } + + if (cpu_is_at91rm9200()) { /* AT91RM9200 Errata #22 */ + if (ckdiv > 5) { + printk(KERN_ERR "AT91 I2C: Invalid TWI_CLOCK value!\n"); + ckdiv = 5; + } + } + + at91_twi_write(AT91_TWI_CWGR, (ckdiv << 16) | (cdiv << 8) | cdiv); +} + +/* + * Poll the i2c status register until the specified bit is set. + * Returns 0 if timed out (100 msec). + */ +static short at91_poll_status(unsigned long bit) +{ + int loop_cntr = 10000; + + do { + udelay(10); + } while (!(at91_twi_read(AT91_TWI_SR) & bit) && (--loop_cntr > 0)); + + return (loop_cntr > 0); +} + +static int xfer_read(struct i2c_adapter *adap, unsigned char *buf, int length) +{ + /* Send Start */ + at91_twi_write(AT91_TWI_CR, AT91_TWI_START); + + /* Read data */ + while (length--) { + if (!length) /* need to send Stop before reading last byte */ + at91_twi_write(AT91_TWI_CR, AT91_TWI_STOP); + if (!at91_poll_status(AT91_TWI_RXRDY)) { + dev_dbg(&adap->dev, "RXRDY timeout\n"); + return -ETIMEDOUT; + } + *buf++ = (at91_twi_read(AT91_TWI_RHR) & 0xff); + } + + return 0; +} + +static int xfer_write(struct i2c_adapter *adap, unsigned char *buf, int length) +{ + /* Load first byte into transmitter */ + at91_twi_write(AT91_TWI_THR, *buf++); + + /* Send Start */ + at91_twi_write(AT91_TWI_CR, AT91_TWI_START); + + do { + if (!at91_poll_status(AT91_TWI_TXRDY)) { + dev_dbg(&adap->dev, "TXRDY timeout\n"); + return -ETIMEDOUT; + } + + length--; /* byte was transmitted */ + + if (length > 0) /* more data to send? */ + at91_twi_write(AT91_TWI_THR, *buf++); + } while (length); + + /* Send Stop */ + at91_twi_write(AT91_TWI_CR, AT91_TWI_STOP); + + return 0; +} + +/* + * Generic i2c master transfer entrypoint. + * + * Note: We do not use Atmel's feature of storing the "internal device address". + * Instead the "internal device address" has to be written using a separate + * i2c message. + * http://lists.arm.linux.org.uk/pipermail/linux-arm-kernel/2004-September/024411.html + */ +static int at91_xfer(struct i2c_adapter *adap, struct i2c_msg *pmsg, int num) +{ + int i, ret; + + dev_dbg(&adap->dev, "at91_xfer: processing %d messages:\n", num); + + for (i = 0; i < num; i++) { + dev_dbg(&adap->dev, " #%d: %sing %d byte%s %s 0x%02x\n", i, + pmsg->flags & I2C_M_RD ? "read" : "writ", + pmsg->len, pmsg->len > 1 ? "s" : "", + pmsg->flags & I2C_M_RD ? "from" : "to", pmsg->addr); + + at91_twi_write(AT91_TWI_MMR, (pmsg->addr << 16) + | ((pmsg->flags & I2C_M_RD) ? AT91_TWI_MREAD : 0)); + + if (pmsg->len && pmsg->buf) { /* sanity check */ + if (pmsg->flags & I2C_M_RD) + ret = xfer_read(adap, pmsg->buf, pmsg->len); + else + ret = xfer_write(adap, pmsg->buf, pmsg->len); + + if (ret) + return ret; + + /* Wait until transfer is finished */ + if (!at91_poll_status(AT91_TWI_TXCOMP)) { + dev_dbg(&adap->dev, "TXCOMP timeout\n"); + return -ETIMEDOUT; + } + } + dev_dbg(&adap->dev, "transfer complete\n"); + pmsg++; /* next message */ + } + return i; +} + +/* + * Return list of supported functionality. + */ +static u32 at91_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static struct i2c_algorithm at91_algorithm = { + .master_xfer = at91_xfer, + .functionality = at91_func, +}; + +/* + * Main initialization routine. + */ +static int __devinit at91_i2c_probe(struct platform_device *pdev) +{ + struct i2c_adapter *adapter; + struct resource *res; + int rc; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENXIO; + + if (!request_mem_region(res->start, res->end - res->start + 1, "at91_i2c")) + return -EBUSY; + + twi_base = ioremap(res->start, res->end - res->start + 1); + if (!twi_base) { + rc = -ENOMEM; + goto fail0; + } + + twi_clk = clk_get(NULL, "twi_clk"); + if (IS_ERR(twi_clk)) { + dev_err(&pdev->dev, "no clock defined\n"); + rc = -ENODEV; + goto fail1; + } + + adapter = kzalloc(sizeof(struct i2c_adapter), GFP_KERNEL); + if (adapter == NULL) { + dev_err(&pdev->dev, "can't allocate inteface!\n"); + rc = -ENOMEM; + goto fail2; + } + sprintf(adapter->name, "AT91"); + adapter->algo = &at91_algorithm; + adapter->class = I2C_CLASS_HWMON; + adapter->dev.parent = &pdev->dev; + /* adapter->id == 0 ... only one TWI controller for now */ + + platform_set_drvdata(pdev, adapter); + + clk_enable(twi_clk); /* enable peripheral clock */ + at91_twi_hwinit(); /* initialize TWI controller */ + + rc = i2c_add_numbered_adapter(adapter); + if (rc) { + dev_err(&pdev->dev, "Adapter %s registration failed\n", + adapter->name); + goto fail3; + } + + dev_info(&pdev->dev, "AT91 i2c bus driver.\n"); + return 0; + +fail3: + platform_set_drvdata(pdev, NULL); + kfree(adapter); + clk_disable(twi_clk); +fail2: + clk_put(twi_clk); +fail1: + iounmap(twi_base); +fail0: + release_mem_region(res->start, res->end - res->start + 1); + + return rc; +} + +static int __devexit at91_i2c_remove(struct platform_device *pdev) +{ + struct i2c_adapter *adapter = platform_get_drvdata(pdev); + struct resource *res; + int rc; + + rc = i2c_del_adapter(adapter); + platform_set_drvdata(pdev, NULL); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + iounmap(twi_base); + release_mem_region(res->start, res->end - res->start + 1); + + clk_disable(twi_clk); /* disable peripheral clock */ + clk_put(twi_clk); + + return rc; +} + +#ifdef CONFIG_PM + +/* NOTE: could save a few mA by keeping clock off outside of at91_xfer... */ + +static int at91_i2c_suspend(struct platform_device *pdev, pm_message_t mesg) +{ + clk_disable(twi_clk); + return 0; +} + +static int at91_i2c_resume(struct platform_device *pdev) +{ + return clk_enable(twi_clk); +} + +#else +#define at91_i2c_suspend NULL +#define at91_i2c_resume NULL +#endif + +/* work with "modprobe at91_i2c" from hotplugging or coldplugging */ +MODULE_ALIAS("platform:at91_i2c"); + +static struct platform_driver at91_i2c_driver = { + .probe = at91_i2c_probe, + .remove = __devexit_p(at91_i2c_remove), + .suspend = at91_i2c_suspend, + .resume = at91_i2c_resume, + .driver = { + .name = "at91_i2c", + .owner = THIS_MODULE, + }, +}; + +static int __init at91_i2c_init(void) +{ + return platform_driver_register(&at91_i2c_driver); +} + +static void __exit at91_i2c_exit(void) +{ + platform_driver_unregister(&at91_i2c_driver); +} + +module_init(at91_i2c_init); +module_exit(at91_i2c_exit); + +MODULE_AUTHOR("Rick Bronson"); +MODULE_DESCRIPTION("I2C (TWI) driver for Atmel AT91"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/busses/i2c-au1550.c b/drivers/i2c/busses/i2c-au1550.c new file mode 100644 index 0000000..66a04c2 --- /dev/null +++ b/drivers/i2c/busses/i2c-au1550.c @@ -0,0 +1,498 @@ +/* + * i2c-au1550.c: SMBus (i2c) adapter for Alchemy PSC interface + * Copyright (C) 2004 Embedded Edge, LLC <dan@embeddededge.com> + * + * 2.6 port by Matt Porter <mporter@kernel.crashing.org> + * + * The documentation describes this as an SMBus controller, but it doesn't + * understand any of the SMBus protocol in hardware. It's really an I2C + * controller that could emulate most of the SMBus in software. + * + * This is just a skeleton adapter to use with the Au1550 PSC + * algorithm. It was developed for the Pb1550, but will work with + * any Au1550 board that has a similar PSC configuration. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/i2c.h> +#include <linux/slab.h> + +#include <asm/mach-au1x00/au1xxx.h> +#include <asm/mach-au1x00/au1xxx_psc.h> + +struct i2c_au1550_data { + u32 psc_base; + int xfer_timeout; + int ack_timeout; + struct i2c_adapter adap; + struct resource *ioarea; +}; + +static int +wait_xfer_done(struct i2c_au1550_data *adap) +{ + u32 stat; + int i; + volatile psc_smb_t *sp; + + sp = (volatile psc_smb_t *)(adap->psc_base); + + /* Wait for Tx Buffer Empty + */ + for (i = 0; i < adap->xfer_timeout; i++) { + stat = sp->psc_smbstat; + au_sync(); + if ((stat & PSC_SMBSTAT_TE) != 0) + return 0; + + udelay(1); + } + + return -ETIMEDOUT; +} + +static int +wait_ack(struct i2c_au1550_data *adap) +{ + u32 stat; + volatile psc_smb_t *sp; + + if (wait_xfer_done(adap)) + return -ETIMEDOUT; + + sp = (volatile psc_smb_t *)(adap->psc_base); + + stat = sp->psc_smbevnt; + au_sync(); + + if ((stat & (PSC_SMBEVNT_DN | PSC_SMBEVNT_AN | PSC_SMBEVNT_AL)) != 0) + return -ETIMEDOUT; + + return 0; +} + +static int +wait_master_done(struct i2c_au1550_data *adap) +{ + u32 stat; + int i; + volatile psc_smb_t *sp; + + sp = (volatile psc_smb_t *)(adap->psc_base); + + /* Wait for Master Done. + */ + for (i = 0; i < adap->xfer_timeout; i++) { + stat = sp->psc_smbevnt; + au_sync(); + if ((stat & PSC_SMBEVNT_MD) != 0) + return 0; + udelay(1); + } + + return -ETIMEDOUT; +} + +static int +do_address(struct i2c_au1550_data *adap, unsigned int addr, int rd, int q) +{ + volatile psc_smb_t *sp; + u32 stat; + + sp = (volatile psc_smb_t *)(adap->psc_base); + + /* Reset the FIFOs, clear events. + */ + stat = sp->psc_smbstat; + sp->psc_smbevnt = PSC_SMBEVNT_ALLCLR; + au_sync(); + + if (!(stat & PSC_SMBSTAT_TE) || !(stat & PSC_SMBSTAT_RE)) { + sp->psc_smbpcr = PSC_SMBPCR_DC; + au_sync(); + do { + stat = sp->psc_smbpcr; + au_sync(); + } while ((stat & PSC_SMBPCR_DC) != 0); + udelay(50); + } + + /* Write out the i2c chip address and specify operation + */ + addr <<= 1; + if (rd) + addr |= 1; + + /* zero-byte xfers stop immediately */ + if (q) + addr |= PSC_SMBTXRX_STP; + + /* Put byte into fifo, start up master. + */ + sp->psc_smbtxrx = addr; + au_sync(); + sp->psc_smbpcr = PSC_SMBPCR_MS; + au_sync(); + if (wait_ack(adap)) + return -EIO; + return (q) ? wait_master_done(adap) : 0; +} + +static u32 +wait_for_rx_byte(struct i2c_au1550_data *adap, u32 *ret_data) +{ + int j; + u32 data, stat; + volatile psc_smb_t *sp; + + if (wait_xfer_done(adap)) + return -EIO; + + sp = (volatile psc_smb_t *)(adap->psc_base); + + j = adap->xfer_timeout * 100; + do { + j--; + if (j <= 0) + return -EIO; + + stat = sp->psc_smbstat; + au_sync(); + if ((stat & PSC_SMBSTAT_RE) == 0) + j = 0; + else + udelay(1); + } while (j > 0); + data = sp->psc_smbtxrx; + au_sync(); + *ret_data = data; + + return 0; +} + +static int +i2c_read(struct i2c_au1550_data *adap, unsigned char *buf, + unsigned int len) +{ + int i; + u32 data; + volatile psc_smb_t *sp; + + if (len == 0) + return 0; + + /* A read is performed by stuffing the transmit fifo with + * zero bytes for timing, waiting for bytes to appear in the + * receive fifo, then reading the bytes. + */ + + sp = (volatile psc_smb_t *)(adap->psc_base); + + i = 0; + while (i < (len-1)) { + sp->psc_smbtxrx = 0; + au_sync(); + if (wait_for_rx_byte(adap, &data)) + return -EIO; + + buf[i] = data; + i++; + } + + /* The last byte has to indicate transfer done. + */ + sp->psc_smbtxrx = PSC_SMBTXRX_STP; + au_sync(); + if (wait_master_done(adap)) + return -EIO; + + data = sp->psc_smbtxrx; + au_sync(); + buf[i] = data; + return 0; +} + +static int +i2c_write(struct i2c_au1550_data *adap, unsigned char *buf, + unsigned int len) +{ + int i; + u32 data; + volatile psc_smb_t *sp; + + if (len == 0) + return 0; + + sp = (volatile psc_smb_t *)(adap->psc_base); + + i = 0; + while (i < (len-1)) { + data = buf[i]; + sp->psc_smbtxrx = data; + au_sync(); + if (wait_ack(adap)) + return -EIO; + i++; + } + + /* The last byte has to indicate transfer done. + */ + data = buf[i]; + data |= PSC_SMBTXRX_STP; + sp->psc_smbtxrx = data; + au_sync(); + if (wait_master_done(adap)) + return -EIO; + return 0; +} + +static int +au1550_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg *msgs, int num) +{ + struct i2c_au1550_data *adap = i2c_adap->algo_data; + volatile psc_smb_t *sp = (volatile psc_smb_t *)adap->psc_base; + struct i2c_msg *p; + int i, err = 0; + + sp->psc_ctrl = PSC_CTRL_ENABLE; + au_sync(); + + for (i = 0; !err && i < num; i++) { + p = &msgs[i]; + err = do_address(adap, p->addr, p->flags & I2C_M_RD, + (p->len == 0)); + if (err || !p->len) + continue; + if (p->flags & I2C_M_RD) + err = i2c_read(adap, p->buf, p->len); + else + err = i2c_write(adap, p->buf, p->len); + } + + /* Return the number of messages processed, or the error code. + */ + if (err == 0) + err = num; + + sp->psc_ctrl = PSC_CTRL_SUSPEND; + au_sync(); + + return err; +} + +static u32 +au1550_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm au1550_algo = { + .master_xfer = au1550_xfer, + .functionality = au1550_func, +}; + +static void i2c_au1550_setup(struct i2c_au1550_data *priv) +{ + volatile psc_smb_t *sp = (volatile psc_smb_t *)priv->psc_base; + u32 stat; + + sp->psc_ctrl = PSC_CTRL_DISABLE; + au_sync(); + sp->psc_sel = PSC_SEL_PS_SMBUSMODE; + sp->psc_smbcfg = 0; + au_sync(); + sp->psc_ctrl = PSC_CTRL_ENABLE; + au_sync(); + do { + stat = sp->psc_smbstat; + au_sync(); + } while ((stat & PSC_SMBSTAT_SR) == 0); + + sp->psc_smbcfg = (PSC_SMBCFG_RT_FIFO8 | PSC_SMBCFG_TT_FIFO8 | + PSC_SMBCFG_DD_DISABLE); + + /* Divide by 8 to get a 6.25 MHz clock. The later protocol + * timings are based on this clock. + */ + sp->psc_smbcfg |= PSC_SMBCFG_SET_DIV(PSC_SMBCFG_DIV8); + sp->psc_smbmsk = PSC_SMBMSK_ALLMASK; + au_sync(); + + /* Set the protocol timer values. See Table 71 in the + * Au1550 Data Book for standard timing values. + */ + sp->psc_smbtmr = PSC_SMBTMR_SET_TH(0) | PSC_SMBTMR_SET_PS(15) | \ + PSC_SMBTMR_SET_PU(15) | PSC_SMBTMR_SET_SH(15) | \ + PSC_SMBTMR_SET_SU(15) | PSC_SMBTMR_SET_CL(15) | \ + PSC_SMBTMR_SET_CH(15); + au_sync(); + + sp->psc_smbcfg |= PSC_SMBCFG_DE_ENABLE; + do { + stat = sp->psc_smbstat; + au_sync(); + } while ((stat & PSC_SMBSTAT_SR) == 0); + + sp->psc_ctrl = PSC_CTRL_SUSPEND; + au_sync(); +} + +static void i2c_au1550_disable(struct i2c_au1550_data *priv) +{ + volatile psc_smb_t *sp = (volatile psc_smb_t *)priv->psc_base; + + sp->psc_smbcfg = 0; + sp->psc_ctrl = PSC_CTRL_DISABLE; + au_sync(); +} + +/* + * registering functions to load algorithms at runtime + * Prior to calling us, the 50MHz clock frequency and routing + * must have been set up for the PSC indicated by the adapter. + */ +static int __devinit +i2c_au1550_probe(struct platform_device *pdev) +{ + struct i2c_au1550_data *priv; + struct resource *r; + int ret; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + ret = -ENODEV; + goto out; + } + + priv = kzalloc(sizeof(struct i2c_au1550_data), GFP_KERNEL); + if (!priv) { + ret = -ENOMEM; + goto out; + } + + priv->ioarea = request_mem_region(r->start, r->end - r->start + 1, + pdev->name); + if (!priv->ioarea) { + ret = -EBUSY; + goto out_mem; + } + + priv->psc_base = CKSEG1ADDR(r->start); + priv->xfer_timeout = 200; + priv->ack_timeout = 200; + + priv->adap.id = I2C_HW_AU1550_PSC; + priv->adap.nr = pdev->id; + priv->adap.algo = &au1550_algo; + priv->adap.algo_data = priv; + priv->adap.dev.parent = &pdev->dev; + strlcpy(priv->adap.name, "Au1xxx PSC I2C", sizeof(priv->adap.name)); + + /* Now, set up the PSC for SMBus PIO mode. + */ + i2c_au1550_setup(priv); + + ret = i2c_add_numbered_adapter(&priv->adap); + if (ret == 0) { + platform_set_drvdata(pdev, priv); + return 0; + } + + i2c_au1550_disable(priv); + + release_resource(priv->ioarea); + kfree(priv->ioarea); +out_mem: + kfree(priv); +out: + return ret; +} + +static int __devexit +i2c_au1550_remove(struct platform_device *pdev) +{ + struct i2c_au1550_data *priv = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + i2c_del_adapter(&priv->adap); + i2c_au1550_disable(priv); + release_resource(priv->ioarea); + kfree(priv->ioarea); + kfree(priv); + return 0; +} + +#ifdef CONFIG_PM +static int +i2c_au1550_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct i2c_au1550_data *priv = platform_get_drvdata(pdev); + + i2c_au1550_disable(priv); + + return 0; +} + +static int +i2c_au1550_resume(struct platform_device *pdev) +{ + struct i2c_au1550_data *priv = platform_get_drvdata(pdev); + + i2c_au1550_setup(priv); + + return 0; +} +#else +#define i2c_au1550_suspend NULL +#define i2c_au1550_resume NULL +#endif + +static struct platform_driver au1xpsc_smbus_driver = { + .driver = { + .name = "au1xpsc_smbus", + .owner = THIS_MODULE, + }, + .probe = i2c_au1550_probe, + .remove = __devexit_p(i2c_au1550_remove), + .suspend = i2c_au1550_suspend, + .resume = i2c_au1550_resume, +}; + +static int __init +i2c_au1550_init(void) +{ + return platform_driver_register(&au1xpsc_smbus_driver); +} + +static void __exit +i2c_au1550_exit(void) +{ + platform_driver_unregister(&au1xpsc_smbus_driver); +} + +MODULE_AUTHOR("Dan Malek, Embedded Edge, LLC."); +MODULE_DESCRIPTION("SMBus adapter Alchemy pb1550"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:au1xpsc_smbus"); + +module_init (i2c_au1550_init); +module_exit (i2c_au1550_exit); diff --git a/drivers/i2c/busses/i2c-bfin-twi.c b/drivers/i2c/busses/i2c-bfin-twi.c new file mode 100644 index 0000000..3c855ff --- /dev/null +++ b/drivers/i2c/busses/i2c-bfin-twi.c @@ -0,0 +1,757 @@ +/* + * Blackfin On-Chip Two Wire Interface Driver + * + * Copyright 2005-2007 Analog Devices Inc. + * + * Enter bugs at http://blackfin.uclinux.org/ + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/mm.h> +#include <linux/timer.h> +#include <linux/spinlock.h> +#include <linux/completion.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> + +#include <asm/blackfin.h> +#include <asm/portmux.h> +#include <asm/irq.h> + +#define POLL_TIMEOUT (2 * HZ) + +/* SMBus mode*/ +#define TWI_I2C_MODE_STANDARD 1 +#define TWI_I2C_MODE_STANDARDSUB 2 +#define TWI_I2C_MODE_COMBINED 3 +#define TWI_I2C_MODE_REPEAT 4 + +struct bfin_twi_iface { + int irq; + spinlock_t lock; + char read_write; + u8 command; + u8 *transPtr; + int readNum; + int writeNum; + int cur_mode; + int manual_stop; + int result; + int timeout_count; + struct timer_list timeout_timer; + struct i2c_adapter adap; + struct completion complete; + struct i2c_msg *pmsg; + int msg_num; + int cur_msg; + u16 saved_clkdiv; + u16 saved_control; + void __iomem *regs_base; +}; + + +#define DEFINE_TWI_REG(reg, off) \ +static inline u16 read_##reg(struct bfin_twi_iface *iface) \ + { return bfin_read16(iface->regs_base + (off)); } \ +static inline void write_##reg(struct bfin_twi_iface *iface, u16 v) \ + { bfin_write16(iface->regs_base + (off), v); } + +DEFINE_TWI_REG(CLKDIV, 0x00) +DEFINE_TWI_REG(CONTROL, 0x04) +DEFINE_TWI_REG(SLAVE_CTL, 0x08) +DEFINE_TWI_REG(SLAVE_STAT, 0x0C) +DEFINE_TWI_REG(SLAVE_ADDR, 0x10) +DEFINE_TWI_REG(MASTER_CTL, 0x14) +DEFINE_TWI_REG(MASTER_STAT, 0x18) +DEFINE_TWI_REG(MASTER_ADDR, 0x1C) +DEFINE_TWI_REG(INT_STAT, 0x20) +DEFINE_TWI_REG(INT_MASK, 0x24) +DEFINE_TWI_REG(FIFO_CTL, 0x28) +DEFINE_TWI_REG(FIFO_STAT, 0x2C) +DEFINE_TWI_REG(XMT_DATA8, 0x80) +DEFINE_TWI_REG(XMT_DATA16, 0x84) +DEFINE_TWI_REG(RCV_DATA8, 0x88) +DEFINE_TWI_REG(RCV_DATA16, 0x8C) + +static const u16 pin_req[2][3] = { + {P_TWI0_SCL, P_TWI0_SDA, 0}, + {P_TWI1_SCL, P_TWI1_SDA, 0}, +}; + +static void bfin_twi_handle_interrupt(struct bfin_twi_iface *iface) +{ + unsigned short twi_int_status = read_INT_STAT(iface); + unsigned short mast_stat = read_MASTER_STAT(iface); + + if (twi_int_status & XMTSERV) { + /* Transmit next data */ + if (iface->writeNum > 0) { + write_XMT_DATA8(iface, *(iface->transPtr++)); + iface->writeNum--; + } + /* start receive immediately after complete sending in + * combine mode. + */ + else if (iface->cur_mode == TWI_I2C_MODE_COMBINED) + write_MASTER_CTL(iface, + read_MASTER_CTL(iface) | MDIR | RSTART); + else if (iface->manual_stop) + write_MASTER_CTL(iface, + read_MASTER_CTL(iface) | STOP); + else if (iface->cur_mode == TWI_I2C_MODE_REPEAT && + iface->cur_msg+1 < iface->msg_num) + write_MASTER_CTL(iface, + read_MASTER_CTL(iface) | RSTART); + SSYNC(); + /* Clear status */ + write_INT_STAT(iface, XMTSERV); + SSYNC(); + } + if (twi_int_status & RCVSERV) { + if (iface->readNum > 0) { + /* Receive next data */ + *(iface->transPtr) = read_RCV_DATA8(iface); + if (iface->cur_mode == TWI_I2C_MODE_COMBINED) { + /* Change combine mode into sub mode after + * read first data. + */ + iface->cur_mode = TWI_I2C_MODE_STANDARDSUB; + /* Get read number from first byte in block + * combine mode. + */ + if (iface->readNum == 1 && iface->manual_stop) + iface->readNum = *iface->transPtr + 1; + } + iface->transPtr++; + iface->readNum--; + } else if (iface->manual_stop) { + write_MASTER_CTL(iface, + read_MASTER_CTL(iface) | STOP); + SSYNC(); + } else if (iface->cur_mode == TWI_I2C_MODE_REPEAT && + iface->cur_msg+1 < iface->msg_num) { + write_MASTER_CTL(iface, + read_MASTER_CTL(iface) | RSTART); + SSYNC(); + } + /* Clear interrupt source */ + write_INT_STAT(iface, RCVSERV); + SSYNC(); + } + if (twi_int_status & MERR) { + write_INT_STAT(iface, MERR); + write_INT_MASK(iface, 0); + write_MASTER_STAT(iface, 0x3e); + write_MASTER_CTL(iface, 0); + SSYNC(); + iface->result = -EIO; + /* if both err and complete int stats are set, return proper + * results. + */ + if (twi_int_status & MCOMP) { + write_INT_STAT(iface, MCOMP); + write_INT_MASK(iface, 0); + write_MASTER_CTL(iface, 0); + SSYNC(); + /* If it is a quick transfer, only address bug no data, + * not an err, return 1. + */ + if (iface->writeNum == 0 && (mast_stat & BUFRDERR)) + iface->result = 1; + /* If address not acknowledged return -1, + * else return 0. + */ + else if (!(mast_stat & ANAK)) + iface->result = 0; + } + complete(&iface->complete); + return; + } + if (twi_int_status & MCOMP) { + write_INT_STAT(iface, MCOMP); + SSYNC(); + if (iface->cur_mode == TWI_I2C_MODE_COMBINED) { + if (iface->readNum == 0) { + /* set the read number to 1 and ask for manual + * stop in block combine mode + */ + iface->readNum = 1; + iface->manual_stop = 1; + write_MASTER_CTL(iface, + read_MASTER_CTL(iface) | (0xff << 6)); + } else { + /* set the readd number in other + * combine mode. + */ + write_MASTER_CTL(iface, + (read_MASTER_CTL(iface) & + (~(0xff << 6))) | + (iface->readNum << 6)); + } + /* remove restart bit and enable master receive */ + write_MASTER_CTL(iface, + read_MASTER_CTL(iface) & ~RSTART); + write_MASTER_CTL(iface, + read_MASTER_CTL(iface) | MEN | MDIR); + SSYNC(); + } else if (iface->cur_mode == TWI_I2C_MODE_REPEAT && + iface->cur_msg+1 < iface->msg_num) { + iface->cur_msg++; + iface->transPtr = iface->pmsg[iface->cur_msg].buf; + iface->writeNum = iface->readNum = + iface->pmsg[iface->cur_msg].len; + /* Set Transmit device address */ + write_MASTER_ADDR(iface, + iface->pmsg[iface->cur_msg].addr); + if (iface->pmsg[iface->cur_msg].flags & I2C_M_RD) + iface->read_write = I2C_SMBUS_READ; + else { + iface->read_write = I2C_SMBUS_WRITE; + /* Transmit first data */ + if (iface->writeNum > 0) { + write_XMT_DATA8(iface, + *(iface->transPtr++)); + iface->writeNum--; + SSYNC(); + } + } + + if (iface->pmsg[iface->cur_msg].len <= 255) + write_MASTER_CTL(iface, + iface->pmsg[iface->cur_msg].len << 6); + else { + write_MASTER_CTL(iface, 0xff << 6); + iface->manual_stop = 1; + } + /* remove restart bit and enable master receive */ + write_MASTER_CTL(iface, + read_MASTER_CTL(iface) & ~RSTART); + write_MASTER_CTL(iface, read_MASTER_CTL(iface) | + MEN | ((iface->read_write == I2C_SMBUS_READ) ? + MDIR : 0)); + SSYNC(); + } else { + iface->result = 1; + write_INT_MASK(iface, 0); + write_MASTER_CTL(iface, 0); + SSYNC(); + complete(&iface->complete); + } + } +} + +/* Interrupt handler */ +static irqreturn_t bfin_twi_interrupt_entry(int irq, void *dev_id) +{ + struct bfin_twi_iface *iface = dev_id; + unsigned long flags; + + spin_lock_irqsave(&iface->lock, flags); + del_timer(&iface->timeout_timer); + bfin_twi_handle_interrupt(iface); + spin_unlock_irqrestore(&iface->lock, flags); + return IRQ_HANDLED; +} + +static void bfin_twi_timeout(unsigned long data) +{ + struct bfin_twi_iface *iface = (struct bfin_twi_iface *)data; + unsigned long flags; + + spin_lock_irqsave(&iface->lock, flags); + bfin_twi_handle_interrupt(iface); + if (iface->result == 0) { + iface->timeout_count--; + if (iface->timeout_count > 0) { + iface->timeout_timer.expires = jiffies + POLL_TIMEOUT; + add_timer(&iface->timeout_timer); + } else { + iface->result = -1; + complete(&iface->complete); + } + } + spin_unlock_irqrestore(&iface->lock, flags); +} + +/* + * Generic i2c master transfer entrypoint + */ +static int bfin_twi_master_xfer(struct i2c_adapter *adap, + struct i2c_msg *msgs, int num) +{ + struct bfin_twi_iface *iface = adap->algo_data; + struct i2c_msg *pmsg; + int rc = 0; + + if (!(read_CONTROL(iface) & TWI_ENA)) + return -ENXIO; + + while (read_MASTER_STAT(iface) & BUSBUSY) + yield(); + + iface->pmsg = msgs; + iface->msg_num = num; + iface->cur_msg = 0; + + pmsg = &msgs[0]; + if (pmsg->flags & I2C_M_TEN) { + dev_err(&adap->dev, "10 bits addr not supported!\n"); + return -EINVAL; + } + + iface->cur_mode = TWI_I2C_MODE_REPEAT; + iface->manual_stop = 0; + iface->transPtr = pmsg->buf; + iface->writeNum = iface->readNum = pmsg->len; + iface->result = 0; + iface->timeout_count = 10; + init_completion(&(iface->complete)); + /* Set Transmit device address */ + write_MASTER_ADDR(iface, pmsg->addr); + + /* FIFO Initiation. Data in FIFO should be + * discarded before start a new operation. + */ + write_FIFO_CTL(iface, 0x3); + SSYNC(); + write_FIFO_CTL(iface, 0); + SSYNC(); + + if (pmsg->flags & I2C_M_RD) + iface->read_write = I2C_SMBUS_READ; + else { + iface->read_write = I2C_SMBUS_WRITE; + /* Transmit first data */ + if (iface->writeNum > 0) { + write_XMT_DATA8(iface, *(iface->transPtr++)); + iface->writeNum--; + SSYNC(); + } + } + + /* clear int stat */ + write_INT_STAT(iface, MERR | MCOMP | XMTSERV | RCVSERV); + + /* Interrupt mask . Enable XMT, RCV interrupt */ + write_INT_MASK(iface, MCOMP | MERR | RCVSERV | XMTSERV); + SSYNC(); + + if (pmsg->len <= 255) + write_MASTER_CTL(iface, pmsg->len << 6); + else { + write_MASTER_CTL(iface, 0xff << 6); + iface->manual_stop = 1; + } + + iface->timeout_timer.expires = jiffies + POLL_TIMEOUT; + add_timer(&iface->timeout_timer); + + /* Master enable */ + write_MASTER_CTL(iface, read_MASTER_CTL(iface) | MEN | + ((iface->read_write == I2C_SMBUS_READ) ? MDIR : 0) | + ((CONFIG_I2C_BLACKFIN_TWI_CLK_KHZ > 100) ? FAST : 0)); + SSYNC(); + + wait_for_completion(&iface->complete); + + rc = iface->result; + + if (rc == 1) + return num; + else + return rc; +} + +/* + * SMBus type transfer entrypoint + */ + +int bfin_twi_smbus_xfer(struct i2c_adapter *adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, union i2c_smbus_data *data) +{ + struct bfin_twi_iface *iface = adap->algo_data; + int rc = 0; + + if (!(read_CONTROL(iface) & TWI_ENA)) + return -ENXIO; + + while (read_MASTER_STAT(iface) & BUSBUSY) + yield(); + + iface->writeNum = 0; + iface->readNum = 0; + + /* Prepare datas & select mode */ + switch (size) { + case I2C_SMBUS_QUICK: + iface->transPtr = NULL; + iface->cur_mode = TWI_I2C_MODE_STANDARD; + break; + case I2C_SMBUS_BYTE: + if (data == NULL) + iface->transPtr = NULL; + else { + if (read_write == I2C_SMBUS_READ) + iface->readNum = 1; + else + iface->writeNum = 1; + iface->transPtr = &data->byte; + } + iface->cur_mode = TWI_I2C_MODE_STANDARD; + break; + case I2C_SMBUS_BYTE_DATA: + if (read_write == I2C_SMBUS_READ) { + iface->readNum = 1; + iface->cur_mode = TWI_I2C_MODE_COMBINED; + } else { + iface->writeNum = 1; + iface->cur_mode = TWI_I2C_MODE_STANDARDSUB; + } + iface->transPtr = &data->byte; + break; + case I2C_SMBUS_WORD_DATA: + if (read_write == I2C_SMBUS_READ) { + iface->readNum = 2; + iface->cur_mode = TWI_I2C_MODE_COMBINED; + } else { + iface->writeNum = 2; + iface->cur_mode = TWI_I2C_MODE_STANDARDSUB; + } + iface->transPtr = (u8 *)&data->word; + break; + case I2C_SMBUS_PROC_CALL: + iface->writeNum = 2; + iface->readNum = 2; + iface->cur_mode = TWI_I2C_MODE_COMBINED; + iface->transPtr = (u8 *)&data->word; + break; + case I2C_SMBUS_BLOCK_DATA: + if (read_write == I2C_SMBUS_READ) { + iface->readNum = 0; + iface->cur_mode = TWI_I2C_MODE_COMBINED; + } else { + iface->writeNum = data->block[0] + 1; + iface->cur_mode = TWI_I2C_MODE_STANDARDSUB; + } + iface->transPtr = data->block; + break; + default: + return -1; + } + + iface->result = 0; + iface->manual_stop = 0; + iface->read_write = read_write; + iface->command = command; + iface->timeout_count = 10; + init_completion(&(iface->complete)); + + /* FIFO Initiation. Data in FIFO should be discarded before + * start a new operation. + */ + write_FIFO_CTL(iface, 0x3); + SSYNC(); + write_FIFO_CTL(iface, 0); + + /* clear int stat */ + write_INT_STAT(iface, MERR | MCOMP | XMTSERV | RCVSERV); + + /* Set Transmit device address */ + write_MASTER_ADDR(iface, addr); + SSYNC(); + + iface->timeout_timer.expires = jiffies + POLL_TIMEOUT; + add_timer(&iface->timeout_timer); + + switch (iface->cur_mode) { + case TWI_I2C_MODE_STANDARDSUB: + write_XMT_DATA8(iface, iface->command); + write_INT_MASK(iface, MCOMP | MERR | + ((iface->read_write == I2C_SMBUS_READ) ? + RCVSERV : XMTSERV)); + SSYNC(); + + if (iface->writeNum + 1 <= 255) + write_MASTER_CTL(iface, (iface->writeNum + 1) << 6); + else { + write_MASTER_CTL(iface, 0xff << 6); + iface->manual_stop = 1; + } + /* Master enable */ + write_MASTER_CTL(iface, read_MASTER_CTL(iface) | MEN | + ((CONFIG_I2C_BLACKFIN_TWI_CLK_KHZ>100) ? FAST : 0)); + break; + case TWI_I2C_MODE_COMBINED: + write_XMT_DATA8(iface, iface->command); + write_INT_MASK(iface, MCOMP | MERR | RCVSERV | XMTSERV); + SSYNC(); + + if (iface->writeNum > 0) + write_MASTER_CTL(iface, (iface->writeNum + 1) << 6); + else + write_MASTER_CTL(iface, 0x1 << 6); + /* Master enable */ + write_MASTER_CTL(iface, read_MASTER_CTL(iface) | MEN | + ((CONFIG_I2C_BLACKFIN_TWI_CLK_KHZ>100) ? FAST : 0)); + break; + default: + write_MASTER_CTL(iface, 0); + if (size != I2C_SMBUS_QUICK) { + /* Don't access xmit data register when this is a + * read operation. + */ + if (iface->read_write != I2C_SMBUS_READ) { + if (iface->writeNum > 0) { + write_XMT_DATA8(iface, + *(iface->transPtr++)); + if (iface->writeNum <= 255) + write_MASTER_CTL(iface, + iface->writeNum << 6); + else { + write_MASTER_CTL(iface, + 0xff << 6); + iface->manual_stop = 1; + } + iface->writeNum--; + } else { + write_XMT_DATA8(iface, iface->command); + write_MASTER_CTL(iface, 1 << 6); + } + } else { + if (iface->readNum > 0 && iface->readNum <= 255) + write_MASTER_CTL(iface, + iface->readNum << 6); + else if (iface->readNum > 255) { + write_MASTER_CTL(iface, 0xff << 6); + iface->manual_stop = 1; + } else { + del_timer(&iface->timeout_timer); + break; + } + } + } + write_INT_MASK(iface, MCOMP | MERR | + ((iface->read_write == I2C_SMBUS_READ) ? + RCVSERV : XMTSERV)); + SSYNC(); + + /* Master enable */ + write_MASTER_CTL(iface, read_MASTER_CTL(iface) | MEN | + ((iface->read_write == I2C_SMBUS_READ) ? MDIR : 0) | + ((CONFIG_I2C_BLACKFIN_TWI_CLK_KHZ > 100) ? FAST : 0)); + break; + } + SSYNC(); + + wait_for_completion(&iface->complete); + + rc = (iface->result >= 0) ? 0 : -1; + + return rc; +} + +/* + * Return what the adapter supports + */ +static u32 bfin_twi_functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_SMBUS_PROC_CALL | + I2C_FUNC_I2C; +} + +static struct i2c_algorithm bfin_twi_algorithm = { + .master_xfer = bfin_twi_master_xfer, + .smbus_xfer = bfin_twi_smbus_xfer, + .functionality = bfin_twi_functionality, +}; + +static int i2c_bfin_twi_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct bfin_twi_iface *iface = platform_get_drvdata(pdev); + + iface->saved_clkdiv = read_CLKDIV(iface); + iface->saved_control = read_CONTROL(iface); + + free_irq(iface->irq, iface); + + /* Disable TWI */ + write_CONTROL(iface, iface->saved_control & ~TWI_ENA); + + return 0; +} + +static int i2c_bfin_twi_resume(struct platform_device *pdev) +{ + struct bfin_twi_iface *iface = platform_get_drvdata(pdev); + + int rc = request_irq(iface->irq, bfin_twi_interrupt_entry, + IRQF_DISABLED, pdev->name, iface); + if (rc) { + dev_err(&pdev->dev, "Can't get IRQ %d !\n", iface->irq); + return -ENODEV; + } + + /* Resume TWI interface clock as specified */ + write_CLKDIV(iface, iface->saved_clkdiv); + + /* Resume TWI */ + write_CONTROL(iface, iface->saved_control); + + return 0; +} + +static int i2c_bfin_twi_probe(struct platform_device *pdev) +{ + struct bfin_twi_iface *iface; + struct i2c_adapter *p_adap; + struct resource *res; + int rc; + + iface = kzalloc(sizeof(struct bfin_twi_iface), GFP_KERNEL); + if (!iface) { + dev_err(&pdev->dev, "Cannot allocate memory\n"); + rc = -ENOMEM; + goto out_error_nomem; + } + + spin_lock_init(&(iface->lock)); + + /* Find and map our resources */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "Cannot get IORESOURCE_MEM\n"); + rc = -ENOENT; + goto out_error_get_res; + } + + iface->regs_base = ioremap(res->start, res->end - res->start + 1); + if (iface->regs_base == NULL) { + dev_err(&pdev->dev, "Cannot map IO\n"); + rc = -ENXIO; + goto out_error_ioremap; + } + + iface->irq = platform_get_irq(pdev, 0); + if (iface->irq < 0) { + dev_err(&pdev->dev, "No IRQ specified\n"); + rc = -ENOENT; + goto out_error_no_irq; + } + + init_timer(&(iface->timeout_timer)); + iface->timeout_timer.function = bfin_twi_timeout; + iface->timeout_timer.data = (unsigned long)iface; + + p_adap = &iface->adap; + p_adap->id = I2C_HW_BLACKFIN; + p_adap->nr = pdev->id; + strlcpy(p_adap->name, pdev->name, sizeof(p_adap->name)); + p_adap->algo = &bfin_twi_algorithm; + p_adap->algo_data = iface; + p_adap->class = I2C_CLASS_ALL; + p_adap->dev.parent = &pdev->dev; + + rc = peripheral_request_list(pin_req[pdev->id], "i2c-bfin-twi"); + if (rc) { + dev_err(&pdev->dev, "Can't setup pin mux!\n"); + goto out_error_pin_mux; + } + + rc = request_irq(iface->irq, bfin_twi_interrupt_entry, + IRQF_DISABLED, pdev->name, iface); + if (rc) { + dev_err(&pdev->dev, "Can't get IRQ %d !\n", iface->irq); + rc = -ENODEV; + goto out_error_req_irq; + } + + /* Set TWI internal clock as 10MHz */ + write_CONTROL(iface, ((get_sclk() / 1024 / 1024 + 5) / 10) & 0x7F); + + /* Set Twi interface clock as specified */ + write_CLKDIV(iface, ((5*1024 / CONFIG_I2C_BLACKFIN_TWI_CLK_KHZ) + << 8) | ((5*1024 / CONFIG_I2C_BLACKFIN_TWI_CLK_KHZ) + & 0xFF)); + + /* Enable TWI */ + write_CONTROL(iface, read_CONTROL(iface) | TWI_ENA); + SSYNC(); + + rc = i2c_add_numbered_adapter(p_adap); + if (rc < 0) { + dev_err(&pdev->dev, "Can't add i2c adapter!\n"); + goto out_error_add_adapter; + } + + platform_set_drvdata(pdev, iface); + + dev_info(&pdev->dev, "Blackfin BF5xx on-chip I2C TWI Contoller, " + "regs_base@%p\n", iface->regs_base); + + return 0; + +out_error_add_adapter: + free_irq(iface->irq, iface); +out_error_req_irq: +out_error_no_irq: + peripheral_free_list(pin_req[pdev->id]); +out_error_pin_mux: + iounmap(iface->regs_base); +out_error_ioremap: +out_error_get_res: + kfree(iface); +out_error_nomem: + return rc; +} + +static int i2c_bfin_twi_remove(struct platform_device *pdev) +{ + struct bfin_twi_iface *iface = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + + i2c_del_adapter(&(iface->adap)); + free_irq(iface->irq, iface); + peripheral_free_list(pin_req[pdev->id]); + iounmap(iface->regs_base); + kfree(iface); + + return 0; +} + +static struct platform_driver i2c_bfin_twi_driver = { + .probe = i2c_bfin_twi_probe, + .remove = i2c_bfin_twi_remove, + .suspend = i2c_bfin_twi_suspend, + .resume = i2c_bfin_twi_resume, + .driver = { + .name = "i2c-bfin-twi", + .owner = THIS_MODULE, + }, +}; + +static int __init i2c_bfin_twi_init(void) +{ + return platform_driver_register(&i2c_bfin_twi_driver); +} + +static void __exit i2c_bfin_twi_exit(void) +{ + platform_driver_unregister(&i2c_bfin_twi_driver); +} + +module_init(i2c_bfin_twi_init); +module_exit(i2c_bfin_twi_exit); + +MODULE_AUTHOR("Bryan Wu, Sonic Zhang"); +MODULE_DESCRIPTION("Blackfin BF5xx on-chip I2C TWI Contoller Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:i2c-bfin-twi"); diff --git a/drivers/i2c/busses/i2c-cpm.c b/drivers/i2c/busses/i2c-cpm.c new file mode 100644 index 0000000..3fcf78e --- /dev/null +++ b/drivers/i2c/busses/i2c-cpm.c @@ -0,0 +1,745 @@ +/* + * Freescale CPM1/CPM2 I2C interface. + * Copyright (c) 1999 Dan Malek (dmalek@jlc.net). + * + * moved into proper i2c interface; + * Brad Parker (brad@heeltoe.com) + * + * Parts from dbox2_i2c.c (cvs.tuxbox.org) + * (C) 2000-2001 Felix Domke (tmbinc@gmx.net), Gillem (htoa@gmx.net) + * + * (C) 2007 Montavista Software, Inc. + * Vitaly Bordug <vitb@kernel.crashing.org> + * + * Converted to of_platform_device. Renamed to i2c-cpm.c. + * (C) 2007,2008 Jochen Friedrich <jochen@scram.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/errno.h> +#include <linux/stddef.h> +#include <linux/i2c.h> +#include <linux/io.h> +#include <linux/dma-mapping.h> +#include <linux/of_device.h> +#include <linux/of_platform.h> +#include <linux/of_i2c.h> +#include <sysdev/fsl_soc.h> +#include <asm/cpm.h> + +/* Try to define this if you have an older CPU (earlier than rev D4) */ +/* However, better use a GPIO based bitbang driver in this case :/ */ +#undef I2C_CHIP_ERRATA + +#define CPM_MAX_READ 513 +#define CPM_MAXBD 4 + +#define I2C_EB (0x10) /* Big endian mode */ +#define I2C_EB_CPM2 (0x30) /* Big endian mode, memory snoop */ + +#define DPRAM_BASE ((u8 __iomem __force *)cpm_muram_addr(0)) + +/* I2C parameter RAM. */ +struct i2c_ram { + ushort rbase; /* Rx Buffer descriptor base address */ + ushort tbase; /* Tx Buffer descriptor base address */ + u_char rfcr; /* Rx function code */ + u_char tfcr; /* Tx function code */ + ushort mrblr; /* Max receive buffer length */ + uint rstate; /* Internal */ + uint rdp; /* Internal */ + ushort rbptr; /* Rx Buffer descriptor pointer */ + ushort rbc; /* Internal */ + uint rxtmp; /* Internal */ + uint tstate; /* Internal */ + uint tdp; /* Internal */ + ushort tbptr; /* Tx Buffer descriptor pointer */ + ushort tbc; /* Internal */ + uint txtmp; /* Internal */ + char res1[4]; /* Reserved */ + ushort rpbase; /* Relocation pointer */ + char res2[2]; /* Reserved */ +}; + +#define I2COM_START 0x80 +#define I2COM_MASTER 0x01 +#define I2CER_TXE 0x10 +#define I2CER_BUSY 0x04 +#define I2CER_TXB 0x02 +#define I2CER_RXB 0x01 +#define I2MOD_EN 0x01 + +/* I2C Registers */ +struct i2c_reg { + u8 i2mod; + u8 res1[3]; + u8 i2add; + u8 res2[3]; + u8 i2brg; + u8 res3[3]; + u8 i2com; + u8 res4[3]; + u8 i2cer; + u8 res5[3]; + u8 i2cmr; +}; + +struct cpm_i2c { + char *base; + struct of_device *ofdev; + struct i2c_adapter adap; + uint dp_addr; + int version; /* CPM1=1, CPM2=2 */ + int irq; + int cp_command; + int freq; + struct i2c_reg __iomem *i2c_reg; + struct i2c_ram __iomem *i2c_ram; + u16 i2c_addr; + wait_queue_head_t i2c_wait; + cbd_t __iomem *tbase; + cbd_t __iomem *rbase; + u_char *txbuf[CPM_MAXBD]; + u_char *rxbuf[CPM_MAXBD]; + u32 txdma[CPM_MAXBD]; + u32 rxdma[CPM_MAXBD]; +}; + +static irqreturn_t cpm_i2c_interrupt(int irq, void *dev_id) +{ + struct cpm_i2c *cpm; + struct i2c_reg __iomem *i2c_reg; + struct i2c_adapter *adap = dev_id; + int i; + + cpm = i2c_get_adapdata(dev_id); + i2c_reg = cpm->i2c_reg; + + /* Clear interrupt. */ + i = in_8(&i2c_reg->i2cer); + out_8(&i2c_reg->i2cer, i); + + dev_dbg(&adap->dev, "Interrupt: %x\n", i); + + wake_up_interruptible(&cpm->i2c_wait); + + return i ? IRQ_HANDLED : IRQ_NONE; +} + +static void cpm_reset_i2c_params(struct cpm_i2c *cpm) +{ + struct i2c_ram __iomem *i2c_ram = cpm->i2c_ram; + + /* Set up the I2C parameters in the parameter ram. */ + out_be16(&i2c_ram->tbase, (u8 __iomem *)cpm->tbase - DPRAM_BASE); + out_be16(&i2c_ram->rbase, (u8 __iomem *)cpm->rbase - DPRAM_BASE); + + if (cpm->version == 1) { + out_8(&i2c_ram->tfcr, I2C_EB); + out_8(&i2c_ram->rfcr, I2C_EB); + } else { + out_8(&i2c_ram->tfcr, I2C_EB_CPM2); + out_8(&i2c_ram->rfcr, I2C_EB_CPM2); + } + + out_be16(&i2c_ram->mrblr, CPM_MAX_READ); + + out_be32(&i2c_ram->rstate, 0); + out_be32(&i2c_ram->rdp, 0); + out_be16(&i2c_ram->rbptr, 0); + out_be16(&i2c_ram->rbc, 0); + out_be32(&i2c_ram->rxtmp, 0); + out_be32(&i2c_ram->tstate, 0); + out_be32(&i2c_ram->tdp, 0); + out_be16(&i2c_ram->tbptr, 0); + out_be16(&i2c_ram->tbc, 0); + out_be32(&i2c_ram->txtmp, 0); +} + +static void cpm_i2c_force_close(struct i2c_adapter *adap) +{ + struct cpm_i2c *cpm = i2c_get_adapdata(adap); + struct i2c_reg __iomem *i2c_reg = cpm->i2c_reg; + + dev_dbg(&adap->dev, "cpm_i2c_force_close()\n"); + + cpm_command(cpm->cp_command, CPM_CR_CLOSE_RX_BD); + + out_8(&i2c_reg->i2cmr, 0x00); /* Disable all interrupts */ + out_8(&i2c_reg->i2cer, 0xff); +} + +static void cpm_i2c_parse_message(struct i2c_adapter *adap, + struct i2c_msg *pmsg, int num, int tx, int rx) +{ + cbd_t __iomem *tbdf; + cbd_t __iomem *rbdf; + u_char addr; + u_char *tb; + u_char *rb; + struct cpm_i2c *cpm = i2c_get_adapdata(adap); + + tbdf = cpm->tbase + tx; + rbdf = cpm->rbase + rx; + + addr = pmsg->addr << 1; + if (pmsg->flags & I2C_M_RD) + addr |= 1; + + tb = cpm->txbuf[tx]; + rb = cpm->rxbuf[rx]; + + /* Align read buffer */ + rb = (u_char *) (((ulong) rb + 1) & ~1); + + tb[0] = addr; /* Device address byte w/rw flag */ + + out_be16(&tbdf->cbd_datlen, pmsg->len + 1); + out_be16(&tbdf->cbd_sc, 0); + + if (!(pmsg->flags & I2C_M_NOSTART)) + setbits16(&tbdf->cbd_sc, BD_I2C_START); + + if (tx + 1 == num) + setbits16(&tbdf->cbd_sc, BD_SC_LAST | BD_SC_WRAP); + + if (pmsg->flags & I2C_M_RD) { + /* + * To read, we need an empty buffer of the proper length. + * All that is used is the first byte for address, the remainder + * is just used for timing (and doesn't really have to exist). + */ + + dev_dbg(&adap->dev, "cpm_i2c_read(abyte=0x%x)\n", addr); + + out_be16(&rbdf->cbd_datlen, 0); + out_be16(&rbdf->cbd_sc, BD_SC_EMPTY | BD_SC_INTRPT); + + if (rx + 1 == CPM_MAXBD) + setbits16(&rbdf->cbd_sc, BD_SC_WRAP); + + eieio(); + setbits16(&tbdf->cbd_sc, BD_SC_READY); + } else { + dev_dbg(&adap->dev, "cpm_i2c_write(abyte=0x%x)\n", addr); + + memcpy(tb+1, pmsg->buf, pmsg->len); + + eieio(); + setbits16(&tbdf->cbd_sc, BD_SC_READY | BD_SC_INTRPT); + } +} + +static int cpm_i2c_check_message(struct i2c_adapter *adap, + struct i2c_msg *pmsg, int tx, int rx) +{ + cbd_t __iomem *tbdf; + cbd_t __iomem *rbdf; + u_char *tb; + u_char *rb; + struct cpm_i2c *cpm = i2c_get_adapdata(adap); + + tbdf = cpm->tbase + tx; + rbdf = cpm->rbase + rx; + + tb = cpm->txbuf[tx]; + rb = cpm->rxbuf[rx]; + + /* Align read buffer */ + rb = (u_char *) (((uint) rb + 1) & ~1); + + eieio(); + if (pmsg->flags & I2C_M_RD) { + dev_dbg(&adap->dev, "tx sc 0x%04x, rx sc 0x%04x\n", + in_be16(&tbdf->cbd_sc), in_be16(&rbdf->cbd_sc)); + + if (in_be16(&tbdf->cbd_sc) & BD_SC_NAK) { + dev_dbg(&adap->dev, "I2C read; No ack\n"); + return -ENXIO; + } + if (in_be16(&rbdf->cbd_sc) & BD_SC_EMPTY) { + dev_err(&adap->dev, + "I2C read; complete but rbuf empty\n"); + return -EREMOTEIO; + } + if (in_be16(&rbdf->cbd_sc) & BD_SC_OV) { + dev_err(&adap->dev, "I2C read; Overrun\n"); + return -EREMOTEIO; + } + memcpy(pmsg->buf, rb, pmsg->len); + } else { + dev_dbg(&adap->dev, "tx sc %d 0x%04x\n", tx, + in_be16(&tbdf->cbd_sc)); + + if (in_be16(&tbdf->cbd_sc) & BD_SC_NAK) { + dev_dbg(&adap->dev, "I2C write; No ack\n"); + return -ENXIO; + } + if (in_be16(&tbdf->cbd_sc) & BD_SC_UN) { + dev_err(&adap->dev, "I2C write; Underrun\n"); + return -EIO; + } + if (in_be16(&tbdf->cbd_sc) & BD_SC_CL) { + dev_err(&adap->dev, "I2C write; Collision\n"); + return -EIO; + } + } + return 0; +} + +static int cpm_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) +{ + struct cpm_i2c *cpm = i2c_get_adapdata(adap); + struct i2c_reg __iomem *i2c_reg = cpm->i2c_reg; + struct i2c_ram __iomem *i2c_ram = cpm->i2c_ram; + struct i2c_msg *pmsg; + int ret, i; + int tptr; + int rptr; + cbd_t __iomem *tbdf; + cbd_t __iomem *rbdf; + + if (num > CPM_MAXBD) + return -EINVAL; + + /* Check if we have any oversized READ requests */ + for (i = 0; i < num; i++) { + pmsg = &msgs[i]; + if (pmsg->len >= CPM_MAX_READ) + return -EINVAL; + } + + /* Reset to use first buffer */ + out_be16(&i2c_ram->rbptr, in_be16(&i2c_ram->rbase)); + out_be16(&i2c_ram->tbptr, in_be16(&i2c_ram->tbase)); + + tbdf = cpm->tbase; + rbdf = cpm->rbase; + + tptr = 0; + rptr = 0; + + while (tptr < num) { + pmsg = &msgs[tptr]; + dev_dbg(&adap->dev, "R: %d T: %d\n", rptr, tptr); + + cpm_i2c_parse_message(adap, pmsg, num, tptr, rptr); + if (pmsg->flags & I2C_M_RD) + rptr++; + tptr++; + } + /* Start transfer now */ + /* Enable RX/TX/Error interupts */ + out_8(&i2c_reg->i2cmr, I2CER_TXE | I2CER_TXB | I2CER_RXB); + out_8(&i2c_reg->i2cer, 0xff); /* Clear interrupt status */ + /* Chip bug, set enable here */ + setbits8(&i2c_reg->i2mod, I2MOD_EN); /* Enable */ + /* Begin transmission */ + setbits8(&i2c_reg->i2com, I2COM_START); + + tptr = 0; + rptr = 0; + + while (tptr < num) { + /* Check for outstanding messages */ + dev_dbg(&adap->dev, "test ready.\n"); + pmsg = &msgs[tptr]; + if (pmsg->flags & I2C_M_RD) + ret = wait_event_interruptible_timeout(cpm->i2c_wait, + (in_be16(&tbdf[tptr].cbd_sc) & BD_SC_NAK) || + !(in_be16(&rbdf[rptr].cbd_sc) & BD_SC_EMPTY), + 1 * HZ); + else + ret = wait_event_interruptible_timeout(cpm->i2c_wait, + !(in_be16(&tbdf[tptr].cbd_sc) & BD_SC_READY), + 1 * HZ); + if (ret == 0) { + ret = -EREMOTEIO; + dev_err(&adap->dev, "I2C transfer: timeout\n"); + goto out_err; + } + if (ret > 0) { + dev_dbg(&adap->dev, "ready.\n"); + ret = cpm_i2c_check_message(adap, pmsg, tptr, rptr); + tptr++; + if (pmsg->flags & I2C_M_RD) + rptr++; + if (ret) + goto out_err; + } + } +#ifdef I2C_CHIP_ERRATA + /* + * Chip errata, clear enable. This is not needed on rev D4 CPUs. + * Disabling I2C too early may cause too short stop condition + */ + udelay(4); + clrbits8(&i2c_reg->i2mod, I2MOD_EN); +#endif + return (num); + +out_err: + cpm_i2c_force_close(adap); +#ifdef I2C_CHIP_ERRATA + /* + * Chip errata, clear enable. This is not needed on rev D4 CPUs. + */ + clrbits8(&i2c_reg->i2mod, I2MOD_EN); +#endif + return ret; +} + +static u32 cpm_i2c_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK); +} + +/* -----exported algorithm data: ------------------------------------- */ + +static const struct i2c_algorithm cpm_i2c_algo = { + .master_xfer = cpm_i2c_xfer, + .functionality = cpm_i2c_func, +}; + +static const struct i2c_adapter cpm_ops = { + .owner = THIS_MODULE, + .name = "i2c-cpm", + .algo = &cpm_i2c_algo, +}; + +static int __devinit cpm_i2c_setup(struct cpm_i2c *cpm) +{ + struct of_device *ofdev = cpm->ofdev; + const u32 *data; + int len, ret, i; + void __iomem *i2c_base; + cbd_t __iomem *tbdf; + cbd_t __iomem *rbdf; + unsigned char brg; + + dev_dbg(&cpm->ofdev->dev, "cpm_i2c_setup()\n"); + + init_waitqueue_head(&cpm->i2c_wait); + + cpm->irq = of_irq_to_resource(ofdev->node, 0, NULL); + if (cpm->irq == NO_IRQ) + return -EINVAL; + + /* Install interrupt handler. */ + ret = request_irq(cpm->irq, cpm_i2c_interrupt, 0, "cpm_i2c", + &cpm->adap); + if (ret) + return ret; + + /* I2C parameter RAM */ + i2c_base = of_iomap(ofdev->node, 1); + if (i2c_base == NULL) { + ret = -EINVAL; + goto out_irq; + } + + if (of_device_is_compatible(ofdev->node, "fsl,cpm1-i2c")) { + + /* Check for and use a microcode relocation patch. */ + cpm->i2c_ram = i2c_base; + cpm->i2c_addr = in_be16(&cpm->i2c_ram->rpbase); + + /* + * Maybe should use cpm_muram_alloc instead of hardcoding + * this in micropatch.c + */ + if (cpm->i2c_addr) { + cpm->i2c_ram = cpm_muram_addr(cpm->i2c_addr); + iounmap(i2c_base); + } + + cpm->version = 1; + + } else if (of_device_is_compatible(ofdev->node, "fsl,cpm2-i2c")) { + cpm->i2c_addr = cpm_muram_alloc(sizeof(struct i2c_ram), 64); + cpm->i2c_ram = cpm_muram_addr(cpm->i2c_addr); + out_be16(i2c_base, cpm->i2c_addr); + iounmap(i2c_base); + + cpm->version = 2; + + } else { + iounmap(i2c_base); + ret = -EINVAL; + goto out_irq; + } + + /* I2C control/status registers */ + cpm->i2c_reg = of_iomap(ofdev->node, 0); + if (cpm->i2c_reg == NULL) { + ret = -EINVAL; + goto out_ram; + } + + data = of_get_property(ofdev->node, "fsl,cpm-command", &len); + if (!data || len != 4) { + ret = -EINVAL; + goto out_reg; + } + cpm->cp_command = *data; + + data = of_get_property(ofdev->node, "linux,i2c-class", &len); + if (data && len == 4) + cpm->adap.class = *data; + + data = of_get_property(ofdev->node, "clock-frequency", &len); + if (data && len == 4) + cpm->freq = *data; + else + cpm->freq = 60000; /* use 60kHz i2c clock by default */ + + /* + * Allocate space for CPM_MAXBD transmit and receive buffer + * descriptors in the DP ram. + */ + cpm->dp_addr = cpm_muram_alloc(sizeof(cbd_t) * 2 * CPM_MAXBD, 8); + if (!cpm->dp_addr) { + ret = -ENOMEM; + goto out_reg; + } + + cpm->tbase = cpm_muram_addr(cpm->dp_addr); + cpm->rbase = cpm_muram_addr(cpm->dp_addr + sizeof(cbd_t) * CPM_MAXBD); + + /* Allocate TX and RX buffers */ + + tbdf = cpm->tbase; + rbdf = cpm->rbase; + + for (i = 0; i < CPM_MAXBD; i++) { + cpm->rxbuf[i] = dma_alloc_coherent( + NULL, CPM_MAX_READ + 1, &cpm->rxdma[i], GFP_KERNEL); + if (!cpm->rxbuf[i]) { + ret = -ENOMEM; + goto out_muram; + } + out_be32(&rbdf[i].cbd_bufaddr, ((cpm->rxdma[i] + 1) & ~1)); + + cpm->txbuf[i] = (unsigned char *)dma_alloc_coherent( + NULL, CPM_MAX_READ + 1, &cpm->txdma[i], GFP_KERNEL); + if (!cpm->txbuf[i]) { + ret = -ENOMEM; + goto out_muram; + } + out_be32(&tbdf[i].cbd_bufaddr, cpm->txdma[i]); + } + + /* Initialize Tx/Rx parameters. */ + + cpm_reset_i2c_params(cpm); + + dev_dbg(&cpm->ofdev->dev, "i2c_ram 0x%p, i2c_addr 0x%04x, freq %d\n", + cpm->i2c_ram, cpm->i2c_addr, cpm->freq); + dev_dbg(&cpm->ofdev->dev, "tbase 0x%04x, rbase 0x%04x\n", + (u8 __iomem *)cpm->tbase - DPRAM_BASE, + (u8 __iomem *)cpm->rbase - DPRAM_BASE); + + cpm_command(cpm->cp_command, CPM_CR_INIT_TRX); + + /* + * Select an invalid address. Just make sure we don't use loopback mode + */ + out_8(&cpm->i2c_reg->i2add, 0x7f << 1); + + /* + * PDIV is set to 00 in i2mod, so brgclk/32 is used as input to the + * i2c baud rate generator. This is divided by 2 x (DIV + 3) to get + * the actual i2c bus frequency. + */ + brg = get_brgfreq() / (32 * 2 * cpm->freq) - 3; + out_8(&cpm->i2c_reg->i2brg, brg); + + out_8(&cpm->i2c_reg->i2mod, 0x00); + out_8(&cpm->i2c_reg->i2com, I2COM_MASTER); /* Master mode */ + + /* Disable interrupts. */ + out_8(&cpm->i2c_reg->i2cmr, 0); + out_8(&cpm->i2c_reg->i2cer, 0xff); + + return 0; + +out_muram: + for (i = 0; i < CPM_MAXBD; i++) { + if (cpm->rxbuf[i]) + dma_free_coherent(NULL, CPM_MAX_READ + 1, + cpm->rxbuf[i], cpm->rxdma[i]); + if (cpm->txbuf[i]) + dma_free_coherent(NULL, CPM_MAX_READ + 1, + cpm->txbuf[i], cpm->txdma[i]); + } + cpm_muram_free(cpm->dp_addr); +out_reg: + iounmap(cpm->i2c_reg); +out_ram: + if ((cpm->version == 1) && (!cpm->i2c_addr)) + iounmap(cpm->i2c_ram); + if (cpm->version == 2) + cpm_muram_free(cpm->i2c_addr); +out_irq: + free_irq(cpm->irq, &cpm->adap); + return ret; +} + +static void cpm_i2c_shutdown(struct cpm_i2c *cpm) +{ + int i; + + /* Shut down I2C. */ + clrbits8(&cpm->i2c_reg->i2mod, I2MOD_EN); + + /* Disable interrupts */ + out_8(&cpm->i2c_reg->i2cmr, 0); + out_8(&cpm->i2c_reg->i2cer, 0xff); + + free_irq(cpm->irq, &cpm->adap); + + /* Free all memory */ + for (i = 0; i < CPM_MAXBD; i++) { + dma_free_coherent(NULL, CPM_MAX_READ + 1, + cpm->rxbuf[i], cpm->rxdma[i]); + dma_free_coherent(NULL, CPM_MAX_READ + 1, + cpm->txbuf[i], cpm->txdma[i]); + } + + cpm_muram_free(cpm->dp_addr); + iounmap(cpm->i2c_reg); + + if ((cpm->version == 1) && (!cpm->i2c_addr)) + iounmap(cpm->i2c_ram); + if (cpm->version == 2) + cpm_muram_free(cpm->i2c_addr); +} + +static int __devinit cpm_i2c_probe(struct of_device *ofdev, + const struct of_device_id *match) +{ + int result, len; + struct cpm_i2c *cpm; + const u32 *data; + + cpm = kzalloc(sizeof(struct cpm_i2c), GFP_KERNEL); + if (!cpm) + return -ENOMEM; + + cpm->ofdev = ofdev; + + dev_set_drvdata(&ofdev->dev, cpm); + + cpm->adap = cpm_ops; + i2c_set_adapdata(&cpm->adap, cpm); + cpm->adap.dev.parent = &ofdev->dev; + + result = cpm_i2c_setup(cpm); + if (result) { + dev_err(&ofdev->dev, "Unable to init hardware\n"); + goto out_free; + } + + /* register new adapter to i2c module... */ + + data = of_get_property(ofdev->node, "linux,i2c-index", &len); + if (data && len == 4) { + cpm->adap.nr = *data; + result = i2c_add_numbered_adapter(&cpm->adap); + } else + result = i2c_add_adapter(&cpm->adap); + + if (result < 0) { + dev_err(&ofdev->dev, "Unable to register with I2C\n"); + goto out_shut; + } + + dev_dbg(&ofdev->dev, "hw routines for %s registered.\n", + cpm->adap.name); + + /* + * register OF I2C devices + */ + of_register_i2c_devices(&cpm->adap, ofdev->node); + + return 0; +out_shut: + cpm_i2c_shutdown(cpm); +out_free: + dev_set_drvdata(&ofdev->dev, NULL); + kfree(cpm); + + return result; +} + +static int __devexit cpm_i2c_remove(struct of_device *ofdev) +{ + struct cpm_i2c *cpm = dev_get_drvdata(&ofdev->dev); + + i2c_del_adapter(&cpm->adap); + + cpm_i2c_shutdown(cpm); + + dev_set_drvdata(&ofdev->dev, NULL); + kfree(cpm); + + return 0; +} + +static const struct of_device_id cpm_i2c_match[] = { + { + .compatible = "fsl,cpm1-i2c", + }, + { + .compatible = "fsl,cpm2-i2c", + }, + {}, +}; + +MODULE_DEVICE_TABLE(of, cpm_i2c_match); + +static struct of_platform_driver cpm_i2c_driver = { + .match_table = cpm_i2c_match, + .probe = cpm_i2c_probe, + .remove = __devexit_p(cpm_i2c_remove), + .driver = { + .name = "fsl-i2c-cpm", + .owner = THIS_MODULE, + } +}; + +static int __init cpm_i2c_init(void) +{ + return of_register_platform_driver(&cpm_i2c_driver); +} + +static void __exit cpm_i2c_exit(void) +{ + of_unregister_platform_driver(&cpm_i2c_driver); +} + +module_init(cpm_i2c_init); +module_exit(cpm_i2c_exit); + +MODULE_AUTHOR("Jochen Friedrich <jochen@scram.de>"); +MODULE_DESCRIPTION("I2C-Bus adapter routines for CPM boards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/busses/i2c-davinci.c b/drivers/i2c/busses/i2c-davinci.c new file mode 100644 index 0000000..5d77898 --- /dev/null +++ b/drivers/i2c/busses/i2c-davinci.c @@ -0,0 +1,627 @@ +/* + * TI DAVINCI I2C adapter driver. + * + * Copyright (C) 2006 Texas Instruments. + * Copyright (C) 2007 MontaVista Software Inc. + * + * Updated by Vinod & Sudhakar Feb 2005 + * + * ---------------------------------------------------------------------------- + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * ---------------------------------------------------------------------------- + * + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/clk.h> +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/io.h> + +#include <mach/hardware.h> + +#include <mach/i2c.h> + +/* ----- global defines ----------------------------------------------- */ + +#define DAVINCI_I2C_TIMEOUT (1*HZ) +#define I2C_DAVINCI_INTR_ALL (DAVINCI_I2C_IMR_AAS | \ + DAVINCI_I2C_IMR_SCD | \ + DAVINCI_I2C_IMR_ARDY | \ + DAVINCI_I2C_IMR_NACK | \ + DAVINCI_I2C_IMR_AL) + +#define DAVINCI_I2C_OAR_REG 0x00 +#define DAVINCI_I2C_IMR_REG 0x04 +#define DAVINCI_I2C_STR_REG 0x08 +#define DAVINCI_I2C_CLKL_REG 0x0c +#define DAVINCI_I2C_CLKH_REG 0x10 +#define DAVINCI_I2C_CNT_REG 0x14 +#define DAVINCI_I2C_DRR_REG 0x18 +#define DAVINCI_I2C_SAR_REG 0x1c +#define DAVINCI_I2C_DXR_REG 0x20 +#define DAVINCI_I2C_MDR_REG 0x24 +#define DAVINCI_I2C_IVR_REG 0x28 +#define DAVINCI_I2C_EMDR_REG 0x2c +#define DAVINCI_I2C_PSC_REG 0x30 + +#define DAVINCI_I2C_IVR_AAS 0x07 +#define DAVINCI_I2C_IVR_SCD 0x06 +#define DAVINCI_I2C_IVR_XRDY 0x05 +#define DAVINCI_I2C_IVR_RDR 0x04 +#define DAVINCI_I2C_IVR_ARDY 0x03 +#define DAVINCI_I2C_IVR_NACK 0x02 +#define DAVINCI_I2C_IVR_AL 0x01 + +#define DAVINCI_I2C_STR_BB (1 << 12) +#define DAVINCI_I2C_STR_RSFULL (1 << 11) +#define DAVINCI_I2C_STR_SCD (1 << 5) +#define DAVINCI_I2C_STR_ARDY (1 << 2) +#define DAVINCI_I2C_STR_NACK (1 << 1) +#define DAVINCI_I2C_STR_AL (1 << 0) + +#define DAVINCI_I2C_MDR_NACK (1 << 15) +#define DAVINCI_I2C_MDR_STT (1 << 13) +#define DAVINCI_I2C_MDR_STP (1 << 11) +#define DAVINCI_I2C_MDR_MST (1 << 10) +#define DAVINCI_I2C_MDR_TRX (1 << 9) +#define DAVINCI_I2C_MDR_XA (1 << 8) +#define DAVINCI_I2C_MDR_RM (1 << 7) +#define DAVINCI_I2C_MDR_IRS (1 << 5) + +#define DAVINCI_I2C_IMR_AAS (1 << 6) +#define DAVINCI_I2C_IMR_SCD (1 << 5) +#define DAVINCI_I2C_IMR_XRDY (1 << 4) +#define DAVINCI_I2C_IMR_RRDY (1 << 3) +#define DAVINCI_I2C_IMR_ARDY (1 << 2) +#define DAVINCI_I2C_IMR_NACK (1 << 1) +#define DAVINCI_I2C_IMR_AL (1 << 0) + +#define MOD_REG_BIT(val, mask, set) do { \ + if (set) { \ + val |= mask; \ + } else { \ + val &= ~mask; \ + } \ +} while (0) + +struct davinci_i2c_dev { + struct device *dev; + void __iomem *base; + struct completion cmd_complete; + struct clk *clk; + int cmd_err; + u8 *buf; + size_t buf_len; + int irq; + u8 terminate; + struct i2c_adapter adapter; +}; + +/* default platform data to use if not supplied in the platform_device */ +static struct davinci_i2c_platform_data davinci_i2c_platform_data_default = { + .bus_freq = 100, + .bus_delay = 0, +}; + +static inline void davinci_i2c_write_reg(struct davinci_i2c_dev *i2c_dev, + int reg, u16 val) +{ + __raw_writew(val, i2c_dev->base + reg); +} + +static inline u16 davinci_i2c_read_reg(struct davinci_i2c_dev *i2c_dev, int reg) +{ + return __raw_readw(i2c_dev->base + reg); +} + +/* + * This functions configures I2C and brings I2C out of reset. + * This function is called during I2C init function. This function + * also gets called if I2C encounters any errors. + */ +static int i2c_davinci_init(struct davinci_i2c_dev *dev) +{ + struct davinci_i2c_platform_data *pdata = dev->dev->platform_data; + u16 psc; + u32 clk; + u32 d; + u32 clkh; + u32 clkl; + u32 input_clock = clk_get_rate(dev->clk); + u16 w; + + if (!pdata) + pdata = &davinci_i2c_platform_data_default; + + /* put I2C into reset */ + w = davinci_i2c_read_reg(dev, DAVINCI_I2C_MDR_REG); + MOD_REG_BIT(w, DAVINCI_I2C_MDR_IRS, 0); + davinci_i2c_write_reg(dev, DAVINCI_I2C_MDR_REG, w); + + /* NOTE: I2C Clock divider programming info + * As per I2C specs the following formulas provide prescaler + * and low/high divider values + * input clk --> PSC Div -----------> ICCL/H Div --> output clock + * module clk + * + * output clk = module clk / (PSC + 1) [ (ICCL + d) + (ICCH + d) ] + * + * Thus, + * (ICCL + ICCH) = clk = (input clk / ((psc +1) * output clk)) - 2d; + * + * where if PSC == 0, d = 7, + * if PSC == 1, d = 6 + * if PSC > 1 , d = 5 + */ + + /* get minimum of 7 MHz clock, but max of 12 MHz */ + psc = (input_clock / 7000000) - 1; + if ((input_clock / (psc + 1)) > 12000000) + psc++; /* better to run under spec than over */ + d = (psc >= 2) ? 5 : 7 - psc; + + clk = ((input_clock / (psc + 1)) / (pdata->bus_freq * 1000)) - (d << 1); + clkh = clk >> 1; + clkl = clk - clkh; + + davinci_i2c_write_reg(dev, DAVINCI_I2C_PSC_REG, psc); + davinci_i2c_write_reg(dev, DAVINCI_I2C_CLKH_REG, clkh); + davinci_i2c_write_reg(dev, DAVINCI_I2C_CLKL_REG, clkl); + + dev_dbg(dev->dev, "input_clock = %d, CLK = %d\n", input_clock, clk); + dev_dbg(dev->dev, "PSC = %d\n", + davinci_i2c_read_reg(dev, DAVINCI_I2C_PSC_REG)); + dev_dbg(dev->dev, "CLKL = %d\n", + davinci_i2c_read_reg(dev, DAVINCI_I2C_CLKL_REG)); + dev_dbg(dev->dev, "CLKH = %d\n", + davinci_i2c_read_reg(dev, DAVINCI_I2C_CLKH_REG)); + dev_dbg(dev->dev, "bus_freq = %dkHz, bus_delay = %d\n", + pdata->bus_freq, pdata->bus_delay); + + /* Take the I2C module out of reset: */ + w = davinci_i2c_read_reg(dev, DAVINCI_I2C_MDR_REG); + MOD_REG_BIT(w, DAVINCI_I2C_MDR_IRS, 1); + davinci_i2c_write_reg(dev, DAVINCI_I2C_MDR_REG, w); + + /* Enable interrupts */ + davinci_i2c_write_reg(dev, DAVINCI_I2C_IMR_REG, I2C_DAVINCI_INTR_ALL); + + return 0; +} + +/* + * Waiting for bus not busy + */ +static int i2c_davinci_wait_bus_not_busy(struct davinci_i2c_dev *dev, + char allow_sleep) +{ + unsigned long timeout; + + timeout = jiffies + DAVINCI_I2C_TIMEOUT; + while (davinci_i2c_read_reg(dev, DAVINCI_I2C_STR_REG) + & DAVINCI_I2C_STR_BB) { + if (time_after(jiffies, timeout)) { + dev_warn(dev->dev, + "timeout waiting for bus ready\n"); + return -ETIMEDOUT; + } + if (allow_sleep) + schedule_timeout(1); + } + + return 0; +} + +/* + * Low level master read/write transaction. This function is called + * from i2c_davinci_xfer. + */ +static int +i2c_davinci_xfer_msg(struct i2c_adapter *adap, struct i2c_msg *msg, int stop) +{ + struct davinci_i2c_dev *dev = i2c_get_adapdata(adap); + struct davinci_i2c_platform_data *pdata = dev->dev->platform_data; + u32 flag; + u16 w; + int r; + + if (msg->len == 0) + return -EINVAL; + + if (!pdata) + pdata = &davinci_i2c_platform_data_default; + /* Introduce a delay, required for some boards (e.g Davinci EVM) */ + if (pdata->bus_delay) + udelay(pdata->bus_delay); + + /* set the slave address */ + davinci_i2c_write_reg(dev, DAVINCI_I2C_SAR_REG, msg->addr); + + dev->buf = msg->buf; + dev->buf_len = msg->len; + + davinci_i2c_write_reg(dev, DAVINCI_I2C_CNT_REG, dev->buf_len); + + INIT_COMPLETION(dev->cmd_complete); + dev->cmd_err = 0; + + /* Take I2C out of reset, configure it as master and set the + * start bit */ + flag = DAVINCI_I2C_MDR_IRS | DAVINCI_I2C_MDR_MST | DAVINCI_I2C_MDR_STT; + + /* if the slave address is ten bit address, enable XA bit */ + if (msg->flags & I2C_M_TEN) + flag |= DAVINCI_I2C_MDR_XA; + if (!(msg->flags & I2C_M_RD)) + flag |= DAVINCI_I2C_MDR_TRX; + if (stop) + flag |= DAVINCI_I2C_MDR_STP; + + /* Enable receive or transmit interrupts */ + w = davinci_i2c_read_reg(dev, DAVINCI_I2C_IMR_REG); + if (msg->flags & I2C_M_RD) + MOD_REG_BIT(w, DAVINCI_I2C_IMR_RRDY, 1); + else + MOD_REG_BIT(w, DAVINCI_I2C_IMR_XRDY, 1); + davinci_i2c_write_reg(dev, DAVINCI_I2C_IMR_REG, w); + + dev->terminate = 0; + /* write the data into mode register */ + davinci_i2c_write_reg(dev, DAVINCI_I2C_MDR_REG, flag); + + r = wait_for_completion_interruptible_timeout(&dev->cmd_complete, + DAVINCI_I2C_TIMEOUT); + if (r == 0) { + dev_err(dev->dev, "controller timed out\n"); + i2c_davinci_init(dev); + dev->buf_len = 0; + return -ETIMEDOUT; + } + if (dev->buf_len) { + /* This should be 0 if all bytes were transferred + * or dev->cmd_err denotes an error. + * A signal may have aborted the transfer. + */ + if (r >= 0) { + dev_err(dev->dev, "abnormal termination buf_len=%i\n", + dev->buf_len); + r = -EREMOTEIO; + } + dev->terminate = 1; + wmb(); + dev->buf_len = 0; + } + if (r < 0) + return r; + + /* no error */ + if (likely(!dev->cmd_err)) + return msg->len; + + /* We have an error */ + if (dev->cmd_err & DAVINCI_I2C_STR_AL) { + i2c_davinci_init(dev); + return -EIO; + } + + if (dev->cmd_err & DAVINCI_I2C_STR_NACK) { + if (msg->flags & I2C_M_IGNORE_NAK) + return msg->len; + if (stop) { + w = davinci_i2c_read_reg(dev, DAVINCI_I2C_MDR_REG); + MOD_REG_BIT(w, DAVINCI_I2C_MDR_STP, 1); + davinci_i2c_write_reg(dev, DAVINCI_I2C_MDR_REG, w); + } + return -EREMOTEIO; + } + return -EIO; +} + +/* + * Prepare controller for a transaction and call i2c_davinci_xfer_msg + */ +static int +i2c_davinci_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) +{ + struct davinci_i2c_dev *dev = i2c_get_adapdata(adap); + int i; + int ret; + + dev_dbg(dev->dev, "%s: msgs: %d\n", __func__, num); + + ret = i2c_davinci_wait_bus_not_busy(dev, 1); + if (ret < 0) { + dev_warn(dev->dev, "timeout waiting for bus ready\n"); + return ret; + } + + for (i = 0; i < num; i++) { + ret = i2c_davinci_xfer_msg(adap, &msgs[i], (i == (num - 1))); + dev_dbg(dev->dev, "%s [%d/%d] ret: %d\n", __func__, i + 1, num, + ret); + if (ret < 0) + return ret; + } + return num; +} + +static u32 i2c_davinci_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK); +} + +static void terminate_read(struct davinci_i2c_dev *dev) +{ + u16 w = davinci_i2c_read_reg(dev, DAVINCI_I2C_MDR_REG); + w |= DAVINCI_I2C_MDR_NACK; + davinci_i2c_write_reg(dev, DAVINCI_I2C_MDR_REG, w); + + /* Throw away data */ + davinci_i2c_read_reg(dev, DAVINCI_I2C_DRR_REG); + if (!dev->terminate) + dev_err(dev->dev, "RDR IRQ while no data requested\n"); +} +static void terminate_write(struct davinci_i2c_dev *dev) +{ + u16 w = davinci_i2c_read_reg(dev, DAVINCI_I2C_MDR_REG); + w |= DAVINCI_I2C_MDR_RM | DAVINCI_I2C_MDR_STP; + davinci_i2c_write_reg(dev, DAVINCI_I2C_MDR_REG, w); + + if (!dev->terminate) + dev_err(dev->dev, "TDR IRQ while no data to send\n"); +} + +/* + * Interrupt service routine. This gets called whenever an I2C interrupt + * occurs. + */ +static irqreturn_t i2c_davinci_isr(int this_irq, void *dev_id) +{ + struct davinci_i2c_dev *dev = dev_id; + u32 stat; + int count = 0; + u16 w; + + while ((stat = davinci_i2c_read_reg(dev, DAVINCI_I2C_IVR_REG))) { + dev_dbg(dev->dev, "%s: stat=0x%x\n", __func__, stat); + if (count++ == 100) { + dev_warn(dev->dev, "Too much work in one IRQ\n"); + break; + } + + switch (stat) { + case DAVINCI_I2C_IVR_AL: + /* Arbitration lost, must retry */ + dev->cmd_err |= DAVINCI_I2C_STR_AL; + dev->buf_len = 0; + complete(&dev->cmd_complete); + break; + + case DAVINCI_I2C_IVR_NACK: + dev->cmd_err |= DAVINCI_I2C_STR_NACK; + dev->buf_len = 0; + complete(&dev->cmd_complete); + break; + + case DAVINCI_I2C_IVR_ARDY: + davinci_i2c_write_reg(dev, + DAVINCI_I2C_STR_REG, DAVINCI_I2C_STR_ARDY); + complete(&dev->cmd_complete); + break; + + case DAVINCI_I2C_IVR_RDR: + if (dev->buf_len) { + *dev->buf++ = + davinci_i2c_read_reg(dev, + DAVINCI_I2C_DRR_REG); + dev->buf_len--; + if (dev->buf_len) + continue; + + davinci_i2c_write_reg(dev, + DAVINCI_I2C_STR_REG, + DAVINCI_I2C_IMR_RRDY); + } else { + /* signal can terminate transfer */ + terminate_read(dev); + } + break; + + case DAVINCI_I2C_IVR_XRDY: + if (dev->buf_len) { + davinci_i2c_write_reg(dev, DAVINCI_I2C_DXR_REG, + *dev->buf++); + dev->buf_len--; + if (dev->buf_len) + continue; + + w = davinci_i2c_read_reg(dev, + DAVINCI_I2C_IMR_REG); + MOD_REG_BIT(w, DAVINCI_I2C_IMR_XRDY, 0); + davinci_i2c_write_reg(dev, + DAVINCI_I2C_IMR_REG, + w); + } else { + /* signal can terminate transfer */ + terminate_write(dev); + } + break; + + case DAVINCI_I2C_IVR_SCD: + davinci_i2c_write_reg(dev, + DAVINCI_I2C_STR_REG, DAVINCI_I2C_STR_SCD); + complete(&dev->cmd_complete); + break; + + case DAVINCI_I2C_IVR_AAS: + dev_warn(dev->dev, "Address as slave interrupt\n"); + }/* switch */ + }/* while */ + + return count ? IRQ_HANDLED : IRQ_NONE; +} + +static struct i2c_algorithm i2c_davinci_algo = { + .master_xfer = i2c_davinci_xfer, + .functionality = i2c_davinci_func, +}; + +static int davinci_i2c_probe(struct platform_device *pdev) +{ + struct davinci_i2c_dev *dev; + struct i2c_adapter *adap; + struct resource *mem, *irq, *ioarea; + int r; + + /* NOTE: driver uses the static register mapping */ + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&pdev->dev, "no mem resource?\n"); + return -ENODEV; + } + + irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!irq) { + dev_err(&pdev->dev, "no irq resource?\n"); + return -ENODEV; + } + + ioarea = request_mem_region(mem->start, (mem->end - mem->start) + 1, + pdev->name); + if (!ioarea) { + dev_err(&pdev->dev, "I2C region already claimed\n"); + return -EBUSY; + } + + dev = kzalloc(sizeof(struct davinci_i2c_dev), GFP_KERNEL); + if (!dev) { + r = -ENOMEM; + goto err_release_region; + } + + init_completion(&dev->cmd_complete); + dev->dev = get_device(&pdev->dev); + dev->irq = irq->start; + platform_set_drvdata(pdev, dev); + + dev->clk = clk_get(&pdev->dev, "I2CCLK"); + if (IS_ERR(dev->clk)) { + r = -ENODEV; + goto err_free_mem; + } + clk_enable(dev->clk); + + dev->base = (void __iomem *)IO_ADDRESS(mem->start); + i2c_davinci_init(dev); + + r = request_irq(dev->irq, i2c_davinci_isr, 0, pdev->name, dev); + if (r) { + dev_err(&pdev->dev, "failure requesting irq %i\n", dev->irq); + goto err_unuse_clocks; + } + + adap = &dev->adapter; + i2c_set_adapdata(adap, dev); + adap->owner = THIS_MODULE; + adap->class = I2C_CLASS_HWMON; + strlcpy(adap->name, "DaVinci I2C adapter", sizeof(adap->name)); + adap->algo = &i2c_davinci_algo; + adap->dev.parent = &pdev->dev; + + /* FIXME */ + adap->timeout = 1; + + adap->nr = pdev->id; + r = i2c_add_numbered_adapter(adap); + if (r) { + dev_err(&pdev->dev, "failure adding adapter\n"); + goto err_free_irq; + } + + return 0; + +err_free_irq: + free_irq(dev->irq, dev); +err_unuse_clocks: + clk_disable(dev->clk); + clk_put(dev->clk); + dev->clk = NULL; +err_free_mem: + platform_set_drvdata(pdev, NULL); + put_device(&pdev->dev); + kfree(dev); +err_release_region: + release_mem_region(mem->start, (mem->end - mem->start) + 1); + + return r; +} + +static int davinci_i2c_remove(struct platform_device *pdev) +{ + struct davinci_i2c_dev *dev = platform_get_drvdata(pdev); + struct resource *mem; + + platform_set_drvdata(pdev, NULL); + i2c_del_adapter(&dev->adapter); + put_device(&pdev->dev); + + clk_disable(dev->clk); + clk_put(dev->clk); + dev->clk = NULL; + + davinci_i2c_write_reg(dev, DAVINCI_I2C_MDR_REG, 0); + free_irq(IRQ_I2C, dev); + kfree(dev); + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(mem->start, (mem->end - mem->start) + 1); + return 0; +} + +/* work with hotplug and coldplug */ +MODULE_ALIAS("platform:i2c_davinci"); + +static struct platform_driver davinci_i2c_driver = { + .probe = davinci_i2c_probe, + .remove = davinci_i2c_remove, + .driver = { + .name = "i2c_davinci", + .owner = THIS_MODULE, + }, +}; + +/* I2C may be needed to bring up other drivers */ +static int __init davinci_i2c_init_driver(void) +{ + return platform_driver_register(&davinci_i2c_driver); +} +subsys_initcall(davinci_i2c_init_driver); + +static void __exit davinci_i2c_exit_driver(void) +{ + platform_driver_unregister(&davinci_i2c_driver); +} +module_exit(davinci_i2c_exit_driver); + +MODULE_AUTHOR("Texas Instruments India"); +MODULE_DESCRIPTION("TI DaVinci I2C bus adapter"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/busses/i2c-elektor.c b/drivers/i2c/busses/i2c-elektor.c new file mode 100644 index 0000000..0ed3ccb --- /dev/null +++ b/drivers/i2c/busses/i2c-elektor.c @@ -0,0 +1,349 @@ +/* ------------------------------------------------------------------------- */ +/* i2c-elektor.c i2c-hw access for PCF8584 style isa bus adaptes */ +/* ------------------------------------------------------------------------- */ +/* Copyright (C) 1995-97 Simon G. Vogl + 1998-99 Hans Berglund + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +/* ------------------------------------------------------------------------- */ + +/* With some changes from Kyösti Mälkki <kmalkki@cc.hut.fi> and even + Frodo Looijaard <frodol@dds.nl> */ + +/* Partialy rewriten by Oleg I. Vdovikin for mmapped support of + for Alpha Processor Inc. UP-2000(+) boards */ + +#include <linux/kernel.h> +#include <linux/ioport.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/wait.h> + +#include <linux/isa.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-pcf.h> + +#include <asm/io.h> +#include <asm/irq.h> + +#include "../algos/i2c-algo-pcf.h" + +#define DEFAULT_BASE 0x330 + +static int base; +static u8 __iomem *base_iomem; + +static int irq; +static int clock = 0x1c; +static int own = 0x55; +static int mmapped; + +/* vdovikin: removed static struct i2c_pcf_isa gpi; code - + this module in real supports only one device, due to missing arguments + in some functions, called from the algo-pcf module. Sometimes it's + need to be rewriten - but for now just remove this for simpler reading */ + +static wait_queue_head_t pcf_wait; +static int pcf_pending; +static spinlock_t lock; + +static struct i2c_adapter pcf_isa_ops; + +/* ----- local functions ---------------------------------------------- */ + +static void pcf_isa_setbyte(void *data, int ctl, int val) +{ + u8 __iomem *address = ctl ? (base_iomem + 1) : base_iomem; + + /* enable irq if any specified for serial operation */ + if (ctl && irq && (val & I2C_PCF_ESO)) { + val |= I2C_PCF_ENI; + } + + pr_debug("%s: Write %p 0x%02X\n", pcf_isa_ops.name, address, val); + iowrite8(val, address); +#ifdef __alpha__ + /* API UP2000 needs some hardware fudging to make the write stick */ + iowrite8(val, address); +#endif +} + +static int pcf_isa_getbyte(void *data, int ctl) +{ + u8 __iomem *address = ctl ? (base_iomem + 1) : base_iomem; + int val = ioread8(address); + + pr_debug("%s: Read %p 0x%02X\n", pcf_isa_ops.name, address, val); + return (val); +} + +static int pcf_isa_getown(void *data) +{ + return (own); +} + + +static int pcf_isa_getclock(void *data) +{ + return (clock); +} + +static void pcf_isa_waitforpin(void *data) +{ + DEFINE_WAIT(wait); + int timeout = 2; + unsigned long flags; + + if (irq > 0) { + spin_lock_irqsave(&lock, flags); + if (pcf_pending == 0) { + spin_unlock_irqrestore(&lock, flags); + prepare_to_wait(&pcf_wait, &wait, TASK_INTERRUPTIBLE); + if (schedule_timeout(timeout*HZ)) { + spin_lock_irqsave(&lock, flags); + if (pcf_pending == 1) { + pcf_pending = 0; + } + spin_unlock_irqrestore(&lock, flags); + } + finish_wait(&pcf_wait, &wait); + } else { + pcf_pending = 0; + spin_unlock_irqrestore(&lock, flags); + } + } else { + udelay(100); + } +} + + +static irqreturn_t pcf_isa_handler(int this_irq, void *dev_id) { + spin_lock(&lock); + pcf_pending = 1; + spin_unlock(&lock); + wake_up_interruptible(&pcf_wait); + return IRQ_HANDLED; +} + + +static int pcf_isa_init(void) +{ + spin_lock_init(&lock); + if (!mmapped) { + if (!request_region(base, 2, pcf_isa_ops.name)) { + printk(KERN_ERR "%s: requested I/O region (%#x:2) is " + "in use\n", pcf_isa_ops.name, base); + return -ENODEV; + } + base_iomem = ioport_map(base, 2); + if (!base_iomem) { + printk(KERN_ERR "%s: remap of I/O region %#x failed\n", + pcf_isa_ops.name, base); + release_region(base, 2); + return -ENODEV; + } + } else { + if (!request_mem_region(base, 2, pcf_isa_ops.name)) { + printk(KERN_ERR "%s: requested memory region (%#x:2) " + "is in use\n", pcf_isa_ops.name, base); + return -ENODEV; + } + base_iomem = ioremap(base, 2); + if (base_iomem == NULL) { + printk(KERN_ERR "%s: remap of memory region %#x " + "failed\n", pcf_isa_ops.name, base); + release_mem_region(base, 2); + return -ENODEV; + } + } + pr_debug("%s: registers %#x remapped to %p\n", pcf_isa_ops.name, base, + base_iomem); + + if (irq > 0) { + if (request_irq(irq, pcf_isa_handler, 0, pcf_isa_ops.name, + NULL) < 0) { + printk(KERN_ERR "%s: Request irq%d failed\n", + pcf_isa_ops.name, irq); + irq = 0; + } else + enable_irq(irq); + } + return 0; +} + +/* ------------------------------------------------------------------------ + * Encapsulate the above functions in the correct operations structure. + * This is only done when more than one hardware adapter is supported. + */ +static struct i2c_algo_pcf_data pcf_isa_data = { + .setpcf = pcf_isa_setbyte, + .getpcf = pcf_isa_getbyte, + .getown = pcf_isa_getown, + .getclock = pcf_isa_getclock, + .waitforpin = pcf_isa_waitforpin, +}; + +static struct i2c_adapter pcf_isa_ops = { + .owner = THIS_MODULE, + .class = I2C_CLASS_HWMON | I2C_CLASS_SPD, + .id = I2C_HW_P_ELEK, + .algo_data = &pcf_isa_data, + .name = "i2c-elektor", +}; + +static int __devinit elektor_match(struct device *dev, unsigned int id) +{ +#ifdef __alpha__ + /* check to see we have memory mapped PCF8584 connected to the + Cypress cy82c693 PCI-ISA bridge as on UP2000 board */ + if (base == 0) { + struct pci_dev *cy693_dev; + + cy693_dev = pci_get_device(PCI_VENDOR_ID_CONTAQ, + PCI_DEVICE_ID_CONTAQ_82C693, NULL); + if (cy693_dev) { + unsigned char config; + /* yeap, we've found cypress, let's check config */ + if (!pci_read_config_byte(cy693_dev, 0x47, &config)) { + + dev_dbg(dev, "found cy82c693, config " + "register 0x47 = 0x%02x\n", config); + + /* UP2000 board has this register set to 0xe1, + but the most significant bit as seems can be + reset during the proper initialisation + sequence if guys from API decides to do that + (so, we can even enable Tsunami Pchip + window for the upper 1 Gb) */ + + /* so just check for ROMCS at 0xe0000, + ROMCS enabled for writes + and external XD Bus buffer in use. */ + if ((config & 0x7f) == 0x61) { + /* seems to be UP2000 like board */ + base = 0xe0000; + mmapped = 1; + /* UP2000 drives ISA with + 8.25 MHz (PCI/4) clock + (this can be read from cypress) */ + clock = I2C_PCF_CLK | I2C_PCF_TRNS90; + dev_info(dev, "found API UP2000 like " + "board, will probe PCF8584 " + "later\n"); + } + } + pci_dev_put(cy693_dev); + } + } +#endif + + /* sanity checks for mmapped I/O */ + if (mmapped && base < 0xc8000) { + dev_err(dev, "incorrect base address (%#x) specified " + "for mmapped I/O\n", base); + return 0; + } + + if (base == 0) { + base = DEFAULT_BASE; + } + return 1; +} + +static int __devinit elektor_probe(struct device *dev, unsigned int id) +{ + init_waitqueue_head(&pcf_wait); + if (pcf_isa_init()) + return -ENODEV; + pcf_isa_ops.dev.parent = dev; + if (i2c_pcf_add_bus(&pcf_isa_ops) < 0) + goto fail; + + dev_info(dev, "found device at %#x\n", base); + + return 0; + + fail: + if (irq > 0) { + disable_irq(irq); + free_irq(irq, NULL); + } + + if (!mmapped) { + ioport_unmap(base_iomem); + release_region(base, 2); + } else { + iounmap(base_iomem); + release_mem_region(base, 2); + } + return -ENODEV; +} + +static int __devexit elektor_remove(struct device *dev, unsigned int id) +{ + i2c_del_adapter(&pcf_isa_ops); + + if (irq > 0) { + disable_irq(irq); + free_irq(irq, NULL); + } + + if (!mmapped) { + ioport_unmap(base_iomem); + release_region(base, 2); + } else { + iounmap(base_iomem); + release_mem_region(base, 2); + } + + return 0; +} + +static struct isa_driver i2c_elektor_driver = { + .match = elektor_match, + .probe = elektor_probe, + .remove = __devexit_p(elektor_remove), + .driver = { + .owner = THIS_MODULE, + .name = "i2c-elektor", + }, +}; + +static int __init i2c_pcfisa_init(void) +{ + return isa_register_driver(&i2c_elektor_driver, 1); +} + +static void __exit i2c_pcfisa_exit(void) +{ + isa_unregister_driver(&i2c_elektor_driver); +} + +MODULE_AUTHOR("Hans Berglund <hb@spacetec.no>"); +MODULE_DESCRIPTION("I2C-Bus adapter routines for PCF8584 ISA bus adapter"); +MODULE_LICENSE("GPL"); + +module_param(base, int, 0); +module_param(irq, int, 0); +module_param(clock, int, 0); +module_param(own, int, 0); +module_param(mmapped, int, 0); + +module_init(i2c_pcfisa_init); +module_exit(i2c_pcfisa_exit); diff --git a/drivers/i2c/busses/i2c-gpio.c b/drivers/i2c/busses/i2c-gpio.c new file mode 100644 index 0000000..32104ea --- /dev/null +++ b/drivers/i2c/busses/i2c-gpio.c @@ -0,0 +1,224 @@ +/* + * Bitbanging I2C bus driver using the GPIO API + * + * Copyright (C) 2007 Atmel Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#include <linux/i2c-gpio.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include <asm/gpio.h> + +/* Toggle SDA by changing the direction of the pin */ +static void i2c_gpio_setsda_dir(void *data, int state) +{ + struct i2c_gpio_platform_data *pdata = data; + + if (state) + gpio_direction_input(pdata->sda_pin); + else + gpio_direction_output(pdata->sda_pin, 0); +} + +/* + * Toggle SDA by changing the output value of the pin. This is only + * valid for pins configured as open drain (i.e. setting the value + * high effectively turns off the output driver.) + */ +static void i2c_gpio_setsda_val(void *data, int state) +{ + struct i2c_gpio_platform_data *pdata = data; + + gpio_set_value(pdata->sda_pin, state); +} + +/* Toggle SCL by changing the direction of the pin. */ +static void i2c_gpio_setscl_dir(void *data, int state) +{ + struct i2c_gpio_platform_data *pdata = data; + + if (state) + gpio_direction_input(pdata->scl_pin); + else + gpio_direction_output(pdata->scl_pin, 0); +} + +/* + * Toggle SCL by changing the output value of the pin. This is used + * for pins that are configured as open drain and for output-only + * pins. The latter case will break the i2c protocol, but it will + * often work in practice. + */ +static void i2c_gpio_setscl_val(void *data, int state) +{ + struct i2c_gpio_platform_data *pdata = data; + + gpio_set_value(pdata->scl_pin, state); +} + +static int i2c_gpio_getsda(void *data) +{ + struct i2c_gpio_platform_data *pdata = data; + + return gpio_get_value(pdata->sda_pin); +} + +static int i2c_gpio_getscl(void *data) +{ + struct i2c_gpio_platform_data *pdata = data; + + return gpio_get_value(pdata->scl_pin); +} + +static int __devinit i2c_gpio_probe(struct platform_device *pdev) +{ + struct i2c_gpio_platform_data *pdata; + struct i2c_algo_bit_data *bit_data; + struct i2c_adapter *adap; + int ret; + + pdata = pdev->dev.platform_data; + if (!pdata) + return -ENXIO; + + ret = -ENOMEM; + adap = kzalloc(sizeof(struct i2c_adapter), GFP_KERNEL); + if (!adap) + goto err_alloc_adap; + bit_data = kzalloc(sizeof(struct i2c_algo_bit_data), GFP_KERNEL); + if (!bit_data) + goto err_alloc_bit_data; + + ret = gpio_request(pdata->sda_pin, "sda"); + if (ret) + goto err_request_sda; + ret = gpio_request(pdata->scl_pin, "scl"); + if (ret) + goto err_request_scl; + + if (pdata->sda_is_open_drain) { + gpio_direction_output(pdata->sda_pin, 1); + bit_data->setsda = i2c_gpio_setsda_val; + } else { + gpio_direction_input(pdata->sda_pin); + bit_data->setsda = i2c_gpio_setsda_dir; + } + + if (pdata->scl_is_open_drain || pdata->scl_is_output_only) { + gpio_direction_output(pdata->scl_pin, 1); + bit_data->setscl = i2c_gpio_setscl_val; + } else { + gpio_direction_input(pdata->scl_pin); + bit_data->setscl = i2c_gpio_setscl_dir; + } + + if (!pdata->scl_is_output_only) + bit_data->getscl = i2c_gpio_getscl; + bit_data->getsda = i2c_gpio_getsda; + + if (pdata->udelay) + bit_data->udelay = pdata->udelay; + else if (pdata->scl_is_output_only) + bit_data->udelay = 50; /* 10 kHz */ + else + bit_data->udelay = 5; /* 100 kHz */ + + if (pdata->timeout) + bit_data->timeout = pdata->timeout; + else + bit_data->timeout = HZ / 10; /* 100 ms */ + + bit_data->data = pdata; + + adap->owner = THIS_MODULE; + snprintf(adap->name, sizeof(adap->name), "i2c-gpio%d", pdev->id); + adap->algo_data = bit_data; + adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD; + adap->dev.parent = &pdev->dev; + + /* + * If "dev->id" is negative we consider it as zero. + * The reason to do so is to avoid sysfs names that only make + * sense when there are multiple adapters. + */ + adap->nr = (pdev->id != -1) ? pdev->id : 0; + ret = i2c_bit_add_numbered_bus(adap); + if (ret) + goto err_add_bus; + + platform_set_drvdata(pdev, adap); + + dev_info(&pdev->dev, "using pins %u (SDA) and %u (SCL%s)\n", + pdata->sda_pin, pdata->scl_pin, + pdata->scl_is_output_only + ? ", no clock stretching" : ""); + + return 0; + +err_add_bus: + gpio_free(pdata->scl_pin); +err_request_scl: + gpio_free(pdata->sda_pin); +err_request_sda: + kfree(bit_data); +err_alloc_bit_data: + kfree(adap); +err_alloc_adap: + return ret; +} + +static int __devexit i2c_gpio_remove(struct platform_device *pdev) +{ + struct i2c_gpio_platform_data *pdata; + struct i2c_adapter *adap; + + adap = platform_get_drvdata(pdev); + pdata = pdev->dev.platform_data; + + i2c_del_adapter(adap); + gpio_free(pdata->scl_pin); + gpio_free(pdata->sda_pin); + kfree(adap->algo_data); + kfree(adap); + + return 0; +} + +static struct platform_driver i2c_gpio_driver = { + .driver = { + .name = "i2c-gpio", + .owner = THIS_MODULE, + }, + .probe = i2c_gpio_probe, + .remove = __devexit_p(i2c_gpio_remove), +}; + +static int __init i2c_gpio_init(void) +{ + int ret; + + ret = platform_driver_register(&i2c_gpio_driver); + if (ret) + printk(KERN_ERR "i2c-gpio: probe failed: %d\n", ret); + + return ret; +} +module_init(i2c_gpio_init); + +static void __exit i2c_gpio_exit(void) +{ + platform_driver_unregister(&i2c_gpio_driver); +} +module_exit(i2c_gpio_exit); + +MODULE_AUTHOR("Haavard Skinnemoen <hskinnemoen@atmel.com>"); +MODULE_DESCRIPTION("Platform-independent bitbanging I2C driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:i2c-gpio"); diff --git a/drivers/i2c/busses/i2c-highlander.c b/drivers/i2c/busses/i2c-highlander.c new file mode 100644 index 0000000..e5a8dae --- /dev/null +++ b/drivers/i2c/busses/i2c-highlander.c @@ -0,0 +1,498 @@ +/* + * Renesas Solutions Highlander FPGA I2C/SMBus support. + * + * Supported devices: R0P7780LC0011RL, R0P7785LC0011RL + * + * Copyright (C) 2008 Paul Mundt + * Copyright (C) 2008 Renesas Solutions Corp. + * Copyright (C) 2008 Atom Create Engineering Co., Ltd. + * + * This file is subject to the terms and conditions of the GNU General + * Public License version 2. See the file "COPYING" in the main directory + * of this archive for more details. + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <linux/completion.h> +#include <linux/io.h> +#include <linux/delay.h> + +#define SMCR 0x00 +#define SMCR_START (1 << 0) +#define SMCR_IRIC (1 << 1) +#define SMCR_BBSY (1 << 2) +#define SMCR_ACKE (1 << 3) +#define SMCR_RST (1 << 4) +#define SMCR_IEIC (1 << 6) + +#define SMSMADR 0x02 + +#define SMMR 0x04 +#define SMMR_MODE0 (1 << 0) +#define SMMR_MODE1 (1 << 1) +#define SMMR_CAP (1 << 3) +#define SMMR_TMMD (1 << 4) +#define SMMR_SP (1 << 7) + +#define SMSADR 0x06 +#define SMTRDR 0x46 + +struct highlander_i2c_dev { + struct device *dev; + void __iomem *base; + struct i2c_adapter adapter; + struct completion cmd_complete; + unsigned long last_read_time; + int irq; + u8 *buf; + size_t buf_len; +}; + +static int iic_force_poll, iic_force_normal; +static int iic_timeout = 1000, iic_read_delay; + +static inline void highlander_i2c_irq_enable(struct highlander_i2c_dev *dev) +{ + iowrite16(ioread16(dev->base + SMCR) | SMCR_IEIC, dev->base + SMCR); +} + +static inline void highlander_i2c_irq_disable(struct highlander_i2c_dev *dev) +{ + iowrite16(ioread16(dev->base + SMCR) & ~SMCR_IEIC, dev->base + SMCR); +} + +static inline void highlander_i2c_start(struct highlander_i2c_dev *dev) +{ + iowrite16(ioread16(dev->base + SMCR) | SMCR_START, dev->base + SMCR); +} + +static inline void highlander_i2c_done(struct highlander_i2c_dev *dev) +{ + iowrite16(ioread16(dev->base + SMCR) | SMCR_IRIC, dev->base + SMCR); +} + +static void highlander_i2c_setup(struct highlander_i2c_dev *dev) +{ + u16 smmr; + + smmr = ioread16(dev->base + SMMR); + smmr |= SMMR_TMMD; + + if (iic_force_normal) + smmr &= ~SMMR_SP; + else + smmr |= SMMR_SP; + + iowrite16(smmr, dev->base + SMMR); +} + +static void smbus_write_data(u8 *src, u16 *dst, int len) +{ + for (; len > 1; len -= 2) { + *dst++ = be16_to_cpup((__be16 *)src); + src += 2; + } + + if (len) + *dst = *src << 8; +} + +static void smbus_read_data(u16 *src, u8 *dst, int len) +{ + for (; len > 1; len -= 2) { + *(__be16 *)dst = cpu_to_be16p(src++); + dst += 2; + } + + if (len) + *dst = *src >> 8; +} + +static void highlander_i2c_command(struct highlander_i2c_dev *dev, + u8 command, int len) +{ + unsigned int i; + u16 cmd = (command << 8) | command; + + for (i = 0; i < len; i += 2) { + if (len - i == 1) + cmd = command << 8; + iowrite16(cmd, dev->base + SMSADR + i); + dev_dbg(dev->dev, "command data[%x] 0x%04x\n", i/2, cmd); + } +} + +static int highlander_i2c_wait_for_bbsy(struct highlander_i2c_dev *dev) +{ + unsigned long timeout; + + timeout = jiffies + msecs_to_jiffies(iic_timeout); + while (ioread16(dev->base + SMCR) & SMCR_BBSY) { + if (time_after(jiffies, timeout)) { + dev_warn(dev->dev, "timeout waiting for bus ready\n"); + return -ETIMEDOUT; + } + + msleep(1); + } + + return 0; +} + +static int highlander_i2c_reset(struct highlander_i2c_dev *dev) +{ + iowrite16(ioread16(dev->base + SMCR) | SMCR_RST, dev->base + SMCR); + return highlander_i2c_wait_for_bbsy(dev); +} + +static int highlander_i2c_wait_for_ack(struct highlander_i2c_dev *dev) +{ + u16 tmp = ioread16(dev->base + SMCR); + + if ((tmp & (SMCR_IRIC | SMCR_ACKE)) == SMCR_ACKE) { + dev_warn(dev->dev, "ack abnormality\n"); + return highlander_i2c_reset(dev); + } + + return 0; +} + +static irqreturn_t highlander_i2c_irq(int irq, void *dev_id) +{ + struct highlander_i2c_dev *dev = dev_id; + + highlander_i2c_done(dev); + complete(&dev->cmd_complete); + + return IRQ_HANDLED; +} + +static void highlander_i2c_poll(struct highlander_i2c_dev *dev) +{ + unsigned long timeout; + u16 smcr; + + timeout = jiffies + msecs_to_jiffies(iic_timeout); + for (;;) { + smcr = ioread16(dev->base + SMCR); + + /* + * Don't bother checking ACKE here, this and the reset + * are handled in highlander_i2c_wait_xfer_done() when + * waiting for the ACK. + */ + + if (smcr & SMCR_IRIC) + return; + if (time_after(jiffies, timeout)) + break; + + cpu_relax(); + cond_resched(); + } + + dev_err(dev->dev, "polling timed out\n"); +} + +static inline int highlander_i2c_wait_xfer_done(struct highlander_i2c_dev *dev) +{ + if (dev->irq) + wait_for_completion_timeout(&dev->cmd_complete, + msecs_to_jiffies(iic_timeout)); + else + /* busy looping, the IRQ of champions */ + highlander_i2c_poll(dev); + + return highlander_i2c_wait_for_ack(dev); +} + +static int highlander_i2c_read(struct highlander_i2c_dev *dev) +{ + int i, cnt; + u16 data[16]; + + if (highlander_i2c_wait_for_bbsy(dev)) + return -EAGAIN; + + highlander_i2c_start(dev); + + if (highlander_i2c_wait_xfer_done(dev)) { + dev_err(dev->dev, "Arbitration loss\n"); + return -EAGAIN; + } + + /* + * The R0P7780LC0011RL FPGA needs a significant delay between + * data read cycles, otherwise the transciever gets confused and + * garbage is returned when the read is subsequently aborted. + * + * It is not sufficient to wait for BBSY. + * + * While this generally only applies to the older SH7780-based + * Highlanders, the same issue can be observed on SH7785 ones, + * albeit less frequently. SH7780-based Highlanders may need + * this to be as high as 1000 ms. + */ + if (iic_read_delay && time_before(jiffies, dev->last_read_time + + msecs_to_jiffies(iic_read_delay))) + msleep(jiffies_to_msecs((dev->last_read_time + + msecs_to_jiffies(iic_read_delay)) - jiffies)); + + cnt = (dev->buf_len + 1) >> 1; + for (i = 0; i < cnt; i++) { + data[i] = ioread16(dev->base + SMTRDR + (i * sizeof(u16))); + dev_dbg(dev->dev, "read data[%x] 0x%04x\n", i, data[i]); + } + + smbus_read_data(data, dev->buf, dev->buf_len); + + dev->last_read_time = jiffies; + + return 0; +} + +static int highlander_i2c_write(struct highlander_i2c_dev *dev) +{ + int i, cnt; + u16 data[16]; + + smbus_write_data(dev->buf, data, dev->buf_len); + + cnt = (dev->buf_len + 1) >> 1; + for (i = 0; i < cnt; i++) { + iowrite16(data[i], dev->base + SMTRDR + (i * sizeof(u16))); + dev_dbg(dev->dev, "write data[%x] 0x%04x\n", i, data[i]); + } + + if (highlander_i2c_wait_for_bbsy(dev)) + return -EAGAIN; + + highlander_i2c_start(dev); + + return highlander_i2c_wait_xfer_done(dev); +} + +static int highlander_i2c_smbus_xfer(struct i2c_adapter *adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, + union i2c_smbus_data *data) +{ + struct highlander_i2c_dev *dev = i2c_get_adapdata(adap); + int read = read_write & I2C_SMBUS_READ; + u16 tmp; + + init_completion(&dev->cmd_complete); + + dev_dbg(dev->dev, "addr %04x, command %02x, read_write %d, size %d\n", + addr, command, read_write, size); + + /* + * Set up the buffer and transfer size + */ + switch (size) { + case I2C_SMBUS_BYTE_DATA: + dev->buf = &data->byte; + dev->buf_len = 1; + break; + case I2C_SMBUS_I2C_BLOCK_DATA: + dev->buf = &data->block[1]; + dev->buf_len = data->block[0]; + break; + default: + dev_err(dev->dev, "unsupported command %d\n", size); + return -EINVAL; + } + + /* + * Encode the mode setting + */ + tmp = ioread16(dev->base + SMMR); + tmp &= ~(SMMR_MODE0 | SMMR_MODE1); + + switch (dev->buf_len) { + case 1: + /* default */ + break; + case 8: + tmp |= SMMR_MODE0; + break; + case 16: + tmp |= SMMR_MODE1; + break; + case 32: + tmp |= (SMMR_MODE0 | SMMR_MODE1); + break; + default: + dev_err(dev->dev, "unsupported xfer size %d\n", dev->buf_len); + return -EINVAL; + } + + iowrite16(tmp, dev->base + SMMR); + + /* Ensure we're in a sane state */ + highlander_i2c_done(dev); + + /* Set slave address */ + iowrite16((addr << 1) | read, dev->base + SMSMADR); + + highlander_i2c_command(dev, command, dev->buf_len); + + if (read) + return highlander_i2c_read(dev); + else + return highlander_i2c_write(dev); +} + +static u32 highlander_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_I2C_BLOCK; +} + +static const struct i2c_algorithm highlander_i2c_algo = { + .smbus_xfer = highlander_i2c_smbus_xfer, + .functionality = highlander_i2c_func, +}; + +static int __devinit highlander_i2c_probe(struct platform_device *pdev) +{ + struct highlander_i2c_dev *dev; + struct i2c_adapter *adap; + struct resource *res; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (unlikely(!res)) { + dev_err(&pdev->dev, "no mem resource\n"); + return -ENODEV; + } + + dev = kzalloc(sizeof(struct highlander_i2c_dev), GFP_KERNEL); + if (unlikely(!dev)) + return -ENOMEM; + + dev->base = ioremap_nocache(res->start, res->end - res->start + 1); + if (unlikely(!dev->base)) { + ret = -ENXIO; + goto err; + } + + dev->dev = &pdev->dev; + platform_set_drvdata(pdev, dev); + + dev->irq = platform_get_irq(pdev, 0); + if (iic_force_poll) + dev->irq = 0; + + if (dev->irq) { + ret = request_irq(dev->irq, highlander_i2c_irq, IRQF_DISABLED, + pdev->name, dev); + if (unlikely(ret)) + goto err_unmap; + + highlander_i2c_irq_enable(dev); + } else { + dev_notice(&pdev->dev, "no IRQ, using polling mode\n"); + highlander_i2c_irq_disable(dev); + } + + dev->last_read_time = jiffies; /* initial read jiffies */ + + highlander_i2c_setup(dev); + + adap = &dev->adapter; + i2c_set_adapdata(adap, dev); + adap->owner = THIS_MODULE; + adap->class = I2C_CLASS_HWMON; + strlcpy(adap->name, "HL FPGA I2C adapter", sizeof(adap->name)); + adap->algo = &highlander_i2c_algo; + adap->dev.parent = &pdev->dev; + adap->nr = pdev->id; + + /* + * Reset the adapter + */ + ret = highlander_i2c_reset(dev); + if (unlikely(ret)) { + dev_err(&pdev->dev, "controller didn't come up\n"); + goto err_free_irq; + } + + ret = i2c_add_numbered_adapter(adap); + if (unlikely(ret)) { + dev_err(&pdev->dev, "failure adding adapter\n"); + goto err_free_irq; + } + + return 0; + +err_free_irq: + if (dev->irq) + free_irq(dev->irq, dev); +err_unmap: + iounmap(dev->base); +err: + kfree(dev); + + platform_set_drvdata(pdev, NULL); + + return ret; +} + +static int __devexit highlander_i2c_remove(struct platform_device *pdev) +{ + struct highlander_i2c_dev *dev = platform_get_drvdata(pdev); + + i2c_del_adapter(&dev->adapter); + + if (dev->irq) + free_irq(dev->irq, dev); + + iounmap(dev->base); + kfree(dev); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver highlander_i2c_driver = { + .driver = { + .name = "i2c-highlander", + .owner = THIS_MODULE, + }, + + .probe = highlander_i2c_probe, + .remove = __devexit_p(highlander_i2c_remove), +}; + +static int __init highlander_i2c_init(void) +{ + return platform_driver_register(&highlander_i2c_driver); +} + +static void __exit highlander_i2c_exit(void) +{ + platform_driver_unregister(&highlander_i2c_driver); +} + +module_init(highlander_i2c_init); +module_exit(highlander_i2c_exit); + +MODULE_AUTHOR("Paul Mundt"); +MODULE_DESCRIPTION("Renesas Highlander FPGA I2C/SMBus adapter"); +MODULE_LICENSE("GPL v2"); + +module_param(iic_force_poll, bool, 0); +module_param(iic_force_normal, bool, 0); +module_param(iic_timeout, int, 0); +module_param(iic_read_delay, int, 0); + +MODULE_PARM_DESC(iic_force_poll, "Force polling mode"); +MODULE_PARM_DESC(iic_force_normal, + "Force normal mode (100 kHz), default is fast mode (400 kHz)"); +MODULE_PARM_DESC(iic_timeout, "Set timeout value in msecs (default 1000 ms)"); +MODULE_PARM_DESC(iic_read_delay, + "Delay between data read cycles (default 0 ms)"); diff --git a/drivers/i2c/busses/i2c-hydra.c b/drivers/i2c/busses/i2c-hydra.c new file mode 100644 index 0000000..648aa7b --- /dev/null +++ b/drivers/i2c/busses/i2c-hydra.c @@ -0,0 +1,179 @@ +/* + i2c Support for the Apple `Hydra' Mac I/O + + Copyright (c) 1999-2004 Geert Uytterhoeven <geert@linux-m68k.org> + + Based on i2c Support for Via Technologies 82C586B South Bridge + Copyright (c) 1998, 1999 Kyösti Mälkki <kmalkki@cc.hut.fi> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/types.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#include <linux/init.h> +#include <asm/io.h> +#include <asm/hydra.h> + + +#define HYDRA_CPD_PD0 0x00000001 /* CachePD lines */ +#define HYDRA_CPD_PD1 0x00000002 +#define HYDRA_CPD_PD2 0x00000004 +#define HYDRA_CPD_PD3 0x00000008 + +#define HYDRA_SCLK HYDRA_CPD_PD0 +#define HYDRA_SDAT HYDRA_CPD_PD1 +#define HYDRA_SCLK_OE 0x00000010 +#define HYDRA_SDAT_OE 0x00000020 + +static inline void pdregw(void *data, u32 val) +{ + struct Hydra *hydra = (struct Hydra *)data; + writel(val, &hydra->CachePD); +} + +static inline u32 pdregr(void *data) +{ + struct Hydra *hydra = (struct Hydra *)data; + return readl(&hydra->CachePD); +} + +static void hydra_bit_setscl(void *data, int state) +{ + u32 val = pdregr(data); + if (state) + val &= ~HYDRA_SCLK_OE; + else { + val &= ~HYDRA_SCLK; + val |= HYDRA_SCLK_OE; + } + pdregw(data, val); +} + +static void hydra_bit_setsda(void *data, int state) +{ + u32 val = pdregr(data); + if (state) + val &= ~HYDRA_SDAT_OE; + else { + val &= ~HYDRA_SDAT; + val |= HYDRA_SDAT_OE; + } + pdregw(data, val); +} + +static int hydra_bit_getscl(void *data) +{ + return (pdregr(data) & HYDRA_SCLK) != 0; +} + +static int hydra_bit_getsda(void *data) +{ + return (pdregr(data) & HYDRA_SDAT) != 0; +} + +/* ------------------------------------------------------------------------ */ + +static struct i2c_algo_bit_data hydra_bit_data = { + .setsda = hydra_bit_setsda, + .setscl = hydra_bit_setscl, + .getsda = hydra_bit_getsda, + .getscl = hydra_bit_getscl, + .udelay = 5, + .timeout = HZ +}; + +static struct i2c_adapter hydra_adap = { + .owner = THIS_MODULE, + .name = "Hydra i2c", + .id = I2C_HW_B_HYDRA, + .algo_data = &hydra_bit_data, +}; + +static struct pci_device_id hydra_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_APPLE, PCI_DEVICE_ID_APPLE_HYDRA) }, + { 0, } +}; + +MODULE_DEVICE_TABLE (pci, hydra_ids); + +static int __devinit hydra_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + unsigned long base = pci_resource_start(dev, 0); + int res; + + if (!request_mem_region(base+offsetof(struct Hydra, CachePD), 4, + hydra_adap.name)) + return -EBUSY; + + hydra_bit_data.data = pci_ioremap_bar(dev, 0); + if (hydra_bit_data.data == NULL) { + release_mem_region(base+offsetof(struct Hydra, CachePD), 4); + return -ENODEV; + } + + pdregw(hydra_bit_data.data, 0); /* clear SCLK_OE and SDAT_OE */ + hydra_adap.dev.parent = &dev->dev; + res = i2c_bit_add_bus(&hydra_adap); + if (res < 0) { + iounmap(hydra_bit_data.data); + release_mem_region(base+offsetof(struct Hydra, CachePD), 4); + return res; + } + return 0; +} + +static void __devexit hydra_remove(struct pci_dev *dev) +{ + pdregw(hydra_bit_data.data, 0); /* clear SCLK_OE and SDAT_OE */ + i2c_del_adapter(&hydra_adap); + iounmap(hydra_bit_data.data); + release_mem_region(pci_resource_start(dev, 0)+ + offsetof(struct Hydra, CachePD), 4); +} + + +static struct pci_driver hydra_driver = { + .name = "hydra_smbus", + .id_table = hydra_ids, + .probe = hydra_probe, + .remove = __devexit_p(hydra_remove), +}; + +static int __init i2c_hydra_init(void) +{ + return pci_register_driver(&hydra_driver); +} + + +static void __exit i2c_hydra_exit(void) +{ + pci_unregister_driver(&hydra_driver); +} + + + +MODULE_AUTHOR("Geert Uytterhoeven <geert@linux-m68k.org>"); +MODULE_DESCRIPTION("i2c for Apple Hydra Mac I/O"); +MODULE_LICENSE("GPL"); + +module_init(i2c_hydra_init); +module_exit(i2c_hydra_exit); + diff --git a/drivers/i2c/busses/i2c-i801.c b/drivers/i2c/busses/i2c-i801.c new file mode 100644 index 0000000..5123eb6 --- /dev/null +++ b/drivers/i2c/busses/i2c-i801.c @@ -0,0 +1,734 @@ +/* + Copyright (c) 1998 - 2002 Frodo Looijaard <frodol@dds.nl>, + Philip Edelbrock <phil@netroedge.com>, and Mark D. Studebaker + <mdsxyz123@yahoo.com> + Copyright (C) 2007, 2008 Jean Delvare <khali@linux-fr.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* + Supports the following Intel I/O Controller Hubs (ICH): + + I/O Block I2C + region SMBus Block proc. block + Chip name PCI ID size PEC buffer call read + ---------------------------------------------------------------------- + 82801AA (ICH) 0x2413 16 no no no no + 82801AB (ICH0) 0x2423 16 no no no no + 82801BA (ICH2) 0x2443 16 no no no no + 82801CA (ICH3) 0x2483 32 soft no no no + 82801DB (ICH4) 0x24c3 32 hard yes no no + 82801E (ICH5) 0x24d3 32 hard yes yes yes + 6300ESB 0x25a4 32 hard yes yes yes + 82801F (ICH6) 0x266a 32 hard yes yes yes + 6310ESB/6320ESB 0x269b 32 hard yes yes yes + 82801G (ICH7) 0x27da 32 hard yes yes yes + 82801H (ICH8) 0x283e 32 hard yes yes yes + 82801I (ICH9) 0x2930 32 hard yes yes yes + Tolapai 0x5032 32 hard yes yes yes + ICH10 0x3a30 32 hard yes yes yes + ICH10 0x3a60 32 hard yes yes yes + PCH 0x3b30 32 hard yes yes yes + + Features supported by this driver: + Software PEC no + Hardware PEC yes + Block buffer yes + Block process call transaction no + I2C block read transaction yes (doesn't use the block buffer) + + See the file Documentation/i2c/busses/i2c-i801 for details. +*/ + +/* Note: we assume there can only be one I801, with one SMBus interface */ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/kernel.h> +#include <linux/stddef.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/acpi.h> +#include <asm/io.h> + +/* I801 SMBus address offsets */ +#define SMBHSTSTS (0 + i801_smba) +#define SMBHSTCNT (2 + i801_smba) +#define SMBHSTCMD (3 + i801_smba) +#define SMBHSTADD (4 + i801_smba) +#define SMBHSTDAT0 (5 + i801_smba) +#define SMBHSTDAT1 (6 + i801_smba) +#define SMBBLKDAT (7 + i801_smba) +#define SMBPEC (8 + i801_smba) /* ICH3 and later */ +#define SMBAUXSTS (12 + i801_smba) /* ICH4 and later */ +#define SMBAUXCTL (13 + i801_smba) /* ICH4 and later */ + +/* PCI Address Constants */ +#define SMBBAR 4 +#define SMBHSTCFG 0x040 + +/* Host configuration bits for SMBHSTCFG */ +#define SMBHSTCFG_HST_EN 1 +#define SMBHSTCFG_SMB_SMI_EN 2 +#define SMBHSTCFG_I2C_EN 4 + +/* Auxillary control register bits, ICH4+ only */ +#define SMBAUXCTL_CRC 1 +#define SMBAUXCTL_E32B 2 + +/* kill bit for SMBHSTCNT */ +#define SMBHSTCNT_KILL 2 + +/* Other settings */ +#define MAX_TIMEOUT 100 +#define ENABLE_INT9 0 /* set to 0x01 to enable - untested */ + +/* I801 command constants */ +#define I801_QUICK 0x00 +#define I801_BYTE 0x04 +#define I801_BYTE_DATA 0x08 +#define I801_WORD_DATA 0x0C +#define I801_PROC_CALL 0x10 /* unimplemented */ +#define I801_BLOCK_DATA 0x14 +#define I801_I2C_BLOCK_DATA 0x18 /* ICH5 and later */ +#define I801_BLOCK_LAST 0x34 +#define I801_I2C_BLOCK_LAST 0x38 /* ICH5 and later */ +#define I801_START 0x40 +#define I801_PEC_EN 0x80 /* ICH3 and later */ + +/* I801 Hosts Status register bits */ +#define SMBHSTSTS_BYTE_DONE 0x80 +#define SMBHSTSTS_INUSE_STS 0x40 +#define SMBHSTSTS_SMBALERT_STS 0x20 +#define SMBHSTSTS_FAILED 0x10 +#define SMBHSTSTS_BUS_ERR 0x08 +#define SMBHSTSTS_DEV_ERR 0x04 +#define SMBHSTSTS_INTR 0x02 +#define SMBHSTSTS_HOST_BUSY 0x01 + +#define STATUS_FLAGS (SMBHSTSTS_BYTE_DONE | SMBHSTSTS_FAILED | \ + SMBHSTSTS_BUS_ERR | SMBHSTSTS_DEV_ERR | \ + SMBHSTSTS_INTR) + +static unsigned long i801_smba; +static unsigned char i801_original_hstcfg; +static struct pci_driver i801_driver; +static struct pci_dev *I801_dev; + +#define FEATURE_SMBUS_PEC (1 << 0) +#define FEATURE_BLOCK_BUFFER (1 << 1) +#define FEATURE_BLOCK_PROC (1 << 2) +#define FEATURE_I2C_BLOCK_READ (1 << 3) +static unsigned int i801_features; + +/* Make sure the SMBus host is ready to start transmitting. + Return 0 if it is, -EBUSY if it is not. */ +static int i801_check_pre(void) +{ + int status; + + status = inb_p(SMBHSTSTS); + if (status & SMBHSTSTS_HOST_BUSY) { + dev_err(&I801_dev->dev, "SMBus is busy, can't use it!\n"); + return -EBUSY; + } + + status &= STATUS_FLAGS; + if (status) { + dev_dbg(&I801_dev->dev, "Clearing status flags (%02x)\n", + status); + outb_p(status, SMBHSTSTS); + status = inb_p(SMBHSTSTS) & STATUS_FLAGS; + if (status) { + dev_err(&I801_dev->dev, + "Failed clearing status flags (%02x)\n", + status); + return -EBUSY; + } + } + + return 0; +} + +/* Convert the status register to an error code, and clear it. */ +static int i801_check_post(int status, int timeout) +{ + int result = 0; + + /* If the SMBus is still busy, we give up */ + if (timeout) { + dev_err(&I801_dev->dev, "Transaction timeout\n"); + /* try to stop the current command */ + dev_dbg(&I801_dev->dev, "Terminating the current operation\n"); + outb_p(inb_p(SMBHSTCNT) | SMBHSTCNT_KILL, SMBHSTCNT); + msleep(1); + outb_p(inb_p(SMBHSTCNT) & (~SMBHSTCNT_KILL), SMBHSTCNT); + + /* Check if it worked */ + status = inb_p(SMBHSTSTS); + if ((status & SMBHSTSTS_HOST_BUSY) || + !(status & SMBHSTSTS_FAILED)) + dev_err(&I801_dev->dev, + "Failed terminating the transaction\n"); + outb_p(STATUS_FLAGS, SMBHSTSTS); + return -ETIMEDOUT; + } + + if (status & SMBHSTSTS_FAILED) { + result = -EIO; + dev_err(&I801_dev->dev, "Transaction failed\n"); + } + if (status & SMBHSTSTS_DEV_ERR) { + result = -ENXIO; + dev_dbg(&I801_dev->dev, "No response\n"); + } + if (status & SMBHSTSTS_BUS_ERR) { + result = -EAGAIN; + dev_dbg(&I801_dev->dev, "Lost arbitration\n"); + } + + if (result) { + /* Clear error flags */ + outb_p(status & STATUS_FLAGS, SMBHSTSTS); + status = inb_p(SMBHSTSTS) & STATUS_FLAGS; + if (status) { + dev_warn(&I801_dev->dev, "Failed clearing status " + "flags at end of transaction (%02x)\n", + status); + } + } + + return result; +} + +static int i801_transaction(int xact) +{ + int status; + int result; + int timeout = 0; + + result = i801_check_pre(); + if (result < 0) + return result; + + /* the current contents of SMBHSTCNT can be overwritten, since PEC, + * INTREN, SMBSCMD are passed in xact */ + outb_p(xact | I801_START, SMBHSTCNT); + + /* We will always wait for a fraction of a second! */ + do { + msleep(1); + status = inb_p(SMBHSTSTS); + } while ((status & SMBHSTSTS_HOST_BUSY) && (timeout++ < MAX_TIMEOUT)); + + result = i801_check_post(status, timeout >= MAX_TIMEOUT); + if (result < 0) + return result; + + outb_p(SMBHSTSTS_INTR, SMBHSTSTS); + return 0; +} + +/* wait for INTR bit as advised by Intel */ +static void i801_wait_hwpec(void) +{ + int timeout = 0; + int status; + + do { + msleep(1); + status = inb_p(SMBHSTSTS); + } while ((!(status & SMBHSTSTS_INTR)) + && (timeout++ < MAX_TIMEOUT)); + + if (timeout >= MAX_TIMEOUT) { + dev_dbg(&I801_dev->dev, "PEC Timeout!\n"); + } + outb_p(status, SMBHSTSTS); +} + +static int i801_block_transaction_by_block(union i2c_smbus_data *data, + char read_write, int hwpec) +{ + int i, len; + int status; + + inb_p(SMBHSTCNT); /* reset the data buffer index */ + + /* Use 32-byte buffer to process this transaction */ + if (read_write == I2C_SMBUS_WRITE) { + len = data->block[0]; + outb_p(len, SMBHSTDAT0); + for (i = 0; i < len; i++) + outb_p(data->block[i+1], SMBBLKDAT); + } + + status = i801_transaction(I801_BLOCK_DATA | ENABLE_INT9 | + I801_PEC_EN * hwpec); + if (status) + return status; + + if (read_write == I2C_SMBUS_READ) { + len = inb_p(SMBHSTDAT0); + if (len < 1 || len > I2C_SMBUS_BLOCK_MAX) + return -EPROTO; + + data->block[0] = len; + for (i = 0; i < len; i++) + data->block[i + 1] = inb_p(SMBBLKDAT); + } + return 0; +} + +static int i801_block_transaction_byte_by_byte(union i2c_smbus_data *data, + char read_write, int command, + int hwpec) +{ + int i, len; + int smbcmd; + int status; + int result; + int timeout; + + result = i801_check_pre(); + if (result < 0) + return result; + + len = data->block[0]; + + if (read_write == I2C_SMBUS_WRITE) { + outb_p(len, SMBHSTDAT0); + outb_p(data->block[1], SMBBLKDAT); + } + + for (i = 1; i <= len; i++) { + if (i == len && read_write == I2C_SMBUS_READ) { + if (command == I2C_SMBUS_I2C_BLOCK_DATA) + smbcmd = I801_I2C_BLOCK_LAST; + else + smbcmd = I801_BLOCK_LAST; + } else { + if (command == I2C_SMBUS_I2C_BLOCK_DATA + && read_write == I2C_SMBUS_READ) + smbcmd = I801_I2C_BLOCK_DATA; + else + smbcmd = I801_BLOCK_DATA; + } + outb_p(smbcmd | ENABLE_INT9, SMBHSTCNT); + + if (i == 1) + outb_p(inb(SMBHSTCNT) | I801_START, SMBHSTCNT); + + /* We will always wait for a fraction of a second! */ + timeout = 0; + do { + msleep(1); + status = inb_p(SMBHSTSTS); + } + while ((!(status & SMBHSTSTS_BYTE_DONE)) + && (timeout++ < MAX_TIMEOUT)); + + result = i801_check_post(status, timeout >= MAX_TIMEOUT); + if (result < 0) + return result; + + if (i == 1 && read_write == I2C_SMBUS_READ + && command != I2C_SMBUS_I2C_BLOCK_DATA) { + len = inb_p(SMBHSTDAT0); + if (len < 1 || len > I2C_SMBUS_BLOCK_MAX) { + dev_err(&I801_dev->dev, + "Illegal SMBus block read size %d\n", + len); + /* Recover */ + while (inb_p(SMBHSTSTS) & SMBHSTSTS_HOST_BUSY) + outb_p(SMBHSTSTS_BYTE_DONE, SMBHSTSTS); + outb_p(SMBHSTSTS_INTR, SMBHSTSTS); + return -EPROTO; + } + data->block[0] = len; + } + + /* Retrieve/store value in SMBBLKDAT */ + if (read_write == I2C_SMBUS_READ) + data->block[i] = inb_p(SMBBLKDAT); + if (read_write == I2C_SMBUS_WRITE && i+1 <= len) + outb_p(data->block[i+1], SMBBLKDAT); + + /* signals SMBBLKDAT ready */ + outb_p(SMBHSTSTS_BYTE_DONE | SMBHSTSTS_INTR, SMBHSTSTS); + } + + return 0; +} + +static int i801_set_block_buffer_mode(void) +{ + outb_p(inb_p(SMBAUXCTL) | SMBAUXCTL_E32B, SMBAUXCTL); + if ((inb_p(SMBAUXCTL) & SMBAUXCTL_E32B) == 0) + return -EIO; + return 0; +} + +/* Block transaction function */ +static int i801_block_transaction(union i2c_smbus_data *data, char read_write, + int command, int hwpec) +{ + int result = 0; + unsigned char hostc; + + if (command == I2C_SMBUS_I2C_BLOCK_DATA) { + if (read_write == I2C_SMBUS_WRITE) { + /* set I2C_EN bit in configuration register */ + pci_read_config_byte(I801_dev, SMBHSTCFG, &hostc); + pci_write_config_byte(I801_dev, SMBHSTCFG, + hostc | SMBHSTCFG_I2C_EN); + } else if (!(i801_features & FEATURE_I2C_BLOCK_READ)) { + dev_err(&I801_dev->dev, + "I2C block read is unsupported!\n"); + return -EOPNOTSUPP; + } + } + + if (read_write == I2C_SMBUS_WRITE + || command == I2C_SMBUS_I2C_BLOCK_DATA) { + if (data->block[0] < 1) + data->block[0] = 1; + if (data->block[0] > I2C_SMBUS_BLOCK_MAX) + data->block[0] = I2C_SMBUS_BLOCK_MAX; + } else { + data->block[0] = 32; /* max for SMBus block reads */ + } + + if ((i801_features & FEATURE_BLOCK_BUFFER) + && !(command == I2C_SMBUS_I2C_BLOCK_DATA + && read_write == I2C_SMBUS_READ) + && i801_set_block_buffer_mode() == 0) + result = i801_block_transaction_by_block(data, read_write, + hwpec); + else + result = i801_block_transaction_byte_by_byte(data, read_write, + command, hwpec); + + if (result == 0 && hwpec) + i801_wait_hwpec(); + + if (command == I2C_SMBUS_I2C_BLOCK_DATA + && read_write == I2C_SMBUS_WRITE) { + /* restore saved configuration register value */ + pci_write_config_byte(I801_dev, SMBHSTCFG, hostc); + } + return result; +} + +/* Return negative errno on error. */ +static s32 i801_access(struct i2c_adapter * adap, u16 addr, + unsigned short flags, char read_write, u8 command, + int size, union i2c_smbus_data * data) +{ + int hwpec; + int block = 0; + int ret, xact = 0; + + hwpec = (i801_features & FEATURE_SMBUS_PEC) && (flags & I2C_CLIENT_PEC) + && size != I2C_SMBUS_QUICK + && size != I2C_SMBUS_I2C_BLOCK_DATA; + + switch (size) { + case I2C_SMBUS_QUICK: + outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), + SMBHSTADD); + xact = I801_QUICK; + break; + case I2C_SMBUS_BYTE: + outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), + SMBHSTADD); + if (read_write == I2C_SMBUS_WRITE) + outb_p(command, SMBHSTCMD); + xact = I801_BYTE; + break; + case I2C_SMBUS_BYTE_DATA: + outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), + SMBHSTADD); + outb_p(command, SMBHSTCMD); + if (read_write == I2C_SMBUS_WRITE) + outb_p(data->byte, SMBHSTDAT0); + xact = I801_BYTE_DATA; + break; + case I2C_SMBUS_WORD_DATA: + outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), + SMBHSTADD); + outb_p(command, SMBHSTCMD); + if (read_write == I2C_SMBUS_WRITE) { + outb_p(data->word & 0xff, SMBHSTDAT0); + outb_p((data->word & 0xff00) >> 8, SMBHSTDAT1); + } + xact = I801_WORD_DATA; + break; + case I2C_SMBUS_BLOCK_DATA: + outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), + SMBHSTADD); + outb_p(command, SMBHSTCMD); + block = 1; + break; + case I2C_SMBUS_I2C_BLOCK_DATA: + /* NB: page 240 of ICH5 datasheet shows that the R/#W + * bit should be cleared here, even when reading */ + outb_p((addr & 0x7f) << 1, SMBHSTADD); + if (read_write == I2C_SMBUS_READ) { + /* NB: page 240 of ICH5 datasheet also shows + * that DATA1 is the cmd field when reading */ + outb_p(command, SMBHSTDAT1); + } else + outb_p(command, SMBHSTCMD); + block = 1; + break; + default: + dev_err(&I801_dev->dev, "Unsupported transaction %d\n", size); + return -EOPNOTSUPP; + } + + if (hwpec) /* enable/disable hardware PEC */ + outb_p(inb_p(SMBAUXCTL) | SMBAUXCTL_CRC, SMBAUXCTL); + else + outb_p(inb_p(SMBAUXCTL) & (~SMBAUXCTL_CRC), SMBAUXCTL); + + if(block) + ret = i801_block_transaction(data, read_write, size, hwpec); + else + ret = i801_transaction(xact | ENABLE_INT9); + + /* Some BIOSes don't like it when PEC is enabled at reboot or resume + time, so we forcibly disable it after every transaction. Turn off + E32B for the same reason. */ + if (hwpec || block) + outb_p(inb_p(SMBAUXCTL) & ~(SMBAUXCTL_CRC | SMBAUXCTL_E32B), + SMBAUXCTL); + + if(block) + return ret; + if(ret) + return ret; + if ((read_write == I2C_SMBUS_WRITE) || (xact == I801_QUICK)) + return 0; + + switch (xact & 0x7f) { + case I801_BYTE: /* Result put in SMBHSTDAT0 */ + case I801_BYTE_DATA: + data->byte = inb_p(SMBHSTDAT0); + break; + case I801_WORD_DATA: + data->word = inb_p(SMBHSTDAT0) + (inb_p(SMBHSTDAT1) << 8); + break; + } + return 0; +} + + +static u32 i801_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_SMBUS_WRITE_I2C_BLOCK | + ((i801_features & FEATURE_SMBUS_PEC) ? I2C_FUNC_SMBUS_PEC : 0) | + ((i801_features & FEATURE_I2C_BLOCK_READ) ? + I2C_FUNC_SMBUS_READ_I2C_BLOCK : 0); +} + +static const struct i2c_algorithm smbus_algorithm = { + .smbus_xfer = i801_access, + .functionality = i801_func, +}; + +static struct i2c_adapter i801_adapter = { + .owner = THIS_MODULE, + .id = I2C_HW_SMBUS_I801, + .class = I2C_CLASS_HWMON | I2C_CLASS_SPD, + .algo = &smbus_algorithm, +}; + +static struct pci_device_id i801_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801AA_3) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801AB_3) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_2) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_3) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_3) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801EB_3) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ESB_4) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH6_16) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_17) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ESB2_17) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH8_5) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH9_6) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_TOLAPAI_1) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH10_4) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH10_5) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_PCH_SMBUS) }, + { 0, } +}; + +MODULE_DEVICE_TABLE (pci, i801_ids); + +static int __devinit i801_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + unsigned char temp; + int err; + + I801_dev = dev; + i801_features = 0; + switch (dev->device) { + case PCI_DEVICE_ID_INTEL_82801EB_3: + case PCI_DEVICE_ID_INTEL_ESB_4: + case PCI_DEVICE_ID_INTEL_ICH6_16: + case PCI_DEVICE_ID_INTEL_ICH7_17: + case PCI_DEVICE_ID_INTEL_ESB2_17: + case PCI_DEVICE_ID_INTEL_ICH8_5: + case PCI_DEVICE_ID_INTEL_ICH9_6: + case PCI_DEVICE_ID_INTEL_TOLAPAI_1: + case PCI_DEVICE_ID_INTEL_ICH10_4: + case PCI_DEVICE_ID_INTEL_ICH10_5: + case PCI_DEVICE_ID_INTEL_PCH_SMBUS: + i801_features |= FEATURE_I2C_BLOCK_READ; + /* fall through */ + case PCI_DEVICE_ID_INTEL_82801DB_3: + i801_features |= FEATURE_SMBUS_PEC; + i801_features |= FEATURE_BLOCK_BUFFER; + break; + } + + err = pci_enable_device(dev); + if (err) { + dev_err(&dev->dev, "Failed to enable SMBus PCI device (%d)\n", + err); + goto exit; + } + + /* Determine the address of the SMBus area */ + i801_smba = pci_resource_start(dev, SMBBAR); + if (!i801_smba) { + dev_err(&dev->dev, "SMBus base address uninitialized, " + "upgrade BIOS\n"); + err = -ENODEV; + goto exit; + } + + err = acpi_check_resource_conflict(&dev->resource[SMBBAR]); + if (err) + goto exit; + + err = pci_request_region(dev, SMBBAR, i801_driver.name); + if (err) { + dev_err(&dev->dev, "Failed to request SMBus region " + "0x%lx-0x%Lx\n", i801_smba, + (unsigned long long)pci_resource_end(dev, SMBBAR)); + goto exit; + } + + pci_read_config_byte(I801_dev, SMBHSTCFG, &temp); + i801_original_hstcfg = temp; + temp &= ~SMBHSTCFG_I2C_EN; /* SMBus timing */ + if (!(temp & SMBHSTCFG_HST_EN)) { + dev_info(&dev->dev, "Enabling SMBus device\n"); + temp |= SMBHSTCFG_HST_EN; + } + pci_write_config_byte(I801_dev, SMBHSTCFG, temp); + + if (temp & SMBHSTCFG_SMB_SMI_EN) + dev_dbg(&dev->dev, "SMBus using interrupt SMI#\n"); + else + dev_dbg(&dev->dev, "SMBus using PCI Interrupt\n"); + + /* Clear special mode bits */ + if (i801_features & (FEATURE_SMBUS_PEC | FEATURE_BLOCK_BUFFER)) + outb_p(inb_p(SMBAUXCTL) & ~(SMBAUXCTL_CRC | SMBAUXCTL_E32B), + SMBAUXCTL); + + /* set up the sysfs linkage to our parent device */ + i801_adapter.dev.parent = &dev->dev; + + snprintf(i801_adapter.name, sizeof(i801_adapter.name), + "SMBus I801 adapter at %04lx", i801_smba); + err = i2c_add_adapter(&i801_adapter); + if (err) { + dev_err(&dev->dev, "Failed to add SMBus adapter\n"); + goto exit_release; + } + return 0; + +exit_release: + pci_release_region(dev, SMBBAR); +exit: + return err; +} + +static void __devexit i801_remove(struct pci_dev *dev) +{ + i2c_del_adapter(&i801_adapter); + pci_write_config_byte(I801_dev, SMBHSTCFG, i801_original_hstcfg); + pci_release_region(dev, SMBBAR); + /* + * do not call pci_disable_device(dev) since it can cause hard hangs on + * some systems during power-off (eg. Fujitsu-Siemens Lifebook E8010) + */ +} + +#ifdef CONFIG_PM +static int i801_suspend(struct pci_dev *dev, pm_message_t mesg) +{ + pci_save_state(dev); + pci_write_config_byte(dev, SMBHSTCFG, i801_original_hstcfg); + pci_set_power_state(dev, pci_choose_state(dev, mesg)); + return 0; +} + +static int i801_resume(struct pci_dev *dev) +{ + pci_set_power_state(dev, PCI_D0); + pci_restore_state(dev); + return pci_enable_device(dev); +} +#else +#define i801_suspend NULL +#define i801_resume NULL +#endif + +static struct pci_driver i801_driver = { + .name = "i801_smbus", + .id_table = i801_ids, + .probe = i801_probe, + .remove = __devexit_p(i801_remove), + .suspend = i801_suspend, + .resume = i801_resume, +}; + +static int __init i2c_i801_init(void) +{ + return pci_register_driver(&i801_driver); +} + +static void __exit i2c_i801_exit(void) +{ + pci_unregister_driver(&i801_driver); +} + +MODULE_AUTHOR("Mark D. Studebaker <mdsxyz123@yahoo.com>, " + "Jean Delvare <khali@linux-fr.org>"); +MODULE_DESCRIPTION("I801 SMBus driver"); +MODULE_LICENSE("GPL"); + +module_init(i2c_i801_init); +module_exit(i2c_i801_exit); diff --git a/drivers/i2c/busses/i2c-ibm_iic.c b/drivers/i2c/busses/i2c-ibm_iic.c new file mode 100644 index 0000000..651f2f1 --- /dev/null +++ b/drivers/i2c/busses/i2c-ibm_iic.c @@ -0,0 +1,827 @@ +/* + * drivers/i2c/busses/i2c-ibm_iic.c + * + * Support for the IIC peripheral on IBM PPC 4xx + * + * Copyright (c) 2003, 2004 Zultys Technologies. + * Eugene Surovegin <eugene.surovegin@zultys.com> or <ebs@ebshome.net> + * + * Copyright (c) 2008 PIKA Technologies + * Sean MacLennan <smaclennan@pikatech.com> + * + * Based on original work by + * Ian DaSilva <idasilva@mvista.com> + * Armin Kuster <akuster@mvista.com> + * Matt Porter <mporter@mvista.com> + * + * Copyright 2000-2003 MontaVista Software Inc. + * + * Original driver version was highly leveraged from i2c-elektor.c + * + * Copyright 1995-97 Simon G. Vogl + * 1998-99 Hans Berglund + * + * With some changes from Kyösti Mälkki <kmalkki@cc.hut.fi> + * and even Frodo Looijaard <frodol@dds.nl> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <asm/irq.h> +#include <asm/io.h> +#include <linux/i2c.h> +#include <linux/i2c-id.h> +#include <linux/of_platform.h> +#include <linux/of_i2c.h> + +#include "i2c-ibm_iic.h" + +#define DRIVER_VERSION "2.2" + +MODULE_DESCRIPTION("IBM IIC driver v" DRIVER_VERSION); +MODULE_LICENSE("GPL"); + +static int iic_force_poll; +module_param(iic_force_poll, bool, 0); +MODULE_PARM_DESC(iic_force_poll, "Force polling mode"); + +static int iic_force_fast; +module_param(iic_force_fast, bool, 0); +MODULE_PARM_DESC(iic_force_fast, "Force fast mode (400 kHz)"); + +#define DBG_LEVEL 0 + +#ifdef DBG +#undef DBG +#endif + +#ifdef DBG2 +#undef DBG2 +#endif + +#if DBG_LEVEL > 0 +# define DBG(f,x...) printk(KERN_DEBUG "ibm-iic" f, ##x) +#else +# define DBG(f,x...) ((void)0) +#endif +#if DBG_LEVEL > 1 +# define DBG2(f,x...) DBG(f, ##x) +#else +# define DBG2(f,x...) ((void)0) +#endif +#if DBG_LEVEL > 2 +static void dump_iic_regs(const char* header, struct ibm_iic_private* dev) +{ + volatile struct iic_regs __iomem *iic = dev->vaddr; + printk(KERN_DEBUG "ibm-iic%d: %s\n", dev->idx, header); + printk(KERN_DEBUG " cntl = 0x%02x, mdcntl = 0x%02x\n" + KERN_DEBUG " sts = 0x%02x, extsts = 0x%02x\n" + KERN_DEBUG " clkdiv = 0x%02x, xfrcnt = 0x%02x\n" + KERN_DEBUG " xtcntlss = 0x%02x, directcntl = 0x%02x\n", + in_8(&iic->cntl), in_8(&iic->mdcntl), in_8(&iic->sts), + in_8(&iic->extsts), in_8(&iic->clkdiv), in_8(&iic->xfrcnt), + in_8(&iic->xtcntlss), in_8(&iic->directcntl)); +} +# define DUMP_REGS(h,dev) dump_iic_regs((h),(dev)) +#else +# define DUMP_REGS(h,dev) ((void)0) +#endif + +/* Bus timings (in ns) for bit-banging */ +static struct i2c_timings { + unsigned int hd_sta; + unsigned int su_sto; + unsigned int low; + unsigned int high; + unsigned int buf; +} timings [] = { +/* Standard mode (100 KHz) */ +{ + .hd_sta = 4000, + .su_sto = 4000, + .low = 4700, + .high = 4000, + .buf = 4700, +}, +/* Fast mode (400 KHz) */ +{ + .hd_sta = 600, + .su_sto = 600, + .low = 1300, + .high = 600, + .buf = 1300, +}}; + +/* Enable/disable interrupt generation */ +static inline void iic_interrupt_mode(struct ibm_iic_private* dev, int enable) +{ + out_8(&dev->vaddr->intmsk, enable ? INTRMSK_EIMTC : 0); +} + +/* + * Initialize IIC interface. + */ +static void iic_dev_init(struct ibm_iic_private* dev) +{ + volatile struct iic_regs __iomem *iic = dev->vaddr; + + DBG("%d: init\n", dev->idx); + + /* Clear master address */ + out_8(&iic->lmadr, 0); + out_8(&iic->hmadr, 0); + + /* Clear slave address */ + out_8(&iic->lsadr, 0); + out_8(&iic->hsadr, 0); + + /* Clear status & extended status */ + out_8(&iic->sts, STS_SCMP | STS_IRQA); + out_8(&iic->extsts, EXTSTS_IRQP | EXTSTS_IRQD | EXTSTS_LA + | EXTSTS_ICT | EXTSTS_XFRA); + + /* Set clock divider */ + out_8(&iic->clkdiv, dev->clckdiv); + + /* Clear transfer count */ + out_8(&iic->xfrcnt, 0); + + /* Clear extended control and status */ + out_8(&iic->xtcntlss, XTCNTLSS_SRC | XTCNTLSS_SRS | XTCNTLSS_SWC + | XTCNTLSS_SWS); + + /* Clear control register */ + out_8(&iic->cntl, 0); + + /* Enable interrupts if possible */ + iic_interrupt_mode(dev, dev->irq >= 0); + + /* Set mode control */ + out_8(&iic->mdcntl, MDCNTL_FMDB | MDCNTL_EINT | MDCNTL_EUBS + | (dev->fast_mode ? MDCNTL_FSM : 0)); + + DUMP_REGS("iic_init", dev); +} + +/* + * Reset IIC interface + */ +static void iic_dev_reset(struct ibm_iic_private* dev) +{ + volatile struct iic_regs __iomem *iic = dev->vaddr; + int i; + u8 dc; + + DBG("%d: soft reset\n", dev->idx); + DUMP_REGS("reset", dev); + + /* Place chip in the reset state */ + out_8(&iic->xtcntlss, XTCNTLSS_SRST); + + /* Check if bus is free */ + dc = in_8(&iic->directcntl); + if (!DIRCTNL_FREE(dc)){ + DBG("%d: trying to regain bus control\n", dev->idx); + + /* Try to set bus free state */ + out_8(&iic->directcntl, DIRCNTL_SDAC | DIRCNTL_SCC); + + /* Wait until we regain bus control */ + for (i = 0; i < 100; ++i){ + dc = in_8(&iic->directcntl); + if (DIRCTNL_FREE(dc)) + break; + + /* Toggle SCL line */ + dc ^= DIRCNTL_SCC; + out_8(&iic->directcntl, dc); + udelay(10); + dc ^= DIRCNTL_SCC; + out_8(&iic->directcntl, dc); + + /* be nice */ + cond_resched(); + } + } + + /* Remove reset */ + out_8(&iic->xtcntlss, 0); + + /* Reinitialize interface */ + iic_dev_init(dev); +} + +/* + * Do 0-length transaction using bit-banging through IIC_DIRECTCNTL register. + */ + +/* Wait for SCL and/or SDA to be high */ +static int iic_dc_wait(volatile struct iic_regs __iomem *iic, u8 mask) +{ + unsigned long x = jiffies + HZ / 28 + 2; + while ((in_8(&iic->directcntl) & mask) != mask){ + if (unlikely(time_after(jiffies, x))) + return -1; + cond_resched(); + } + return 0; +} + +static int iic_smbus_quick(struct ibm_iic_private* dev, const struct i2c_msg* p) +{ + volatile struct iic_regs __iomem *iic = dev->vaddr; + const struct i2c_timings* t = &timings[dev->fast_mode ? 1 : 0]; + u8 mask, v, sda; + int i, res; + + /* Only 7-bit addresses are supported */ + if (unlikely(p->flags & I2C_M_TEN)){ + DBG("%d: smbus_quick - 10 bit addresses are not supported\n", + dev->idx); + return -EINVAL; + } + + DBG("%d: smbus_quick(0x%02x)\n", dev->idx, p->addr); + + /* Reset IIC interface */ + out_8(&iic->xtcntlss, XTCNTLSS_SRST); + + /* Wait for bus to become free */ + out_8(&iic->directcntl, DIRCNTL_SDAC | DIRCNTL_SCC); + if (unlikely(iic_dc_wait(iic, DIRCNTL_MSDA | DIRCNTL_MSC))) + goto err; + ndelay(t->buf); + + /* START */ + out_8(&iic->directcntl, DIRCNTL_SCC); + sda = 0; + ndelay(t->hd_sta); + + /* Send address */ + v = (u8)((p->addr << 1) | ((p->flags & I2C_M_RD) ? 1 : 0)); + for (i = 0, mask = 0x80; i < 8; ++i, mask >>= 1){ + out_8(&iic->directcntl, sda); + ndelay(t->low / 2); + sda = (v & mask) ? DIRCNTL_SDAC : 0; + out_8(&iic->directcntl, sda); + ndelay(t->low / 2); + + out_8(&iic->directcntl, DIRCNTL_SCC | sda); + if (unlikely(iic_dc_wait(iic, DIRCNTL_MSC))) + goto err; + ndelay(t->high); + } + + /* ACK */ + out_8(&iic->directcntl, sda); + ndelay(t->low / 2); + out_8(&iic->directcntl, DIRCNTL_SDAC); + ndelay(t->low / 2); + out_8(&iic->directcntl, DIRCNTL_SDAC | DIRCNTL_SCC); + if (unlikely(iic_dc_wait(iic, DIRCNTL_MSC))) + goto err; + res = (in_8(&iic->directcntl) & DIRCNTL_MSDA) ? -EREMOTEIO : 1; + ndelay(t->high); + + /* STOP */ + out_8(&iic->directcntl, 0); + ndelay(t->low); + out_8(&iic->directcntl, DIRCNTL_SCC); + if (unlikely(iic_dc_wait(iic, DIRCNTL_MSC))) + goto err; + ndelay(t->su_sto); + out_8(&iic->directcntl, DIRCNTL_SDAC | DIRCNTL_SCC); + + ndelay(t->buf); + + DBG("%d: smbus_quick -> %s\n", dev->idx, res ? "NACK" : "ACK"); +out: + /* Remove reset */ + out_8(&iic->xtcntlss, 0); + + /* Reinitialize interface */ + iic_dev_init(dev); + + return res; +err: + DBG("%d: smbus_quick - bus is stuck\n", dev->idx); + res = -EREMOTEIO; + goto out; +} + +/* + * IIC interrupt handler + */ +static irqreturn_t iic_handler(int irq, void *dev_id) +{ + struct ibm_iic_private* dev = (struct ibm_iic_private*)dev_id; + volatile struct iic_regs __iomem *iic = dev->vaddr; + + DBG2("%d: irq handler, STS = 0x%02x, EXTSTS = 0x%02x\n", + dev->idx, in_8(&iic->sts), in_8(&iic->extsts)); + + /* Acknowledge IRQ and wakeup iic_wait_for_tc */ + out_8(&iic->sts, STS_IRQA | STS_SCMP); + wake_up_interruptible(&dev->wq); + + return IRQ_HANDLED; +} + +/* + * Get master transfer result and clear errors if any. + * Returns the number of actually transferred bytes or error (<0) + */ +static int iic_xfer_result(struct ibm_iic_private* dev) +{ + volatile struct iic_regs __iomem *iic = dev->vaddr; + + if (unlikely(in_8(&iic->sts) & STS_ERR)){ + DBG("%d: xfer error, EXTSTS = 0x%02x\n", dev->idx, + in_8(&iic->extsts)); + + /* Clear errors and possible pending IRQs */ + out_8(&iic->extsts, EXTSTS_IRQP | EXTSTS_IRQD | + EXTSTS_LA | EXTSTS_ICT | EXTSTS_XFRA); + + /* Flush master data buffer */ + out_8(&iic->mdcntl, in_8(&iic->mdcntl) | MDCNTL_FMDB); + + /* Is bus free? + * If error happened during combined xfer + * IIC interface is usually stuck in some strange + * state, the only way out - soft reset. + */ + if ((in_8(&iic->extsts) & EXTSTS_BCS_MASK) != EXTSTS_BCS_FREE){ + DBG("%d: bus is stuck, resetting\n", dev->idx); + iic_dev_reset(dev); + } + return -EREMOTEIO; + } + else + return in_8(&iic->xfrcnt) & XFRCNT_MTC_MASK; +} + +/* + * Try to abort active transfer. + */ +static void iic_abort_xfer(struct ibm_iic_private* dev) +{ + volatile struct iic_regs __iomem *iic = dev->vaddr; + unsigned long x; + + DBG("%d: iic_abort_xfer\n", dev->idx); + + out_8(&iic->cntl, CNTL_HMT); + + /* + * Wait for the abort command to complete. + * It's not worth to be optimized, just poll (timeout >= 1 tick) + */ + x = jiffies + 2; + while ((in_8(&iic->extsts) & EXTSTS_BCS_MASK) != EXTSTS_BCS_FREE){ + if (time_after(jiffies, x)){ + DBG("%d: abort timeout, resetting...\n", dev->idx); + iic_dev_reset(dev); + return; + } + schedule(); + } + + /* Just to clear errors */ + iic_xfer_result(dev); +} + +/* + * Wait for master transfer to complete. + * It puts current process to sleep until we get interrupt or timeout expires. + * Returns the number of transferred bytes or error (<0) + */ +static int iic_wait_for_tc(struct ibm_iic_private* dev){ + + volatile struct iic_regs __iomem *iic = dev->vaddr; + int ret = 0; + + if (dev->irq >= 0){ + /* Interrupt mode */ + ret = wait_event_interruptible_timeout(dev->wq, + !(in_8(&iic->sts) & STS_PT), dev->adap.timeout * HZ); + + if (unlikely(ret < 0)) + DBG("%d: wait interrupted\n", dev->idx); + else if (unlikely(in_8(&iic->sts) & STS_PT)){ + DBG("%d: wait timeout\n", dev->idx); + ret = -ETIMEDOUT; + } + } + else { + /* Polling mode */ + unsigned long x = jiffies + dev->adap.timeout * HZ; + + while (in_8(&iic->sts) & STS_PT){ + if (unlikely(time_after(jiffies, x))){ + DBG("%d: poll timeout\n", dev->idx); + ret = -ETIMEDOUT; + break; + } + + if (unlikely(signal_pending(current))){ + DBG("%d: poll interrupted\n", dev->idx); + ret = -ERESTARTSYS; + break; + } + schedule(); + } + } + + if (unlikely(ret < 0)) + iic_abort_xfer(dev); + else + ret = iic_xfer_result(dev); + + DBG2("%d: iic_wait_for_tc -> %d\n", dev->idx, ret); + + return ret; +} + +/* + * Low level master transfer routine + */ +static int iic_xfer_bytes(struct ibm_iic_private* dev, struct i2c_msg* pm, + int combined_xfer) +{ + volatile struct iic_regs __iomem *iic = dev->vaddr; + char* buf = pm->buf; + int i, j, loops, ret = 0; + int len = pm->len; + + u8 cntl = (in_8(&iic->cntl) & CNTL_AMD) | CNTL_PT; + if (pm->flags & I2C_M_RD) + cntl |= CNTL_RW; + + loops = (len + 3) / 4; + for (i = 0; i < loops; ++i, len -= 4){ + int count = len > 4 ? 4 : len; + u8 cmd = cntl | ((count - 1) << CNTL_TCT_SHIFT); + + if (!(cntl & CNTL_RW)) + for (j = 0; j < count; ++j) + out_8((void __iomem *)&iic->mdbuf, *buf++); + + if (i < loops - 1) + cmd |= CNTL_CHT; + else if (combined_xfer) + cmd |= CNTL_RPST; + + DBG2("%d: xfer_bytes, %d, CNTL = 0x%02x\n", dev->idx, count, cmd); + + /* Start transfer */ + out_8(&iic->cntl, cmd); + + /* Wait for completion */ + ret = iic_wait_for_tc(dev); + + if (unlikely(ret < 0)) + break; + else if (unlikely(ret != count)){ + DBG("%d: xfer_bytes, requested %d, transfered %d\n", + dev->idx, count, ret); + + /* If it's not a last part of xfer, abort it */ + if (combined_xfer || (i < loops - 1)) + iic_abort_xfer(dev); + + ret = -EREMOTEIO; + break; + } + + if (cntl & CNTL_RW) + for (j = 0; j < count; ++j) + *buf++ = in_8((void __iomem *)&iic->mdbuf); + } + + return ret > 0 ? 0 : ret; +} + +/* + * Set target slave address for master transfer + */ +static inline void iic_address(struct ibm_iic_private* dev, struct i2c_msg* msg) +{ + volatile struct iic_regs __iomem *iic = dev->vaddr; + u16 addr = msg->addr; + + DBG2("%d: iic_address, 0x%03x (%d-bit)\n", dev->idx, + addr, msg->flags & I2C_M_TEN ? 10 : 7); + + if (msg->flags & I2C_M_TEN){ + out_8(&iic->cntl, CNTL_AMD); + out_8(&iic->lmadr, addr); + out_8(&iic->hmadr, 0xf0 | ((addr >> 7) & 0x06)); + } + else { + out_8(&iic->cntl, 0); + out_8(&iic->lmadr, addr << 1); + } +} + +static inline int iic_invalid_address(const struct i2c_msg* p) +{ + return (p->addr > 0x3ff) || (!(p->flags & I2C_M_TEN) && (p->addr > 0x7f)); +} + +static inline int iic_address_neq(const struct i2c_msg* p1, + const struct i2c_msg* p2) +{ + return (p1->addr != p2->addr) + || ((p1->flags & I2C_M_TEN) != (p2->flags & I2C_M_TEN)); +} + +/* + * Generic master transfer entrypoint. + * Returns the number of processed messages or error (<0) + */ +static int iic_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) +{ + struct ibm_iic_private* dev = (struct ibm_iic_private*)(i2c_get_adapdata(adap)); + volatile struct iic_regs __iomem *iic = dev->vaddr; + int i, ret = 0; + + DBG2("%d: iic_xfer, %d msg(s)\n", dev->idx, num); + + if (!num) + return 0; + + /* Check the sanity of the passed messages. + * Uhh, generic i2c layer is more suitable place for such code... + */ + if (unlikely(iic_invalid_address(&msgs[0]))){ + DBG("%d: invalid address 0x%03x (%d-bit)\n", dev->idx, + msgs[0].addr, msgs[0].flags & I2C_M_TEN ? 10 : 7); + return -EINVAL; + } + for (i = 0; i < num; ++i){ + if (unlikely(msgs[i].len <= 0)){ + if (num == 1 && !msgs[0].len){ + /* Special case for I2C_SMBUS_QUICK emulation. + * IBM IIC doesn't support 0-length transactions + * so we have to emulate them using bit-banging. + */ + return iic_smbus_quick(dev, &msgs[0]); + } + DBG("%d: invalid len %d in msg[%d]\n", dev->idx, + msgs[i].len, i); + return -EINVAL; + } + if (unlikely(iic_address_neq(&msgs[0], &msgs[i]))){ + DBG("%d: invalid addr in msg[%d]\n", dev->idx, i); + return -EINVAL; + } + } + + /* Check bus state */ + if (unlikely((in_8(&iic->extsts) & EXTSTS_BCS_MASK) != EXTSTS_BCS_FREE)){ + DBG("%d: iic_xfer, bus is not free\n", dev->idx); + + /* Usually it means something serious has happend. + * We *cannot* have unfinished previous transfer + * so it doesn't make any sense to try to stop it. + * Probably we were not able to recover from the + * previous error. + * The only *reasonable* thing I can think of here + * is soft reset. --ebs + */ + iic_dev_reset(dev); + + if ((in_8(&iic->extsts) & EXTSTS_BCS_MASK) != EXTSTS_BCS_FREE){ + DBG("%d: iic_xfer, bus is still not free\n", dev->idx); + return -EREMOTEIO; + } + } + else { + /* Flush master data buffer (just in case) */ + out_8(&iic->mdcntl, in_8(&iic->mdcntl) | MDCNTL_FMDB); + } + + /* Load slave address */ + iic_address(dev, &msgs[0]); + + /* Do real transfer */ + for (i = 0; i < num && !ret; ++i) + ret = iic_xfer_bytes(dev, &msgs[i], i < num - 1); + + return ret < 0 ? ret : num; +} + +static u32 iic_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_10BIT_ADDR; +} + +static const struct i2c_algorithm iic_algo = { + .master_xfer = iic_xfer, + .functionality = iic_func +}; + +/* + * Calculates IICx_CLCKDIV value for a specific OPB clock frequency + */ +static inline u8 iic_clckdiv(unsigned int opb) +{ + /* Compatibility kludge, should go away after all cards + * are fixed to fill correct value for opbfreq. + * Previous driver version used hardcoded divider value 4, + * it corresponds to OPB frequency from the range (40, 50] MHz + */ + if (!opb){ + printk(KERN_WARNING "ibm-iic: using compatibility value for OPB freq," + " fix your board specific setup\n"); + opb = 50000000; + } + + /* Convert to MHz */ + opb /= 1000000; + + if (opb < 20 || opb > 150){ + printk(KERN_WARNING "ibm-iic: invalid OPB clock frequency %u MHz\n", + opb); + opb = opb < 20 ? 20 : 150; + } + return (u8)((opb + 9) / 10 - 1); +} + +static int __devinit iic_request_irq(struct of_device *ofdev, + struct ibm_iic_private *dev) +{ + struct device_node *np = ofdev->node; + int irq; + + if (iic_force_poll) + return NO_IRQ; + + irq = irq_of_parse_and_map(np, 0); + if (irq == NO_IRQ) { + dev_err(&ofdev->dev, "irq_of_parse_and_map failed\n"); + return NO_IRQ; + } + + /* Disable interrupts until we finish initialization, assumes + * level-sensitive IRQ setup... + */ + iic_interrupt_mode(dev, 0); + if (request_irq(irq, iic_handler, 0, "IBM IIC", dev)) { + dev_err(&ofdev->dev, "request_irq %d failed\n", irq); + /* Fallback to the polling mode */ + return NO_IRQ; + } + + return irq; +} + +/* + * Register single IIC interface + */ +static int __devinit iic_probe(struct of_device *ofdev, + const struct of_device_id *match) +{ + struct device_node *np = ofdev->node; + struct ibm_iic_private *dev; + struct i2c_adapter *adap; + const u32 *freq; + int ret; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + dev_err(&ofdev->dev, "failed to allocate device data\n"); + return -ENOMEM; + } + + dev_set_drvdata(&ofdev->dev, dev); + + dev->vaddr = of_iomap(np, 0); + if (dev->vaddr == NULL) { + dev_err(&ofdev->dev, "failed to iomap device\n"); + ret = -ENXIO; + goto error_cleanup; + } + + init_waitqueue_head(&dev->wq); + + dev->irq = iic_request_irq(ofdev, dev); + if (dev->irq == NO_IRQ) + dev_warn(&ofdev->dev, "using polling mode\n"); + + /* Board specific settings */ + if (iic_force_fast || of_get_property(np, "fast-mode", NULL)) + dev->fast_mode = 1; + + freq = of_get_property(np, "clock-frequency", NULL); + if (freq == NULL) { + freq = of_get_property(np->parent, "clock-frequency", NULL); + if (freq == NULL) { + dev_err(&ofdev->dev, "Unable to get bus frequency\n"); + ret = -EINVAL; + goto error_cleanup; + } + } + + dev->clckdiv = iic_clckdiv(*freq); + dev_dbg(&ofdev->dev, "clckdiv = %d\n", dev->clckdiv); + + /* Initialize IIC interface */ + iic_dev_init(dev); + + /* Register it with i2c layer */ + adap = &dev->adap; + adap->dev.parent = &ofdev->dev; + strlcpy(adap->name, "IBM IIC", sizeof(adap->name)); + i2c_set_adapdata(adap, dev); + adap->id = I2C_HW_OCP; + adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD; + adap->algo = &iic_algo; + adap->timeout = 1; + + ret = i2c_add_adapter(adap); + if (ret < 0) { + dev_err(&ofdev->dev, "failed to register i2c adapter\n"); + goto error_cleanup; + } + + /* Now register all the child nodes */ + of_register_i2c_devices(adap, np); + + dev_info(&ofdev->dev, "using %s mode\n", + dev->fast_mode ? "fast (400 kHz)" : "standard (100 kHz)"); + + return 0; + +error_cleanup: + if (dev->irq != NO_IRQ) { + iic_interrupt_mode(dev, 0); + free_irq(dev->irq, dev); + } + + if (dev->vaddr) + iounmap(dev->vaddr); + + dev_set_drvdata(&ofdev->dev, NULL); + kfree(dev); + return ret; +} + +/* + * Cleanup initialized IIC interface + */ +static int __devexit iic_remove(struct of_device *ofdev) +{ + struct ibm_iic_private *dev = dev_get_drvdata(&ofdev->dev); + + dev_set_drvdata(&ofdev->dev, NULL); + + i2c_del_adapter(&dev->adap); + + if (dev->irq != NO_IRQ) { + iic_interrupt_mode(dev, 0); + free_irq(dev->irq, dev); + } + + iounmap(dev->vaddr); + kfree(dev); + + return 0; +} + +static const struct of_device_id ibm_iic_match[] = { + { .compatible = "ibm,iic", }, + {} +}; + +static struct of_platform_driver ibm_iic_driver = { + .name = "ibm-iic", + .match_table = ibm_iic_match, + .probe = iic_probe, + .remove = __devexit_p(iic_remove), +}; + +static int __init iic_init(void) +{ + return of_register_platform_driver(&ibm_iic_driver); +} + +static void __exit iic_exit(void) +{ + of_unregister_platform_driver(&ibm_iic_driver); +} + +module_init(iic_init); +module_exit(iic_exit); diff --git a/drivers/i2c/busses/i2c-ibm_iic.h b/drivers/i2c/busses/i2c-ibm_iic.h new file mode 100644 index 0000000..fdaa482 --- /dev/null +++ b/drivers/i2c/busses/i2c-ibm_iic.h @@ -0,0 +1,123 @@ +/* + * drivers/i2c/busses/i2c-ibm_iic.h + * + * Support for the IIC peripheral on IBM PPC 4xx + * + * Copyright (c) 2003 Zultys Technologies. + * Eugene Surovegin <eugene.surovegin@zultys.com> or <ebs@ebshome.net> + * + * Based on original work by + * Ian DaSilva <idasilva@mvista.com> + * Armin Kuster <akuster@mvista.com> + * Matt Porter <mporter@mvista.com> + * + * Copyright 2000-2003 MontaVista Software Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ +#ifndef __I2C_IBM_IIC_H_ +#define __I2C_IBM_IIC_H_ + +#include <linux/i2c.h> + +struct iic_regs { + u16 mdbuf; + u16 sbbuf; + u8 lmadr; + u8 hmadr; + u8 cntl; + u8 mdcntl; + u8 sts; + u8 extsts; + u8 lsadr; + u8 hsadr; + u8 clkdiv; + u8 intmsk; + u8 xfrcnt; + u8 xtcntlss; + u8 directcntl; +}; + +struct ibm_iic_private { + struct i2c_adapter adap; + volatile struct iic_regs __iomem *vaddr; + wait_queue_head_t wq; + int idx; + int irq; + int fast_mode; + u8 clckdiv; +}; + +/* IICx_CNTL register */ +#define CNTL_HMT 0x80 +#define CNTL_AMD 0x40 +#define CNTL_TCT_MASK 0x30 +#define CNTL_TCT_SHIFT 4 +#define CNTL_RPST 0x08 +#define CNTL_CHT 0x04 +#define CNTL_RW 0x02 +#define CNTL_PT 0x01 + +/* IICx_MDCNTL register */ +#define MDCNTL_FSDB 0x80 +#define MDCNTL_FMDB 0x40 +#define MDCNTL_EGC 0x20 +#define MDCNTL_FSM 0x10 +#define MDCNTL_ESM 0x08 +#define MDCNTL_EINT 0x04 +#define MDCNTL_EUBS 0x02 +#define MDCNTL_HSCL 0x01 + +/* IICx_STS register */ +#define STS_SSS 0x80 +#define STS_SLPR 0x40 +#define STS_MDBS 0x20 +#define STS_MDBF 0x10 +#define STS_SCMP 0x08 +#define STS_ERR 0x04 +#define STS_IRQA 0x02 +#define STS_PT 0x01 + +/* IICx_EXTSTS register */ +#define EXTSTS_IRQP 0x80 +#define EXTSTS_BCS_MASK 0x70 +#define EXTSTS_BCS_FREE 0x40 +#define EXTSTS_IRQD 0x08 +#define EXTSTS_LA 0x04 +#define EXTSTS_ICT 0x02 +#define EXTSTS_XFRA 0x01 + +/* IICx_INTRMSK register */ +#define INTRMSK_EIRC 0x80 +#define INTRMSK_EIRS 0x40 +#define INTRMSK_EIWC 0x20 +#define INTRMSK_EIWS 0x10 +#define INTRMSK_EIHE 0x08 +#define INTRMSK_EIIC 0x04 +#define INTRMSK_EITA 0x02 +#define INTRMSK_EIMTC 0x01 + +/* IICx_XFRCNT register */ +#define XFRCNT_MTC_MASK 0x07 + +/* IICx_XTCNTLSS register */ +#define XTCNTLSS_SRC 0x80 +#define XTCNTLSS_SRS 0x40 +#define XTCNTLSS_SWC 0x20 +#define XTCNTLSS_SWS 0x10 +#define XTCNTLSS_SRST 0x01 + +/* IICx_DIRECTCNTL register */ +#define DIRCNTL_SDAC 0x08 +#define DIRCNTL_SCC 0x04 +#define DIRCNTL_MSDA 0x02 +#define DIRCNTL_MSC 0x01 + +/* Check if we really control the I2C bus and bus is free */ +#define DIRCTNL_FREE(v) (((v) & 0x0f) == 0x0f) + +#endif /* __I2C_IBM_IIC_H_ */ diff --git a/drivers/i2c/busses/i2c-iop3xx.c b/drivers/i2c/busses/i2c-iop3xx.c new file mode 100644 index 0000000..fc2714a --- /dev/null +++ b/drivers/i2c/busses/i2c-iop3xx.c @@ -0,0 +1,553 @@ +/* ------------------------------------------------------------------------- */ +/* i2c-iop3xx.c i2c driver algorithms for Intel XScale IOP3xx & IXP46x */ +/* ------------------------------------------------------------------------- */ +/* Copyright (C) 2003 Peter Milne, D-TACQ Solutions Ltd + * <Peter dot Milne at D hyphen TACQ dot com> + * + * With acknowledgements to i2c-algo-ibm_ocp.c by + * Ian DaSilva, MontaVista Software, Inc. idasilva@mvista.com + * + * And i2c-algo-pcf.c, which was created by Simon G. Vogl and Hans Berglund: + * + * Copyright (C) 1995-1997 Simon G. Vogl, 1998-2000 Hans Berglund + * + * And which acknowledged Kyösti Mälkki <kmalkki@cc.hut.fi>, + * Frodo Looijaard <frodol@dds.nl>, Martin Bailey<mbailey@littlefeet-inc.com> + * + * Major cleanup by Deepak Saxena <dsaxena@plexity.net>, 01/2005: + * + * - Use driver model to pass per-chip info instead of hardcoding and #ifdefs + * - Use ioremap/__raw_readl/__raw_writel instead of direct dereference + * - Make it work with IXP46x chips + * - Cleanup function names, coding style, etc + * + * - writing to slave address causes latchup on iop331. + * fix: driver refuses to address self. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + */ + +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> + +#include <asm/io.h> + +#include "i2c-iop3xx.h" + +/* global unit counter */ +static int i2c_id; + +static inline unsigned char +iic_cook_addr(struct i2c_msg *msg) +{ + unsigned char addr; + + addr = (msg->addr << 1); + + if (msg->flags & I2C_M_RD) + addr |= 1; + + /* + * Read or Write? + */ + if (msg->flags & I2C_M_REV_DIR_ADDR) + addr ^= 1; + + return addr; +} + +static void +iop3xx_i2c_reset(struct i2c_algo_iop3xx_data *iop3xx_adap) +{ + /* Follows devman 9.3 */ + __raw_writel(IOP3XX_ICR_UNIT_RESET, iop3xx_adap->ioaddr + CR_OFFSET); + __raw_writel(IOP3XX_ISR_CLEARBITS, iop3xx_adap->ioaddr + SR_OFFSET); + __raw_writel(0, iop3xx_adap->ioaddr + CR_OFFSET); +} + +static void +iop3xx_i2c_enable(struct i2c_algo_iop3xx_data *iop3xx_adap) +{ + u32 cr = IOP3XX_ICR_GCD | IOP3XX_ICR_SCLEN | IOP3XX_ICR_UE; + + /* + * Every time unit enable is asserted, GPOD needs to be cleared + * on IOP3XX to avoid data corruption on the bus. + */ +#if defined(CONFIG_ARCH_IOP32X) || defined(CONFIG_ARCH_IOP33X) + if (iop3xx_adap->id == 0) { + gpio_line_set(IOP3XX_GPIO_LINE(7), GPIO_LOW); + gpio_line_set(IOP3XX_GPIO_LINE(6), GPIO_LOW); + } else { + gpio_line_set(IOP3XX_GPIO_LINE(5), GPIO_LOW); + gpio_line_set(IOP3XX_GPIO_LINE(4), GPIO_LOW); + } +#endif + /* NB SR bits not same position as CR IE bits :-( */ + iop3xx_adap->SR_enabled = + IOP3XX_ISR_ALD | IOP3XX_ISR_BERRD | + IOP3XX_ISR_RXFULL | IOP3XX_ISR_TXEMPTY; + + cr |= IOP3XX_ICR_ALD_IE | IOP3XX_ICR_BERR_IE | + IOP3XX_ICR_RXFULL_IE | IOP3XX_ICR_TXEMPTY_IE; + + __raw_writel(cr, iop3xx_adap->ioaddr + CR_OFFSET); +} + +static void +iop3xx_i2c_transaction_cleanup(struct i2c_algo_iop3xx_data *iop3xx_adap) +{ + unsigned long cr = __raw_readl(iop3xx_adap->ioaddr + CR_OFFSET); + + cr &= ~(IOP3XX_ICR_MSTART | IOP3XX_ICR_TBYTE | + IOP3XX_ICR_MSTOP | IOP3XX_ICR_SCLEN); + + __raw_writel(cr, iop3xx_adap->ioaddr + CR_OFFSET); +} + +/* + * NB: the handler has to clear the source of the interrupt! + * Then it passes the SR flags of interest to BH via adap data + */ +static irqreturn_t +iop3xx_i2c_irq_handler(int this_irq, void *dev_id) +{ + struct i2c_algo_iop3xx_data *iop3xx_adap = dev_id; + u32 sr = __raw_readl(iop3xx_adap->ioaddr + SR_OFFSET); + + if ((sr &= iop3xx_adap->SR_enabled)) { + __raw_writel(sr, iop3xx_adap->ioaddr + SR_OFFSET); + iop3xx_adap->SR_received |= sr; + wake_up_interruptible(&iop3xx_adap->waitq); + } + return IRQ_HANDLED; +} + +/* check all error conditions, clear them , report most important */ +static int +iop3xx_i2c_error(u32 sr) +{ + int rc = 0; + + if ((sr & IOP3XX_ISR_BERRD)) { + if ( !rc ) rc = -I2C_ERR_BERR; + } + if ((sr & IOP3XX_ISR_ALD)) { + if ( !rc ) rc = -I2C_ERR_ALD; + } + return rc; +} + +static inline u32 +iop3xx_i2c_get_srstat(struct i2c_algo_iop3xx_data *iop3xx_adap) +{ + unsigned long flags; + u32 sr; + + spin_lock_irqsave(&iop3xx_adap->lock, flags); + sr = iop3xx_adap->SR_received; + iop3xx_adap->SR_received = 0; + spin_unlock_irqrestore(&iop3xx_adap->lock, flags); + + return sr; +} + +/* + * sleep until interrupted, then recover and analyse the SR + * saved by handler + */ +typedef int (* compare_func)(unsigned test, unsigned mask); +/* returns 1 on correct comparison */ + +static int +iop3xx_i2c_wait_event(struct i2c_algo_iop3xx_data *iop3xx_adap, + unsigned flags, unsigned* status, + compare_func compare) +{ + unsigned sr = 0; + int interrupted; + int done; + int rc = 0; + + do { + interrupted = wait_event_interruptible_timeout ( + iop3xx_adap->waitq, + (done = compare( sr = iop3xx_i2c_get_srstat(iop3xx_adap) ,flags )), + 1 * HZ; + ); + if ((rc = iop3xx_i2c_error(sr)) < 0) { + *status = sr; + return rc; + } else if (!interrupted) { + *status = sr; + return -ETIMEDOUT; + } + } while(!done); + + *status = sr; + + return 0; +} + +/* + * Concrete compare_funcs + */ +static int +all_bits_clear(unsigned test, unsigned mask) +{ + return (test & mask) == 0; +} + +static int +any_bits_set(unsigned test, unsigned mask) +{ + return (test & mask) != 0; +} + +static int +iop3xx_i2c_wait_tx_done(struct i2c_algo_iop3xx_data *iop3xx_adap, int *status) +{ + return iop3xx_i2c_wait_event( + iop3xx_adap, + IOP3XX_ISR_TXEMPTY | IOP3XX_ISR_ALD | IOP3XX_ISR_BERRD, + status, any_bits_set); +} + +static int +iop3xx_i2c_wait_rx_done(struct i2c_algo_iop3xx_data *iop3xx_adap, int *status) +{ + return iop3xx_i2c_wait_event( + iop3xx_adap, + IOP3XX_ISR_RXFULL | IOP3XX_ISR_ALD | IOP3XX_ISR_BERRD, + status, any_bits_set); +} + +static int +iop3xx_i2c_wait_idle(struct i2c_algo_iop3xx_data *iop3xx_adap, int *status) +{ + return iop3xx_i2c_wait_event( + iop3xx_adap, IOP3XX_ISR_UNITBUSY, status, all_bits_clear); +} + +static int +iop3xx_i2c_send_target_addr(struct i2c_algo_iop3xx_data *iop3xx_adap, + struct i2c_msg* msg) +{ + unsigned long cr = __raw_readl(iop3xx_adap->ioaddr + CR_OFFSET); + int status; + int rc; + + /* avoid writing to my slave address (hangs on 80331), + * forbidden in Intel developer manual + */ + if (msg->addr == MYSAR) { + return -EBUSY; + } + + __raw_writel(iic_cook_addr(msg), iop3xx_adap->ioaddr + DBR_OFFSET); + + cr &= ~(IOP3XX_ICR_MSTOP | IOP3XX_ICR_NACK); + cr |= IOP3XX_ICR_MSTART | IOP3XX_ICR_TBYTE; + + __raw_writel(cr, iop3xx_adap->ioaddr + CR_OFFSET); + rc = iop3xx_i2c_wait_tx_done(iop3xx_adap, &status); + + return rc; +} + +static int +iop3xx_i2c_write_byte(struct i2c_algo_iop3xx_data *iop3xx_adap, char byte, + int stop) +{ + unsigned long cr = __raw_readl(iop3xx_adap->ioaddr + CR_OFFSET); + int status; + int rc = 0; + + __raw_writel(byte, iop3xx_adap->ioaddr + DBR_OFFSET); + cr &= ~IOP3XX_ICR_MSTART; + if (stop) { + cr |= IOP3XX_ICR_MSTOP; + } else { + cr &= ~IOP3XX_ICR_MSTOP; + } + cr |= IOP3XX_ICR_TBYTE; + __raw_writel(cr, iop3xx_adap->ioaddr + CR_OFFSET); + rc = iop3xx_i2c_wait_tx_done(iop3xx_adap, &status); + + return rc; +} + +static int +iop3xx_i2c_read_byte(struct i2c_algo_iop3xx_data *iop3xx_adap, char* byte, + int stop) +{ + unsigned long cr = __raw_readl(iop3xx_adap->ioaddr + CR_OFFSET); + int status; + int rc = 0; + + cr &= ~IOP3XX_ICR_MSTART; + + if (stop) { + cr |= IOP3XX_ICR_MSTOP | IOP3XX_ICR_NACK; + } else { + cr &= ~(IOP3XX_ICR_MSTOP | IOP3XX_ICR_NACK); + } + cr |= IOP3XX_ICR_TBYTE; + __raw_writel(cr, iop3xx_adap->ioaddr + CR_OFFSET); + + rc = iop3xx_i2c_wait_rx_done(iop3xx_adap, &status); + + *byte = __raw_readl(iop3xx_adap->ioaddr + DBR_OFFSET); + + return rc; +} + +static int +iop3xx_i2c_writebytes(struct i2c_adapter *i2c_adap, const char *buf, int count) +{ + struct i2c_algo_iop3xx_data *iop3xx_adap = i2c_adap->algo_data; + int ii; + int rc = 0; + + for (ii = 0; rc == 0 && ii != count; ++ii) + rc = iop3xx_i2c_write_byte(iop3xx_adap, buf[ii], ii==count-1); + return rc; +} + +static int +iop3xx_i2c_readbytes(struct i2c_adapter *i2c_adap, char *buf, int count) +{ + struct i2c_algo_iop3xx_data *iop3xx_adap = i2c_adap->algo_data; + int ii; + int rc = 0; + + for (ii = 0; rc == 0 && ii != count; ++ii) + rc = iop3xx_i2c_read_byte(iop3xx_adap, &buf[ii], ii==count-1); + + return rc; +} + +/* + * Description: This function implements combined transactions. Combined + * transactions consist of combinations of reading and writing blocks of data. + * FROM THE SAME ADDRESS + * Each transfer (i.e. a read or a write) is separated by a repeated start + * condition. + */ +static int +iop3xx_i2c_handle_msg(struct i2c_adapter *i2c_adap, struct i2c_msg* pmsg) +{ + struct i2c_algo_iop3xx_data *iop3xx_adap = i2c_adap->algo_data; + int rc; + + rc = iop3xx_i2c_send_target_addr(iop3xx_adap, pmsg); + if (rc < 0) { + return rc; + } + + if ((pmsg->flags&I2C_M_RD)) { + return iop3xx_i2c_readbytes(i2c_adap, pmsg->buf, pmsg->len); + } else { + return iop3xx_i2c_writebytes(i2c_adap, pmsg->buf, pmsg->len); + } +} + +/* + * master_xfer() - main read/write entry + */ +static int +iop3xx_i2c_master_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg *msgs, + int num) +{ + struct i2c_algo_iop3xx_data *iop3xx_adap = i2c_adap->algo_data; + int im = 0; + int ret = 0; + int status; + + iop3xx_i2c_wait_idle(iop3xx_adap, &status); + iop3xx_i2c_reset(iop3xx_adap); + iop3xx_i2c_enable(iop3xx_adap); + + for (im = 0; ret == 0 && im != num; im++) { + ret = iop3xx_i2c_handle_msg(i2c_adap, &msgs[im]); + } + + iop3xx_i2c_transaction_cleanup(iop3xx_adap); + + if(ret) + return ret; + + return im; +} + +static u32 +iop3xx_i2c_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm iop3xx_i2c_algo = { + .master_xfer = iop3xx_i2c_master_xfer, + .functionality = iop3xx_i2c_func, +}; + +static int +iop3xx_i2c_remove(struct platform_device *pdev) +{ + struct i2c_adapter *padapter = platform_get_drvdata(pdev); + struct i2c_algo_iop3xx_data *adapter_data = + (struct i2c_algo_iop3xx_data *)padapter->algo_data; + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + unsigned long cr = __raw_readl(adapter_data->ioaddr + CR_OFFSET); + + /* + * Disable the actual HW unit + */ + cr &= ~(IOP3XX_ICR_ALD_IE | IOP3XX_ICR_BERR_IE | + IOP3XX_ICR_RXFULL_IE | IOP3XX_ICR_TXEMPTY_IE); + __raw_writel(cr, adapter_data->ioaddr + CR_OFFSET); + + iounmap((void __iomem*)adapter_data->ioaddr); + release_mem_region(res->start, IOP3XX_I2C_IO_SIZE); + kfree(adapter_data); + kfree(padapter); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static int +iop3xx_i2c_probe(struct platform_device *pdev) +{ + struct resource *res; + int ret, irq; + struct i2c_adapter *new_adapter; + struct i2c_algo_iop3xx_data *adapter_data; + + new_adapter = kzalloc(sizeof(struct i2c_adapter), GFP_KERNEL); + if (!new_adapter) { + ret = -ENOMEM; + goto out; + } + + adapter_data = kzalloc(sizeof(struct i2c_algo_iop3xx_data), GFP_KERNEL); + if (!adapter_data) { + ret = -ENOMEM; + goto free_adapter; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -ENODEV; + goto free_both; + } + + if (!request_mem_region(res->start, IOP3XX_I2C_IO_SIZE, pdev->name)) { + ret = -EBUSY; + goto free_both; + } + + /* set the adapter enumeration # */ + adapter_data->id = i2c_id++; + + adapter_data->ioaddr = (u32)ioremap(res->start, IOP3XX_I2C_IO_SIZE); + if (!adapter_data->ioaddr) { + ret = -ENOMEM; + goto release_region; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + ret = -ENXIO; + goto unmap; + } + ret = request_irq(irq, iop3xx_i2c_irq_handler, 0, + pdev->name, adapter_data); + + if (ret) { + ret = -EIO; + goto unmap; + } + + memcpy(new_adapter->name, pdev->name, strlen(pdev->name)); + new_adapter->id = I2C_HW_IOP3XX; + new_adapter->owner = THIS_MODULE; + new_adapter->class = I2C_CLASS_HWMON | I2C_CLASS_SPD; + new_adapter->dev.parent = &pdev->dev; + new_adapter->nr = pdev->id; + + /* + * Default values...should these come in from board code? + */ + new_adapter->timeout = 100; + new_adapter->algo = &iop3xx_i2c_algo; + + init_waitqueue_head(&adapter_data->waitq); + spin_lock_init(&adapter_data->lock); + + iop3xx_i2c_reset(adapter_data); + iop3xx_i2c_enable(adapter_data); + + platform_set_drvdata(pdev, new_adapter); + new_adapter->algo_data = adapter_data; + + i2c_add_numbered_adapter(new_adapter); + + return 0; + +unmap: + iounmap((void __iomem*)adapter_data->ioaddr); + +release_region: + release_mem_region(res->start, IOP3XX_I2C_IO_SIZE); + +free_both: + kfree(adapter_data); + +free_adapter: + kfree(new_adapter); + +out: + return ret; +} + + +static struct platform_driver iop3xx_i2c_driver = { + .probe = iop3xx_i2c_probe, + .remove = iop3xx_i2c_remove, + .driver = { + .owner = THIS_MODULE, + .name = "IOP3xx-I2C", + }, +}; + +static int __init +i2c_iop3xx_init (void) +{ + return platform_driver_register(&iop3xx_i2c_driver); +} + +static void __exit +i2c_iop3xx_exit (void) +{ + platform_driver_unregister(&iop3xx_i2c_driver); + return; +} + +module_init (i2c_iop3xx_init); +module_exit (i2c_iop3xx_exit); + +MODULE_AUTHOR("D-TACQ Solutions Ltd <www.d-tacq.com>"); +MODULE_DESCRIPTION("IOP3xx iic algorithm and driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:IOP3xx-I2C"); diff --git a/drivers/i2c/busses/i2c-iop3xx.h b/drivers/i2c/busses/i2c-iop3xx.h new file mode 100644 index 0000000..8485861 --- /dev/null +++ b/drivers/i2c/busses/i2c-iop3xx.h @@ -0,0 +1,107 @@ +/* ------------------------------------------------------------------------- */ +/* i2c-iop3xx.h algorithm driver definitions private to i2c-iop3xx.c */ +/* ------------------------------------------------------------------------- */ +/* Copyright (C) 2003 Peter Milne, D-TACQ Solutions Ltd + * <Peter dot Milne at D hyphen TACQ dot com> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, version 2. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +/* ------------------------------------------------------------------------- */ + + +#ifndef I2C_IOP3XX_H +#define I2C_IOP3XX_H 1 + +/* + * iop321 hardware bit definitions + */ +#define IOP3XX_ICR_FAST_MODE 0x8000 /* 1=400kBps, 0=100kBps */ +#define IOP3XX_ICR_UNIT_RESET 0x4000 /* 1=RESET */ +#define IOP3XX_ICR_SAD_IE 0x2000 /* 1=Slave Detect Interrupt Enable */ +#define IOP3XX_ICR_ALD_IE 0x1000 /* 1=Arb Loss Detect Interrupt Enable */ +#define IOP3XX_ICR_SSD_IE 0x0800 /* 1=Slave STOP Detect Interrupt Enable */ +#define IOP3XX_ICR_BERR_IE 0x0400 /* 1=Bus Error Interrupt Enable */ +#define IOP3XX_ICR_RXFULL_IE 0x0200 /* 1=Receive Full Interrupt Enable */ +#define IOP3XX_ICR_TXEMPTY_IE 0x0100 /* 1=Transmit Empty Interrupt Enable */ +#define IOP3XX_ICR_GCD 0x0080 /* 1=General Call Disable */ +/* + * IOP3XX_ICR_GCD: 1 disables response as slave. "This bit must be set + * when sending a master mode general call message from the I2C unit" + */ +#define IOP3XX_ICR_UE 0x0040 /* 1=Unit Enable */ +/* + * "NOTE: To avoid I2C bus integrity problems, + * the user needs to ensure that the GPIO Output Data Register - + * GPOD bits associated with an I2C port are cleared prior to setting + * the enable bit for that I2C serial port. + * The user prepares to enable I2C port 0 and + * I2C port 1 by clearing GPOD bits 7:6 and GPOD bits 5:4, respectively. + */ +#define IOP3XX_ICR_SCLEN 0x0020 /* 1=SCL enable for master mode */ +#define IOP3XX_ICR_MABORT 0x0010 /* 1=Send a STOP with no data + * NB TBYTE must be clear */ +#define IOP3XX_ICR_TBYTE 0x0008 /* 1=Send/Receive a byte. i2c clears */ +#define IOP3XX_ICR_NACK 0x0004 /* 1=reply with NACK */ +#define IOP3XX_ICR_MSTOP 0x0002 /* 1=send a STOP after next data byte */ +#define IOP3XX_ICR_MSTART 0x0001 /* 1=initiate a START */ + + +#define IOP3XX_ISR_BERRD 0x0400 /* 1=BUS ERROR Detected */ +#define IOP3XX_ISR_SAD 0x0200 /* 1=Slave ADdress Detected */ +#define IOP3XX_ISR_GCAD 0x0100 /* 1=General Call Address Detected */ +#define IOP3XX_ISR_RXFULL 0x0080 /* 1=Receive Full */ +#define IOP3XX_ISR_TXEMPTY 0x0040 /* 1=Transmit Empty */ +#define IOP3XX_ISR_ALD 0x0020 /* 1=Arbitration Loss Detected */ +#define IOP3XX_ISR_SSD 0x0010 /* 1=Slave STOP Detected */ +#define IOP3XX_ISR_BBUSY 0x0008 /* 1=Bus BUSY */ +#define IOP3XX_ISR_UNITBUSY 0x0004 /* 1=Unit Busy */ +#define IOP3XX_ISR_NACK 0x0002 /* 1=Unit Rx or Tx a NACK */ +#define IOP3XX_ISR_RXREAD 0x0001 /* 1=READ 0=WRITE (R/W bit of slave addr */ + +#define IOP3XX_ISR_CLEARBITS 0x07f0 + +#define IOP3XX_ISAR_SAMASK 0x007f + +#define IOP3XX_IDBR_MASK 0x00ff + +#define IOP3XX_IBMR_SCL 0x0002 +#define IOP3XX_IBMR_SDA 0x0001 + +#define IOP3XX_GPOD_I2C0 0x00c0 /* clear these bits to enable ch0 */ +#define IOP3XX_GPOD_I2C1 0x0030 /* clear these bits to enable ch1 */ + +#define MYSAR 0 /* default slave address */ + +#define I2C_ERR 321 +#define I2C_ERR_BERR (I2C_ERR+0) +#define I2C_ERR_ALD (I2C_ERR+1) + + +#define CR_OFFSET 0 +#define SR_OFFSET 0x4 +#define SAR_OFFSET 0x8 +#define DBR_OFFSET 0xc +#define CCR_OFFSET 0x10 +#define BMR_OFFSET 0x14 + +#define IOP3XX_I2C_IO_SIZE 0x18 + +struct i2c_algo_iop3xx_data { + u32 ioaddr; + wait_queue_head_t waitq; + spinlock_t lock; + u32 SR_enabled, SR_received; + int id; +}; + +#endif /* I2C_IOP3XX_H */ diff --git a/drivers/i2c/busses/i2c-isch.c b/drivers/i2c/busses/i2c-isch.c new file mode 100644 index 0000000..b9c01aa --- /dev/null +++ b/drivers/i2c/busses/i2c-isch.c @@ -0,0 +1,339 @@ +/* + i2c-isch.c - Linux kernel driver for Intel SCH chipset SMBus + - Based on i2c-piix4.c + Copyright (c) 1998 - 2002 Frodo Looijaard <frodol@dds.nl> and + Philip Edelbrock <phil@netroedge.com> + - Intel SCH support + Copyright (c) 2007 - 2008 Jacob Jun Pan <jacob.jun.pan@intel.com> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* + Supports: + Intel SCH chipsets (AF82US15W, AF82US15L, AF82UL11L) + Note: we assume there can only be one device, with one SMBus interface. +*/ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/stddef.h> +#include <linux/ioport.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/acpi.h> + +/* SCH SMBus address offsets */ +#define SMBHSTCNT (0 + sch_smba) +#define SMBHSTSTS (1 + sch_smba) +#define SMBHSTADD (4 + sch_smba) /* TSA */ +#define SMBHSTCMD (5 + sch_smba) +#define SMBHSTDAT0 (6 + sch_smba) +#define SMBHSTDAT1 (7 + sch_smba) +#define SMBBLKDAT (0x20 + sch_smba) + +/* count for request_region */ +#define SMBIOSIZE 64 + +/* PCI Address Constants */ +#define SMBBA_SCH 0x40 + +/* Other settings */ +#define MAX_TIMEOUT 500 + +/* I2C constants */ +#define SCH_QUICK 0x00 +#define SCH_BYTE 0x01 +#define SCH_BYTE_DATA 0x02 +#define SCH_WORD_DATA 0x03 +#define SCH_BLOCK_DATA 0x05 + +static unsigned short sch_smba; +static struct pci_driver sch_driver; +static struct i2c_adapter sch_adapter; + +/* + * Start the i2c transaction -- the i2c_access will prepare the transaction + * and this function will execute it. + * return 0 for success and others for failure. + */ +static int sch_transaction(void) +{ + int temp; + int result = 0; + int timeout = 0; + + dev_dbg(&sch_adapter.dev, "Transaction (pre): CNT=%02x, CMD=%02x, " + "ADD=%02x, DAT0=%02x, DAT1=%02x\n", inb(SMBHSTCNT), + inb(SMBHSTCMD), inb(SMBHSTADD), inb(SMBHSTDAT0), + inb(SMBHSTDAT1)); + + /* Make sure the SMBus host is ready to start transmitting */ + temp = inb(SMBHSTSTS) & 0x0f; + if (temp) { + /* Can not be busy since we checked it in sch_access */ + if (temp & 0x01) { + dev_dbg(&sch_adapter.dev, "Completion (%02x). " + "Clear...\n", temp); + } + if (temp & 0x06) { + dev_dbg(&sch_adapter.dev, "SMBus error (%02x). " + "Resetting...\n", temp); + } + outb(temp, SMBHSTSTS); + temp = inb(SMBHSTSTS) & 0x0f; + if (temp) { + dev_err(&sch_adapter.dev, + "SMBus is not ready: (%02x)\n", temp); + return -EAGAIN; + } + } + + /* start the transaction by setting bit 4 */ + outb(inb(SMBHSTCNT) | 0x10, SMBHSTCNT); + + do { + msleep(1); + temp = inb(SMBHSTSTS) & 0x0f; + } while ((temp & 0x08) && (timeout++ < MAX_TIMEOUT)); + + /* If the SMBus is still busy, we give up */ + if (timeout >= MAX_TIMEOUT) { + dev_err(&sch_adapter.dev, "SMBus Timeout!\n"); + result = -ETIMEDOUT; + } + if (temp & 0x04) { + result = -EIO; + dev_dbg(&sch_adapter.dev, "Bus collision! SMBus may be " + "locked until next hard reset. (sorry!)\n"); + /* Clock stops and slave is stuck in mid-transmission */ + } else if (temp & 0x02) { + result = -EIO; + dev_err(&sch_adapter.dev, "Error: no response!\n"); + } else if (temp & 0x01) { + dev_dbg(&sch_adapter.dev, "Post complete!\n"); + outb(temp, SMBHSTSTS); + temp = inb(SMBHSTSTS) & 0x07; + if (temp & 0x06) { + /* Completion clear failed */ + dev_dbg(&sch_adapter.dev, "Failed reset at end of " + "transaction (%02x), Bus error!\n", temp); + } + } else { + result = -ENXIO; + dev_dbg(&sch_adapter.dev, "No such address.\n"); + } + dev_dbg(&sch_adapter.dev, "Transaction (post): CNT=%02x, CMD=%02x, " + "ADD=%02x, DAT0=%02x, DAT1=%02x\n", inb(SMBHSTCNT), + inb(SMBHSTCMD), inb(SMBHSTADD), inb(SMBHSTDAT0), + inb(SMBHSTDAT1)); + return result; +} + +/* + * This is the main access entry for i2c-sch access + * adap is i2c_adapter pointer, addr is the i2c device bus address, read_write + * (0 for read and 1 for write), size is i2c transaction type and data is the + * union of transaction for data to be transfered or data read from bus. + * return 0 for success and others for failure. + */ +static s32 sch_access(struct i2c_adapter *adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, union i2c_smbus_data *data) +{ + int i, len, temp, rc; + + /* Make sure the SMBus host is not busy */ + temp = inb(SMBHSTSTS) & 0x0f; + if (temp & 0x08) { + dev_dbg(&sch_adapter.dev, "SMBus busy (%02x)\n", temp); + return -EAGAIN; + } + dev_dbg(&sch_adapter.dev, "access size: %d %s\n", size, + (read_write)?"READ":"WRITE"); + switch (size) { + case I2C_SMBUS_QUICK: + outb((addr << 1) | read_write, SMBHSTADD); + size = SCH_QUICK; + break; + case I2C_SMBUS_BYTE: + outb((addr << 1) | read_write, SMBHSTADD); + if (read_write == I2C_SMBUS_WRITE) + outb(command, SMBHSTCMD); + size = SCH_BYTE; + break; + case I2C_SMBUS_BYTE_DATA: + outb((addr << 1) | read_write, SMBHSTADD); + outb(command, SMBHSTCMD); + if (read_write == I2C_SMBUS_WRITE) + outb(data->byte, SMBHSTDAT0); + size = SCH_BYTE_DATA; + break; + case I2C_SMBUS_WORD_DATA: + outb((addr << 1) | read_write, SMBHSTADD); + outb(command, SMBHSTCMD); + if (read_write == I2C_SMBUS_WRITE) { + outb(data->word & 0xff, SMBHSTDAT0); + outb((data->word & 0xff00) >> 8, SMBHSTDAT1); + } + size = SCH_WORD_DATA; + break; + case I2C_SMBUS_BLOCK_DATA: + outb((addr << 1) | read_write, SMBHSTADD); + outb(command, SMBHSTCMD); + if (read_write == I2C_SMBUS_WRITE) { + len = data->block[0]; + if (len == 0 || len > I2C_SMBUS_BLOCK_MAX) + return -EINVAL; + outb(len, SMBHSTDAT0); + for (i = 1; i <= len; i++) + outb(data->block[i], SMBBLKDAT+i-1); + } + size = SCH_BLOCK_DATA; + break; + default: + dev_warn(&adap->dev, "Unsupported transaction %d\n", size); + return -EOPNOTSUPP; + } + dev_dbg(&sch_adapter.dev, "write size %d to 0x%04x\n", size, SMBHSTCNT); + outb((inb(SMBHSTCNT) & 0xb0) | (size & 0x7), SMBHSTCNT); + + rc = sch_transaction(); + if (rc) /* Error in transaction */ + return rc; + + if ((read_write == I2C_SMBUS_WRITE) || (size == SCH_QUICK)) + return 0; + + switch (size) { + case SCH_BYTE: + case SCH_BYTE_DATA: + data->byte = inb(SMBHSTDAT0); + break; + case SCH_WORD_DATA: + data->word = inb(SMBHSTDAT0) + (inb(SMBHSTDAT1) << 8); + break; + case SCH_BLOCK_DATA: + data->block[0] = inb(SMBHSTDAT0); + if (data->block[0] == 0 || data->block[0] > I2C_SMBUS_BLOCK_MAX) + return -EPROTO; + for (i = 1; i <= data->block[0]; i++) + data->block[i] = inb(SMBBLKDAT+i-1); + break; + } + return 0; +} + +static u32 sch_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_BLOCK_DATA; +} + +static const struct i2c_algorithm smbus_algorithm = { + .smbus_xfer = sch_access, + .functionality = sch_func, +}; + +static struct i2c_adapter sch_adapter = { + .owner = THIS_MODULE, + .class = I2C_CLASS_HWMON | I2C_CLASS_SPD, + .algo = &smbus_algorithm, +}; + +static struct pci_device_id sch_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_SCH_LPC) }, + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, sch_ids); + +static int __devinit sch_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + int retval; + unsigned int smba; + + pci_read_config_dword(dev, SMBBA_SCH, &smba); + if (!(smba & (1 << 31))) { + dev_err(&dev->dev, "SMBus I/O space disabled!\n"); + return -ENODEV; + } + + sch_smba = (unsigned short)smba; + if (sch_smba == 0) { + dev_err(&dev->dev, "SMBus base address uninitialized!\n"); + return -ENODEV; + } + if (acpi_check_region(sch_smba, SMBIOSIZE, sch_driver.name)) + return -EBUSY; + if (!request_region(sch_smba, SMBIOSIZE, sch_driver.name)) { + dev_err(&dev->dev, "SMBus region 0x%x already in use!\n", + sch_smba); + return -EBUSY; + } + dev_dbg(&dev->dev, "SMBA = 0x%X\n", sch_smba); + + /* set up the sysfs linkage to our parent device */ + sch_adapter.dev.parent = &dev->dev; + + snprintf(sch_adapter.name, sizeof(sch_adapter.name), + "SMBus SCH adapter at %04x", sch_smba); + + retval = i2c_add_adapter(&sch_adapter); + if (retval) { + dev_err(&dev->dev, "Couldn't register adapter!\n"); + release_region(sch_smba, SMBIOSIZE); + sch_smba = 0; + } + + return retval; +} + +static void __devexit sch_remove(struct pci_dev *dev) +{ + if (sch_smba) { + i2c_del_adapter(&sch_adapter); + release_region(sch_smba, SMBIOSIZE); + sch_smba = 0; + } +} + +static struct pci_driver sch_driver = { + .name = "isch_smbus", + .id_table = sch_ids, + .probe = sch_probe, + .remove = __devexit_p(sch_remove), +}; + +static int __init i2c_sch_init(void) +{ + return pci_register_driver(&sch_driver); +} + +static void __exit i2c_sch_exit(void) +{ + pci_unregister_driver(&sch_driver); +} + +MODULE_AUTHOR("Jacob Pan <jacob.jun.pan@intel.com>"); +MODULE_DESCRIPTION("Intel SCH SMBus driver"); +MODULE_LICENSE("GPL"); + +module_init(i2c_sch_init); +module_exit(i2c_sch_exit); diff --git a/drivers/i2c/busses/i2c-ixp2000.c b/drivers/i2c/busses/i2c-ixp2000.c new file mode 100644 index 0000000..05d72e9 --- /dev/null +++ b/drivers/i2c/busses/i2c-ixp2000.c @@ -0,0 +1,168 @@ +/* + * drivers/i2c/busses/i2c-ixp2000.c + * + * I2C adapter for IXP2000 systems using GPIOs for I2C bus + * + * Author: Deepak Saxena <dsaxena@plexity.net> + * Based on IXDP2400 code by: Naeem M. Afzal <naeem.m.afzal@intel.com> + * Made generic by: Jeff Daly <jeffrey.daly@intel.com> + * + * Copyright (c) 2003-2004 MontaVista Software Inc. + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + * + * From Jeff Daly: + * + * I2C adapter driver for Intel IXDP2xxx platforms. This should work for any + * IXP2000 platform if it uses the HW GPIO in the same manner. Basically, + * SDA and SCL GPIOs have external pullups. Setting the respective GPIO to + * an input will make the signal a '1' via the pullup. Setting them to + * outputs will pull them down. + * + * The GPIOs are open drain signals and are used as configuration strap inputs + * during power-up so there's generally a buffer on the board that needs to be + * 'enabled' to drive the GPIOs. + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> + +#include <mach/hardware.h> /* Pick up IXP2000-specific bits */ +#include <mach/gpio.h> + +static inline int ixp2000_scl_pin(void *data) +{ + return ((struct ixp2000_i2c_pins*)data)->scl_pin; +} + +static inline int ixp2000_sda_pin(void *data) +{ + return ((struct ixp2000_i2c_pins*)data)->sda_pin; +} + + +static void ixp2000_bit_setscl(void *data, int val) +{ + int i = 5000; + + if (val) { + gpio_line_config(ixp2000_scl_pin(data), GPIO_IN); + while(!gpio_line_get(ixp2000_scl_pin(data)) && i--); + } else { + gpio_line_config(ixp2000_scl_pin(data), GPIO_OUT); + } +} + +static void ixp2000_bit_setsda(void *data, int val) +{ + if (val) { + gpio_line_config(ixp2000_sda_pin(data), GPIO_IN); + } else { + gpio_line_config(ixp2000_sda_pin(data), GPIO_OUT); + } +} + +static int ixp2000_bit_getscl(void *data) +{ + return gpio_line_get(ixp2000_scl_pin(data)); +} + +static int ixp2000_bit_getsda(void *data) +{ + return gpio_line_get(ixp2000_sda_pin(data)); +} + +struct ixp2000_i2c_data { + struct ixp2000_i2c_pins *gpio_pins; + struct i2c_adapter adapter; + struct i2c_algo_bit_data algo_data; +}; + +static int ixp2000_i2c_remove(struct platform_device *plat_dev) +{ + struct ixp2000_i2c_data *drv_data = platform_get_drvdata(plat_dev); + + platform_set_drvdata(plat_dev, NULL); + + i2c_del_adapter(&drv_data->adapter); + + kfree(drv_data); + + return 0; +} + +static int ixp2000_i2c_probe(struct platform_device *plat_dev) +{ + int err; + struct ixp2000_i2c_pins *gpio = plat_dev->dev.platform_data; + struct ixp2000_i2c_data *drv_data = + kzalloc(sizeof(struct ixp2000_i2c_data), GFP_KERNEL); + + if (!drv_data) + return -ENOMEM; + drv_data->gpio_pins = gpio; + + drv_data->algo_data.data = gpio; + drv_data->algo_data.setsda = ixp2000_bit_setsda; + drv_data->algo_data.setscl = ixp2000_bit_setscl; + drv_data->algo_data.getsda = ixp2000_bit_getsda; + drv_data->algo_data.getscl = ixp2000_bit_getscl; + drv_data->algo_data.udelay = 6; + drv_data->algo_data.timeout = 100; + + drv_data->adapter.id = I2C_HW_B_IXP2000, + strlcpy(drv_data->adapter.name, plat_dev->dev.driver->name, + sizeof(drv_data->adapter.name)); + drv_data->adapter.algo_data = &drv_data->algo_data, + + drv_data->adapter.dev.parent = &plat_dev->dev; + + gpio_line_config(gpio->sda_pin, GPIO_IN); + gpio_line_config(gpio->scl_pin, GPIO_IN); + gpio_line_set(gpio->scl_pin, 0); + gpio_line_set(gpio->sda_pin, 0); + + if ((err = i2c_bit_add_bus(&drv_data->adapter)) != 0) { + dev_err(&plat_dev->dev, "Could not install, error %d\n", err); + kfree(drv_data); + return err; + } + + platform_set_drvdata(plat_dev, drv_data); + + return 0; +} + +static struct platform_driver ixp2000_i2c_driver = { + .probe = ixp2000_i2c_probe, + .remove = ixp2000_i2c_remove, + .driver = { + .name = "IXP2000-I2C", + .owner = THIS_MODULE, + }, +}; + +static int __init ixp2000_i2c_init(void) +{ + return platform_driver_register(&ixp2000_i2c_driver); +} + +static void __exit ixp2000_i2c_exit(void) +{ + platform_driver_unregister(&ixp2000_i2c_driver); +} + +module_init(ixp2000_i2c_init); +module_exit(ixp2000_i2c_exit); + +MODULE_AUTHOR ("Deepak Saxena <dsaxena@plexity.net>"); +MODULE_DESCRIPTION("IXP2000 GPIO-based I2C bus driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:IXP2000-I2C"); + diff --git a/drivers/i2c/busses/i2c-mpc.c b/drivers/i2c/busses/i2c-mpc.c new file mode 100644 index 0000000..a9a45fc --- /dev/null +++ b/drivers/i2c/busses/i2c-mpc.c @@ -0,0 +1,437 @@ +/* + * (C) Copyright 2003-2004 + * Humboldt Solutions Ltd, adrian@humboldt.co.uk. + + * This is a combined i2c adapter and algorithm driver for the + * MPC107/Tsi107 PowerPC northbridge and processors that include + * the same I2C unit (8240, 8245, 85xx). + * + * Release 0.8 + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/init.h> +#include <linux/of_platform.h> +#include <linux/of_i2c.h> + +#include <asm/io.h> +#include <linux/fsl_devices.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/delay.h> + +#define DRV_NAME "mpc-i2c" + +#define MPC_I2C_FDR 0x04 +#define MPC_I2C_CR 0x08 +#define MPC_I2C_SR 0x0c +#define MPC_I2C_DR 0x10 +#define MPC_I2C_DFSRR 0x14 + +#define CCR_MEN 0x80 +#define CCR_MIEN 0x40 +#define CCR_MSTA 0x20 +#define CCR_MTX 0x10 +#define CCR_TXAK 0x08 +#define CCR_RSTA 0x04 + +#define CSR_MCF 0x80 +#define CSR_MAAS 0x40 +#define CSR_MBB 0x20 +#define CSR_MAL 0x10 +#define CSR_SRW 0x04 +#define CSR_MIF 0x02 +#define CSR_RXAK 0x01 + +struct mpc_i2c { + void __iomem *base; + u32 interrupt; + wait_queue_head_t queue; + struct i2c_adapter adap; + int irq; + u32 flags; +}; + +static __inline__ void writeccr(struct mpc_i2c *i2c, u32 x) +{ + writeb(x, i2c->base + MPC_I2C_CR); +} + +static irqreturn_t mpc_i2c_isr(int irq, void *dev_id) +{ + struct mpc_i2c *i2c = dev_id; + if (readb(i2c->base + MPC_I2C_SR) & CSR_MIF) { + /* Read again to allow register to stabilise */ + i2c->interrupt = readb(i2c->base + MPC_I2C_SR); + writeb(0, i2c->base + MPC_I2C_SR); + wake_up_interruptible(&i2c->queue); + } + return IRQ_HANDLED; +} + +/* Sometimes 9th clock pulse isn't generated, and slave doesn't release + * the bus, because it wants to send ACK. + * Following sequence of enabling/disabling and sending start/stop generates + * the pulse, so it's all OK. + */ +static void mpc_i2c_fixup(struct mpc_i2c *i2c) +{ + writeccr(i2c, 0); + udelay(30); + writeccr(i2c, CCR_MEN); + udelay(30); + writeccr(i2c, CCR_MSTA | CCR_MTX); + udelay(30); + writeccr(i2c, CCR_MSTA | CCR_MTX | CCR_MEN); + udelay(30); + writeccr(i2c, CCR_MEN); + udelay(30); +} + +static int i2c_wait(struct mpc_i2c *i2c, unsigned timeout, int writing) +{ + unsigned long orig_jiffies = jiffies; + u32 x; + int result = 0; + + if (i2c->irq == NO_IRQ) + { + while (!(readb(i2c->base + MPC_I2C_SR) & CSR_MIF)) { + schedule(); + if (time_after(jiffies, orig_jiffies + timeout)) { + pr_debug("I2C: timeout\n"); + writeccr(i2c, 0); + result = -EIO; + break; + } + } + x = readb(i2c->base + MPC_I2C_SR); + writeb(0, i2c->base + MPC_I2C_SR); + } else { + /* Interrupt mode */ + result = wait_event_interruptible_timeout(i2c->queue, + (i2c->interrupt & CSR_MIF), timeout * HZ); + + if (unlikely(result < 0)) { + pr_debug("I2C: wait interrupted\n"); + writeccr(i2c, 0); + } else if (unlikely(!(i2c->interrupt & CSR_MIF))) { + pr_debug("I2C: wait timeout\n"); + writeccr(i2c, 0); + result = -ETIMEDOUT; + } + + x = i2c->interrupt; + i2c->interrupt = 0; + } + + if (result < 0) + return result; + + if (!(x & CSR_MCF)) { + pr_debug("I2C: unfinished\n"); + return -EIO; + } + + if (x & CSR_MAL) { + pr_debug("I2C: MAL\n"); + return -EIO; + } + + if (writing && (x & CSR_RXAK)) { + pr_debug("I2C: No RXAK\n"); + /* generate stop */ + writeccr(i2c, CCR_MEN); + return -EIO; + } + return 0; +} + +static void mpc_i2c_setclock(struct mpc_i2c *i2c) +{ + /* Set clock and filters */ + if (i2c->flags & FSL_I2C_DEV_SEPARATE_DFSRR) { + writeb(0x31, i2c->base + MPC_I2C_FDR); + writeb(0x10, i2c->base + MPC_I2C_DFSRR); + } else if (i2c->flags & FSL_I2C_DEV_CLOCK_5200) + writeb(0x3f, i2c->base + MPC_I2C_FDR); + else + writel(0x1031, i2c->base + MPC_I2C_FDR); +} + +static void mpc_i2c_start(struct mpc_i2c *i2c) +{ + /* Clear arbitration */ + writeb(0, i2c->base + MPC_I2C_SR); + /* Start with MEN */ + writeccr(i2c, CCR_MEN); +} + +static void mpc_i2c_stop(struct mpc_i2c *i2c) +{ + writeccr(i2c, CCR_MEN); +} + +static int mpc_write(struct mpc_i2c *i2c, int target, + const u8 * data, int length, int restart) +{ + int i, result; + unsigned timeout = i2c->adap.timeout; + u32 flags = restart ? CCR_RSTA : 0; + + /* Start with MEN */ + if (!restart) + writeccr(i2c, CCR_MEN); + /* Start as master */ + writeccr(i2c, CCR_MIEN | CCR_MEN | CCR_MSTA | CCR_MTX | flags); + /* Write target byte */ + writeb((target << 1), i2c->base + MPC_I2C_DR); + + result = i2c_wait(i2c, timeout, 1); + if (result < 0) + return result; + + for (i = 0; i < length; i++) { + /* Write data byte */ + writeb(data[i], i2c->base + MPC_I2C_DR); + + result = i2c_wait(i2c, timeout, 1); + if (result < 0) + return result; + } + + return 0; +} + +static int mpc_read(struct mpc_i2c *i2c, int target, + u8 * data, int length, int restart) +{ + unsigned timeout = i2c->adap.timeout; + int i, result; + u32 flags = restart ? CCR_RSTA : 0; + + /* Start with MEN */ + if (!restart) + writeccr(i2c, CCR_MEN); + /* Switch to read - restart */ + writeccr(i2c, CCR_MIEN | CCR_MEN | CCR_MSTA | CCR_MTX | flags); + /* Write target address byte - this time with the read flag set */ + writeb((target << 1) | 1, i2c->base + MPC_I2C_DR); + + result = i2c_wait(i2c, timeout, 1); + if (result < 0) + return result; + + if (length) { + if (length == 1) + writeccr(i2c, CCR_MIEN | CCR_MEN | CCR_MSTA | CCR_TXAK); + else + writeccr(i2c, CCR_MIEN | CCR_MEN | CCR_MSTA); + /* Dummy read */ + readb(i2c->base + MPC_I2C_DR); + } + + for (i = 0; i < length; i++) { + result = i2c_wait(i2c, timeout, 0); + if (result < 0) + return result; + + /* Generate txack on next to last byte */ + if (i == length - 2) + writeccr(i2c, CCR_MIEN | CCR_MEN | CCR_MSTA | CCR_TXAK); + /* Generate stop on last byte */ + if (i == length - 1) + writeccr(i2c, CCR_MIEN | CCR_MEN | CCR_TXAK); + data[i] = readb(i2c->base + MPC_I2C_DR); + } + + return length; +} + +static int mpc_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) +{ + struct i2c_msg *pmsg; + int i; + int ret = 0; + unsigned long orig_jiffies = jiffies; + struct mpc_i2c *i2c = i2c_get_adapdata(adap); + + mpc_i2c_start(i2c); + + /* Allow bus up to 1s to become not busy */ + while (readb(i2c->base + MPC_I2C_SR) & CSR_MBB) { + if (signal_pending(current)) { + pr_debug("I2C: Interrupted\n"); + writeccr(i2c, 0); + return -EINTR; + } + if (time_after(jiffies, orig_jiffies + HZ)) { + pr_debug("I2C: timeout\n"); + if (readb(i2c->base + MPC_I2C_SR) == + (CSR_MCF | CSR_MBB | CSR_RXAK)) + mpc_i2c_fixup(i2c); + return -EIO; + } + schedule(); + } + + for (i = 0; ret >= 0 && i < num; i++) { + pmsg = &msgs[i]; + pr_debug("Doing %s %d bytes to 0x%02x - %d of %d messages\n", + pmsg->flags & I2C_M_RD ? "read" : "write", + pmsg->len, pmsg->addr, i + 1, num); + if (pmsg->flags & I2C_M_RD) + ret = + mpc_read(i2c, pmsg->addr, pmsg->buf, pmsg->len, i); + else + ret = + mpc_write(i2c, pmsg->addr, pmsg->buf, pmsg->len, i); + } + mpc_i2c_stop(i2c); + return (ret < 0) ? ret : num; +} + +static u32 mpc_functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm mpc_algo = { + .master_xfer = mpc_xfer, + .functionality = mpc_functionality, +}; + +static struct i2c_adapter mpc_ops = { + .owner = THIS_MODULE, + .name = "MPC adapter", + .id = I2C_HW_MPC107, + .algo = &mpc_algo, + .timeout = 1, +}; + +static int __devinit fsl_i2c_probe(struct of_device *op, const struct of_device_id *match) +{ + int result = 0; + struct mpc_i2c *i2c; + + i2c = kzalloc(sizeof(*i2c), GFP_KERNEL); + if (!i2c) + return -ENOMEM; + + if (of_get_property(op->node, "dfsrr", NULL)) + i2c->flags |= FSL_I2C_DEV_SEPARATE_DFSRR; + + if (of_device_is_compatible(op->node, "fsl,mpc5200-i2c") || + of_device_is_compatible(op->node, "mpc5200-i2c")) + i2c->flags |= FSL_I2C_DEV_CLOCK_5200; + + init_waitqueue_head(&i2c->queue); + + i2c->base = of_iomap(op->node, 0); + if (!i2c->base) { + printk(KERN_ERR "i2c-mpc - failed to map controller\n"); + result = -ENOMEM; + goto fail_map; + } + + i2c->irq = irq_of_parse_and_map(op->node, 0); + if (i2c->irq != NO_IRQ) { /* i2c->irq = NO_IRQ implies polling */ + result = request_irq(i2c->irq, mpc_i2c_isr, + IRQF_SHARED, "i2c-mpc", i2c); + if (result < 0) { + printk(KERN_ERR "i2c-mpc - failed to attach interrupt\n"); + goto fail_request; + } + } + + mpc_i2c_setclock(i2c); + + dev_set_drvdata(&op->dev, i2c); + + i2c->adap = mpc_ops; + i2c_set_adapdata(&i2c->adap, i2c); + i2c->adap.dev.parent = &op->dev; + + result = i2c_add_adapter(&i2c->adap); + if (result < 0) { + printk(KERN_ERR "i2c-mpc - failed to add adapter\n"); + goto fail_add; + } + of_register_i2c_devices(&i2c->adap, op->node); + + return result; + + fail_add: + dev_set_drvdata(&op->dev, NULL); + free_irq(i2c->irq, i2c); + fail_request: + irq_dispose_mapping(i2c->irq); + iounmap(i2c->base); + fail_map: + kfree(i2c); + return result; +}; + +static int __devexit fsl_i2c_remove(struct of_device *op) +{ + struct mpc_i2c *i2c = dev_get_drvdata(&op->dev); + + i2c_del_adapter(&i2c->adap); + dev_set_drvdata(&op->dev, NULL); + + if (i2c->irq != NO_IRQ) + free_irq(i2c->irq, i2c); + + irq_dispose_mapping(i2c->irq); + iounmap(i2c->base); + kfree(i2c); + return 0; +}; + +static const struct of_device_id mpc_i2c_of_match[] = { + {.compatible = "fsl-i2c",}, + {}, +}; +MODULE_DEVICE_TABLE(of, mpc_i2c_of_match); + + +/* Structure for a device driver */ +static struct of_platform_driver mpc_i2c_driver = { + .match_table = mpc_i2c_of_match, + .probe = fsl_i2c_probe, + .remove = __devexit_p(fsl_i2c_remove), + .driver = { + .owner = THIS_MODULE, + .name = DRV_NAME, + }, +}; + +static int __init fsl_i2c_init(void) +{ + int rv; + + rv = of_register_platform_driver(&mpc_i2c_driver); + if (rv) + printk(KERN_ERR DRV_NAME + " of_register_platform_driver failed (%i)\n", rv); + return rv; +} + +static void __exit fsl_i2c_exit(void) +{ + of_unregister_platform_driver(&mpc_i2c_driver); +} + +module_init(fsl_i2c_init); +module_exit(fsl_i2c_exit); + +MODULE_AUTHOR("Adrian Cox <adrian@humboldt.co.uk>"); +MODULE_DESCRIPTION + ("I2C-Bus adapter for MPC107 bridge and MPC824x/85xx/52xx processors"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/busses/i2c-mv64xxx.c b/drivers/i2c/busses/i2c-mv64xxx.c new file mode 100644 index 0000000..9e8118d --- /dev/null +++ b/drivers/i2c/busses/i2c-mv64xxx.c @@ -0,0 +1,605 @@ +/* + * Driver for the i2c controller on the Marvell line of host bridges + * (e.g, gt642[46]0, mv643[46]0, mv644[46]0, and Orion SoC family). + * + * Author: Mark A. Greer <mgreer@mvista.com> + * + * 2005 (c) MontaVista, Software, Inc. This file is licensed under + * the terms of the GNU General Public License version 2. This program + * is licensed "as is" without any warranty of any kind, whether express + * or implied. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/mv643xx_i2c.h> +#include <linux/platform_device.h> + +#include <asm/io.h> + +/* Register defines */ +#define MV64XXX_I2C_REG_SLAVE_ADDR 0x00 +#define MV64XXX_I2C_REG_DATA 0x04 +#define MV64XXX_I2C_REG_CONTROL 0x08 +#define MV64XXX_I2C_REG_STATUS 0x0c +#define MV64XXX_I2C_REG_BAUD 0x0c +#define MV64XXX_I2C_REG_EXT_SLAVE_ADDR 0x10 +#define MV64XXX_I2C_REG_SOFT_RESET 0x1c + +#define MV64XXX_I2C_REG_CONTROL_ACK 0x00000004 +#define MV64XXX_I2C_REG_CONTROL_IFLG 0x00000008 +#define MV64XXX_I2C_REG_CONTROL_STOP 0x00000010 +#define MV64XXX_I2C_REG_CONTROL_START 0x00000020 +#define MV64XXX_I2C_REG_CONTROL_TWSIEN 0x00000040 +#define MV64XXX_I2C_REG_CONTROL_INTEN 0x00000080 + +/* Ctlr status values */ +#define MV64XXX_I2C_STATUS_BUS_ERR 0x00 +#define MV64XXX_I2C_STATUS_MAST_START 0x08 +#define MV64XXX_I2C_STATUS_MAST_REPEAT_START 0x10 +#define MV64XXX_I2C_STATUS_MAST_WR_ADDR_ACK 0x18 +#define MV64XXX_I2C_STATUS_MAST_WR_ADDR_NO_ACK 0x20 +#define MV64XXX_I2C_STATUS_MAST_WR_ACK 0x28 +#define MV64XXX_I2C_STATUS_MAST_WR_NO_ACK 0x30 +#define MV64XXX_I2C_STATUS_MAST_LOST_ARB 0x38 +#define MV64XXX_I2C_STATUS_MAST_RD_ADDR_ACK 0x40 +#define MV64XXX_I2C_STATUS_MAST_RD_ADDR_NO_ACK 0x48 +#define MV64XXX_I2C_STATUS_MAST_RD_DATA_ACK 0x50 +#define MV64XXX_I2C_STATUS_MAST_RD_DATA_NO_ACK 0x58 +#define MV64XXX_I2C_STATUS_MAST_WR_ADDR_2_ACK 0xd0 +#define MV64XXX_I2C_STATUS_MAST_WR_ADDR_2_NO_ACK 0xd8 +#define MV64XXX_I2C_STATUS_MAST_RD_ADDR_2_ACK 0xe0 +#define MV64XXX_I2C_STATUS_MAST_RD_ADDR_2_NO_ACK 0xe8 +#define MV64XXX_I2C_STATUS_NO_STATUS 0xf8 + +/* Driver states */ +enum { + MV64XXX_I2C_STATE_INVALID, + MV64XXX_I2C_STATE_IDLE, + MV64XXX_I2C_STATE_WAITING_FOR_START_COND, + MV64XXX_I2C_STATE_WAITING_FOR_ADDR_1_ACK, + MV64XXX_I2C_STATE_WAITING_FOR_ADDR_2_ACK, + MV64XXX_I2C_STATE_WAITING_FOR_SLAVE_ACK, + MV64XXX_I2C_STATE_WAITING_FOR_SLAVE_DATA, +}; + +/* Driver actions */ +enum { + MV64XXX_I2C_ACTION_INVALID, + MV64XXX_I2C_ACTION_CONTINUE, + MV64XXX_I2C_ACTION_SEND_START, + MV64XXX_I2C_ACTION_SEND_ADDR_1, + MV64XXX_I2C_ACTION_SEND_ADDR_2, + MV64XXX_I2C_ACTION_SEND_DATA, + MV64XXX_I2C_ACTION_RCV_DATA, + MV64XXX_I2C_ACTION_RCV_DATA_STOP, + MV64XXX_I2C_ACTION_SEND_STOP, +}; + +struct mv64xxx_i2c_data { + int irq; + u32 state; + u32 action; + u32 aborting; + u32 cntl_bits; + void __iomem *reg_base; + u32 reg_base_p; + u32 reg_size; + u32 addr1; + u32 addr2; + u32 bytes_left; + u32 byte_posn; + u32 block; + int rc; + u32 freq_m; + u32 freq_n; + wait_queue_head_t waitq; + spinlock_t lock; + struct i2c_msg *msg; + struct i2c_adapter adapter; +}; + +/* + ***************************************************************************** + * + * Finite State Machine & Interrupt Routines + * + ***************************************************************************** + */ + +/* Reset hardware and initialize FSM */ +static void +mv64xxx_i2c_hw_init(struct mv64xxx_i2c_data *drv_data) +{ + writel(0, drv_data->reg_base + MV64XXX_I2C_REG_SOFT_RESET); + writel((((drv_data->freq_m & 0xf) << 3) | (drv_data->freq_n & 0x7)), + drv_data->reg_base + MV64XXX_I2C_REG_BAUD); + writel(0, drv_data->reg_base + MV64XXX_I2C_REG_SLAVE_ADDR); + writel(0, drv_data->reg_base + MV64XXX_I2C_REG_EXT_SLAVE_ADDR); + writel(MV64XXX_I2C_REG_CONTROL_TWSIEN | MV64XXX_I2C_REG_CONTROL_STOP, + drv_data->reg_base + MV64XXX_I2C_REG_CONTROL); + drv_data->state = MV64XXX_I2C_STATE_IDLE; +} + +static void +mv64xxx_i2c_fsm(struct mv64xxx_i2c_data *drv_data, u32 status) +{ + /* + * If state is idle, then this is likely the remnants of an old + * operation that driver has given up on or the user has killed. + * If so, issue the stop condition and go to idle. + */ + if (drv_data->state == MV64XXX_I2C_STATE_IDLE) { + drv_data->action = MV64XXX_I2C_ACTION_SEND_STOP; + return; + } + + /* The status from the ctlr [mostly] tells us what to do next */ + switch (status) { + /* Start condition interrupt */ + case MV64XXX_I2C_STATUS_MAST_START: /* 0x08 */ + case MV64XXX_I2C_STATUS_MAST_REPEAT_START: /* 0x10 */ + drv_data->action = MV64XXX_I2C_ACTION_SEND_ADDR_1; + drv_data->state = MV64XXX_I2C_STATE_WAITING_FOR_ADDR_1_ACK; + break; + + /* Performing a write */ + case MV64XXX_I2C_STATUS_MAST_WR_ADDR_ACK: /* 0x18 */ + if (drv_data->msg->flags & I2C_M_TEN) { + drv_data->action = MV64XXX_I2C_ACTION_SEND_ADDR_2; + drv_data->state = + MV64XXX_I2C_STATE_WAITING_FOR_ADDR_2_ACK; + break; + } + /* FALLTHRU */ + case MV64XXX_I2C_STATUS_MAST_WR_ADDR_2_ACK: /* 0xd0 */ + case MV64XXX_I2C_STATUS_MAST_WR_ACK: /* 0x28 */ + if ((drv_data->bytes_left == 0) + || (drv_data->aborting + && (drv_data->byte_posn != 0))) { + drv_data->action = MV64XXX_I2C_ACTION_SEND_STOP; + drv_data->state = MV64XXX_I2C_STATE_IDLE; + } else { + drv_data->action = MV64XXX_I2C_ACTION_SEND_DATA; + drv_data->state = + MV64XXX_I2C_STATE_WAITING_FOR_SLAVE_ACK; + drv_data->bytes_left--; + } + break; + + /* Performing a read */ + case MV64XXX_I2C_STATUS_MAST_RD_ADDR_ACK: /* 40 */ + if (drv_data->msg->flags & I2C_M_TEN) { + drv_data->action = MV64XXX_I2C_ACTION_SEND_ADDR_2; + drv_data->state = + MV64XXX_I2C_STATE_WAITING_FOR_ADDR_2_ACK; + break; + } + /* FALLTHRU */ + case MV64XXX_I2C_STATUS_MAST_RD_ADDR_2_ACK: /* 0xe0 */ + if (drv_data->bytes_left == 0) { + drv_data->action = MV64XXX_I2C_ACTION_SEND_STOP; + drv_data->state = MV64XXX_I2C_STATE_IDLE; + break; + } + /* FALLTHRU */ + case MV64XXX_I2C_STATUS_MAST_RD_DATA_ACK: /* 0x50 */ + if (status != MV64XXX_I2C_STATUS_MAST_RD_DATA_ACK) + drv_data->action = MV64XXX_I2C_ACTION_CONTINUE; + else { + drv_data->action = MV64XXX_I2C_ACTION_RCV_DATA; + drv_data->bytes_left--; + } + drv_data->state = MV64XXX_I2C_STATE_WAITING_FOR_SLAVE_DATA; + + if ((drv_data->bytes_left == 1) || drv_data->aborting) + drv_data->cntl_bits &= ~MV64XXX_I2C_REG_CONTROL_ACK; + break; + + case MV64XXX_I2C_STATUS_MAST_RD_DATA_NO_ACK: /* 0x58 */ + drv_data->action = MV64XXX_I2C_ACTION_RCV_DATA_STOP; + drv_data->state = MV64XXX_I2C_STATE_IDLE; + break; + + case MV64XXX_I2C_STATUS_MAST_WR_ADDR_NO_ACK: /* 0x20 */ + case MV64XXX_I2C_STATUS_MAST_WR_NO_ACK: /* 30 */ + case MV64XXX_I2C_STATUS_MAST_RD_ADDR_NO_ACK: /* 48 */ + /* Doesn't seem to be a device at other end */ + drv_data->action = MV64XXX_I2C_ACTION_SEND_STOP; + drv_data->state = MV64XXX_I2C_STATE_IDLE; + drv_data->rc = -ENODEV; + break; + + default: + dev_err(&drv_data->adapter.dev, + "mv64xxx_i2c_fsm: Ctlr Error -- state: 0x%x, " + "status: 0x%x, addr: 0x%x, flags: 0x%x\n", + drv_data->state, status, drv_data->msg->addr, + drv_data->msg->flags); + drv_data->action = MV64XXX_I2C_ACTION_SEND_STOP; + mv64xxx_i2c_hw_init(drv_data); + drv_data->rc = -EIO; + } +} + +static void +mv64xxx_i2c_do_action(struct mv64xxx_i2c_data *drv_data) +{ + switch(drv_data->action) { + case MV64XXX_I2C_ACTION_CONTINUE: + writel(drv_data->cntl_bits, + drv_data->reg_base + MV64XXX_I2C_REG_CONTROL); + break; + + case MV64XXX_I2C_ACTION_SEND_START: + writel(drv_data->cntl_bits | MV64XXX_I2C_REG_CONTROL_START, + drv_data->reg_base + MV64XXX_I2C_REG_CONTROL); + break; + + case MV64XXX_I2C_ACTION_SEND_ADDR_1: + writel(drv_data->addr1, + drv_data->reg_base + MV64XXX_I2C_REG_DATA); + writel(drv_data->cntl_bits, + drv_data->reg_base + MV64XXX_I2C_REG_CONTROL); + break; + + case MV64XXX_I2C_ACTION_SEND_ADDR_2: + writel(drv_data->addr2, + drv_data->reg_base + MV64XXX_I2C_REG_DATA); + writel(drv_data->cntl_bits, + drv_data->reg_base + MV64XXX_I2C_REG_CONTROL); + break; + + case MV64XXX_I2C_ACTION_SEND_DATA: + writel(drv_data->msg->buf[drv_data->byte_posn++], + drv_data->reg_base + MV64XXX_I2C_REG_DATA); + writel(drv_data->cntl_bits, + drv_data->reg_base + MV64XXX_I2C_REG_CONTROL); + break; + + case MV64XXX_I2C_ACTION_RCV_DATA: + drv_data->msg->buf[drv_data->byte_posn++] = + readl(drv_data->reg_base + MV64XXX_I2C_REG_DATA); + writel(drv_data->cntl_bits, + drv_data->reg_base + MV64XXX_I2C_REG_CONTROL); + break; + + case MV64XXX_I2C_ACTION_RCV_DATA_STOP: + drv_data->msg->buf[drv_data->byte_posn++] = + readl(drv_data->reg_base + MV64XXX_I2C_REG_DATA); + drv_data->cntl_bits &= ~MV64XXX_I2C_REG_CONTROL_INTEN; + writel(drv_data->cntl_bits | MV64XXX_I2C_REG_CONTROL_STOP, + drv_data->reg_base + MV64XXX_I2C_REG_CONTROL); + drv_data->block = 0; + wake_up_interruptible(&drv_data->waitq); + break; + + case MV64XXX_I2C_ACTION_INVALID: + default: + dev_err(&drv_data->adapter.dev, + "mv64xxx_i2c_do_action: Invalid action: %d\n", + drv_data->action); + drv_data->rc = -EIO; + /* FALLTHRU */ + case MV64XXX_I2C_ACTION_SEND_STOP: + drv_data->cntl_bits &= ~MV64XXX_I2C_REG_CONTROL_INTEN; + writel(drv_data->cntl_bits | MV64XXX_I2C_REG_CONTROL_STOP, + drv_data->reg_base + MV64XXX_I2C_REG_CONTROL); + drv_data->block = 0; + wake_up_interruptible(&drv_data->waitq); + break; + } +} + +static int +mv64xxx_i2c_intr(int irq, void *dev_id) +{ + struct mv64xxx_i2c_data *drv_data = dev_id; + unsigned long flags; + u32 status; + int rc = IRQ_NONE; + + spin_lock_irqsave(&drv_data->lock, flags); + while (readl(drv_data->reg_base + MV64XXX_I2C_REG_CONTROL) & + MV64XXX_I2C_REG_CONTROL_IFLG) { + status = readl(drv_data->reg_base + MV64XXX_I2C_REG_STATUS); + mv64xxx_i2c_fsm(drv_data, status); + mv64xxx_i2c_do_action(drv_data); + rc = IRQ_HANDLED; + } + spin_unlock_irqrestore(&drv_data->lock, flags); + + return rc; +} + +/* + ***************************************************************************** + * + * I2C Msg Execution Routines + * + ***************************************************************************** + */ +static void +mv64xxx_i2c_prepare_for_io(struct mv64xxx_i2c_data *drv_data, + struct i2c_msg *msg) +{ + u32 dir = 0; + + drv_data->msg = msg; + drv_data->byte_posn = 0; + drv_data->bytes_left = msg->len; + drv_data->aborting = 0; + drv_data->rc = 0; + drv_data->cntl_bits = MV64XXX_I2C_REG_CONTROL_ACK | + MV64XXX_I2C_REG_CONTROL_INTEN | MV64XXX_I2C_REG_CONTROL_TWSIEN; + + if (msg->flags & I2C_M_RD) + dir = 1; + + if (msg->flags & I2C_M_REV_DIR_ADDR) + dir ^= 1; + + if (msg->flags & I2C_M_TEN) { + drv_data->addr1 = 0xf0 | (((u32)msg->addr & 0x300) >> 7) | dir; + drv_data->addr2 = (u32)msg->addr & 0xff; + } else { + drv_data->addr1 = ((u32)msg->addr & 0x7f) << 1 | dir; + drv_data->addr2 = 0; + } +} + +static void +mv64xxx_i2c_wait_for_completion(struct mv64xxx_i2c_data *drv_data) +{ + long time_left; + unsigned long flags; + char abort = 0; + + time_left = wait_event_interruptible_timeout(drv_data->waitq, + !drv_data->block, msecs_to_jiffies(drv_data->adapter.timeout)); + + spin_lock_irqsave(&drv_data->lock, flags); + if (!time_left) { /* Timed out */ + drv_data->rc = -ETIMEDOUT; + abort = 1; + } else if (time_left < 0) { /* Interrupted/Error */ + drv_data->rc = time_left; /* errno value */ + abort = 1; + } + + if (abort && drv_data->block) { + drv_data->aborting = 1; + spin_unlock_irqrestore(&drv_data->lock, flags); + + time_left = wait_event_timeout(drv_data->waitq, + !drv_data->block, + msecs_to_jiffies(drv_data->adapter.timeout)); + + if ((time_left <= 0) && drv_data->block) { + drv_data->state = MV64XXX_I2C_STATE_IDLE; + dev_err(&drv_data->adapter.dev, + "mv64xxx: I2C bus locked, block: %d, " + "time_left: %d\n", drv_data->block, + (int)time_left); + mv64xxx_i2c_hw_init(drv_data); + } + } else + spin_unlock_irqrestore(&drv_data->lock, flags); +} + +static int +mv64xxx_i2c_execute_msg(struct mv64xxx_i2c_data *drv_data, struct i2c_msg *msg) +{ + unsigned long flags; + + spin_lock_irqsave(&drv_data->lock, flags); + mv64xxx_i2c_prepare_for_io(drv_data, msg); + + if (unlikely(msg->flags & I2C_M_NOSTART)) { /* Skip start/addr phases */ + if (drv_data->msg->flags & I2C_M_RD) { + /* No action to do, wait for slave to send a byte */ + drv_data->action = MV64XXX_I2C_ACTION_CONTINUE; + drv_data->state = + MV64XXX_I2C_STATE_WAITING_FOR_SLAVE_DATA; + } else { + drv_data->action = MV64XXX_I2C_ACTION_SEND_DATA; + drv_data->state = + MV64XXX_I2C_STATE_WAITING_FOR_SLAVE_ACK; + drv_data->bytes_left--; + } + } else { + drv_data->action = MV64XXX_I2C_ACTION_SEND_START; + drv_data->state = MV64XXX_I2C_STATE_WAITING_FOR_START_COND; + } + + drv_data->block = 1; + mv64xxx_i2c_do_action(drv_data); + spin_unlock_irqrestore(&drv_data->lock, flags); + + mv64xxx_i2c_wait_for_completion(drv_data); + return drv_data->rc; +} + +/* + ***************************************************************************** + * + * I2C Core Support Routines (Interface to higher level I2C code) + * + ***************************************************************************** + */ +static u32 +mv64xxx_i2c_functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_10BIT_ADDR | I2C_FUNC_SMBUS_EMUL; +} + +static int +mv64xxx_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) +{ + struct mv64xxx_i2c_data *drv_data = i2c_get_adapdata(adap); + int i, rc; + + for (i=0; i<num; i++) + if ((rc = mv64xxx_i2c_execute_msg(drv_data, &msgs[i])) < 0) + return rc; + + return num; +} + +static const struct i2c_algorithm mv64xxx_i2c_algo = { + .master_xfer = mv64xxx_i2c_xfer, + .functionality = mv64xxx_i2c_functionality, +}; + +/* + ***************************************************************************** + * + * Driver Interface & Early Init Routines + * + ***************************************************************************** + */ +static int __devinit +mv64xxx_i2c_map_regs(struct platform_device *pd, + struct mv64xxx_i2c_data *drv_data) +{ + int size; + struct resource *r = platform_get_resource(pd, IORESOURCE_MEM, 0); + + if (!r) + return -ENODEV; + + size = r->end - r->start + 1; + + if (!request_mem_region(r->start, size, drv_data->adapter.name)) + return -EBUSY; + + drv_data->reg_base = ioremap(r->start, size); + drv_data->reg_base_p = r->start; + drv_data->reg_size = size; + + return 0; +} + +static void __devexit +mv64xxx_i2c_unmap_regs(struct mv64xxx_i2c_data *drv_data) +{ + if (drv_data->reg_base) { + iounmap(drv_data->reg_base); + release_mem_region(drv_data->reg_base_p, drv_data->reg_size); + } + + drv_data->reg_base = NULL; + drv_data->reg_base_p = 0; +} + +static int __devinit +mv64xxx_i2c_probe(struct platform_device *pd) +{ + struct mv64xxx_i2c_data *drv_data; + struct mv64xxx_i2c_pdata *pdata = pd->dev.platform_data; + int rc; + + if ((pd->id != 0) || !pdata) + return -ENODEV; + + drv_data = kzalloc(sizeof(struct mv64xxx_i2c_data), GFP_KERNEL); + if (!drv_data) + return -ENOMEM; + + if (mv64xxx_i2c_map_regs(pd, drv_data)) { + rc = -ENODEV; + goto exit_kfree; + } + + strlcpy(drv_data->adapter.name, MV64XXX_I2C_CTLR_NAME " adapter", + sizeof(drv_data->adapter.name)); + + init_waitqueue_head(&drv_data->waitq); + spin_lock_init(&drv_data->lock); + + drv_data->freq_m = pdata->freq_m; + drv_data->freq_n = pdata->freq_n; + drv_data->irq = platform_get_irq(pd, 0); + if (drv_data->irq < 0) { + rc = -ENXIO; + goto exit_unmap_regs; + } + drv_data->adapter.dev.parent = &pd->dev; + drv_data->adapter.id = I2C_HW_MV64XXX; + drv_data->adapter.algo = &mv64xxx_i2c_algo; + drv_data->adapter.owner = THIS_MODULE; + drv_data->adapter.class = I2C_CLASS_HWMON | I2C_CLASS_SPD; + drv_data->adapter.timeout = pdata->timeout; + drv_data->adapter.nr = pd->id; + platform_set_drvdata(pd, drv_data); + i2c_set_adapdata(&drv_data->adapter, drv_data); + + mv64xxx_i2c_hw_init(drv_data); + + if (request_irq(drv_data->irq, mv64xxx_i2c_intr, 0, + MV64XXX_I2C_CTLR_NAME, drv_data)) { + dev_err(&drv_data->adapter.dev, + "mv64xxx: Can't register intr handler irq: %d\n", + drv_data->irq); + rc = -EINVAL; + goto exit_unmap_regs; + } else if ((rc = i2c_add_numbered_adapter(&drv_data->adapter)) != 0) { + dev_err(&drv_data->adapter.dev, + "mv64xxx: Can't add i2c adapter, rc: %d\n", -rc); + goto exit_free_irq; + } + + return 0; + + exit_free_irq: + free_irq(drv_data->irq, drv_data); + exit_unmap_regs: + mv64xxx_i2c_unmap_regs(drv_data); + exit_kfree: + kfree(drv_data); + return rc; +} + +static int __devexit +mv64xxx_i2c_remove(struct platform_device *dev) +{ + struct mv64xxx_i2c_data *drv_data = platform_get_drvdata(dev); + int rc; + + rc = i2c_del_adapter(&drv_data->adapter); + free_irq(drv_data->irq, drv_data); + mv64xxx_i2c_unmap_regs(drv_data); + kfree(drv_data); + + return rc; +} + +static struct platform_driver mv64xxx_i2c_driver = { + .probe = mv64xxx_i2c_probe, + .remove = mv64xxx_i2c_remove, + .driver = { + .owner = THIS_MODULE, + .name = MV64XXX_I2C_CTLR_NAME, + }, +}; + +static int __init +mv64xxx_i2c_init(void) +{ + return platform_driver_register(&mv64xxx_i2c_driver); +} + +static void __exit +mv64xxx_i2c_exit(void) +{ + platform_driver_unregister(&mv64xxx_i2c_driver); +} + +module_init(mv64xxx_i2c_init); +module_exit(mv64xxx_i2c_exit); + +MODULE_AUTHOR("Mark A. Greer <mgreer@mvista.com>"); +MODULE_DESCRIPTION("Marvell mv64xxx host bridge i2c ctlr driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/busses/i2c-nforce2-s4985.c b/drivers/i2c/busses/i2c-nforce2-s4985.c new file mode 100644 index 0000000..29015eb --- /dev/null +++ b/drivers/i2c/busses/i2c-nforce2-s4985.c @@ -0,0 +1,257 @@ +/* + * i2c-nforce2-s4985.c - i2c-nforce2 extras for the Tyan S4985 motherboard + * + * Copyright (C) 2008 Jean Delvare <khali@linux-fr.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * We select the channels by sending commands to the Philips + * PCA9556 chip at I2C address 0x18. The main adapter is used for + * the non-multiplexed part of the bus, and 4 virtual adapters + * are defined for the multiplexed addresses: 0x50-0x53 (memory + * module EEPROM) located on channels 1-4. We define one virtual + * adapter per CPU, which corresponds to one multiplexed channel: + * CPU0: virtual adapter 1, channel 1 + * CPU1: virtual adapter 2, channel 2 + * CPU2: virtual adapter 3, channel 3 + * CPU3: virtual adapter 4, channel 4 + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/mutex.h> + +extern struct i2c_adapter *nforce2_smbus; + +static struct i2c_adapter *s4985_adapter; +static struct i2c_algorithm *s4985_algo; + +/* Wrapper access functions for multiplexed SMBus */ +static DEFINE_MUTEX(nforce2_lock); + +static s32 nforce2_access_virt0(struct i2c_adapter *adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, + union i2c_smbus_data *data) +{ + int error; + + /* We exclude the multiplexed addresses */ + if ((addr & 0xfc) == 0x50 || (addr & 0xfc) == 0x30 + || addr == 0x18) + return -ENXIO; + + mutex_lock(&nforce2_lock); + error = nforce2_smbus->algo->smbus_xfer(adap, addr, flags, read_write, + command, size, data); + mutex_unlock(&nforce2_lock); + + return error; +} + +/* We remember the last used channels combination so as to only switch + channels when it is really needed. This greatly reduces the SMBus + overhead, but also assumes that nobody will be writing to the PCA9556 + in our back. */ +static u8 last_channels; + +static inline s32 nforce2_access_channel(struct i2c_adapter *adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, + union i2c_smbus_data *data, + u8 channels) +{ + int error; + + /* We exclude the non-multiplexed addresses */ + if ((addr & 0xfc) != 0x50 && (addr & 0xfc) != 0x30) + return -ENXIO; + + mutex_lock(&nforce2_lock); + if (last_channels != channels) { + union i2c_smbus_data mplxdata; + mplxdata.byte = channels; + + error = nforce2_smbus->algo->smbus_xfer(adap, 0x18, 0, + I2C_SMBUS_WRITE, 0x01, + I2C_SMBUS_BYTE_DATA, + &mplxdata); + if (error) + goto UNLOCK; + last_channels = channels; + } + error = nforce2_smbus->algo->smbus_xfer(adap, addr, flags, read_write, + command, size, data); + +UNLOCK: + mutex_unlock(&nforce2_lock); + return error; +} + +static s32 nforce2_access_virt1(struct i2c_adapter *adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, + union i2c_smbus_data *data) +{ + /* CPU0: channel 1 enabled */ + return nforce2_access_channel(adap, addr, flags, read_write, command, + size, data, 0x02); +} + +static s32 nforce2_access_virt2(struct i2c_adapter *adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, + union i2c_smbus_data *data) +{ + /* CPU1: channel 2 enabled */ + return nforce2_access_channel(adap, addr, flags, read_write, command, + size, data, 0x04); +} + +static s32 nforce2_access_virt3(struct i2c_adapter *adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, + union i2c_smbus_data *data) +{ + /* CPU2: channel 3 enabled */ + return nforce2_access_channel(adap, addr, flags, read_write, command, + size, data, 0x08); +} + +static s32 nforce2_access_virt4(struct i2c_adapter *adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, + union i2c_smbus_data *data) +{ + /* CPU3: channel 4 enabled */ + return nforce2_access_channel(adap, addr, flags, read_write, command, + size, data, 0x10); +} + +static int __init nforce2_s4985_init(void) +{ + int i, error; + union i2c_smbus_data ioconfig; + + if (!nforce2_smbus) + return -ENODEV; + + /* Configure the PCA9556 multiplexer */ + ioconfig.byte = 0x00; /* All I/O to output mode */ + error = i2c_smbus_xfer(nforce2_smbus, 0x18, 0, I2C_SMBUS_WRITE, 0x03, + I2C_SMBUS_BYTE_DATA, &ioconfig); + if (error) { + dev_err(&nforce2_smbus->dev, "PCA9556 configuration failed\n"); + error = -EIO; + goto ERROR0; + } + + /* Unregister physical bus */ + error = i2c_del_adapter(nforce2_smbus); + if (error) { + dev_err(&nforce2_smbus->dev, "Physical bus removal failed\n"); + goto ERROR0; + } + + printk(KERN_INFO "Enabling SMBus multiplexing for Tyan S4985\n"); + /* Define the 5 virtual adapters and algorithms structures */ + s4985_adapter = kzalloc(5 * sizeof(struct i2c_adapter), GFP_KERNEL); + if (!s4985_adapter) { + error = -ENOMEM; + goto ERROR1; + } + s4985_algo = kzalloc(5 * sizeof(struct i2c_algorithm), GFP_KERNEL); + if (!s4985_algo) { + error = -ENOMEM; + goto ERROR2; + } + + /* Fill in the new structures */ + s4985_algo[0] = *(nforce2_smbus->algo); + s4985_algo[0].smbus_xfer = nforce2_access_virt0; + s4985_adapter[0] = *nforce2_smbus; + s4985_adapter[0].algo = s4985_algo; + s4985_adapter[0].dev.parent = nforce2_smbus->dev.parent; + for (i = 1; i < 5; i++) { + s4985_algo[i] = *(nforce2_smbus->algo); + s4985_adapter[i] = *nforce2_smbus; + snprintf(s4985_adapter[i].name, sizeof(s4985_adapter[i].name), + "SMBus nForce2 adapter (CPU%d)", i - 1); + s4985_adapter[i].algo = s4985_algo + i; + s4985_adapter[i].dev.parent = nforce2_smbus->dev.parent; + } + s4985_algo[1].smbus_xfer = nforce2_access_virt1; + s4985_algo[2].smbus_xfer = nforce2_access_virt2; + s4985_algo[3].smbus_xfer = nforce2_access_virt3; + s4985_algo[4].smbus_xfer = nforce2_access_virt4; + + /* Register virtual adapters */ + for (i = 0; i < 5; i++) { + error = i2c_add_adapter(s4985_adapter + i); + if (error) { + printk(KERN_ERR "i2c-nforce2-s4985: " + "Virtual adapter %d registration " + "failed, module not inserted\n", i); + for (i--; i >= 0; i--) + i2c_del_adapter(s4985_adapter + i); + goto ERROR3; + } + } + + return 0; + +ERROR3: + kfree(s4985_algo); + s4985_algo = NULL; +ERROR2: + kfree(s4985_adapter); + s4985_adapter = NULL; +ERROR1: + /* Restore physical bus */ + i2c_add_adapter(nforce2_smbus); +ERROR0: + return error; +} + +static void __exit nforce2_s4985_exit(void) +{ + if (s4985_adapter) { + int i; + + for (i = 0; i < 5; i++) + i2c_del_adapter(s4985_adapter+i); + kfree(s4985_adapter); + s4985_adapter = NULL; + } + kfree(s4985_algo); + s4985_algo = NULL; + + /* Restore physical bus */ + if (i2c_add_adapter(nforce2_smbus)) + printk(KERN_ERR "i2c-nforce2-s4985: " + "Physical bus restoration failed\n"); +} + +MODULE_AUTHOR("Jean Delvare <khali@linux-fr.org>"); +MODULE_DESCRIPTION("S4985 SMBus multiplexing"); +MODULE_LICENSE("GPL"); + +module_init(nforce2_s4985_init); +module_exit(nforce2_s4985_exit); diff --git a/drivers/i2c/busses/i2c-nforce2.c b/drivers/i2c/busses/i2c-nforce2.c new file mode 100644 index 0000000..3b19bc4 --- /dev/null +++ b/drivers/i2c/busses/i2c-nforce2.c @@ -0,0 +1,462 @@ +/* + SMBus driver for nVidia nForce2 MCP + + Added nForce3 Pro 150 Thomas Leibold <thomas@plx.com>, + Ported to 2.5 Patrick Dreker <patrick@dreker.de>, + Copyright (c) 2003 Hans-Frieder Vogt <hfvogt@arcor.de>, + Based on + SMBus 2.0 driver for AMD-8111 IO-Hub + Copyright (c) 2002 Vojtech Pavlik + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* + SUPPORTED DEVICES PCI ID + nForce2 MCP 0064 + nForce2 Ultra 400 MCP 0084 + nForce3 Pro150 MCP 00D4 + nForce3 250Gb MCP 00E4 + nForce4 MCP 0052 + nForce4 MCP-04 0034 + nForce4 MCP51 0264 + nForce4 MCP55 0368 + nForce MCP61 03EB + nForce MCP65 0446 + + This driver supports the 2 SMBuses that are included in the MCP of the + nForce2/3/4/5xx chipsets. +*/ + +/* Note: we assume there can only be one nForce2, with two SMBus interfaces */ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/kernel.h> +#include <linux/stddef.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/dmi.h> +#include <linux/acpi.h> +#include <asm/io.h> + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR ("Hans-Frieder Vogt <hfvogt@gmx.net>"); +MODULE_DESCRIPTION("nForce2/3/4/5xx SMBus driver"); + + +struct nforce2_smbus { + struct i2c_adapter adapter; + int base; + int size; + int blockops; + int can_abort; +}; + + +/* + * nVidia nForce2 SMBus control register definitions + * (Newer incarnations use standard BARs 4 and 5 instead) + */ +#define NFORCE_PCI_SMB1 0x50 +#define NFORCE_PCI_SMB2 0x54 + + +/* + * ACPI 2.0 chapter 13 SMBus 2.0 EC register model + */ +#define NVIDIA_SMB_PRTCL (smbus->base + 0x00) /* protocol, PEC */ +#define NVIDIA_SMB_STS (smbus->base + 0x01) /* status */ +#define NVIDIA_SMB_ADDR (smbus->base + 0x02) /* address */ +#define NVIDIA_SMB_CMD (smbus->base + 0x03) /* command */ +#define NVIDIA_SMB_DATA (smbus->base + 0x04) /* 32 data registers */ +#define NVIDIA_SMB_BCNT (smbus->base + 0x24) /* number of data + bytes */ +#define NVIDIA_SMB_STATUS_ABRT (smbus->base + 0x3c) /* register used to + check the status of + the abort command */ +#define NVIDIA_SMB_CTRL (smbus->base + 0x3e) /* control register */ + +#define NVIDIA_SMB_STATUS_ABRT_STS 0x01 /* Bit to notify that + abort succeeded */ +#define NVIDIA_SMB_CTRL_ABORT 0x20 +#define NVIDIA_SMB_STS_DONE 0x80 +#define NVIDIA_SMB_STS_ALRM 0x40 +#define NVIDIA_SMB_STS_RES 0x20 +#define NVIDIA_SMB_STS_STATUS 0x1f + +#define NVIDIA_SMB_PRTCL_WRITE 0x00 +#define NVIDIA_SMB_PRTCL_READ 0x01 +#define NVIDIA_SMB_PRTCL_QUICK 0x02 +#define NVIDIA_SMB_PRTCL_BYTE 0x04 +#define NVIDIA_SMB_PRTCL_BYTE_DATA 0x06 +#define NVIDIA_SMB_PRTCL_WORD_DATA 0x08 +#define NVIDIA_SMB_PRTCL_BLOCK_DATA 0x0a +#define NVIDIA_SMB_PRTCL_PEC 0x80 + +/* Misc definitions */ +#define MAX_TIMEOUT 100 + +/* We disable the second SMBus channel on these boards */ +static struct dmi_system_id __devinitdata nforce2_dmi_blacklist2[] = { + { + .ident = "DFI Lanparty NF4 Expert", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "DFI Corp,LTD"), + DMI_MATCH(DMI_BOARD_NAME, "LP UT NF4 Expert"), + }, + }, + { } +}; + +static struct pci_driver nforce2_driver; + +/* For multiplexing support, we need a global reference to the 1st + SMBus channel */ +#if defined CONFIG_I2C_NFORCE2_S4985 || defined CONFIG_I2C_NFORCE2_S4985_MODULE +struct i2c_adapter *nforce2_smbus; +EXPORT_SYMBOL_GPL(nforce2_smbus); + +static void nforce2_set_reference(struct i2c_adapter *adap) +{ + nforce2_smbus = adap; +} +#else +static inline void nforce2_set_reference(struct i2c_adapter *adap) { } +#endif + +static void nforce2_abort(struct i2c_adapter *adap) +{ + struct nforce2_smbus *smbus = adap->algo_data; + int timeout = 0; + unsigned char temp; + + dev_dbg(&adap->dev, "Aborting current transaction\n"); + + outb_p(NVIDIA_SMB_CTRL_ABORT, NVIDIA_SMB_CTRL); + do { + msleep(1); + temp = inb_p(NVIDIA_SMB_STATUS_ABRT); + } while (!(temp & NVIDIA_SMB_STATUS_ABRT_STS) && + (timeout++ < MAX_TIMEOUT)); + if (!(temp & NVIDIA_SMB_STATUS_ABRT_STS)) + dev_err(&adap->dev, "Can't reset the smbus\n"); + outb_p(NVIDIA_SMB_STATUS_ABRT_STS, NVIDIA_SMB_STATUS_ABRT); +} + +static int nforce2_check_status(struct i2c_adapter *adap) +{ + struct nforce2_smbus *smbus = adap->algo_data; + int timeout = 0; + unsigned char temp; + + do { + msleep(1); + temp = inb_p(NVIDIA_SMB_STS); + } while ((!temp) && (timeout++ < MAX_TIMEOUT)); + + if (timeout >= MAX_TIMEOUT) { + dev_dbg(&adap->dev, "SMBus Timeout!\n"); + if (smbus->can_abort) + nforce2_abort(adap); + return -ETIMEDOUT; + } + if (!(temp & NVIDIA_SMB_STS_DONE) || (temp & NVIDIA_SMB_STS_STATUS)) { + dev_dbg(&adap->dev, "Transaction failed (0x%02x)!\n", temp); + return -EIO; + } + return 0; +} + +/* Return negative errno on error */ +static s32 nforce2_access(struct i2c_adapter * adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, union i2c_smbus_data * data) +{ + struct nforce2_smbus *smbus = adap->algo_data; + unsigned char protocol, pec; + u8 len; + int i, status; + + protocol = (read_write == I2C_SMBUS_READ) ? NVIDIA_SMB_PRTCL_READ : + NVIDIA_SMB_PRTCL_WRITE; + pec = (flags & I2C_CLIENT_PEC) ? NVIDIA_SMB_PRTCL_PEC : 0; + + switch (size) { + + case I2C_SMBUS_QUICK: + protocol |= NVIDIA_SMB_PRTCL_QUICK; + read_write = I2C_SMBUS_WRITE; + break; + + case I2C_SMBUS_BYTE: + if (read_write == I2C_SMBUS_WRITE) + outb_p(command, NVIDIA_SMB_CMD); + protocol |= NVIDIA_SMB_PRTCL_BYTE; + break; + + case I2C_SMBUS_BYTE_DATA: + outb_p(command, NVIDIA_SMB_CMD); + if (read_write == I2C_SMBUS_WRITE) + outb_p(data->byte, NVIDIA_SMB_DATA); + protocol |= NVIDIA_SMB_PRTCL_BYTE_DATA; + break; + + case I2C_SMBUS_WORD_DATA: + outb_p(command, NVIDIA_SMB_CMD); + if (read_write == I2C_SMBUS_WRITE) { + outb_p(data->word, NVIDIA_SMB_DATA); + outb_p(data->word >> 8, NVIDIA_SMB_DATA+1); + } + protocol |= NVIDIA_SMB_PRTCL_WORD_DATA | pec; + break; + + case I2C_SMBUS_BLOCK_DATA: + outb_p(command, NVIDIA_SMB_CMD); + if (read_write == I2C_SMBUS_WRITE) { + len = data->block[0]; + if ((len == 0) || (len > I2C_SMBUS_BLOCK_MAX)) { + dev_err(&adap->dev, + "Transaction failed " + "(requested block size: %d)\n", + len); + return -EINVAL; + } + outb_p(len, NVIDIA_SMB_BCNT); + for (i = 0; i < I2C_SMBUS_BLOCK_MAX; i++) + outb_p(data->block[i + 1], + NVIDIA_SMB_DATA+i); + } + protocol |= NVIDIA_SMB_PRTCL_BLOCK_DATA | pec; + break; + + default: + dev_err(&adap->dev, "Unsupported transaction %d\n", size); + return -EOPNOTSUPP; + } + + outb_p((addr & 0x7f) << 1, NVIDIA_SMB_ADDR); + outb_p(protocol, NVIDIA_SMB_PRTCL); + + status = nforce2_check_status(adap); + if (status) + return status; + + if (read_write == I2C_SMBUS_WRITE) + return 0; + + switch (size) { + + case I2C_SMBUS_BYTE: + case I2C_SMBUS_BYTE_DATA: + data->byte = inb_p(NVIDIA_SMB_DATA); + break; + + case I2C_SMBUS_WORD_DATA: + data->word = inb_p(NVIDIA_SMB_DATA) | (inb_p(NVIDIA_SMB_DATA+1) << 8); + break; + + case I2C_SMBUS_BLOCK_DATA: + len = inb_p(NVIDIA_SMB_BCNT); + if ((len <= 0) || (len > I2C_SMBUS_BLOCK_MAX)) { + dev_err(&adap->dev, "Transaction failed " + "(received block size: 0x%02x)\n", + len); + return -EPROTO; + } + for (i = 0; i < len; i++) + data->block[i+1] = inb_p(NVIDIA_SMB_DATA + i); + data->block[0] = len; + break; + } + + return 0; +} + + +static u32 nforce2_func(struct i2c_adapter *adapter) +{ + /* other functionality might be possible, but is not tested */ + return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_PEC | + (((struct nforce2_smbus*)adapter->algo_data)->blockops ? + I2C_FUNC_SMBUS_BLOCK_DATA : 0); +} + +static struct i2c_algorithm smbus_algorithm = { + .smbus_xfer = nforce2_access, + .functionality = nforce2_func, +}; + + +static struct pci_device_id nforce2_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE2_SMBUS) }, + { PCI_DEVICE(PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE2S_SMBUS) }, + { PCI_DEVICE(PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE3_SMBUS) }, + { PCI_DEVICE(PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE3S_SMBUS) }, + { PCI_DEVICE(PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE4_SMBUS) }, + { PCI_DEVICE(PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MCP04_SMBUS) }, + { PCI_DEVICE(PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MCP51_SMBUS) }, + { PCI_DEVICE(PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MCP55_SMBUS) }, + { PCI_DEVICE(PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MCP61_SMBUS) }, + { PCI_DEVICE(PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MCP65_SMBUS) }, + { 0 } +}; + +MODULE_DEVICE_TABLE (pci, nforce2_ids); + + +static int __devinit nforce2_probe_smb (struct pci_dev *dev, int bar, + int alt_reg, struct nforce2_smbus *smbus, const char *name) +{ + int error; + + smbus->base = pci_resource_start(dev, bar); + if (smbus->base) { + smbus->size = pci_resource_len(dev, bar); + } else { + /* Older incarnations of the device used non-standard BARs */ + u16 iobase; + + if (pci_read_config_word(dev, alt_reg, &iobase) + != PCIBIOS_SUCCESSFUL) { + dev_err(&dev->dev, "Error reading PCI config for %s\n", + name); + return -EIO; + } + + smbus->base = iobase & PCI_BASE_ADDRESS_IO_MASK; + smbus->size = 64; + } + + error = acpi_check_region(smbus->base, smbus->size, + nforce2_driver.name); + if (error) + return -1; + + if (!request_region(smbus->base, smbus->size, nforce2_driver.name)) { + dev_err(&smbus->adapter.dev, "Error requesting region %02x .. %02X for %s\n", + smbus->base, smbus->base+smbus->size-1, name); + return -EBUSY; + } + smbus->adapter.owner = THIS_MODULE; + smbus->adapter.id = I2C_HW_SMBUS_NFORCE2; + smbus->adapter.class = I2C_CLASS_HWMON | I2C_CLASS_SPD; + smbus->adapter.algo = &smbus_algorithm; + smbus->adapter.algo_data = smbus; + smbus->adapter.dev.parent = &dev->dev; + snprintf(smbus->adapter.name, sizeof(smbus->adapter.name), + "SMBus nForce2 adapter at %04x", smbus->base); + + error = i2c_add_adapter(&smbus->adapter); + if (error) { + dev_err(&smbus->adapter.dev, "Failed to register adapter.\n"); + release_region(smbus->base, smbus->size); + return error; + } + dev_info(&smbus->adapter.dev, "nForce2 SMBus adapter at %#x\n", smbus->base); + return 0; +} + + +static int __devinit nforce2_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + struct nforce2_smbus *smbuses; + int res1, res2; + + /* we support 2 SMBus adapters */ + if (!(smbuses = kzalloc(2*sizeof(struct nforce2_smbus), GFP_KERNEL))) + return -ENOMEM; + pci_set_drvdata(dev, smbuses); + + switch(dev->device) { + case PCI_DEVICE_ID_NVIDIA_NFORCE2_SMBUS: + case PCI_DEVICE_ID_NVIDIA_NFORCE_MCP51_SMBUS: + case PCI_DEVICE_ID_NVIDIA_NFORCE_MCP55_SMBUS: + smbuses[0].blockops = 1; + smbuses[1].blockops = 1; + smbuses[0].can_abort = 1; + smbuses[1].can_abort = 1; + } + + /* SMBus adapter 1 */ + res1 = nforce2_probe_smb(dev, 4, NFORCE_PCI_SMB1, &smbuses[0], "SMB1"); + if (res1 < 0) { + dev_err(&dev->dev, "Error probing SMB1.\n"); + smbuses[0].base = 0; /* to have a check value */ + } + /* SMBus adapter 2 */ + if (dmi_check_system(nforce2_dmi_blacklist2)) { + dev_err(&dev->dev, "Disabling SMB2 for safety reasons.\n"); + res2 = -EPERM; + smbuses[1].base = 0; + } else { + res2 = nforce2_probe_smb(dev, 5, NFORCE_PCI_SMB2, &smbuses[1], + "SMB2"); + if (res2 < 0) { + dev_err(&dev->dev, "Error probing SMB2.\n"); + smbuses[1].base = 0; /* to have a check value */ + } + } + if ((res1 < 0) && (res2 < 0)) { + /* we did not find even one of the SMBuses, so we give up */ + kfree(smbuses); + return -ENODEV; + } + + nforce2_set_reference(&smbuses[0].adapter); + return 0; +} + + +static void __devexit nforce2_remove(struct pci_dev *dev) +{ + struct nforce2_smbus *smbuses = (void*) pci_get_drvdata(dev); + + nforce2_set_reference(NULL); + if (smbuses[0].base) { + i2c_del_adapter(&smbuses[0].adapter); + release_region(smbuses[0].base, smbuses[0].size); + } + if (smbuses[1].base) { + i2c_del_adapter(&smbuses[1].adapter); + release_region(smbuses[1].base, smbuses[1].size); + } + kfree(smbuses); +} + +static struct pci_driver nforce2_driver = { + .name = "nForce2_smbus", + .id_table = nforce2_ids, + .probe = nforce2_probe, + .remove = __devexit_p(nforce2_remove), +}; + +static int __init nforce2_init(void) +{ + return pci_register_driver(&nforce2_driver); +} + +static void __exit nforce2_exit(void) +{ + pci_unregister_driver(&nforce2_driver); +} + +module_init(nforce2_init); +module_exit(nforce2_exit); + diff --git a/drivers/i2c/busses/i2c-ocores.c b/drivers/i2c/busses/i2c-ocores.c new file mode 100644 index 0000000..e5193bf --- /dev/null +++ b/drivers/i2c/busses/i2c-ocores.c @@ -0,0 +1,370 @@ +/* + * i2c-ocores.c: I2C bus driver for OpenCores I2C controller + * (http://www.opencores.org/projects.cgi/web/i2c/overview). + * + * Peter Korsgaard <jacmet@sunsite.dk> + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/wait.h> +#include <linux/i2c-ocores.h> +#include <asm/io.h> + +struct ocores_i2c { + void __iomem *base; + int regstep; + wait_queue_head_t wait; + struct i2c_adapter adap; + struct i2c_msg *msg; + int pos; + int nmsgs; + int state; /* see STATE_ */ + int clock_khz; +}; + +/* registers */ +#define OCI2C_PRELOW 0 +#define OCI2C_PREHIGH 1 +#define OCI2C_CONTROL 2 +#define OCI2C_DATA 3 +#define OCI2C_CMD 4 /* write only */ +#define OCI2C_STATUS 4 /* read only, same address as OCI2C_CMD */ + +#define OCI2C_CTRL_IEN 0x40 +#define OCI2C_CTRL_EN 0x80 + +#define OCI2C_CMD_START 0x91 +#define OCI2C_CMD_STOP 0x41 +#define OCI2C_CMD_READ 0x21 +#define OCI2C_CMD_WRITE 0x11 +#define OCI2C_CMD_READ_ACK 0x21 +#define OCI2C_CMD_READ_NACK 0x29 +#define OCI2C_CMD_IACK 0x01 + +#define OCI2C_STAT_IF 0x01 +#define OCI2C_STAT_TIP 0x02 +#define OCI2C_STAT_ARBLOST 0x20 +#define OCI2C_STAT_BUSY 0x40 +#define OCI2C_STAT_NACK 0x80 + +#define STATE_DONE 0 +#define STATE_START 1 +#define STATE_WRITE 2 +#define STATE_READ 3 +#define STATE_ERROR 4 + +static inline void oc_setreg(struct ocores_i2c *i2c, int reg, u8 value) +{ + iowrite8(value, i2c->base + reg * i2c->regstep); +} + +static inline u8 oc_getreg(struct ocores_i2c *i2c, int reg) +{ + return ioread8(i2c->base + reg * i2c->regstep); +} + +static void ocores_process(struct ocores_i2c *i2c) +{ + struct i2c_msg *msg = i2c->msg; + u8 stat = oc_getreg(i2c, OCI2C_STATUS); + + if ((i2c->state == STATE_DONE) || (i2c->state == STATE_ERROR)) { + /* stop has been sent */ + oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_IACK); + wake_up(&i2c->wait); + return; + } + + /* error? */ + if (stat & OCI2C_STAT_ARBLOST) { + i2c->state = STATE_ERROR; + oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_STOP); + return; + } + + if ((i2c->state == STATE_START) || (i2c->state == STATE_WRITE)) { + i2c->state = + (msg->flags & I2C_M_RD) ? STATE_READ : STATE_WRITE; + + if (stat & OCI2C_STAT_NACK) { + i2c->state = STATE_ERROR; + oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_STOP); + return; + } + } else + msg->buf[i2c->pos++] = oc_getreg(i2c, OCI2C_DATA); + + /* end of msg? */ + if (i2c->pos == msg->len) { + i2c->nmsgs--; + i2c->msg++; + i2c->pos = 0; + msg = i2c->msg; + + if (i2c->nmsgs) { /* end? */ + /* send start? */ + if (!(msg->flags & I2C_M_NOSTART)) { + u8 addr = (msg->addr << 1); + + if (msg->flags & I2C_M_RD) + addr |= 1; + + i2c->state = STATE_START; + + oc_setreg(i2c, OCI2C_DATA, addr); + oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_START); + return; + } else + i2c->state = (msg->flags & I2C_M_RD) + ? STATE_READ : STATE_WRITE; + } else { + i2c->state = STATE_DONE; + oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_STOP); + return; + } + } + + if (i2c->state == STATE_READ) { + oc_setreg(i2c, OCI2C_CMD, i2c->pos == (msg->len-1) ? + OCI2C_CMD_READ_NACK : OCI2C_CMD_READ_ACK); + } else { + oc_setreg(i2c, OCI2C_DATA, msg->buf[i2c->pos++]); + oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_WRITE); + } +} + +static irqreturn_t ocores_isr(int irq, void *dev_id) +{ + struct ocores_i2c *i2c = dev_id; + + ocores_process(i2c); + + return IRQ_HANDLED; +} + +static int ocores_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) +{ + struct ocores_i2c *i2c = i2c_get_adapdata(adap); + + i2c->msg = msgs; + i2c->pos = 0; + i2c->nmsgs = num; + i2c->state = STATE_START; + + oc_setreg(i2c, OCI2C_DATA, + (i2c->msg->addr << 1) | + ((i2c->msg->flags & I2C_M_RD) ? 1:0)); + + oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_START); + + if (wait_event_timeout(i2c->wait, (i2c->state == STATE_ERROR) || + (i2c->state == STATE_DONE), HZ)) + return (i2c->state == STATE_DONE) ? num : -EIO; + else + return -ETIMEDOUT; +} + +static void ocores_init(struct ocores_i2c *i2c) +{ + int prescale; + u8 ctrl = oc_getreg(i2c, OCI2C_CONTROL); + + /* make sure the device is disabled */ + oc_setreg(i2c, OCI2C_CONTROL, ctrl & ~(OCI2C_CTRL_EN|OCI2C_CTRL_IEN)); + + prescale = (i2c->clock_khz / (5*100)) - 1; + oc_setreg(i2c, OCI2C_PRELOW, prescale & 0xff); + oc_setreg(i2c, OCI2C_PREHIGH, prescale >> 8); + + /* Init the device */ + oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_IACK); + oc_setreg(i2c, OCI2C_CONTROL, ctrl | OCI2C_CTRL_IEN | OCI2C_CTRL_EN); +} + + +static u32 ocores_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm ocores_algorithm = { + .master_xfer = ocores_xfer, + .functionality = ocores_func, +}; + +static struct i2c_adapter ocores_adapter = { + .owner = THIS_MODULE, + .name = "i2c-ocores", + .class = I2C_CLASS_HWMON | I2C_CLASS_SPD, + .algo = &ocores_algorithm, +}; + + +static int __devinit ocores_i2c_probe(struct platform_device *pdev) +{ + struct ocores_i2c *i2c; + struct ocores_i2c_platform_data *pdata; + struct resource *res, *res2; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + res2 = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res2) + return -ENODEV; + + pdata = (struct ocores_i2c_platform_data*) pdev->dev.platform_data; + if (!pdata) + return -ENODEV; + + i2c = kzalloc(sizeof(*i2c), GFP_KERNEL); + if (!i2c) + return -ENOMEM; + + if (!request_mem_region(res->start, res->end - res->start + 1, + pdev->name)) { + dev_err(&pdev->dev, "Memory region busy\n"); + ret = -EBUSY; + goto request_mem_failed; + } + + i2c->base = ioremap(res->start, res->end - res->start + 1); + if (!i2c->base) { + dev_err(&pdev->dev, "Unable to map registers\n"); + ret = -EIO; + goto map_failed; + } + + i2c->regstep = pdata->regstep; + i2c->clock_khz = pdata->clock_khz; + ocores_init(i2c); + + init_waitqueue_head(&i2c->wait); + ret = request_irq(res2->start, ocores_isr, 0, pdev->name, i2c); + if (ret) { + dev_err(&pdev->dev, "Cannot claim IRQ\n"); + goto request_irq_failed; + } + + /* hook up driver to tree */ + platform_set_drvdata(pdev, i2c); + i2c->adap = ocores_adapter; + i2c_set_adapdata(&i2c->adap, i2c); + i2c->adap.dev.parent = &pdev->dev; + + /* add i2c adapter to i2c tree */ + ret = i2c_add_adapter(&i2c->adap); + if (ret) { + dev_err(&pdev->dev, "Failed to add adapter\n"); + goto add_adapter_failed; + } + + return 0; + +add_adapter_failed: + free_irq(res2->start, i2c); +request_irq_failed: + iounmap(i2c->base); +map_failed: + release_mem_region(res->start, res->end - res->start + 1); +request_mem_failed: + kfree(i2c); + + return ret; +} + +static int __devexit ocores_i2c_remove(struct platform_device* pdev) +{ + struct ocores_i2c *i2c = platform_get_drvdata(pdev); + struct resource *res; + + /* disable i2c logic */ + oc_setreg(i2c, OCI2C_CONTROL, oc_getreg(i2c, OCI2C_CONTROL) + & ~(OCI2C_CTRL_EN|OCI2C_CTRL_IEN)); + + /* remove adapter & data */ + i2c_del_adapter(&i2c->adap); + platform_set_drvdata(pdev, NULL); + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (res) + free_irq(res->start, i2c); + + iounmap(i2c->base); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res) + release_mem_region(res->start, res->end - res->start + 1); + + kfree(i2c); + + return 0; +} + +#ifdef CONFIG_PM +static int ocores_i2c_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct ocores_i2c *i2c = platform_get_drvdata(pdev); + u8 ctrl = oc_getreg(i2c, OCI2C_CONTROL); + + /* make sure the device is disabled */ + oc_setreg(i2c, OCI2C_CONTROL, ctrl & ~(OCI2C_CTRL_EN|OCI2C_CTRL_IEN)); + + return 0; +} + +static int ocores_i2c_resume(struct platform_device *pdev) +{ + struct ocores_i2c *i2c = platform_get_drvdata(pdev); + + ocores_init(i2c); + + return 0; +} +#else +#define ocores_i2c_suspend NULL +#define ocores_i2c_resume NULL +#endif + +/* work with hotplug and coldplug */ +MODULE_ALIAS("platform:ocores-i2c"); + +static struct platform_driver ocores_i2c_driver = { + .probe = ocores_i2c_probe, + .remove = __devexit_p(ocores_i2c_remove), + .suspend = ocores_i2c_suspend, + .resume = ocores_i2c_resume, + .driver = { + .owner = THIS_MODULE, + .name = "ocores-i2c", + }, +}; + +static int __init ocores_i2c_init(void) +{ + return platform_driver_register(&ocores_i2c_driver); +} + +static void __exit ocores_i2c_exit(void) +{ + platform_driver_unregister(&ocores_i2c_driver); +} + +module_init(ocores_i2c_init); +module_exit(ocores_i2c_exit); + +MODULE_AUTHOR("Peter Korsgaard <jacmet@sunsite.dk>"); +MODULE_DESCRIPTION("OpenCores I2C bus driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/busses/i2c-omap.c b/drivers/i2c/busses/i2c-omap.c new file mode 100644 index 0000000..608038d --- /dev/null +++ b/drivers/i2c/busses/i2c-omap.c @@ -0,0 +1,704 @@ +/* + * TI OMAP I2C master mode driver + * + * Copyright (C) 2003 MontaVista Software, Inc. + * Copyright (C) 2004 Texas Instruments. + * + * Updated to work with multiple I2C interfaces on 24xx by + * Tony Lindgren <tony@atomide.com> and Imre Deak <imre.deak@nokia.com> + * Copyright (C) 2005 Nokia Corporation + * + * Cleaned up by Juha Yrjölä <juha.yrjola@nokia.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/completion.h> +#include <linux/platform_device.h> +#include <linux/clk.h> + +#include <asm/io.h> + +/* timeout waiting for the controller to respond */ +#define OMAP_I2C_TIMEOUT (msecs_to_jiffies(1000)) + +#define OMAP_I2C_REV_REG 0x00 +#define OMAP_I2C_IE_REG 0x04 +#define OMAP_I2C_STAT_REG 0x08 +#define OMAP_I2C_IV_REG 0x0c +#define OMAP_I2C_SYSS_REG 0x10 +#define OMAP_I2C_BUF_REG 0x14 +#define OMAP_I2C_CNT_REG 0x18 +#define OMAP_I2C_DATA_REG 0x1c +#define OMAP_I2C_SYSC_REG 0x20 +#define OMAP_I2C_CON_REG 0x24 +#define OMAP_I2C_OA_REG 0x28 +#define OMAP_I2C_SA_REG 0x2c +#define OMAP_I2C_PSC_REG 0x30 +#define OMAP_I2C_SCLL_REG 0x34 +#define OMAP_I2C_SCLH_REG 0x38 +#define OMAP_I2C_SYSTEST_REG 0x3c + +/* I2C Interrupt Enable Register (OMAP_I2C_IE): */ +#define OMAP_I2C_IE_XRDY (1 << 4) /* TX data ready int enable */ +#define OMAP_I2C_IE_RRDY (1 << 3) /* RX data ready int enable */ +#define OMAP_I2C_IE_ARDY (1 << 2) /* Access ready int enable */ +#define OMAP_I2C_IE_NACK (1 << 1) /* No ack interrupt enable */ +#define OMAP_I2C_IE_AL (1 << 0) /* Arbitration lost int ena */ + +/* I2C Status Register (OMAP_I2C_STAT): */ +#define OMAP_I2C_STAT_SBD (1 << 15) /* Single byte data */ +#define OMAP_I2C_STAT_BB (1 << 12) /* Bus busy */ +#define OMAP_I2C_STAT_ROVR (1 << 11) /* Receive overrun */ +#define OMAP_I2C_STAT_XUDF (1 << 10) /* Transmit underflow */ +#define OMAP_I2C_STAT_AAS (1 << 9) /* Address as slave */ +#define OMAP_I2C_STAT_AD0 (1 << 8) /* Address zero */ +#define OMAP_I2C_STAT_XRDY (1 << 4) /* Transmit data ready */ +#define OMAP_I2C_STAT_RRDY (1 << 3) /* Receive data ready */ +#define OMAP_I2C_STAT_ARDY (1 << 2) /* Register access ready */ +#define OMAP_I2C_STAT_NACK (1 << 1) /* No ack interrupt enable */ +#define OMAP_I2C_STAT_AL (1 << 0) /* Arbitration lost int ena */ + +/* I2C Buffer Configuration Register (OMAP_I2C_BUF): */ +#define OMAP_I2C_BUF_RDMA_EN (1 << 15) /* RX DMA channel enable */ +#define OMAP_I2C_BUF_XDMA_EN (1 << 7) /* TX DMA channel enable */ + +/* I2C Configuration Register (OMAP_I2C_CON): */ +#define OMAP_I2C_CON_EN (1 << 15) /* I2C module enable */ +#define OMAP_I2C_CON_BE (1 << 14) /* Big endian mode */ +#define OMAP_I2C_CON_STB (1 << 11) /* Start byte mode (master) */ +#define OMAP_I2C_CON_MST (1 << 10) /* Master/slave mode */ +#define OMAP_I2C_CON_TRX (1 << 9) /* TX/RX mode (master only) */ +#define OMAP_I2C_CON_XA (1 << 8) /* Expand address */ +#define OMAP_I2C_CON_RM (1 << 2) /* Repeat mode (master only) */ +#define OMAP_I2C_CON_STP (1 << 1) /* Stop cond (master only) */ +#define OMAP_I2C_CON_STT (1 << 0) /* Start condition (master) */ + +/* I2C System Test Register (OMAP_I2C_SYSTEST): */ +#ifdef DEBUG +#define OMAP_I2C_SYSTEST_ST_EN (1 << 15) /* System test enable */ +#define OMAP_I2C_SYSTEST_FREE (1 << 14) /* Free running mode */ +#define OMAP_I2C_SYSTEST_TMODE_MASK (3 << 12) /* Test mode select */ +#define OMAP_I2C_SYSTEST_TMODE_SHIFT (12) /* Test mode select */ +#define OMAP_I2C_SYSTEST_SCL_I (1 << 3) /* SCL line sense in */ +#define OMAP_I2C_SYSTEST_SCL_O (1 << 2) /* SCL line drive out */ +#define OMAP_I2C_SYSTEST_SDA_I (1 << 1) /* SDA line sense in */ +#define OMAP_I2C_SYSTEST_SDA_O (1 << 0) /* SDA line drive out */ +#endif + +/* I2C System Status register (OMAP_I2C_SYSS): */ +#define OMAP_I2C_SYSS_RDONE (1 << 0) /* Reset Done */ + +/* I2C System Configuration Register (OMAP_I2C_SYSC): */ +#define OMAP_I2C_SYSC_SRST (1 << 1) /* Soft Reset */ + +/* REVISIT: Use platform_data instead of module parameters */ +/* Fast Mode = 400 kHz, Standard = 100 kHz */ +static int clock = 100; /* Default: 100 kHz */ +module_param(clock, int, 0); +MODULE_PARM_DESC(clock, "Set I2C clock in kHz: 400=fast mode (default == 100)"); + +struct omap_i2c_dev { + struct device *dev; + void __iomem *base; /* virtual */ + int irq; + struct clk *iclk; /* Interface clock */ + struct clk *fclk; /* Functional clock */ + struct completion cmd_complete; + struct resource *ioarea; + u16 cmd_err; + u8 *buf; + size_t buf_len; + struct i2c_adapter adapter; + unsigned rev1:1; + unsigned idle:1; + u16 iestate; /* Saved interrupt register */ +}; + +static inline void omap_i2c_write_reg(struct omap_i2c_dev *i2c_dev, + int reg, u16 val) +{ + __raw_writew(val, i2c_dev->base + reg); +} + +static inline u16 omap_i2c_read_reg(struct omap_i2c_dev *i2c_dev, int reg) +{ + return __raw_readw(i2c_dev->base + reg); +} + +static int omap_i2c_get_clocks(struct omap_i2c_dev *dev) +{ + if (cpu_is_omap16xx() || cpu_is_omap24xx()) { + dev->iclk = clk_get(dev->dev, "i2c_ick"); + if (IS_ERR(dev->iclk)) { + dev->iclk = NULL; + return -ENODEV; + } + } + + dev->fclk = clk_get(dev->dev, "i2c_fck"); + if (IS_ERR(dev->fclk)) { + if (dev->iclk != NULL) { + clk_put(dev->iclk); + dev->iclk = NULL; + } + dev->fclk = NULL; + return -ENODEV; + } + + return 0; +} + +static void omap_i2c_put_clocks(struct omap_i2c_dev *dev) +{ + clk_put(dev->fclk); + dev->fclk = NULL; + if (dev->iclk != NULL) { + clk_put(dev->iclk); + dev->iclk = NULL; + } +} + +static void omap_i2c_unidle(struct omap_i2c_dev *dev) +{ + if (dev->iclk != NULL) + clk_enable(dev->iclk); + clk_enable(dev->fclk); + if (dev->iestate) + omap_i2c_write_reg(dev, OMAP_I2C_IE_REG, dev->iestate); + dev->idle = 0; +} + +static void omap_i2c_idle(struct omap_i2c_dev *dev) +{ + u16 iv; + + dev->idle = 1; + dev->iestate = omap_i2c_read_reg(dev, OMAP_I2C_IE_REG); + omap_i2c_write_reg(dev, OMAP_I2C_IE_REG, 0); + if (dev->rev1) + iv = omap_i2c_read_reg(dev, OMAP_I2C_IV_REG); /* Read clears */ + else + omap_i2c_write_reg(dev, OMAP_I2C_STAT_REG, dev->iestate); + clk_disable(dev->fclk); + if (dev->iclk != NULL) + clk_disable(dev->iclk); +} + +static int omap_i2c_init(struct omap_i2c_dev *dev) +{ + u16 psc = 0; + unsigned long fclk_rate = 12000000; + unsigned long timeout; + + if (!dev->rev1) { + omap_i2c_write_reg(dev, OMAP_I2C_SYSC_REG, OMAP_I2C_SYSC_SRST); + /* For some reason we need to set the EN bit before the + * reset done bit gets set. */ + timeout = jiffies + OMAP_I2C_TIMEOUT; + omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, OMAP_I2C_CON_EN); + while (!(omap_i2c_read_reg(dev, OMAP_I2C_SYSS_REG) & + OMAP_I2C_SYSS_RDONE)) { + if (time_after(jiffies, timeout)) { + dev_warn(dev->dev, "timeout waiting " + "for controller reset\n"); + return -ETIMEDOUT; + } + msleep(1); + } + } + omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, 0); + + if (cpu_class_is_omap1()) { + struct clk *armxor_ck; + + armxor_ck = clk_get(NULL, "armxor_ck"); + if (IS_ERR(armxor_ck)) + dev_warn(dev->dev, "Could not get armxor_ck\n"); + else { + fclk_rate = clk_get_rate(armxor_ck); + clk_put(armxor_ck); + } + /* TRM for 5912 says the I2C clock must be prescaled to be + * between 7 - 12 MHz. The XOR input clock is typically + * 12, 13 or 19.2 MHz. So we should have code that produces: + * + * XOR MHz Divider Prescaler + * 12 1 0 + * 13 2 1 + * 19.2 2 1 + */ + if (fclk_rate > 12000000) + psc = fclk_rate / 12000000; + } + + /* Setup clock prescaler to obtain approx 12MHz I2C module clock: */ + omap_i2c_write_reg(dev, OMAP_I2C_PSC_REG, psc); + + /* Program desired operating rate */ + fclk_rate /= (psc + 1) * 1000; + if (psc > 2) + psc = 2; + + omap_i2c_write_reg(dev, OMAP_I2C_SCLL_REG, + fclk_rate / (clock * 2) - 7 + psc); + omap_i2c_write_reg(dev, OMAP_I2C_SCLH_REG, + fclk_rate / (clock * 2) - 7 + psc); + + /* Take the I2C module out of reset: */ + omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, OMAP_I2C_CON_EN); + + /* Enable interrupts */ + omap_i2c_write_reg(dev, OMAP_I2C_IE_REG, + (OMAP_I2C_IE_XRDY | OMAP_I2C_IE_RRDY | + OMAP_I2C_IE_ARDY | OMAP_I2C_IE_NACK | + OMAP_I2C_IE_AL)); + return 0; +} + +/* + * Waiting on Bus Busy + */ +static int omap_i2c_wait_for_bb(struct omap_i2c_dev *dev) +{ + unsigned long timeout; + + timeout = jiffies + OMAP_I2C_TIMEOUT; + while (omap_i2c_read_reg(dev, OMAP_I2C_STAT_REG) & OMAP_I2C_STAT_BB) { + if (time_after(jiffies, timeout)) { + dev_warn(dev->dev, "timeout waiting for bus ready\n"); + return -ETIMEDOUT; + } + msleep(1); + } + + return 0; +} + +/* + * Low level master read/write transaction. + */ +static int omap_i2c_xfer_msg(struct i2c_adapter *adap, + struct i2c_msg *msg, int stop) +{ + struct omap_i2c_dev *dev = i2c_get_adapdata(adap); + int r; + u16 w; + + dev_dbg(dev->dev, "addr: 0x%04x, len: %d, flags: 0x%x, stop: %d\n", + msg->addr, msg->len, msg->flags, stop); + + if (msg->len == 0) + return -EINVAL; + + omap_i2c_write_reg(dev, OMAP_I2C_SA_REG, msg->addr); + + /* REVISIT: Could the STB bit of I2C_CON be used with probing? */ + dev->buf = msg->buf; + dev->buf_len = msg->len; + + omap_i2c_write_reg(dev, OMAP_I2C_CNT_REG, dev->buf_len); + + init_completion(&dev->cmd_complete); + dev->cmd_err = 0; + + w = OMAP_I2C_CON_EN | OMAP_I2C_CON_MST | OMAP_I2C_CON_STT; + if (msg->flags & I2C_M_TEN) + w |= OMAP_I2C_CON_XA; + if (!(msg->flags & I2C_M_RD)) + w |= OMAP_I2C_CON_TRX; + if (stop) + w |= OMAP_I2C_CON_STP; + omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, w); + + r = wait_for_completion_interruptible_timeout(&dev->cmd_complete, + OMAP_I2C_TIMEOUT); + dev->buf_len = 0; + if (r < 0) + return r; + if (r == 0) { + dev_err(dev->dev, "controller timed out\n"); + omap_i2c_init(dev); + return -ETIMEDOUT; + } + + if (likely(!dev->cmd_err)) + return 0; + + /* We have an error */ + if (dev->cmd_err & (OMAP_I2C_STAT_AL | OMAP_I2C_STAT_ROVR | + OMAP_I2C_STAT_XUDF)) { + omap_i2c_init(dev); + return -EIO; + } + + if (dev->cmd_err & OMAP_I2C_STAT_NACK) { + if (msg->flags & I2C_M_IGNORE_NAK) + return 0; + if (stop) { + w = omap_i2c_read_reg(dev, OMAP_I2C_CON_REG); + w |= OMAP_I2C_CON_STP; + omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, w); + } + return -EREMOTEIO; + } + return -EIO; +} + + +/* + * Prepare controller for a transaction and call omap_i2c_xfer_msg + * to do the work during IRQ processing. + */ +static int +omap_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) +{ + struct omap_i2c_dev *dev = i2c_get_adapdata(adap); + int i; + int r; + + omap_i2c_unidle(dev); + + if ((r = omap_i2c_wait_for_bb(dev)) < 0) + goto out; + + for (i = 0; i < num; i++) { + r = omap_i2c_xfer_msg(adap, &msgs[i], (i == (num - 1))); + if (r != 0) + break; + } + + if (r == 0) + r = num; +out: + omap_i2c_idle(dev); + return r; +} + +static u32 +omap_i2c_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK); +} + +static inline void +omap_i2c_complete_cmd(struct omap_i2c_dev *dev, u16 err) +{ + dev->cmd_err |= err; + complete(&dev->cmd_complete); +} + +static inline void +omap_i2c_ack_stat(struct omap_i2c_dev *dev, u16 stat) +{ + omap_i2c_write_reg(dev, OMAP_I2C_STAT_REG, stat); +} + +static irqreturn_t +omap_i2c_rev1_isr(int this_irq, void *dev_id) +{ + struct omap_i2c_dev *dev = dev_id; + u16 iv, w; + + if (dev->idle) + return IRQ_NONE; + + iv = omap_i2c_read_reg(dev, OMAP_I2C_IV_REG); + switch (iv) { + case 0x00: /* None */ + break; + case 0x01: /* Arbitration lost */ + dev_err(dev->dev, "Arbitration lost\n"); + omap_i2c_complete_cmd(dev, OMAP_I2C_STAT_AL); + break; + case 0x02: /* No acknowledgement */ + omap_i2c_complete_cmd(dev, OMAP_I2C_STAT_NACK); + omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, OMAP_I2C_CON_STP); + break; + case 0x03: /* Register access ready */ + omap_i2c_complete_cmd(dev, 0); + break; + case 0x04: /* Receive data ready */ + if (dev->buf_len) { + w = omap_i2c_read_reg(dev, OMAP_I2C_DATA_REG); + *dev->buf++ = w; + dev->buf_len--; + if (dev->buf_len) { + *dev->buf++ = w >> 8; + dev->buf_len--; + } + } else + dev_err(dev->dev, "RRDY IRQ while no data requested\n"); + break; + case 0x05: /* Transmit data ready */ + if (dev->buf_len) { + w = *dev->buf++; + dev->buf_len--; + if (dev->buf_len) { + w |= *dev->buf++ << 8; + dev->buf_len--; + } + omap_i2c_write_reg(dev, OMAP_I2C_DATA_REG, w); + } else + dev_err(dev->dev, "XRDY IRQ while no data to send\n"); + break; + default: + return IRQ_NONE; + } + + return IRQ_HANDLED; +} + +static irqreturn_t +omap_i2c_isr(int this_irq, void *dev_id) +{ + struct omap_i2c_dev *dev = dev_id; + u16 bits; + u16 stat, w; + int count = 0; + + if (dev->idle) + return IRQ_NONE; + + bits = omap_i2c_read_reg(dev, OMAP_I2C_IE_REG); + while ((stat = (omap_i2c_read_reg(dev, OMAP_I2C_STAT_REG))) & bits) { + dev_dbg(dev->dev, "IRQ (ISR = 0x%04x)\n", stat); + if (count++ == 100) { + dev_warn(dev->dev, "Too much work in one IRQ\n"); + break; + } + + omap_i2c_write_reg(dev, OMAP_I2C_STAT_REG, stat); + + if (stat & OMAP_I2C_STAT_ARDY) { + omap_i2c_complete_cmd(dev, 0); + continue; + } + if (stat & OMAP_I2C_STAT_RRDY) { + w = omap_i2c_read_reg(dev, OMAP_I2C_DATA_REG); + if (dev->buf_len) { + *dev->buf++ = w; + dev->buf_len--; + if (dev->buf_len) { + *dev->buf++ = w >> 8; + dev->buf_len--; + } + } else + dev_err(dev->dev, "RRDY IRQ while no data " + "requested\n"); + omap_i2c_ack_stat(dev, OMAP_I2C_STAT_RRDY); + continue; + } + if (stat & OMAP_I2C_STAT_XRDY) { + w = 0; + if (dev->buf_len) { + w = *dev->buf++; + dev->buf_len--; + if (dev->buf_len) { + w |= *dev->buf++ << 8; + dev->buf_len--; + } + } else + dev_err(dev->dev, "XRDY IRQ while no " + "data to send\n"); + omap_i2c_write_reg(dev, OMAP_I2C_DATA_REG, w); + omap_i2c_ack_stat(dev, OMAP_I2C_STAT_XRDY); + continue; + } + if (stat & OMAP_I2C_STAT_ROVR) { + dev_err(dev->dev, "Receive overrun\n"); + dev->cmd_err |= OMAP_I2C_STAT_ROVR; + } + if (stat & OMAP_I2C_STAT_XUDF) { + dev_err(dev->dev, "Transmit overflow\n"); + dev->cmd_err |= OMAP_I2C_STAT_XUDF; + } + if (stat & OMAP_I2C_STAT_NACK) { + omap_i2c_complete_cmd(dev, OMAP_I2C_STAT_NACK); + omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, + OMAP_I2C_CON_STP); + } + if (stat & OMAP_I2C_STAT_AL) { + dev_err(dev->dev, "Arbitration lost\n"); + omap_i2c_complete_cmd(dev, OMAP_I2C_STAT_AL); + } + } + + return count ? IRQ_HANDLED : IRQ_NONE; +} + +static const struct i2c_algorithm omap_i2c_algo = { + .master_xfer = omap_i2c_xfer, + .functionality = omap_i2c_func, +}; + +static int +omap_i2c_probe(struct platform_device *pdev) +{ + struct omap_i2c_dev *dev; + struct i2c_adapter *adap; + struct resource *mem, *irq, *ioarea; + int r; + + /* NOTE: driver uses the static register mapping */ + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&pdev->dev, "no mem resource?\n"); + return -ENODEV; + } + irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!irq) { + dev_err(&pdev->dev, "no irq resource?\n"); + return -ENODEV; + } + + ioarea = request_mem_region(mem->start, (mem->end - mem->start) + 1, + pdev->name); + if (!ioarea) { + dev_err(&pdev->dev, "I2C region already claimed\n"); + return -EBUSY; + } + + if (clock > 200) + clock = 400; /* Fast mode */ + else + clock = 100; /* Standard mode */ + + dev = kzalloc(sizeof(struct omap_i2c_dev), GFP_KERNEL); + if (!dev) { + r = -ENOMEM; + goto err_release_region; + } + + dev->dev = &pdev->dev; + dev->irq = irq->start; + dev->base = ioremap(mem->start, mem->end - mem->start + 1); + if (!dev->base) { + r = -ENOMEM; + goto err_free_mem; + } + + platform_set_drvdata(pdev, dev); + + if ((r = omap_i2c_get_clocks(dev)) != 0) + goto err_iounmap; + + omap_i2c_unidle(dev); + + if (cpu_is_omap15xx()) + dev->rev1 = omap_i2c_read_reg(dev, OMAP_I2C_REV_REG) < 0x20; + + /* reset ASAP, clearing any IRQs */ + omap_i2c_init(dev); + + r = request_irq(dev->irq, dev->rev1 ? omap_i2c_rev1_isr : omap_i2c_isr, + 0, pdev->name, dev); + + if (r) { + dev_err(dev->dev, "failure requesting irq %i\n", dev->irq); + goto err_unuse_clocks; + } + r = omap_i2c_read_reg(dev, OMAP_I2C_REV_REG) & 0xff; + dev_info(dev->dev, "bus %d rev%d.%d at %d kHz\n", + pdev->id, r >> 4, r & 0xf, clock); + + adap = &dev->adapter; + i2c_set_adapdata(adap, dev); + adap->owner = THIS_MODULE; + adap->class = I2C_CLASS_HWMON; + strncpy(adap->name, "OMAP I2C adapter", sizeof(adap->name)); + adap->algo = &omap_i2c_algo; + adap->dev.parent = &pdev->dev; + + /* i2c device drivers may be active on return from add_adapter() */ + adap->nr = pdev->id; + r = i2c_add_numbered_adapter(adap); + if (r) { + dev_err(dev->dev, "failure adding adapter\n"); + goto err_free_irq; + } + + omap_i2c_idle(dev); + + return 0; + +err_free_irq: + free_irq(dev->irq, dev); +err_unuse_clocks: + omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, 0); + omap_i2c_idle(dev); + omap_i2c_put_clocks(dev); +err_iounmap: + iounmap(dev->base); +err_free_mem: + platform_set_drvdata(pdev, NULL); + kfree(dev); +err_release_region: + release_mem_region(mem->start, (mem->end - mem->start) + 1); + + return r; +} + +static int +omap_i2c_remove(struct platform_device *pdev) +{ + struct omap_i2c_dev *dev = platform_get_drvdata(pdev); + struct resource *mem; + + platform_set_drvdata(pdev, NULL); + + free_irq(dev->irq, dev); + i2c_del_adapter(&dev->adapter); + omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, 0); + omap_i2c_put_clocks(dev); + iounmap(dev->base); + kfree(dev); + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(mem->start, (mem->end - mem->start) + 1); + return 0; +} + +static struct platform_driver omap_i2c_driver = { + .probe = omap_i2c_probe, + .remove = omap_i2c_remove, + .driver = { + .name = "i2c_omap", + .owner = THIS_MODULE, + }, +}; + +/* I2C may be needed to bring up other drivers */ +static int __init +omap_i2c_init_driver(void) +{ + return platform_driver_register(&omap_i2c_driver); +} +subsys_initcall(omap_i2c_init_driver); + +static void __exit omap_i2c_exit_driver(void) +{ + platform_driver_unregister(&omap_i2c_driver); +} +module_exit(omap_i2c_exit_driver); + +MODULE_AUTHOR("MontaVista Software, Inc. (and others)"); +MODULE_DESCRIPTION("TI OMAP I2C bus adapter"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:i2c_omap"); diff --git a/drivers/i2c/busses/i2c-parport-light.c b/drivers/i2c/busses/i2c-parport-light.c new file mode 100644 index 0000000..b2b8380 --- /dev/null +++ b/drivers/i2c/busses/i2c-parport-light.c @@ -0,0 +1,242 @@ +/* ------------------------------------------------------------------------ * + * i2c-parport-light.c I2C bus over parallel port * + * ------------------------------------------------------------------------ * + Copyright (C) 2003-2007 Jean Delvare <khali@linux-fr.org> + + Based on older i2c-velleman.c driver + Copyright (C) 1995-2000 Simon G. Vogl + With some changes from: + Frodo Looijaard <frodol@dds.nl> + Kyösti Mälkki <kmalkki@cc.hut.fi> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * ------------------------------------------------------------------------ */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/ioport.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#include <asm/io.h> +#include "i2c-parport.h" + +#define DEFAULT_BASE 0x378 +#define DRVNAME "i2c-parport-light" + +static struct platform_device *pdev; + +static u16 base; +module_param(base, ushort, 0); +MODULE_PARM_DESC(base, "Base I/O address"); + +/* ----- Low-level parallel port access ----------------------------------- */ + +static inline void port_write(unsigned char p, unsigned char d) +{ + outb(d, base+p); +} + +static inline unsigned char port_read(unsigned char p) +{ + return inb(base+p); +} + +/* ----- Unified line operation functions --------------------------------- */ + +static inline void line_set(int state, const struct lineop *op) +{ + u8 oldval = port_read(op->port); + + /* Touch only the bit(s) needed */ + if ((op->inverted && !state) || (!op->inverted && state)) + port_write(op->port, oldval | op->val); + else + port_write(op->port, oldval & ~op->val); +} + +static inline int line_get(const struct lineop *op) +{ + u8 oldval = port_read(op->port); + + return ((op->inverted && (oldval & op->val) != op->val) + || (!op->inverted && (oldval & op->val) == op->val)); +} + +/* ----- I2C algorithm call-back functions and structures ----------------- */ + +static void parport_setscl(void *data, int state) +{ + line_set(state, &adapter_parm[type].setscl); +} + +static void parport_setsda(void *data, int state) +{ + line_set(state, &adapter_parm[type].setsda); +} + +static int parport_getscl(void *data) +{ + return line_get(&adapter_parm[type].getscl); +} + +static int parport_getsda(void *data) +{ + return line_get(&adapter_parm[type].getsda); +} + +/* Encapsulate the functions above in the correct structure + Note that getscl will be set to NULL by the attaching code for adapters + that cannot read SCL back */ +static struct i2c_algo_bit_data parport_algo_data = { + .setsda = parport_setsda, + .setscl = parport_setscl, + .getsda = parport_getsda, + .getscl = parport_getscl, + .udelay = 50, + .timeout = HZ, +}; + +/* ----- Driver registration ---------------------------------------------- */ + +static struct i2c_adapter parport_adapter = { + .owner = THIS_MODULE, + .class = I2C_CLASS_HWMON, + .id = I2C_HW_B_LP, + .algo_data = &parport_algo_data, + .name = "Parallel port adapter (light)", +}; + +static int __devinit i2c_parport_probe(struct platform_device *pdev) +{ + int err; + + /* Reset hardware to a sane state (SCL and SDA high) */ + parport_setsda(NULL, 1); + parport_setscl(NULL, 1); + /* Other init if needed (power on...) */ + if (adapter_parm[type].init.val) + line_set(1, &adapter_parm[type].init); + + parport_adapter.dev.parent = &pdev->dev; + err = i2c_bit_add_bus(&parport_adapter); + if (err) + dev_err(&pdev->dev, "Unable to register with I2C\n"); + return err; +} + +static int __devexit i2c_parport_remove(struct platform_device *pdev) +{ + i2c_del_adapter(&parport_adapter); + + /* Un-init if needed (power off...) */ + if (adapter_parm[type].init.val) + line_set(0, &adapter_parm[type].init); + + return 0; +} + +static struct platform_driver i2c_parport_driver = { + .driver = { + .owner = THIS_MODULE, + .name = DRVNAME, + }, + .probe = i2c_parport_probe, + .remove = __devexit_p(i2c_parport_remove), +}; + +static int __init i2c_parport_device_add(u16 address) +{ + int err; + + pdev = platform_device_alloc(DRVNAME, -1); + if (!pdev) { + err = -ENOMEM; + printk(KERN_ERR DRVNAME ": Device allocation failed\n"); + goto exit; + } + + err = platform_device_add(pdev); + if (err) { + printk(KERN_ERR DRVNAME ": Device addition failed (%d)\n", + err); + goto exit_device_put; + } + + return 0; + +exit_device_put: + platform_device_put(pdev); +exit: + return err; +} + +static int __init i2c_parport_init(void) +{ + int err; + + if (type < 0) { + printk(KERN_ERR DRVNAME ": adapter type unspecified\n"); + return -ENODEV; + } + + if (type >= ARRAY_SIZE(adapter_parm)) { + printk(KERN_ERR DRVNAME ": invalid type (%d)\n", type); + return -ENODEV; + } + + if (base == 0) { + pr_info(DRVNAME ": using default base 0x%x\n", DEFAULT_BASE); + base = DEFAULT_BASE; + } + + if (!request_region(base, 3, DRVNAME)) + return -EBUSY; + + if (!adapter_parm[type].getscl.val) + parport_algo_data.getscl = NULL; + + /* Sets global pdev as a side effect */ + err = i2c_parport_device_add(base); + if (err) + goto exit_release; + + err = platform_driver_register(&i2c_parport_driver); + if (err) + goto exit_device; + + return 0; + +exit_device: + platform_device_unregister(pdev); +exit_release: + release_region(base, 3); + return err; +} + +static void __exit i2c_parport_exit(void) +{ + platform_driver_unregister(&i2c_parport_driver); + platform_device_unregister(pdev); + release_region(base, 3); +} + +MODULE_AUTHOR("Jean Delvare <khali@linux-fr.org>"); +MODULE_DESCRIPTION("I2C bus over parallel port (light)"); +MODULE_LICENSE("GPL"); + +module_init(i2c_parport_init); +module_exit(i2c_parport_exit); diff --git a/drivers/i2c/busses/i2c-parport.c b/drivers/i2c/busses/i2c-parport.c new file mode 100644 index 0000000..a257cd5 --- /dev/null +++ b/drivers/i2c/busses/i2c-parport.c @@ -0,0 +1,268 @@ +/* ------------------------------------------------------------------------ * + * i2c-parport.c I2C bus over parallel port * + * ------------------------------------------------------------------------ * + Copyright (C) 2003-2007 Jean Delvare <khali@linux-fr.org> + + Based on older i2c-philips-par.c driver + Copyright (C) 1995-2000 Simon G. Vogl + With some changes from: + Frodo Looijaard <frodol@dds.nl> + Kyösti Mälkki <kmalkki@cc.hut.fi> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * ------------------------------------------------------------------------ */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/parport.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#include "i2c-parport.h" + +/* ----- Device list ------------------------------------------------------ */ + +struct i2c_par { + struct pardevice *pdev; + struct i2c_adapter adapter; + struct i2c_algo_bit_data algo_data; + struct i2c_par *next; +}; + +static struct i2c_par *adapter_list; + +/* ----- Low-level parallel port access ----------------------------------- */ + +static void port_write_data(struct parport *p, unsigned char d) +{ + parport_write_data(p, d); +} + +static void port_write_control(struct parport *p, unsigned char d) +{ + parport_write_control(p, d); +} + +static unsigned char port_read_data(struct parport *p) +{ + return parport_read_data(p); +} + +static unsigned char port_read_status(struct parport *p) +{ + return parport_read_status(p); +} + +static unsigned char port_read_control(struct parport *p) +{ + return parport_read_control(p); +} + +static void (*port_write[])(struct parport *, unsigned char) = { + port_write_data, + NULL, + port_write_control, +}; + +static unsigned char (*port_read[])(struct parport *) = { + port_read_data, + port_read_status, + port_read_control, +}; + +/* ----- Unified line operation functions --------------------------------- */ + +static inline void line_set(struct parport *data, int state, + const struct lineop *op) +{ + u8 oldval = port_read[op->port](data); + + /* Touch only the bit(s) needed */ + if ((op->inverted && !state) || (!op->inverted && state)) + port_write[op->port](data, oldval | op->val); + else + port_write[op->port](data, oldval & ~op->val); +} + +static inline int line_get(struct parport *data, + const struct lineop *op) +{ + u8 oldval = port_read[op->port](data); + + return ((op->inverted && (oldval & op->val) != op->val) + || (!op->inverted && (oldval & op->val) == op->val)); +} + +/* ----- I2C algorithm call-back functions and structures ----------------- */ + +static void parport_setscl(void *data, int state) +{ + line_set((struct parport *) data, state, &adapter_parm[type].setscl); +} + +static void parport_setsda(void *data, int state) +{ + line_set((struct parport *) data, state, &adapter_parm[type].setsda); +} + +static int parport_getscl(void *data) +{ + return line_get((struct parport *) data, &adapter_parm[type].getscl); +} + +static int parport_getsda(void *data) +{ + return line_get((struct parport *) data, &adapter_parm[type].getsda); +} + +/* Encapsulate the functions above in the correct structure. + Note that this is only a template, from which the real structures are + copied. The attaching code will set getscl to NULL for adapters that + cannot read SCL back, and will also make the data field point to + the parallel port structure. */ +static struct i2c_algo_bit_data parport_algo_data = { + .setsda = parport_setsda, + .setscl = parport_setscl, + .getsda = parport_getsda, + .getscl = parport_getscl, + .udelay = 10, /* ~50 kbps */ + .timeout = HZ, +}; + +/* ----- I2c and parallel port call-back functions and structures --------- */ + +static void i2c_parport_attach (struct parport *port) +{ + struct i2c_par *adapter; + + adapter = kzalloc(sizeof(struct i2c_par), GFP_KERNEL); + if (adapter == NULL) { + printk(KERN_ERR "i2c-parport: Failed to kzalloc\n"); + return; + } + + pr_debug("i2c-parport: attaching to %s\n", port->name); + adapter->pdev = parport_register_device(port, "i2c-parport", + NULL, NULL, NULL, PARPORT_FLAG_EXCL, NULL); + if (!adapter->pdev) { + printk(KERN_ERR "i2c-parport: Unable to register with parport\n"); + goto ERROR0; + } + + /* Fill the rest of the structure */ + adapter->adapter.owner = THIS_MODULE; + adapter->adapter.class = I2C_CLASS_HWMON; + adapter->adapter.id = I2C_HW_B_LP; + strlcpy(adapter->adapter.name, "Parallel port adapter", + sizeof(adapter->adapter.name)); + adapter->algo_data = parport_algo_data; + /* Slow down if we can't sense SCL */ + if (!adapter_parm[type].getscl.val) { + adapter->algo_data.getscl = NULL; + adapter->algo_data.udelay = 50; /* ~10 kbps */ + } + adapter->algo_data.data = port; + adapter->adapter.algo_data = &adapter->algo_data; + adapter->adapter.dev.parent = port->physport->dev; + + if (parport_claim_or_block(adapter->pdev) < 0) { + printk(KERN_ERR "i2c-parport: Could not claim parallel port\n"); + goto ERROR1; + } + + /* Reset hardware to a sane state (SCL and SDA high) */ + parport_setsda(port, 1); + parport_setscl(port, 1); + /* Other init if needed (power on...) */ + if (adapter_parm[type].init.val) + line_set(port, 1, &adapter_parm[type].init); + + if (i2c_bit_add_bus(&adapter->adapter) < 0) { + printk(KERN_ERR "i2c-parport: Unable to register with I2C\n"); + goto ERROR1; + } + + /* Add the new adapter to the list */ + adapter->next = adapter_list; + adapter_list = adapter; + return; + +ERROR1: + parport_release(adapter->pdev); + parport_unregister_device(adapter->pdev); +ERROR0: + kfree(adapter); +} + +static void i2c_parport_detach (struct parport *port) +{ + struct i2c_par *adapter, *prev; + + /* Walk the list */ + for (prev = NULL, adapter = adapter_list; adapter; + prev = adapter, adapter = adapter->next) { + if (adapter->pdev->port == port) { + i2c_del_adapter(&adapter->adapter); + + /* Un-init if needed (power off...) */ + if (adapter_parm[type].init.val) + line_set(port, 0, &adapter_parm[type].init); + + parport_release(adapter->pdev); + parport_unregister_device(adapter->pdev); + if (prev) + prev->next = adapter->next; + else + adapter_list = adapter->next; + kfree(adapter); + return; + } + } +} + +static struct parport_driver i2c_parport_driver = { + .name = "i2c-parport", + .attach = i2c_parport_attach, + .detach = i2c_parport_detach, +}; + +/* ----- Module loading, unloading and information ------------------------ */ + +static int __init i2c_parport_init(void) +{ + if (type < 0) { + printk(KERN_WARNING "i2c-parport: adapter type unspecified\n"); + return -ENODEV; + } + + if (type >= ARRAY_SIZE(adapter_parm)) { + printk(KERN_WARNING "i2c-parport: invalid type (%d)\n", type); + return -ENODEV; + } + + return parport_register_driver(&i2c_parport_driver); +} + +static void __exit i2c_parport_exit(void) +{ + parport_unregister_driver(&i2c_parport_driver); +} + +MODULE_AUTHOR("Jean Delvare <khali@linux-fr.org>"); +MODULE_DESCRIPTION("I2C bus over parallel port"); +MODULE_LICENSE("GPL"); + +module_init(i2c_parport_init); +module_exit(i2c_parport_exit); diff --git a/drivers/i2c/busses/i2c-parport.h b/drivers/i2c/busses/i2c-parport.h new file mode 100644 index 0000000..ed69d84 --- /dev/null +++ b/drivers/i2c/busses/i2c-parport.h @@ -0,0 +1,112 @@ +/* ------------------------------------------------------------------------ * + * i2c-parport.h I2C bus over parallel port * + * ------------------------------------------------------------------------ * + Copyright (C) 2003-2004 Jean Delvare <khali@linux-fr.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * ------------------------------------------------------------------------ */ + +#ifdef DATA +#undef DATA +#endif + +#define DATA 0 +#define STAT 1 +#define CTRL 2 + +struct lineop { + u8 val; + u8 port; + u8 inverted; +}; + +struct adapter_parm { + struct lineop setsda; + struct lineop setscl; + struct lineop getsda; + struct lineop getscl; + struct lineop init; +}; + +static struct adapter_parm adapter_parm[] = { + /* type 0: Philips adapter */ + { + .setsda = { 0x80, DATA, 1 }, + .setscl = { 0x08, CTRL, 0 }, + .getsda = { 0x80, STAT, 0 }, + .getscl = { 0x08, STAT, 0 }, + }, + /* type 1: home brew teletext adapter */ + { + .setsda = { 0x02, DATA, 0 }, + .setscl = { 0x01, DATA, 0 }, + .getsda = { 0x80, STAT, 1 }, + }, + /* type 2: Velleman K8000 adapter */ + { + .setsda = { 0x02, CTRL, 1 }, + .setscl = { 0x08, CTRL, 1 }, + .getsda = { 0x10, STAT, 0 }, + }, + /* type 3: ELV adapter */ + { + .setsda = { 0x02, DATA, 1 }, + .setscl = { 0x01, DATA, 1 }, + .getsda = { 0x40, STAT, 1 }, + .getscl = { 0x08, STAT, 1 }, + }, + /* type 4: ADM1032 evaluation board */ + { + .setsda = { 0x02, DATA, 1 }, + .setscl = { 0x01, DATA, 1 }, + .getsda = { 0x10, STAT, 1 }, + .init = { 0xf0, DATA, 0 }, + }, + /* type 5: ADM1025, ADM1030 and ADM1031 evaluation boards */ + { + .setsda = { 0x02, DATA, 1 }, + .setscl = { 0x01, DATA, 1 }, + .getsda = { 0x10, STAT, 1 }, + }, + /* type 6: Barco LPT->DVI (K5800236) adapter */ + { + .setsda = { 0x02, DATA, 1 }, + .setscl = { 0x01, DATA, 1 }, + .getsda = { 0x20, STAT, 0 }, + .getscl = { 0x40, STAT, 0 }, + .init = { 0xfc, DATA, 0 }, + }, + /* type 7: One For All JP1 parallel port adapter */ + { + .setsda = { 0x01, DATA, 0 }, + .setscl = { 0x02, DATA, 0 }, + .getsda = { 0x80, STAT, 1 }, + .init = { 0x04, DATA, 1 }, + }, +}; + +static int type = -1; +module_param(type, int, 0); +MODULE_PARM_DESC(type, + "Type of adapter:\n" + " 0 = Philips adapter\n" + " 1 = home brew teletext adapter\n" + " 2 = Velleman K8000 adapter\n" + " 3 = ELV adapter\n" + " 4 = ADM1032 evaluation board\n" + " 5 = ADM1025, ADM1030 and ADM1031 evaluation boards\n" + " 6 = Barco LPT->DVI (K5800236) adapter\n" + " 7 = One For All JP1 parallel port adapter\n" +); diff --git a/drivers/i2c/busses/i2c-pasemi.c b/drivers/i2c/busses/i2c-pasemi.c new file mode 100644 index 0000000..adf0fbb --- /dev/null +++ b/drivers/i2c/busses/i2c-pasemi.c @@ -0,0 +1,432 @@ +/* + * Copyright (C) 2006-2007 PA Semi, Inc + * + * SMBus host driver for PA Semi PWRficient + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/kernel.h> +#include <linux/stddef.h> +#include <linux/sched.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <asm/io.h> + +static struct pci_driver pasemi_smb_driver; + +struct pasemi_smbus { + struct pci_dev *dev; + struct i2c_adapter adapter; + unsigned long base; + int size; +}; + +/* Register offsets */ +#define REG_MTXFIFO 0x00 +#define REG_MRXFIFO 0x04 +#define REG_SMSTA 0x14 +#define REG_CTL 0x1c + +/* Register defs */ +#define MTXFIFO_READ 0x00000400 +#define MTXFIFO_STOP 0x00000200 +#define MTXFIFO_START 0x00000100 +#define MTXFIFO_DATA_M 0x000000ff + +#define MRXFIFO_EMPTY 0x00000100 +#define MRXFIFO_DATA_M 0x000000ff + +#define SMSTA_XEN 0x08000000 +#define SMSTA_MTN 0x00200000 + +#define CTL_MRR 0x00000400 +#define CTL_MTR 0x00000200 +#define CTL_CLK_M 0x000000ff + +#define CLK_100K_DIV 84 +#define CLK_400K_DIV 21 + +static inline void reg_write(struct pasemi_smbus *smbus, int reg, int val) +{ + dev_dbg(&smbus->dev->dev, "smbus write reg %lx val %08x\n", + smbus->base + reg, val); + outl(val, smbus->base + reg); +} + +static inline int reg_read(struct pasemi_smbus *smbus, int reg) +{ + int ret; + ret = inl(smbus->base + reg); + dev_dbg(&smbus->dev->dev, "smbus read reg %lx val %08x\n", + smbus->base + reg, ret); + return ret; +} + +#define TXFIFO_WR(smbus, reg) reg_write((smbus), REG_MTXFIFO, (reg)) +#define RXFIFO_RD(smbus) reg_read((smbus), REG_MRXFIFO) + +static void pasemi_smb_clear(struct pasemi_smbus *smbus) +{ + unsigned int status; + + status = reg_read(smbus, REG_SMSTA); + reg_write(smbus, REG_SMSTA, status); +} + +static unsigned int pasemi_smb_waitready(struct pasemi_smbus *smbus) +{ + int timeout = 10; + unsigned int status; + + status = reg_read(smbus, REG_SMSTA); + + while (!(status & SMSTA_XEN) && timeout--) { + msleep(1); + status = reg_read(smbus, REG_SMSTA); + } + + /* Got NACK? */ + if (status & SMSTA_MTN) + return -ENXIO; + + if (timeout < 0) { + dev_warn(&smbus->dev->dev, "Timeout, status 0x%08x\n", status); + reg_write(smbus, REG_SMSTA, status); + return -ETIME; + } + + /* Clear XEN */ + reg_write(smbus, REG_SMSTA, SMSTA_XEN); + + return 0; +} + +static int pasemi_i2c_xfer_msg(struct i2c_adapter *adapter, + struct i2c_msg *msg, int stop) +{ + struct pasemi_smbus *smbus = adapter->algo_data; + int read, i, err; + u32 rd; + + read = msg->flags & I2C_M_RD ? 1 : 0; + + TXFIFO_WR(smbus, MTXFIFO_START | (msg->addr << 1) | read); + + if (read) { + TXFIFO_WR(smbus, msg->len | MTXFIFO_READ | + (stop ? MTXFIFO_STOP : 0)); + + err = pasemi_smb_waitready(smbus); + if (err) + goto reset_out; + + for (i = 0; i < msg->len; i++) { + rd = RXFIFO_RD(smbus); + if (rd & MRXFIFO_EMPTY) { + err = -ENODATA; + goto reset_out; + } + msg->buf[i] = rd & MRXFIFO_DATA_M; + } + } else { + for (i = 0; i < msg->len - 1; i++) + TXFIFO_WR(smbus, msg->buf[i]); + + TXFIFO_WR(smbus, msg->buf[msg->len-1] | + (stop ? MTXFIFO_STOP : 0)); + } + + return 0; + + reset_out: + reg_write(smbus, REG_CTL, (CTL_MTR | CTL_MRR | + (CLK_100K_DIV & CTL_CLK_M))); + return err; +} + +static int pasemi_i2c_xfer(struct i2c_adapter *adapter, + struct i2c_msg *msgs, int num) +{ + struct pasemi_smbus *smbus = adapter->algo_data; + int ret, i; + + pasemi_smb_clear(smbus); + + ret = 0; + + for (i = 0; i < num && !ret; i++) + ret = pasemi_i2c_xfer_msg(adapter, &msgs[i], (i == (num - 1))); + + return ret ? ret : num; +} + +static int pasemi_smb_xfer(struct i2c_adapter *adapter, + u16 addr, unsigned short flags, char read_write, u8 command, + int size, union i2c_smbus_data *data) +{ + struct pasemi_smbus *smbus = adapter->algo_data; + unsigned int rd; + int read_flag, err; + int len = 0, i; + + /* All our ops take 8-bit shifted addresses */ + addr <<= 1; + read_flag = read_write == I2C_SMBUS_READ; + + pasemi_smb_clear(smbus); + + switch (size) { + case I2C_SMBUS_QUICK: + TXFIFO_WR(smbus, addr | read_flag | MTXFIFO_START | + MTXFIFO_STOP); + break; + case I2C_SMBUS_BYTE: + TXFIFO_WR(smbus, addr | read_flag | MTXFIFO_START); + if (read_write) + TXFIFO_WR(smbus, 1 | MTXFIFO_STOP | MTXFIFO_READ); + else + TXFIFO_WR(smbus, MTXFIFO_STOP | command); + break; + case I2C_SMBUS_BYTE_DATA: + TXFIFO_WR(smbus, addr | MTXFIFO_START); + TXFIFO_WR(smbus, command); + if (read_write) { + TXFIFO_WR(smbus, addr | I2C_SMBUS_READ | MTXFIFO_START); + TXFIFO_WR(smbus, 1 | MTXFIFO_READ | MTXFIFO_STOP); + } else { + TXFIFO_WR(smbus, MTXFIFO_STOP | data->byte); + } + break; + case I2C_SMBUS_WORD_DATA: + TXFIFO_WR(smbus, addr | MTXFIFO_START); + TXFIFO_WR(smbus, command); + if (read_write) { + TXFIFO_WR(smbus, addr | I2C_SMBUS_READ | MTXFIFO_START); + TXFIFO_WR(smbus, 2 | MTXFIFO_READ | MTXFIFO_STOP); + } else { + TXFIFO_WR(smbus, data->word & MTXFIFO_DATA_M); + TXFIFO_WR(smbus, MTXFIFO_STOP | (data->word >> 8)); + } + break; + case I2C_SMBUS_BLOCK_DATA: + TXFIFO_WR(smbus, addr | MTXFIFO_START); + TXFIFO_WR(smbus, command); + if (read_write) { + TXFIFO_WR(smbus, addr | I2C_SMBUS_READ | MTXFIFO_START); + TXFIFO_WR(smbus, 1 | MTXFIFO_READ); + rd = RXFIFO_RD(smbus); + len = min_t(u8, (rd & MRXFIFO_DATA_M), + I2C_SMBUS_BLOCK_MAX); + TXFIFO_WR(smbus, len | MTXFIFO_READ | + MTXFIFO_STOP); + } else { + len = min_t(u8, data->block[0], I2C_SMBUS_BLOCK_MAX); + TXFIFO_WR(smbus, len); + for (i = 1; i < len; i++) + TXFIFO_WR(smbus, data->block[i]); + TXFIFO_WR(smbus, data->block[len] | MTXFIFO_STOP); + } + break; + case I2C_SMBUS_PROC_CALL: + read_write = I2C_SMBUS_READ; + TXFIFO_WR(smbus, addr | MTXFIFO_START); + TXFIFO_WR(smbus, command); + TXFIFO_WR(smbus, data->word & MTXFIFO_DATA_M); + TXFIFO_WR(smbus, (data->word >> 8) & MTXFIFO_DATA_M); + TXFIFO_WR(smbus, addr | I2C_SMBUS_READ | MTXFIFO_START); + TXFIFO_WR(smbus, 2 | MTXFIFO_STOP | MTXFIFO_READ); + break; + case I2C_SMBUS_BLOCK_PROC_CALL: + len = min_t(u8, data->block[0], I2C_SMBUS_BLOCK_MAX - 1); + read_write = I2C_SMBUS_READ; + TXFIFO_WR(smbus, addr | MTXFIFO_START); + TXFIFO_WR(smbus, command); + TXFIFO_WR(smbus, len); + for (i = 1; i <= len; i++) + TXFIFO_WR(smbus, data->block[i]); + TXFIFO_WR(smbus, addr | I2C_SMBUS_READ); + TXFIFO_WR(smbus, MTXFIFO_READ | 1); + rd = RXFIFO_RD(smbus); + len = min_t(u8, (rd & MRXFIFO_DATA_M), + I2C_SMBUS_BLOCK_MAX - len); + TXFIFO_WR(smbus, len | MTXFIFO_READ | MTXFIFO_STOP); + break; + + default: + dev_warn(&adapter->dev, "Unsupported transaction %d\n", size); + return -EINVAL; + } + + err = pasemi_smb_waitready(smbus); + if (err) + goto reset_out; + + if (read_write == I2C_SMBUS_WRITE) + return 0; + + switch (size) { + case I2C_SMBUS_BYTE: + case I2C_SMBUS_BYTE_DATA: + rd = RXFIFO_RD(smbus); + if (rd & MRXFIFO_EMPTY) { + err = -ENODATA; + goto reset_out; + } + data->byte = rd & MRXFIFO_DATA_M; + break; + case I2C_SMBUS_WORD_DATA: + case I2C_SMBUS_PROC_CALL: + rd = RXFIFO_RD(smbus); + if (rd & MRXFIFO_EMPTY) { + err = -ENODATA; + goto reset_out; + } + data->word = rd & MRXFIFO_DATA_M; + rd = RXFIFO_RD(smbus); + if (rd & MRXFIFO_EMPTY) { + err = -ENODATA; + goto reset_out; + } + data->word |= (rd & MRXFIFO_DATA_M) << 8; + break; + case I2C_SMBUS_BLOCK_DATA: + case I2C_SMBUS_BLOCK_PROC_CALL: + data->block[0] = len; + for (i = 1; i <= len; i ++) { + rd = RXFIFO_RD(smbus); + if (rd & MRXFIFO_EMPTY) { + err = -ENODATA; + goto reset_out; + } + data->block[i] = rd & MRXFIFO_DATA_M; + } + break; + } + + return 0; + + reset_out: + reg_write(smbus, REG_CTL, (CTL_MTR | CTL_MRR | + (CLK_100K_DIV & CTL_CLK_M))); + return err; +} + +static u32 pasemi_smb_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_SMBUS_PROC_CALL | + I2C_FUNC_SMBUS_BLOCK_PROC_CALL | I2C_FUNC_I2C; +} + +static const struct i2c_algorithm smbus_algorithm = { + .master_xfer = pasemi_i2c_xfer, + .smbus_xfer = pasemi_smb_xfer, + .functionality = pasemi_smb_func, +}; + +static int __devinit pasemi_smb_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + struct pasemi_smbus *smbus; + int error; + + if (!(pci_resource_flags(dev, 0) & IORESOURCE_IO)) + return -ENODEV; + + smbus = kzalloc(sizeof(struct pasemi_smbus), GFP_KERNEL); + if (!smbus) + return -ENOMEM; + + smbus->dev = dev; + smbus->base = pci_resource_start(dev, 0); + smbus->size = pci_resource_len(dev, 0); + + if (!request_region(smbus->base, smbus->size, + pasemi_smb_driver.name)) { + error = -EBUSY; + goto out_kfree; + } + + smbus->adapter.owner = THIS_MODULE; + snprintf(smbus->adapter.name, sizeof(smbus->adapter.name), + "PA Semi SMBus adapter at 0x%lx", smbus->base); + smbus->adapter.class = I2C_CLASS_HWMON | I2C_CLASS_SPD; + smbus->adapter.algo = &smbus_algorithm; + smbus->adapter.algo_data = smbus; + smbus->adapter.nr = PCI_FUNC(dev->devfn); + + /* set up the sysfs linkage to our parent device */ + smbus->adapter.dev.parent = &dev->dev; + + reg_write(smbus, REG_CTL, (CTL_MTR | CTL_MRR | + (CLK_100K_DIV & CTL_CLK_M))); + + error = i2c_add_numbered_adapter(&smbus->adapter); + if (error) + goto out_release_region; + + pci_set_drvdata(dev, smbus); + + return 0; + + out_release_region: + release_region(smbus->base, smbus->size); + out_kfree: + kfree(smbus); + return error; +} + +static void __devexit pasemi_smb_remove(struct pci_dev *dev) +{ + struct pasemi_smbus *smbus = pci_get_drvdata(dev); + + i2c_del_adapter(&smbus->adapter); + release_region(smbus->base, smbus->size); + kfree(smbus); +} + +static struct pci_device_id pasemi_smb_ids[] = { + { PCI_DEVICE(0x1959, 0xa003) }, + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, pasemi_smb_ids); + +static struct pci_driver pasemi_smb_driver = { + .name = "i2c-pasemi", + .id_table = pasemi_smb_ids, + .probe = pasemi_smb_probe, + .remove = __devexit_p(pasemi_smb_remove), +}; + +static int __init pasemi_smb_init(void) +{ + return pci_register_driver(&pasemi_smb_driver); +} + +static void __exit pasemi_smb_exit(void) +{ + pci_unregister_driver(&pasemi_smb_driver); +} + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR ("Olof Johansson <olof@lixom.net>"); +MODULE_DESCRIPTION("PA Semi PWRficient SMBus driver"); + +module_init(pasemi_smb_init); +module_exit(pasemi_smb_exit); diff --git a/drivers/i2c/busses/i2c-pca-isa.c b/drivers/i2c/busses/i2c-pca-isa.c new file mode 100644 index 0000000..9eb7626 --- /dev/null +++ b/drivers/i2c/busses/i2c-pca-isa.c @@ -0,0 +1,211 @@ +/* + * i2c-pca-isa.c driver for PCA9564 on ISA boards + * Copyright (C) 2004 Arcom Control Systems + * Copyright (C) 2008 Pengutronix + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/kernel.h> +#include <linux/ioport.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/wait.h> +#include <linux/isa.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-pca.h> + +#include <asm/io.h> +#include <asm/irq.h> + +#define DRIVER "i2c-pca-isa" +#define IO_SIZE 4 + +static unsigned long base; +static int irq = -1; + +/* Data sheet recommends 59kHz for 100kHz operation due to variation + * in the actual clock rate */ +static int clock = I2C_PCA_CON_59kHz; + +static wait_queue_head_t pca_wait; + +static void pca_isa_writebyte(void *pd, int reg, int val) +{ +#ifdef DEBUG_IO + static char *names[] = { "T/O", "DAT", "ADR", "CON" }; + printk("*** write %s at %#lx <= %#04x\n", names[reg], base+reg, val); +#endif + outb(val, base+reg); +} + +static int pca_isa_readbyte(void *pd, int reg) +{ + int res = inb(base+reg); +#ifdef DEBUG_IO + { + static char *names[] = { "STA", "DAT", "ADR", "CON" }; + printk("*** read %s => %#04x\n", names[reg], res); + } +#endif + return res; +} + +static int pca_isa_waitforcompletion(void *pd) +{ + int ret = 0; + + if (irq > -1) { + ret = wait_event_interruptible(pca_wait, + pca_isa_readbyte(pd, I2C_PCA_CON) & I2C_PCA_CON_SI); + } else { + while ((pca_isa_readbyte(pd, I2C_PCA_CON) & I2C_PCA_CON_SI) == 0) + udelay(100); + } + return ret; +} + +static void pca_isa_resetchip(void *pd) +{ + /* apparently only an external reset will do it. not a lot can be done */ + printk(KERN_WARNING DRIVER ": Haven't figured out how to do a reset yet\n"); +} + +static irqreturn_t pca_handler(int this_irq, void *dev_id) { + wake_up_interruptible(&pca_wait); + return IRQ_HANDLED; +} + +static struct i2c_algo_pca_data pca_isa_data = { + /* .data intentionally left NULL, not needed with ISA */ + .write_byte = pca_isa_writebyte, + .read_byte = pca_isa_readbyte, + .wait_for_completion = pca_isa_waitforcompletion, + .reset_chip = pca_isa_resetchip, +}; + +static struct i2c_adapter pca_isa_ops = { + .owner = THIS_MODULE, + .id = I2C_HW_A_ISA, + .algo_data = &pca_isa_data, + .name = "PCA9564 ISA Adapter", + .timeout = 100, +}; + +static int __devinit pca_isa_match(struct device *dev, unsigned int id) +{ + int match = base != 0; + + if (match) { + if (irq <= -1) + dev_warn(dev, "Using polling mode (specify irq)\n"); + } else + dev_err(dev, "Please specify I/O base\n"); + + return match; +} + +static int __devinit pca_isa_probe(struct device *dev, unsigned int id) +{ + init_waitqueue_head(&pca_wait); + + dev_info(dev, "i/o base %#08lx. irq %d\n", base, irq); + +#ifdef CONFIG_PPC + if (check_legacy_ioport(base)) { + dev_err(dev, "I/O address %#08lx is not available\n", base); + goto out; + } +#endif + + if (!request_region(base, IO_SIZE, "i2c-pca-isa")) { + dev_err(dev, "I/O address %#08lx is in use\n", base); + goto out; + } + + if (irq > -1) { + if (request_irq(irq, pca_handler, 0, "i2c-pca-isa", &pca_isa_ops) < 0) { + dev_err(dev, "Request irq%d failed\n", irq); + goto out_region; + } + } + + pca_isa_data.i2c_clock = clock; + if (i2c_pca_add_bus(&pca_isa_ops) < 0) { + dev_err(dev, "Failed to add i2c bus\n"); + goto out_irq; + } + + return 0; + + out_irq: + if (irq > -1) + free_irq(irq, &pca_isa_ops); + out_region: + release_region(base, IO_SIZE); + out: + return -ENODEV; +} + +static int __devexit pca_isa_remove(struct device *dev, unsigned int id) +{ + i2c_del_adapter(&pca_isa_ops); + + if (irq > -1) { + disable_irq(irq); + free_irq(irq, &pca_isa_ops); + } + release_region(base, IO_SIZE); + + return 0; +} + +static struct isa_driver pca_isa_driver = { + .match = pca_isa_match, + .probe = pca_isa_probe, + .remove = __devexit_p(pca_isa_remove), + .driver = { + .owner = THIS_MODULE, + .name = DRIVER, + } +}; + +static int __init pca_isa_init(void) +{ + return isa_register_driver(&pca_isa_driver, 1); +} + +static void __exit pca_isa_exit(void) +{ + isa_unregister_driver(&pca_isa_driver); +} + +MODULE_AUTHOR("Ian Campbell <icampbell@arcom.com>"); +MODULE_DESCRIPTION("ISA base PCA9564 driver"); +MODULE_LICENSE("GPL"); + +module_param(base, ulong, 0); +MODULE_PARM_DESC(base, "I/O base address"); + +module_param(irq, int, 0); +MODULE_PARM_DESC(irq, "IRQ"); +module_param(clock, int, 0); +MODULE_PARM_DESC(clock, "Clock rate as described in table 1 of PCA9564 datasheet"); + +module_init(pca_isa_init); +module_exit(pca_isa_exit); diff --git a/drivers/i2c/busses/i2c-pca-platform.c b/drivers/i2c/busses/i2c-pca-platform.c new file mode 100644 index 0000000..6bb15ad --- /dev/null +++ b/drivers/i2c/busses/i2c-pca-platform.c @@ -0,0 +1,298 @@ +/* + * i2c_pca_platform.c + * + * Platform driver for the PCA9564 I2C controller. + * + * Copyright (C) 2008 Pengutronix + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/i2c-algo-pca.h> +#include <linux/i2c-pca-platform.h> +#include <linux/gpio.h> + +#include <asm/irq.h> +#include <asm/io.h> + +#define res_len(r) ((r)->end - (r)->start + 1) + +struct i2c_pca_pf_data { + void __iomem *reg_base; + int irq; /* if 0, use polling */ + int gpio; + wait_queue_head_t wait; + struct i2c_adapter adap; + struct i2c_algo_pca_data algo_data; + unsigned long io_base; + unsigned long io_size; +}; + +/* Read/Write functions for different register alignments */ + +static int i2c_pca_pf_readbyte8(void *pd, int reg) +{ + struct i2c_pca_pf_data *i2c = pd; + return ioread8(i2c->reg_base + reg); +} + +static int i2c_pca_pf_readbyte16(void *pd, int reg) +{ + struct i2c_pca_pf_data *i2c = pd; + return ioread8(i2c->reg_base + reg * 2); +} + +static int i2c_pca_pf_readbyte32(void *pd, int reg) +{ + struct i2c_pca_pf_data *i2c = pd; + return ioread8(i2c->reg_base + reg * 4); +} + +static void i2c_pca_pf_writebyte8(void *pd, int reg, int val) +{ + struct i2c_pca_pf_data *i2c = pd; + iowrite8(val, i2c->reg_base + reg); +} + +static void i2c_pca_pf_writebyte16(void *pd, int reg, int val) +{ + struct i2c_pca_pf_data *i2c = pd; + iowrite8(val, i2c->reg_base + reg * 2); +} + +static void i2c_pca_pf_writebyte32(void *pd, int reg, int val) +{ + struct i2c_pca_pf_data *i2c = pd; + iowrite8(val, i2c->reg_base + reg * 4); +} + + +static int i2c_pca_pf_waitforcompletion(void *pd) +{ + struct i2c_pca_pf_data *i2c = pd; + int ret = 0; + + if (i2c->irq) { + ret = wait_event_interruptible(i2c->wait, + i2c->algo_data.read_byte(i2c, I2C_PCA_CON) + & I2C_PCA_CON_SI); + } else { + /* + * Do polling... + * XXX: Could get stuck in extreme cases! + * Maybe add timeout, but using irqs is preferred anyhow. + */ + while ((i2c->algo_data.read_byte(i2c, I2C_PCA_CON) + & I2C_PCA_CON_SI) == 0) + udelay(100); + } + + return ret; +} + +static void i2c_pca_pf_dummyreset(void *pd) +{ + struct i2c_pca_pf_data *i2c = pd; + printk(KERN_WARNING "%s: No reset-pin found. Chip may get stuck!\n", + i2c->adap.name); +} + +static void i2c_pca_pf_resetchip(void *pd) +{ + struct i2c_pca_pf_data *i2c = pd; + + gpio_set_value(i2c->gpio, 0); + ndelay(100); + gpio_set_value(i2c->gpio, 1); +} + +static irqreturn_t i2c_pca_pf_handler(int this_irq, void *dev_id) +{ + struct i2c_pca_pf_data *i2c = dev_id; + + if ((i2c->algo_data.read_byte(i2c, I2C_PCA_CON) & I2C_PCA_CON_SI) == 0) + return IRQ_NONE; + + wake_up_interruptible(&i2c->wait); + + return IRQ_HANDLED; +} + + +static int __devinit i2c_pca_pf_probe(struct platform_device *pdev) +{ + struct i2c_pca_pf_data *i2c; + struct resource *res; + struct i2c_pca9564_pf_platform_data *platform_data = + pdev->dev.platform_data; + int ret = 0; + int irq; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + irq = platform_get_irq(pdev, 0); + /* If irq is 0, we do polling. */ + + if (res == NULL) { + ret = -ENODEV; + goto e_print; + } + + if (!request_mem_region(res->start, res_len(res), res->name)) { + ret = -ENOMEM; + goto e_print; + } + + i2c = kzalloc(sizeof(struct i2c_pca_pf_data), GFP_KERNEL); + if (!i2c) { + ret = -ENOMEM; + goto e_alloc; + } + + init_waitqueue_head(&i2c->wait); + + i2c->reg_base = ioremap(res->start, res_len(res)); + if (!i2c->reg_base) { + ret = -ENOMEM; + goto e_remap; + } + i2c->io_base = res->start; + i2c->io_size = res_len(res); + i2c->irq = irq; + + i2c->adap.nr = pdev->id >= 0 ? pdev->id : 0; + i2c->adap.owner = THIS_MODULE; + snprintf(i2c->adap.name, sizeof(i2c->adap.name), "PCA9564 at 0x%08lx", + (unsigned long) res->start); + i2c->adap.algo_data = &i2c->algo_data; + i2c->adap.dev.parent = &pdev->dev; + i2c->adap.timeout = platform_data->timeout; + + i2c->algo_data.i2c_clock = platform_data->i2c_clock_speed; + i2c->algo_data.data = i2c; + + switch (res->flags & IORESOURCE_MEM_TYPE_MASK) { + case IORESOURCE_MEM_32BIT: + i2c->algo_data.write_byte = i2c_pca_pf_writebyte32; + i2c->algo_data.read_byte = i2c_pca_pf_readbyte32; + break; + case IORESOURCE_MEM_16BIT: + i2c->algo_data.write_byte = i2c_pca_pf_writebyte16; + i2c->algo_data.read_byte = i2c_pca_pf_readbyte16; + break; + case IORESOURCE_MEM_8BIT: + default: + i2c->algo_data.write_byte = i2c_pca_pf_writebyte8; + i2c->algo_data.read_byte = i2c_pca_pf_readbyte8; + break; + } + + i2c->algo_data.wait_for_completion = i2c_pca_pf_waitforcompletion; + + i2c->gpio = platform_data->gpio; + i2c->algo_data.reset_chip = i2c_pca_pf_dummyreset; + + /* Use gpio_is_valid() when in mainline */ + if (i2c->gpio > -1) { + ret = gpio_request(i2c->gpio, i2c->adap.name); + if (ret == 0) { + gpio_direction_output(i2c->gpio, 1); + i2c->algo_data.reset_chip = i2c_pca_pf_resetchip; + } else { + printk(KERN_WARNING "%s: Registering gpio failed!\n", + i2c->adap.name); + i2c->gpio = ret; + } + } + + if (irq) { + ret = request_irq(irq, i2c_pca_pf_handler, + IRQF_TRIGGER_FALLING, i2c->adap.name, i2c); + if (ret) + goto e_reqirq; + } + + if (i2c_pca_add_numbered_bus(&i2c->adap) < 0) { + ret = -ENODEV; + goto e_adapt; + } + + platform_set_drvdata(pdev, i2c); + + printk(KERN_INFO "%s registered.\n", i2c->adap.name); + + return 0; + +e_adapt: + if (irq) + free_irq(irq, i2c); +e_reqirq: + if (i2c->gpio > -1) + gpio_free(i2c->gpio); + + iounmap(i2c->reg_base); +e_remap: + kfree(i2c); +e_alloc: + release_mem_region(res->start, res_len(res)); +e_print: + printk(KERN_ERR "Registering PCA9564 FAILED! (%d)\n", ret); + return ret; +} + +static int __devexit i2c_pca_pf_remove(struct platform_device *pdev) +{ + struct i2c_pca_pf_data *i2c = platform_get_drvdata(pdev); + platform_set_drvdata(pdev, NULL); + + i2c_del_adapter(&i2c->adap); + + if (i2c->irq) + free_irq(i2c->irq, i2c); + + if (i2c->gpio > -1) + gpio_free(i2c->gpio); + + iounmap(i2c->reg_base); + release_mem_region(i2c->io_base, i2c->io_size); + kfree(i2c); + + return 0; +} + +static struct platform_driver i2c_pca_pf_driver = { + .probe = i2c_pca_pf_probe, + .remove = __devexit_p(i2c_pca_pf_remove), + .driver = { + .name = "i2c-pca-platform", + .owner = THIS_MODULE, + }, +}; + +static int __init i2c_pca_pf_init(void) +{ + return platform_driver_register(&i2c_pca_pf_driver); +} + +static void __exit i2c_pca_pf_exit(void) +{ + platform_driver_unregister(&i2c_pca_pf_driver); +} + +MODULE_AUTHOR("Wolfram Sang <w.sang@pengutronix.de>"); +MODULE_DESCRIPTION("I2C-PCA9564 platform driver"); +MODULE_LICENSE("GPL"); + +module_init(i2c_pca_pf_init); +module_exit(i2c_pca_pf_exit); + diff --git a/drivers/i2c/busses/i2c-piix4.c b/drivers/i2c/busses/i2c-piix4.c new file mode 100644 index 0000000..eaa9b38 --- /dev/null +++ b/drivers/i2c/busses/i2c-piix4.c @@ -0,0 +1,488 @@ +/* + Copyright (c) 1998 - 2002 Frodo Looijaard <frodol@dds.nl> and + Philip Edelbrock <phil@netroedge.com> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* + Supports: + Intel PIIX4, 440MX + Serverworks OSB4, CSB5, CSB6, HT-1000 + ATI IXP200, IXP300, IXP400, SB600, SB700, SB800 + SMSC Victory66 + + Note: we assume there can only be one device, with one SMBus interface. +*/ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/pci.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/stddef.h> +#include <linux/ioport.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/dmi.h> +#include <linux/acpi.h> +#include <asm/io.h> + + +/* PIIX4 SMBus address offsets */ +#define SMBHSTSTS (0 + piix4_smba) +#define SMBHSLVSTS (1 + piix4_smba) +#define SMBHSTCNT (2 + piix4_smba) +#define SMBHSTCMD (3 + piix4_smba) +#define SMBHSTADD (4 + piix4_smba) +#define SMBHSTDAT0 (5 + piix4_smba) +#define SMBHSTDAT1 (6 + piix4_smba) +#define SMBBLKDAT (7 + piix4_smba) +#define SMBSLVCNT (8 + piix4_smba) +#define SMBSHDWCMD (9 + piix4_smba) +#define SMBSLVEVT (0xA + piix4_smba) +#define SMBSLVDAT (0xC + piix4_smba) + +/* count for request_region */ +#define SMBIOSIZE 8 + +/* PCI Address Constants */ +#define SMBBA 0x090 +#define SMBHSTCFG 0x0D2 +#define SMBSLVC 0x0D3 +#define SMBSHDW1 0x0D4 +#define SMBSHDW2 0x0D5 +#define SMBREV 0x0D6 + +/* Other settings */ +#define MAX_TIMEOUT 500 +#define ENABLE_INT9 0 + +/* PIIX4 constants */ +#define PIIX4_QUICK 0x00 +#define PIIX4_BYTE 0x04 +#define PIIX4_BYTE_DATA 0x08 +#define PIIX4_WORD_DATA 0x0C +#define PIIX4_BLOCK_DATA 0x14 + +/* insmod parameters */ + +/* If force is set to anything different from 0, we forcibly enable the + PIIX4. DANGEROUS! */ +static int force; +module_param (force, int, 0); +MODULE_PARM_DESC(force, "Forcibly enable the PIIX4. DANGEROUS!"); + +/* If force_addr is set to anything different from 0, we forcibly enable + the PIIX4 at the given address. VERY DANGEROUS! */ +static int force_addr; +module_param (force_addr, int, 0); +MODULE_PARM_DESC(force_addr, + "Forcibly enable the PIIX4 at the given address. " + "EXTREMELY DANGEROUS!"); + +static unsigned short piix4_smba; +static int srvrworks_csb5_delay; +static struct pci_driver piix4_driver; +static struct i2c_adapter piix4_adapter; + +static struct dmi_system_id __devinitdata piix4_dmi_blacklist[] = { + { + .ident = "Sapphire AM2RD790", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "SAPPHIRE Inc."), + DMI_MATCH(DMI_BOARD_NAME, "PC-AM2RD790"), + }, + }, + { + .ident = "DFI Lanparty UT 790FX", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "DFI Inc."), + DMI_MATCH(DMI_BOARD_NAME, "LP UT 790FX"), + }, + }, + { } +}; + +/* The IBM entry is in a separate table because we only check it + on Intel-based systems */ +static struct dmi_system_id __devinitdata piix4_dmi_ibm[] = { + { + .ident = "IBM", + .matches = { DMI_MATCH(DMI_SYS_VENDOR, "IBM"), }, + }, + { }, +}; + +static int __devinit piix4_setup(struct pci_dev *PIIX4_dev, + const struct pci_device_id *id) +{ + unsigned char temp; + + if ((PIIX4_dev->vendor == PCI_VENDOR_ID_SERVERWORKS) && + (PIIX4_dev->device == PCI_DEVICE_ID_SERVERWORKS_CSB5)) + srvrworks_csb5_delay = 1; + + /* On some motherboards, it was reported that accessing the SMBus + caused severe hardware problems */ + if (dmi_check_system(piix4_dmi_blacklist)) { + dev_err(&PIIX4_dev->dev, + "Accessing the SMBus on this system is unsafe!\n"); + return -EPERM; + } + + /* Don't access SMBus on IBM systems which get corrupted eeproms */ + if (dmi_check_system(piix4_dmi_ibm) && + PIIX4_dev->vendor == PCI_VENDOR_ID_INTEL) { + dev_err(&PIIX4_dev->dev, "IBM system detected; this module " + "may corrupt your serial eeprom! Refusing to load " + "module!\n"); + return -EPERM; + } + + /* Determine the address of the SMBus areas */ + if (force_addr) { + piix4_smba = force_addr & 0xfff0; + force = 0; + } else { + pci_read_config_word(PIIX4_dev, SMBBA, &piix4_smba); + piix4_smba &= 0xfff0; + if(piix4_smba == 0) { + dev_err(&PIIX4_dev->dev, "SMBus base address " + "uninitialized - upgrade BIOS or use " + "force_addr=0xaddr\n"); + return -ENODEV; + } + } + + if (acpi_check_region(piix4_smba, SMBIOSIZE, piix4_driver.name)) + return -EBUSY; + + if (!request_region(piix4_smba, SMBIOSIZE, piix4_driver.name)) { + dev_err(&PIIX4_dev->dev, "SMBus region 0x%x already in use!\n", + piix4_smba); + return -EBUSY; + } + + pci_read_config_byte(PIIX4_dev, SMBHSTCFG, &temp); + + /* If force_addr is set, we program the new address here. Just to make + sure, we disable the PIIX4 first. */ + if (force_addr) { + pci_write_config_byte(PIIX4_dev, SMBHSTCFG, temp & 0xfe); + pci_write_config_word(PIIX4_dev, SMBBA, piix4_smba); + pci_write_config_byte(PIIX4_dev, SMBHSTCFG, temp | 0x01); + dev_info(&PIIX4_dev->dev, "WARNING: SMBus interface set to " + "new address %04x!\n", piix4_smba); + } else if ((temp & 1) == 0) { + if (force) { + /* This should never need to be done, but has been + * noted that many Dell machines have the SMBus + * interface on the PIIX4 disabled!? NOTE: This assumes + * I/O space and other allocations WERE done by the + * Bios! Don't complain if your hardware does weird + * things after enabling this. :') Check for Bios + * updates before resorting to this. + */ + pci_write_config_byte(PIIX4_dev, SMBHSTCFG, + temp | 1); + dev_printk(KERN_NOTICE, &PIIX4_dev->dev, + "WARNING: SMBus interface has been " + "FORCEFULLY ENABLED!\n"); + } else { + dev_err(&PIIX4_dev->dev, + "Host SMBus controller not enabled!\n"); + release_region(piix4_smba, SMBIOSIZE); + piix4_smba = 0; + return -ENODEV; + } + } + + if (((temp & 0x0E) == 8) || ((temp & 0x0E) == 2)) + dev_dbg(&PIIX4_dev->dev, "Using Interrupt 9 for SMBus.\n"); + else if ((temp & 0x0E) == 0) + dev_dbg(&PIIX4_dev->dev, "Using Interrupt SMI# for SMBus.\n"); + else + dev_err(&PIIX4_dev->dev, "Illegal Interrupt configuration " + "(or code out of date)!\n"); + + pci_read_config_byte(PIIX4_dev, SMBREV, &temp); + dev_info(&PIIX4_dev->dev, + "SMBus Host Controller at 0x%x, revision %d\n", + piix4_smba, temp); + + return 0; +} + +static int piix4_transaction(void) +{ + int temp; + int result = 0; + int timeout = 0; + + dev_dbg(&piix4_adapter.dev, "Transaction (pre): CNT=%02x, CMD=%02x, " + "ADD=%02x, DAT0=%02x, DAT1=%02x\n", inb_p(SMBHSTCNT), + inb_p(SMBHSTCMD), inb_p(SMBHSTADD), inb_p(SMBHSTDAT0), + inb_p(SMBHSTDAT1)); + + /* Make sure the SMBus host is ready to start transmitting */ + if ((temp = inb_p(SMBHSTSTS)) != 0x00) { + dev_dbg(&piix4_adapter.dev, "SMBus busy (%02x). " + "Resetting...\n", temp); + outb_p(temp, SMBHSTSTS); + if ((temp = inb_p(SMBHSTSTS)) != 0x00) { + dev_err(&piix4_adapter.dev, "Failed! (%02x)\n", temp); + return -EBUSY; + } else { + dev_dbg(&piix4_adapter.dev, "Successful!\n"); + } + } + + /* start the transaction by setting bit 6 */ + outb_p(inb(SMBHSTCNT) | 0x040, SMBHSTCNT); + + /* We will always wait for a fraction of a second! (See PIIX4 docs errata) */ + if (srvrworks_csb5_delay) /* Extra delay for SERVERWORKS_CSB5 */ + msleep(2); + else + msleep(1); + + while ((timeout++ < MAX_TIMEOUT) && + ((temp = inb_p(SMBHSTSTS)) & 0x01)) + msleep(1); + + /* If the SMBus is still busy, we give up */ + if (timeout >= MAX_TIMEOUT) { + dev_err(&piix4_adapter.dev, "SMBus Timeout!\n"); + result = -ETIMEDOUT; + } + + if (temp & 0x10) { + result = -EIO; + dev_err(&piix4_adapter.dev, "Error: Failed bus transaction\n"); + } + + if (temp & 0x08) { + result = -EIO; + dev_dbg(&piix4_adapter.dev, "Bus collision! SMBus may be " + "locked until next hard reset. (sorry!)\n"); + /* Clock stops and slave is stuck in mid-transmission */ + } + + if (temp & 0x04) { + result = -ENXIO; + dev_dbg(&piix4_adapter.dev, "Error: no response!\n"); + } + + if (inb_p(SMBHSTSTS) != 0x00) + outb_p(inb(SMBHSTSTS), SMBHSTSTS); + + if ((temp = inb_p(SMBHSTSTS)) != 0x00) { + dev_err(&piix4_adapter.dev, "Failed reset at end of " + "transaction (%02x)\n", temp); + } + dev_dbg(&piix4_adapter.dev, "Transaction (post): CNT=%02x, CMD=%02x, " + "ADD=%02x, DAT0=%02x, DAT1=%02x\n", inb_p(SMBHSTCNT), + inb_p(SMBHSTCMD), inb_p(SMBHSTADD), inb_p(SMBHSTDAT0), + inb_p(SMBHSTDAT1)); + return result; +} + +/* Return negative errno on error. */ +static s32 piix4_access(struct i2c_adapter * adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, union i2c_smbus_data * data) +{ + int i, len; + int status; + + switch (size) { + case I2C_SMBUS_QUICK: + outb_p((addr << 1) | read_write, + SMBHSTADD); + size = PIIX4_QUICK; + break; + case I2C_SMBUS_BYTE: + outb_p((addr << 1) | read_write, + SMBHSTADD); + if (read_write == I2C_SMBUS_WRITE) + outb_p(command, SMBHSTCMD); + size = PIIX4_BYTE; + break; + case I2C_SMBUS_BYTE_DATA: + outb_p((addr << 1) | read_write, + SMBHSTADD); + outb_p(command, SMBHSTCMD); + if (read_write == I2C_SMBUS_WRITE) + outb_p(data->byte, SMBHSTDAT0); + size = PIIX4_BYTE_DATA; + break; + case I2C_SMBUS_WORD_DATA: + outb_p((addr << 1) | read_write, + SMBHSTADD); + outb_p(command, SMBHSTCMD); + if (read_write == I2C_SMBUS_WRITE) { + outb_p(data->word & 0xff, SMBHSTDAT0); + outb_p((data->word & 0xff00) >> 8, SMBHSTDAT1); + } + size = PIIX4_WORD_DATA; + break; + case I2C_SMBUS_BLOCK_DATA: + outb_p((addr << 1) | read_write, + SMBHSTADD); + outb_p(command, SMBHSTCMD); + if (read_write == I2C_SMBUS_WRITE) { + len = data->block[0]; + if (len == 0 || len > I2C_SMBUS_BLOCK_MAX) + return -EINVAL; + outb_p(len, SMBHSTDAT0); + i = inb_p(SMBHSTCNT); /* Reset SMBBLKDAT */ + for (i = 1; i <= len; i++) + outb_p(data->block[i], SMBBLKDAT); + } + size = PIIX4_BLOCK_DATA; + break; + default: + dev_warn(&adap->dev, "Unsupported transaction %d\n", size); + return -EOPNOTSUPP; + } + + outb_p((size & 0x1C) + (ENABLE_INT9 & 1), SMBHSTCNT); + + status = piix4_transaction(); + if (status) + return status; + + if ((read_write == I2C_SMBUS_WRITE) || (size == PIIX4_QUICK)) + return 0; + + + switch (size) { + case PIIX4_BYTE: + case PIIX4_BYTE_DATA: + data->byte = inb_p(SMBHSTDAT0); + break; + case PIIX4_WORD_DATA: + data->word = inb_p(SMBHSTDAT0) + (inb_p(SMBHSTDAT1) << 8); + break; + case PIIX4_BLOCK_DATA: + data->block[0] = inb_p(SMBHSTDAT0); + if (data->block[0] == 0 || data->block[0] > I2C_SMBUS_BLOCK_MAX) + return -EPROTO; + i = inb_p(SMBHSTCNT); /* Reset SMBBLKDAT */ + for (i = 1; i <= data->block[0]; i++) + data->block[i] = inb_p(SMBBLKDAT); + break; + } + return 0; +} + +static u32 piix4_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_BLOCK_DATA; +} + +static const struct i2c_algorithm smbus_algorithm = { + .smbus_xfer = piix4_access, + .functionality = piix4_func, +}; + +static struct i2c_adapter piix4_adapter = { + .owner = THIS_MODULE, + .id = I2C_HW_SMBUS_PIIX4, + .class = I2C_CLASS_HWMON | I2C_CLASS_SPD, + .algo = &smbus_algorithm, +}; + +static struct pci_device_id piix4_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371AB_3) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82443MX_3) }, + { PCI_DEVICE(PCI_VENDOR_ID_EFAR, PCI_DEVICE_ID_EFAR_SLC90E66_3) }, + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_IXP200_SMBUS) }, + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_IXP300_SMBUS) }, + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_IXP400_SMBUS) }, + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_SBX00_SMBUS) }, + { PCI_DEVICE(PCI_VENDOR_ID_SERVERWORKS, + PCI_DEVICE_ID_SERVERWORKS_OSB4) }, + { PCI_DEVICE(PCI_VENDOR_ID_SERVERWORKS, + PCI_DEVICE_ID_SERVERWORKS_CSB5) }, + { PCI_DEVICE(PCI_VENDOR_ID_SERVERWORKS, + PCI_DEVICE_ID_SERVERWORKS_CSB6) }, + { PCI_DEVICE(PCI_VENDOR_ID_SERVERWORKS, + PCI_DEVICE_ID_SERVERWORKS_HT1000SB) }, + { 0, } +}; + +MODULE_DEVICE_TABLE (pci, piix4_ids); + +static int __devinit piix4_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + int retval; + + retval = piix4_setup(dev, id); + if (retval) + return retval; + + /* set up the sysfs linkage to our parent device */ + piix4_adapter.dev.parent = &dev->dev; + + snprintf(piix4_adapter.name, sizeof(piix4_adapter.name), + "SMBus PIIX4 adapter at %04x", piix4_smba); + + if ((retval = i2c_add_adapter(&piix4_adapter))) { + dev_err(&dev->dev, "Couldn't register adapter!\n"); + release_region(piix4_smba, SMBIOSIZE); + piix4_smba = 0; + } + + return retval; +} + +static void __devexit piix4_remove(struct pci_dev *dev) +{ + if (piix4_smba) { + i2c_del_adapter(&piix4_adapter); + release_region(piix4_smba, SMBIOSIZE); + piix4_smba = 0; + } +} + +static struct pci_driver piix4_driver = { + .name = "piix4_smbus", + .id_table = piix4_ids, + .probe = piix4_probe, + .remove = __devexit_p(piix4_remove), +}; + +static int __init i2c_piix4_init(void) +{ + return pci_register_driver(&piix4_driver); +} + +static void __exit i2c_piix4_exit(void) +{ + pci_unregister_driver(&piix4_driver); +} + +MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl> and " + "Philip Edelbrock <phil@netroedge.com>"); +MODULE_DESCRIPTION("PIIX4 SMBus driver"); +MODULE_LICENSE("GPL"); + +module_init(i2c_piix4_init); +module_exit(i2c_piix4_exit); diff --git a/drivers/i2c/busses/i2c-pmcmsp.c b/drivers/i2c/busses/i2c-pmcmsp.c new file mode 100644 index 0000000..0bdb2d7 --- /dev/null +++ b/drivers/i2c/busses/i2c-pmcmsp.c @@ -0,0 +1,656 @@ +/* + * Specific bus support for PMC-TWI compliant implementation on MSP71xx. + * + * Copyright 2005-2007 PMC-Sierra, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/completion.h> +#include <linux/mutex.h> +#include <linux/delay.h> +#include <asm/io.h> + +#define DRV_NAME "pmcmsptwi" + +#define MSP_TWI_SF_CLK_REG_OFFSET 0x00 +#define MSP_TWI_HS_CLK_REG_OFFSET 0x04 +#define MSP_TWI_CFG_REG_OFFSET 0x08 +#define MSP_TWI_CMD_REG_OFFSET 0x0c +#define MSP_TWI_ADD_REG_OFFSET 0x10 +#define MSP_TWI_DAT_0_REG_OFFSET 0x14 +#define MSP_TWI_DAT_1_REG_OFFSET 0x18 +#define MSP_TWI_INT_STS_REG_OFFSET 0x1c +#define MSP_TWI_INT_MSK_REG_OFFSET 0x20 +#define MSP_TWI_BUSY_REG_OFFSET 0x24 + +#define MSP_TWI_INT_STS_DONE (1 << 0) +#define MSP_TWI_INT_STS_LOST_ARBITRATION (1 << 1) +#define MSP_TWI_INT_STS_NO_RESPONSE (1 << 2) +#define MSP_TWI_INT_STS_DATA_COLLISION (1 << 3) +#define MSP_TWI_INT_STS_BUSY (1 << 4) +#define MSP_TWI_INT_STS_ALL 0x1f + +#define MSP_MAX_BYTES_PER_RW 8 +#define MSP_MAX_POLL 5 +#define MSP_POLL_DELAY 10 +#define MSP_IRQ_TIMEOUT (MSP_MAX_POLL * MSP_POLL_DELAY) + +/* IO Operation macros */ +#define pmcmsptwi_readl __raw_readl +#define pmcmsptwi_writel __raw_writel + +/* TWI command type */ +enum pmcmsptwi_cmd_type { + MSP_TWI_CMD_WRITE = 0, /* Write only */ + MSP_TWI_CMD_READ = 1, /* Read only */ + MSP_TWI_CMD_WRITE_READ = 2, /* Write then Read */ +}; + +/* The possible results of the xferCmd */ +enum pmcmsptwi_xfer_result { + MSP_TWI_XFER_OK = 0, + MSP_TWI_XFER_TIMEOUT, + MSP_TWI_XFER_BUSY, + MSP_TWI_XFER_DATA_COLLISION, + MSP_TWI_XFER_NO_RESPONSE, + MSP_TWI_XFER_LOST_ARBITRATION, +}; + +/* Corresponds to a PMCTWI clock configuration register */ +struct pmcmsptwi_clock { + u8 filter; /* Bits 15:12, default = 0x03 */ + u16 clock; /* Bits 9:0, default = 0x001f */ +}; + +struct pmcmsptwi_clockcfg { + struct pmcmsptwi_clock standard; /* The standard/fast clock config */ + struct pmcmsptwi_clock highspeed; /* The highspeed clock config */ +}; + +/* Corresponds to the main TWI configuration register */ +struct pmcmsptwi_cfg { + u8 arbf; /* Bits 15:12, default=0x03 */ + u8 nak; /* Bits 11:8, default=0x03 */ + u8 add10; /* Bit 7, default=0x00 */ + u8 mst_code; /* Bits 6:4, default=0x00 */ + u8 arb; /* Bit 1, default=0x01 */ + u8 highspeed; /* Bit 0, default=0x00 */ +}; + +/* A single pmctwi command to issue */ +struct pmcmsptwi_cmd { + u16 addr; /* The slave address (7 or 10 bits) */ + enum pmcmsptwi_cmd_type type; /* The command type */ + u8 write_len; /* Number of bytes in the write buffer */ + u8 read_len; /* Number of bytes in the read buffer */ + u8 *write_data; /* Buffer of characters to send */ + u8 *read_data; /* Buffer to fill with incoming data */ +}; + +/* The private data */ +struct pmcmsptwi_data { + void __iomem *iobase; /* iomapped base for IO */ + int irq; /* IRQ to use (0 disables) */ + struct completion wait; /* Completion for xfer */ + struct mutex lock; /* Used for threadsafeness */ + enum pmcmsptwi_xfer_result last_result; /* result of last xfer */ +}; + +/* The default settings */ +static const struct pmcmsptwi_clockcfg pmcmsptwi_defclockcfg = { + .standard = { + .filter = 0x3, + .clock = 0x1f, + }, + .highspeed = { + .filter = 0x3, + .clock = 0x1f, + }, +}; + +static const struct pmcmsptwi_cfg pmcmsptwi_defcfg = { + .arbf = 0x03, + .nak = 0x03, + .add10 = 0x00, + .mst_code = 0x00, + .arb = 0x01, + .highspeed = 0x00, +}; + +static struct pmcmsptwi_data pmcmsptwi_data; + +static struct i2c_adapter pmcmsptwi_adapter; + +/* inline helper functions */ +static inline u32 pmcmsptwi_clock_to_reg( + const struct pmcmsptwi_clock *clock) +{ + return ((clock->filter & 0xf) << 12) | (clock->clock & 0x03ff); +} + +static inline void pmcmsptwi_reg_to_clock( + u32 reg, struct pmcmsptwi_clock *clock) +{ + clock->filter = (reg >> 12) & 0xf; + clock->clock = reg & 0x03ff; +} + +static inline u32 pmcmsptwi_cfg_to_reg(const struct pmcmsptwi_cfg *cfg) +{ + return ((cfg->arbf & 0xf) << 12) | + ((cfg->nak & 0xf) << 8) | + ((cfg->add10 & 0x1) << 7) | + ((cfg->mst_code & 0x7) << 4) | + ((cfg->arb & 0x1) << 1) | + (cfg->highspeed & 0x1); +} + +static inline void pmcmsptwi_reg_to_cfg(u32 reg, struct pmcmsptwi_cfg *cfg) +{ + cfg->arbf = (reg >> 12) & 0xf; + cfg->nak = (reg >> 8) & 0xf; + cfg->add10 = (reg >> 7) & 0x1; + cfg->mst_code = (reg >> 4) & 0x7; + cfg->arb = (reg >> 1) & 0x1; + cfg->highspeed = reg & 0x1; +} + +/* + * Sets the current clock configuration + */ +static void pmcmsptwi_set_clock_config(const struct pmcmsptwi_clockcfg *cfg, + struct pmcmsptwi_data *data) +{ + mutex_lock(&data->lock); + pmcmsptwi_writel(pmcmsptwi_clock_to_reg(&cfg->standard), + data->iobase + MSP_TWI_SF_CLK_REG_OFFSET); + pmcmsptwi_writel(pmcmsptwi_clock_to_reg(&cfg->highspeed), + data->iobase + MSP_TWI_HS_CLK_REG_OFFSET); + mutex_unlock(&data->lock); +} + +/* + * Gets the current TWI bus configuration + */ +static void pmcmsptwi_get_twi_config(struct pmcmsptwi_cfg *cfg, + struct pmcmsptwi_data *data) +{ + mutex_lock(&data->lock); + pmcmsptwi_reg_to_cfg(pmcmsptwi_readl( + data->iobase + MSP_TWI_CFG_REG_OFFSET), cfg); + mutex_unlock(&data->lock); +} + +/* + * Sets the current TWI bus configuration + */ +static void pmcmsptwi_set_twi_config(const struct pmcmsptwi_cfg *cfg, + struct pmcmsptwi_data *data) +{ + mutex_lock(&data->lock); + pmcmsptwi_writel(pmcmsptwi_cfg_to_reg(cfg), + data->iobase + MSP_TWI_CFG_REG_OFFSET); + mutex_unlock(&data->lock); +} + +/* + * Parses the 'int_sts' register and returns a well-defined error code + */ +static enum pmcmsptwi_xfer_result pmcmsptwi_get_result(u32 reg) +{ + if (reg & MSP_TWI_INT_STS_LOST_ARBITRATION) { + dev_dbg(&pmcmsptwi_adapter.dev, + "Result: Lost arbitration\n"); + return MSP_TWI_XFER_LOST_ARBITRATION; + } else if (reg & MSP_TWI_INT_STS_NO_RESPONSE) { + dev_dbg(&pmcmsptwi_adapter.dev, + "Result: No response\n"); + return MSP_TWI_XFER_NO_RESPONSE; + } else if (reg & MSP_TWI_INT_STS_DATA_COLLISION) { + dev_dbg(&pmcmsptwi_adapter.dev, + "Result: Data collision\n"); + return MSP_TWI_XFER_DATA_COLLISION; + } else if (reg & MSP_TWI_INT_STS_BUSY) { + dev_dbg(&pmcmsptwi_adapter.dev, + "Result: Bus busy\n"); + return MSP_TWI_XFER_BUSY; + } + + dev_dbg(&pmcmsptwi_adapter.dev, "Result: Operation succeeded\n"); + return MSP_TWI_XFER_OK; +} + +/* + * In interrupt mode, handle the interrupt. + * NOTE: Assumes data->lock is held. + */ +static irqreturn_t pmcmsptwi_interrupt(int irq, void *ptr) +{ + struct pmcmsptwi_data *data = ptr; + + u32 reason = pmcmsptwi_readl(data->iobase + + MSP_TWI_INT_STS_REG_OFFSET); + pmcmsptwi_writel(reason, data->iobase + MSP_TWI_INT_STS_REG_OFFSET); + + dev_dbg(&pmcmsptwi_adapter.dev, "Got interrupt 0x%08x\n", reason); + if (!(reason & MSP_TWI_INT_STS_DONE)) + return IRQ_NONE; + + data->last_result = pmcmsptwi_get_result(reason); + complete(&data->wait); + + return IRQ_HANDLED; +} + +/* + * Probe for and register the device and return 0 if there is one. + */ +static int __devinit pmcmsptwi_probe(struct platform_device *pldev) +{ + struct resource *res; + int rc = -ENODEV; + + /* get the static platform resources */ + res = platform_get_resource(pldev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pldev->dev, "IOMEM resource not found\n"); + goto ret_err; + } + + /* reserve the memory region */ + if (!request_mem_region(res->start, res->end - res->start + 1, + pldev->name)) { + dev_err(&pldev->dev, + "Unable to get memory/io address region 0x%08x\n", + res->start); + rc = -EBUSY; + goto ret_err; + } + + /* remap the memory */ + pmcmsptwi_data.iobase = ioremap_nocache(res->start, + res->end - res->start + 1); + if (!pmcmsptwi_data.iobase) { + dev_err(&pldev->dev, + "Unable to ioremap address 0x%08x\n", res->start); + rc = -EIO; + goto ret_unreserve; + } + + /* request the irq */ + pmcmsptwi_data.irq = platform_get_irq(pldev, 0); + if (pmcmsptwi_data.irq) { + rc = request_irq(pmcmsptwi_data.irq, &pmcmsptwi_interrupt, + IRQF_SHARED | IRQF_DISABLED | IRQF_SAMPLE_RANDOM, + pldev->name, &pmcmsptwi_data); + if (rc == 0) { + /* + * Enable 'DONE' interrupt only. + * + * If you enable all interrupts, you will get one on + * error and another when the operation completes. + * This way you only have to handle one interrupt, + * but you can still check all result flags. + */ + pmcmsptwi_writel(MSP_TWI_INT_STS_DONE, + pmcmsptwi_data.iobase + + MSP_TWI_INT_MSK_REG_OFFSET); + } else { + dev_warn(&pldev->dev, + "Could not assign TWI IRQ handler " + "to irq %d (continuing with poll)\n", + pmcmsptwi_data.irq); + pmcmsptwi_data.irq = 0; + } + } + + init_completion(&pmcmsptwi_data.wait); + mutex_init(&pmcmsptwi_data.lock); + + pmcmsptwi_set_clock_config(&pmcmsptwi_defclockcfg, &pmcmsptwi_data); + pmcmsptwi_set_twi_config(&pmcmsptwi_defcfg, &pmcmsptwi_data); + + printk(KERN_INFO DRV_NAME ": Registering MSP71xx I2C adapter\n"); + + pmcmsptwi_adapter.dev.parent = &pldev->dev; + platform_set_drvdata(pldev, &pmcmsptwi_adapter); + i2c_set_adapdata(&pmcmsptwi_adapter, &pmcmsptwi_data); + + rc = i2c_add_adapter(&pmcmsptwi_adapter); + if (rc) { + dev_err(&pldev->dev, "Unable to register I2C adapter\n"); + goto ret_unmap; + } + + return 0; + +ret_unmap: + platform_set_drvdata(pldev, NULL); + if (pmcmsptwi_data.irq) { + pmcmsptwi_writel(0, + pmcmsptwi_data.iobase + MSP_TWI_INT_MSK_REG_OFFSET); + free_irq(pmcmsptwi_data.irq, &pmcmsptwi_data); + } + + iounmap(pmcmsptwi_data.iobase); + +ret_unreserve: + release_mem_region(res->start, res->end - res->start + 1); + +ret_err: + return rc; +} + +/* + * Release the device and return 0 if there is one. + */ +static int __devexit pmcmsptwi_remove(struct platform_device *pldev) +{ + struct resource *res; + + i2c_del_adapter(&pmcmsptwi_adapter); + + platform_set_drvdata(pldev, NULL); + if (pmcmsptwi_data.irq) { + pmcmsptwi_writel(0, + pmcmsptwi_data.iobase + MSP_TWI_INT_MSK_REG_OFFSET); + free_irq(pmcmsptwi_data.irq, &pmcmsptwi_data); + } + + iounmap(pmcmsptwi_data.iobase); + + res = platform_get_resource(pldev, IORESOURCE_MEM, 0); + release_mem_region(res->start, res->end - res->start + 1); + + return 0; +} + +/* + * Polls the 'busy' register until the command is complete. + * NOTE: Assumes data->lock is held. + */ +static void pmcmsptwi_poll_complete(struct pmcmsptwi_data *data) +{ + int i; + + for (i = 0; i < MSP_MAX_POLL; i++) { + u32 val = pmcmsptwi_readl(data->iobase + + MSP_TWI_BUSY_REG_OFFSET); + if (val == 0) { + u32 reason = pmcmsptwi_readl(data->iobase + + MSP_TWI_INT_STS_REG_OFFSET); + pmcmsptwi_writel(reason, data->iobase + + MSP_TWI_INT_STS_REG_OFFSET); + data->last_result = pmcmsptwi_get_result(reason); + return; + } + udelay(MSP_POLL_DELAY); + } + + dev_dbg(&pmcmsptwi_adapter.dev, "Result: Poll timeout\n"); + data->last_result = MSP_TWI_XFER_TIMEOUT; +} + +/* + * Do the transfer (low level): + * May use interrupt-driven or polling, depending on if an IRQ is + * presently registered. + * NOTE: Assumes data->lock is held. + */ +static enum pmcmsptwi_xfer_result pmcmsptwi_do_xfer( + u32 reg, struct pmcmsptwi_data *data) +{ + dev_dbg(&pmcmsptwi_adapter.dev, "Writing cmd reg 0x%08x\n", reg); + pmcmsptwi_writel(reg, data->iobase + MSP_TWI_CMD_REG_OFFSET); + if (data->irq) { + unsigned long timeleft = wait_for_completion_timeout( + &data->wait, MSP_IRQ_TIMEOUT); + if (timeleft == 0) { + dev_dbg(&pmcmsptwi_adapter.dev, + "Result: IRQ timeout\n"); + complete(&data->wait); + data->last_result = MSP_TWI_XFER_TIMEOUT; + } + } else + pmcmsptwi_poll_complete(data); + + return data->last_result; +} + +/* + * Helper routine, converts 'pmctwi_cmd' struct to register format + */ +static inline u32 pmcmsptwi_cmd_to_reg(const struct pmcmsptwi_cmd *cmd) +{ + return ((cmd->type & 0x3) << 8) | + (((cmd->write_len - 1) & 0x7) << 4) | + ((cmd->read_len - 1) & 0x7); +} + +/* + * Do the transfer (high level) + */ +static enum pmcmsptwi_xfer_result pmcmsptwi_xfer_cmd( + struct pmcmsptwi_cmd *cmd, + struct pmcmsptwi_data *data) +{ + enum pmcmsptwi_xfer_result retval; + + if ((cmd->type == MSP_TWI_CMD_WRITE && cmd->write_len == 0) || + (cmd->type == MSP_TWI_CMD_READ && cmd->read_len == 0) || + (cmd->type == MSP_TWI_CMD_WRITE_READ && + (cmd->read_len == 0 || cmd->write_len == 0))) { + dev_err(&pmcmsptwi_adapter.dev, + "%s: Cannot transfer less than 1 byte\n", + __func__); + return -EINVAL; + } + + if (cmd->read_len > MSP_MAX_BYTES_PER_RW || + cmd->write_len > MSP_MAX_BYTES_PER_RW) { + dev_err(&pmcmsptwi_adapter.dev, + "%s: Cannot transfer more than %d bytes\n", + __func__, MSP_MAX_BYTES_PER_RW); + return -EINVAL; + } + + mutex_lock(&data->lock); + dev_dbg(&pmcmsptwi_adapter.dev, + "Setting address to 0x%04x\n", cmd->addr); + pmcmsptwi_writel(cmd->addr, data->iobase + MSP_TWI_ADD_REG_OFFSET); + + if (cmd->type == MSP_TWI_CMD_WRITE || + cmd->type == MSP_TWI_CMD_WRITE_READ) { + u64 tmp = be64_to_cpup((__be64 *)cmd->write_data); + tmp >>= (MSP_MAX_BYTES_PER_RW - cmd->write_len) * 8; + dev_dbg(&pmcmsptwi_adapter.dev, "Writing 0x%016llx\n", tmp); + pmcmsptwi_writel(tmp & 0x00000000ffffffffLL, + data->iobase + MSP_TWI_DAT_0_REG_OFFSET); + if (cmd->write_len > 4) + pmcmsptwi_writel(tmp >> 32, + data->iobase + MSP_TWI_DAT_1_REG_OFFSET); + } + + retval = pmcmsptwi_do_xfer(pmcmsptwi_cmd_to_reg(cmd), data); + if (retval != MSP_TWI_XFER_OK) + goto xfer_err; + + if (cmd->type == MSP_TWI_CMD_READ || + cmd->type == MSP_TWI_CMD_WRITE_READ) { + int i; + u64 rmsk = ~(0xffffffffffffffffLL << (cmd->read_len * 8)); + u64 tmp = (u64)pmcmsptwi_readl(data->iobase + + MSP_TWI_DAT_0_REG_OFFSET); + if (cmd->read_len > 4) + tmp |= (u64)pmcmsptwi_readl(data->iobase + + MSP_TWI_DAT_1_REG_OFFSET) << 32; + tmp &= rmsk; + dev_dbg(&pmcmsptwi_adapter.dev, "Read 0x%016llx\n", tmp); + + for (i = 0; i < cmd->read_len; i++) + cmd->read_data[i] = tmp >> i; + } + +xfer_err: + mutex_unlock(&data->lock); + + return retval; +} + +/* -- Algorithm functions -- */ + +/* + * Sends an i2c command out on the adapter + */ +static int pmcmsptwi_master_xfer(struct i2c_adapter *adap, + struct i2c_msg *msg, int num) +{ + struct pmcmsptwi_data *data = i2c_get_adapdata(adap); + struct pmcmsptwi_cmd cmd; + struct pmcmsptwi_cfg oldcfg, newcfg; + int ret; + + if (num > 2) { + dev_dbg(&adap->dev, "%d messages unsupported\n", num); + return -EINVAL; + } else if (num == 2) { + /* Check for a dual write-then-read command */ + struct i2c_msg *nextmsg = msg + 1; + if (!(msg->flags & I2C_M_RD) && + (nextmsg->flags & I2C_M_RD) && + msg->addr == nextmsg->addr) { + cmd.type = MSP_TWI_CMD_WRITE_READ; + cmd.write_len = msg->len; + cmd.write_data = msg->buf; + cmd.read_len = nextmsg->len; + cmd.read_data = nextmsg->buf; + } else { + dev_dbg(&adap->dev, + "Non write-read dual messages unsupported\n"); + return -EINVAL; + } + } else if (msg->flags & I2C_M_RD) { + cmd.type = MSP_TWI_CMD_READ; + cmd.read_len = msg->len; + cmd.read_data = msg->buf; + cmd.write_len = 0; + cmd.write_data = NULL; + } else { + cmd.type = MSP_TWI_CMD_WRITE; + cmd.read_len = 0; + cmd.read_data = NULL; + cmd.write_len = msg->len; + cmd.write_data = msg->buf; + } + + if (msg->len == 0) { + dev_err(&adap->dev, "Zero-byte messages unsupported\n"); + return -EINVAL; + } + + cmd.addr = msg->addr; + + if (msg->flags & I2C_M_TEN) { + pmcmsptwi_get_twi_config(&newcfg, data); + memcpy(&oldcfg, &newcfg, sizeof(oldcfg)); + + /* Set the special 10-bit address flag */ + newcfg.add10 = 1; + + pmcmsptwi_set_twi_config(&newcfg, data); + } + + /* Execute the command */ + ret = pmcmsptwi_xfer_cmd(&cmd, data); + + if (msg->flags & I2C_M_TEN) + pmcmsptwi_set_twi_config(&oldcfg, data); + + dev_dbg(&adap->dev, "I2C %s of %d bytes %s\n", + (msg->flags & I2C_M_RD) ? "read" : "write", msg->len, + (ret == MSP_TWI_XFER_OK) ? "succeeded" : "failed"); + + if (ret != MSP_TWI_XFER_OK) { + /* + * TODO: We could potentially loop and retry in the case + * of MSP_TWI_XFER_TIMEOUT. + */ + return -1; + } + + return 0; +} + +static u32 pmcmsptwi_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C | I2C_FUNC_10BIT_ADDR | + I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_PROC_CALL; +} + +/* -- Initialization -- */ + +static struct i2c_algorithm pmcmsptwi_algo = { + .master_xfer = pmcmsptwi_master_xfer, + .functionality = pmcmsptwi_i2c_func, +}; + +static struct i2c_adapter pmcmsptwi_adapter = { + .owner = THIS_MODULE, + .class = I2C_CLASS_HWMON | I2C_CLASS_SPD, + .algo = &pmcmsptwi_algo, + .name = DRV_NAME, +}; + +/* work with hotplug and coldplug */ +MODULE_ALIAS("platform:" DRV_NAME); + +static struct platform_driver pmcmsptwi_driver = { + .probe = pmcmsptwi_probe, + .remove = __devexit_p(pmcmsptwi_remove), + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init pmcmsptwi_init(void) +{ + return platform_driver_register(&pmcmsptwi_driver); +} + +static void __exit pmcmsptwi_exit(void) +{ + platform_driver_unregister(&pmcmsptwi_driver); +} + +MODULE_DESCRIPTION("PMC MSP TWI/SMBus/I2C driver"); +MODULE_LICENSE("GPL"); + +module_init(pmcmsptwi_init); +module_exit(pmcmsptwi_exit); diff --git a/drivers/i2c/busses/i2c-pnx.c b/drivers/i2c/busses/i2c-pnx.c new file mode 100644 index 0000000..ec15cff --- /dev/null +++ b/drivers/i2c/busses/i2c-pnx.c @@ -0,0 +1,704 @@ +/* + * Provides I2C support for Philips PNX010x/PNX4008 boards. + * + * Authors: Dennis Kovalev <dkovalev@ru.mvista.com> + * Vitaly Wool <vwool@ru.mvista.com> + * + * 2004-2006 (c) MontaVista Software, Inc. This file is licensed under + * the terms of the GNU General Public License version 2. This program + * is licensed "as is" without any warranty of any kind, whether express + * or implied. + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/timer.h> +#include <linux/completion.h> +#include <linux/platform_device.h> +#include <linux/i2c-pnx.h> +#include <mach/hardware.h> +#include <asm/irq.h> +#include <asm/uaccess.h> + +#define I2C_PNX_TIMEOUT 10 /* msec */ +#define I2C_PNX_SPEED_KHZ 100 +#define I2C_PNX_REGION_SIZE 0x100 +#define PNX_DEFAULT_FREQ 13 /* MHz */ + +static inline int wait_timeout(long timeout, struct i2c_pnx_algo_data *data) +{ + while (timeout > 0 && + (ioread32(I2C_REG_STS(data)) & mstatus_active)) { + mdelay(1); + timeout--; + } + return (timeout <= 0); +} + +static inline int wait_reset(long timeout, struct i2c_pnx_algo_data *data) +{ + while (timeout > 0 && + (ioread32(I2C_REG_CTL(data)) & mcntrl_reset)) { + mdelay(1); + timeout--; + } + return (timeout <= 0); +} + +static inline void i2c_pnx_arm_timer(struct i2c_adapter *adap) +{ + struct i2c_pnx_algo_data *data = adap->algo_data; + struct timer_list *timer = &data->mif.timer; + int expires = I2C_PNX_TIMEOUT / (1000 / HZ); + + del_timer_sync(timer); + + dev_dbg(&adap->dev, "Timer armed at %lu plus %u jiffies.\n", + jiffies, expires); + + timer->expires = jiffies + expires; + timer->data = (unsigned long)adap; + + add_timer(timer); +} + +/** + * i2c_pnx_start - start a device + * @slave_addr: slave address + * @adap: pointer to adapter structure + * + * Generate a START signal in the desired mode. + */ +static int i2c_pnx_start(unsigned char slave_addr, struct i2c_adapter *adap) +{ + struct i2c_pnx_algo_data *alg_data = adap->algo_data; + + dev_dbg(&adap->dev, "%s(): addr 0x%x mode %d\n", __func__, + slave_addr, alg_data->mif.mode); + + /* Check for 7 bit slave addresses only */ + if (slave_addr & ~0x7f) { + dev_err(&adap->dev, "%s: Invalid slave address %x. " + "Only 7-bit addresses are supported\n", + adap->name, slave_addr); + return -EINVAL; + } + + /* First, make sure bus is idle */ + if (wait_timeout(I2C_PNX_TIMEOUT, alg_data)) { + /* Somebody else is monopolizing the bus */ + dev_err(&adap->dev, "%s: Bus busy. Slave addr = %02x, " + "cntrl = %x, stat = %x\n", + adap->name, slave_addr, + ioread32(I2C_REG_CTL(alg_data)), + ioread32(I2C_REG_STS(alg_data))); + return -EBUSY; + } else if (ioread32(I2C_REG_STS(alg_data)) & mstatus_afi) { + /* Sorry, we lost the bus */ + dev_err(&adap->dev, "%s: Arbitration failure. " + "Slave addr = %02x\n", adap->name, slave_addr); + return -EIO; + } + + /* + * OK, I2C is enabled and we have the bus. + * Clear the current TDI and AFI status flags. + */ + iowrite32(ioread32(I2C_REG_STS(alg_data)) | mstatus_tdi | mstatus_afi, + I2C_REG_STS(alg_data)); + + dev_dbg(&adap->dev, "%s(): sending %#x\n", __func__, + (slave_addr << 1) | start_bit | alg_data->mif.mode); + + /* Write the slave address, START bit and R/W bit */ + iowrite32((slave_addr << 1) | start_bit | alg_data->mif.mode, + I2C_REG_TX(alg_data)); + + dev_dbg(&adap->dev, "%s(): exit\n", __func__); + + return 0; +} + +/** + * i2c_pnx_stop - stop a device + * @adap: pointer to I2C adapter structure + * + * Generate a STOP signal to terminate the master transaction. + */ +static void i2c_pnx_stop(struct i2c_adapter *adap) +{ + struct i2c_pnx_algo_data *alg_data = adap->algo_data; + /* Only 1 msec max timeout due to interrupt context */ + long timeout = 1000; + + dev_dbg(&adap->dev, "%s(): entering: stat = %04x.\n", + __func__, ioread32(I2C_REG_STS(alg_data))); + + /* Write a STOP bit to TX FIFO */ + iowrite32(0xff | stop_bit, I2C_REG_TX(alg_data)); + + /* Wait until the STOP is seen. */ + while (timeout > 0 && + (ioread32(I2C_REG_STS(alg_data)) & mstatus_active)) { + /* may be called from interrupt context */ + udelay(1); + timeout--; + } + + dev_dbg(&adap->dev, "%s(): exiting: stat = %04x.\n", + __func__, ioread32(I2C_REG_STS(alg_data))); +} + +/** + * i2c_pnx_master_xmit - transmit data to slave + * @adap: pointer to I2C adapter structure + * + * Sends one byte of data to the slave + */ +static int i2c_pnx_master_xmit(struct i2c_adapter *adap) +{ + struct i2c_pnx_algo_data *alg_data = adap->algo_data; + u32 val; + + dev_dbg(&adap->dev, "%s(): entering: stat = %04x.\n", + __func__, ioread32(I2C_REG_STS(alg_data))); + + if (alg_data->mif.len > 0) { + /* We still have something to talk about... */ + val = *alg_data->mif.buf++; + + if (alg_data->mif.len == 1) { + val |= stop_bit; + if (!alg_data->last) + val |= start_bit; + } + + alg_data->mif.len--; + iowrite32(val, I2C_REG_TX(alg_data)); + + dev_dbg(&adap->dev, "%s(): xmit %#x [%d]\n", __func__, + val, alg_data->mif.len + 1); + + if (alg_data->mif.len == 0) { + if (alg_data->last) { + /* Wait until the STOP is seen. */ + if (wait_timeout(I2C_PNX_TIMEOUT, alg_data)) + dev_err(&adap->dev, "The bus is still " + "active after timeout\n"); + } + /* Disable master interrupts */ + iowrite32(ioread32(I2C_REG_CTL(alg_data)) & + ~(mcntrl_afie | mcntrl_naie | mcntrl_drmie), + I2C_REG_CTL(alg_data)); + + del_timer_sync(&alg_data->mif.timer); + + dev_dbg(&adap->dev, "%s(): Waking up xfer routine.\n", + __func__); + + complete(&alg_data->mif.complete); + } + } else if (alg_data->mif.len == 0) { + /* zero-sized transfer */ + i2c_pnx_stop(adap); + + /* Disable master interrupts. */ + iowrite32(ioread32(I2C_REG_CTL(alg_data)) & + ~(mcntrl_afie | mcntrl_naie | mcntrl_drmie), + I2C_REG_CTL(alg_data)); + + /* Stop timer. */ + del_timer_sync(&alg_data->mif.timer); + dev_dbg(&adap->dev, "%s(): Waking up xfer routine after " + "zero-xfer.\n", __func__); + + complete(&alg_data->mif.complete); + } + + dev_dbg(&adap->dev, "%s(): exiting: stat = %04x.\n", + __func__, ioread32(I2C_REG_STS(alg_data))); + + return 0; +} + +/** + * i2c_pnx_master_rcv - receive data from slave + * @adap: pointer to I2C adapter structure + * + * Reads one byte data from the slave + */ +static int i2c_pnx_master_rcv(struct i2c_adapter *adap) +{ + struct i2c_pnx_algo_data *alg_data = adap->algo_data; + unsigned int val = 0; + u32 ctl = 0; + + dev_dbg(&adap->dev, "%s(): entering: stat = %04x.\n", + __func__, ioread32(I2C_REG_STS(alg_data))); + + /* Check, whether there is already data, + * or we didn't 'ask' for it yet. + */ + if (ioread32(I2C_REG_STS(alg_data)) & mstatus_rfe) { + dev_dbg(&adap->dev, "%s(): Write dummy data to fill " + "Rx-fifo...\n", __func__); + + if (alg_data->mif.len == 1) { + /* Last byte, do not acknowledge next rcv. */ + val |= stop_bit; + if (!alg_data->last) + val |= start_bit; + + /* + * Enable interrupt RFDAIE (data in Rx fifo), + * and disable DRMIE (need data for Tx) + */ + ctl = ioread32(I2C_REG_CTL(alg_data)); + ctl |= mcntrl_rffie | mcntrl_daie; + ctl &= ~mcntrl_drmie; + iowrite32(ctl, I2C_REG_CTL(alg_data)); + } + + /* + * Now we'll 'ask' for data: + * For each byte we want to receive, we must + * write a (dummy) byte to the Tx-FIFO. + */ + iowrite32(val, I2C_REG_TX(alg_data)); + + return 0; + } + + /* Handle data. */ + if (alg_data->mif.len > 0) { + val = ioread32(I2C_REG_RX(alg_data)); + *alg_data->mif.buf++ = (u8) (val & 0xff); + dev_dbg(&adap->dev, "%s(): rcv 0x%x [%d]\n", __func__, val, + alg_data->mif.len); + + alg_data->mif.len--; + if (alg_data->mif.len == 0) { + if (alg_data->last) + /* Wait until the STOP is seen. */ + if (wait_timeout(I2C_PNX_TIMEOUT, alg_data)) + dev_err(&adap->dev, "The bus is still " + "active after timeout\n"); + + /* Disable master interrupts */ + ctl = ioread32(I2C_REG_CTL(alg_data)); + ctl &= ~(mcntrl_afie | mcntrl_naie | mcntrl_rffie | + mcntrl_drmie | mcntrl_daie); + iowrite32(ctl, I2C_REG_CTL(alg_data)); + + /* Kill timer. */ + del_timer_sync(&alg_data->mif.timer); + complete(&alg_data->mif.complete); + } + } + + dev_dbg(&adap->dev, "%s(): exiting: stat = %04x.\n", + __func__, ioread32(I2C_REG_STS(alg_data))); + + return 0; +} + +static irqreturn_t i2c_pnx_interrupt(int irq, void *dev_id) +{ + u32 stat, ctl; + struct i2c_adapter *adap = dev_id; + struct i2c_pnx_algo_data *alg_data = adap->algo_data; + + dev_dbg(&adap->dev, "%s(): mstat = %x mctrl = %x, mode = %d\n", + __func__, + ioread32(I2C_REG_STS(alg_data)), + ioread32(I2C_REG_CTL(alg_data)), + alg_data->mif.mode); + stat = ioread32(I2C_REG_STS(alg_data)); + + /* let's see what kind of event this is */ + if (stat & mstatus_afi) { + /* We lost arbitration in the midst of a transfer */ + alg_data->mif.ret = -EIO; + + /* Disable master interrupts. */ + ctl = ioread32(I2C_REG_CTL(alg_data)); + ctl &= ~(mcntrl_afie | mcntrl_naie | mcntrl_rffie | + mcntrl_drmie); + iowrite32(ctl, I2C_REG_CTL(alg_data)); + + /* Stop timer, to prevent timeout. */ + del_timer_sync(&alg_data->mif.timer); + complete(&alg_data->mif.complete); + } else if (stat & mstatus_nai) { + /* Slave did not acknowledge, generate a STOP */ + dev_dbg(&adap->dev, "%s(): " + "Slave did not acknowledge, generating a STOP.\n", + __func__); + i2c_pnx_stop(adap); + + /* Disable master interrupts. */ + ctl = ioread32(I2C_REG_CTL(alg_data)); + ctl &= ~(mcntrl_afie | mcntrl_naie | mcntrl_rffie | + mcntrl_drmie); + iowrite32(ctl, I2C_REG_CTL(alg_data)); + + /* Our return value. */ + alg_data->mif.ret = -EIO; + + /* Stop timer, to prevent timeout. */ + del_timer_sync(&alg_data->mif.timer); + complete(&alg_data->mif.complete); + } else { + /* + * Two options: + * - Master Tx needs data. + * - There is data in the Rx-fifo + * The latter is only the case if we have requested for data, + * via a dummy write. (See 'i2c_pnx_master_rcv'.) + * We therefore check, as a sanity check, whether that interrupt + * has been enabled. + */ + if ((stat & mstatus_drmi) || !(stat & mstatus_rfe)) { + if (alg_data->mif.mode == I2C_SMBUS_WRITE) { + i2c_pnx_master_xmit(adap); + } else if (alg_data->mif.mode == I2C_SMBUS_READ) { + i2c_pnx_master_rcv(adap); + } + } + } + + /* Clear TDI and AFI bits */ + stat = ioread32(I2C_REG_STS(alg_data)); + iowrite32(stat | mstatus_tdi | mstatus_afi, I2C_REG_STS(alg_data)); + + dev_dbg(&adap->dev, "%s(): exiting, stat = %x ctrl = %x.\n", + __func__, ioread32(I2C_REG_STS(alg_data)), + ioread32(I2C_REG_CTL(alg_data))); + + return IRQ_HANDLED; +} + +static void i2c_pnx_timeout(unsigned long data) +{ + struct i2c_adapter *adap = (struct i2c_adapter *)data; + struct i2c_pnx_algo_data *alg_data = adap->algo_data; + u32 ctl; + + dev_err(&adap->dev, "Master timed out. stat = %04x, cntrl = %04x. " + "Resetting master...\n", + ioread32(I2C_REG_STS(alg_data)), + ioread32(I2C_REG_CTL(alg_data))); + + /* Reset master and disable interrupts */ + ctl = ioread32(I2C_REG_CTL(alg_data)); + ctl &= ~(mcntrl_afie | mcntrl_naie | mcntrl_rffie | mcntrl_drmie); + iowrite32(ctl, I2C_REG_CTL(alg_data)); + + ctl |= mcntrl_reset; + iowrite32(ctl, I2C_REG_CTL(alg_data)); + wait_reset(I2C_PNX_TIMEOUT, alg_data); + alg_data->mif.ret = -EIO; + complete(&alg_data->mif.complete); +} + +static inline void bus_reset_if_active(struct i2c_adapter *adap) +{ + struct i2c_pnx_algo_data *alg_data = adap->algo_data; + u32 stat; + + if ((stat = ioread32(I2C_REG_STS(alg_data))) & mstatus_active) { + dev_err(&adap->dev, + "%s: Bus is still active after xfer. Reset it...\n", + adap->name); + iowrite32(ioread32(I2C_REG_CTL(alg_data)) | mcntrl_reset, + I2C_REG_CTL(alg_data)); + wait_reset(I2C_PNX_TIMEOUT, alg_data); + } else if (!(stat & mstatus_rfe) || !(stat & mstatus_tfe)) { + /* If there is data in the fifo's after transfer, + * flush fifo's by reset. + */ + iowrite32(ioread32(I2C_REG_CTL(alg_data)) | mcntrl_reset, + I2C_REG_CTL(alg_data)); + wait_reset(I2C_PNX_TIMEOUT, alg_data); + } else if (stat & mstatus_nai) { + iowrite32(ioread32(I2C_REG_CTL(alg_data)) | mcntrl_reset, + I2C_REG_CTL(alg_data)); + wait_reset(I2C_PNX_TIMEOUT, alg_data); + } +} + +/** + * i2c_pnx_xfer - generic transfer entry point + * @adap: pointer to I2C adapter structure + * @msgs: array of messages + * @num: number of messages + * + * Initiates the transfer + */ +static int +i2c_pnx_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) +{ + struct i2c_msg *pmsg; + int rc = 0, completed = 0, i; + struct i2c_pnx_algo_data *alg_data = adap->algo_data; + u32 stat = ioread32(I2C_REG_STS(alg_data)); + + dev_dbg(&adap->dev, "%s(): entering: %d messages, stat = %04x.\n", + __func__, num, ioread32(I2C_REG_STS(alg_data))); + + bus_reset_if_active(adap); + + /* Process transactions in a loop. */ + for (i = 0; rc >= 0 && i < num; i++) { + u8 addr; + + pmsg = &msgs[i]; + addr = pmsg->addr; + + if (pmsg->flags & I2C_M_TEN) { + dev_err(&adap->dev, + "%s: 10 bits addr not supported!\n", + adap->name); + rc = -EINVAL; + break; + } + + alg_data->mif.buf = pmsg->buf; + alg_data->mif.len = pmsg->len; + alg_data->mif.mode = (pmsg->flags & I2C_M_RD) ? + I2C_SMBUS_READ : I2C_SMBUS_WRITE; + alg_data->mif.ret = 0; + alg_data->last = (i == num - 1); + + dev_dbg(&adap->dev, "%s(): mode %d, %d bytes\n", __func__, + alg_data->mif.mode, + alg_data->mif.len); + + i2c_pnx_arm_timer(adap); + + /* initialize the completion var */ + init_completion(&alg_data->mif.complete); + + /* Enable master interrupt */ + iowrite32(ioread32(I2C_REG_CTL(alg_data)) | mcntrl_afie | + mcntrl_naie | mcntrl_drmie, + I2C_REG_CTL(alg_data)); + + /* Put start-code and slave-address on the bus. */ + rc = i2c_pnx_start(addr, adap); + if (rc < 0) + break; + + /* Wait for completion */ + wait_for_completion(&alg_data->mif.complete); + + if (!(rc = alg_data->mif.ret)) + completed++; + dev_dbg(&adap->dev, "%s(): Complete, return code = %d.\n", + __func__, rc); + + /* Clear TDI and AFI bits in case they are set. */ + if ((stat = ioread32(I2C_REG_STS(alg_data))) & mstatus_tdi) { + dev_dbg(&adap->dev, + "%s: TDI still set... clearing now.\n", + adap->name); + iowrite32(stat, I2C_REG_STS(alg_data)); + } + if ((stat = ioread32(I2C_REG_STS(alg_data))) & mstatus_afi) { + dev_dbg(&adap->dev, + "%s: AFI still set... clearing now.\n", + adap->name); + iowrite32(stat, I2C_REG_STS(alg_data)); + } + } + + bus_reset_if_active(adap); + + /* Cleanup to be sure... */ + alg_data->mif.buf = NULL; + alg_data->mif.len = 0; + + dev_dbg(&adap->dev, "%s(): exiting, stat = %x\n", + __func__, ioread32(I2C_REG_STS(alg_data))); + + if (completed != num) + return ((rc < 0) ? rc : -EREMOTEIO); + + return num; +} + +static u32 i2c_pnx_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static struct i2c_algorithm pnx_algorithm = { + .master_xfer = i2c_pnx_xfer, + .functionality = i2c_pnx_func, +}; + +static int i2c_pnx_controller_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct i2c_pnx_data *i2c_pnx = platform_get_drvdata(pdev); + return i2c_pnx->suspend(pdev, state); +} + +static int i2c_pnx_controller_resume(struct platform_device *pdev) +{ + struct i2c_pnx_data *i2c_pnx = platform_get_drvdata(pdev); + return i2c_pnx->resume(pdev); +} + +static int __devinit i2c_pnx_probe(struct platform_device *pdev) +{ + unsigned long tmp; + int ret = 0; + struct i2c_pnx_algo_data *alg_data; + int freq_mhz; + struct i2c_pnx_data *i2c_pnx = pdev->dev.platform_data; + + if (!i2c_pnx || !i2c_pnx->adapter) { + dev_err(&pdev->dev, "%s: no platform data supplied\n", + __func__); + ret = -EINVAL; + goto out; + } + + platform_set_drvdata(pdev, i2c_pnx); + + if (i2c_pnx->calculate_input_freq) + freq_mhz = i2c_pnx->calculate_input_freq(pdev); + else { + freq_mhz = PNX_DEFAULT_FREQ; + dev_info(&pdev->dev, "Setting bus frequency to default value: " + "%d MHz\n", freq_mhz); + } + + i2c_pnx->adapter->algo = &pnx_algorithm; + + alg_data = i2c_pnx->adapter->algo_data; + init_timer(&alg_data->mif.timer); + alg_data->mif.timer.function = i2c_pnx_timeout; + alg_data->mif.timer.data = (unsigned long)i2c_pnx->adapter; + + /* Register I/O resource */ + if (!request_region(alg_data->base, I2C_PNX_REGION_SIZE, pdev->name)) { + dev_err(&pdev->dev, + "I/O region 0x%08x for I2C already in use.\n", + alg_data->base); + ret = -ENODEV; + goto out_drvdata; + } + + if (!(alg_data->ioaddr = + (u32)ioremap(alg_data->base, I2C_PNX_REGION_SIZE))) { + dev_err(&pdev->dev, "Couldn't ioremap I2C I/O region\n"); + ret = -ENOMEM; + goto out_release; + } + + i2c_pnx->set_clock_run(pdev); + + /* + * Clock Divisor High This value is the number of system clocks + * the serial clock (SCL) will be high. + * For example, if the system clock period is 50 ns and the maximum + * desired serial period is 10000 ns (100 kHz), then CLKHI would be + * set to 0.5*(f_sys/f_i2c)-2=0.5*(20e6/100e3)-2=98. The actual value + * programmed into CLKHI will vary from this slightly due to + * variations in the output pad's rise and fall times as well as + * the deglitching filter length. + */ + + tmp = ((freq_mhz * 1000) / I2C_PNX_SPEED_KHZ) / 2 - 2; + iowrite32(tmp, I2C_REG_CKH(alg_data)); + iowrite32(tmp, I2C_REG_CKL(alg_data)); + + iowrite32(mcntrl_reset, I2C_REG_CTL(alg_data)); + if (wait_reset(I2C_PNX_TIMEOUT, alg_data)) { + ret = -ENODEV; + goto out_unmap; + } + init_completion(&alg_data->mif.complete); + + ret = request_irq(alg_data->irq, i2c_pnx_interrupt, + 0, pdev->name, i2c_pnx->adapter); + if (ret) + goto out_clock; + + /* Register this adapter with the I2C subsystem */ + i2c_pnx->adapter->dev.parent = &pdev->dev; + ret = i2c_add_adapter(i2c_pnx->adapter); + if (ret < 0) { + dev_err(&pdev->dev, "I2C: Failed to add bus\n"); + goto out_irq; + } + + dev_dbg(&pdev->dev, "%s: Master at %#8x, irq %d.\n", + i2c_pnx->adapter->name, alg_data->base, alg_data->irq); + + return 0; + +out_irq: + free_irq(alg_data->irq, alg_data); +out_clock: + i2c_pnx->set_clock_stop(pdev); +out_unmap: + iounmap((void *)alg_data->ioaddr); +out_release: + release_region(alg_data->base, I2C_PNX_REGION_SIZE); +out_drvdata: + platform_set_drvdata(pdev, NULL); +out: + return ret; +} + +static int __devexit i2c_pnx_remove(struct platform_device *pdev) +{ + struct i2c_pnx_data *i2c_pnx = platform_get_drvdata(pdev); + struct i2c_adapter *adap = i2c_pnx->adapter; + struct i2c_pnx_algo_data *alg_data = adap->algo_data; + + free_irq(alg_data->irq, alg_data); + i2c_del_adapter(adap); + i2c_pnx->set_clock_stop(pdev); + iounmap((void *)alg_data->ioaddr); + release_region(alg_data->base, I2C_PNX_REGION_SIZE); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver i2c_pnx_driver = { + .driver = { + .name = "pnx-i2c", + .owner = THIS_MODULE, + }, + .probe = i2c_pnx_probe, + .remove = __devexit_p(i2c_pnx_remove), + .suspend = i2c_pnx_controller_suspend, + .resume = i2c_pnx_controller_resume, +}; + +static int __init i2c_adap_pnx_init(void) +{ + return platform_driver_register(&i2c_pnx_driver); +} + +static void __exit i2c_adap_pnx_exit(void) +{ + platform_driver_unregister(&i2c_pnx_driver); +} + +MODULE_AUTHOR("Vitaly Wool, Dennis Kovalev <source@mvista.com>"); +MODULE_DESCRIPTION("I2C driver for Philips IP3204-based I2C busses"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:pnx-i2c"); + +/* We need to make sure I2C is initialized before USB */ +subsys_initcall(i2c_adap_pnx_init); +module_exit(i2c_adap_pnx_exit); diff --git a/drivers/i2c/busses/i2c-powermac.c b/drivers/i2c/busses/i2c-powermac.c new file mode 100644 index 0000000..60ca917 --- /dev/null +++ b/drivers/i2c/busses/i2c-powermac.c @@ -0,0 +1,320 @@ +/* + i2c Support for Apple SMU Controller + + Copyright (c) 2005 Benjamin Herrenschmidt, IBM Corp. + <benh@kernel.crashing.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <asm/prom.h> +#include <asm/pmac_low_i2c.h> + +MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); +MODULE_DESCRIPTION("I2C driver for Apple PowerMac"); +MODULE_LICENSE("GPL"); + +/* + * SMBUS-type transfer entrypoint + */ +static s32 i2c_powermac_smbus_xfer( struct i2c_adapter* adap, + u16 addr, + unsigned short flags, + char read_write, + u8 command, + int size, + union i2c_smbus_data* data) +{ + struct pmac_i2c_bus *bus = i2c_get_adapdata(adap); + int rc = 0; + int read = (read_write == I2C_SMBUS_READ); + int addrdir = (addr << 1) | read; + u8 local[2]; + + rc = pmac_i2c_open(bus, 0); + if (rc) + return rc; + + switch (size) { + case I2C_SMBUS_QUICK: + rc = pmac_i2c_setmode(bus, pmac_i2c_mode_std); + if (rc) + goto bail; + rc = pmac_i2c_xfer(bus, addrdir, 0, 0, NULL, 0); + break; + case I2C_SMBUS_BYTE: + rc = pmac_i2c_setmode(bus, pmac_i2c_mode_std); + if (rc) + goto bail; + rc = pmac_i2c_xfer(bus, addrdir, 0, 0, &data->byte, 1); + break; + case I2C_SMBUS_BYTE_DATA: + rc = pmac_i2c_setmode(bus, read ? + pmac_i2c_mode_combined : + pmac_i2c_mode_stdsub); + if (rc) + goto bail; + rc = pmac_i2c_xfer(bus, addrdir, 1, command, &data->byte, 1); + break; + case I2C_SMBUS_WORD_DATA: + rc = pmac_i2c_setmode(bus, read ? + pmac_i2c_mode_combined : + pmac_i2c_mode_stdsub); + if (rc) + goto bail; + if (!read) { + local[0] = data->word & 0xff; + local[1] = (data->word >> 8) & 0xff; + } + rc = pmac_i2c_xfer(bus, addrdir, 1, command, local, 2); + if (rc == 0 && read) { + data->word = ((u16)local[1]) << 8; + data->word |= local[0]; + } + break; + + /* Note that these are broken vs. the expected smbus API where + * on reads, the length is actually returned from the function, + * but I think the current API makes no sense and I don't want + * any driver that I haven't verified for correctness to go + * anywhere near a pmac i2c bus anyway ... + * + * I'm also not completely sure what kind of phases to do between + * the actual command and the data (what I am _supposed_ to do that + * is). For now, I assume writes are a single stream and reads have + * a repeat start/addr phase (but not stop in between) + */ + case I2C_SMBUS_BLOCK_DATA: + rc = pmac_i2c_setmode(bus, read ? + pmac_i2c_mode_combined : + pmac_i2c_mode_stdsub); + if (rc) + goto bail; + rc = pmac_i2c_xfer(bus, addrdir, 1, command, data->block, + data->block[0] + 1); + + break; + case I2C_SMBUS_I2C_BLOCK_DATA: + rc = pmac_i2c_setmode(bus, read ? + pmac_i2c_mode_combined : + pmac_i2c_mode_stdsub); + if (rc) + goto bail; + rc = pmac_i2c_xfer(bus, addrdir, 1, command, + &data->block[1], data->block[0]); + break; + + default: + rc = -EINVAL; + } + bail: + pmac_i2c_close(bus); + return rc; +} + +/* + * Generic i2c master transfer entrypoint. This driver only support single + * messages (for "lame i2c" transfers). Anything else should use the smbus + * entry point + */ +static int i2c_powermac_master_xfer( struct i2c_adapter *adap, + struct i2c_msg *msgs, + int num) +{ + struct pmac_i2c_bus *bus = i2c_get_adapdata(adap); + int rc = 0; + int read; + int addrdir; + + if (msgs->flags & I2C_M_TEN) + return -EINVAL; + read = (msgs->flags & I2C_M_RD) != 0; + addrdir = (msgs->addr << 1) | read; + if (msgs->flags & I2C_M_REV_DIR_ADDR) + addrdir ^= 1; + + rc = pmac_i2c_open(bus, 0); + if (rc) + return rc; + rc = pmac_i2c_setmode(bus, pmac_i2c_mode_std); + if (rc) + goto bail; + rc = pmac_i2c_xfer(bus, addrdir, 0, 0, msgs->buf, msgs->len); + bail: + pmac_i2c_close(bus); + return rc < 0 ? rc : 1; +} + +static u32 i2c_powermac_func(struct i2c_adapter * adapter) +{ + return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_I2C; +} + +/* For now, we only handle smbus */ +static const struct i2c_algorithm i2c_powermac_algorithm = { + .smbus_xfer = i2c_powermac_smbus_xfer, + .master_xfer = i2c_powermac_master_xfer, + .functionality = i2c_powermac_func, +}; + + +static int __devexit i2c_powermac_remove(struct platform_device *dev) +{ + struct i2c_adapter *adapter = platform_get_drvdata(dev); + struct pmac_i2c_bus *bus = i2c_get_adapdata(adapter); + int rc; + + rc = i2c_del_adapter(adapter); + pmac_i2c_detach_adapter(bus, adapter); + i2c_set_adapdata(adapter, NULL); + /* We aren't that prepared to deal with this... */ + if (rc) + printk("i2c-powermac.c: Failed to remove bus %s !\n", + adapter->name); + platform_set_drvdata(dev, NULL); + kfree(adapter); + + return 0; +} + + +static int __devinit i2c_powermac_probe(struct platform_device *dev) +{ + struct pmac_i2c_bus *bus = dev->dev.platform_data; + struct device_node *parent = NULL; + struct i2c_adapter *adapter; + char name[32]; + const char *basename; + int rc; + + if (bus == NULL) + return -EINVAL; + + /* Ok, now we need to make up a name for the interface that will + * match what we used to do in the past, that is basically the + * controller's parent device node for keywest. PMU didn't have a + * naming convention and SMU has a different one + */ + switch(pmac_i2c_get_type(bus)) { + case pmac_i2c_bus_keywest: + parent = of_get_parent(pmac_i2c_get_controller(bus)); + if (parent == NULL) + return -EINVAL; + basename = parent->name; + break; + case pmac_i2c_bus_pmu: + basename = "pmu"; + break; + case pmac_i2c_bus_smu: + /* This is not what we used to do but I'm fixing drivers at + * the same time as this change + */ + basename = "smu"; + break; + default: + return -EINVAL; + } + snprintf(name, 32, "%s %d", basename, pmac_i2c_get_channel(bus)); + of_node_put(parent); + + adapter = kzalloc(sizeof(struct i2c_adapter), GFP_KERNEL); + if (adapter == NULL) { + printk(KERN_ERR "i2c-powermac: can't allocate inteface !\n"); + return -ENOMEM; + } + platform_set_drvdata(dev, adapter); + strcpy(adapter->name, name); + adapter->algo = &i2c_powermac_algorithm; + i2c_set_adapdata(adapter, bus); + adapter->dev.parent = &dev->dev; + pmac_i2c_attach_adapter(bus, adapter); + rc = i2c_add_adapter(adapter); + if (rc) { + printk(KERN_ERR "i2c-powermac: Adapter %s registration " + "failed\n", name); + i2c_set_adapdata(adapter, NULL); + pmac_i2c_detach_adapter(bus, adapter); + } + + printk(KERN_INFO "PowerMac i2c bus %s registered\n", name); + + if (!strncmp(basename, "uni-n", 5)) { + struct device_node *np; + const u32 *prop; + struct i2c_board_info info; + + /* Instantiate I2C motion sensor if present */ + np = of_find_node_by_name(NULL, "accelerometer"); + if (np && of_device_is_compatible(np, "AAPL,accelerometer_1") && + (prop = of_get_property(np, "reg", NULL))) { + int i2c_bus; + const char *tmp_bus; + + /* look for bus either using "reg" or by path */ + tmp_bus = strstr(np->full_name, "/i2c-bus@"); + if (tmp_bus) + i2c_bus = *(tmp_bus + 9) - '0'; + else + i2c_bus = ((*prop) >> 8) & 0x0f; + + if (pmac_i2c_get_channel(bus) == i2c_bus) { + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = ((*prop) & 0xff) >> 1; + strlcpy(info.type, "ams", I2C_NAME_SIZE); + i2c_new_device(adapter, &info); + } + } + } + + return rc; +} + + +/* work with hotplug and coldplug */ +MODULE_ALIAS("platform:i2c-powermac"); + +static struct platform_driver i2c_powermac_driver = { + .probe = i2c_powermac_probe, + .remove = __devexit_p(i2c_powermac_remove), + .driver = { + .name = "i2c-powermac", + .bus = &platform_bus_type, + }, +}; + +static int __init i2c_powermac_init(void) +{ + platform_driver_register(&i2c_powermac_driver); + return 0; +} + + +static void __exit i2c_powermac_cleanup(void) +{ + platform_driver_unregister(&i2c_powermac_driver); +} + +module_init(i2c_powermac_init); +module_exit(i2c_powermac_cleanup); diff --git a/drivers/i2c/busses/i2c-pxa.c b/drivers/i2c/busses/i2c-pxa.c new file mode 100644 index 0000000..7f80d51 --- /dev/null +++ b/drivers/i2c/busses/i2c-pxa.c @@ -0,0 +1,1168 @@ +/* + * i2c_adap_pxa.c + * + * I2C adapter for the PXA I2C bus access. + * + * Copyright (C) 2002 Intrinsyc Software Inc. + * Copyright (C) 2004-2005 Deep Blue Solutions Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * History: + * Apr 2002: Initial version [CS] + * Jun 2002: Properly seperated algo/adap [FB] + * Jan 2003: Fixed several bugs concerning interrupt handling [Kai-Uwe Bloem] + * Jan 2003: added limited signal handling [Kai-Uwe Bloem] + * Sep 2004: Major rework to ensure efficient bus handling [RMK] + * Dec 2004: Added support for PXA27x and slave device probing [Liam Girdwood] + * Feb 2005: Rework slave mode handling [RMK] + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/i2c-id.h> +#include <linux/init.h> +#include <linux/time.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/i2c-pxa.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/clk.h> + +#include <mach/hardware.h> +#include <asm/irq.h> +#include <asm/io.h> +#include <mach/i2c.h> + +/* + * I2C registers and bit definitions + */ +#define IBMR (0x00) +#define IDBR (0x08) +#define ICR (0x10) +#define ISR (0x18) +#define ISAR (0x20) + +#define ICR_START (1 << 0) /* start bit */ +#define ICR_STOP (1 << 1) /* stop bit */ +#define ICR_ACKNAK (1 << 2) /* send ACK(0) or NAK(1) */ +#define ICR_TB (1 << 3) /* transfer byte bit */ +#define ICR_MA (1 << 4) /* master abort */ +#define ICR_SCLE (1 << 5) /* master clock enable */ +#define ICR_IUE (1 << 6) /* unit enable */ +#define ICR_GCD (1 << 7) /* general call disable */ +#define ICR_ITEIE (1 << 8) /* enable tx interrupts */ +#define ICR_IRFIE (1 << 9) /* enable rx interrupts */ +#define ICR_BEIE (1 << 10) /* enable bus error ints */ +#define ICR_SSDIE (1 << 11) /* slave STOP detected int enable */ +#define ICR_ALDIE (1 << 12) /* enable arbitration interrupt */ +#define ICR_SADIE (1 << 13) /* slave address detected int enable */ +#define ICR_UR (1 << 14) /* unit reset */ +#define ICR_FM (1 << 15) /* fast mode */ + +#define ISR_RWM (1 << 0) /* read/write mode */ +#define ISR_ACKNAK (1 << 1) /* ack/nak status */ +#define ISR_UB (1 << 2) /* unit busy */ +#define ISR_IBB (1 << 3) /* bus busy */ +#define ISR_SSD (1 << 4) /* slave stop detected */ +#define ISR_ALD (1 << 5) /* arbitration loss detected */ +#define ISR_ITE (1 << 6) /* tx buffer empty */ +#define ISR_IRF (1 << 7) /* rx buffer full */ +#define ISR_GCAD (1 << 8) /* general call address detected */ +#define ISR_SAD (1 << 9) /* slave address detected */ +#define ISR_BED (1 << 10) /* bus error no ACK/NAK */ + +struct pxa_i2c { + spinlock_t lock; + wait_queue_head_t wait; + struct i2c_msg *msg; + unsigned int msg_num; + unsigned int msg_idx; + unsigned int msg_ptr; + unsigned int slave_addr; + + struct i2c_adapter adap; + struct clk *clk; +#ifdef CONFIG_I2C_PXA_SLAVE + struct i2c_slave_client *slave; +#endif + + unsigned int irqlogidx; + u32 isrlog[32]; + u32 icrlog[32]; + + void __iomem *reg_base; + unsigned int reg_shift; + + unsigned long iobase; + unsigned long iosize; + + int irq; + unsigned int use_pio :1; + unsigned int fast_mode :1; +}; + +#define _IBMR(i2c) ((i2c)->reg_base + (0x0 << (i2c)->reg_shift)) +#define _IDBR(i2c) ((i2c)->reg_base + (0x4 << (i2c)->reg_shift)) +#define _ICR(i2c) ((i2c)->reg_base + (0x8 << (i2c)->reg_shift)) +#define _ISR(i2c) ((i2c)->reg_base + (0xc << (i2c)->reg_shift)) +#define _ISAR(i2c) ((i2c)->reg_base + (0x10 << (i2c)->reg_shift)) + +/* + * I2C Slave mode address + */ +#define I2C_PXA_SLAVE_ADDR 0x1 + +#ifdef DEBUG + +struct bits { + u32 mask; + const char *set; + const char *unset; +}; +#define PXA_BIT(m, s, u) { .mask = m, .set = s, .unset = u } + +static inline void +decode_bits(const char *prefix, const struct bits *bits, int num, u32 val) +{ + printk("%s %08x: ", prefix, val); + while (num--) { + const char *str = val & bits->mask ? bits->set : bits->unset; + if (str) + printk("%s ", str); + bits++; + } +} + +static const struct bits isr_bits[] = { + PXA_BIT(ISR_RWM, "RX", "TX"), + PXA_BIT(ISR_ACKNAK, "NAK", "ACK"), + PXA_BIT(ISR_UB, "Bsy", "Rdy"), + PXA_BIT(ISR_IBB, "BusBsy", "BusRdy"), + PXA_BIT(ISR_SSD, "SlaveStop", NULL), + PXA_BIT(ISR_ALD, "ALD", NULL), + PXA_BIT(ISR_ITE, "TxEmpty", NULL), + PXA_BIT(ISR_IRF, "RxFull", NULL), + PXA_BIT(ISR_GCAD, "GenCall", NULL), + PXA_BIT(ISR_SAD, "SlaveAddr", NULL), + PXA_BIT(ISR_BED, "BusErr", NULL), +}; + +static void decode_ISR(unsigned int val) +{ + decode_bits(KERN_DEBUG "ISR", isr_bits, ARRAY_SIZE(isr_bits), val); + printk("\n"); +} + +static const struct bits icr_bits[] = { + PXA_BIT(ICR_START, "START", NULL), + PXA_BIT(ICR_STOP, "STOP", NULL), + PXA_BIT(ICR_ACKNAK, "ACKNAK", NULL), + PXA_BIT(ICR_TB, "TB", NULL), + PXA_BIT(ICR_MA, "MA", NULL), + PXA_BIT(ICR_SCLE, "SCLE", "scle"), + PXA_BIT(ICR_IUE, "IUE", "iue"), + PXA_BIT(ICR_GCD, "GCD", NULL), + PXA_BIT(ICR_ITEIE, "ITEIE", NULL), + PXA_BIT(ICR_IRFIE, "IRFIE", NULL), + PXA_BIT(ICR_BEIE, "BEIE", NULL), + PXA_BIT(ICR_SSDIE, "SSDIE", NULL), + PXA_BIT(ICR_ALDIE, "ALDIE", NULL), + PXA_BIT(ICR_SADIE, "SADIE", NULL), + PXA_BIT(ICR_UR, "UR", "ur"), +}; + +#ifdef CONFIG_I2C_PXA_SLAVE +static void decode_ICR(unsigned int val) +{ + decode_bits(KERN_DEBUG "ICR", icr_bits, ARRAY_SIZE(icr_bits), val); + printk("\n"); +} +#endif + +static unsigned int i2c_debug = DEBUG; + +static void i2c_pxa_show_state(struct pxa_i2c *i2c, int lno, const char *fname) +{ + dev_dbg(&i2c->adap.dev, "state:%s:%d: ISR=%08x, ICR=%08x, IBMR=%02x\n", fname, lno, + readl(_ISR(i2c)), readl(_ICR(i2c)), readl(_IBMR(i2c))); +} + +#define show_state(i2c) i2c_pxa_show_state(i2c, __LINE__, __func__) +#else +#define i2c_debug 0 + +#define show_state(i2c) do { } while (0) +#define decode_ISR(val) do { } while (0) +#define decode_ICR(val) do { } while (0) +#endif + +#define eedbg(lvl, x...) do { if ((lvl) < 1) { printk(KERN_DEBUG "" x); } } while(0) + +static void i2c_pxa_master_complete(struct pxa_i2c *i2c, int ret); +static irqreturn_t i2c_pxa_handler(int this_irq, void *dev_id); + +static void i2c_pxa_scream_blue_murder(struct pxa_i2c *i2c, const char *why) +{ + unsigned int i; + printk("i2c: error: %s\n", why); + printk("i2c: msg_num: %d msg_idx: %d msg_ptr: %d\n", + i2c->msg_num, i2c->msg_idx, i2c->msg_ptr); + printk("i2c: ICR: %08x ISR: %08x\n" + "i2c: log: ", readl(_ICR(i2c)), readl(_ISR(i2c))); + for (i = 0; i < i2c->irqlogidx; i++) + printk("[%08x:%08x] ", i2c->isrlog[i], i2c->icrlog[i]); + printk("\n"); +} + +static inline int i2c_pxa_is_slavemode(struct pxa_i2c *i2c) +{ + return !(readl(_ICR(i2c)) & ICR_SCLE); +} + +static void i2c_pxa_abort(struct pxa_i2c *i2c) +{ + int i = 250; + + if (i2c_pxa_is_slavemode(i2c)) { + dev_dbg(&i2c->adap.dev, "%s: called in slave mode\n", __func__); + return; + } + + while ((i > 0) && (readl(_IBMR(i2c)) & 0x1) == 0) { + unsigned long icr = readl(_ICR(i2c)); + + icr &= ~ICR_START; + icr |= ICR_ACKNAK | ICR_STOP | ICR_TB; + + writel(icr, _ICR(i2c)); + + show_state(i2c); + + mdelay(1); + i --; + } + + writel(readl(_ICR(i2c)) & ~(ICR_MA | ICR_START | ICR_STOP), + _ICR(i2c)); +} + +static int i2c_pxa_wait_bus_not_busy(struct pxa_i2c *i2c) +{ + int timeout = DEF_TIMEOUT; + + while (timeout-- && readl(_ISR(i2c)) & (ISR_IBB | ISR_UB)) { + if ((readl(_ISR(i2c)) & ISR_SAD) != 0) + timeout += 4; + + msleep(2); + show_state(i2c); + } + + if (timeout <= 0) + show_state(i2c); + + return timeout <= 0 ? I2C_RETRY : 0; +} + +static int i2c_pxa_wait_master(struct pxa_i2c *i2c) +{ + unsigned long timeout = jiffies + HZ*4; + + while (time_before(jiffies, timeout)) { + if (i2c_debug > 1) + dev_dbg(&i2c->adap.dev, "%s: %ld: ISR=%08x, ICR=%08x, IBMR=%02x\n", + __func__, (long)jiffies, readl(_ISR(i2c)), readl(_ICR(i2c)), readl(_IBMR(i2c))); + + if (readl(_ISR(i2c)) & ISR_SAD) { + if (i2c_debug > 0) + dev_dbg(&i2c->adap.dev, "%s: Slave detected\n", __func__); + goto out; + } + + /* wait for unit and bus being not busy, and we also do a + * quick check of the i2c lines themselves to ensure they've + * gone high... + */ + if ((readl(_ISR(i2c)) & (ISR_UB | ISR_IBB)) == 0 && readl(_IBMR(i2c)) == 3) { + if (i2c_debug > 0) + dev_dbg(&i2c->adap.dev, "%s: done\n", __func__); + return 1; + } + + msleep(1); + } + + if (i2c_debug > 0) + dev_dbg(&i2c->adap.dev, "%s: did not free\n", __func__); + out: + return 0; +} + +static int i2c_pxa_set_master(struct pxa_i2c *i2c) +{ + if (i2c_debug) + dev_dbg(&i2c->adap.dev, "setting to bus master\n"); + + if ((readl(_ISR(i2c)) & (ISR_UB | ISR_IBB)) != 0) { + dev_dbg(&i2c->adap.dev, "%s: unit is busy\n", __func__); + if (!i2c_pxa_wait_master(i2c)) { + dev_dbg(&i2c->adap.dev, "%s: error: unit busy\n", __func__); + return I2C_RETRY; + } + } + + writel(readl(_ICR(i2c)) | ICR_SCLE, _ICR(i2c)); + return 0; +} + +#ifdef CONFIG_I2C_PXA_SLAVE +static int i2c_pxa_wait_slave(struct pxa_i2c *i2c) +{ + unsigned long timeout = jiffies + HZ*1; + + /* wait for stop */ + + show_state(i2c); + + while (time_before(jiffies, timeout)) { + if (i2c_debug > 1) + dev_dbg(&i2c->adap.dev, "%s: %ld: ISR=%08x, ICR=%08x, IBMR=%02x\n", + __func__, (long)jiffies, readl(_ISR(i2c)), readl(_ICR(i2c)), readl(_IBMR(i2c))); + + if ((readl(_ISR(i2c)) & (ISR_UB|ISR_IBB)) == 0 || + (readl(_ISR(i2c)) & ISR_SAD) != 0 || + (readl(_ICR(i2c)) & ICR_SCLE) == 0) { + if (i2c_debug > 1) + dev_dbg(&i2c->adap.dev, "%s: done\n", __func__); + return 1; + } + + msleep(1); + } + + if (i2c_debug > 0) + dev_dbg(&i2c->adap.dev, "%s: did not free\n", __func__); + return 0; +} + +/* + * clear the hold on the bus, and take of anything else + * that has been configured + */ +static void i2c_pxa_set_slave(struct pxa_i2c *i2c, int errcode) +{ + show_state(i2c); + + if (errcode < 0) { + udelay(100); /* simple delay */ + } else { + /* we need to wait for the stop condition to end */ + + /* if we where in stop, then clear... */ + if (readl(_ICR(i2c)) & ICR_STOP) { + udelay(100); + writel(readl(_ICR(i2c)) & ~ICR_STOP, _ICR(i2c)); + } + + if (!i2c_pxa_wait_slave(i2c)) { + dev_err(&i2c->adap.dev, "%s: wait timedout\n", + __func__); + return; + } + } + + writel(readl(_ICR(i2c)) & ~(ICR_STOP|ICR_ACKNAK|ICR_MA), _ICR(i2c)); + writel(readl(_ICR(i2c)) & ~ICR_SCLE, _ICR(i2c)); + + if (i2c_debug) { + dev_dbg(&i2c->adap.dev, "ICR now %08x, ISR %08x\n", readl(_ICR(i2c)), readl(_ISR(i2c))); + decode_ICR(readl(_ICR(i2c))); + } +} +#else +#define i2c_pxa_set_slave(i2c, err) do { } while (0) +#endif + +static void i2c_pxa_reset(struct pxa_i2c *i2c) +{ + pr_debug("Resetting I2C Controller Unit\n"); + + /* abort any transfer currently under way */ + i2c_pxa_abort(i2c); + + /* reset according to 9.8 */ + writel(ICR_UR, _ICR(i2c)); + writel(I2C_ISR_INIT, _ISR(i2c)); + writel(readl(_ICR(i2c)) & ~ICR_UR, _ICR(i2c)); + + writel(i2c->slave_addr, _ISAR(i2c)); + + /* set control register values */ + writel(I2C_ICR_INIT | (i2c->fast_mode ? ICR_FM : 0), _ICR(i2c)); + +#ifdef CONFIG_I2C_PXA_SLAVE + dev_info(&i2c->adap.dev, "Enabling slave mode\n"); + writel(readl(_ICR(i2c)) | ICR_SADIE | ICR_ALDIE | ICR_SSDIE, _ICR(i2c)); +#endif + + i2c_pxa_set_slave(i2c, 0); + + /* enable unit */ + writel(readl(_ICR(i2c)) | ICR_IUE, _ICR(i2c)); + udelay(100); +} + + +#ifdef CONFIG_I2C_PXA_SLAVE +/* + * PXA I2C Slave mode + */ + +static void i2c_pxa_slave_txempty(struct pxa_i2c *i2c, u32 isr) +{ + if (isr & ISR_BED) { + /* what should we do here? */ + } else { + int ret = 0; + + if (i2c->slave != NULL) + ret = i2c->slave->read(i2c->slave->data); + + writel(ret, _IDBR(i2c)); + writel(readl(_ICR(i2c)) | ICR_TB, _ICR(i2c)); /* allow next byte */ + } +} + +static void i2c_pxa_slave_rxfull(struct pxa_i2c *i2c, u32 isr) +{ + unsigned int byte = readl(_IDBR(i2c)); + + if (i2c->slave != NULL) + i2c->slave->write(i2c->slave->data, byte); + + writel(readl(_ICR(i2c)) | ICR_TB, _ICR(i2c)); +} + +static void i2c_pxa_slave_start(struct pxa_i2c *i2c, u32 isr) +{ + int timeout; + + if (i2c_debug > 0) + dev_dbg(&i2c->adap.dev, "SAD, mode is slave-%cx\n", + (isr & ISR_RWM) ? 'r' : 't'); + + if (i2c->slave != NULL) + i2c->slave->event(i2c->slave->data, + (isr & ISR_RWM) ? I2C_SLAVE_EVENT_START_READ : I2C_SLAVE_EVENT_START_WRITE); + + /* + * slave could interrupt in the middle of us generating a + * start condition... if this happens, we'd better back off + * and stop holding the poor thing up + */ + writel(readl(_ICR(i2c)) & ~(ICR_START|ICR_STOP), _ICR(i2c)); + writel(readl(_ICR(i2c)) | ICR_TB, _ICR(i2c)); + + timeout = 0x10000; + + while (1) { + if ((readl(_IBMR(i2c)) & 2) == 2) + break; + + timeout--; + + if (timeout <= 0) { + dev_err(&i2c->adap.dev, "timeout waiting for SCL high\n"); + break; + } + } + + writel(readl(_ICR(i2c)) & ~ICR_SCLE, _ICR(i2c)); +} + +static void i2c_pxa_slave_stop(struct pxa_i2c *i2c) +{ + if (i2c_debug > 2) + dev_dbg(&i2c->adap.dev, "ISR: SSD (Slave Stop)\n"); + + if (i2c->slave != NULL) + i2c->slave->event(i2c->slave->data, I2C_SLAVE_EVENT_STOP); + + if (i2c_debug > 2) + dev_dbg(&i2c->adap.dev, "ISR: SSD (Slave Stop) acked\n"); + + /* + * If we have a master-mode message waiting, + * kick it off now that the slave has completed. + */ + if (i2c->msg) + i2c_pxa_master_complete(i2c, I2C_RETRY); +} +#else +static void i2c_pxa_slave_txempty(struct pxa_i2c *i2c, u32 isr) +{ + if (isr & ISR_BED) { + /* what should we do here? */ + } else { + writel(0, _IDBR(i2c)); + writel(readl(_ICR(i2c)) | ICR_TB, _ICR(i2c)); + } +} + +static void i2c_pxa_slave_rxfull(struct pxa_i2c *i2c, u32 isr) +{ + writel(readl(_ICR(i2c)) | ICR_TB | ICR_ACKNAK, _ICR(i2c)); +} + +static void i2c_pxa_slave_start(struct pxa_i2c *i2c, u32 isr) +{ + int timeout; + + /* + * slave could interrupt in the middle of us generating a + * start condition... if this happens, we'd better back off + * and stop holding the poor thing up + */ + writel(readl(_ICR(i2c)) & ~(ICR_START|ICR_STOP), _ICR(i2c)); + writel(readl(_ICR(i2c)) | ICR_TB | ICR_ACKNAK, _ICR(i2c)); + + timeout = 0x10000; + + while (1) { + if ((readl(_IBMR(i2c)) & 2) == 2) + break; + + timeout--; + + if (timeout <= 0) { + dev_err(&i2c->adap.dev, "timeout waiting for SCL high\n"); + break; + } + } + + writel(readl(_ICR(i2c)) & ~ICR_SCLE, _ICR(i2c)); +} + +static void i2c_pxa_slave_stop(struct pxa_i2c *i2c) +{ + if (i2c->msg) + i2c_pxa_master_complete(i2c, I2C_RETRY); +} +#endif + +/* + * PXA I2C Master mode + */ + +static inline unsigned int i2c_pxa_addr_byte(struct i2c_msg *msg) +{ + unsigned int addr = (msg->addr & 0x7f) << 1; + + if (msg->flags & I2C_M_RD) + addr |= 1; + + return addr; +} + +static inline void i2c_pxa_start_message(struct pxa_i2c *i2c) +{ + u32 icr; + + /* + * Step 1: target slave address into IDBR + */ + writel(i2c_pxa_addr_byte(i2c->msg), _IDBR(i2c)); + + /* + * Step 2: initiate the write. + */ + icr = readl(_ICR(i2c)) & ~(ICR_STOP | ICR_ALDIE); + writel(icr | ICR_START | ICR_TB, _ICR(i2c)); +} + +static inline void i2c_pxa_stop_message(struct pxa_i2c *i2c) +{ + u32 icr; + + /* + * Clear the STOP and ACK flags + */ + icr = readl(_ICR(i2c)); + icr &= ~(ICR_STOP | ICR_ACKNAK); + writel(icr, _ICR(i2c)); +} + +static int i2c_pxa_pio_set_master(struct pxa_i2c *i2c) +{ + /* make timeout the same as for interrupt based functions */ + long timeout = 2 * DEF_TIMEOUT; + + /* + * Wait for the bus to become free. + */ + while (timeout-- && readl(_ISR(i2c)) & (ISR_IBB | ISR_UB)) { + udelay(1000); + show_state(i2c); + } + + if (timeout <= 0) { + show_state(i2c); + dev_err(&i2c->adap.dev, + "i2c_pxa: timeout waiting for bus free\n"); + return I2C_RETRY; + } + + /* + * Set master mode. + */ + writel(readl(_ICR(i2c)) | ICR_SCLE, _ICR(i2c)); + + return 0; +} + +static int i2c_pxa_do_pio_xfer(struct pxa_i2c *i2c, + struct i2c_msg *msg, int num) +{ + unsigned long timeout = 500000; /* 5 seconds */ + int ret = 0; + + ret = i2c_pxa_pio_set_master(i2c); + if (ret) + goto out; + + i2c->msg = msg; + i2c->msg_num = num; + i2c->msg_idx = 0; + i2c->msg_ptr = 0; + i2c->irqlogidx = 0; + + i2c_pxa_start_message(i2c); + + while (i2c->msg_num > 0 && --timeout) { + i2c_pxa_handler(0, i2c); + udelay(10); + } + + i2c_pxa_stop_message(i2c); + + /* + * We place the return code in i2c->msg_idx. + */ + ret = i2c->msg_idx; + +out: + if (timeout == 0) + i2c_pxa_scream_blue_murder(i2c, "timeout"); + + return ret; +} + +/* + * We are protected by the adapter bus mutex. + */ +static int i2c_pxa_do_xfer(struct pxa_i2c *i2c, struct i2c_msg *msg, int num) +{ + long timeout; + int ret; + + /* + * Wait for the bus to become free. + */ + ret = i2c_pxa_wait_bus_not_busy(i2c); + if (ret) { + dev_err(&i2c->adap.dev, "i2c_pxa: timeout waiting for bus free\n"); + goto out; + } + + /* + * Set master mode. + */ + ret = i2c_pxa_set_master(i2c); + if (ret) { + dev_err(&i2c->adap.dev, "i2c_pxa_set_master: error %d\n", ret); + goto out; + } + + spin_lock_irq(&i2c->lock); + + i2c->msg = msg; + i2c->msg_num = num; + i2c->msg_idx = 0; + i2c->msg_ptr = 0; + i2c->irqlogidx = 0; + + i2c_pxa_start_message(i2c); + + spin_unlock_irq(&i2c->lock); + + /* + * The rest of the processing occurs in the interrupt handler. + */ + timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5); + i2c_pxa_stop_message(i2c); + + /* + * We place the return code in i2c->msg_idx. + */ + ret = i2c->msg_idx; + + if (timeout == 0) + i2c_pxa_scream_blue_murder(i2c, "timeout"); + + out: + return ret; +} + +static int i2c_pxa_pio_xfer(struct i2c_adapter *adap, + struct i2c_msg msgs[], int num) +{ + struct pxa_i2c *i2c = adap->algo_data; + int ret, i; + + /* If the I2C controller is disabled we need to reset it + (probably due to a suspend/resume destroying state). We do + this here as we can then avoid worrying about resuming the + controller before its users. */ + if (!(readl(_ICR(i2c)) & ICR_IUE)) + i2c_pxa_reset(i2c); + + for (i = adap->retries; i >= 0; i--) { + ret = i2c_pxa_do_pio_xfer(i2c, msgs, num); + if (ret != I2C_RETRY) + goto out; + + if (i2c_debug) + dev_dbg(&adap->dev, "Retrying transmission\n"); + udelay(100); + } + i2c_pxa_scream_blue_murder(i2c, "exhausted retries"); + ret = -EREMOTEIO; + out: + i2c_pxa_set_slave(i2c, ret); + return ret; +} + +/* + * i2c_pxa_master_complete - complete the message and wake up. + */ +static void i2c_pxa_master_complete(struct pxa_i2c *i2c, int ret) +{ + i2c->msg_ptr = 0; + i2c->msg = NULL; + i2c->msg_idx ++; + i2c->msg_num = 0; + if (ret) + i2c->msg_idx = ret; + if (!i2c->use_pio) + wake_up(&i2c->wait); +} + +static void i2c_pxa_irq_txempty(struct pxa_i2c *i2c, u32 isr) +{ + u32 icr = readl(_ICR(i2c)) & ~(ICR_START|ICR_STOP|ICR_ACKNAK|ICR_TB); + + again: + /* + * If ISR_ALD is set, we lost arbitration. + */ + if (isr & ISR_ALD) { + /* + * Do we need to do anything here? The PXA docs + * are vague about what happens. + */ + i2c_pxa_scream_blue_murder(i2c, "ALD set"); + + /* + * We ignore this error. We seem to see spurious ALDs + * for seemingly no reason. If we handle them as I think + * they should, we end up causing an I2C error, which + * is painful for some systems. + */ + return; /* ignore */ + } + + if (isr & ISR_BED) { + int ret = BUS_ERROR; + + /* + * I2C bus error - either the device NAK'd us, or + * something more serious happened. If we were NAK'd + * on the initial address phase, we can retry. + */ + if (isr & ISR_ACKNAK) { + if (i2c->msg_ptr == 0 && i2c->msg_idx == 0) + ret = I2C_RETRY; + else + ret = XFER_NAKED; + } + i2c_pxa_master_complete(i2c, ret); + } else if (isr & ISR_RWM) { + /* + * Read mode. We have just sent the address byte, and + * now we must initiate the transfer. + */ + if (i2c->msg_ptr == i2c->msg->len - 1 && + i2c->msg_idx == i2c->msg_num - 1) + icr |= ICR_STOP | ICR_ACKNAK; + + icr |= ICR_ALDIE | ICR_TB; + } else if (i2c->msg_ptr < i2c->msg->len) { + /* + * Write mode. Write the next data byte. + */ + writel(i2c->msg->buf[i2c->msg_ptr++], _IDBR(i2c)); + + icr |= ICR_ALDIE | ICR_TB; + + /* + * If this is the last byte of the last message, send + * a STOP. + */ + if (i2c->msg_ptr == i2c->msg->len && + i2c->msg_idx == i2c->msg_num - 1) + icr |= ICR_STOP; + } else if (i2c->msg_idx < i2c->msg_num - 1) { + /* + * Next segment of the message. + */ + i2c->msg_ptr = 0; + i2c->msg_idx ++; + i2c->msg++; + + /* + * If we aren't doing a repeated start and address, + * go back and try to send the next byte. Note that + * we do not support switching the R/W direction here. + */ + if (i2c->msg->flags & I2C_M_NOSTART) + goto again; + + /* + * Write the next address. + */ + writel(i2c_pxa_addr_byte(i2c->msg), _IDBR(i2c)); + + /* + * And trigger a repeated start, and send the byte. + */ + icr &= ~ICR_ALDIE; + icr |= ICR_START | ICR_TB; + } else { + if (i2c->msg->len == 0) { + /* + * Device probes have a message length of zero + * and need the bus to be reset before it can + * be used again. + */ + i2c_pxa_reset(i2c); + } + i2c_pxa_master_complete(i2c, 0); + } + + i2c->icrlog[i2c->irqlogidx-1] = icr; + + writel(icr, _ICR(i2c)); + show_state(i2c); +} + +static void i2c_pxa_irq_rxfull(struct pxa_i2c *i2c, u32 isr) +{ + u32 icr = readl(_ICR(i2c)) & ~(ICR_START|ICR_STOP|ICR_ACKNAK|ICR_TB); + + /* + * Read the byte. + */ + i2c->msg->buf[i2c->msg_ptr++] = readl(_IDBR(i2c)); + + if (i2c->msg_ptr < i2c->msg->len) { + /* + * If this is the last byte of the last + * message, send a STOP. + */ + if (i2c->msg_ptr == i2c->msg->len - 1) + icr |= ICR_STOP | ICR_ACKNAK; + + icr |= ICR_ALDIE | ICR_TB; + } else { + i2c_pxa_master_complete(i2c, 0); + } + + i2c->icrlog[i2c->irqlogidx-1] = icr; + + writel(icr, _ICR(i2c)); +} + +static irqreturn_t i2c_pxa_handler(int this_irq, void *dev_id) +{ + struct pxa_i2c *i2c = dev_id; + u32 isr = readl(_ISR(i2c)); + + if (i2c_debug > 2 && 0) { + dev_dbg(&i2c->adap.dev, "%s: ISR=%08x, ICR=%08x, IBMR=%02x\n", + __func__, isr, readl(_ICR(i2c)), readl(_IBMR(i2c))); + decode_ISR(isr); + } + + if (i2c->irqlogidx < ARRAY_SIZE(i2c->isrlog)) + i2c->isrlog[i2c->irqlogidx++] = isr; + + show_state(i2c); + + /* + * Always clear all pending IRQs. + */ + writel(isr & (ISR_SSD|ISR_ALD|ISR_ITE|ISR_IRF|ISR_SAD|ISR_BED), _ISR(i2c)); + + if (isr & ISR_SAD) + i2c_pxa_slave_start(i2c, isr); + if (isr & ISR_SSD) + i2c_pxa_slave_stop(i2c); + + if (i2c_pxa_is_slavemode(i2c)) { + if (isr & ISR_ITE) + i2c_pxa_slave_txempty(i2c, isr); + if (isr & ISR_IRF) + i2c_pxa_slave_rxfull(i2c, isr); + } else if (i2c->msg) { + if (isr & ISR_ITE) + i2c_pxa_irq_txempty(i2c, isr); + if (isr & ISR_IRF) + i2c_pxa_irq_rxfull(i2c, isr); + } else { + i2c_pxa_scream_blue_murder(i2c, "spurious irq"); + } + + return IRQ_HANDLED; +} + + +static int i2c_pxa_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) +{ + struct pxa_i2c *i2c = adap->algo_data; + int ret, i; + + for (i = adap->retries; i >= 0; i--) { + ret = i2c_pxa_do_xfer(i2c, msgs, num); + if (ret != I2C_RETRY) + goto out; + + if (i2c_debug) + dev_dbg(&adap->dev, "Retrying transmission\n"); + udelay(100); + } + i2c_pxa_scream_blue_murder(i2c, "exhausted retries"); + ret = -EREMOTEIO; + out: + i2c_pxa_set_slave(i2c, ret); + return ret; +} + +static u32 i2c_pxa_functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm i2c_pxa_algorithm = { + .master_xfer = i2c_pxa_xfer, + .functionality = i2c_pxa_functionality, +}; + +static const struct i2c_algorithm i2c_pxa_pio_algorithm = { + .master_xfer = i2c_pxa_pio_xfer, + .functionality = i2c_pxa_functionality, +}; + +#define res_len(r) ((r)->end - (r)->start + 1) +static int i2c_pxa_probe(struct platform_device *dev) +{ + struct pxa_i2c *i2c; + struct resource *res; + struct i2c_pxa_platform_data *plat = dev->dev.platform_data; + int ret; + int irq; + + res = platform_get_resource(dev, IORESOURCE_MEM, 0); + irq = platform_get_irq(dev, 0); + if (res == NULL || irq < 0) + return -ENODEV; + + if (!request_mem_region(res->start, res_len(res), res->name)) + return -ENOMEM; + + i2c = kzalloc(sizeof(struct pxa_i2c), GFP_KERNEL); + if (!i2c) { + ret = -ENOMEM; + goto emalloc; + } + + i2c->adap.owner = THIS_MODULE; + i2c->adap.retries = 5; + + spin_lock_init(&i2c->lock); + init_waitqueue_head(&i2c->wait); + + /* + * If "dev->id" is negative we consider it as zero. + * The reason to do so is to avoid sysfs names that only make + * sense when there are multiple adapters. + */ + i2c->adap.nr = dev->id != -1 ? dev->id : 0; + snprintf(i2c->adap.name, sizeof(i2c->adap.name), "pxa_i2c-i2c.%u", + i2c->adap.nr); + + i2c->clk = clk_get(&dev->dev, "I2CCLK"); + if (IS_ERR(i2c->clk)) { + ret = PTR_ERR(i2c->clk); + goto eclk; + } + + i2c->reg_base = ioremap(res->start, res_len(res)); + if (!i2c->reg_base) { + ret = -EIO; + goto eremap; + } + i2c->reg_shift = (cpu_is_pxa3xx() && (dev->id == 1)) ? 0 : 1; + + i2c->iobase = res->start; + i2c->iosize = res_len(res); + + i2c->irq = irq; + + i2c->slave_addr = I2C_PXA_SLAVE_ADDR; + +#ifdef CONFIG_I2C_PXA_SLAVE + if (plat) { + i2c->slave_addr = plat->slave_addr; + i2c->slave = plat->slave; + } +#endif + + clk_enable(i2c->clk); + + if (plat) { + i2c->adap.class = plat->class; + i2c->use_pio = plat->use_pio; + i2c->fast_mode = plat->fast_mode; + } + + if (i2c->use_pio) { + i2c->adap.algo = &i2c_pxa_pio_algorithm; + } else { + i2c->adap.algo = &i2c_pxa_algorithm; + ret = request_irq(irq, i2c_pxa_handler, IRQF_DISABLED, + i2c->adap.name, i2c); + if (ret) + goto ereqirq; + } + + i2c_pxa_reset(i2c); + + i2c->adap.algo_data = i2c; + i2c->adap.dev.parent = &dev->dev; + + ret = i2c_add_numbered_adapter(&i2c->adap); + if (ret < 0) { + printk(KERN_INFO "I2C: Failed to add bus\n"); + goto eadapt; + } + + platform_set_drvdata(dev, i2c); + +#ifdef CONFIG_I2C_PXA_SLAVE + printk(KERN_INFO "I2C: %s: PXA I2C adapter, slave address %d\n", + i2c->adap.dev.bus_id, i2c->slave_addr); +#else + printk(KERN_INFO "I2C: %s: PXA I2C adapter\n", + i2c->adap.dev.bus_id); +#endif + return 0; + +eadapt: + if (!i2c->use_pio) + free_irq(irq, i2c); +ereqirq: + clk_disable(i2c->clk); + iounmap(i2c->reg_base); +eremap: + clk_put(i2c->clk); +eclk: + kfree(i2c); +emalloc: + release_mem_region(res->start, res_len(res)); + return ret; +} + +static int __exit i2c_pxa_remove(struct platform_device *dev) +{ + struct pxa_i2c *i2c = platform_get_drvdata(dev); + + platform_set_drvdata(dev, NULL); + + i2c_del_adapter(&i2c->adap); + if (!i2c->use_pio) + free_irq(i2c->irq, i2c); + + clk_disable(i2c->clk); + clk_put(i2c->clk); + + iounmap(i2c->reg_base); + release_mem_region(i2c->iobase, i2c->iosize); + kfree(i2c); + + return 0; +} + +#ifdef CONFIG_PM +static int i2c_pxa_suspend_late(struct platform_device *dev, pm_message_t state) +{ + struct pxa_i2c *i2c = platform_get_drvdata(dev); + clk_disable(i2c->clk); + return 0; +} + +static int i2c_pxa_resume_early(struct platform_device *dev) +{ + struct pxa_i2c *i2c = platform_get_drvdata(dev); + + clk_enable(i2c->clk); + i2c_pxa_reset(i2c); + + return 0; +} +#else +#define i2c_pxa_suspend_late NULL +#define i2c_pxa_resume_early NULL +#endif + +static struct platform_driver i2c_pxa_driver = { + .probe = i2c_pxa_probe, + .remove = __exit_p(i2c_pxa_remove), + .suspend_late = i2c_pxa_suspend_late, + .resume_early = i2c_pxa_resume_early, + .driver = { + .name = "pxa2xx-i2c", + .owner = THIS_MODULE, + }, +}; + +static int __init i2c_adap_pxa_init(void) +{ + return platform_driver_register(&i2c_pxa_driver); +} + +static void __exit i2c_adap_pxa_exit(void) +{ + platform_driver_unregister(&i2c_pxa_driver); +} + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:pxa2xx-i2c"); + +subsys_initcall(i2c_adap_pxa_init); +module_exit(i2c_adap_pxa_exit); diff --git a/drivers/i2c/busses/i2c-s3c2410.c b/drivers/i2c/busses/i2c-s3c2410.c new file mode 100644 index 0000000..b7434d2 --- /dev/null +++ b/drivers/i2c/busses/i2c-s3c2410.c @@ -0,0 +1,1064 @@ +/* linux/drivers/i2c/busses/i2c-s3c2410.c + * + * Copyright (C) 2004,2005 Simtec Electronics + * Ben Dooks <ben@simtec.co.uk> + * + * S3C2410 I2C Controller + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include <linux/kernel.h> +#include <linux/module.h> + +#include <linux/i2c.h> +#include <linux/i2c-id.h> +#include <linux/init.h> +#include <linux/time.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/cpufreq.h> + +#include <mach/hardware.h> +#include <asm/irq.h> +#include <asm/io.h> + +#include <mach/regs-gpio.h> +#include <asm/plat-s3c/regs-iic.h> +#include <asm/plat-s3c/iic.h> + +/* i2c controller state */ + +enum s3c24xx_i2c_state { + STATE_IDLE, + STATE_START, + STATE_READ, + STATE_WRITE, + STATE_STOP +}; + +struct s3c24xx_i2c { + spinlock_t lock; + wait_queue_head_t wait; + unsigned int suspended:1; + + struct i2c_msg *msg; + unsigned int msg_num; + unsigned int msg_idx; + unsigned int msg_ptr; + + unsigned int tx_setup; + + enum s3c24xx_i2c_state state; + unsigned long clkrate; + + void __iomem *regs; + struct clk *clk; + struct device *dev; + struct resource *irq; + struct resource *ioarea; + struct i2c_adapter adap; + +#ifdef CONFIG_CPU_FREQ + struct notifier_block freq_transition; +#endif +}; + +/* default platform data to use if not supplied in the platform_device +*/ + +static struct s3c2410_platform_i2c s3c24xx_i2c_default_platform = { + .flags = 0, + .slave_addr = 0x10, + .bus_freq = 100*1000, + .max_freq = 400*1000, + .sda_delay = S3C2410_IICLC_SDA_DELAY5 | S3C2410_IICLC_FILTER_ON, +}; + +/* s3c24xx_i2c_is2440() + * + * return true is this is an s3c2440 +*/ + +static inline int s3c24xx_i2c_is2440(struct s3c24xx_i2c *i2c) +{ + struct platform_device *pdev = to_platform_device(i2c->dev); + + return !strcmp(pdev->name, "s3c2440-i2c"); +} + + +/* s3c24xx_i2c_get_platformdata + * + * get the platform data associated with the given device, or return + * the default if there is none +*/ + +static inline struct s3c2410_platform_i2c *s3c24xx_i2c_get_platformdata(struct device *dev) +{ + if (dev->platform_data != NULL) + return (struct s3c2410_platform_i2c *)dev->platform_data; + + return &s3c24xx_i2c_default_platform; +} + +/* s3c24xx_i2c_master_complete + * + * complete the message and wake up the caller, using the given return code, + * or zero to mean ok. +*/ + +static inline void s3c24xx_i2c_master_complete(struct s3c24xx_i2c *i2c, int ret) +{ + dev_dbg(i2c->dev, "master_complete %d\n", ret); + + i2c->msg_ptr = 0; + i2c->msg = NULL; + i2c->msg_idx ++; + i2c->msg_num = 0; + if (ret) + i2c->msg_idx = ret; + + wake_up(&i2c->wait); +} + +static inline void s3c24xx_i2c_disable_ack(struct s3c24xx_i2c *i2c) +{ + unsigned long tmp; + + tmp = readl(i2c->regs + S3C2410_IICCON); + writel(tmp & ~S3C2410_IICCON_ACKEN, i2c->regs + S3C2410_IICCON); + +} + +static inline void s3c24xx_i2c_enable_ack(struct s3c24xx_i2c *i2c) +{ + unsigned long tmp; + + tmp = readl(i2c->regs + S3C2410_IICCON); + writel(tmp | S3C2410_IICCON_ACKEN, i2c->regs + S3C2410_IICCON); + +} + +/* irq enable/disable functions */ + +static inline void s3c24xx_i2c_disable_irq(struct s3c24xx_i2c *i2c) +{ + unsigned long tmp; + + tmp = readl(i2c->regs + S3C2410_IICCON); + writel(tmp & ~S3C2410_IICCON_IRQEN, i2c->regs + S3C2410_IICCON); +} + +static inline void s3c24xx_i2c_enable_irq(struct s3c24xx_i2c *i2c) +{ + unsigned long tmp; + + tmp = readl(i2c->regs + S3C2410_IICCON); + writel(tmp | S3C2410_IICCON_IRQEN, i2c->regs + S3C2410_IICCON); +} + + +/* s3c24xx_i2c_message_start + * + * put the start of a message onto the bus +*/ + +static void s3c24xx_i2c_message_start(struct s3c24xx_i2c *i2c, + struct i2c_msg *msg) +{ + unsigned int addr = (msg->addr & 0x7f) << 1; + unsigned long stat; + unsigned long iiccon; + + stat = 0; + stat |= S3C2410_IICSTAT_TXRXEN; + + if (msg->flags & I2C_M_RD) { + stat |= S3C2410_IICSTAT_MASTER_RX; + addr |= 1; + } else + stat |= S3C2410_IICSTAT_MASTER_TX; + + if (msg->flags & I2C_M_REV_DIR_ADDR) + addr ^= 1; + + // todo - check for wether ack wanted or not + s3c24xx_i2c_enable_ack(i2c); + + iiccon = readl(i2c->regs + S3C2410_IICCON); + writel(stat, i2c->regs + S3C2410_IICSTAT); + + dev_dbg(i2c->dev, "START: %08lx to IICSTAT, %02x to DS\n", stat, addr); + writeb(addr, i2c->regs + S3C2410_IICDS); + + /* delay here to ensure the data byte has gotten onto the bus + * before the transaction is started */ + + ndelay(i2c->tx_setup); + + dev_dbg(i2c->dev, "iiccon, %08lx\n", iiccon); + writel(iiccon, i2c->regs + S3C2410_IICCON); + + stat |= S3C2410_IICSTAT_START; + writel(stat, i2c->regs + S3C2410_IICSTAT); +} + +static inline void s3c24xx_i2c_stop(struct s3c24xx_i2c *i2c, int ret) +{ + unsigned long iicstat = readl(i2c->regs + S3C2410_IICSTAT); + + dev_dbg(i2c->dev, "STOP\n"); + + /* stop the transfer */ + iicstat &= ~ S3C2410_IICSTAT_START; + writel(iicstat, i2c->regs + S3C2410_IICSTAT); + + i2c->state = STATE_STOP; + + s3c24xx_i2c_master_complete(i2c, ret); + s3c24xx_i2c_disable_irq(i2c); +} + +/* helper functions to determine the current state in the set of + * messages we are sending */ + +/* is_lastmsg() + * + * returns TRUE if the current message is the last in the set +*/ + +static inline int is_lastmsg(struct s3c24xx_i2c *i2c) +{ + return i2c->msg_idx >= (i2c->msg_num - 1); +} + +/* is_msglast + * + * returns TRUE if we this is the last byte in the current message +*/ + +static inline int is_msglast(struct s3c24xx_i2c *i2c) +{ + return i2c->msg_ptr == i2c->msg->len-1; +} + +/* is_msgend + * + * returns TRUE if we reached the end of the current message +*/ + +static inline int is_msgend(struct s3c24xx_i2c *i2c) +{ + return i2c->msg_ptr >= i2c->msg->len; +} + +/* i2s_s3c_irq_nextbyte + * + * process an interrupt and work out what to do + */ + +static int i2s_s3c_irq_nextbyte(struct s3c24xx_i2c *i2c, unsigned long iicstat) +{ + unsigned long tmp; + unsigned char byte; + int ret = 0; + + switch (i2c->state) { + + case STATE_IDLE: + dev_err(i2c->dev, "%s: called in STATE_IDLE\n", __func__); + goto out; + break; + + case STATE_STOP: + dev_err(i2c->dev, "%s: called in STATE_STOP\n", __func__); + s3c24xx_i2c_disable_irq(i2c); + goto out_ack; + + case STATE_START: + /* last thing we did was send a start condition on the + * bus, or started a new i2c message + */ + + if (iicstat & S3C2410_IICSTAT_LASTBIT && + !(i2c->msg->flags & I2C_M_IGNORE_NAK)) { + /* ack was not received... */ + + dev_dbg(i2c->dev, "ack was not received\n"); + s3c24xx_i2c_stop(i2c, -ENXIO); + goto out_ack; + } + + if (i2c->msg->flags & I2C_M_RD) + i2c->state = STATE_READ; + else + i2c->state = STATE_WRITE; + + /* terminate the transfer if there is nothing to do + * as this is used by the i2c probe to find devices. */ + + if (is_lastmsg(i2c) && i2c->msg->len == 0) { + s3c24xx_i2c_stop(i2c, 0); + goto out_ack; + } + + if (i2c->state == STATE_READ) + goto prepare_read; + + /* fall through to the write state, as we will need to + * send a byte as well */ + + case STATE_WRITE: + /* we are writing data to the device... check for the + * end of the message, and if so, work out what to do + */ + + if (!(i2c->msg->flags & I2C_M_IGNORE_NAK)) { + if (iicstat & S3C2410_IICSTAT_LASTBIT) { + dev_dbg(i2c->dev, "WRITE: No Ack\n"); + + s3c24xx_i2c_stop(i2c, -ECONNREFUSED); + goto out_ack; + } + } + + retry_write: + + if (!is_msgend(i2c)) { + byte = i2c->msg->buf[i2c->msg_ptr++]; + writeb(byte, i2c->regs + S3C2410_IICDS); + + /* delay after writing the byte to allow the + * data setup time on the bus, as writing the + * data to the register causes the first bit + * to appear on SDA, and SCL will change as + * soon as the interrupt is acknowledged */ + + ndelay(i2c->tx_setup); + + } else if (!is_lastmsg(i2c)) { + /* we need to go to the next i2c message */ + + dev_dbg(i2c->dev, "WRITE: Next Message\n"); + + i2c->msg_ptr = 0; + i2c->msg_idx ++; + i2c->msg++; + + /* check to see if we need to do another message */ + if (i2c->msg->flags & I2C_M_NOSTART) { + + if (i2c->msg->flags & I2C_M_RD) { + /* cannot do this, the controller + * forces us to send a new START + * when we change direction */ + + s3c24xx_i2c_stop(i2c, -EINVAL); + } + + goto retry_write; + } else { + + /* send the new start */ + s3c24xx_i2c_message_start(i2c, i2c->msg); + i2c->state = STATE_START; + } + + } else { + /* send stop */ + + s3c24xx_i2c_stop(i2c, 0); + } + break; + + case STATE_READ: + /* we have a byte of data in the data register, do + * something with it, and then work out wether we are + * going to do any more read/write + */ + + byte = readb(i2c->regs + S3C2410_IICDS); + i2c->msg->buf[i2c->msg_ptr++] = byte; + + prepare_read: + if (is_msglast(i2c)) { + /* last byte of buffer */ + + if (is_lastmsg(i2c)) + s3c24xx_i2c_disable_ack(i2c); + + } else if (is_msgend(i2c)) { + /* ok, we've read the entire buffer, see if there + * is anything else we need to do */ + + if (is_lastmsg(i2c)) { + /* last message, send stop and complete */ + dev_dbg(i2c->dev, "READ: Send Stop\n"); + + s3c24xx_i2c_stop(i2c, 0); + } else { + /* go to the next transfer */ + dev_dbg(i2c->dev, "READ: Next Transfer\n"); + + i2c->msg_ptr = 0; + i2c->msg_idx++; + i2c->msg++; + } + } + + break; + } + + /* acknowlegde the IRQ and get back on with the work */ + + out_ack: + tmp = readl(i2c->regs + S3C2410_IICCON); + tmp &= ~S3C2410_IICCON_IRQPEND; + writel(tmp, i2c->regs + S3C2410_IICCON); + out: + return ret; +} + +/* s3c24xx_i2c_irq + * + * top level IRQ servicing routine +*/ + +static irqreturn_t s3c24xx_i2c_irq(int irqno, void *dev_id) +{ + struct s3c24xx_i2c *i2c = dev_id; + unsigned long status; + unsigned long tmp; + + status = readl(i2c->regs + S3C2410_IICSTAT); + + if (status & S3C2410_IICSTAT_ARBITR) { + // deal with arbitration loss + dev_err(i2c->dev, "deal with arbitration loss\n"); + } + + if (i2c->state == STATE_IDLE) { + dev_dbg(i2c->dev, "IRQ: error i2c->state == IDLE\n"); + + tmp = readl(i2c->regs + S3C2410_IICCON); + tmp &= ~S3C2410_IICCON_IRQPEND; + writel(tmp, i2c->regs + S3C2410_IICCON); + goto out; + } + + /* pretty much this leaves us with the fact that we've + * transmitted or received whatever byte we last sent */ + + i2s_s3c_irq_nextbyte(i2c, status); + + out: + return IRQ_HANDLED; +} + + +/* s3c24xx_i2c_set_master + * + * get the i2c bus for a master transaction +*/ + +static int s3c24xx_i2c_set_master(struct s3c24xx_i2c *i2c) +{ + unsigned long iicstat; + int timeout = 400; + + while (timeout-- > 0) { + iicstat = readl(i2c->regs + S3C2410_IICSTAT); + + if (!(iicstat & S3C2410_IICSTAT_BUSBUSY)) + return 0; + + msleep(1); + } + + dev_dbg(i2c->dev, "timeout: GPEDAT is %08x\n", + __raw_readl(S3C2410_GPEDAT)); + + return -ETIMEDOUT; +} + +/* s3c24xx_i2c_doxfer + * + * this starts an i2c transfer +*/ + +static int s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c, struct i2c_msg *msgs, int num) +{ + unsigned long timeout; + int ret; + + if (i2c->suspended) + return -EIO; + + ret = s3c24xx_i2c_set_master(i2c); + if (ret != 0) { + dev_err(i2c->dev, "cannot get bus (error %d)\n", ret); + ret = -EAGAIN; + goto out; + } + + spin_lock_irq(&i2c->lock); + + i2c->msg = msgs; + i2c->msg_num = num; + i2c->msg_ptr = 0; + i2c->msg_idx = 0; + i2c->state = STATE_START; + + s3c24xx_i2c_enable_irq(i2c); + s3c24xx_i2c_message_start(i2c, msgs); + spin_unlock_irq(&i2c->lock); + + timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5); + + ret = i2c->msg_idx; + + /* having these next two as dev_err() makes life very + * noisy when doing an i2cdetect */ + + if (timeout == 0) + dev_dbg(i2c->dev, "timeout\n"); + else if (ret != num) + dev_dbg(i2c->dev, "incomplete xfer (%d)\n", ret); + + /* ensure the stop has been through the bus */ + + msleep(1); + + out: + return ret; +} + +/* s3c24xx_i2c_xfer + * + * first port of call from the i2c bus code when an message needs + * transferring across the i2c bus. +*/ + +static int s3c24xx_i2c_xfer(struct i2c_adapter *adap, + struct i2c_msg *msgs, int num) +{ + struct s3c24xx_i2c *i2c = (struct s3c24xx_i2c *)adap->algo_data; + int retry; + int ret; + + for (retry = 0; retry < adap->retries; retry++) { + + ret = s3c24xx_i2c_doxfer(i2c, msgs, num); + + if (ret != -EAGAIN) + return ret; + + dev_dbg(i2c->dev, "Retrying transmission (%d)\n", retry); + + udelay(100); + } + + return -EREMOTEIO; +} + +/* declare our i2c functionality */ +static u32 s3c24xx_i2c_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_PROTOCOL_MANGLING; +} + +/* i2c bus registration info */ + +static const struct i2c_algorithm s3c24xx_i2c_algorithm = { + .master_xfer = s3c24xx_i2c_xfer, + .functionality = s3c24xx_i2c_func, +}; + +static struct s3c24xx_i2c s3c24xx_i2c = { + .lock = __SPIN_LOCK_UNLOCKED(s3c24xx_i2c.lock), + .wait = __WAIT_QUEUE_HEAD_INITIALIZER(s3c24xx_i2c.wait), + .tx_setup = 50, + .adap = { + .name = "s3c2410-i2c", + .owner = THIS_MODULE, + .algo = &s3c24xx_i2c_algorithm, + .retries = 2, + .class = I2C_CLASS_HWMON | I2C_CLASS_SPD, + }, +}; + +/* s3c24xx_i2c_calcdivisor + * + * return the divisor settings for a given frequency +*/ + +static int s3c24xx_i2c_calcdivisor(unsigned long clkin, unsigned int wanted, + unsigned int *div1, unsigned int *divs) +{ + unsigned int calc_divs = clkin / wanted; + unsigned int calc_div1; + + if (calc_divs > (16*16)) + calc_div1 = 512; + else + calc_div1 = 16; + + calc_divs += calc_div1-1; + calc_divs /= calc_div1; + + if (calc_divs == 0) + calc_divs = 1; + if (calc_divs > 17) + calc_divs = 17; + + *divs = calc_divs; + *div1 = calc_div1; + + return clkin / (calc_divs * calc_div1); +} + +/* freq_acceptable + * + * test wether a frequency is within the acceptable range of error +*/ + +static inline int freq_acceptable(unsigned int freq, unsigned int wanted) +{ + int diff = freq - wanted; + + return (diff >= -2 && diff <= 2); +} + +/* s3c24xx_i2c_clockrate + * + * work out a divisor for the user requested frequency setting, + * either by the requested frequency, or scanning the acceptable + * range of frequencies until something is found +*/ + +static int s3c24xx_i2c_clockrate(struct s3c24xx_i2c *i2c, unsigned int *got) +{ + struct s3c2410_platform_i2c *pdata; + unsigned long clkin = clk_get_rate(i2c->clk); + unsigned int divs, div1; + u32 iiccon; + int freq; + int start, end; + + i2c->clkrate = clkin; + + pdata = s3c24xx_i2c_get_platformdata(i2c->adap.dev.parent); + clkin /= 1000; /* clkin now in KHz */ + + dev_dbg(i2c->dev, "pdata %p, freq %lu %lu..%lu\n", + pdata, pdata->bus_freq, pdata->min_freq, pdata->max_freq); + + if (pdata->bus_freq != 0) { + freq = s3c24xx_i2c_calcdivisor(clkin, pdata->bus_freq/1000, + &div1, &divs); + if (freq_acceptable(freq, pdata->bus_freq/1000)) + goto found; + } + + /* ok, we may have to search for something suitable... */ + + start = (pdata->max_freq == 0) ? pdata->bus_freq : pdata->max_freq; + end = pdata->min_freq; + + start /= 1000; + end /= 1000; + + /* search loop... */ + + for (; start > end; start--) { + freq = s3c24xx_i2c_calcdivisor(clkin, start, &div1, &divs); + if (freq_acceptable(freq, start)) + goto found; + } + + /* cannot find frequency spec */ + + return -EINVAL; + + found: + *got = freq; + + iiccon = readl(i2c->regs + S3C2410_IICCON); + iiccon &= ~(S3C2410_IICCON_SCALEMASK | S3C2410_IICCON_TXDIV_512); + iiccon |= (divs-1); + + if (div1 == 512) + iiccon |= S3C2410_IICCON_TXDIV_512; + + writel(iiccon, i2c->regs + S3C2410_IICCON); + + return 0; +} + +#ifdef CONFIG_CPU_FREQ + +#define freq_to_i2c(_n) container_of(_n, struct s3c24xx_i2c, freq_transition) + +static int s3c24xx_i2c_cpufreq_transition(struct notifier_block *nb, + unsigned long val, void *data) +{ + struct s3c24xx_i2c *i2c = freq_to_i2c(nb); + unsigned long flags; + unsigned int got; + int delta_f; + int ret; + + delta_f = clk_get_rate(i2c->clk) - i2c->clkrate; + + /* if we're post-change and the input clock has slowed down + * or at pre-change and the clock is about to speed up, then + * adjust our clock rate. <0 is slow, >0 speedup. + */ + + if ((val == CPUFREQ_POSTCHANGE && delta_f < 0) || + (val == CPUFREQ_PRECHANGE && delta_f > 0)) { + spin_lock_irqsave(&i2c->lock, flags); + ret = s3c24xx_i2c_clockrate(i2c, &got); + spin_unlock_irqrestore(&i2c->lock, flags); + + if (ret < 0) + dev_err(i2c->dev, "cannot find frequency\n"); + else + dev_info(i2c->dev, "setting freq %d\n", got); + } + + return 0; +} + +static inline int s3c24xx_i2c_register_cpufreq(struct s3c24xx_i2c *i2c) +{ + i2c->freq_transition.notifier_call = s3c24xx_i2c_cpufreq_transition; + + return cpufreq_register_notifier(&i2c->freq_transition, + CPUFREQ_TRANSITION_NOTIFIER); +} + +static inline void s3c24xx_i2c_deregister_cpufreq(struct s3c24xx_i2c *i2c) +{ + cpufreq_unregister_notifier(&i2c->freq_transition, + CPUFREQ_TRANSITION_NOTIFIER); +} + +#else +static inline int s3c24xx_i2c_register_cpufreq(struct s3c24xx_i2c *i2c) +{ + return 0; +} + +static inline void s3c24xx_i2c_deregister_cpufreq(struct s3c24xx_i2c *i2c) +{ +} +#endif + +/* s3c24xx_i2c_init + * + * initialise the controller, set the IO lines and frequency +*/ + +static int s3c24xx_i2c_init(struct s3c24xx_i2c *i2c) +{ + unsigned long iicon = S3C2410_IICCON_IRQEN | S3C2410_IICCON_ACKEN; + struct s3c2410_platform_i2c *pdata; + unsigned int freq; + + /* get the plafrom data */ + + pdata = s3c24xx_i2c_get_platformdata(i2c->adap.dev.parent); + + /* inititalise the gpio */ + + s3c2410_gpio_cfgpin(S3C2410_GPE15, S3C2410_GPE15_IICSDA); + s3c2410_gpio_cfgpin(S3C2410_GPE14, S3C2410_GPE14_IICSCL); + + /* write slave address */ + + writeb(pdata->slave_addr, i2c->regs + S3C2410_IICADD); + + dev_info(i2c->dev, "slave address 0x%02x\n", pdata->slave_addr); + + writel(iicon, i2c->regs + S3C2410_IICCON); + + /* we need to work out the divisors for the clock... */ + + if (s3c24xx_i2c_clockrate(i2c, &freq) != 0) { + writel(0, i2c->regs + S3C2410_IICCON); + dev_err(i2c->dev, "cannot meet bus frequency required\n"); + return -EINVAL; + } + + /* todo - check that the i2c lines aren't being dragged anywhere */ + + dev_info(i2c->dev, "bus frequency set to %d KHz\n", freq); + dev_dbg(i2c->dev, "S3C2410_IICCON=0x%02lx\n", iicon); + + /* check for s3c2440 i2c controller */ + + if (s3c24xx_i2c_is2440(i2c)) { + dev_dbg(i2c->dev, "S3C2440_IICLC=%08x\n", pdata->sda_delay); + + writel(pdata->sda_delay, i2c->regs + S3C2440_IICLC); + } + + return 0; +} + +/* s3c24xx_i2c_probe + * + * called by the bus driver when a suitable device is found +*/ + +static int s3c24xx_i2c_probe(struct platform_device *pdev) +{ + struct s3c24xx_i2c *i2c = &s3c24xx_i2c; + struct s3c2410_platform_i2c *pdata; + struct resource *res; + int ret; + + pdata = s3c24xx_i2c_get_platformdata(&pdev->dev); + + /* find the clock and enable it */ + + i2c->dev = &pdev->dev; + i2c->clk = clk_get(&pdev->dev, "i2c"); + if (IS_ERR(i2c->clk)) { + dev_err(&pdev->dev, "cannot get clock\n"); + ret = -ENOENT; + goto err_noclk; + } + + dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk); + + clk_enable(i2c->clk); + + /* map the registers */ + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "cannot find IO resource\n"); + ret = -ENOENT; + goto err_clk; + } + + i2c->ioarea = request_mem_region(res->start, (res->end-res->start)+1, + pdev->name); + + if (i2c->ioarea == NULL) { + dev_err(&pdev->dev, "cannot request IO\n"); + ret = -ENXIO; + goto err_clk; + } + + i2c->regs = ioremap(res->start, (res->end-res->start)+1); + + if (i2c->regs == NULL) { + dev_err(&pdev->dev, "cannot map IO\n"); + ret = -ENXIO; + goto err_ioarea; + } + + dev_dbg(&pdev->dev, "registers %p (%p, %p)\n", i2c->regs, i2c->ioarea, res); + + /* setup info block for the i2c core */ + + i2c->adap.algo_data = i2c; + i2c->adap.dev.parent = &pdev->dev; + + /* initialise the i2c controller */ + + ret = s3c24xx_i2c_init(i2c); + if (ret != 0) + goto err_iomap; + + /* find the IRQ for this unit (note, this relies on the init call to + * ensure no current IRQs pending + */ + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (res == NULL) { + dev_err(&pdev->dev, "cannot find IRQ\n"); + ret = -ENOENT; + goto err_iomap; + } + + ret = request_irq(res->start, s3c24xx_i2c_irq, IRQF_DISABLED, + pdev->name, i2c); + + if (ret != 0) { + dev_err(&pdev->dev, "cannot claim IRQ\n"); + goto err_iomap; + } + + i2c->irq = res; + + dev_dbg(&pdev->dev, "irq resource %p (%lu)\n", res, + (unsigned long)res->start); + + ret = s3c24xx_i2c_register_cpufreq(i2c); + if (ret < 0) { + dev_err(&pdev->dev, "failed to register cpufreq notifier\n"); + goto err_irq; + } + + /* Note, previous versions of the driver used i2c_add_adapter() + * to add the bus at any number. We now pass the bus number via + * the platform data, so if unset it will now default to always + * being bus 0. + */ + + i2c->adap.nr = pdata->bus_num; + + ret = i2c_add_numbered_adapter(&i2c->adap); + if (ret < 0) { + dev_err(&pdev->dev, "failed to add bus to i2c core\n"); + goto err_cpufreq; + } + + platform_set_drvdata(pdev, i2c); + + dev_info(&pdev->dev, "%s: S3C I2C adapter\n", i2c->adap.dev.bus_id); + return 0; + + err_cpufreq: + s3c24xx_i2c_deregister_cpufreq(i2c); + + err_irq: + free_irq(i2c->irq->start, i2c); + + err_iomap: + iounmap(i2c->regs); + + err_ioarea: + release_resource(i2c->ioarea); + kfree(i2c->ioarea); + + err_clk: + clk_disable(i2c->clk); + clk_put(i2c->clk); + + err_noclk: + return ret; +} + +/* s3c24xx_i2c_remove + * + * called when device is removed from the bus +*/ + +static int s3c24xx_i2c_remove(struct platform_device *pdev) +{ + struct s3c24xx_i2c *i2c = platform_get_drvdata(pdev); + + s3c24xx_i2c_deregister_cpufreq(i2c); + + i2c_del_adapter(&i2c->adap); + free_irq(i2c->irq->start, i2c); + + clk_disable(i2c->clk); + clk_put(i2c->clk); + + iounmap(i2c->regs); + + release_resource(i2c->ioarea); + kfree(i2c->ioarea); + + return 0; +} + +#ifdef CONFIG_PM +static int s3c24xx_i2c_suspend_late(struct platform_device *dev, + pm_message_t msg) +{ + struct s3c24xx_i2c *i2c = platform_get_drvdata(dev); + i2c->suspended = 1; + return 0; +} + +static int s3c24xx_i2c_resume(struct platform_device *dev) +{ + struct s3c24xx_i2c *i2c = platform_get_drvdata(dev); + + i2c->suspended = 0; + s3c24xx_i2c_init(i2c); + + return 0; +} + +#else +#define s3c24xx_i2c_suspend_late NULL +#define s3c24xx_i2c_resume NULL +#endif + +/* device driver for platform bus bits */ + +static struct platform_driver s3c2410_i2c_driver = { + .probe = s3c24xx_i2c_probe, + .remove = s3c24xx_i2c_remove, + .suspend_late = s3c24xx_i2c_suspend_late, + .resume = s3c24xx_i2c_resume, + .driver = { + .owner = THIS_MODULE, + .name = "s3c2410-i2c", + }, +}; + +static struct platform_driver s3c2440_i2c_driver = { + .probe = s3c24xx_i2c_probe, + .remove = s3c24xx_i2c_remove, + .suspend_late = s3c24xx_i2c_suspend_late, + .resume = s3c24xx_i2c_resume, + .driver = { + .owner = THIS_MODULE, + .name = "s3c2440-i2c", + }, +}; + +static int __init i2c_adap_s3c_init(void) +{ + int ret; + + ret = platform_driver_register(&s3c2410_i2c_driver); + if (ret == 0) { + ret = platform_driver_register(&s3c2440_i2c_driver); + if (ret) + platform_driver_unregister(&s3c2410_i2c_driver); + } + + return ret; +} + +static void __exit i2c_adap_s3c_exit(void) +{ + platform_driver_unregister(&s3c2410_i2c_driver); + platform_driver_unregister(&s3c2440_i2c_driver); +} + +module_init(i2c_adap_s3c_init); +module_exit(i2c_adap_s3c_exit); + +MODULE_DESCRIPTION("S3C24XX I2C Bus driver"); +MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:s3c2410-i2c"); +MODULE_ALIAS("platform:s3c2440-i2c"); diff --git a/drivers/i2c/busses/i2c-sh7760.c b/drivers/i2c/busses/i2c-sh7760.c new file mode 100644 index 0000000..5e0e254 --- /dev/null +++ b/drivers/i2c/busses/i2c-sh7760.c @@ -0,0 +1,577 @@ +/* + * I2C bus driver for the SH7760 I2C Interfaces. + * + * (c) 2005-2008 MSC Vertriebsges.m.b.H, Manuel Lauss <mlau@msc-ge.com> + * + * licensed under the terms outlined in the file COPYING. + * + */ + +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/platform_device.h> +#include <linux/slab.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 /* non-fifo mode switch */ +#define MCR_FSCL 0x40 /* override SCL pin */ +#define MCR_FSDA 0x20 /* override SDA pin */ +#define MCR_OBPC 0x10 /* override pins */ +#define MCR_MIE 0x08 /* master if enable */ +#define MCR_TSBE 0x04 +#define MCR_FSB 0x02 /* force stop bit */ +#define MCR_ESG 0x01 /* en startbit gen. */ + +#define MSR_MNR 0x40 /* nack received */ +#define MSR_MAL 0x20 /* arbitration lost */ +#define MSR_MST 0x10 /* sent a stop */ +#define MSR_MDE 0x08 +#define MSR_MDT 0x04 +#define MSR_MDR 0x02 +#define MSR_MAT 0x01 /* slave addr xfer done */ + +#define MIE_MNRE 0x40 /* nack irq en */ +#define MIE_MALE 0x20 /* arblos irq en */ +#define MIE_MSTE 0x10 /* stop irq en */ +#define MIE_MDEE 0x08 +#define MIE_MDTE 0x04 +#define MIE_MDRE 0x02 +#define MIE_MATE 0x01 /* address sent irq en */ + +#define FCR_RFRST 0x02 /* reset rx fifo */ +#define FCR_TFRST 0x01 /* reset tx fifo */ + +#define FSR_TEND 0x04 /* last byte sent */ +#define FSR_RDF 0x02 /* rx fifo trigger */ +#define FSR_TDFE 0x01 /* tx fifo empty */ + +#define FIER_TEIE 0x04 /* tx fifo empty irq en */ +#define FIER_RXIE 0x02 /* rx fifo trig irq en */ +#define FIER_TXIE 0x01 /* tx fifo trig irq en */ + +#define FIFO_SIZE 16 + +struct cami2c { + void __iomem *iobase; + struct i2c_adapter adap; + + /* 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; + + int irq; + struct resource *ioarea; +}; + +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 irq, void *ptr) +{ + struct cami2c *id = ptr; + struct i2c_msg *msg = id->msg; + char *data = msg->buf; + unsigned long msr, fsr, fier, len; + + 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 transfer or ctl will stop working + */ + 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. */ + } + + /* i2c-stop was sent */ + if (msr & MSR_MST) { + id->status |= IDS_DONE; + goto out; + } + + /* i2c slave addr was sent; set to "normal" operation */ + if (msr & MSR_MAT) + OUT32(id, I2CMCR, MCR_MIE); + + fier = IN32(id, I2CFIER); + + if (fsr & FSR_RDF) { + len = IN32(id, I2CRFDR); + if (msg->len <= len) { + 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 be 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. + */ + fsr &= ~FSR_RDF; + } + } + while (msg->len && len) { + *data++ = IN32(id, I2CRXTX); + msg->len--; + len--; + } + + if (msg->len) { + len = (msg->len >= FIFO_SIZE) ? FIFO_SIZE - 1 + : msg->len - 1; + + OUT32(id, I2CFCR, FCR_TFRST | ((len & 0xf) << 4)); + } + + } else if (id->flags & IDF_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. + */ + fsr &= ~FSR_TEND; + } + } + 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 { + len = (msg->len >= FIFO_SIZE) ? 2 : 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; +} + + +/* prepare and start a master receive operation */ +static void sh7760_i2c_mrecv(struct cami2c *id) +{ + int len; + + id->flags |= IDF_RECV; + + /* 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)); + + OUT32(id, I2CMSR, 0); + 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; + + id->flags |= IDF_SEND; + + /* 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)++; + } + + OUT32(id, I2CMSR, 0); + 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 inline int sh7760_i2c_busy_check(struct cami2c *id) +{ + return (IN32(id, I2CMCR) & MCR_FSDA); +} + +static int sh7760_i2c_master_xfer(struct i2c_adapter *adap, + struct i2c_msg *msgs, + int num) +{ + struct cami2c *id = adap->algo_data; + int i, retr; + + if (sh7760_i2c_busy_check(id)) { + dev_err(&adap->dev, "sh7760-i2c%d: bus busy!\n", adap->nr); + return -EBUSY; + } + + i = 0; + while (i < num) { + retr = adap->retries; +retry: + id->flags = ((i == (num-1)) ? IDF_STOP : 0); + id->status = 0; + id->msg = msgs; + init_completion(&id->xfer_done); + + if (msgs->flags & I2C_M_RD) + 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) { + /* wait a bit or i2c module stops working */ + mdelay(1); + num = -EREMOTEIO; + break; + } + + if (id->status & IDS_ARBLOST) { + if (retr--) { + mdelay(2); + goto retry; + } + num = -EREMOTEIO; + break; + } + + msgs++; + 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); + + /* reset slave module registers 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) +{ + return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK); +} + +static const 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. SCL clock is + * derived from I2C module clock (iclk) which in turn is derived from + * peripheral module clock (mclk, usually around 33MHz): + * iclk = mclk/(CDF + 1). iclk must be < 20MHz. + * scl = iclk/(SCGD*8 + 20). + */ +static int __devinit calc_CCR(unsigned long scl_hz) +{ + struct clk *mclk; + unsigned long mck, m1, dff, odff, iclk; + signed char cdf, cdfm; + int scgd, scgdm, scgds; + + mclk = clk_get(NULL, "module_clk"); + if (IS_ERR(mclk)) { + return PTR_ERR(mclk); + } else { + mck = mclk->rate; + clk_put(mclk); + } + + odff = scl_hz; + scgdm = cdfm = m1 = 0; + for (cdf = 3; cdf >= 0; cdf--) { + iclk = mck / (1 + cdf); + if (iclk >= 20000000) + continue; + scgds = ((iclk / scl_hz) - 20) >> 3; + for (scgd = scgds; (scgd < 63) && scgd <= scgds + 1; scgd++) { + m1 = iclk / (20 + (scgd << 3)); + dff = abs(scl_hz - m1); + if (dff < odff) { + odff = dff; + cdfm = cdf; + scgdm = scgd; + } + } + } + /* fail if more than 25% off of requested SCL */ + if (odff > (scl_hz >> 2)) + return -EINVAL; + + /* create a CCR register value */ + return ((scgdm << 2) | cdfm); +} + +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) { + dev_err(&pdev->dev, "no mem for private data\n"); + ret = -ENOMEM; + goto out0; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "no mmio resources\n"); + ret = -ENODEV; + goto out1; + } + + id->ioarea = request_mem_region(res->start, REGSIZE, pdev->name); + if (!id->ioarea) { + dev_err(&pdev->dev, "mmio already reserved\n"); + ret = -EBUSY; + goto out1; + } + + id->iobase = ioremap(res->start, REGSIZE); + if (!id->iobase) { + dev_err(&pdev->dev, "cannot ioremap\n"); + ret = -ENODEV; + goto out2; + } + + id->irq = platform_get_irq(pdev, 0); + + id->adap.nr = pdev->id; + id->adap.algo = &sh7760_i2c_algo; + id->adap.class = I2C_CLASS_ALL; + id->adap.retries = 3; + id->adap.algo_data = id; + id->adap.dev.parent = &pdev->dev; + snprintf(id->adap.name, sizeof(id->adap.name), + "SH7760 I2C at %08lx", (unsigned long)res->start); + + 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); + + ret = calc_CCR(pd->speed_khz * 1000); + if (ret < 0) { + dev_err(&pdev->dev, "invalid SCL clock: %dkHz\n", + pd->speed_khz); + goto out3; + } + OUT32(id, I2CCCR, ret); + + 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 = -EBUSY; + goto out3; + } + + ret = i2c_add_numbered_adapter(&id->adap); + if (ret < 0) { + dev_err(&pdev->dev, "reg adap failed: %d\n", ret); + goto out4; + } + + platform_set_drvdata(pdev, id); + + dev_info(&pdev->dev, "%d kHz mmio %08x irq %d\n", + pd->speed_khz, res->start, id->irq); + + return 0; + +out4: + free_irq(id->irq, id); +out3: + iounmap(id->iobase); +out2: + release_resource(id->ioarea); + kfree(id->ioarea); +out1: + kfree(id); +out0: + return ret; +} + +static int __devexit sh7760_i2c_remove(struct platform_device *pdev) +{ + struct cami2c *id = platform_get_drvdata(pdev); + + i2c_del_adapter(&id->adap); + free_irq(id->irq, id); + iounmap(id->iobase); + release_resource(id->ioarea); + kfree(id->ioarea); + 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 <mano@roarinelk.homelinux.net>"); diff --git a/drivers/i2c/busses/i2c-sh_mobile.c b/drivers/i2c/busses/i2c-sh_mobile.c new file mode 100644 index 0000000..3384a71 --- /dev/null +++ b/drivers/i2c/busses/i2c-sh_mobile.c @@ -0,0 +1,646 @@ +/* + * SuperH Mobile I2C Controller + * + * Copyright (C) 2008 Magnus Damm + * + * Portions of the code based on out-of-tree driver i2c-sh7343.c + * Copyright (c) 2006 Carlos Munoz <carlos@kenati.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#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> + +/* Transmit operation: */ +/* */ +/* 0 byte transmit */ +/* BUS: S A8 ACK P */ +/* IRQ: DTE WAIT */ +/* ICIC: */ +/* ICCR: 0x94 0x90 */ +/* ICDR: A8 */ +/* */ +/* 1 byte transmit */ +/* BUS: S A8 ACK D8(1) ACK P */ +/* IRQ: DTE WAIT WAIT */ +/* ICIC: -DTE */ +/* ICCR: 0x94 0x90 */ +/* ICDR: A8 D8(1) */ +/* */ +/* 2 byte transmit */ +/* BUS: S A8 ACK D8(1) ACK D8(2) ACK P */ +/* IRQ: DTE WAIT WAIT WAIT */ +/* ICIC: -DTE */ +/* ICCR: 0x94 0x90 */ +/* ICDR: A8 D8(1) D8(2) */ +/* */ +/* 3 bytes or more, +---------+ gets repeated */ +/* */ +/* */ +/* Receive operation: */ +/* */ +/* 0 byte receive - not supported since slave may hold SDA low */ +/* */ +/* 1 byte receive [TX] | [RX] */ +/* BUS: S A8 ACK | D8(1) ACK P */ +/* IRQ: DTE WAIT | WAIT DTE */ +/* ICIC: -DTE | +DTE */ +/* ICCR: 0x94 0x81 | 0xc0 */ +/* ICDR: A8 | D8(1) */ +/* */ +/* 2 byte receive [TX]| [RX] */ +/* BUS: S A8 ACK | D8(1) ACK D8(2) ACK P */ +/* IRQ: DTE WAIT | WAIT WAIT DTE */ +/* ICIC: -DTE | +DTE */ +/* ICCR: 0x94 0x81 | 0xc0 */ +/* ICDR: A8 | D8(1) D8(2) */ +/* */ +/* 3 byte receive [TX] | [RX] */ +/* BUS: S A8 ACK | D8(1) ACK D8(2) ACK D8(3) ACK P */ +/* IRQ: DTE WAIT | WAIT WAIT WAIT DTE */ +/* ICIC: -DTE | +DTE */ +/* ICCR: 0x94 0x81 | 0xc0 */ +/* ICDR: A8 | D8(1) D8(2) D8(3) */ +/* */ +/* 4 bytes or more, this part is repeated +---------+ */ +/* */ +/* */ +/* Interrupt order and BUSY flag */ +/* ___ _ */ +/* SDA ___\___XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXAAAAAAAAA___/ */ +/* SCL \_/1\_/2\_/3\_/4\_/5\_/6\_/7\_/8\___/9\_____/ */ +/* */ +/* S D7 D6 D5 D4 D3 D2 D1 D0 P */ +/* ___ */ +/* WAIT IRQ ________________________________/ \___________ */ +/* TACK IRQ ____________________________________/ \_______ */ +/* DTE IRQ __________________________________________/ \_ */ +/* AL IRQ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */ +/* _______________________________________________ */ +/* BUSY __/ \_ */ +/* */ + +enum sh_mobile_i2c_op { + OP_START = 0, + OP_TX_FIRST, + OP_TX, + OP_TX_STOP, + OP_TX_TO_RX, + OP_RX, + OP_RX_STOP, + OP_RX_STOP_DATA, +}; + +struct sh_mobile_i2c_data { + struct device *dev; + void __iomem *reg; + struct i2c_adapter adap; + + struct clk *clk; + u_int8_t iccl; + u_int8_t icch; + + spinlock_t lock; + wait_queue_head_t wait; + struct i2c_msg *msg; + int pos; + int sr; +}; + +#define NORMAL_SPEED 100000 /* FAST_SPEED 400000 */ + +/* Register offsets */ +#define ICDR(pd) (pd->reg + 0x00) +#define ICCR(pd) (pd->reg + 0x04) +#define ICSR(pd) (pd->reg + 0x08) +#define ICIC(pd) (pd->reg + 0x0c) +#define ICCL(pd) (pd->reg + 0x10) +#define ICCH(pd) (pd->reg + 0x14) + +/* Register bits */ +#define ICCR_ICE 0x80 +#define ICCR_RACK 0x40 +#define ICCR_TRS 0x10 +#define ICCR_BBSY 0x04 +#define ICCR_SCP 0x01 + +#define ICSR_SCLM 0x80 +#define ICSR_SDAM 0x40 +#define SW_DONE 0x20 +#define ICSR_BUSY 0x10 +#define ICSR_AL 0x08 +#define ICSR_TACK 0x04 +#define ICSR_WAIT 0x02 +#define ICSR_DTE 0x01 + +#define ICIC_ALE 0x08 +#define ICIC_TACKE 0x04 +#define ICIC_WAITE 0x02 +#define ICIC_DTEE 0x01 + +static void activate_ch(struct sh_mobile_i2c_data *pd) +{ + /* Make sure the clock is enabled */ + clk_enable(pd->clk); + + /* Enable channel and configure rx ack */ + iowrite8(ioread8(ICCR(pd)) | ICCR_ICE, ICCR(pd)); + + /* Mask all interrupts */ + iowrite8(0, ICIC(pd)); + + /* Set the clock */ + iowrite8(pd->iccl, ICCL(pd)); + iowrite8(pd->icch, ICCH(pd)); +} + +static void deactivate_ch(struct sh_mobile_i2c_data *pd) +{ + /* Clear/disable interrupts */ + iowrite8(0, ICSR(pd)); + iowrite8(0, ICIC(pd)); + + /* Disable channel */ + iowrite8(ioread8(ICCR(pd)) & ~ICCR_ICE, ICCR(pd)); + + /* Disable clock */ + clk_disable(pd->clk); +} + +static unsigned char i2c_op(struct sh_mobile_i2c_data *pd, + enum sh_mobile_i2c_op op, unsigned char data) +{ + unsigned char ret = 0; + unsigned long flags; + + dev_dbg(pd->dev, "op %d, data in 0x%02x\n", op, data); + + spin_lock_irqsave(&pd->lock, flags); + + switch (op) { + case OP_START: /* issue start and trigger DTE interrupt */ + iowrite8(0x94, ICCR(pd)); + break; + case OP_TX_FIRST: /* disable DTE interrupt and write data */ + iowrite8(ICIC_WAITE | ICIC_ALE | ICIC_TACKE, ICIC(pd)); + iowrite8(data, ICDR(pd)); + break; + case OP_TX: /* write data */ + iowrite8(data, ICDR(pd)); + break; + case OP_TX_STOP: /* write data and issue a stop afterwards */ + iowrite8(data, ICDR(pd)); + iowrite8(0x90, ICCR(pd)); + break; + case OP_TX_TO_RX: /* select read mode */ + iowrite8(0x81, ICCR(pd)); + break; + case OP_RX: /* just read data */ + ret = ioread8(ICDR(pd)); + break; + case OP_RX_STOP: /* enable DTE interrupt, issue stop */ + iowrite8(ICIC_DTEE | ICIC_WAITE | ICIC_ALE | ICIC_TACKE, + ICIC(pd)); + iowrite8(0xc0, ICCR(pd)); + break; + case OP_RX_STOP_DATA: /* enable DTE interrupt, read data, issue stop */ + iowrite8(ICIC_DTEE | ICIC_WAITE | ICIC_ALE | ICIC_TACKE, + ICIC(pd)); + ret = ioread8(ICDR(pd)); + iowrite8(0xc0, ICCR(pd)); + break; + } + + spin_unlock_irqrestore(&pd->lock, flags); + + dev_dbg(pd->dev, "op %d, data out 0x%02x\n", op, ret); + return ret; +} + +static int sh_mobile_i2c_is_first_byte(struct sh_mobile_i2c_data *pd) +{ + if (pd->pos == -1) + return 1; + + return 0; +} + +static int sh_mobile_i2c_is_last_byte(struct sh_mobile_i2c_data *pd) +{ + if (pd->pos == (pd->msg->len - 1)) + return 1; + + return 0; +} + +static void sh_mobile_i2c_get_data(struct sh_mobile_i2c_data *pd, + unsigned char *buf) +{ + switch (pd->pos) { + case -1: + *buf = (pd->msg->addr & 0x7f) << 1; + *buf |= (pd->msg->flags & I2C_M_RD) ? 1 : 0; + break; + default: + *buf = pd->msg->buf[pd->pos]; + } +} + +static int sh_mobile_i2c_isr_tx(struct sh_mobile_i2c_data *pd) +{ + unsigned char data; + + if (pd->pos == pd->msg->len) + return 1; + + sh_mobile_i2c_get_data(pd, &data); + + if (sh_mobile_i2c_is_last_byte(pd)) + i2c_op(pd, OP_TX_STOP, data); + else if (sh_mobile_i2c_is_first_byte(pd)) + i2c_op(pd, OP_TX_FIRST, data); + else + i2c_op(pd, OP_TX, data); + + pd->pos++; + return 0; +} + +static int sh_mobile_i2c_isr_rx(struct sh_mobile_i2c_data *pd) +{ + unsigned char data; + int real_pos; + + do { + if (pd->pos <= -1) { + sh_mobile_i2c_get_data(pd, &data); + + if (sh_mobile_i2c_is_first_byte(pd)) + i2c_op(pd, OP_TX_FIRST, data); + else + i2c_op(pd, OP_TX, data); + break; + } + + if (pd->pos == 0) { + i2c_op(pd, OP_TX_TO_RX, 0); + break; + } + + real_pos = pd->pos - 2; + + if (pd->pos == pd->msg->len) { + if (real_pos < 0) { + i2c_op(pd, OP_RX_STOP, 0); + break; + } + data = i2c_op(pd, OP_RX_STOP_DATA, 0); + } else + data = i2c_op(pd, OP_RX, 0); + + if (real_pos >= 0) + pd->msg->buf[real_pos] = data; + } while (0); + + pd->pos++; + return pd->pos == (pd->msg->len + 2); +} + +static irqreturn_t sh_mobile_i2c_isr(int irq, void *dev_id) +{ + struct platform_device *dev = dev_id; + struct sh_mobile_i2c_data *pd = platform_get_drvdata(dev); + unsigned char sr; + int wakeup; + + sr = ioread8(ICSR(pd)); + pd->sr |= sr; /* remember state */ + + dev_dbg(pd->dev, "i2c_isr 0x%02x 0x%02x %s %d %d!\n", sr, pd->sr, + (pd->msg->flags & I2C_M_RD) ? "read" : "write", + pd->pos, pd->msg->len); + + if (sr & (ICSR_AL | ICSR_TACK)) { + /* don't interrupt transaction - continue to issue stop */ + iowrite8(sr & ~(ICSR_AL | ICSR_TACK), ICSR(pd)); + wakeup = 0; + } else if (pd->msg->flags & I2C_M_RD) + wakeup = sh_mobile_i2c_isr_rx(pd); + else + wakeup = sh_mobile_i2c_isr_tx(pd); + + if (sr & ICSR_WAIT) /* TODO: add delay here to support slow acks */ + iowrite8(sr & ~ICSR_WAIT, ICSR(pd)); + + if (wakeup) { + pd->sr |= SW_DONE; + wake_up(&pd->wait); + } + + return IRQ_HANDLED; +} + +static int start_ch(struct sh_mobile_i2c_data *pd, struct i2c_msg *usr_msg) +{ + if (usr_msg->len == 0 && (usr_msg->flags & I2C_M_RD)) { + dev_err(pd->dev, "Unsupported zero length i2c read\n"); + return -EIO; + } + + /* Initialize channel registers */ + iowrite8(ioread8(ICCR(pd)) & ~ICCR_ICE, ICCR(pd)); + + /* Enable channel and configure rx ack */ + iowrite8(ioread8(ICCR(pd)) | ICCR_ICE, ICCR(pd)); + + /* Set the clock */ + iowrite8(pd->iccl, ICCL(pd)); + iowrite8(pd->icch, ICCH(pd)); + + pd->msg = usr_msg; + pd->pos = -1; + pd->sr = 0; + + /* Enable all interrupts to begin with */ + iowrite8(ICIC_WAITE | ICIC_ALE | ICIC_TACKE | ICIC_DTEE, ICIC(pd)); + return 0; +} + +static int sh_mobile_i2c_xfer(struct i2c_adapter *adapter, + struct i2c_msg *msgs, + int num) +{ + struct sh_mobile_i2c_data *pd = i2c_get_adapdata(adapter); + struct i2c_msg *msg; + int err = 0; + u_int8_t val; + int i, k, retry_count; + + activate_ch(pd); + + /* Process all messages */ + for (i = 0; i < num; i++) { + msg = &msgs[i]; + + err = start_ch(pd, msg); + if (err) + break; + + i2c_op(pd, OP_START, 0); + + /* The interrupt handler takes care of the rest... */ + k = wait_event_timeout(pd->wait, + pd->sr & (ICSR_TACK | SW_DONE), + 5 * HZ); + if (!k) + dev_err(pd->dev, "Transfer request timed out\n"); + + retry_count = 1000; +again: + val = ioread8(ICSR(pd)); + + dev_dbg(pd->dev, "val 0x%02x pd->sr 0x%02x\n", val, pd->sr); + + /* the interrupt handler may wake us up before the + * transfer is finished, so poll the hardware + * until we're done. + */ + if (val & ICSR_BUSY) { + udelay(10); + if (retry_count--) + goto again; + + err = -EIO; + dev_err(pd->dev, "Polling timed out\n"); + break; + } + + /* handle missing acknowledge and arbitration lost */ + if ((val | pd->sr) & (ICSR_TACK | ICSR_AL)) { + err = -EIO; + break; + } + } + + deactivate_ch(pd); + + if (!err) + err = num; + return err; +} + +static u32 sh_mobile_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static struct i2c_algorithm sh_mobile_i2c_algorithm = { + .functionality = sh_mobile_i2c_func, + .master_xfer = sh_mobile_i2c_xfer, +}; + +static void sh_mobile_i2c_setup_channel(struct platform_device *dev) +{ + struct sh_mobile_i2c_data *pd = platform_get_drvdata(dev); + unsigned long peripheral_clk = clk_get_rate(pd->clk); + u_int32_t num; + u_int32_t denom; + u_int32_t tmp; + + spin_lock_init(&pd->lock); + init_waitqueue_head(&pd->wait); + + /* Calculate the value for iccl. From the data sheet: + * iccl = (p clock / transfer rate) * (L / (L + H)) + * where L and H are the SCL low/high ratio (5/4 in this case). + * We also round off the result. + */ + num = peripheral_clk * 5; + denom = NORMAL_SPEED * 9; + tmp = num * 10 / denom; + if (tmp % 10 >= 5) + pd->iccl = (u_int8_t)((num/denom) + 1); + else + pd->iccl = (u_int8_t)(num/denom); + + /* Calculate the value for icch. From the data sheet: + icch = (p clock / transfer rate) * (H / (L + H)) */ + num = peripheral_clk * 4; + tmp = num * 10 / denom; + if (tmp % 10 >= 5) + pd->icch = (u_int8_t)((num/denom) + 1); + else + pd->icch = (u_int8_t)(num/denom); +} + +static int sh_mobile_i2c_hook_irqs(struct platform_device *dev, int hook) +{ + struct resource *res; + int ret = -ENXIO; + int q, m; + int k = 0; + int n = 0; + + while ((res = platform_get_resource(dev, IORESOURCE_IRQ, k))) { + for (n = res->start; hook && n <= res->end; n++) { + if (request_irq(n, sh_mobile_i2c_isr, IRQF_DISABLED, + dev->dev.bus_id, dev)) + goto rollback; + } + k++; + } + + if (hook) + return k > 0 ? 0 : -ENOENT; + + k--; + ret = 0; + + rollback: + for (q = k; k >= 0; k--) { + for (m = n; m >= res->start; m--) + free_irq(m, dev); + + res = platform_get_resource(dev, IORESOURCE_IRQ, k - 1); + m = res->end; + } + + return ret; +} + +static int sh_mobile_i2c_probe(struct platform_device *dev) +{ + struct sh_mobile_i2c_data *pd; + struct i2c_adapter *adap; + struct resource *res; + int size; + int ret; + + pd = kzalloc(sizeof(struct sh_mobile_i2c_data), GFP_KERNEL); + if (pd == NULL) { + dev_err(&dev->dev, "cannot allocate private data\n"); + return -ENOMEM; + } + + pd->clk = clk_get(&dev->dev, "peripheral_clk"); + if (IS_ERR(pd->clk)) { + dev_err(&dev->dev, "cannot get peripheral clock\n"); + ret = PTR_ERR(pd->clk); + goto err; + } + + ret = sh_mobile_i2c_hook_irqs(dev, 1); + if (ret) { + dev_err(&dev->dev, "cannot request IRQ\n"); + goto err_clk; + } + + pd->dev = &dev->dev; + platform_set_drvdata(dev, pd); + + res = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&dev->dev, "cannot find IO resource\n"); + ret = -ENOENT; + goto err_irq; + } + + size = (res->end - res->start) + 1; + + pd->reg = ioremap(res->start, size); + if (pd->reg == NULL) { + dev_err(&dev->dev, "cannot map IO\n"); + ret = -ENXIO; + goto err_irq; + } + + /* setup the private data */ + adap = &pd->adap; + i2c_set_adapdata(adap, pd); + + adap->owner = THIS_MODULE; + adap->algo = &sh_mobile_i2c_algorithm; + adap->dev.parent = &dev->dev; + adap->retries = 5; + adap->nr = dev->id; + + strlcpy(adap->name, dev->name, sizeof(adap->name)); + + sh_mobile_i2c_setup_channel(dev); + + ret = i2c_add_numbered_adapter(adap); + if (ret < 0) { + dev_err(&dev->dev, "cannot add numbered adapter\n"); + goto err_all; + } + + return 0; + + err_all: + iounmap(pd->reg); + err_irq: + sh_mobile_i2c_hook_irqs(dev, 0); + err_clk: + clk_put(pd->clk); + err: + kfree(pd); + return ret; +} + +static int sh_mobile_i2c_remove(struct platform_device *dev) +{ + struct sh_mobile_i2c_data *pd = platform_get_drvdata(dev); + + i2c_del_adapter(&pd->adap); + iounmap(pd->reg); + sh_mobile_i2c_hook_irqs(dev, 0); + clk_put(pd->clk); + kfree(pd); + return 0; +} + +static struct platform_driver sh_mobile_i2c_driver = { + .driver = { + .name = "i2c-sh_mobile", + .owner = THIS_MODULE, + }, + .probe = sh_mobile_i2c_probe, + .remove = sh_mobile_i2c_remove, +}; + +static int __init sh_mobile_i2c_adap_init(void) +{ + return platform_driver_register(&sh_mobile_i2c_driver); +} + +static void __exit sh_mobile_i2c_adap_exit(void) +{ + platform_driver_unregister(&sh_mobile_i2c_driver); +} + +module_init(sh_mobile_i2c_adap_init); +module_exit(sh_mobile_i2c_adap_exit); + +MODULE_DESCRIPTION("SuperH Mobile I2C Bus Controller driver"); +MODULE_AUTHOR("Magnus Damm"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/i2c/busses/i2c-sibyte.c b/drivers/i2c/busses/i2c-sibyte.c new file mode 100644 index 0000000..4ddefbf --- /dev/null +++ b/drivers/i2c/busses/i2c-sibyte.c @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2004 Steven J. Hill + * Copyright (C) 2001,2002,2003 Broadcom Corporation + * Copyright (C) 1995-2000 Simon G. Vogl + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <asm/io.h> +#include <asm/sibyte/sb1250_regs.h> +#include <asm/sibyte/sb1250_smbus.h> + + +struct i2c_algo_sibyte_data { + void *data; /* private data */ + int bus; /* which bus */ + void *reg_base; /* CSR base */ +}; + +/* ----- global defines ----------------------------------------------- */ +#define SMB_CSR(a,r) ((long)(a->reg_base + r)) + + +static int smbus_xfer(struct i2c_adapter *i2c_adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, union i2c_smbus_data * data) +{ + struct i2c_algo_sibyte_data *adap = i2c_adap->algo_data; + int data_bytes = 0; + int error; + + while (csr_in32(SMB_CSR(adap, R_SMB_STATUS)) & M_SMB_BUSY) + ; + + switch (size) { + case I2C_SMBUS_QUICK: + csr_out32((V_SMB_ADDR(addr) | + (read_write == I2C_SMBUS_READ ? M_SMB_QDATA : 0) | + V_SMB_TT_QUICKCMD), SMB_CSR(adap, R_SMB_START)); + break; + case I2C_SMBUS_BYTE: + if (read_write == I2C_SMBUS_READ) { + csr_out32((V_SMB_ADDR(addr) | V_SMB_TT_RD1BYTE), + SMB_CSR(adap, R_SMB_START)); + data_bytes = 1; + } else { + csr_out32(V_SMB_CMD(command), SMB_CSR(adap, R_SMB_CMD)); + csr_out32((V_SMB_ADDR(addr) | V_SMB_TT_WR1BYTE), + SMB_CSR(adap, R_SMB_START)); + } + break; + case I2C_SMBUS_BYTE_DATA: + csr_out32(V_SMB_CMD(command), SMB_CSR(adap, R_SMB_CMD)); + if (read_write == I2C_SMBUS_READ) { + csr_out32((V_SMB_ADDR(addr) | V_SMB_TT_CMD_RD1BYTE), + SMB_CSR(adap, R_SMB_START)); + data_bytes = 1; + } else { + csr_out32(V_SMB_LB(data->byte), + SMB_CSR(adap, R_SMB_DATA)); + csr_out32((V_SMB_ADDR(addr) | V_SMB_TT_WR2BYTE), + SMB_CSR(adap, R_SMB_START)); + } + break; + case I2C_SMBUS_WORD_DATA: + csr_out32(V_SMB_CMD(command), SMB_CSR(adap, R_SMB_CMD)); + if (read_write == I2C_SMBUS_READ) { + csr_out32((V_SMB_ADDR(addr) | V_SMB_TT_CMD_RD2BYTE), + SMB_CSR(adap, R_SMB_START)); + data_bytes = 2; + } else { + csr_out32(V_SMB_LB(data->word & 0xff), + SMB_CSR(adap, R_SMB_DATA)); + csr_out32(V_SMB_MB(data->word >> 8), + SMB_CSR(adap, R_SMB_DATA)); + csr_out32((V_SMB_ADDR(addr) | V_SMB_TT_WR2BYTE), + SMB_CSR(adap, R_SMB_START)); + } + break; + default: + return -1; /* XXXKW better error code? */ + } + + while (csr_in32(SMB_CSR(adap, R_SMB_STATUS)) & M_SMB_BUSY) + ; + + error = csr_in32(SMB_CSR(adap, R_SMB_STATUS)); + if (error & M_SMB_ERROR) { + /* Clear error bit by writing a 1 */ + csr_out32(M_SMB_ERROR, SMB_CSR(adap, R_SMB_STATUS)); + return -1; /* XXXKW better error code? */ + } + + if (data_bytes == 1) + data->byte = csr_in32(SMB_CSR(adap, R_SMB_DATA)) & 0xff; + if (data_bytes == 2) + data->word = csr_in32(SMB_CSR(adap, R_SMB_DATA)) & 0xffff; + + return 0; +} + +static u32 bit_func(struct i2c_adapter *adap) +{ + return (I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA); +} + + +/* -----exported algorithm data: ------------------------------------- */ + +static const struct i2c_algorithm i2c_sibyte_algo = { + .smbus_xfer = smbus_xfer, + .functionality = bit_func, +}; + +/* + * registering functions to load algorithms at runtime + */ +static int __init i2c_sibyte_add_bus(struct i2c_adapter *i2c_adap, int speed) +{ + struct i2c_algo_sibyte_data *adap = i2c_adap->algo_data; + + /* Register new adapter to i2c module... */ + i2c_adap->algo = &i2c_sibyte_algo; + + /* Set the requested frequency. */ + csr_out32(speed, SMB_CSR(adap,R_SMB_FREQ)); + csr_out32(0, SMB_CSR(adap,R_SMB_CONTROL)); + + return i2c_add_numbered_adapter(i2c_adap); +} + + +static struct i2c_algo_sibyte_data sibyte_board_data[2] = { + { NULL, 0, (void *) (CKSEG1+A_SMB_BASE(0)) }, + { NULL, 1, (void *) (CKSEG1+A_SMB_BASE(1)) } +}; + +static struct i2c_adapter sibyte_board_adapter[2] = { + { + .owner = THIS_MODULE, + .id = I2C_HW_SIBYTE, + .class = I2C_CLASS_HWMON | I2C_CLASS_SPD, + .algo = NULL, + .algo_data = &sibyte_board_data[0], + .nr = 0, + .name = "SiByte SMBus 0", + }, + { + .owner = THIS_MODULE, + .id = I2C_HW_SIBYTE, + .class = I2C_CLASS_HWMON | I2C_CLASS_SPD, + .algo = NULL, + .algo_data = &sibyte_board_data[1], + .nr = 1, + .name = "SiByte SMBus 1", + }, +}; + +static int __init i2c_sibyte_init(void) +{ + pr_info("i2c-sibyte: i2c SMBus adapter module for SiByte board\n"); + if (i2c_sibyte_add_bus(&sibyte_board_adapter[0], K_SMB_FREQ_100KHZ) < 0) + return -ENODEV; + if (i2c_sibyte_add_bus(&sibyte_board_adapter[1], + K_SMB_FREQ_400KHZ) < 0) { + i2c_del_adapter(&sibyte_board_adapter[0]); + return -ENODEV; + } + return 0; +} + +static void __exit i2c_sibyte_exit(void) +{ + i2c_del_adapter(&sibyte_board_adapter[0]); + i2c_del_adapter(&sibyte_board_adapter[1]); +} + +module_init(i2c_sibyte_init); +module_exit(i2c_sibyte_exit); + +MODULE_AUTHOR("Kip Walker (Broadcom Corp.), Steven J. Hill <sjhill@realitydiluted.com>"); +MODULE_DESCRIPTION("SMBus adapter routines for SiByte boards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/busses/i2c-simtec.c b/drivers/i2c/busses/i2c-simtec.c new file mode 100644 index 0000000..042fda2 --- /dev/null +++ b/drivers/i2c/busses/i2c-simtec.c @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2005 Simtec Electronics + * Ben Dooks <ben@simtec.co.uk> + * + * Simtec Generic I2C Controller + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/platform_device.h> + +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> + +#include <asm/io.h> + +struct simtec_i2c_data { + struct resource *ioarea; + void __iomem *reg; + struct i2c_adapter adap; + struct i2c_algo_bit_data bit; +}; + +#define CMD_SET_SDA (1<<2) +#define CMD_SET_SCL (1<<3) + +#define STATE_SDA (1<<0) +#define STATE_SCL (1<<1) + +/* i2c bit-bus functions */ + +static void simtec_i2c_setsda(void *pw, int state) +{ + struct simtec_i2c_data *pd = pw; + writeb(CMD_SET_SDA | (state ? STATE_SDA : 0), pd->reg); +} + +static void simtec_i2c_setscl(void *pw, int state) +{ + struct simtec_i2c_data *pd = pw; + writeb(CMD_SET_SCL | (state ? STATE_SCL : 0), pd->reg); +} + +static int simtec_i2c_getsda(void *pw) +{ + struct simtec_i2c_data *pd = pw; + return readb(pd->reg) & STATE_SDA ? 1 : 0; +} + +static int simtec_i2c_getscl(void *pw) +{ + struct simtec_i2c_data *pd = pw; + return readb(pd->reg) & STATE_SCL ? 1 : 0; +} + +/* device registration */ + +static int simtec_i2c_probe(struct platform_device *dev) +{ + struct simtec_i2c_data *pd; + struct resource *res; + int size; + int ret; + + pd = kzalloc(sizeof(struct simtec_i2c_data), GFP_KERNEL); + if (pd == NULL) { + dev_err(&dev->dev, "cannot allocate private data\n"); + return -ENOMEM; + } + + platform_set_drvdata(dev, pd); + + res = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&dev->dev, "cannot find IO resource\n"); + ret = -ENOENT; + goto err; + } + + size = (res->end-res->start)+1; + + pd->ioarea = request_mem_region(res->start, size, dev->name); + if (pd->ioarea == NULL) { + dev_err(&dev->dev, "cannot request IO\n"); + ret = -ENXIO; + goto err; + } + + pd->reg = ioremap(res->start, size); + if (pd->reg == NULL) { + dev_err(&dev->dev, "cannot map IO\n"); + ret = -ENXIO; + goto err_res; + } + + /* setup the private data */ + + pd->adap.owner = THIS_MODULE; + pd->adap.algo_data = &pd->bit; + pd->adap.dev.parent = &dev->dev; + + strlcpy(pd->adap.name, "Simtec I2C", sizeof(pd->adap.name)); + + pd->bit.data = pd; + pd->bit.setsda = simtec_i2c_setsda; + pd->bit.setscl = simtec_i2c_setscl; + pd->bit.getsda = simtec_i2c_getsda; + pd->bit.getscl = simtec_i2c_getscl; + pd->bit.timeout = HZ; + pd->bit.udelay = 20; + + ret = i2c_bit_add_bus(&pd->adap); + if (ret) + goto err_all; + + return 0; + + err_all: + iounmap(pd->reg); + + err_res: + release_resource(pd->ioarea); + kfree(pd->ioarea); + + err: + kfree(pd); + return ret; +} + +static int simtec_i2c_remove(struct platform_device *dev) +{ + struct simtec_i2c_data *pd = platform_get_drvdata(dev); + + i2c_del_adapter(&pd->adap); + + iounmap(pd->reg); + release_resource(pd->ioarea); + kfree(pd->ioarea); + kfree(pd); + + return 0; +} + + +/* device driver */ + +/* work with hotplug and coldplug */ +MODULE_ALIAS("platform:simtec-i2c"); + +static struct platform_driver simtec_i2c_driver = { + .driver = { + .name = "simtec-i2c", + .owner = THIS_MODULE, + }, + .probe = simtec_i2c_probe, + .remove = simtec_i2c_remove, +}; + +static int __init i2c_adap_simtec_init(void) +{ + return platform_driver_register(&simtec_i2c_driver); +} + +static void __exit i2c_adap_simtec_exit(void) +{ + platform_driver_unregister(&simtec_i2c_driver); +} + +module_init(i2c_adap_simtec_init); +module_exit(i2c_adap_simtec_exit); + +MODULE_DESCRIPTION("Simtec Generic I2C Bus driver"); +MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/busses/i2c-sis5595.c b/drivers/i2c/busses/i2c-sis5595.c new file mode 100644 index 0000000..dfc2d5e --- /dev/null +++ b/drivers/i2c/busses/i2c-sis5595.c @@ -0,0 +1,435 @@ +/* + Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl> and + Philip Edelbrock <phil@netroedge.com> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* Note: we assume there can only be one SIS5595 with one SMBus interface */ + +/* + Note: all have mfr. ID 0x1039. + SUPPORTED PCI ID + 5595 0008 + + Note: these chips contain a 0008 device which is incompatible with the + 5595. We recognize these by the presence of the listed + "blacklist" PCI ID and refuse to load. + + NOT SUPPORTED PCI ID BLACKLIST PCI ID + 540 0008 0540 + 550 0008 0550 + 5513 0008 5511 + 5581 0008 5597 + 5582 0008 5597 + 5597 0008 5597 + 5598 0008 5597/5598 + 630 0008 0630 + 645 0008 0645 + 646 0008 0646 + 648 0008 0648 + 650 0008 0650 + 651 0008 0651 + 730 0008 0730 + 735 0008 0735 + 745 0008 0745 + 746 0008 0746 +*/ + +/* TO DO: + * Add Block Transfers (ugly, but supported by the adapter) + * Add adapter resets + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/pci.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/acpi.h> +#include <asm/io.h> + +static int blacklist[] = { + PCI_DEVICE_ID_SI_540, + PCI_DEVICE_ID_SI_550, + PCI_DEVICE_ID_SI_630, + PCI_DEVICE_ID_SI_645, + PCI_DEVICE_ID_SI_646, + PCI_DEVICE_ID_SI_648, + PCI_DEVICE_ID_SI_650, + PCI_DEVICE_ID_SI_651, + PCI_DEVICE_ID_SI_730, + PCI_DEVICE_ID_SI_735, + PCI_DEVICE_ID_SI_745, + PCI_DEVICE_ID_SI_746, + PCI_DEVICE_ID_SI_5511, /* 5513 chip has the 0008 device but that ID + shows up in other chips so we use the 5511 + ID for recognition */ + PCI_DEVICE_ID_SI_5597, + PCI_DEVICE_ID_SI_5598, + 0, /* terminates the list */ +}; + +/* Length of ISA address segment */ +#define SIS5595_EXTENT 8 +/* SIS5595 SMBus registers */ +#define SMB_STS_LO 0x00 +#define SMB_STS_HI 0x01 +#define SMB_CTL_LO 0x02 +#define SMB_CTL_HI 0x03 +#define SMB_ADDR 0x04 +#define SMB_CMD 0x05 +#define SMB_PCNT 0x06 +#define SMB_CNT 0x07 +#define SMB_BYTE 0x08 +#define SMB_DEV 0x10 +#define SMB_DB0 0x11 +#define SMB_DB1 0x12 +#define SMB_HAA 0x13 + +/* PCI Address Constants */ +#define SMB_INDEX 0x38 +#define SMB_DAT 0x39 +#define SIS5595_ENABLE_REG 0x40 +#define ACPI_BASE 0x90 + +/* Other settings */ +#define MAX_TIMEOUT 500 + +/* SIS5595 constants */ +#define SIS5595_QUICK 0x00 +#define SIS5595_BYTE 0x02 +#define SIS5595_BYTE_DATA 0x04 +#define SIS5595_WORD_DATA 0x06 +#define SIS5595_PROC_CALL 0x08 +#define SIS5595_BLOCK_DATA 0x0A + +/* insmod parameters */ + +/* If force_addr is set to anything different from 0, we forcibly enable + the device at the given address. */ +static u16 force_addr; +module_param(force_addr, ushort, 0); +MODULE_PARM_DESC(force_addr, "Initialize the base address of the i2c controller"); + +static struct pci_driver sis5595_driver; +static unsigned short sis5595_base; +static struct pci_dev *sis5595_pdev; + +static u8 sis5595_read(u8 reg) +{ + outb(reg, sis5595_base + SMB_INDEX); + return inb(sis5595_base + SMB_DAT); +} + +static void sis5595_write(u8 reg, u8 data) +{ + outb(reg, sis5595_base + SMB_INDEX); + outb(data, sis5595_base + SMB_DAT); +} + +static int sis5595_setup(struct pci_dev *SIS5595_dev) +{ + u16 a; + u8 val; + int *i; + int retval = -ENODEV; + + /* Look for imposters */ + for (i = blacklist; *i != 0; i++) { + struct pci_dev *dev; + dev = pci_get_device(PCI_VENDOR_ID_SI, *i, NULL); + if (dev) { + dev_err(&SIS5595_dev->dev, "Looked for SIS5595 but found unsupported device %.4x\n", *i); + pci_dev_put(dev); + return -ENODEV; + } + } + + /* Determine the address of the SMBus areas */ + pci_read_config_word(SIS5595_dev, ACPI_BASE, &sis5595_base); + if (sis5595_base == 0 && force_addr == 0) { + dev_err(&SIS5595_dev->dev, "ACPI base address uninitialized - upgrade BIOS or use force_addr=0xaddr\n"); + return -ENODEV; + } + + if (force_addr) + sis5595_base = force_addr & ~(SIS5595_EXTENT - 1); + dev_dbg(&SIS5595_dev->dev, "ACPI Base address: %04x\n", sis5595_base); + + /* NB: We grab just the two SMBus registers here, but this may still + * interfere with ACPI :-( */ + retval = acpi_check_region(sis5595_base + SMB_INDEX, 2, + sis5595_driver.name); + if (retval) + return retval; + + if (!request_region(sis5595_base + SMB_INDEX, 2, + sis5595_driver.name)) { + dev_err(&SIS5595_dev->dev, "SMBus registers 0x%04x-0x%04x already in use!\n", + sis5595_base + SMB_INDEX, sis5595_base + SMB_INDEX + 1); + return -ENODEV; + } + + if (force_addr) { + dev_info(&SIS5595_dev->dev, "forcing ISA address 0x%04X\n", sis5595_base); + if (pci_write_config_word(SIS5595_dev, ACPI_BASE, sis5595_base) + != PCIBIOS_SUCCESSFUL) + goto error; + if (pci_read_config_word(SIS5595_dev, ACPI_BASE, &a) + != PCIBIOS_SUCCESSFUL) + goto error; + if ((a & ~(SIS5595_EXTENT - 1)) != sis5595_base) { + /* doesn't work for some chips! */ + dev_err(&SIS5595_dev->dev, "force address failed - not supported?\n"); + goto error; + } + } + + if (pci_read_config_byte(SIS5595_dev, SIS5595_ENABLE_REG, &val) + != PCIBIOS_SUCCESSFUL) + goto error; + if ((val & 0x80) == 0) { + dev_info(&SIS5595_dev->dev, "enabling ACPI\n"); + if (pci_write_config_byte(SIS5595_dev, SIS5595_ENABLE_REG, val | 0x80) + != PCIBIOS_SUCCESSFUL) + goto error; + if (pci_read_config_byte(SIS5595_dev, SIS5595_ENABLE_REG, &val) + != PCIBIOS_SUCCESSFUL) + goto error; + if ((val & 0x80) == 0) { + /* doesn't work for some chips? */ + dev_err(&SIS5595_dev->dev, "ACPI enable failed - not supported?\n"); + goto error; + } + } + + /* Everything is happy */ + return 0; + +error: + release_region(sis5595_base + SMB_INDEX, 2); + return retval; +} + +static int sis5595_transaction(struct i2c_adapter *adap) +{ + int temp; + int result = 0; + int timeout = 0; + + /* Make sure the SMBus host is ready to start transmitting */ + temp = sis5595_read(SMB_STS_LO) + (sis5595_read(SMB_STS_HI) << 8); + if (temp != 0x00) { + dev_dbg(&adap->dev, "SMBus busy (%04x). Resetting...\n", temp); + sis5595_write(SMB_STS_LO, temp & 0xff); + sis5595_write(SMB_STS_HI, temp >> 8); + if ((temp = sis5595_read(SMB_STS_LO) + (sis5595_read(SMB_STS_HI) << 8)) != 0x00) { + dev_dbg(&adap->dev, "Failed! (%02x)\n", temp); + return -EBUSY; + } else { + dev_dbg(&adap->dev, "Successful!\n"); + } + } + + /* start the transaction by setting bit 4 */ + sis5595_write(SMB_CTL_LO, sis5595_read(SMB_CTL_LO) | 0x10); + + /* We will always wait for a fraction of a second! */ + do { + msleep(1); + temp = sis5595_read(SMB_STS_LO); + } while (!(temp & 0x40) && (timeout++ < MAX_TIMEOUT)); + + /* If the SMBus is still busy, we give up */ + if (timeout >= MAX_TIMEOUT) { + dev_dbg(&adap->dev, "SMBus Timeout!\n"); + result = -ETIMEDOUT; + } + + if (temp & 0x10) { + dev_dbg(&adap->dev, "Error: Failed bus transaction\n"); + result = -ENXIO; + } + + if (temp & 0x20) { + dev_err(&adap->dev, "Bus collision! SMBus may be locked until " + "next hard reset (or not...)\n"); + /* Clock stops and slave is stuck in mid-transmission */ + result = -EIO; + } + + temp = sis5595_read(SMB_STS_LO) + (sis5595_read(SMB_STS_HI) << 8); + if (temp != 0x00) { + sis5595_write(SMB_STS_LO, temp & 0xff); + sis5595_write(SMB_STS_HI, temp >> 8); + } + + temp = sis5595_read(SMB_STS_LO) + (sis5595_read(SMB_STS_HI) << 8); + if (temp != 0x00) + dev_dbg(&adap->dev, "Failed reset at end of transaction (%02x)\n", temp); + + return result; +} + +/* Return negative errno on error. */ +static s32 sis5595_access(struct i2c_adapter *adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, union i2c_smbus_data *data) +{ + int status; + + switch (size) { + case I2C_SMBUS_QUICK: + sis5595_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01)); + size = SIS5595_QUICK; + break; + case I2C_SMBUS_BYTE: + sis5595_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01)); + if (read_write == I2C_SMBUS_WRITE) + sis5595_write(SMB_CMD, command); + size = SIS5595_BYTE; + break; + case I2C_SMBUS_BYTE_DATA: + sis5595_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01)); + sis5595_write(SMB_CMD, command); + if (read_write == I2C_SMBUS_WRITE) + sis5595_write(SMB_BYTE, data->byte); + size = SIS5595_BYTE_DATA; + break; + case I2C_SMBUS_PROC_CALL: + case I2C_SMBUS_WORD_DATA: + sis5595_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01)); + sis5595_write(SMB_CMD, command); + if (read_write == I2C_SMBUS_WRITE) { + sis5595_write(SMB_BYTE, data->word & 0xff); + sis5595_write(SMB_BYTE + 1, + (data->word & 0xff00) >> 8); + } + size = (size == I2C_SMBUS_PROC_CALL) ? SIS5595_PROC_CALL : SIS5595_WORD_DATA; + break; + default: + dev_warn(&adap->dev, "Unsupported transaction %d\n", size); + return -EOPNOTSUPP; + } + + sis5595_write(SMB_CTL_LO, ((size & 0x0E))); + + status = sis5595_transaction(adap); + if (status) + return status; + + if ((size != SIS5595_PROC_CALL) && + ((read_write == I2C_SMBUS_WRITE) || (size == SIS5595_QUICK))) + return 0; + + + switch (size) { + case SIS5595_BYTE: + case SIS5595_BYTE_DATA: + data->byte = sis5595_read(SMB_BYTE); + break; + case SIS5595_WORD_DATA: + case SIS5595_PROC_CALL: + data->word = sis5595_read(SMB_BYTE) + (sis5595_read(SMB_BYTE + 1) << 8); + break; + } + return 0; +} + +static u32 sis5595_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_PROC_CALL; +} + +static const struct i2c_algorithm smbus_algorithm = { + .smbus_xfer = sis5595_access, + .functionality = sis5595_func, +}; + +static struct i2c_adapter sis5595_adapter = { + .owner = THIS_MODULE, + .id = I2C_HW_SMBUS_SIS5595, + .class = I2C_CLASS_HWMON | I2C_CLASS_SPD, + .algo = &smbus_algorithm, +}; + +static struct pci_device_id sis5595_ids[] __devinitdata = { + { PCI_DEVICE(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_503) }, + { 0, } +}; + +MODULE_DEVICE_TABLE (pci, sis5595_ids); + +static int __devinit sis5595_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + int err; + + if (sis5595_setup(dev)) { + dev_err(&dev->dev, "SIS5595 not detected, module not inserted.\n"); + return -ENODEV; + } + + /* set up the sysfs linkage to our parent device */ + sis5595_adapter.dev.parent = &dev->dev; + + sprintf(sis5595_adapter.name, "SMBus SIS5595 adapter at %04x", + sis5595_base + SMB_INDEX); + err = i2c_add_adapter(&sis5595_adapter); + if (err) { + release_region(sis5595_base + SMB_INDEX, 2); + return err; + } + + /* Always return failure here. This is to allow other drivers to bind + * to this pci device. We don't really want to have control over the + * pci device, we only wanted to read as few register values from it. + */ + sis5595_pdev = pci_dev_get(dev); + return -ENODEV; +} + +static struct pci_driver sis5595_driver = { + .name = "sis5595_smbus", + .id_table = sis5595_ids, + .probe = sis5595_probe, +}; + +static int __init i2c_sis5595_init(void) +{ + return pci_register_driver(&sis5595_driver); +} + +static void __exit i2c_sis5595_exit(void) +{ + pci_unregister_driver(&sis5595_driver); + if (sis5595_pdev) { + i2c_del_adapter(&sis5595_adapter); + release_region(sis5595_base + SMB_INDEX, 2); + pci_dev_put(sis5595_pdev); + sis5595_pdev = NULL; + } +} + +MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl>"); +MODULE_DESCRIPTION("SIS5595 SMBus driver"); +MODULE_LICENSE("GPL"); + +module_init(i2c_sis5595_init); +module_exit(i2c_sis5595_exit); diff --git a/drivers/i2c/busses/i2c-sis630.c b/drivers/i2c/busses/i2c-sis630.c new file mode 100644 index 0000000..e7c4b79 --- /dev/null +++ b/drivers/i2c/busses/i2c-sis630.c @@ -0,0 +1,530 @@ +/* + Copyright (c) 2002,2003 Alexander Malysh <amalysh@web.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* + Changes: + 24.08.2002 + Fixed the typo in sis630_access (Thanks to Mark M. Hoffman) + Changed sis630_transaction.(Thanks to Mark M. Hoffman) + 18.09.2002 + Added SIS730 as supported. + 21.09.2002 + Added high_clock module option.If this option is set + used Host Master Clock 56KHz (default 14KHz).For now we save old Host + Master Clock and after transaction completed restore (otherwise + it's confuse BIOS and hung Machine). + 24.09.2002 + Fixed typo in sis630_access + Fixed logical error by restoring of Host Master Clock + 31.07.2003 + Added block data read/write support. +*/ + +/* + Status: beta + + Supports: + SIS 630 + SIS 730 + + Note: we assume there can only be one device, with one SMBus interface. +*/ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/pci.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/acpi.h> +#include <asm/io.h> + +/* SIS630 SMBus registers */ +#define SMB_STS 0x80 /* status */ +#define SMB_EN 0x81 /* status enable */ +#define SMB_CNT 0x82 +#define SMBHOST_CNT 0x83 +#define SMB_ADDR 0x84 +#define SMB_CMD 0x85 +#define SMB_PCOUNT 0x86 /* processed count */ +#define SMB_COUNT 0x87 +#define SMB_BYTE 0x88 /* ~0x8F data byte field */ +#define SMBDEV_ADDR 0x90 +#define SMB_DB0 0x91 +#define SMB_DB1 0x92 +#define SMB_SAA 0x93 + +/* register count for request_region */ +#define SIS630_SMB_IOREGION 20 + +/* PCI address constants */ +/* acpi base address register */ +#define SIS630_ACPI_BASE_REG 0x74 +/* bios control register */ +#define SIS630_BIOS_CTL_REG 0x40 + +/* Other settings */ +#define MAX_TIMEOUT 500 + +/* SIS630 constants */ +#define SIS630_QUICK 0x00 +#define SIS630_BYTE 0x01 +#define SIS630_BYTE_DATA 0x02 +#define SIS630_WORD_DATA 0x03 +#define SIS630_PCALL 0x04 +#define SIS630_BLOCK_DATA 0x05 + +static struct pci_driver sis630_driver; + +/* insmod parameters */ +static int high_clock; +static int force; +module_param(high_clock, bool, 0); +MODULE_PARM_DESC(high_clock, "Set Host Master Clock to 56KHz (default 14KHz)."); +module_param(force, bool, 0); +MODULE_PARM_DESC(force, "Forcibly enable the SIS630. DANGEROUS!"); + +/* acpi base address */ +static unsigned short acpi_base; + +/* supported chips */ +static int supported[] = { + PCI_DEVICE_ID_SI_630, + PCI_DEVICE_ID_SI_730, + 0 /* terminates the list */ +}; + +static inline u8 sis630_read(u8 reg) +{ + return inb(acpi_base + reg); +} + +static inline void sis630_write(u8 reg, u8 data) +{ + outb(data, acpi_base + reg); +} + +static int sis630_transaction_start(struct i2c_adapter *adap, int size, u8 *oldclock) +{ + int temp; + + /* Make sure the SMBus host is ready to start transmitting. */ + if ((temp = sis630_read(SMB_CNT) & 0x03) != 0x00) { + dev_dbg(&adap->dev, "SMBus busy (%02x).Resetting...\n",temp); + /* kill smbus transaction */ + sis630_write(SMBHOST_CNT, 0x20); + + if ((temp = sis630_read(SMB_CNT) & 0x03) != 0x00) { + dev_dbg(&adap->dev, "Failed! (%02x)\n", temp); + return -EBUSY; + } else { + dev_dbg(&adap->dev, "Successful!\n"); + } + } + + /* save old clock, so we can prevent machine for hung */ + *oldclock = sis630_read(SMB_CNT); + + dev_dbg(&adap->dev, "saved clock 0x%02x\n", *oldclock); + + /* disable timeout interrupt , set Host Master Clock to 56KHz if requested */ + if (high_clock) + sis630_write(SMB_CNT, 0x20); + else + sis630_write(SMB_CNT, (*oldclock & ~0x40)); + + /* clear all sticky bits */ + temp = sis630_read(SMB_STS); + sis630_write(SMB_STS, temp & 0x1e); + + /* start the transaction by setting bit 4 and size */ + sis630_write(SMBHOST_CNT,0x10 | (size & 0x07)); + + return 0; +} + +static int sis630_transaction_wait(struct i2c_adapter *adap, int size) +{ + int temp, result = 0, timeout = 0; + + /* We will always wait for a fraction of a second! */ + do { + msleep(1); + temp = sis630_read(SMB_STS); + /* check if block transmitted */ + if (size == SIS630_BLOCK_DATA && (temp & 0x10)) + break; + } while (!(temp & 0x0e) && (timeout++ < MAX_TIMEOUT)); + + /* If the SMBus is still busy, we give up */ + if (timeout >= MAX_TIMEOUT) { + dev_dbg(&adap->dev, "SMBus Timeout!\n"); + result = -ETIMEDOUT; + } + + if (temp & 0x02) { + dev_dbg(&adap->dev, "Error: Failed bus transaction\n"); + result = -ENXIO; + } + + if (temp & 0x04) { + dev_err(&adap->dev, "Bus collision!\n"); + result = -EIO; + /* + TBD: Datasheet say: + the software should clear this bit and restart SMBUS operation. + Should we do it or user start request again? + */ + } + + return result; +} + +static void sis630_transaction_end(struct i2c_adapter *adap, u8 oldclock) +{ + int temp = 0; + + /* clear all status "sticky" bits */ + sis630_write(SMB_STS, temp); + + dev_dbg(&adap->dev, "SMB_CNT before clock restore 0x%02x\n", sis630_read(SMB_CNT)); + + /* + * restore old Host Master Clock if high_clock is set + * and oldclock was not 56KHz + */ + if (high_clock && !(oldclock & 0x20)) + sis630_write(SMB_CNT,(sis630_read(SMB_CNT) & ~0x20)); + + dev_dbg(&adap->dev, "SMB_CNT after clock restore 0x%02x\n", sis630_read(SMB_CNT)); +} + +static int sis630_transaction(struct i2c_adapter *adap, int size) +{ + int result = 0; + u8 oldclock = 0; + + result = sis630_transaction_start(adap, size, &oldclock); + if (!result) { + result = sis630_transaction_wait(adap, size); + sis630_transaction_end(adap, oldclock); + } + + return result; +} + +static int sis630_block_data(struct i2c_adapter *adap, union i2c_smbus_data *data, int read_write) +{ + int i, len = 0, rc = 0; + u8 oldclock = 0; + + if (read_write == I2C_SMBUS_WRITE) { + len = data->block[0]; + if (len < 0) + len = 0; + else if (len > 32) + len = 32; + sis630_write(SMB_COUNT, len); + for (i=1; i <= len; i++) { + dev_dbg(&adap->dev, "set data 0x%02x\n", data->block[i]); + /* set data */ + sis630_write(SMB_BYTE+(i-1)%8, data->block[i]); + if (i==8 || (len<8 && i==len)) { + dev_dbg(&adap->dev, "start trans len=%d i=%d\n",len ,i); + /* first transaction */ + rc = sis630_transaction_start(adap, + SIS630_BLOCK_DATA, &oldclock); + if (rc) + return rc; + } + else if ((i-1)%8 == 7 || i==len) { + dev_dbg(&adap->dev, "trans_wait len=%d i=%d\n",len,i); + if (i>8) { + dev_dbg(&adap->dev, "clear smbary_sts len=%d i=%d\n",len,i); + /* + If this is not first transaction, + we must clear sticky bit. + clear SMBARY_STS + */ + sis630_write(SMB_STS,0x10); + } + rc = sis630_transaction_wait(adap, + SIS630_BLOCK_DATA); + if (rc) { + dev_dbg(&adap->dev, "trans_wait failed\n"); + break; + } + } + } + } + else { + /* read request */ + data->block[0] = len = 0; + rc = sis630_transaction_start(adap, + SIS630_BLOCK_DATA, &oldclock); + if (rc) + return rc; + do { + rc = sis630_transaction_wait(adap, SIS630_BLOCK_DATA); + if (rc) { + dev_dbg(&adap->dev, "trans_wait failed\n"); + break; + } + /* if this first transaction then read byte count */ + if (len == 0) + data->block[0] = sis630_read(SMB_COUNT); + + /* just to be sure */ + if (data->block[0] > 32) + data->block[0] = 32; + + dev_dbg(&adap->dev, "block data read len=0x%x\n", data->block[0]); + + for (i=0; i < 8 && len < data->block[0]; i++,len++) { + dev_dbg(&adap->dev, "read i=%d len=%d\n", i, len); + data->block[len+1] = sis630_read(SMB_BYTE+i); + } + + dev_dbg(&adap->dev, "clear smbary_sts len=%d i=%d\n",len,i); + + /* clear SMBARY_STS */ + sis630_write(SMB_STS,0x10); + } while(len < data->block[0]); + } + + sis630_transaction_end(adap, oldclock); + + return rc; +} + +/* Return negative errno on error. */ +static s32 sis630_access(struct i2c_adapter *adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, union i2c_smbus_data *data) +{ + int status; + + switch (size) { + case I2C_SMBUS_QUICK: + sis630_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01)); + size = SIS630_QUICK; + break; + case I2C_SMBUS_BYTE: + sis630_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01)); + if (read_write == I2C_SMBUS_WRITE) + sis630_write(SMB_CMD, command); + size = SIS630_BYTE; + break; + case I2C_SMBUS_BYTE_DATA: + sis630_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01)); + sis630_write(SMB_CMD, command); + if (read_write == I2C_SMBUS_WRITE) + sis630_write(SMB_BYTE, data->byte); + size = SIS630_BYTE_DATA; + break; + case I2C_SMBUS_PROC_CALL: + case I2C_SMBUS_WORD_DATA: + sis630_write(SMB_ADDR,((addr & 0x7f) << 1) | (read_write & 0x01)); + sis630_write(SMB_CMD, command); + if (read_write == I2C_SMBUS_WRITE) { + sis630_write(SMB_BYTE, data->word & 0xff); + sis630_write(SMB_BYTE + 1,(data->word & 0xff00) >> 8); + } + size = (size == I2C_SMBUS_PROC_CALL ? SIS630_PCALL : SIS630_WORD_DATA); + break; + case I2C_SMBUS_BLOCK_DATA: + sis630_write(SMB_ADDR,((addr & 0x7f) << 1) | (read_write & 0x01)); + sis630_write(SMB_CMD, command); + size = SIS630_BLOCK_DATA; + return sis630_block_data(adap, data, read_write); + default: + dev_warn(&adap->dev, "Unsupported transaction %d\n", + size); + return -EOPNOTSUPP; + } + + status = sis630_transaction(adap, size); + if (status) + return status; + + if ((size != SIS630_PCALL) && + ((read_write == I2C_SMBUS_WRITE) || (size == SIS630_QUICK))) { + return 0; + } + + switch(size) { + case SIS630_BYTE: + case SIS630_BYTE_DATA: + data->byte = sis630_read(SMB_BYTE); + break; + case SIS630_PCALL: + case SIS630_WORD_DATA: + data->word = sis630_read(SMB_BYTE) + (sis630_read(SMB_BYTE + 1) << 8); + break; + } + + return 0; +} + +static u32 sis630_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_PROC_CALL | + I2C_FUNC_SMBUS_BLOCK_DATA; +} + +static int sis630_setup(struct pci_dev *sis630_dev) +{ + unsigned char b; + struct pci_dev *dummy = NULL; + int retval = -ENODEV, i; + + /* check for supported SiS devices */ + for (i=0; supported[i] > 0 ; i++) { + if ((dummy = pci_get_device(PCI_VENDOR_ID_SI, supported[i], dummy))) + break; /* found */ + } + + if (dummy) { + pci_dev_put(dummy); + } + else if (force) { + dev_err(&sis630_dev->dev, "WARNING: Can't detect SIS630 compatible device, but " + "loading because of force option enabled\n"); + } + else { + return -ENODEV; + } + + /* + Enable ACPI first , so we can accsess reg 74-75 + in acpi io space and read acpi base addr + */ + if (pci_read_config_byte(sis630_dev, SIS630_BIOS_CTL_REG,&b)) { + dev_err(&sis630_dev->dev, "Error: Can't read bios ctl reg\n"); + goto exit; + } + /* if ACPI already enabled , do nothing */ + if (!(b & 0x80) && + pci_write_config_byte(sis630_dev, SIS630_BIOS_CTL_REG, b | 0x80)) { + dev_err(&sis630_dev->dev, "Error: Can't enable ACPI\n"); + goto exit; + } + + /* Determine the ACPI base address */ + if (pci_read_config_word(sis630_dev,SIS630_ACPI_BASE_REG,&acpi_base)) { + dev_err(&sis630_dev->dev, "Error: Can't determine ACPI base address\n"); + goto exit; + } + + dev_dbg(&sis630_dev->dev, "ACPI base at 0x%04x\n", acpi_base); + + retval = acpi_check_region(acpi_base + SMB_STS, SIS630_SMB_IOREGION, + sis630_driver.name); + if (retval) + goto exit; + + /* Everything is happy, let's grab the memory and set things up. */ + if (!request_region(acpi_base + SMB_STS, SIS630_SMB_IOREGION, + sis630_driver.name)) { + dev_err(&sis630_dev->dev, "SMBus registers 0x%04x-0x%04x already " + "in use!\n", acpi_base + SMB_STS, acpi_base + SMB_SAA); + goto exit; + } + + retval = 0; + +exit: + if (retval) + acpi_base = 0; + return retval; +} + + +static const struct i2c_algorithm smbus_algorithm = { + .smbus_xfer = sis630_access, + .functionality = sis630_func, +}; + +static struct i2c_adapter sis630_adapter = { + .owner = THIS_MODULE, + .id = I2C_HW_SMBUS_SIS630, + .class = I2C_CLASS_HWMON | I2C_CLASS_SPD, + .algo = &smbus_algorithm, +}; + +static struct pci_device_id sis630_ids[] __devinitdata = { + { PCI_DEVICE(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_503) }, + { PCI_DEVICE(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_LPC) }, + { 0, } +}; + +MODULE_DEVICE_TABLE (pci, sis630_ids); + +static int __devinit sis630_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + if (sis630_setup(dev)) { + dev_err(&dev->dev, "SIS630 comp. bus not detected, module not inserted.\n"); + return -ENODEV; + } + + /* set up the sysfs linkage to our parent device */ + sis630_adapter.dev.parent = &dev->dev; + + sprintf(sis630_adapter.name, "SMBus SIS630 adapter at %04x", + acpi_base + SMB_STS); + + return i2c_add_adapter(&sis630_adapter); +} + +static void __devexit sis630_remove(struct pci_dev *dev) +{ + if (acpi_base) { + i2c_del_adapter(&sis630_adapter); + release_region(acpi_base + SMB_STS, SIS630_SMB_IOREGION); + acpi_base = 0; + } +} + + +static struct pci_driver sis630_driver = { + .name = "sis630_smbus", + .id_table = sis630_ids, + .probe = sis630_probe, + .remove = __devexit_p(sis630_remove), +}; + +static int __init i2c_sis630_init(void) +{ + return pci_register_driver(&sis630_driver); +} + + +static void __exit i2c_sis630_exit(void) +{ + pci_unregister_driver(&sis630_driver); +} + + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Alexander Malysh <amalysh@web.de>"); +MODULE_DESCRIPTION("SIS630 SMBus driver"); + +module_init(i2c_sis630_init); +module_exit(i2c_sis630_exit); diff --git a/drivers/i2c/busses/i2c-sis96x.c b/drivers/i2c/busses/i2c-sis96x.c new file mode 100644 index 0000000..f1bba63 --- /dev/null +++ b/drivers/i2c/busses/i2c-sis96x.c @@ -0,0 +1,345 @@ +/* + Copyright (c) 2003 Mark M. Hoffman <mhoffman@lightlink.com> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* + This module must be considered BETA unless and until + the chipset manufacturer releases a datasheet. + The register definitions are based on the SiS630. + + This module relies on quirk_sis_96x_smbus (drivers/pci/quirks.c) + for just about every machine for which users have reported. + If this module isn't detecting your 96x south bridge, have a + look there. + + We assume there can only be one SiS96x with one SMBus interface. +*/ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/stddef.h> +#include <linux/ioport.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/acpi.h> +#include <asm/io.h> + +/* base address register in PCI config space */ +#define SIS96x_BAR 0x04 + +/* SiS96x SMBus registers */ +#define SMB_STS 0x00 +#define SMB_EN 0x01 +#define SMB_CNT 0x02 +#define SMB_HOST_CNT 0x03 +#define SMB_ADDR 0x04 +#define SMB_CMD 0x05 +#define SMB_PCOUNT 0x06 +#define SMB_COUNT 0x07 +#define SMB_BYTE 0x08 +#define SMB_DEV_ADDR 0x10 +#define SMB_DB0 0x11 +#define SMB_DB1 0x12 +#define SMB_SAA 0x13 + +/* register count for request_region */ +#define SMB_IOSIZE 0x20 + +/* Other settings */ +#define MAX_TIMEOUT 500 + +/* SiS96x SMBus constants */ +#define SIS96x_QUICK 0x00 +#define SIS96x_BYTE 0x01 +#define SIS96x_BYTE_DATA 0x02 +#define SIS96x_WORD_DATA 0x03 +#define SIS96x_PROC_CALL 0x04 +#define SIS96x_BLOCK_DATA 0x05 + +static struct pci_driver sis96x_driver; +static struct i2c_adapter sis96x_adapter; +static u16 sis96x_smbus_base; + +static inline u8 sis96x_read(u8 reg) +{ + return inb(sis96x_smbus_base + reg) ; +} + +static inline void sis96x_write(u8 reg, u8 data) +{ + outb(data, sis96x_smbus_base + reg) ; +} + +/* Execute a SMBus transaction. + int size is from SIS96x_QUICK to SIS96x_BLOCK_DATA + */ +static int sis96x_transaction(int size) +{ + int temp; + int result = 0; + int timeout = 0; + + dev_dbg(&sis96x_adapter.dev, "SMBus transaction %d\n", size); + + /* Make sure the SMBus host is ready to start transmitting */ + if (((temp = sis96x_read(SMB_CNT)) & 0x03) != 0x00) { + + dev_dbg(&sis96x_adapter.dev, "SMBus busy (0x%02x). " + "Resetting...\n", temp); + + /* kill the transaction */ + sis96x_write(SMB_HOST_CNT, 0x20); + + /* check it again */ + if (((temp = sis96x_read(SMB_CNT)) & 0x03) != 0x00) { + dev_dbg(&sis96x_adapter.dev, "Failed (0x%02x)\n", temp); + return -EBUSY; + } else { + dev_dbg(&sis96x_adapter.dev, "Successful\n"); + } + } + + /* Turn off timeout interrupts, set fast host clock */ + sis96x_write(SMB_CNT, 0x20); + + /* clear all (sticky) status flags */ + temp = sis96x_read(SMB_STS); + sis96x_write(SMB_STS, temp & 0x1e); + + /* start the transaction by setting bit 4 and size bits */ + sis96x_write(SMB_HOST_CNT, 0x10 | (size & 0x07)); + + /* We will always wait for a fraction of a second! */ + do { + msleep(1); + temp = sis96x_read(SMB_STS); + } while (!(temp & 0x0e) && (timeout++ < MAX_TIMEOUT)); + + /* If the SMBus is still busy, we give up */ + if (timeout >= MAX_TIMEOUT) { + dev_dbg(&sis96x_adapter.dev, "SMBus Timeout! (0x%02x)\n", temp); + result = -ETIMEDOUT; + } + + /* device error - probably missing ACK */ + if (temp & 0x02) { + dev_dbg(&sis96x_adapter.dev, "Failed bus transaction!\n"); + result = -ENXIO; + } + + /* bus collision */ + if (temp & 0x04) { + dev_dbg(&sis96x_adapter.dev, "Bus collision!\n"); + result = -EIO; + } + + /* Finish up by resetting the bus */ + sis96x_write(SMB_STS, temp); + if ((temp = sis96x_read(SMB_STS))) { + dev_dbg(&sis96x_adapter.dev, "Failed reset at " + "end of transaction! (0x%02x)\n", temp); + } + + return result; +} + +/* Return negative errno on error. */ +static s32 sis96x_access(struct i2c_adapter * adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, union i2c_smbus_data * data) +{ + int status; + + switch (size) { + case I2C_SMBUS_QUICK: + sis96x_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01)); + size = SIS96x_QUICK; + break; + + case I2C_SMBUS_BYTE: + sis96x_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01)); + if (read_write == I2C_SMBUS_WRITE) + sis96x_write(SMB_CMD, command); + size = SIS96x_BYTE; + break; + + case I2C_SMBUS_BYTE_DATA: + sis96x_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01)); + sis96x_write(SMB_CMD, command); + if (read_write == I2C_SMBUS_WRITE) + sis96x_write(SMB_BYTE, data->byte); + size = SIS96x_BYTE_DATA; + break; + + case I2C_SMBUS_PROC_CALL: + case I2C_SMBUS_WORD_DATA: + sis96x_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01)); + sis96x_write(SMB_CMD, command); + if (read_write == I2C_SMBUS_WRITE) { + sis96x_write(SMB_BYTE, data->word & 0xff); + sis96x_write(SMB_BYTE + 1, (data->word & 0xff00) >> 8); + } + size = (size == I2C_SMBUS_PROC_CALL ? + SIS96x_PROC_CALL : SIS96x_WORD_DATA); + break; + + default: + dev_warn(&adap->dev, "Unsupported transaction %d\n", size); + return -EOPNOTSUPP; + } + + status = sis96x_transaction(size); + if (status) + return status; + + if ((size != SIS96x_PROC_CALL) && + ((read_write == I2C_SMBUS_WRITE) || (size == SIS96x_QUICK))) + return 0; + + switch (size) { + case SIS96x_BYTE: + case SIS96x_BYTE_DATA: + data->byte = sis96x_read(SMB_BYTE); + break; + + case SIS96x_WORD_DATA: + case SIS96x_PROC_CALL: + data->word = sis96x_read(SMB_BYTE) + + (sis96x_read(SMB_BYTE + 1) << 8); + break; + } + return 0; +} + +static u32 sis96x_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_PROC_CALL; +} + +static const struct i2c_algorithm smbus_algorithm = { + .smbus_xfer = sis96x_access, + .functionality = sis96x_func, +}; + +static struct i2c_adapter sis96x_adapter = { + .owner = THIS_MODULE, + .id = I2C_HW_SMBUS_SIS96X, + .class = I2C_CLASS_HWMON | I2C_CLASS_SPD, + .algo = &smbus_algorithm, +}; + +static struct pci_device_id sis96x_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_SMBUS) }, + { 0, } +}; + +MODULE_DEVICE_TABLE (pci, sis96x_ids); + +static int __devinit sis96x_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + u16 ww = 0; + int retval; + + if (sis96x_smbus_base) { + dev_err(&dev->dev, "Only one device supported.\n"); + return -EBUSY; + } + + pci_read_config_word(dev, PCI_CLASS_DEVICE, &ww); + if (PCI_CLASS_SERIAL_SMBUS != ww) { + dev_err(&dev->dev, "Unsupported device class 0x%04x!\n", ww); + return -ENODEV; + } + + sis96x_smbus_base = pci_resource_start(dev, SIS96x_BAR); + if (!sis96x_smbus_base) { + dev_err(&dev->dev, "SiS96x SMBus base address " + "not initialized!\n"); + return -EINVAL; + } + dev_info(&dev->dev, "SiS96x SMBus base address: 0x%04x\n", + sis96x_smbus_base); + + retval = acpi_check_resource_conflict(&dev->resource[SIS96x_BAR]); + if (retval) + return retval; + + /* Everything is happy, let's grab the memory and set things up. */ + if (!request_region(sis96x_smbus_base, SMB_IOSIZE, + sis96x_driver.name)) { + dev_err(&dev->dev, "SMBus registers 0x%04x-0x%04x " + "already in use!\n", sis96x_smbus_base, + sis96x_smbus_base + SMB_IOSIZE - 1); + + sis96x_smbus_base = 0; + return -EINVAL; + } + + /* set up the sysfs linkage to our parent device */ + sis96x_adapter.dev.parent = &dev->dev; + + snprintf(sis96x_adapter.name, sizeof(sis96x_adapter.name), + "SiS96x SMBus adapter at 0x%04x", sis96x_smbus_base); + + if ((retval = i2c_add_adapter(&sis96x_adapter))) { + dev_err(&dev->dev, "Couldn't register adapter!\n"); + release_region(sis96x_smbus_base, SMB_IOSIZE); + sis96x_smbus_base = 0; + } + + return retval; +} + +static void __devexit sis96x_remove(struct pci_dev *dev) +{ + if (sis96x_smbus_base) { + i2c_del_adapter(&sis96x_adapter); + release_region(sis96x_smbus_base, SMB_IOSIZE); + sis96x_smbus_base = 0; + } +} + +static struct pci_driver sis96x_driver = { + .name = "sis96x_smbus", + .id_table = sis96x_ids, + .probe = sis96x_probe, + .remove = __devexit_p(sis96x_remove), +}; + +static int __init i2c_sis96x_init(void) +{ + return pci_register_driver(&sis96x_driver); +} + +static void __exit i2c_sis96x_exit(void) +{ + pci_unregister_driver(&sis96x_driver); +} + +MODULE_AUTHOR("Mark M. Hoffman <mhoffman@lightlink.com>"); +MODULE_DESCRIPTION("SiS96x SMBus driver"); +MODULE_LICENSE("GPL"); + +/* Register initialization functions using helper macros */ +module_init(i2c_sis96x_init); +module_exit(i2c_sis96x_exit); + diff --git a/drivers/i2c/busses/i2c-stub.c b/drivers/i2c/busses/i2c-stub.c new file mode 100644 index 0000000..1b7b2af --- /dev/null +++ b/drivers/i2c/busses/i2c-stub.c @@ -0,0 +1,193 @@ +/* + i2c-stub.c - I2C/SMBus chip emulator + + Copyright (c) 2004 Mark M. Hoffman <mhoffman@lightlink.com> + Copyright (C) 2007 Jean Delvare <khali@linux-fr.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#define DEBUG 1 + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/i2c.h> + +#define MAX_CHIPS 10 + +static unsigned short chip_addr[MAX_CHIPS]; +module_param_array(chip_addr, ushort, NULL, S_IRUGO); +MODULE_PARM_DESC(chip_addr, + "Chip addresses (up to 10, between 0x03 and 0x77)"); + +struct stub_chip { + u8 pointer; + u16 words[256]; /* Byte operations use the LSB as per SMBus + specification */ +}; + +static struct stub_chip *stub_chips; + +/* Return negative errno on error. */ +static s32 stub_xfer(struct i2c_adapter * adap, u16 addr, unsigned short flags, + char read_write, u8 command, int size, union i2c_smbus_data * data) +{ + s32 ret; + int i; + struct stub_chip *chip = NULL; + + /* Search for the right chip */ + for (i = 0; i < MAX_CHIPS && chip_addr[i]; i++) { + if (addr == chip_addr[i]) { + chip = stub_chips + i; + break; + } + } + if (!chip) + return -ENODEV; + + switch (size) { + + case I2C_SMBUS_QUICK: + dev_dbg(&adap->dev, "smbus quick - addr 0x%02x\n", addr); + ret = 0; + break; + + case I2C_SMBUS_BYTE: + if (read_write == I2C_SMBUS_WRITE) { + chip->pointer = command; + dev_dbg(&adap->dev, "smbus byte - addr 0x%02x, " + "wrote 0x%02x.\n", + addr, command); + } else { + data->byte = chip->words[chip->pointer++] & 0xff; + dev_dbg(&adap->dev, "smbus byte - addr 0x%02x, " + "read 0x%02x.\n", + addr, data->byte); + } + + ret = 0; + break; + + case I2C_SMBUS_BYTE_DATA: + if (read_write == I2C_SMBUS_WRITE) { + chip->words[command] &= 0xff00; + chip->words[command] |= data->byte; + dev_dbg(&adap->dev, "smbus byte data - addr 0x%02x, " + "wrote 0x%02x at 0x%02x.\n", + addr, data->byte, command); + } else { + data->byte = chip->words[command] & 0xff; + dev_dbg(&adap->dev, "smbus byte data - addr 0x%02x, " + "read 0x%02x at 0x%02x.\n", + addr, data->byte, command); + } + chip->pointer = command + 1; + + ret = 0; + break; + + case I2C_SMBUS_WORD_DATA: + if (read_write == I2C_SMBUS_WRITE) { + chip->words[command] = data->word; + dev_dbg(&adap->dev, "smbus word data - addr 0x%02x, " + "wrote 0x%04x at 0x%02x.\n", + addr, data->word, command); + } else { + data->word = chip->words[command]; + dev_dbg(&adap->dev, "smbus word data - addr 0x%02x, " + "read 0x%04x at 0x%02x.\n", + addr, data->word, command); + } + + ret = 0; + break; + + default: + dev_dbg(&adap->dev, "Unsupported I2C/SMBus command\n"); + ret = -EOPNOTSUPP; + break; + } /* switch (size) */ + + return ret; +} + +static u32 stub_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA; +} + +static const struct i2c_algorithm smbus_algorithm = { + .functionality = stub_func, + .smbus_xfer = stub_xfer, +}; + +static struct i2c_adapter stub_adapter = { + .owner = THIS_MODULE, + .class = I2C_CLASS_HWMON | I2C_CLASS_SPD, + .algo = &smbus_algorithm, + .name = "SMBus stub driver", +}; + +static int __init i2c_stub_init(void) +{ + int i, ret; + + if (!chip_addr[0]) { + printk(KERN_ERR "i2c-stub: Please specify a chip address\n"); + return -ENODEV; + } + + for (i = 0; i < MAX_CHIPS && chip_addr[i]; i++) { + if (chip_addr[i] < 0x03 || chip_addr[i] > 0x77) { + printk(KERN_ERR "i2c-stub: Invalid chip address " + "0x%02x\n", chip_addr[i]); + return -EINVAL; + } + + printk(KERN_INFO "i2c-stub: Virtual chip at 0x%02x\n", + chip_addr[i]); + } + + /* Allocate memory for all chips at once */ + stub_chips = kzalloc(i * sizeof(struct stub_chip), GFP_KERNEL); + if (!stub_chips) { + printk(KERN_ERR "i2c-stub: Out of memory\n"); + return -ENOMEM; + } + + ret = i2c_add_adapter(&stub_adapter); + if (ret) + kfree(stub_chips); + return ret; +} + +static void __exit i2c_stub_exit(void) +{ + i2c_del_adapter(&stub_adapter); + kfree(stub_chips); +} + +MODULE_AUTHOR("Mark M. Hoffman <mhoffman@lightlink.com>"); +MODULE_DESCRIPTION("I2C stub driver"); +MODULE_LICENSE("GPL"); + +module_init(i2c_stub_init); +module_exit(i2c_stub_exit); + diff --git a/drivers/i2c/busses/i2c-taos-evm.c b/drivers/i2c/busses/i2c-taos-evm.c new file mode 100644 index 0000000..224aa12 --- /dev/null +++ b/drivers/i2c/busses/i2c-taos-evm.c @@ -0,0 +1,328 @@ +/* + * Driver for the TAOS evaluation modules + * These devices include an I2C master which can be controlled over the + * serial port. + * + * Copyright (C) 2007 Jean Delvare <khali@linux-fr.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/init.h> +#include <linux/i2c.h> + +#define TAOS_BUFFER_SIZE 63 + +#define TAOS_STATE_INIT 0 +#define TAOS_STATE_IDLE 1 +#define TAOS_STATE_SEND 2 +#define TAOS_STATE_RECV 3 + +#define TAOS_CMD_RESET 0x12 + +static DECLARE_WAIT_QUEUE_HEAD(wq); + +struct taos_data { + struct i2c_adapter adapter; + struct i2c_client *client; + int state; + u8 addr; /* last used address */ + unsigned char buffer[TAOS_BUFFER_SIZE]; + unsigned int pos; /* position inside the buffer */ +}; + +/* TAOS TSL2550 EVM */ +static struct i2c_board_info tsl2550_info = { + I2C_BOARD_INFO("tsl2550", 0x39), +}; + +/* Instantiate i2c devices based on the adapter name */ +static struct i2c_client *taos_instantiate_device(struct i2c_adapter *adapter) +{ + if (!strncmp(adapter->name, "TAOS TSL2550 EVM", 16)) { + dev_info(&adapter->dev, "Instantiating device %s at 0x%02x\n", + tsl2550_info.type, tsl2550_info.addr); + return i2c_new_device(adapter, &tsl2550_info); + } + + return NULL; +} + +static int taos_smbus_xfer(struct i2c_adapter *adapter, u16 addr, + unsigned short flags, char read_write, u8 command, + int size, union i2c_smbus_data *data) +{ + struct serio *serio = adapter->algo_data; + struct taos_data *taos = serio_get_drvdata(serio); + char *p; + + /* Encode our transaction. "@" is for the device address, "$" for the + SMBus command and "#" for the data. */ + p = taos->buffer; + + /* The device remembers the last used address, no need to send it + again if it's the same */ + if (addr != taos->addr) + p += sprintf(p, "@%02X", addr); + + switch (size) { + case I2C_SMBUS_BYTE: + if (read_write == I2C_SMBUS_WRITE) + sprintf(p, "$#%02X", command); + else + sprintf(p, "$"); + break; + case I2C_SMBUS_BYTE_DATA: + if (read_write == I2C_SMBUS_WRITE) + sprintf(p, "$%02X#%02X", command, data->byte); + else + sprintf(p, "$%02X", command); + break; + default: + dev_warn(&adapter->dev, "Unsupported transaction %d\n", size); + return -EOPNOTSUPP; + } + + /* Send the transaction to the TAOS EVM */ + dev_dbg(&adapter->dev, "Command buffer: %s\n", taos->buffer); + taos->pos = 0; + taos->state = TAOS_STATE_SEND; + serio_write(serio, taos->buffer[0]); + wait_event_interruptible_timeout(wq, taos->state == TAOS_STATE_IDLE, + msecs_to_jiffies(250)); + if (taos->state != TAOS_STATE_IDLE) { + dev_err(&adapter->dev, "Transaction failed " + "(state=%d, pos=%d)\n", taos->state, taos->pos); + taos->addr = 0; + return -EIO; + } + taos->addr = addr; + + /* Start the transaction and read the answer */ + taos->pos = 0; + taos->state = TAOS_STATE_RECV; + serio_write(serio, read_write == I2C_SMBUS_WRITE ? '>' : '<'); + wait_event_interruptible_timeout(wq, taos->state == TAOS_STATE_IDLE, + msecs_to_jiffies(150)); + if (taos->state != TAOS_STATE_IDLE + || taos->pos != 6) { + dev_err(&adapter->dev, "Transaction timeout (pos=%d)\n", + taos->pos); + return -EIO; + } + dev_dbg(&adapter->dev, "Answer buffer: %s\n", taos->buffer); + + /* Interpret the returned string */ + p = taos->buffer + 2; + p[3] = '\0'; + if (!strcmp(p, "NAK")) + return -ENODEV; + + if (read_write == I2C_SMBUS_WRITE) { + if (!strcmp(p, "ACK")) + return 0; + } else { + if (p[0] == 'x') { + data->byte = simple_strtol(p + 1, NULL, 16); + return 0; + } + } + + return -EIO; +} + +static u32 taos_smbus_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA; +} + +static const struct i2c_algorithm taos_algorithm = { + .smbus_xfer = taos_smbus_xfer, + .functionality = taos_smbus_func, +}; + +static irqreturn_t taos_interrupt(struct serio *serio, unsigned char data, + unsigned int flags) +{ + struct taos_data *taos = serio_get_drvdata(serio); + + switch (taos->state) { + case TAOS_STATE_INIT: + taos->buffer[taos->pos++] = data; + if (data == ':' + || taos->pos == TAOS_BUFFER_SIZE - 1) { + taos->buffer[taos->pos] = '\0'; + taos->state = TAOS_STATE_IDLE; + wake_up_interruptible(&wq); + } + break; + case TAOS_STATE_SEND: + if (taos->buffer[++taos->pos]) + serio_write(serio, taos->buffer[taos->pos]); + else { + taos->state = TAOS_STATE_IDLE; + wake_up_interruptible(&wq); + } + break; + case TAOS_STATE_RECV: + taos->buffer[taos->pos++] = data; + if (data == ']') { + taos->buffer[taos->pos] = '\0'; + taos->state = TAOS_STATE_IDLE; + wake_up_interruptible(&wq); + } + break; + } + + return IRQ_HANDLED; +} + +/* Extract the adapter name from the buffer received after reset. + The buffer is modified and a pointer inside the buffer is returned. */ +static char *taos_adapter_name(char *buffer) +{ + char *start, *end; + + start = strstr(buffer, "TAOS "); + if (!start) + return NULL; + + end = strchr(start, '\r'); + if (!end) + return NULL; + *end = '\0'; + + return start; +} + +static int taos_connect(struct serio *serio, struct serio_driver *drv) +{ + struct taos_data *taos; + struct i2c_adapter *adapter; + char *name; + int err; + + taos = kzalloc(sizeof(struct taos_data), GFP_KERNEL); + if (!taos) { + err = -ENOMEM; + goto exit; + } + taos->state = TAOS_STATE_INIT; + serio_set_drvdata(serio, taos); + + err = serio_open(serio, drv); + if (err) + goto exit_kfree; + + adapter = &taos->adapter; + adapter->owner = THIS_MODULE; + adapter->algo = &taos_algorithm; + adapter->algo_data = serio; + adapter->dev.parent = &serio->dev; + + /* Reset the TAOS evaluation module to identify it */ + serio_write(serio, TAOS_CMD_RESET); + wait_event_interruptible_timeout(wq, taos->state == TAOS_STATE_IDLE, + msecs_to_jiffies(2000)); + + if (taos->state != TAOS_STATE_IDLE) { + err = -ENODEV; + dev_dbg(&serio->dev, "TAOS EVM reset failed (state=%d, " + "pos=%d)\n", taos->state, taos->pos); + goto exit_close; + } + + name = taos_adapter_name(taos->buffer); + if (!name) { + err = -ENODEV; + dev_err(&serio->dev, "TAOS EVM identification failed\n"); + goto exit_close; + } + strlcpy(adapter->name, name, sizeof(adapter->name)); + + err = i2c_add_adapter(adapter); + if (err) + goto exit_close; + dev_dbg(&serio->dev, "Connected to TAOS EVM\n"); + + taos->client = taos_instantiate_device(adapter); + return 0; + + exit_close: + serio_close(serio); + exit_kfree: + serio_set_drvdata(serio, NULL); + kfree(taos); + exit: + return err; +} + +static void taos_disconnect(struct serio *serio) +{ + struct taos_data *taos = serio_get_drvdata(serio); + + if (taos->client) + i2c_unregister_device(taos->client); + i2c_del_adapter(&taos->adapter); + serio_close(serio); + serio_set_drvdata(serio, NULL); + kfree(taos); + + dev_dbg(&serio->dev, "Disconnected from TAOS EVM\n"); +} + +static struct serio_device_id taos_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_TAOSEVM, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; +MODULE_DEVICE_TABLE(serio, taos_serio_ids); + +static struct serio_driver taos_drv = { + .driver = { + .name = "taos-evm", + }, + .description = "TAOS evaluation module driver", + .id_table = taos_serio_ids, + .connect = taos_connect, + .disconnect = taos_disconnect, + .interrupt = taos_interrupt, +}; + +static int __init taos_init(void) +{ + return serio_register_driver(&taos_drv); +} + +static void __exit taos_exit(void) +{ + serio_unregister_driver(&taos_drv); +} + +MODULE_AUTHOR("Jean Delvare <khali@linux-fr.org>"); +MODULE_DESCRIPTION("TAOS evaluation module driver"); +MODULE_LICENSE("GPL"); + +module_init(taos_init); +module_exit(taos_exit); diff --git a/drivers/i2c/busses/i2c-tiny-usb.c b/drivers/i2c/busses/i2c-tiny-usb.c new file mode 100644 index 0000000..b1c050f --- /dev/null +++ b/drivers/i2c/busses/i2c-tiny-usb.c @@ -0,0 +1,281 @@ +/* + * driver for the i2c-tiny-usb adapter - 1.0 + * http://www.harbaum.org/till/i2c_tiny_usb + * + * Copyright (C) 2006-2007 Till Harbaum (Till@Harbaum.org) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/module.h> + +/* include interfaces to usb layer */ +#include <linux/usb.h> + +/* include interface to i2c layer */ +#include <linux/i2c.h> + +/* commands via USB, must match command ids in the firmware */ +#define CMD_ECHO 0 +#define CMD_GET_FUNC 1 +#define CMD_SET_DELAY 2 +#define CMD_GET_STATUS 3 + +#define CMD_I2C_IO 4 +#define CMD_I2C_IO_BEGIN (1<<0) +#define CMD_I2C_IO_END (1<<1) + +/* i2c bit delay, default is 10us -> 100kHz */ +static int delay = 10; +module_param(delay, int, 0); +MODULE_PARM_DESC(delay, "bit delay in microseconds, " + "e.g. 10 for 100kHz (default is 100kHz)"); + +static int usb_read(struct i2c_adapter *adapter, int cmd, + int value, int index, void *data, int len); + +static int usb_write(struct i2c_adapter *adapter, int cmd, + int value, int index, void *data, int len); + +/* ----- begin of i2c layer ---------------------------------------------- */ + +#define STATUS_IDLE 0 +#define STATUS_ADDRESS_ACK 1 +#define STATUS_ADDRESS_NAK 2 + +static int usb_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs, int num) +{ + unsigned char status; + struct i2c_msg *pmsg; + int i; + + dev_dbg(&adapter->dev, "master xfer %d messages:\n", num); + + for (i = 0 ; i < num ; i++) { + int cmd = CMD_I2C_IO; + + if (i == 0) + cmd |= CMD_I2C_IO_BEGIN; + + if (i == num-1) + cmd |= CMD_I2C_IO_END; + + pmsg = &msgs[i]; + + dev_dbg(&adapter->dev, + " %d: %s (flags %d) %d bytes to 0x%02x\n", + i, pmsg->flags & I2C_M_RD ? "read" : "write", + pmsg->flags, pmsg->len, pmsg->addr); + + /* and directly send the message */ + if (pmsg->flags & I2C_M_RD) { + /* read data */ + if (usb_read(adapter, cmd, + pmsg->flags, pmsg->addr, + pmsg->buf, pmsg->len) != pmsg->len) { + dev_err(&adapter->dev, + "failure reading data\n"); + return -EREMOTEIO; + } + } else { + /* write data */ + if (usb_write(adapter, cmd, + pmsg->flags, pmsg->addr, + pmsg->buf, pmsg->len) != pmsg->len) { + dev_err(&adapter->dev, + "failure writing data\n"); + return -EREMOTEIO; + } + } + + /* read status */ + if (usb_read(adapter, CMD_GET_STATUS, 0, 0, &status, 1) != 1) { + dev_err(&adapter->dev, "failure reading status\n"); + return -EREMOTEIO; + } + + dev_dbg(&adapter->dev, " status = %d\n", status); + if (status == STATUS_ADDRESS_NAK) + return -EREMOTEIO; + } + + return i; +} + +static u32 usb_func(struct i2c_adapter *adapter) +{ + u32 func; + + /* get functionality from adapter */ + if (usb_read(adapter, CMD_GET_FUNC, 0, 0, &func, sizeof(func)) != + sizeof(func)) { + dev_err(&adapter->dev, "failure reading functionality\n"); + return 0; + } + + return func; +} + +/* This is the actual algorithm we define */ +static const struct i2c_algorithm usb_algorithm = { + .master_xfer = usb_xfer, + .functionality = usb_func, +}; + +/* ----- end of i2c layer ------------------------------------------------ */ + +/* ----- begin of usb layer ---------------------------------------------- */ + +/* + * Initially the usb i2c interface uses a vid/pid pair donated by + * Future Technology Devices International Ltd., later a pair was + * bought from EZPrototypes + */ +static struct usb_device_id i2c_tiny_usb_table [] = { + { USB_DEVICE(0x0403, 0xc631) }, /* FTDI */ + { USB_DEVICE(0x1c40, 0x0534) }, /* EZPrototypes */ + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, i2c_tiny_usb_table); + +/* Structure to hold all of our device specific stuff */ +struct i2c_tiny_usb { + struct usb_device *usb_dev; /* the usb device for this device */ + struct usb_interface *interface; /* the interface for this device */ + struct i2c_adapter adapter; /* i2c related things */ +}; + +static int usb_read(struct i2c_adapter *adapter, int cmd, + int value, int index, void *data, int len) +{ + struct i2c_tiny_usb *dev = (struct i2c_tiny_usb *)adapter->algo_data; + + /* do control transfer */ + return usb_control_msg(dev->usb_dev, usb_rcvctrlpipe(dev->usb_dev, 0), + cmd, USB_TYPE_VENDOR | USB_RECIP_INTERFACE | + USB_DIR_IN, value, index, data, len, 2000); +} + +static int usb_write(struct i2c_adapter *adapter, int cmd, + int value, int index, void *data, int len) +{ + struct i2c_tiny_usb *dev = (struct i2c_tiny_usb *)adapter->algo_data; + + /* do control transfer */ + return usb_control_msg(dev->usb_dev, usb_sndctrlpipe(dev->usb_dev, 0), + cmd, USB_TYPE_VENDOR | USB_RECIP_INTERFACE, + value, index, data, len, 2000); +} + +static void i2c_tiny_usb_free(struct i2c_tiny_usb *dev) +{ + usb_put_dev(dev->usb_dev); + kfree(dev); +} + +static int i2c_tiny_usb_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct i2c_tiny_usb *dev; + int retval = -ENOMEM; + u16 version; + + dev_dbg(&interface->dev, "probing usb device\n"); + + /* allocate memory for our device state and initialize it */ + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (dev == NULL) { + dev_err(&interface->dev, "Out of memory\n"); + goto error; + } + + dev->usb_dev = usb_get_dev(interface_to_usbdev(interface)); + dev->interface = interface; + + /* save our data pointer in this interface device */ + usb_set_intfdata(interface, dev); + + version = le16_to_cpu(dev->usb_dev->descriptor.bcdDevice); + dev_info(&interface->dev, + "version %x.%02x found at bus %03d address %03d\n", + version >> 8, version & 0xff, + dev->usb_dev->bus->busnum, dev->usb_dev->devnum); + + /* setup i2c adapter description */ + dev->adapter.owner = THIS_MODULE; + dev->adapter.class = I2C_CLASS_HWMON; + dev->adapter.algo = &usb_algorithm; + dev->adapter.algo_data = dev; + snprintf(dev->adapter.name, sizeof(dev->adapter.name), + "i2c-tiny-usb at bus %03d device %03d", + dev->usb_dev->bus->busnum, dev->usb_dev->devnum); + + if (usb_write(&dev->adapter, CMD_SET_DELAY, + cpu_to_le16(delay), 0, NULL, 0) != 0) { + dev_err(&dev->adapter.dev, + "failure setting delay to %dus\n", delay); + retval = -EIO; + goto error; + } + + dev->adapter.dev.parent = &dev->interface->dev; + + /* and finally attach to i2c layer */ + i2c_add_adapter(&dev->adapter); + + /* inform user about successful attachment to i2c layer */ + dev_info(&dev->adapter.dev, "connected i2c-tiny-usb device\n"); + + return 0; + + error: + if (dev) + i2c_tiny_usb_free(dev); + + return retval; +} + +static void i2c_tiny_usb_disconnect(struct usb_interface *interface) +{ + struct i2c_tiny_usb *dev = usb_get_intfdata(interface); + + i2c_del_adapter(&dev->adapter); + usb_set_intfdata(interface, NULL); + i2c_tiny_usb_free(dev); + + dev_dbg(&interface->dev, "disconnected\n"); +} + +static struct usb_driver i2c_tiny_usb_driver = { + .name = "i2c-tiny-usb", + .probe = i2c_tiny_usb_probe, + .disconnect = i2c_tiny_usb_disconnect, + .id_table = i2c_tiny_usb_table, +}; + +static int __init usb_i2c_tiny_usb_init(void) +{ + /* register this driver with the USB subsystem */ + return usb_register(&i2c_tiny_usb_driver); +} + +static void __exit usb_i2c_tiny_usb_exit(void) +{ + /* deregister this driver with the USB subsystem */ + usb_deregister(&i2c_tiny_usb_driver); +} + +module_init(usb_i2c_tiny_usb_init); +module_exit(usb_i2c_tiny_usb_exit); + +/* ----- end of usb layer ------------------------------------------------ */ + +MODULE_AUTHOR("Till Harbaum <Till@Harbaum.org>"); +MODULE_DESCRIPTION("i2c-tiny-usb driver v1.0"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/busses/i2c-versatile.c b/drivers/i2c/busses/i2c-versatile.c new file mode 100644 index 0000000..4678bab --- /dev/null +++ b/drivers/i2c/busses/i2c-versatile.c @@ -0,0 +1,154 @@ +/* + * i2c-versatile.c + * + * Copyright (C) 2006 ARM Ltd. + * written by Russell King, Deep Blue Solutions Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#include <linux/init.h> +#include <linux/platform_device.h> + +#include <asm/io.h> + +#define I2C_CONTROL 0x00 +#define I2C_CONTROLS 0x00 +#define I2C_CONTROLC 0x04 +#define SCL (1 << 0) +#define SDA (1 << 1) + +struct i2c_versatile { + struct i2c_adapter adap; + struct i2c_algo_bit_data algo; + void __iomem *base; +}; + +static void i2c_versatile_setsda(void *data, int state) +{ + struct i2c_versatile *i2c = data; + + writel(SDA, i2c->base + (state ? I2C_CONTROLS : I2C_CONTROLC)); +} + +static void i2c_versatile_setscl(void *data, int state) +{ + struct i2c_versatile *i2c = data; + + writel(SCL, i2c->base + (state ? I2C_CONTROLS : I2C_CONTROLC)); +} + +static int i2c_versatile_getsda(void *data) +{ + struct i2c_versatile *i2c = data; + return !!(readl(i2c->base + I2C_CONTROL) & SDA); +} + +static int i2c_versatile_getscl(void *data) +{ + struct i2c_versatile *i2c = data; + return !!(readl(i2c->base + I2C_CONTROL) & SCL); +} + +static struct i2c_algo_bit_data i2c_versatile_algo = { + .setsda = i2c_versatile_setsda, + .setscl = i2c_versatile_setscl, + .getsda = i2c_versatile_getsda, + .getscl = i2c_versatile_getscl, + .udelay = 30, + .timeout = HZ, +}; + +static int i2c_versatile_probe(struct platform_device *dev) +{ + struct i2c_versatile *i2c; + struct resource *r; + int ret; + + r = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (!r) { + ret = -EINVAL; + goto err_out; + } + + if (!request_mem_region(r->start, r->end - r->start + 1, "versatile-i2c")) { + ret = -EBUSY; + goto err_out; + } + + i2c = kzalloc(sizeof(struct i2c_versatile), GFP_KERNEL); + if (!i2c) { + ret = -ENOMEM; + goto err_release; + } + + i2c->base = ioremap(r->start, r->end - r->start + 1); + if (!i2c->base) { + ret = -ENOMEM; + goto err_free; + } + + writel(SCL | SDA, i2c->base + I2C_CONTROLS); + + i2c->adap.owner = THIS_MODULE; + strlcpy(i2c->adap.name, "Versatile I2C adapter", sizeof(i2c->adap.name)); + i2c->adap.algo_data = &i2c->algo; + i2c->adap.dev.parent = &dev->dev; + i2c->algo = i2c_versatile_algo; + i2c->algo.data = i2c; + + ret = i2c_bit_add_bus(&i2c->adap); + if (ret >= 0) { + platform_set_drvdata(dev, i2c); + return 0; + } + + iounmap(i2c->base); + err_free: + kfree(i2c); + err_release: + release_mem_region(r->start, r->end - r->start + 1); + err_out: + return ret; +} + +static int i2c_versatile_remove(struct platform_device *dev) +{ + struct i2c_versatile *i2c = platform_get_drvdata(dev); + + platform_set_drvdata(dev, NULL); + + i2c_del_adapter(&i2c->adap); + return 0; +} + +static struct platform_driver i2c_versatile_driver = { + .probe = i2c_versatile_probe, + .remove = i2c_versatile_remove, + .driver = { + .name = "versatile-i2c", + .owner = THIS_MODULE, + }, +}; + +static int __init i2c_versatile_init(void) +{ + return platform_driver_register(&i2c_versatile_driver); +} + +static void __exit i2c_versatile_exit(void) +{ + platform_driver_unregister(&i2c_versatile_driver); +} + +module_init(i2c_versatile_init); +module_exit(i2c_versatile_exit); + +MODULE_DESCRIPTION("ARM Versatile I2C bus driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:versatile-i2c"); diff --git a/drivers/i2c/busses/i2c-via.c b/drivers/i2c/busses/i2c-via.c new file mode 100644 index 0000000..29cef04 --- /dev/null +++ b/drivers/i2c/busses/i2c-via.c @@ -0,0 +1,181 @@ +/* + i2c Support for Via Technologies 82C586B South Bridge + + Copyright (c) 1998, 1999 Kyösti Mälkki <kmalkki@cc.hut.fi> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#include <asm/io.h> + +/* Power management registers */ +#define PM_CFG_REVID 0x08 /* silicon revision code */ +#define PM_CFG_IOBASE0 0x20 +#define PM_CFG_IOBASE1 0x48 + +#define I2C_DIR (pm_io_base+0x40) +#define I2C_OUT (pm_io_base+0x42) +#define I2C_IN (pm_io_base+0x44) +#define I2C_SCL 0x02 /* clock bit in DIR/OUT/IN register */ +#define I2C_SDA 0x04 + +/* io-region reservation */ +#define IOSPACE 0x06 + +static struct pci_driver vt586b_driver; +static u16 pm_io_base; + +/* + It does not appear from the datasheet that the GPIO pins are + open drain. So a we set a low value by setting the direction to + output and a high value by setting the direction to input and + relying on the required I2C pullup. The data value is initialized + to 0 in via_init() and never changed. +*/ +static void bit_via_setscl(void *data, int state) +{ + outb(state ? inb(I2C_DIR) & ~I2C_SCL : inb(I2C_DIR) | I2C_SCL, I2C_DIR); +} + +static void bit_via_setsda(void *data, int state) +{ + outb(state ? inb(I2C_DIR) & ~I2C_SDA : inb(I2C_DIR) | I2C_SDA, I2C_DIR); +} + +static int bit_via_getscl(void *data) +{ + return (0 != (inb(I2C_IN) & I2C_SCL)); +} + +static int bit_via_getsda(void *data) +{ + return (0 != (inb(I2C_IN) & I2C_SDA)); +} + + +static struct i2c_algo_bit_data bit_data = { + .setsda = bit_via_setsda, + .setscl = bit_via_setscl, + .getsda = bit_via_getsda, + .getscl = bit_via_getscl, + .udelay = 5, + .timeout = HZ +}; + +static struct i2c_adapter vt586b_adapter = { + .owner = THIS_MODULE, + .id = I2C_HW_B_VIA, + .class = I2C_CLASS_HWMON | I2C_CLASS_SPD, + .name = "VIA i2c", + .algo_data = &bit_data, +}; + + +static struct pci_device_id vt586b_ids[] __devinitdata = { + { PCI_DEVICE(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_82C586_3) }, + { 0, } +}; + +MODULE_DEVICE_TABLE (pci, vt586b_ids); + +static int __devinit vt586b_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + u16 base; + u8 rev; + int res; + + if (pm_io_base) { + dev_err(&dev->dev, "i2c-via: Will only support one host\n"); + return -ENODEV; + } + + pci_read_config_byte(dev, PM_CFG_REVID, &rev); + + switch (rev) { + case 0x00: + base = PM_CFG_IOBASE0; + break; + case 0x01: + case 0x10: + base = PM_CFG_IOBASE1; + break; + + default: + base = PM_CFG_IOBASE1; + /* later revision */ + } + + pci_read_config_word(dev, base, &pm_io_base); + pm_io_base &= (0xff << 8); + + if (!request_region(I2C_DIR, IOSPACE, vt586b_driver.name)) { + dev_err(&dev->dev, "IO 0x%x-0x%x already in use\n", I2C_DIR, I2C_DIR + IOSPACE); + return -ENODEV; + } + + outb(inb(I2C_DIR) & ~(I2C_SDA | I2C_SCL), I2C_DIR); + outb(inb(I2C_OUT) & ~(I2C_SDA | I2C_SCL), I2C_OUT); + + /* set up the sysfs linkage to our parent device */ + vt586b_adapter.dev.parent = &dev->dev; + + res = i2c_bit_add_bus(&vt586b_adapter); + if ( res < 0 ) { + release_region(I2C_DIR, IOSPACE); + pm_io_base = 0; + return res; + } + return 0; +} + +static void __devexit vt586b_remove(struct pci_dev *dev) +{ + i2c_del_adapter(&vt586b_adapter); + release_region(I2C_DIR, IOSPACE); + pm_io_base = 0; +} + + +static struct pci_driver vt586b_driver = { + .name = "vt586b_smbus", + .id_table = vt586b_ids, + .probe = vt586b_probe, + .remove = __devexit_p(vt586b_remove), +}; + +static int __init i2c_vt586b_init(void) +{ + return pci_register_driver(&vt586b_driver); +} + +static void __exit i2c_vt586b_exit(void) +{ + pci_unregister_driver(&vt586b_driver); +} + + +MODULE_AUTHOR("Kyösti Mälkki <kmalkki@cc.hut.fi>"); +MODULE_DESCRIPTION("i2c for Via vt82c586b southbridge"); +MODULE_LICENSE("GPL"); + +module_init(i2c_vt586b_init); +module_exit(i2c_vt586b_exit); diff --git a/drivers/i2c/busses/i2c-viapro.c b/drivers/i2c/busses/i2c-viapro.c new file mode 100644 index 0000000..9f194d9 --- /dev/null +++ b/drivers/i2c/busses/i2c-viapro.c @@ -0,0 +1,508 @@ +/* + Copyright (c) 1998 - 2002 Frodo Looijaard <frodol@dds.nl>, + Philip Edelbrock <phil@netroedge.com>, Kyösti Mälkki <kmalkki@cc.hut.fi>, + Mark D. Studebaker <mdsxyz123@yahoo.com> + Copyright (C) 2005 - 2008 Jean Delvare <khali@linux-fr.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* + Supports the following VIA south bridges: + + Chip name PCI ID REV I2C block + VT82C596A 0x3050 no + VT82C596B 0x3051 no + VT82C686A 0x3057 0x30 no + VT82C686B 0x3057 0x40 yes + VT8231 0x8235 no? + VT8233 0x3074 yes + VT8233A 0x3147 yes? + VT8235 0x3177 yes + VT8237R 0x3227 yes + VT8237A 0x3337 yes + VT8237S 0x3372 yes + VT8251 0x3287 yes + CX700 0x8324 yes + VX800/VX820 0x8353 yes + + Note: we assume there can only be one device, with one SMBus interface. +*/ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/pci.h> +#include <linux/kernel.h> +#include <linux/stddef.h> +#include <linux/ioport.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/acpi.h> +#include <asm/io.h> + +static struct pci_dev *vt596_pdev; + +#define SMBBA1 0x90 +#define SMBBA2 0x80 +#define SMBBA3 0xD0 + +/* SMBus address offsets */ +static unsigned short vt596_smba; +#define SMBHSTSTS (vt596_smba + 0) +#define SMBHSTCNT (vt596_smba + 2) +#define SMBHSTCMD (vt596_smba + 3) +#define SMBHSTADD (vt596_smba + 4) +#define SMBHSTDAT0 (vt596_smba + 5) +#define SMBHSTDAT1 (vt596_smba + 6) +#define SMBBLKDAT (vt596_smba + 7) + +/* PCI Address Constants */ + +/* SMBus data in configuration space can be found in two places, + We try to select the better one */ + +static unsigned short SMBHSTCFG = 0xD2; + +/* Other settings */ +#define MAX_TIMEOUT 500 + +/* VT82C596 constants */ +#define VT596_QUICK 0x00 +#define VT596_BYTE 0x04 +#define VT596_BYTE_DATA 0x08 +#define VT596_WORD_DATA 0x0C +#define VT596_PROC_CALL 0x10 +#define VT596_BLOCK_DATA 0x14 +#define VT596_I2C_BLOCK_DATA 0x34 + + +/* If force is set to anything different from 0, we forcibly enable the + VT596. DANGEROUS! */ +static int force; +module_param(force, bool, 0); +MODULE_PARM_DESC(force, "Forcibly enable the SMBus. DANGEROUS!"); + +/* If force_addr is set to anything different from 0, we forcibly enable + the VT596 at the given address. VERY DANGEROUS! */ +static u16 force_addr; +module_param(force_addr, ushort, 0); +MODULE_PARM_DESC(force_addr, + "Forcibly enable the SMBus at the given address. " + "EXTREMELY DANGEROUS!"); + + +static struct pci_driver vt596_driver; +static struct i2c_adapter vt596_adapter; + +#define FEATURE_I2CBLOCK (1<<0) +static unsigned int vt596_features; + +#ifdef DEBUG +static void vt596_dump_regs(const char *msg, u8 size) +{ + dev_dbg(&vt596_adapter.dev, "%s: STS=%02x CNT=%02x CMD=%02x ADD=%02x " + "DAT=%02x,%02x\n", msg, inb_p(SMBHSTSTS), inb_p(SMBHSTCNT), + inb_p(SMBHSTCMD), inb_p(SMBHSTADD), inb_p(SMBHSTDAT0), + inb_p(SMBHSTDAT1)); + + if (size == VT596_BLOCK_DATA + || size == VT596_I2C_BLOCK_DATA) { + int i; + + dev_dbg(&vt596_adapter.dev, "BLK="); + for (i = 0; i < I2C_SMBUS_BLOCK_MAX / 2; i++) + printk("%02x,", inb_p(SMBBLKDAT)); + printk("\n"); + dev_dbg(&vt596_adapter.dev, " "); + for (; i < I2C_SMBUS_BLOCK_MAX - 1; i++) + printk("%02x,", inb_p(SMBBLKDAT)); + printk("%02x\n", inb_p(SMBBLKDAT)); + } +} +#else +static inline void vt596_dump_regs(const char *msg, u8 size) { } +#endif + +/* Return -1 on error, 0 on success */ +static int vt596_transaction(u8 size) +{ + int temp; + int result = 0; + int timeout = 0; + + vt596_dump_regs("Transaction (pre)", size); + + /* Make sure the SMBus host is ready to start transmitting */ + if ((temp = inb_p(SMBHSTSTS)) & 0x1F) { + dev_dbg(&vt596_adapter.dev, "SMBus busy (0x%02x). " + "Resetting...\n", temp); + + outb_p(temp, SMBHSTSTS); + if ((temp = inb_p(SMBHSTSTS)) & 0x1F) { + dev_err(&vt596_adapter.dev, "SMBus reset failed! " + "(0x%02x)\n", temp); + return -EBUSY; + } + } + + /* Start the transaction by setting bit 6 */ + outb_p(0x40 | size, SMBHSTCNT); + + /* We will always wait for a fraction of a second */ + do { + msleep(1); + temp = inb_p(SMBHSTSTS); + } while ((temp & 0x01) && (timeout++ < MAX_TIMEOUT)); + + /* If the SMBus is still busy, we give up */ + if (timeout >= MAX_TIMEOUT) { + result = -ETIMEDOUT; + dev_err(&vt596_adapter.dev, "SMBus timeout!\n"); + } + + if (temp & 0x10) { + result = -EIO; + dev_err(&vt596_adapter.dev, "Transaction failed (0x%02x)\n", + size); + } + + if (temp & 0x08) { + result = -EIO; + dev_err(&vt596_adapter.dev, "SMBus collision!\n"); + } + + if (temp & 0x04) { + int read = inb_p(SMBHSTADD) & 0x01; + result = -ENXIO; + /* The quick and receive byte commands are used to probe + for chips, so errors are expected, and we don't want + to frighten the user. */ + if (!((size == VT596_QUICK && !read) || + (size == VT596_BYTE && read))) + dev_err(&vt596_adapter.dev, "Transaction error!\n"); + } + + /* Resetting status register */ + if (temp & 0x1F) + outb_p(temp, SMBHSTSTS); + + vt596_dump_regs("Transaction (post)", size); + + return result; +} + +/* Return negative errno on error, 0 on success */ +static s32 vt596_access(struct i2c_adapter *adap, u16 addr, + unsigned short flags, char read_write, u8 command, + int size, union i2c_smbus_data *data) +{ + int i; + int status; + + switch (size) { + case I2C_SMBUS_QUICK: + size = VT596_QUICK; + break; + case I2C_SMBUS_BYTE: + if (read_write == I2C_SMBUS_WRITE) + outb_p(command, SMBHSTCMD); + size = VT596_BYTE; + break; + case I2C_SMBUS_BYTE_DATA: + outb_p(command, SMBHSTCMD); + if (read_write == I2C_SMBUS_WRITE) + outb_p(data->byte, SMBHSTDAT0); + size = VT596_BYTE_DATA; + break; + case I2C_SMBUS_WORD_DATA: + outb_p(command, SMBHSTCMD); + if (read_write == I2C_SMBUS_WRITE) { + outb_p(data->word & 0xff, SMBHSTDAT0); + outb_p((data->word & 0xff00) >> 8, SMBHSTDAT1); + } + size = VT596_WORD_DATA; + break; + case I2C_SMBUS_PROC_CALL: + outb_p(command, SMBHSTCMD); + outb_p(data->word & 0xff, SMBHSTDAT0); + outb_p((data->word & 0xff00) >> 8, SMBHSTDAT1); + size = VT596_PROC_CALL; + break; + case I2C_SMBUS_I2C_BLOCK_DATA: + if (!(vt596_features & FEATURE_I2CBLOCK)) + goto exit_unsupported; + if (read_write == I2C_SMBUS_READ) + outb_p(data->block[0], SMBHSTDAT0); + /* Fall through */ + case I2C_SMBUS_BLOCK_DATA: + outb_p(command, SMBHSTCMD); + if (read_write == I2C_SMBUS_WRITE) { + u8 len = data->block[0]; + if (len > I2C_SMBUS_BLOCK_MAX) + len = I2C_SMBUS_BLOCK_MAX; + outb_p(len, SMBHSTDAT0); + inb_p(SMBHSTCNT); /* Reset SMBBLKDAT */ + for (i = 1; i <= len; i++) + outb_p(data->block[i], SMBBLKDAT); + } + size = (size == I2C_SMBUS_I2C_BLOCK_DATA) ? + VT596_I2C_BLOCK_DATA : VT596_BLOCK_DATA; + break; + default: + goto exit_unsupported; + } + + outb_p(((addr & 0x7f) << 1) | read_write, SMBHSTADD); + + status = vt596_transaction(size); + if (status) + return status; + + if (size == VT596_PROC_CALL) + read_write = I2C_SMBUS_READ; + + if ((read_write == I2C_SMBUS_WRITE) || (size == VT596_QUICK)) + return 0; + + switch (size) { + case VT596_BYTE: + case VT596_BYTE_DATA: + data->byte = inb_p(SMBHSTDAT0); + break; + case VT596_WORD_DATA: + case VT596_PROC_CALL: + data->word = inb_p(SMBHSTDAT0) + (inb_p(SMBHSTDAT1) << 8); + break; + case VT596_I2C_BLOCK_DATA: + case VT596_BLOCK_DATA: + data->block[0] = inb_p(SMBHSTDAT0); + if (data->block[0] > I2C_SMBUS_BLOCK_MAX) + data->block[0] = I2C_SMBUS_BLOCK_MAX; + inb_p(SMBHSTCNT); /* Reset SMBBLKDAT */ + for (i = 1; i <= data->block[0]; i++) + data->block[i] = inb_p(SMBBLKDAT); + break; + } + return 0; + +exit_unsupported: + dev_warn(&vt596_adapter.dev, "Unsupported transaction %d\n", + size); + return -EOPNOTSUPP; +} + +static u32 vt596_func(struct i2c_adapter *adapter) +{ + u32 func = I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | + I2C_SMBUS_PROC_CALL | I2C_FUNC_SMBUS_BLOCK_DATA; + + if (vt596_features & FEATURE_I2CBLOCK) + func |= I2C_FUNC_SMBUS_I2C_BLOCK; + return func; +} + +static const struct i2c_algorithm smbus_algorithm = { + .smbus_xfer = vt596_access, + .functionality = vt596_func, +}; + +static struct i2c_adapter vt596_adapter = { + .owner = THIS_MODULE, + .id = I2C_HW_SMBUS_VIA2, + .class = I2C_CLASS_HWMON | I2C_CLASS_SPD, + .algo = &smbus_algorithm, +}; + +static int __devinit vt596_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + unsigned char temp; + int error = -ENODEV; + + /* Determine the address of the SMBus areas */ + if (force_addr) { + vt596_smba = force_addr & 0xfff0; + force = 0; + goto found; + } + + if ((pci_read_config_word(pdev, id->driver_data, &vt596_smba)) || + !(vt596_smba & 0x0001)) { + /* try 2nd address and config reg. for 596 */ + if (id->device == PCI_DEVICE_ID_VIA_82C596_3 && + !pci_read_config_word(pdev, SMBBA2, &vt596_smba) && + (vt596_smba & 0x0001)) { + SMBHSTCFG = 0x84; + } else { + /* no matches at all */ + dev_err(&pdev->dev, "Cannot configure " + "SMBus I/O Base address\n"); + return -ENODEV; + } + } + + vt596_smba &= 0xfff0; + if (vt596_smba == 0) { + dev_err(&pdev->dev, "SMBus base address " + "uninitialized - upgrade BIOS or use " + "force_addr=0xaddr\n"); + return -ENODEV; + } + +found: + error = acpi_check_region(vt596_smba, 8, vt596_driver.name); + if (error) + return error; + + if (!request_region(vt596_smba, 8, vt596_driver.name)) { + dev_err(&pdev->dev, "SMBus region 0x%x already in use!\n", + vt596_smba); + return -ENODEV; + } + + pci_read_config_byte(pdev, SMBHSTCFG, &temp); + /* If force_addr is set, we program the new address here. Just to make + sure, we disable the VT596 first. */ + if (force_addr) { + pci_write_config_byte(pdev, SMBHSTCFG, temp & 0xfe); + pci_write_config_word(pdev, id->driver_data, vt596_smba); + pci_write_config_byte(pdev, SMBHSTCFG, temp | 0x01); + dev_warn(&pdev->dev, "WARNING: SMBus interface set to new " + "address 0x%04x!\n", vt596_smba); + } else if (!(temp & 0x01)) { + if (force) { + /* NOTE: This assumes I/O space and other allocations + * WERE done by the Bios! Don't complain if your + * hardware does weird things after enabling this. + * :') Check for Bios updates before resorting to + * this. + */ + pci_write_config_byte(pdev, SMBHSTCFG, temp | 0x01); + dev_info(&pdev->dev, "Enabling SMBus device\n"); + } else { + dev_err(&pdev->dev, "SMBUS: Error: Host SMBus " + "controller not enabled! - upgrade BIOS or " + "use force=1\n"); + goto release_region; + } + } + + dev_dbg(&pdev->dev, "VT596_smba = 0x%X\n", vt596_smba); + + switch (pdev->device) { + case PCI_DEVICE_ID_VIA_CX700: + case PCI_DEVICE_ID_VIA_VX800: + case PCI_DEVICE_ID_VIA_8251: + case PCI_DEVICE_ID_VIA_8237: + case PCI_DEVICE_ID_VIA_8237A: + case PCI_DEVICE_ID_VIA_8237S: + case PCI_DEVICE_ID_VIA_8235: + case PCI_DEVICE_ID_VIA_8233A: + case PCI_DEVICE_ID_VIA_8233_0: + vt596_features |= FEATURE_I2CBLOCK; + break; + case PCI_DEVICE_ID_VIA_82C686_4: + /* The VT82C686B (rev 0x40) does support I2C block + transactions, but the VT82C686A (rev 0x30) doesn't */ + if (pdev->revision >= 0x40) + vt596_features |= FEATURE_I2CBLOCK; + break; + } + + vt596_adapter.dev.parent = &pdev->dev; + snprintf(vt596_adapter.name, sizeof(vt596_adapter.name), + "SMBus Via Pro adapter at %04x", vt596_smba); + + vt596_pdev = pci_dev_get(pdev); + if (i2c_add_adapter(&vt596_adapter)) { + pci_dev_put(vt596_pdev); + vt596_pdev = NULL; + } + + /* Always return failure here. This is to allow other drivers to bind + * to this pci device. We don't really want to have control over the + * pci device, we only wanted to read as few register values from it. + */ + return -ENODEV; + +release_region: + release_region(vt596_smba, 8); + return error; +} + +static struct pci_device_id vt596_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_82C596_3), + .driver_data = SMBBA1 }, + { PCI_DEVICE(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_82C596B_3), + .driver_data = SMBBA1 }, + { PCI_DEVICE(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_82C686_4), + .driver_data = SMBBA1 }, + { PCI_DEVICE(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8233_0), + .driver_data = SMBBA3 }, + { PCI_DEVICE(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8233A), + .driver_data = SMBBA3 }, + { PCI_DEVICE(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8235), + .driver_data = SMBBA3 }, + { PCI_DEVICE(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8237), + .driver_data = SMBBA3 }, + { PCI_DEVICE(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8237A), + .driver_data = SMBBA3 }, + { PCI_DEVICE(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8237S), + .driver_data = SMBBA3 }, + { PCI_DEVICE(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8231_4), + .driver_data = SMBBA1 }, + { PCI_DEVICE(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8251), + .driver_data = SMBBA3 }, + { PCI_DEVICE(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_CX700), + .driver_data = SMBBA3 }, + { PCI_DEVICE(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_VX800), + .driver_data = SMBBA3 }, + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, vt596_ids); + +static struct pci_driver vt596_driver = { + .name = "vt596_smbus", + .id_table = vt596_ids, + .probe = vt596_probe, +}; + +static int __init i2c_vt596_init(void) +{ + return pci_register_driver(&vt596_driver); +} + + +static void __exit i2c_vt596_exit(void) +{ + pci_unregister_driver(&vt596_driver); + if (vt596_pdev != NULL) { + i2c_del_adapter(&vt596_adapter); + release_region(vt596_smba, 8); + pci_dev_put(vt596_pdev); + vt596_pdev = NULL; + } +} + +MODULE_AUTHOR("Kyosti Malkki <kmalkki@cc.hut.fi>, " + "Mark D. Studebaker <mdsxyz123@yahoo.com> and " + "Jean Delvare <khali@linux-fr.org>"); +MODULE_DESCRIPTION("vt82c596 SMBus driver"); +MODULE_LICENSE("GPL"); + +module_init(i2c_vt596_init); +module_exit(i2c_vt596_exit); diff --git a/drivers/i2c/busses/i2c-voodoo3.c b/drivers/i2c/busses/i2c-voodoo3.c new file mode 100644 index 0000000..1d4ae26 --- /dev/null +++ b/drivers/i2c/busses/i2c-voodoo3.c @@ -0,0 +1,251 @@ +/* + Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl>, + Philip Edelbrock <phil@netroedge.com>, + Ralph Metzler <rjkm@thp.uni-koeln.de>, and + Mark D. Studebaker <mdsxyz123@yahoo.com> + + Based on code written by Ralph Metzler <rjkm@thp.uni-koeln.de> and + Simon Vogl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* This interfaces to the I2C bus of the Voodoo3 to gain access to + the BT869 and possibly other I2C devices. */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#include <asm/io.h> + +/* the only registers we use */ +#define REG 0x78 +#define REG2 0x70 + +/* bit locations in the register */ +#define DDC_ENAB 0x00040000 +#define DDC_SCL_OUT 0x00080000 +#define DDC_SDA_OUT 0x00100000 +#define DDC_SCL_IN 0x00200000 +#define DDC_SDA_IN 0x00400000 +#define I2C_ENAB 0x00800000 +#define I2C_SCL_OUT 0x01000000 +#define I2C_SDA_OUT 0x02000000 +#define I2C_SCL_IN 0x04000000 +#define I2C_SDA_IN 0x08000000 + +/* initialization states */ +#define INIT2 0x2 +#define INIT3 0x4 + +/* delays */ +#define CYCLE_DELAY 10 +#define TIMEOUT (HZ / 2) + + +static void __iomem *ioaddr; + +/* The voo GPIO registers don't have individual masks for each bit + so we always have to read before writing. */ + +static void bit_vooi2c_setscl(void *data, int val) +{ + unsigned int r; + r = readl(ioaddr + REG); + if (val) + r |= I2C_SCL_OUT; + else + r &= ~I2C_SCL_OUT; + writel(r, ioaddr + REG); + readl(ioaddr + REG); /* flush posted write */ +} + +static void bit_vooi2c_setsda(void *data, int val) +{ + unsigned int r; + r = readl(ioaddr + REG); + if (val) + r |= I2C_SDA_OUT; + else + r &= ~I2C_SDA_OUT; + writel(r, ioaddr + REG); + readl(ioaddr + REG); /* flush posted write */ +} + +/* The GPIO pins are open drain, so the pins always remain outputs. + We rely on the i2c-algo-bit routines to set the pins high before + reading the input from other chips. */ + +static int bit_vooi2c_getscl(void *data) +{ + return (0 != (readl(ioaddr + REG) & I2C_SCL_IN)); +} + +static int bit_vooi2c_getsda(void *data) +{ + return (0 != (readl(ioaddr + REG) & I2C_SDA_IN)); +} + +static void bit_vooddc_setscl(void *data, int val) +{ + unsigned int r; + r = readl(ioaddr + REG); + if (val) + r |= DDC_SCL_OUT; + else + r &= ~DDC_SCL_OUT; + writel(r, ioaddr + REG); + readl(ioaddr + REG); /* flush posted write */ +} + +static void bit_vooddc_setsda(void *data, int val) +{ + unsigned int r; + r = readl(ioaddr + REG); + if (val) + r |= DDC_SDA_OUT; + else + r &= ~DDC_SDA_OUT; + writel(r, ioaddr + REG); + readl(ioaddr + REG); /* flush posted write */ +} + +static int bit_vooddc_getscl(void *data) +{ + return (0 != (readl(ioaddr + REG) & DDC_SCL_IN)); +} + +static int bit_vooddc_getsda(void *data) +{ + return (0 != (readl(ioaddr + REG) & DDC_SDA_IN)); +} + +static int config_v3(struct pci_dev *dev) +{ + unsigned long cadr; + + /* map Voodoo3 memory */ + cadr = dev->resource[0].start; + cadr &= PCI_BASE_ADDRESS_MEM_MASK; + ioaddr = ioremap_nocache(cadr, 0x1000); + if (ioaddr) { + writel(0x8160, ioaddr + REG2); + writel(0xcffc0020, ioaddr + REG); + dev_info(&dev->dev, "Using Banshee/Voodoo3 I2C device at %p\n", ioaddr); + return 0; + } + return -ENODEV; +} + +static struct i2c_algo_bit_data voo_i2c_bit_data = { + .setsda = bit_vooi2c_setsda, + .setscl = bit_vooi2c_setscl, + .getsda = bit_vooi2c_getsda, + .getscl = bit_vooi2c_getscl, + .udelay = CYCLE_DELAY, + .timeout = TIMEOUT +}; + +static struct i2c_adapter voodoo3_i2c_adapter = { + .owner = THIS_MODULE, + .id = I2C_HW_B_VOO, + .class = I2C_CLASS_TV_ANALOG, + .name = "I2C Voodoo3/Banshee adapter", + .algo_data = &voo_i2c_bit_data, +}; + +static struct i2c_algo_bit_data voo_ddc_bit_data = { + .setsda = bit_vooddc_setsda, + .setscl = bit_vooddc_setscl, + .getsda = bit_vooddc_getsda, + .getscl = bit_vooddc_getscl, + .udelay = CYCLE_DELAY, + .timeout = TIMEOUT +}; + +static struct i2c_adapter voodoo3_ddc_adapter = { + .owner = THIS_MODULE, + .id = I2C_HW_B_VOO, + .class = I2C_CLASS_DDC, + .name = "DDC Voodoo3/Banshee adapter", + .algo_data = &voo_ddc_bit_data, +}; + +static struct pci_device_id voodoo3_ids[] __devinitdata = { + { PCI_DEVICE(PCI_VENDOR_ID_3DFX, PCI_DEVICE_ID_3DFX_VOODOO3) }, + { PCI_DEVICE(PCI_VENDOR_ID_3DFX, PCI_DEVICE_ID_3DFX_BANSHEE) }, + { 0, } +}; + +MODULE_DEVICE_TABLE (pci, voodoo3_ids); + +static int __devinit voodoo3_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + int retval; + + retval = config_v3(dev); + if (retval) + return retval; + + /* set up the sysfs linkage to our parent device */ + voodoo3_i2c_adapter.dev.parent = &dev->dev; + voodoo3_ddc_adapter.dev.parent = &dev->dev; + + retval = i2c_bit_add_bus(&voodoo3_i2c_adapter); + if (retval) + return retval; + retval = i2c_bit_add_bus(&voodoo3_ddc_adapter); + if (retval) + i2c_del_adapter(&voodoo3_i2c_adapter); + return retval; +} + +static void __devexit voodoo3_remove(struct pci_dev *dev) +{ + i2c_del_adapter(&voodoo3_i2c_adapter); + i2c_del_adapter(&voodoo3_ddc_adapter); + iounmap(ioaddr); +} + +static struct pci_driver voodoo3_driver = { + .name = "voodoo3_smbus", + .id_table = voodoo3_ids, + .probe = voodoo3_probe, + .remove = __devexit_p(voodoo3_remove), +}; + +static int __init i2c_voodoo3_init(void) +{ + return pci_register_driver(&voodoo3_driver); +} + +static void __exit i2c_voodoo3_exit(void) +{ + pci_unregister_driver(&voodoo3_driver); +} + + +MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl>, " + "Philip Edelbrock <phil@netroedge.com>, " + "Ralph Metzler <rjkm@thp.uni-koeln.de>, " + "and Mark D. Studebaker <mdsxyz123@yahoo.com>"); +MODULE_DESCRIPTION("Voodoo3 I2C/SMBus driver"); +MODULE_LICENSE("GPL"); + +module_init(i2c_voodoo3_init); +module_exit(i2c_voodoo3_exit); diff --git a/drivers/i2c/busses/scx200_acb.c b/drivers/i2c/busses/scx200_acb.c new file mode 100644 index 0000000..ed794b1 --- /dev/null +++ b/drivers/i2c/busses/scx200_acb.c @@ -0,0 +1,655 @@ +/* + Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com> + + National Semiconductor SCx200 ACCESS.bus support + Also supports the AMD CS5535 and AMD CS5536 + + Based on i2c-keywest.c which is: + Copyright (c) 2001 Benjamin Herrenschmidt <benh@kernel.crashing.org> + Copyright (c) 2000 Philip Edelbrock <phil@stimpy.netroedge.com> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/pci.h> +#include <linux/delay.h> +#include <linux/mutex.h> +#include <asm/io.h> + +#include <linux/scx200.h> + +#define NAME "scx200_acb" + +MODULE_AUTHOR("Christer Weinigel <wingel@nano-system.com>"); +MODULE_DESCRIPTION("NatSemi SCx200 ACCESS.bus Driver"); +MODULE_LICENSE("GPL"); + +#define MAX_DEVICES 4 +static int base[MAX_DEVICES] = { 0x820, 0x840 }; +module_param_array(base, int, NULL, 0); +MODULE_PARM_DESC(base, "Base addresses for the ACCESS.bus controllers"); + +#define POLL_TIMEOUT (HZ/5) + +enum scx200_acb_state { + state_idle, + state_address, + state_command, + state_repeat_start, + state_quick, + state_read, + state_write, +}; + +static const char *scx200_acb_state_name[] = { + "idle", + "address", + "command", + "repeat_start", + "quick", + "read", + "write", +}; + +/* Physical interface */ +struct scx200_acb_iface { + struct scx200_acb_iface *next; + struct i2c_adapter adapter; + unsigned base; + struct mutex mutex; + + /* State machine data */ + enum scx200_acb_state state; + int result; + u8 address_byte; + u8 command; + u8 *ptr; + char needs_reset; + unsigned len; + + /* PCI device info */ + struct pci_dev *pdev; + int bar; +}; + +/* Register Definitions */ +#define ACBSDA (iface->base + 0) +#define ACBST (iface->base + 1) +#define ACBST_SDAST 0x40 /* SDA Status */ +#define ACBST_BER 0x20 +#define ACBST_NEGACK 0x10 /* Negative Acknowledge */ +#define ACBST_STASTR 0x08 /* Stall After Start */ +#define ACBST_MASTER 0x02 +#define ACBCST (iface->base + 2) +#define ACBCST_BB 0x02 +#define ACBCTL1 (iface->base + 3) +#define ACBCTL1_STASTRE 0x80 +#define ACBCTL1_NMINTE 0x40 +#define ACBCTL1_ACK 0x10 +#define ACBCTL1_STOP 0x02 +#define ACBCTL1_START 0x01 +#define ACBADDR (iface->base + 4) +#define ACBCTL2 (iface->base + 5) +#define ACBCTL2_ENABLE 0x01 + +/************************************************************************/ + +static void scx200_acb_machine(struct scx200_acb_iface *iface, u8 status) +{ + const char *errmsg; + + dev_dbg(&iface->adapter.dev, "state %s, status = 0x%02x\n", + scx200_acb_state_name[iface->state], status); + + if (status & ACBST_BER) { + errmsg = "bus error"; + goto error; + } + if (!(status & ACBST_MASTER)) { + errmsg = "not master"; + goto error; + } + if (status & ACBST_NEGACK) { + dev_dbg(&iface->adapter.dev, "negative ack in state %s\n", + scx200_acb_state_name[iface->state]); + + iface->state = state_idle; + iface->result = -ENXIO; + + outb(inb(ACBCTL1) | ACBCTL1_STOP, ACBCTL1); + outb(ACBST_STASTR | ACBST_NEGACK, ACBST); + + /* Reset the status register */ + outb(0, ACBST); + return; + } + + switch (iface->state) { + case state_idle: + dev_warn(&iface->adapter.dev, "interrupt in idle state\n"); + break; + + case state_address: + /* Do a pointer write first */ + outb(iface->address_byte & ~1, ACBSDA); + + iface->state = state_command; + break; + + case state_command: + outb(iface->command, ACBSDA); + + if (iface->address_byte & 1) + iface->state = state_repeat_start; + else + iface->state = state_write; + break; + + case state_repeat_start: + outb(inb(ACBCTL1) | ACBCTL1_START, ACBCTL1); + /* fallthrough */ + + case state_quick: + if (iface->address_byte & 1) { + if (iface->len == 1) + outb(inb(ACBCTL1) | ACBCTL1_ACK, ACBCTL1); + else + outb(inb(ACBCTL1) & ~ACBCTL1_ACK, ACBCTL1); + outb(iface->address_byte, ACBSDA); + + iface->state = state_read; + } else { + outb(iface->address_byte, ACBSDA); + + iface->state = state_write; + } + break; + + case state_read: + /* Set ACK if _next_ byte will be the last one */ + if (iface->len == 2) + outb(inb(ACBCTL1) | ACBCTL1_ACK, ACBCTL1); + else + outb(inb(ACBCTL1) & ~ACBCTL1_ACK, ACBCTL1); + + if (iface->len == 1) { + iface->result = 0; + iface->state = state_idle; + outb(inb(ACBCTL1) | ACBCTL1_STOP, ACBCTL1); + } + + *iface->ptr++ = inb(ACBSDA); + --iface->len; + + break; + + case state_write: + if (iface->len == 0) { + iface->result = 0; + iface->state = state_idle; + outb(inb(ACBCTL1) | ACBCTL1_STOP, ACBCTL1); + break; + } + + outb(*iface->ptr++, ACBSDA); + --iface->len; + + break; + } + + return; + + error: + dev_err(&iface->adapter.dev, "%s in state %s\n", errmsg, + scx200_acb_state_name[iface->state]); + + iface->state = state_idle; + iface->result = -EIO; + iface->needs_reset = 1; +} + +static void scx200_acb_poll(struct scx200_acb_iface *iface) +{ + u8 status; + unsigned long timeout; + + timeout = jiffies + POLL_TIMEOUT; + while (1) { + status = inb(ACBST); + + /* Reset the status register to avoid the hang */ + outb(0, ACBST); + + if ((status & (ACBST_SDAST|ACBST_BER|ACBST_NEGACK)) != 0) { + scx200_acb_machine(iface, status); + return; + } + if (time_after(jiffies, timeout)) + break; + cpu_relax(); + cond_resched(); + } + + dev_err(&iface->adapter.dev, "timeout in state %s\n", + scx200_acb_state_name[iface->state]); + + iface->state = state_idle; + iface->result = -EIO; + iface->needs_reset = 1; +} + +static void scx200_acb_reset(struct scx200_acb_iface *iface) +{ + /* Disable the ACCESS.bus device and Configure the SCL + frequency: 16 clock cycles */ + outb(0x70, ACBCTL2); + /* Polling mode */ + outb(0, ACBCTL1); + /* Disable slave address */ + outb(0, ACBADDR); + /* Enable the ACCESS.bus device */ + outb(inb(ACBCTL2) | ACBCTL2_ENABLE, ACBCTL2); + /* Free STALL after START */ + outb(inb(ACBCTL1) & ~(ACBCTL1_STASTRE | ACBCTL1_NMINTE), ACBCTL1); + /* Send a STOP */ + outb(inb(ACBCTL1) | ACBCTL1_STOP, ACBCTL1); + /* Clear BER, NEGACK and STASTR bits */ + outb(ACBST_BER | ACBST_NEGACK | ACBST_STASTR, ACBST); + /* Clear BB bit */ + outb(inb(ACBCST) | ACBCST_BB, ACBCST); +} + +static s32 scx200_acb_smbus_xfer(struct i2c_adapter *adapter, + u16 address, unsigned short flags, + char rw, u8 command, int size, + union i2c_smbus_data *data) +{ + struct scx200_acb_iface *iface = i2c_get_adapdata(adapter); + int len; + u8 *buffer; + u16 cur_word; + int rc; + + switch (size) { + case I2C_SMBUS_QUICK: + len = 0; + buffer = NULL; + break; + + case I2C_SMBUS_BYTE: + len = 1; + buffer = rw ? &data->byte : &command; + break; + + case I2C_SMBUS_BYTE_DATA: + len = 1; + buffer = &data->byte; + break; + + case I2C_SMBUS_WORD_DATA: + len = 2; + cur_word = cpu_to_le16(data->word); + buffer = (u8 *)&cur_word; + break; + + case I2C_SMBUS_I2C_BLOCK_DATA: + len = data->block[0]; + if (len == 0 || len > I2C_SMBUS_BLOCK_MAX) + return -EINVAL; + buffer = &data->block[1]; + break; + + default: + return -EINVAL; + } + + dev_dbg(&adapter->dev, + "size=%d, address=0x%x, command=0x%x, len=%d, read=%d\n", + size, address, command, len, rw); + + if (!len && rw == I2C_SMBUS_READ) { + dev_dbg(&adapter->dev, "zero length read\n"); + return -EINVAL; + } + + mutex_lock(&iface->mutex); + + iface->address_byte = (address << 1) | rw; + iface->command = command; + iface->ptr = buffer; + iface->len = len; + iface->result = -EINVAL; + iface->needs_reset = 0; + + outb(inb(ACBCTL1) | ACBCTL1_START, ACBCTL1); + + if (size == I2C_SMBUS_QUICK || size == I2C_SMBUS_BYTE) + iface->state = state_quick; + else + iface->state = state_address; + + while (iface->state != state_idle) + scx200_acb_poll(iface); + + if (iface->needs_reset) + scx200_acb_reset(iface); + + rc = iface->result; + + mutex_unlock(&iface->mutex); + + if (rc == 0 && size == I2C_SMBUS_WORD_DATA && rw == I2C_SMBUS_READ) + data->word = le16_to_cpu(cur_word); + +#ifdef DEBUG + dev_dbg(&adapter->dev, "transfer done, result: %d", rc); + if (buffer) { + int i; + printk(" data:"); + for (i = 0; i < len; ++i) + printk(" %02x", buffer[i]); + } + printk("\n"); +#endif + + return rc; +} + +static u32 scx200_acb_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_I2C_BLOCK; +} + +/* For now, we only handle combined mode (smbus) */ +static const struct i2c_algorithm scx200_acb_algorithm = { + .smbus_xfer = scx200_acb_smbus_xfer, + .functionality = scx200_acb_func, +}; + +static struct scx200_acb_iface *scx200_acb_list; +static DEFINE_MUTEX(scx200_acb_list_mutex); + +static __init int scx200_acb_probe(struct scx200_acb_iface *iface) +{ + u8 val; + + /* Disable the ACCESS.bus device and Configure the SCL + frequency: 16 clock cycles */ + outb(0x70, ACBCTL2); + + if (inb(ACBCTL2) != 0x70) { + pr_debug(NAME ": ACBCTL2 readback failed\n"); + return -ENXIO; + } + + outb(inb(ACBCTL1) | ACBCTL1_NMINTE, ACBCTL1); + + val = inb(ACBCTL1); + if (val) { + pr_debug(NAME ": disabled, but ACBCTL1=0x%02x\n", + val); + return -ENXIO; + } + + outb(inb(ACBCTL2) | ACBCTL2_ENABLE, ACBCTL2); + + outb(inb(ACBCTL1) | ACBCTL1_NMINTE, ACBCTL1); + + val = inb(ACBCTL1); + if ((val & ACBCTL1_NMINTE) != ACBCTL1_NMINTE) { + pr_debug(NAME ": enabled, but NMINTE won't be set, " + "ACBCTL1=0x%02x\n", val); + return -ENXIO; + } + + return 0; +} + +static __init struct scx200_acb_iface *scx200_create_iface(const char *text, + struct device *dev, int index) +{ + struct scx200_acb_iface *iface; + struct i2c_adapter *adapter; + + iface = kzalloc(sizeof(*iface), GFP_KERNEL); + if (!iface) { + printk(KERN_ERR NAME ": can't allocate memory\n"); + return NULL; + } + + adapter = &iface->adapter; + i2c_set_adapdata(adapter, iface); + snprintf(adapter->name, sizeof(adapter->name), "%s ACB%d", text, index); + adapter->owner = THIS_MODULE; + adapter->id = I2C_HW_SMBUS_SCX200; + adapter->algo = &scx200_acb_algorithm; + adapter->class = I2C_CLASS_HWMON | I2C_CLASS_SPD; + adapter->dev.parent = dev; + + mutex_init(&iface->mutex); + + return iface; +} + +static int __init scx200_acb_create(struct scx200_acb_iface *iface) +{ + struct i2c_adapter *adapter; + int rc; + + adapter = &iface->adapter; + + rc = scx200_acb_probe(iface); + if (rc) { + printk(KERN_WARNING NAME ": probe failed\n"); + return rc; + } + + scx200_acb_reset(iface); + + if (i2c_add_adapter(adapter) < 0) { + printk(KERN_ERR NAME ": failed to register\n"); + return -ENODEV; + } + + mutex_lock(&scx200_acb_list_mutex); + iface->next = scx200_acb_list; + scx200_acb_list = iface; + mutex_unlock(&scx200_acb_list_mutex); + + return 0; +} + +static __init int scx200_create_pci(const char *text, struct pci_dev *pdev, + int bar) +{ + struct scx200_acb_iface *iface; + int rc; + + iface = scx200_create_iface(text, &pdev->dev, 0); + + if (iface == NULL) + return -ENOMEM; + + iface->pdev = pdev; + iface->bar = bar; + + rc = pci_enable_device_io(iface->pdev); + if (rc) + goto errout_free; + + rc = pci_request_region(iface->pdev, iface->bar, iface->adapter.name); + if (rc) { + printk(KERN_ERR NAME ": can't allocate PCI BAR %d\n", + iface->bar); + goto errout_free; + } + + iface->base = pci_resource_start(iface->pdev, iface->bar); + rc = scx200_acb_create(iface); + + if (rc == 0) + return 0; + + pci_release_region(iface->pdev, iface->bar); + pci_dev_put(iface->pdev); + errout_free: + kfree(iface); + return rc; +} + +static int __init scx200_create_isa(const char *text, unsigned long base, + int index) +{ + struct scx200_acb_iface *iface; + int rc; + + iface = scx200_create_iface(text, NULL, index); + + if (iface == NULL) + return -ENOMEM; + + if (!request_region(base, 8, iface->adapter.name)) { + printk(KERN_ERR NAME ": can't allocate io 0x%lx-0x%lx\n", + base, base + 8 - 1); + rc = -EBUSY; + goto errout_free; + } + + iface->base = base; + rc = scx200_acb_create(iface); + + if (rc == 0) + return 0; + + release_region(base, 8); + errout_free: + kfree(iface); + return rc; +} + +/* Driver data is an index into the scx200_data array that indicates + * the name and the BAR where the I/O address resource is located. ISA + * devices are flagged with a bar value of -1 */ + +static struct pci_device_id scx200_pci[] = { + { PCI_DEVICE(PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_SCx200_BRIDGE), + .driver_data = 0 }, + { PCI_DEVICE(PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_SC1100_BRIDGE), + .driver_data = 0 }, + { PCI_DEVICE(PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_CS5535_ISA), + .driver_data = 1 }, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_CS5536_ISA), + .driver_data = 2 } +}; + +static struct { + const char *name; + int bar; +} scx200_data[] = { + { "SCx200", -1 }, + { "CS5535", 0 }, + { "CS5536", 0 } +}; + +static __init int scx200_scan_pci(void) +{ + int data, dev; + int rc = -ENODEV; + struct pci_dev *pdev; + + for(dev = 0; dev < ARRAY_SIZE(scx200_pci); dev++) { + pdev = pci_get_device(scx200_pci[dev].vendor, + scx200_pci[dev].device, NULL); + + if (pdev == NULL) + continue; + + data = scx200_pci[dev].driver_data; + + /* if .bar is greater or equal to zero, this is a + * PCI device - otherwise, we assume + that the ports are ISA based + */ + + if (scx200_data[data].bar >= 0) + rc = scx200_create_pci(scx200_data[data].name, pdev, + scx200_data[data].bar); + else { + int i; + + pci_dev_put(pdev); + for (i = 0; i < MAX_DEVICES; ++i) { + if (base[i] == 0) + continue; + + rc = scx200_create_isa(scx200_data[data].name, + base[i], + i); + } + } + + break; + } + + return rc; +} + +static int __init scx200_acb_init(void) +{ + int rc; + + pr_debug(NAME ": NatSemi SCx200 ACCESS.bus Driver\n"); + + rc = scx200_scan_pci(); + + /* If at least one bus was created, init must succeed */ + if (scx200_acb_list) + return 0; + return rc; +} + +static void __exit scx200_acb_cleanup(void) +{ + struct scx200_acb_iface *iface; + + mutex_lock(&scx200_acb_list_mutex); + while ((iface = scx200_acb_list) != NULL) { + scx200_acb_list = iface->next; + mutex_unlock(&scx200_acb_list_mutex); + + i2c_del_adapter(&iface->adapter); + + if (iface->pdev) { + pci_release_region(iface->pdev, iface->bar); + pci_dev_put(iface->pdev); + } + else + release_region(iface->base, 8); + + kfree(iface); + mutex_lock(&scx200_acb_list_mutex); + } + mutex_unlock(&scx200_acb_list_mutex); +} + +module_init(scx200_acb_init); +module_exit(scx200_acb_cleanup); diff --git a/drivers/i2c/busses/scx200_i2c.c b/drivers/i2c/busses/scx200_i2c.c new file mode 100644 index 0000000..e4c9853 --- /dev/null +++ b/drivers/i2c/busses/scx200_i2c.c @@ -0,0 +1,132 @@ +/* linux/drivers/i2c/busses/scx200_i2c.c + + Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com> + + National Semiconductor SCx200 I2C bus on GPIO pins + + Based on i2c-velleman.c Copyright (C) 1995-96, 2000 Simon G. Vogl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#include <asm/io.h> + +#include <linux/scx200_gpio.h> + +#define NAME "scx200_i2c" + +MODULE_AUTHOR("Christer Weinigel <wingel@nano-system.com>"); +MODULE_DESCRIPTION("NatSemi SCx200 I2C Driver"); +MODULE_LICENSE("GPL"); + +static int scl = CONFIG_SCx200_I2C_SCL; +static int sda = CONFIG_SCx200_I2C_SDA; + +module_param(scl, int, 0); +MODULE_PARM_DESC(scl, "GPIO line for SCL"); +module_param(sda, int, 0); +MODULE_PARM_DESC(sda, "GPIO line for SDA"); + +static void scx200_i2c_setscl(void *data, int state) +{ + scx200_gpio_set(scl, state); +} + +static void scx200_i2c_setsda(void *data, int state) +{ + scx200_gpio_set(sda, state); +} + +static int scx200_i2c_getscl(void *data) +{ + return scx200_gpio_get(scl); +} + +static int scx200_i2c_getsda(void *data) +{ + return scx200_gpio_get(sda); +} + +/* ------------------------------------------------------------------------ + * Encapsulate the above functions in the correct operations structure. + * This is only done when more than one hardware adapter is supported. + */ + +static struct i2c_algo_bit_data scx200_i2c_data = { + .setsda = scx200_i2c_setsda, + .setscl = scx200_i2c_setscl, + .getsda = scx200_i2c_getsda, + .getscl = scx200_i2c_getscl, + .udelay = 10, + .timeout = 100, +}; + +static struct i2c_adapter scx200_i2c_ops = { + .owner = THIS_MODULE, + .class = I2C_CLASS_HWMON | I2C_CLASS_SPD, + .id = I2C_HW_B_SCX200, + .algo_data = &scx200_i2c_data, + .name = "NatSemi SCx200 I2C", +}; + +static int scx200_i2c_init(void) +{ + pr_debug(NAME ": NatSemi SCx200 I2C Driver\n"); + + if (!scx200_gpio_present()) { + printk(KERN_ERR NAME ": no SCx200 gpio pins available\n"); + return -ENODEV; + } + + pr_debug(NAME ": SCL=GPIO%02u, SDA=GPIO%02u\n", scl, sda); + + if (scl == -1 || sda == -1 || scl == sda) { + printk(KERN_ERR NAME ": scl and sda must be specified\n"); + return -EINVAL; + } + + /* Configure GPIOs as open collector outputs */ + scx200_gpio_configure(scl, ~2, 5); + scx200_gpio_configure(sda, ~2, 5); + + if (i2c_bit_add_bus(&scx200_i2c_ops) < 0) { + printk(KERN_ERR NAME ": adapter %s registration failed\n", + scx200_i2c_ops.name); + return -ENODEV; + } + + return 0; +} + +static void scx200_i2c_cleanup(void) +{ + i2c_del_adapter(&scx200_i2c_ops); +} + +module_init(scx200_i2c_init); +module_exit(scx200_i2c_cleanup); + +/* + Local variables: + compile-command: "make -k -C ../.. SUBDIRS=drivers/i2c modules" + c-basic-offset: 8 + End: +*/ diff --git a/drivers/i2c/chips/Kconfig b/drivers/i2c/chips/Kconfig new file mode 100644 index 0000000..4c35702 --- /dev/null +++ b/drivers/i2c/chips/Kconfig @@ -0,0 +1,188 @@ +# +# Miscellaneous I2C chip drivers configuration +# +# *** DEPRECATED! Do not add new entries! See Makefile *** +# + +menu "Miscellaneous I2C Chip support" + +config DS1682 + tristate "Dallas DS1682 Total Elapsed Time Recorder with Alarm" + depends on EXPERIMENTAL + help + If you say yes here you get support for Dallas Semiconductor + DS1682 Total Elapsed Time Recorder. + + This driver can also be built as a module. If so, the module + will be called ds1682. + +config AT24 + tristate "EEPROMs from most vendors" + depends on SYSFS && EXPERIMENTAL + help + Enable this driver to get read/write support to most I2C EEPROMs, + after you configure the driver to know about each EEPROM on + your target board. Use these generic chip names, instead of + vendor-specific ones like at24c64 or 24lc02: + + 24c00, 24c01, 24c02, spd (readonly 24c02), 24c04, 24c08, + 24c16, 24c32, 24c64, 24c128, 24c256, 24c512, 24c1024 + + Unless you like data loss puzzles, always be sure that any chip + you configure as a 24c32 (32 kbit) or larger is NOT really a + 24c16 (16 kbit) or smaller, and vice versa. Marking the chip + as read-only won't help recover from this. Also, if your chip + has any software write-protect mechanism you may want to review the + code to make sure this driver won't turn it on by accident. + + If you use this with an SMBus adapter instead of an I2C adapter, + full functionality is not available. Only smaller devices are + supported (24c16 and below, max 4 kByte). + + This driver can also be built as a module. If so, the module + will be called at24. + +config SENSORS_EEPROM + tristate "EEPROM reader" + depends on EXPERIMENTAL + help + If you say yes here you get read-only access to the EEPROM data + available on modern memory DIMMs and Sony Vaio laptops. Such + EEPROMs could theoretically be available on other devices as well. + + This driver can also be built as a module. If so, the module + will be called eeprom. + +config SENSORS_PCF8574 + tristate "Philips PCF8574 and PCF8574A (DEPRECATED)" + depends on EXPERIMENTAL && GPIO_PCF857X = "n" + default n + help + If you say yes here you get support for Philips PCF8574 and + PCF8574A chips. These chips are 8-bit I/O expanders for the I2C bus. + + This driver can also be built as a module. If so, the module + will be called pcf8574. + + This driver is deprecated and will be dropped soon. Use + drivers/gpio/pcf857x.c instead. + + These devices are hard to detect and rarely found on mainstream + hardware. If unsure, say N. + +config PCF8575 + tristate "Philips PCF8575 (DEPRECATED)" + default n + depends on GPIO_PCF857X = "n" + help + If you say yes here you get support for Philips PCF8575 chip. + This chip is a 16-bit I/O expander for the I2C bus. Several other + chip manufacturers sell equivalent chips, e.g. Texas Instruments. + + This driver can also be built as a module. If so, the module + will be called pcf8575. + + This driver is deprecated and will be dropped soon. Use + drivers/gpio/pcf857x.c instead. + + This device is hard to detect and is rarely found on mainstream + hardware. If unsure, say N. + +config SENSORS_PCA9539 + tristate "Philips PCA9539 16-bit I/O port (DEPRECATED)" + depends on EXPERIMENTAL && GPIO_PCA953X = "n" + help + If you say yes here you get support for the Philips PCA9539 + 16-bit I/O port. + + This driver can also be built as a module. If so, the module + will be called pca9539. + + This driver is deprecated and will be dropped soon. Use + drivers/gpio/pca953x.c instead. + +config SENSORS_PCF8591 + tristate "Philips PCF8591" + depends on EXPERIMENTAL + default n + help + If you say yes here you get support for Philips PCF8591 chips. + + This driver can also be built as a module. If so, the module + will be called pcf8591. + + These devices are hard to detect and rarely found on mainstream + hardware. If unsure, say N. + +config ISP1301_OMAP + tristate "Philips ISP1301 with OMAP OTG" + depends on ARCH_OMAP_OTG + help + If you say yes here you get support for the Philips ISP1301 + USB-On-The-Go transceiver working with the OMAP OTG controller. + The ISP1301 is used in products including H2 and H3 development + boards for Texas Instruments OMAP processors. + + This driver can also be built as a module. If so, the module + will be called isp1301_omap. + +config TPS65010 + tristate "TPS6501x Power Management chips" + depends on GPIOLIB + default y if MACH_OMAP_H2 || MACH_OMAP_H3 || MACH_OMAP_OSK + help + If you say yes here you get support for the TPS6501x series of + Power Management chips. These include voltage regulators, + lithium ion/polymer battery charging, and other features that + are often used in portable devices like cell phones and cameras. + + This driver can also be built as a module. If so, the module + will be called tps65010. + +config SENSORS_MAX6875 + tristate "Maxim MAX6875 Power supply supervisor" + depends on EXPERIMENTAL + help + If you say yes here you get support for the Maxim MAX6875 + EEPROM-programmable, quad power-supply sequencer/supervisor. + + This provides an interface to program the EEPROM and reset the chip. + + This driver also supports the Maxim MAX6874 hex power-supply + sequencer/supervisor if found at a compatible address. + + This driver can also be built as a module. If so, the module + will be called max6875. + +config SENSORS_TSL2550 + tristate "Taos TSL2550 ambient light sensor" + depends on EXPERIMENTAL + help + If you say yes here you get support for the Taos TSL2550 + ambient light sensor. + + This driver can also be built as a module. If so, the module + will be called tsl2550. + +config MENELAUS + bool "TWL92330/Menelaus PM chip" + depends on I2C=y && ARCH_OMAP24XX + help + If you say yes here you get support for the Texas Instruments + TWL92330/Menelaus Power Management chip. This include voltage + regulators, Dual slot memory card tranceivers, real-time clock + and other features that are often used in portable devices like + cell phones and PDAs. + +config MCU_MPC8349EMITX + tristate "MPC8349E-mITX MCU driver" + depends on I2C && PPC_83xx + select GENERIC_GPIO + select ARCH_REQUIRE_GPIOLIB + help + Say Y here to enable soft power-off functionality on the Freescale + boards with the MPC8349E-mITX-compatible MCU chips. This driver will + also register MCU GPIOs with the generic GPIO API, so you'll able + to use MCU pins as GPIOs. + +endmenu diff --git a/drivers/i2c/chips/Makefile b/drivers/i2c/chips/Makefile new file mode 100644 index 0000000..23d2a31 --- /dev/null +++ b/drivers/i2c/chips/Makefile @@ -0,0 +1,30 @@ +# +# Makefile for miscellaneous I2C chip drivers. +# +# Do not add new drivers to this directory! It is DEPRECATED. +# +# Device drivers are better grouped according to the functionality they +# implement rather than to the bus they are connected to. In particular: +# * Hardware monitoring chip drivers go to drivers/hwmon +# * RTC chip drivers go to drivers/rtc +# * I/O expander drivers go to drivers/gpio +# + +obj-$(CONFIG_DS1682) += ds1682.o +obj-$(CONFIG_AT24) += at24.o +obj-$(CONFIG_SENSORS_EEPROM) += eeprom.o +obj-$(CONFIG_SENSORS_MAX6875) += max6875.o +obj-$(CONFIG_SENSORS_PCA9539) += pca9539.o +obj-$(CONFIG_SENSORS_PCF8574) += pcf8574.o +obj-$(CONFIG_PCF8575) += pcf8575.o +obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o +obj-$(CONFIG_ISP1301_OMAP) += isp1301_omap.o +obj-$(CONFIG_TPS65010) += tps65010.o +obj-$(CONFIG_MENELAUS) += menelaus.o +obj-$(CONFIG_SENSORS_TSL2550) += tsl2550.o +obj-$(CONFIG_MCU_MPC8349EMITX) += mcu_mpc8349emitx.o + +ifeq ($(CONFIG_I2C_DEBUG_CHIP),y) +EXTRA_CFLAGS += -DDEBUG +endif + diff --git a/drivers/i2c/chips/at24.c b/drivers/i2c/chips/at24.c new file mode 100644 index 0000000..d477552 --- /dev/null +++ b/drivers/i2c/chips/at24.c @@ -0,0 +1,582 @@ +/* + * at24.c - handle most I2C EEPROMs + * + * Copyright (C) 2005-2007 David Brownell + * Copyright (C) 2008 Wolfram Sang, Pengutronix + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/mutex.h> +#include <linux/sysfs.h> +#include <linux/mod_devicetable.h> +#include <linux/log2.h> +#include <linux/bitops.h> +#include <linux/jiffies.h> +#include <linux/i2c.h> +#include <linux/i2c/at24.h> + +/* + * I2C EEPROMs from most vendors are inexpensive and mostly interchangeable. + * Differences between different vendor product lines (like Atmel AT24C or + * MicroChip 24LC, etc) won't much matter for typical read/write access. + * There are also I2C RAM chips, likewise interchangeable. One example + * would be the PCF8570, which acts like a 24c02 EEPROM (256 bytes). + * + * However, misconfiguration can lose data. "Set 16-bit memory address" + * to a part with 8-bit addressing will overwrite data. Writing with too + * big a page size also loses data. And it's not safe to assume that the + * conventional addresses 0x50..0x57 only hold eeproms; a PCF8563 RTC + * uses 0x51, for just one example. + * + * Accordingly, explicit board-specific configuration data should be used + * in almost all cases. (One partial exception is an SMBus used to access + * "SPD" data for DRAM sticks. Those only use 24c02 EEPROMs.) + * + * So this driver uses "new style" I2C driver binding, expecting to be + * told what devices exist. That may be in arch/X/mach-Y/board-Z.c or + * similar kernel-resident tables; or, configuration data coming from + * a bootloader. + * + * Other than binding model, current differences from "eeprom" driver are + * that this one handles write access and isn't restricted to 24c02 devices. + * It also handles larger devices (32 kbit and up) with two-byte addresses, + * which won't work on pure SMBus systems. + */ + +struct at24_data { + struct at24_platform_data chip; + bool use_smbus; + + /* + * Lock protects against activities from other Linux tasks, + * but not from changes by other I2C masters. + */ + struct mutex lock; + struct bin_attribute bin; + + u8 *writebuf; + unsigned write_max; + unsigned num_addresses; + + /* + * Some chips tie up multiple I2C addresses; dummy devices reserve + * them for us, and we'll use them with SMBus calls. + */ + struct i2c_client *client[]; +}; + +/* + * This parameter is to help this driver avoid blocking other drivers out + * of I2C for potentially troublesome amounts of time. With a 100 kHz I2C + * clock, one 256 byte read takes about 1/43 second which is excessive; + * but the 1/170 second it takes at 400 kHz may be quite reasonable; and + * at 1 MHz (Fm+) a 1/430 second delay could easily be invisible. + * + * This value is forced to be a power of two so that writes align on pages. + */ +static unsigned io_limit = 128; +module_param(io_limit, uint, 0); +MODULE_PARM_DESC(io_limit, "Maximum bytes per I/O (default 128)"); + +/* + * Specs often allow 5 msec for a page write, sometimes 20 msec; + * it's important to recover from write timeouts. + */ +static unsigned write_timeout = 25; +module_param(write_timeout, uint, 0); +MODULE_PARM_DESC(write_timeout, "Time (in ms) to try writes (default 25)"); + +#define AT24_SIZE_BYTELEN 5 +#define AT24_SIZE_FLAGS 8 + +#define AT24_BITMASK(x) (BIT(x) - 1) + +/* create non-zero magic value for given eeprom parameters */ +#define AT24_DEVICE_MAGIC(_len, _flags) \ + ((1 << AT24_SIZE_FLAGS | (_flags)) \ + << AT24_SIZE_BYTELEN | ilog2(_len)) + +static const struct i2c_device_id at24_ids[] = { + /* needs 8 addresses as A0-A2 are ignored */ + { "24c00", AT24_DEVICE_MAGIC(128 / 8, AT24_FLAG_TAKE8ADDR) }, + /* old variants can't be handled with this generic entry! */ + { "24c01", AT24_DEVICE_MAGIC(1024 / 8, 0) }, + { "24c02", AT24_DEVICE_MAGIC(2048 / 8, 0) }, + /* spd is a 24c02 in memory DIMMs */ + { "spd", AT24_DEVICE_MAGIC(2048 / 8, + AT24_FLAG_READONLY | AT24_FLAG_IRUGO) }, + { "24c04", AT24_DEVICE_MAGIC(4096 / 8, 0) }, + /* 24rf08 quirk is handled at i2c-core */ + { "24c08", AT24_DEVICE_MAGIC(8192 / 8, 0) }, + { "24c16", AT24_DEVICE_MAGIC(16384 / 8, 0) }, + { "24c32", AT24_DEVICE_MAGIC(32768 / 8, AT24_FLAG_ADDR16) }, + { "24c64", AT24_DEVICE_MAGIC(65536 / 8, AT24_FLAG_ADDR16) }, + { "24c128", AT24_DEVICE_MAGIC(131072 / 8, AT24_FLAG_ADDR16) }, + { "24c256", AT24_DEVICE_MAGIC(262144 / 8, AT24_FLAG_ADDR16) }, + { "24c512", AT24_DEVICE_MAGIC(524288 / 8, AT24_FLAG_ADDR16) }, + { "24c1024", AT24_DEVICE_MAGIC(1048576 / 8, AT24_FLAG_ADDR16) }, + { "at24", 0 }, + { /* END OF LIST */ } +}; +MODULE_DEVICE_TABLE(i2c, at24_ids); + +/*-------------------------------------------------------------------------*/ + +/* + * This routine supports chips which consume multiple I2C addresses. It + * computes the addressing information to be used for a given r/w request. + * Assumes that sanity checks for offset happened at sysfs-layer. + */ +static struct i2c_client *at24_translate_offset(struct at24_data *at24, + unsigned *offset) +{ + unsigned i; + + if (at24->chip.flags & AT24_FLAG_ADDR16) { + i = *offset >> 16; + *offset &= 0xffff; + } else { + i = *offset >> 8; + *offset &= 0xff; + } + + return at24->client[i]; +} + +static ssize_t at24_eeprom_read(struct at24_data *at24, char *buf, + unsigned offset, size_t count) +{ + struct i2c_msg msg[2]; + u8 msgbuf[2]; + struct i2c_client *client; + int status, i; + + memset(msg, 0, sizeof(msg)); + + /* + * REVISIT some multi-address chips don't rollover page reads to + * the next slave address, so we may need to truncate the count. + * Those chips might need another quirk flag. + * + * If the real hardware used four adjacent 24c02 chips and that + * were misconfigured as one 24c08, that would be a similar effect: + * one "eeprom" file not four, but larger reads would fail when + * they crossed certain pages. + */ + + /* + * Slave address and byte offset derive from the offset. Always + * set the byte address; on a multi-master board, another master + * may have changed the chip's "current" address pointer. + */ + client = at24_translate_offset(at24, &offset); + + if (count > io_limit) + count = io_limit; + + /* Smaller eeproms can work given some SMBus extension calls */ + if (at24->use_smbus) { + if (count > I2C_SMBUS_BLOCK_MAX) + count = I2C_SMBUS_BLOCK_MAX; + status = i2c_smbus_read_i2c_block_data(client, offset, + count, buf); + dev_dbg(&client->dev, "smbus read %zu@%d --> %d\n", + count, offset, status); + return (status < 0) ? -EIO : status; + } + + /* + * When we have a better choice than SMBus calls, use a combined + * I2C message. Write address; then read up to io_limit data bytes. + * Note that read page rollover helps us here (unlike writes). + * msgbuf is u8 and will cast to our needs. + */ + i = 0; + if (at24->chip.flags & AT24_FLAG_ADDR16) + msgbuf[i++] = offset >> 8; + msgbuf[i++] = offset; + + msg[0].addr = client->addr; + msg[0].buf = msgbuf; + msg[0].len = i; + + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].buf = buf; + msg[1].len = count; + + status = i2c_transfer(client->adapter, msg, 2); + dev_dbg(&client->dev, "i2c read %zu@%d --> %d\n", + count, offset, status); + + if (status == 2) + return count; + else if (status >= 0) + return -EIO; + else + return status; +} + +static ssize_t at24_bin_read(struct kobject *kobj, struct bin_attribute *attr, + char *buf, loff_t off, size_t count) +{ + struct at24_data *at24; + ssize_t retval = 0; + + at24 = dev_get_drvdata(container_of(kobj, struct device, kobj)); + + if (unlikely(!count)) + return count; + + /* + * Read data from chip, protecting against concurrent updates + * from this host, but not from other I2C masters. + */ + mutex_lock(&at24->lock); + + while (count) { + ssize_t status; + + status = at24_eeprom_read(at24, buf, off, count); + if (status <= 0) { + if (retval == 0) + retval = status; + break; + } + buf += status; + off += status; + count -= status; + retval += status; + } + + mutex_unlock(&at24->lock); + + return retval; +} + + +/* + * REVISIT: export at24_bin{read,write}() to let other kernel code use + * eeprom data. For example, it might hold a board's Ethernet address, or + * board-specific calibration data generated on the manufacturing floor. + */ + + +/* + * Note that if the hardware write-protect pin is pulled high, the whole + * chip is normally write protected. But there are plenty of product + * variants here, including OTP fuses and partial chip protect. + * + * We only use page mode writes; the alternative is sloooow. This routine + * writes at most one page. + */ +static ssize_t at24_eeprom_write(struct at24_data *at24, char *buf, + unsigned offset, size_t count) +{ + struct i2c_client *client; + struct i2c_msg msg; + ssize_t status; + unsigned long timeout, write_time; + unsigned next_page; + + /* Get corresponding I2C address and adjust offset */ + client = at24_translate_offset(at24, &offset); + + /* write_max is at most a page */ + if (count > at24->write_max) + count = at24->write_max; + + /* Never roll over backwards, to the start of this page */ + next_page = roundup(offset + 1, at24->chip.page_size); + if (offset + count > next_page) + count = next_page - offset; + + /* If we'll use I2C calls for I/O, set up the message */ + if (!at24->use_smbus) { + int i = 0; + + msg.addr = client->addr; + msg.flags = 0; + + /* msg.buf is u8 and casts will mask the values */ + msg.buf = at24->writebuf; + if (at24->chip.flags & AT24_FLAG_ADDR16) + msg.buf[i++] = offset >> 8; + + msg.buf[i++] = offset; + memcpy(&msg.buf[i], buf, count); + msg.len = i + count; + } + + /* + * Writes fail if the previous one didn't complete yet. We may + * loop a few times until this one succeeds, waiting at least + * long enough for one entire page write to work. + */ + timeout = jiffies + msecs_to_jiffies(write_timeout); + do { + write_time = jiffies; + if (at24->use_smbus) { + status = i2c_smbus_write_i2c_block_data(client, + offset, count, buf); + if (status == 0) + status = count; + } else { + status = i2c_transfer(client->adapter, &msg, 1); + if (status == 1) + status = count; + } + dev_dbg(&client->dev, "write %zu@%d --> %zd (%ld)\n", + count, offset, status, jiffies); + + if (status == count) + return count; + + /* REVISIT: at HZ=100, this is sloooow */ + msleep(1); + } while (time_before(write_time, timeout)); + + return -ETIMEDOUT; +} + +static ssize_t at24_bin_write(struct kobject *kobj, struct bin_attribute *attr, + char *buf, loff_t off, size_t count) +{ + struct at24_data *at24; + ssize_t retval = 0; + + at24 = dev_get_drvdata(container_of(kobj, struct device, kobj)); + + if (unlikely(!count)) + return count; + + /* + * Write data to chip, protecting against concurrent updates + * from this host, but not from other I2C masters. + */ + mutex_lock(&at24->lock); + + while (count) { + ssize_t status; + + status = at24_eeprom_write(at24, buf, off, count); + if (status <= 0) { + if (retval == 0) + retval = status; + break; + } + buf += status; + off += status; + count -= status; + retval += status; + } + + mutex_unlock(&at24->lock); + + return retval; +} + +/*-------------------------------------------------------------------------*/ + +static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct at24_platform_data chip; + bool writable; + bool use_smbus = false; + struct at24_data *at24; + int err; + unsigned i, num_addresses; + kernel_ulong_t magic; + + if (client->dev.platform_data) { + chip = *(struct at24_platform_data *)client->dev.platform_data; + } else { + if (!id->driver_data) { + err = -ENODEV; + goto err_out; + } + magic = id->driver_data; + chip.byte_len = BIT(magic & AT24_BITMASK(AT24_SIZE_BYTELEN)); + magic >>= AT24_SIZE_BYTELEN; + chip.flags = magic & AT24_BITMASK(AT24_SIZE_FLAGS); + /* + * This is slow, but we can't know all eeproms, so we better + * play safe. Specifying custom eeprom-types via platform_data + * is recommended anyhow. + */ + chip.page_size = 1; + } + + if (!is_power_of_2(chip.byte_len)) + dev_warn(&client->dev, + "byte_len looks suspicious (no power of 2)!\n"); + if (!is_power_of_2(chip.page_size)) + dev_warn(&client->dev, + "page_size looks suspicious (no power of 2)!\n"); + + /* Use I2C operations unless we're stuck with SMBus extensions. */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + if (chip.flags & AT24_FLAG_ADDR16) { + err = -EPFNOSUPPORT; + goto err_out; + } + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { + err = -EPFNOSUPPORT; + goto err_out; + } + use_smbus = true; + } + + if (chip.flags & AT24_FLAG_TAKE8ADDR) + num_addresses = 8; + else + num_addresses = DIV_ROUND_UP(chip.byte_len, + (chip.flags & AT24_FLAG_ADDR16) ? 65536 : 256); + + at24 = kzalloc(sizeof(struct at24_data) + + num_addresses * sizeof(struct i2c_client *), GFP_KERNEL); + if (!at24) { + err = -ENOMEM; + goto err_out; + } + + mutex_init(&at24->lock); + at24->use_smbus = use_smbus; + at24->chip = chip; + at24->num_addresses = num_addresses; + + /* + * Export the EEPROM bytes through sysfs, since that's convenient. + * By default, only root should see the data (maybe passwords etc) + */ + at24->bin.attr.name = "eeprom"; + at24->bin.attr.mode = chip.flags & AT24_FLAG_IRUGO ? S_IRUGO : S_IRUSR; + at24->bin.read = at24_bin_read; + at24->bin.size = chip.byte_len; + + writable = !(chip.flags & AT24_FLAG_READONLY); + if (writable) { + if (!use_smbus || i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WRITE_I2C_BLOCK)) { + + unsigned write_max = chip.page_size; + + at24->bin.write = at24_bin_write; + at24->bin.attr.mode |= S_IWUSR; + + if (write_max > io_limit) + write_max = io_limit; + if (use_smbus && write_max > I2C_SMBUS_BLOCK_MAX) + write_max = I2C_SMBUS_BLOCK_MAX; + at24->write_max = write_max; + + /* buffer (data + address at the beginning) */ + at24->writebuf = kmalloc(write_max + 2, GFP_KERNEL); + if (!at24->writebuf) { + err = -ENOMEM; + goto err_struct; + } + } else { + dev_warn(&client->dev, + "cannot write due to controller restrictions."); + } + } + + at24->client[0] = client; + + /* use dummy devices for multiple-address chips */ + for (i = 1; i < num_addresses; i++) { + at24->client[i] = i2c_new_dummy(client->adapter, + client->addr + i); + if (!at24->client[i]) { + dev_err(&client->dev, "address 0x%02x unavailable\n", + client->addr + i); + err = -EADDRINUSE; + goto err_clients; + } + } + + err = sysfs_create_bin_file(&client->dev.kobj, &at24->bin); + if (err) + goto err_clients; + + i2c_set_clientdata(client, at24); + + dev_info(&client->dev, "%zu byte %s EEPROM %s\n", + at24->bin.size, client->name, + writable ? "(writable)" : "(read-only)"); + dev_dbg(&client->dev, + "page_size %d, num_addresses %d, write_max %d%s\n", + chip.page_size, num_addresses, + at24->write_max, + use_smbus ? ", use_smbus" : ""); + + return 0; + +err_clients: + for (i = 1; i < num_addresses; i++) + if (at24->client[i]) + i2c_unregister_device(at24->client[i]); + + kfree(at24->writebuf); +err_struct: + kfree(at24); +err_out: + dev_dbg(&client->dev, "probe error %d\n", err); + return err; +} + +static int __devexit at24_remove(struct i2c_client *client) +{ + struct at24_data *at24; + int i; + + at24 = i2c_get_clientdata(client); + sysfs_remove_bin_file(&client->dev.kobj, &at24->bin); + + for (i = 1; i < at24->num_addresses; i++) + i2c_unregister_device(at24->client[i]); + + kfree(at24->writebuf); + kfree(at24); + i2c_set_clientdata(client, NULL); + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static struct i2c_driver at24_driver = { + .driver = { + .name = "at24", + .owner = THIS_MODULE, + }, + .probe = at24_probe, + .remove = __devexit_p(at24_remove), + .id_table = at24_ids, +}; + +static int __init at24_init(void) +{ + io_limit = rounddown_pow_of_two(io_limit); + return i2c_add_driver(&at24_driver); +} +module_init(at24_init); + +static void __exit at24_exit(void) +{ + i2c_del_driver(&at24_driver); +} +module_exit(at24_exit); + +MODULE_DESCRIPTION("Driver for most I2C EEPROMs"); +MODULE_AUTHOR("David Brownell and Wolfram Sang"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/chips/ds1682.c b/drivers/i2c/chips/ds1682.c new file mode 100644 index 0000000..f3ee4a1 --- /dev/null +++ b/drivers/i2c/chips/ds1682.c @@ -0,0 +1,267 @@ +/* + * Dallas Semiconductor DS1682 Elapsed Time Recorder device driver + * + * Written by: Grant Likely <grant.likely@secretlab.ca> + * + * Copyright (C) 2007 Secret Lab Technologies Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +/* + * The DS1682 elapsed timer recorder is a simple device that implements + * one elapsed time counter, one event counter, an alarm signal and 10 + * bytes of general purpose EEPROM. + * + * This driver provides access to the DS1682 counters and user data via + * the sysfs. The following attributes are added to the device node: + * elapsed_time (u32): Total elapsed event time in ms resolution + * alarm_time (u32): When elapsed time exceeds the value in alarm_time, + * then the alarm pin is asserted. + * event_count (u16): number of times the event pin has gone low. + * eeprom (u8[10]): general purpose EEPROM + * + * Counter registers and user data are both read/write unless the device + * has been write protected. This driver does not support turning off write + * protection. Once write protection is turned on, it is impossible to + * turn it off again, so I have left the feature out of this driver to avoid + * accidental enabling, but it is trivial to add write protect support. + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/string.h> +#include <linux/list.h> +#include <linux/sysfs.h> +#include <linux/ctype.h> +#include <linux/hwmon-sysfs.h> + +/* Device registers */ +#define DS1682_REG_CONFIG 0x00 +#define DS1682_REG_ALARM 0x01 +#define DS1682_REG_ELAPSED 0x05 +#define DS1682_REG_EVT_CNTR 0x09 +#define DS1682_REG_EEPROM 0x0b +#define DS1682_REG_RESET 0x1d +#define DS1682_REG_WRITE_DISABLE 0x1e +#define DS1682_REG_WRITE_MEM_DISABLE 0x1f + +#define DS1682_EEPROM_SIZE 10 + +/* + * Generic counter attributes + */ +static ssize_t ds1682_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct i2c_client *client = to_i2c_client(dev); + __le32 val = 0; + int rc; + + dev_dbg(dev, "ds1682_show() called on %s\n", attr->attr.name); + + /* Read the register */ + rc = i2c_smbus_read_i2c_block_data(client, sattr->index, sattr->nr, + (u8 *) & val); + if (rc < 0) + return -EIO; + + /* Special case: the 32 bit regs are time values with 1/4s + * resolution, scale them up to milliseconds */ + if (sattr->nr == 4) + return sprintf(buf, "%llu\n", + ((unsigned long long)le32_to_cpu(val)) * 250); + + /* Format the output string and return # of bytes */ + return sprintf(buf, "%li\n", (long)le32_to_cpu(val)); +} + +static ssize_t ds1682_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct i2c_client *client = to_i2c_client(dev); + char *endp; + u64 val; + __le32 val_le; + int rc; + + dev_dbg(dev, "ds1682_store() called on %s\n", attr->attr.name); + + /* Decode input */ + val = simple_strtoull(buf, &endp, 0); + if (buf == endp) { + dev_dbg(dev, "input string not a number\n"); + return -EINVAL; + } + + /* Special case: the 32 bit regs are time values with 1/4s + * resolution, scale input down to quarter-seconds */ + if (sattr->nr == 4) + do_div(val, 250); + + /* write out the value */ + val_le = cpu_to_le32(val); + rc = i2c_smbus_write_i2c_block_data(client, sattr->index, sattr->nr, + (u8 *) & val_le); + if (rc < 0) { + dev_err(dev, "register write failed; reg=0x%x, size=%i\n", + sattr->index, sattr->nr); + return -EIO; + } + + return count; +} + +/* + * Simple register attributes + */ +static SENSOR_DEVICE_ATTR_2(elapsed_time, S_IRUGO | S_IWUSR, ds1682_show, + ds1682_store, 4, DS1682_REG_ELAPSED); +static SENSOR_DEVICE_ATTR_2(alarm_time, S_IRUGO | S_IWUSR, ds1682_show, + ds1682_store, 4, DS1682_REG_ALARM); +static SENSOR_DEVICE_ATTR_2(event_count, S_IRUGO | S_IWUSR, ds1682_show, + ds1682_store, 2, DS1682_REG_EVT_CNTR); + +static const struct attribute_group ds1682_group = { + .attrs = (struct attribute *[]) { + &sensor_dev_attr_elapsed_time.dev_attr.attr, + &sensor_dev_attr_alarm_time.dev_attr.attr, + &sensor_dev_attr_event_count.dev_attr.attr, + NULL, + }, +}; + +/* + * User data attribute + */ +static ssize_t ds1682_eeprom_read(struct kobject *kobj, struct bin_attribute *attr, + char *buf, loff_t off, size_t count) +{ + struct i2c_client *client = kobj_to_i2c_client(kobj); + int rc; + + dev_dbg(&client->dev, "ds1682_eeprom_read(p=%p, off=%lli, c=%zi)\n", + buf, off, count); + + if (off >= DS1682_EEPROM_SIZE) + return 0; + + if (off + count > DS1682_EEPROM_SIZE) + count = DS1682_EEPROM_SIZE - off; + + rc = i2c_smbus_read_i2c_block_data(client, DS1682_REG_EEPROM + off, + count, buf); + if (rc < 0) + return -EIO; + + return count; +} + +static ssize_t ds1682_eeprom_write(struct kobject *kobj, struct bin_attribute *attr, + char *buf, loff_t off, size_t count) +{ + struct i2c_client *client = kobj_to_i2c_client(kobj); + + dev_dbg(&client->dev, "ds1682_eeprom_write(p=%p, off=%lli, c=%zi)\n", + buf, off, count); + + if (off >= DS1682_EEPROM_SIZE) + return -ENOSPC; + + if (off + count > DS1682_EEPROM_SIZE) + count = DS1682_EEPROM_SIZE - off; + + /* Write out to the device */ + if (i2c_smbus_write_i2c_block_data(client, DS1682_REG_EEPROM + off, + count, buf) < 0) + return -EIO; + + return count; +} + +static struct bin_attribute ds1682_eeprom_attr = { + .attr = { + .name = "eeprom", + .mode = S_IRUGO | S_IWUSR, + }, + .size = DS1682_EEPROM_SIZE, + .read = ds1682_eeprom_read, + .write = ds1682_eeprom_write, +}; + +/* + * Called when a ds1682 device is matched with this driver + */ +static int ds1682_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_I2C_BLOCK)) { + dev_err(&client->dev, "i2c bus does not support the ds1682\n"); + rc = -ENODEV; + goto exit; + } + + rc = sysfs_create_group(&client->dev.kobj, &ds1682_group); + if (rc) + goto exit; + + rc = sysfs_create_bin_file(&client->dev.kobj, &ds1682_eeprom_attr); + if (rc) + goto exit_bin_attr; + + return 0; + + exit_bin_attr: + sysfs_remove_group(&client->dev.kobj, &ds1682_group); + exit: + return rc; +} + +static int ds1682_remove(struct i2c_client *client) +{ + sysfs_remove_bin_file(&client->dev.kobj, &ds1682_eeprom_attr); + sysfs_remove_group(&client->dev.kobj, &ds1682_group); + return 0; +} + +static const struct i2c_device_id ds1682_id[] = { + { "ds1682", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ds1682_id); + +static struct i2c_driver ds1682_driver = { + .driver = { + .name = "ds1682", + }, + .probe = ds1682_probe, + .remove = ds1682_remove, + .id_table = ds1682_id, +}; + +static int __init ds1682_init(void) +{ + return i2c_add_driver(&ds1682_driver); +} + +static void __exit ds1682_exit(void) +{ + i2c_del_driver(&ds1682_driver); +} + +MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>"); +MODULE_DESCRIPTION("DS1682 Elapsed Time Indicator driver"); +MODULE_LICENSE("GPL"); + +module_init(ds1682_init); +module_exit(ds1682_exit); diff --git a/drivers/i2c/chips/eeprom.c b/drivers/i2c/chips/eeprom.c new file mode 100644 index 0000000..2c27193 --- /dev/null +++ b/drivers/i2c/chips/eeprom.c @@ -0,0 +1,257 @@ +/* + Copyright (C) 1998, 1999 Frodo Looijaard <frodol@dds.nl> and + Philip Edelbrock <phil@netroedge.com> + Copyright (C) 2003 Greg Kroah-Hartman <greg@kroah.com> + Copyright (C) 2003 IBM Corp. + Copyright (C) 2004 Jean Delvare <khali@linux-fr.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/jiffies.h> +#include <linux/i2c.h> +#include <linux/mutex.h> + +/* Addresses to scan */ +static const unsigned short normal_i2c[] = { 0x50, 0x51, 0x52, 0x53, 0x54, + 0x55, 0x56, 0x57, I2C_CLIENT_END }; + +/* Insmod parameters */ +I2C_CLIENT_INSMOD_1(eeprom); + + +/* Size of EEPROM in bytes */ +#define EEPROM_SIZE 256 + +/* possible types of eeprom devices */ +enum eeprom_nature { + UNKNOWN, + VAIO, +}; + +/* Each client has this additional data */ +struct eeprom_data { + struct mutex update_lock; + u8 valid; /* bitfield, bit!=0 if slice is valid */ + unsigned long last_updated[8]; /* In jiffies, 8 slices */ + u8 data[EEPROM_SIZE]; /* Register values */ + enum eeprom_nature nature; +}; + + +static void eeprom_update_client(struct i2c_client *client, u8 slice) +{ + struct eeprom_data *data = i2c_get_clientdata(client); + int i; + + mutex_lock(&data->update_lock); + + if (!(data->valid & (1 << slice)) || + time_after(jiffies, data->last_updated[slice] + 300 * HZ)) { + dev_dbg(&client->dev, "Starting eeprom update, slice %u\n", slice); + + if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { + for (i = slice << 5; i < (slice + 1) << 5; i += 32) + if (i2c_smbus_read_i2c_block_data(client, i, + 32, data->data + i) + != 32) + goto exit; + } else { + for (i = slice << 5; i < (slice + 1) << 5; i += 2) { + int word = i2c_smbus_read_word_data(client, i); + if (word < 0) + goto exit; + data->data[i] = word & 0xff; + data->data[i + 1] = word >> 8; + } + } + data->last_updated[slice] = jiffies; + data->valid |= (1 << slice); + } +exit: + mutex_unlock(&data->update_lock); +} + +static ssize_t eeprom_read(struct kobject *kobj, struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct i2c_client *client = to_i2c_client(container_of(kobj, struct device, kobj)); + struct eeprom_data *data = i2c_get_clientdata(client); + u8 slice; + + if (off > EEPROM_SIZE) + return 0; + if (off + count > EEPROM_SIZE) + count = EEPROM_SIZE - off; + + /* Only refresh slices which contain requested bytes */ + for (slice = off >> 5; slice <= (off + count - 1) >> 5; slice++) + eeprom_update_client(client, slice); + + /* Hide Vaio private settings to regular users: + - BIOS passwords: bytes 0x00 to 0x0f + - UUID: bytes 0x10 to 0x1f + - Serial number: 0xc0 to 0xdf */ + if (data->nature == VAIO && !capable(CAP_SYS_ADMIN)) { + int i; + + for (i = 0; i < count; i++) { + if ((off + i <= 0x1f) || + (off + i >= 0xc0 && off + i <= 0xdf)) + buf[i] = 0; + else + buf[i] = data->data[off + i]; + } + } else { + memcpy(buf, &data->data[off], count); + } + + return count; +} + +static struct bin_attribute eeprom_attr = { + .attr = { + .name = "eeprom", + .mode = S_IRUGO, + }, + .size = EEPROM_SIZE, + .read = eeprom_read, +}; + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int eeprom_detect(struct i2c_client *client, int kind, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + + /* EDID EEPROMs are often 24C00 EEPROMs, which answer to all + addresses 0x50-0x57, but we only care about 0x50. So decline + attaching to addresses >= 0x51 on DDC buses */ + if (!(adapter->class & I2C_CLASS_SPD) && client->addr >= 0x51) + return -ENODEV; + + /* There are four ways we can read the EEPROM data: + (1) I2C block reads (faster, but unsupported by most adapters) + (2) Word reads (128% overhead) + (3) Consecutive byte reads (88% overhead, unsafe) + (4) Regular byte data reads (265% overhead) + The third and fourth methods are not implemented by this driver + because all known adapters support one of the first two. */ + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_READ_WORD_DATA) + && !i2c_check_functionality(adapter, I2C_FUNC_SMBUS_READ_I2C_BLOCK)) + return -ENODEV; + + strlcpy(info->type, "eeprom", I2C_NAME_SIZE); + + return 0; +} + +static int eeprom_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + struct eeprom_data *data; + int err; + + if (!(data = kzalloc(sizeof(struct eeprom_data), GFP_KERNEL))) { + err = -ENOMEM; + goto exit; + } + + memset(data->data, 0xff, EEPROM_SIZE); + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + data->nature = UNKNOWN; + + /* Detect the Vaio nature of EEPROMs. + We use the "PCG-" or "VGN-" prefix as the signature. */ + if (client->addr == 0x57 + && i2c_check_functionality(adapter, I2C_FUNC_SMBUS_READ_BYTE_DATA)) { + char name[4]; + + name[0] = i2c_smbus_read_byte_data(client, 0x80); + name[1] = i2c_smbus_read_byte_data(client, 0x81); + name[2] = i2c_smbus_read_byte_data(client, 0x82); + name[3] = i2c_smbus_read_byte_data(client, 0x83); + + if (!memcmp(name, "PCG-", 4) || !memcmp(name, "VGN-", 4)) { + dev_info(&client->dev, "Vaio EEPROM detected, " + "enabling privacy protection\n"); + data->nature = VAIO; + } + } + + /* create the sysfs eeprom file */ + err = sysfs_create_bin_file(&client->dev.kobj, &eeprom_attr); + if (err) + goto exit_kfree; + + return 0; + +exit_kfree: + kfree(data); +exit: + return err; +} + +static int eeprom_remove(struct i2c_client *client) +{ + sysfs_remove_bin_file(&client->dev.kobj, &eeprom_attr); + kfree(i2c_get_clientdata(client)); + + return 0; +} + +static const struct i2c_device_id eeprom_id[] = { + { "eeprom", 0 }, + { } +}; + +static struct i2c_driver eeprom_driver = { + .driver = { + .name = "eeprom", + }, + .probe = eeprom_probe, + .remove = eeprom_remove, + .id_table = eeprom_id, + + .class = I2C_CLASS_DDC | I2C_CLASS_SPD, + .detect = eeprom_detect, + .address_data = &addr_data, +}; + +static int __init eeprom_init(void) +{ + return i2c_add_driver(&eeprom_driver); +} + +static void __exit eeprom_exit(void) +{ + i2c_del_driver(&eeprom_driver); +} + + +MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl> and " + "Philip Edelbrock <phil@netroedge.com> and " + "Greg Kroah-Hartman <greg@kroah.com>"); +MODULE_DESCRIPTION("I2C EEPROM driver"); +MODULE_LICENSE("GPL"); + +module_init(eeprom_init); +module_exit(eeprom_exit); diff --git a/drivers/i2c/chips/isp1301_omap.c b/drivers/i2c/chips/isp1301_omap.c new file mode 100644 index 0000000..e0d56ef --- /dev/null +++ b/drivers/i2c/chips/isp1301_omap.c @@ -0,0 +1,1683 @@ +/* + * isp1301_omap - ISP 1301 USB transceiver, talking to OMAP OTG controller + * + * Copyright (C) 2004 Texas Instruments + * Copyright (C) 2004 David Brownell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/gpio.h> +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/usb.h> +#include <linux/usb/otg.h> +#include <linux/i2c.h> +#include <linux/workqueue.h> + +#include <asm/irq.h> +#include <asm/mach-types.h> + +#include <mach/usb.h> +#include <mach/mux.h> + + +#ifndef DEBUG +#undef VERBOSE +#endif + + +#define DRIVER_VERSION "24 August 2004" +#define DRIVER_NAME (isp1301_driver.driver.name) + +MODULE_DESCRIPTION("ISP1301 USB OTG Transceiver Driver"); +MODULE_LICENSE("GPL"); + +struct isp1301 { + struct otg_transceiver otg; + struct i2c_client *client; + void (*i2c_release)(struct device *dev); + + int irq_type; + + u32 last_otg_ctrl; + unsigned working:1; + + struct timer_list timer; + + /* use keventd context to change the state for us */ + struct work_struct work; + + unsigned long todo; +# define WORK_UPDATE_ISP 0 /* update ISP from OTG */ +# define WORK_UPDATE_OTG 1 /* update OTG from ISP */ +# define WORK_HOST_RESUME 4 /* resume host */ +# define WORK_TIMER 6 /* timer fired */ +# define WORK_STOP 7 /* don't resubmit */ +}; + + +/* bits in OTG_CTRL */ + +#define OTG_XCEIV_OUTPUTS \ + (OTG_ASESSVLD|OTG_BSESSEND|OTG_BSESSVLD|OTG_VBUSVLD|OTG_ID) +#define OTG_XCEIV_INPUTS \ + (OTG_PULLDOWN|OTG_PULLUP|OTG_DRV_VBUS|OTG_PD_VBUS|OTG_PU_VBUS|OTG_PU_ID) +#define OTG_CTRL_BITS \ + (OTG_A_BUSREQ|OTG_A_SETB_HNPEN|OTG_B_BUSREQ|OTG_B_HNPEN|OTG_BUSDROP) + /* and OTG_PULLUP is sometimes written */ + +#define OTG_CTRL_MASK (OTG_DRIVER_SEL| \ + OTG_XCEIV_OUTPUTS|OTG_XCEIV_INPUTS| \ + OTG_CTRL_BITS) + + +/*-------------------------------------------------------------------------*/ + +/* board-specific PM hooks */ + +#if defined(CONFIG_MACH_OMAP_H2) || defined(CONFIG_MACH_OMAP_H3) + +#if defined(CONFIG_TPS65010) || defined(CONFIG_TPS65010_MODULE) + +#include <linux/i2c/tps65010.h> + +#else + +static inline int tps65010_set_vbus_draw(unsigned mA) +{ + pr_debug("tps65010: draw %d mA (STUB)\n", mA); + return 0; +} + +#endif + +static void enable_vbus_draw(struct isp1301 *isp, unsigned mA) +{ + int status = tps65010_set_vbus_draw(mA); + if (status < 0) + pr_debug(" VBUS %d mA error %d\n", mA, status); +} + +static void enable_vbus_source(struct isp1301 *isp) +{ + /* this board won't supply more than 8mA vbus power. + * some boards can switch a 100ma "unit load" (or more). + */ +} + + +/* products will deliver OTG messages with LEDs, GUI, etc */ +static inline void notresponding(struct isp1301 *isp) +{ + printk(KERN_NOTICE "OTG device not responding.\n"); +} + + +#endif + +#if defined(CONFIG_MACH_OMAP_H4) + +static void enable_vbus_draw(struct isp1301 *isp, unsigned mA) +{ + /* H4 controls this by DIP switch S2.4; no soft control. + * ON means the charger is always enabled. Leave it OFF + * unless the OTG port is used only in B-peripheral mode. + */ +} + +static void enable_vbus_source(struct isp1301 *isp) +{ + /* this board won't supply more than 8mA vbus power. + * some boards can switch a 100ma "unit load" (or more). + */ +} + + +/* products will deliver OTG messages with LEDs, GUI, etc */ +static inline void notresponding(struct isp1301 *isp) +{ + printk(KERN_NOTICE "OTG device not responding.\n"); +} + + +#endif + +/*-------------------------------------------------------------------------*/ + +static struct i2c_driver isp1301_driver; + +/* smbus apis are used for portability */ + +static inline u8 +isp1301_get_u8(struct isp1301 *isp, u8 reg) +{ + return i2c_smbus_read_byte_data(isp->client, reg + 0); +} + +static inline int +isp1301_get_u16(struct isp1301 *isp, u8 reg) +{ + return i2c_smbus_read_word_data(isp->client, reg); +} + +static inline int +isp1301_set_bits(struct isp1301 *isp, u8 reg, u8 bits) +{ + return i2c_smbus_write_byte_data(isp->client, reg + 0, bits); +} + +static inline int +isp1301_clear_bits(struct isp1301 *isp, u8 reg, u8 bits) +{ + return i2c_smbus_write_byte_data(isp->client, reg + 1, bits); +} + +/*-------------------------------------------------------------------------*/ + +/* identification */ +#define ISP1301_VENDOR_ID 0x00 /* u16 read */ +#define ISP1301_PRODUCT_ID 0x02 /* u16 read */ +#define ISP1301_BCD_DEVICE 0x14 /* u16 read */ + +#define I2C_VENDOR_ID_PHILIPS 0x04cc +#define I2C_PRODUCT_ID_PHILIPS_1301 0x1301 + +/* operational registers */ +#define ISP1301_MODE_CONTROL_1 0x04 /* u8 read, set, +1 clear */ +# define MC1_SPEED (1 << 0) +# define MC1_SUSPEND (1 << 1) +# define MC1_DAT_SE0 (1 << 2) +# define MC1_TRANSPARENT (1 << 3) +# define MC1_BDIS_ACON_EN (1 << 4) +# define MC1_OE_INT_EN (1 << 5) +# define MC1_UART_EN (1 << 6) +# define MC1_MASK 0x7f +#define ISP1301_MODE_CONTROL_2 0x12 /* u8 read, set, +1 clear */ +# define MC2_GLOBAL_PWR_DN (1 << 0) +# define MC2_SPD_SUSP_CTRL (1 << 1) +# define MC2_BI_DI (1 << 2) +# define MC2_TRANSP_BDIR0 (1 << 3) +# define MC2_TRANSP_BDIR1 (1 << 4) +# define MC2_AUDIO_EN (1 << 5) +# define MC2_PSW_EN (1 << 6) +# define MC2_EN2V7 (1 << 7) +#define ISP1301_OTG_CONTROL_1 0x06 /* u8 read, set, +1 clear */ +# define OTG1_DP_PULLUP (1 << 0) +# define OTG1_DM_PULLUP (1 << 1) +# define OTG1_DP_PULLDOWN (1 << 2) +# define OTG1_DM_PULLDOWN (1 << 3) +# define OTG1_ID_PULLDOWN (1 << 4) +# define OTG1_VBUS_DRV (1 << 5) +# define OTG1_VBUS_DISCHRG (1 << 6) +# define OTG1_VBUS_CHRG (1 << 7) +#define ISP1301_OTG_STATUS 0x10 /* u8 readonly */ +# define OTG_B_SESS_END (1 << 6) +# define OTG_B_SESS_VLD (1 << 7) + +#define ISP1301_INTERRUPT_SOURCE 0x08 /* u8 read */ +#define ISP1301_INTERRUPT_LATCH 0x0A /* u8 read, set, +1 clear */ + +#define ISP1301_INTERRUPT_FALLING 0x0C /* u8 read, set, +1 clear */ +#define ISP1301_INTERRUPT_RISING 0x0E /* u8 read, set, +1 clear */ + +/* same bitfields in all interrupt registers */ +# define INTR_VBUS_VLD (1 << 0) +# define INTR_SESS_VLD (1 << 1) +# define INTR_DP_HI (1 << 2) +# define INTR_ID_GND (1 << 3) +# define INTR_DM_HI (1 << 4) +# define INTR_ID_FLOAT (1 << 5) +# define INTR_BDIS_ACON (1 << 6) +# define INTR_CR_INT (1 << 7) + +/*-------------------------------------------------------------------------*/ + +static const char *state_string(enum usb_otg_state state) +{ + switch (state) { + case OTG_STATE_A_IDLE: return "a_idle"; + case OTG_STATE_A_WAIT_VRISE: return "a_wait_vrise"; + case OTG_STATE_A_WAIT_BCON: return "a_wait_bcon"; + case OTG_STATE_A_HOST: return "a_host"; + case OTG_STATE_A_SUSPEND: return "a_suspend"; + case OTG_STATE_A_PERIPHERAL: return "a_peripheral"; + case OTG_STATE_A_WAIT_VFALL: return "a_wait_vfall"; + case OTG_STATE_A_VBUS_ERR: return "a_vbus_err"; + case OTG_STATE_B_IDLE: return "b_idle"; + case OTG_STATE_B_SRP_INIT: return "b_srp_init"; + case OTG_STATE_B_PERIPHERAL: return "b_peripheral"; + case OTG_STATE_B_WAIT_ACON: return "b_wait_acon"; + case OTG_STATE_B_HOST: return "b_host"; + default: return "UNDEFINED"; + } +} + +static inline const char *state_name(struct isp1301 *isp) +{ + return state_string(isp->otg.state); +} + +/*-------------------------------------------------------------------------*/ + +/* NOTE: some of this ISP1301 setup is specific to H2 boards; + * not everything is guarded by board-specific checks, or even using + * omap_usb_config data to deduce MC1_DAT_SE0 and MC2_BI_DI. + * + * ALSO: this currently doesn't use ISP1301 low-power modes + * while OTG is running. + */ + +static void power_down(struct isp1301 *isp) +{ + isp->otg.state = OTG_STATE_UNDEFINED; + + // isp1301_set_bits(isp, ISP1301_MODE_CONTROL_2, MC2_GLOBAL_PWR_DN); + isp1301_set_bits(isp, ISP1301_MODE_CONTROL_1, MC1_SUSPEND); + + isp1301_clear_bits(isp, ISP1301_OTG_CONTROL_1, OTG1_ID_PULLDOWN); + isp1301_clear_bits(isp, ISP1301_MODE_CONTROL_1, MC1_DAT_SE0); +} + +static void power_up(struct isp1301 *isp) +{ + // isp1301_clear_bits(isp, ISP1301_MODE_CONTROL_2, MC2_GLOBAL_PWR_DN); + isp1301_clear_bits(isp, ISP1301_MODE_CONTROL_1, MC1_SUSPEND); + + /* do this only when cpu is driving transceiver, + * so host won't see a low speed device... + */ + isp1301_set_bits(isp, ISP1301_MODE_CONTROL_1, MC1_DAT_SE0); +} + +#define NO_HOST_SUSPEND + +static int host_suspend(struct isp1301 *isp) +{ +#ifdef NO_HOST_SUSPEND + return 0; +#else + struct device *dev; + + if (!isp->otg.host) + return -ENODEV; + + /* Currently ASSUMES only the OTG port matters; + * other ports could be active... + */ + dev = isp->otg.host->controller; + return dev->driver->suspend(dev, 3, 0); +#endif +} + +static int host_resume(struct isp1301 *isp) +{ +#ifdef NO_HOST_SUSPEND + return 0; +#else + struct device *dev; + + if (!isp->otg.host) + return -ENODEV; + + dev = isp->otg.host->controller; + return dev->driver->resume(dev, 0); +#endif +} + +static int gadget_suspend(struct isp1301 *isp) +{ + isp->otg.gadget->b_hnp_enable = 0; + isp->otg.gadget->a_hnp_support = 0; + isp->otg.gadget->a_alt_hnp_support = 0; + return usb_gadget_vbus_disconnect(isp->otg.gadget); +} + +/*-------------------------------------------------------------------------*/ + +#define TIMER_MINUTES 10 +#define TIMER_JIFFIES (TIMER_MINUTES * 60 * HZ) + +/* Almost all our I2C messaging comes from a work queue's task context. + * NOTE: guaranteeing certain response times might mean we shouldn't + * share keventd's work queue; a realtime task might be safest. + */ +static void isp1301_defer_work(struct isp1301 *isp, int work) +{ + int status; + + if (isp && !test_and_set_bit(work, &isp->todo)) { + (void) get_device(&isp->client->dev); + status = schedule_work(&isp->work); + if (!status && !isp->working) + dev_vdbg(&isp->client->dev, + "work item %d may be lost\n", work); + } +} + +/* called from irq handlers */ +static void a_idle(struct isp1301 *isp, const char *tag) +{ + u32 l; + + if (isp->otg.state == OTG_STATE_A_IDLE) + return; + + isp->otg.default_a = 1; + if (isp->otg.host) { + isp->otg.host->is_b_host = 0; + host_suspend(isp); + } + if (isp->otg.gadget) { + isp->otg.gadget->is_a_peripheral = 1; + gadget_suspend(isp); + } + isp->otg.state = OTG_STATE_A_IDLE; + l = omap_readl(OTG_CTRL) & OTG_XCEIV_OUTPUTS; + omap_writel(l, OTG_CTRL); + isp->last_otg_ctrl = l; + pr_debug(" --> %s/%s\n", state_name(isp), tag); +} + +/* called from irq handlers */ +static void b_idle(struct isp1301 *isp, const char *tag) +{ + u32 l; + + if (isp->otg.state == OTG_STATE_B_IDLE) + return; + + isp->otg.default_a = 0; + if (isp->otg.host) { + isp->otg.host->is_b_host = 1; + host_suspend(isp); + } + if (isp->otg.gadget) { + isp->otg.gadget->is_a_peripheral = 0; + gadget_suspend(isp); + } + isp->otg.state = OTG_STATE_B_IDLE; + l = omap_readl(OTG_CTRL) & OTG_XCEIV_OUTPUTS; + omap_writel(l, OTG_CTRL); + isp->last_otg_ctrl = l; + pr_debug(" --> %s/%s\n", state_name(isp), tag); +} + +static void +dump_regs(struct isp1301 *isp, const char *label) +{ +#ifdef DEBUG + u8 ctrl = isp1301_get_u8(isp, ISP1301_OTG_CONTROL_1); + u8 status = isp1301_get_u8(isp, ISP1301_OTG_STATUS); + u8 src = isp1301_get_u8(isp, ISP1301_INTERRUPT_SOURCE); + + pr_debug("otg: %06x, %s %s, otg/%02x stat/%02x.%02x\n", + omap_readl(OTG_CTRL), label, state_name(isp), + ctrl, status, src); + /* mode control and irq enables don't change much */ +#endif +} + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_USB_OTG + +/* + * The OMAP OTG controller handles most of the OTG state transitions. + * + * We translate isp1301 outputs (mostly voltage comparator status) into + * OTG inputs; OTG outputs (mostly pullup/pulldown controls) and HNP state + * flags into isp1301 inputs ... and infer state transitions. + */ + +#ifdef VERBOSE + +static void check_state(struct isp1301 *isp, const char *tag) +{ + enum usb_otg_state state = OTG_STATE_UNDEFINED; + u8 fsm = omap_readw(OTG_TEST) & 0x0ff; + unsigned extra = 0; + + switch (fsm) { + + /* default-b */ + case 0x0: + state = OTG_STATE_B_IDLE; + break; + case 0x3: + case 0x7: + extra = 1; + case 0x1: + state = OTG_STATE_B_PERIPHERAL; + break; + case 0x11: + state = OTG_STATE_B_SRP_INIT; + break; + + /* extra dual-role default-b states */ + case 0x12: + case 0x13: + case 0x16: + extra = 1; + case 0x17: + state = OTG_STATE_B_WAIT_ACON; + break; + case 0x34: + state = OTG_STATE_B_HOST; + break; + + /* default-a */ + case 0x36: + state = OTG_STATE_A_IDLE; + break; + case 0x3c: + state = OTG_STATE_A_WAIT_VFALL; + break; + case 0x7d: + state = OTG_STATE_A_VBUS_ERR; + break; + case 0x9e: + case 0x9f: + extra = 1; + case 0x89: + state = OTG_STATE_A_PERIPHERAL; + break; + case 0xb7: + state = OTG_STATE_A_WAIT_VRISE; + break; + case 0xb8: + state = OTG_STATE_A_WAIT_BCON; + break; + case 0xb9: + state = OTG_STATE_A_HOST; + break; + case 0xba: + state = OTG_STATE_A_SUSPEND; + break; + default: + break; + } + if (isp->otg.state == state && !extra) + return; + pr_debug("otg: %s FSM %s/%02x, %s, %06x\n", tag, + state_string(state), fsm, state_name(isp), + omap_readl(OTG_CTRL)); +} + +#else + +static inline void check_state(struct isp1301 *isp, const char *tag) { } + +#endif + +/* outputs from ISP1301_INTERRUPT_SOURCE */ +static void update_otg1(struct isp1301 *isp, u8 int_src) +{ + u32 otg_ctrl; + + otg_ctrl = omap_readl(OTG_CTRL) & OTG_CTRL_MASK; + otg_ctrl &= ~OTG_XCEIV_INPUTS; + otg_ctrl &= ~(OTG_ID|OTG_ASESSVLD|OTG_VBUSVLD); + + if (int_src & INTR_SESS_VLD) + otg_ctrl |= OTG_ASESSVLD; + else if (isp->otg.state == OTG_STATE_A_WAIT_VFALL) { + a_idle(isp, "vfall"); + otg_ctrl &= ~OTG_CTRL_BITS; + } + if (int_src & INTR_VBUS_VLD) + otg_ctrl |= OTG_VBUSVLD; + if (int_src & INTR_ID_GND) { /* default-A */ + if (isp->otg.state == OTG_STATE_B_IDLE + || isp->otg.state == OTG_STATE_UNDEFINED) { + a_idle(isp, "init"); + return; + } + } else { /* default-B */ + otg_ctrl |= OTG_ID; + if (isp->otg.state == OTG_STATE_A_IDLE + || isp->otg.state == OTG_STATE_UNDEFINED) { + b_idle(isp, "init"); + return; + } + } + omap_writel(otg_ctrl, OTG_CTRL); +} + +/* outputs from ISP1301_OTG_STATUS */ +static void update_otg2(struct isp1301 *isp, u8 otg_status) +{ + u32 otg_ctrl; + + otg_ctrl = omap_readl(OTG_CTRL) & OTG_CTRL_MASK; + otg_ctrl &= ~OTG_XCEIV_INPUTS; + otg_ctrl &= ~(OTG_BSESSVLD | OTG_BSESSEND); + if (otg_status & OTG_B_SESS_VLD) + otg_ctrl |= OTG_BSESSVLD; + else if (otg_status & OTG_B_SESS_END) + otg_ctrl |= OTG_BSESSEND; + omap_writel(otg_ctrl, OTG_CTRL); +} + +/* inputs going to ISP1301 */ +static void otg_update_isp(struct isp1301 *isp) +{ + u32 otg_ctrl, otg_change; + u8 set = OTG1_DM_PULLDOWN, clr = OTG1_DM_PULLUP; + + otg_ctrl = omap_readl(OTG_CTRL); + otg_change = otg_ctrl ^ isp->last_otg_ctrl; + isp->last_otg_ctrl = otg_ctrl; + otg_ctrl = otg_ctrl & OTG_XCEIV_INPUTS; + + switch (isp->otg.state) { + case OTG_STATE_B_IDLE: + case OTG_STATE_B_PERIPHERAL: + case OTG_STATE_B_SRP_INIT: + if (!(otg_ctrl & OTG_PULLUP)) { + // if (otg_ctrl & OTG_B_HNPEN) { + if (isp->otg.gadget->b_hnp_enable) { + isp->otg.state = OTG_STATE_B_WAIT_ACON; + pr_debug(" --> b_wait_acon\n"); + } + goto pulldown; + } +pullup: + set |= OTG1_DP_PULLUP; + clr |= OTG1_DP_PULLDOWN; + break; + case OTG_STATE_A_SUSPEND: + case OTG_STATE_A_PERIPHERAL: + if (otg_ctrl & OTG_PULLUP) + goto pullup; + /* FALLTHROUGH */ + // case OTG_STATE_B_WAIT_ACON: + default: +pulldown: + set |= OTG1_DP_PULLDOWN; + clr |= OTG1_DP_PULLUP; + break; + } + +# define toggle(OTG,ISP) do { \ + if (otg_ctrl & OTG) set |= ISP; \ + else clr |= ISP; \ + } while (0) + + if (!(isp->otg.host)) + otg_ctrl &= ~OTG_DRV_VBUS; + + switch (isp->otg.state) { + case OTG_STATE_A_SUSPEND: + if (otg_ctrl & OTG_DRV_VBUS) { + set |= OTG1_VBUS_DRV; + break; + } + /* HNP failed for some reason (A_AIDL_BDIS timeout) */ + notresponding(isp); + + /* FALLTHROUGH */ + case OTG_STATE_A_VBUS_ERR: + isp->otg.state = OTG_STATE_A_WAIT_VFALL; + pr_debug(" --> a_wait_vfall\n"); + /* FALLTHROUGH */ + case OTG_STATE_A_WAIT_VFALL: + /* FIXME usbcore thinks port power is still on ... */ + clr |= OTG1_VBUS_DRV; + break; + case OTG_STATE_A_IDLE: + if (otg_ctrl & OTG_DRV_VBUS) { + isp->otg.state = OTG_STATE_A_WAIT_VRISE; + pr_debug(" --> a_wait_vrise\n"); + } + /* FALLTHROUGH */ + default: + toggle(OTG_DRV_VBUS, OTG1_VBUS_DRV); + } + + toggle(OTG_PU_VBUS, OTG1_VBUS_CHRG); + toggle(OTG_PD_VBUS, OTG1_VBUS_DISCHRG); + +# undef toggle + + isp1301_set_bits(isp, ISP1301_OTG_CONTROL_1, set); + isp1301_clear_bits(isp, ISP1301_OTG_CONTROL_1, clr); + + /* HNP switch to host or peripheral; and SRP */ + if (otg_change & OTG_PULLUP) { + u32 l; + + switch (isp->otg.state) { + case OTG_STATE_B_IDLE: + if (clr & OTG1_DP_PULLUP) + break; + isp->otg.state = OTG_STATE_B_PERIPHERAL; + pr_debug(" --> b_peripheral\n"); + break; + case OTG_STATE_A_SUSPEND: + if (clr & OTG1_DP_PULLUP) + break; + isp->otg.state = OTG_STATE_A_PERIPHERAL; + pr_debug(" --> a_peripheral\n"); + break; + default: + break; + } + l = omap_readl(OTG_CTRL); + l |= OTG_PULLUP; + omap_writel(l, OTG_CTRL); + } + + check_state(isp, __func__); + dump_regs(isp, "otg->isp1301"); +} + +static irqreturn_t omap_otg_irq(int irq, void *_isp) +{ + u16 otg_irq = omap_readw(OTG_IRQ_SRC); + u32 otg_ctrl; + int ret = IRQ_NONE; + struct isp1301 *isp = _isp; + + /* update ISP1301 transciever from OTG controller */ + if (otg_irq & OPRT_CHG) { + omap_writew(OPRT_CHG, OTG_IRQ_SRC); + isp1301_defer_work(isp, WORK_UPDATE_ISP); + ret = IRQ_HANDLED; + + /* SRP to become b_peripheral failed */ + } else if (otg_irq & B_SRP_TMROUT) { + pr_debug("otg: B_SRP_TIMEOUT, %06x\n", omap_readl(OTG_CTRL)); + notresponding(isp); + + /* gadget drivers that care should monitor all kinds of + * remote wakeup (SRP, normal) using their own timer + * to give "check cable and A-device" messages. + */ + if (isp->otg.state == OTG_STATE_B_SRP_INIT) + b_idle(isp, "srp_timeout"); + + omap_writew(B_SRP_TMROUT, OTG_IRQ_SRC); + ret = IRQ_HANDLED; + + /* HNP to become b_host failed */ + } else if (otg_irq & B_HNP_FAIL) { + pr_debug("otg: %s B_HNP_FAIL, %06x\n", + state_name(isp), omap_readl(OTG_CTRL)); + notresponding(isp); + + otg_ctrl = omap_readl(OTG_CTRL); + otg_ctrl |= OTG_BUSDROP; + otg_ctrl &= OTG_CTRL_MASK & ~OTG_XCEIV_INPUTS; + omap_writel(otg_ctrl, OTG_CTRL); + + /* subset of b_peripheral()... */ + isp->otg.state = OTG_STATE_B_PERIPHERAL; + pr_debug(" --> b_peripheral\n"); + + omap_writew(B_HNP_FAIL, OTG_IRQ_SRC); + ret = IRQ_HANDLED; + + /* detect SRP from B-device ... */ + } else if (otg_irq & A_SRP_DETECT) { + pr_debug("otg: %s SRP_DETECT, %06x\n", + state_name(isp), omap_readl(OTG_CTRL)); + + isp1301_defer_work(isp, WORK_UPDATE_OTG); + switch (isp->otg.state) { + case OTG_STATE_A_IDLE: + if (!isp->otg.host) + break; + isp1301_defer_work(isp, WORK_HOST_RESUME); + otg_ctrl = omap_readl(OTG_CTRL); + otg_ctrl |= OTG_A_BUSREQ; + otg_ctrl &= ~(OTG_BUSDROP|OTG_B_BUSREQ) + & ~OTG_XCEIV_INPUTS + & OTG_CTRL_MASK; + omap_writel(otg_ctrl, OTG_CTRL); + break; + default: + break; + } + + omap_writew(A_SRP_DETECT, OTG_IRQ_SRC); + ret = IRQ_HANDLED; + + /* timer expired: T(a_wait_bcon) and maybe T(a_wait_vrise) + * we don't track them separately + */ + } else if (otg_irq & A_REQ_TMROUT) { + otg_ctrl = omap_readl(OTG_CTRL); + pr_info("otg: BCON_TMOUT from %s, %06x\n", + state_name(isp), otg_ctrl); + notresponding(isp); + + otg_ctrl |= OTG_BUSDROP; + otg_ctrl &= ~OTG_A_BUSREQ & OTG_CTRL_MASK & ~OTG_XCEIV_INPUTS; + omap_writel(otg_ctrl, OTG_CTRL); + isp->otg.state = OTG_STATE_A_WAIT_VFALL; + + omap_writew(A_REQ_TMROUT, OTG_IRQ_SRC); + ret = IRQ_HANDLED; + + /* A-supplied voltage fell too low; overcurrent */ + } else if (otg_irq & A_VBUS_ERR) { + otg_ctrl = omap_readl(OTG_CTRL); + printk(KERN_ERR "otg: %s, VBUS_ERR %04x ctrl %06x\n", + state_name(isp), otg_irq, otg_ctrl); + + otg_ctrl |= OTG_BUSDROP; + otg_ctrl &= ~OTG_A_BUSREQ & OTG_CTRL_MASK & ~OTG_XCEIV_INPUTS; + omap_writel(otg_ctrl, OTG_CTRL); + isp->otg.state = OTG_STATE_A_VBUS_ERR; + + omap_writew(A_VBUS_ERR, OTG_IRQ_SRC); + ret = IRQ_HANDLED; + + /* switch driver; the transciever code activates it, + * ungating the udc clock or resuming OHCI. + */ + } else if (otg_irq & DRIVER_SWITCH) { + int kick = 0; + + otg_ctrl = omap_readl(OTG_CTRL); + printk(KERN_NOTICE "otg: %s, SWITCH to %s, ctrl %06x\n", + state_name(isp), + (otg_ctrl & OTG_DRIVER_SEL) + ? "gadget" : "host", + otg_ctrl); + isp1301_defer_work(isp, WORK_UPDATE_ISP); + + /* role is peripheral */ + if (otg_ctrl & OTG_DRIVER_SEL) { + switch (isp->otg.state) { + case OTG_STATE_A_IDLE: + b_idle(isp, __func__); + break; + default: + break; + } + isp1301_defer_work(isp, WORK_UPDATE_ISP); + + /* role is host */ + } else { + if (!(otg_ctrl & OTG_ID)) { + otg_ctrl &= OTG_CTRL_MASK & ~OTG_XCEIV_INPUTS; + omap_writel(otg_ctrl | OTG_A_BUSREQ, OTG_CTRL); + } + + if (isp->otg.host) { + switch (isp->otg.state) { + case OTG_STATE_B_WAIT_ACON: + isp->otg.state = OTG_STATE_B_HOST; + pr_debug(" --> b_host\n"); + kick = 1; + break; + case OTG_STATE_A_WAIT_BCON: + isp->otg.state = OTG_STATE_A_HOST; + pr_debug(" --> a_host\n"); + break; + case OTG_STATE_A_PERIPHERAL: + isp->otg.state = OTG_STATE_A_WAIT_BCON; + pr_debug(" --> a_wait_bcon\n"); + break; + default: + break; + } + isp1301_defer_work(isp, WORK_HOST_RESUME); + } + } + + omap_writew(DRIVER_SWITCH, OTG_IRQ_SRC); + ret = IRQ_HANDLED; + + if (kick) + usb_bus_start_enum(isp->otg.host, + isp->otg.host->otg_port); + } + + check_state(isp, __func__); + return ret; +} + +static struct platform_device *otg_dev; + +static int otg_init(struct isp1301 *isp) +{ + u32 l; + + if (!otg_dev) + return -ENODEV; + + dump_regs(isp, __func__); + /* some of these values are board-specific... */ + l = omap_readl(OTG_SYSCON_2); + l |= OTG_EN + /* for B-device: */ + | SRP_GPDATA /* 9msec Bdev D+ pulse */ + | SRP_GPDVBUS /* discharge after VBUS pulse */ + // | (3 << 24) /* 2msec VBUS pulse */ + /* for A-device: */ + | (0 << 20) /* 200ms nominal A_WAIT_VRISE timer */ + | SRP_DPW /* detect 167+ns SRP pulses */ + | SRP_DATA | SRP_VBUS /* accept both kinds of SRP pulse */ + ; + omap_writel(l, OTG_SYSCON_2); + + update_otg1(isp, isp1301_get_u8(isp, ISP1301_INTERRUPT_SOURCE)); + update_otg2(isp, isp1301_get_u8(isp, ISP1301_OTG_STATUS)); + + check_state(isp, __func__); + pr_debug("otg: %s, %s %06x\n", + state_name(isp), __func__, omap_readl(OTG_CTRL)); + + omap_writew(DRIVER_SWITCH | OPRT_CHG + | B_SRP_TMROUT | B_HNP_FAIL + | A_VBUS_ERR | A_SRP_DETECT | A_REQ_TMROUT, OTG_IRQ_EN); + + l = omap_readl(OTG_SYSCON_2); + l |= OTG_EN; + omap_writel(l, OTG_SYSCON_2); + + return 0; +} + +static int otg_probe(struct platform_device *dev) +{ + // struct omap_usb_config *config = dev->platform_data; + + otg_dev = dev; + return 0; +} + +static int otg_remove(struct platform_device *dev) +{ + otg_dev = NULL; + return 0; +} + +static struct platform_driver omap_otg_driver = { + .probe = otg_probe, + .remove = otg_remove, + .driver = { + .owner = THIS_MODULE, + .name = "omap_otg", + }, +}; + +static int otg_bind(struct isp1301 *isp) +{ + int status; + + if (otg_dev) + return -EBUSY; + + status = platform_driver_register(&omap_otg_driver); + if (status < 0) + return status; + + if (otg_dev) + status = request_irq(otg_dev->resource[1].start, omap_otg_irq, + IRQF_DISABLED, DRIVER_NAME, isp); + else + status = -ENODEV; + + if (status < 0) + platform_driver_unregister(&omap_otg_driver); + return status; +} + +static void otg_unbind(struct isp1301 *isp) +{ + if (!otg_dev) + return; + free_irq(otg_dev->resource[1].start, isp); +} + +#else + +/* OTG controller isn't clocked */ + +#endif /* CONFIG_USB_OTG */ + +/*-------------------------------------------------------------------------*/ + +static void b_peripheral(struct isp1301 *isp) +{ + u32 l; + + l = omap_readl(OTG_CTRL) & OTG_XCEIV_OUTPUTS; + omap_writel(l, OTG_CTRL); + + usb_gadget_vbus_connect(isp->otg.gadget); + +#ifdef CONFIG_USB_OTG + enable_vbus_draw(isp, 8); + otg_update_isp(isp); +#else + enable_vbus_draw(isp, 100); + /* UDC driver just set OTG_BSESSVLD */ + isp1301_set_bits(isp, ISP1301_OTG_CONTROL_1, OTG1_DP_PULLUP); + isp1301_clear_bits(isp, ISP1301_OTG_CONTROL_1, OTG1_DP_PULLDOWN); + isp->otg.state = OTG_STATE_B_PERIPHERAL; + pr_debug(" --> b_peripheral\n"); + dump_regs(isp, "2periph"); +#endif +} + +static void isp_update_otg(struct isp1301 *isp, u8 stat) +{ + u8 isp_stat, isp_bstat; + enum usb_otg_state state = isp->otg.state; + + if (stat & INTR_BDIS_ACON) + pr_debug("OTG: BDIS_ACON, %s\n", state_name(isp)); + + /* start certain state transitions right away */ + isp_stat = isp1301_get_u8(isp, ISP1301_INTERRUPT_SOURCE); + if (isp_stat & INTR_ID_GND) { + if (isp->otg.default_a) { + switch (state) { + case OTG_STATE_B_IDLE: + a_idle(isp, "idle"); + /* FALLTHROUGH */ + case OTG_STATE_A_IDLE: + enable_vbus_source(isp); + /* FALLTHROUGH */ + case OTG_STATE_A_WAIT_VRISE: + /* we skip over OTG_STATE_A_WAIT_BCON, since + * the HC will transition to A_HOST (or + * A_SUSPEND!) without our noticing except + * when HNP is used. + */ + if (isp_stat & INTR_VBUS_VLD) + isp->otg.state = OTG_STATE_A_HOST; + break; + case OTG_STATE_A_WAIT_VFALL: + if (!(isp_stat & INTR_SESS_VLD)) + a_idle(isp, "vfell"); + break; + default: + if (!(isp_stat & INTR_VBUS_VLD)) + isp->otg.state = OTG_STATE_A_VBUS_ERR; + break; + } + isp_bstat = isp1301_get_u8(isp, ISP1301_OTG_STATUS); + } else { + switch (state) { + case OTG_STATE_B_PERIPHERAL: + case OTG_STATE_B_HOST: + case OTG_STATE_B_WAIT_ACON: + usb_gadget_vbus_disconnect(isp->otg.gadget); + break; + default: + break; + } + if (state != OTG_STATE_A_IDLE) + a_idle(isp, "id"); + if (isp->otg.host && state == OTG_STATE_A_IDLE) + isp1301_defer_work(isp, WORK_HOST_RESUME); + isp_bstat = 0; + } + } else { + u32 l; + + /* if user unplugged mini-A end of cable, + * don't bypass A_WAIT_VFALL. + */ + if (isp->otg.default_a) { + switch (state) { + default: + isp->otg.state = OTG_STATE_A_WAIT_VFALL; + break; + case OTG_STATE_A_WAIT_VFALL: + state = OTG_STATE_A_IDLE; + /* khubd may take a while to notice and + * handle this disconnect, so don't go + * to B_IDLE quite yet. + */ + break; + case OTG_STATE_A_IDLE: + host_suspend(isp); + isp1301_clear_bits(isp, ISP1301_MODE_CONTROL_1, + MC1_BDIS_ACON_EN); + isp->otg.state = OTG_STATE_B_IDLE; + l = omap_readl(OTG_CTRL) & OTG_CTRL_MASK; + l &= ~OTG_CTRL_BITS; + omap_writel(l, OTG_CTRL); + break; + case OTG_STATE_B_IDLE: + break; + } + } + isp_bstat = isp1301_get_u8(isp, ISP1301_OTG_STATUS); + + switch (isp->otg.state) { + case OTG_STATE_B_PERIPHERAL: + case OTG_STATE_B_WAIT_ACON: + case OTG_STATE_B_HOST: + if (likely(isp_bstat & OTG_B_SESS_VLD)) + break; + enable_vbus_draw(isp, 0); +#ifndef CONFIG_USB_OTG + /* UDC driver will clear OTG_BSESSVLD */ + isp1301_set_bits(isp, ISP1301_OTG_CONTROL_1, + OTG1_DP_PULLDOWN); + isp1301_clear_bits(isp, ISP1301_OTG_CONTROL_1, + OTG1_DP_PULLUP); + dump_regs(isp, __func__); +#endif + /* FALLTHROUGH */ + case OTG_STATE_B_SRP_INIT: + b_idle(isp, __func__); + l = omap_readl(OTG_CTRL) & OTG_XCEIV_OUTPUTS; + omap_writel(l, OTG_CTRL); + /* FALLTHROUGH */ + case OTG_STATE_B_IDLE: + if (isp->otg.gadget && (isp_bstat & OTG_B_SESS_VLD)) { +#ifdef CONFIG_USB_OTG + update_otg1(isp, isp_stat); + update_otg2(isp, isp_bstat); +#endif + b_peripheral(isp); + } else if (!(isp_stat & (INTR_VBUS_VLD|INTR_SESS_VLD))) + isp_bstat |= OTG_B_SESS_END; + break; + case OTG_STATE_A_WAIT_VFALL: + break; + default: + pr_debug("otg: unsupported b-device %s\n", + state_name(isp)); + break; + } + } + + if (state != isp->otg.state) + pr_debug(" isp, %s -> %s\n", + state_string(state), state_name(isp)); + +#ifdef CONFIG_USB_OTG + /* update the OTG controller state to match the isp1301; may + * trigger OPRT_CHG irqs for changes going to the isp1301. + */ + update_otg1(isp, isp_stat); + update_otg2(isp, isp_bstat); + check_state(isp, __func__); +#endif + + dump_regs(isp, "isp1301->otg"); +} + +/*-------------------------------------------------------------------------*/ + +static u8 isp1301_clear_latch(struct isp1301 *isp) +{ + u8 latch = isp1301_get_u8(isp, ISP1301_INTERRUPT_LATCH); + isp1301_clear_bits(isp, ISP1301_INTERRUPT_LATCH, latch); + return latch; +} + +static void +isp1301_work(struct work_struct *work) +{ + struct isp1301 *isp = container_of(work, struct isp1301, work); + int stop; + + /* implicit lock: we're the only task using this device */ + isp->working = 1; + do { + stop = test_bit(WORK_STOP, &isp->todo); + +#ifdef CONFIG_USB_OTG + /* transfer state from otg engine to isp1301 */ + if (test_and_clear_bit(WORK_UPDATE_ISP, &isp->todo)) { + otg_update_isp(isp); + put_device(&isp->client->dev); + } +#endif + /* transfer state from isp1301 to otg engine */ + if (test_and_clear_bit(WORK_UPDATE_OTG, &isp->todo)) { + u8 stat = isp1301_clear_latch(isp); + + isp_update_otg(isp, stat); + put_device(&isp->client->dev); + } + + if (test_and_clear_bit(WORK_HOST_RESUME, &isp->todo)) { + u32 otg_ctrl; + + /* + * skip A_WAIT_VRISE; hc transitions invisibly + * skip A_WAIT_BCON; same. + */ + switch (isp->otg.state) { + case OTG_STATE_A_WAIT_BCON: + case OTG_STATE_A_WAIT_VRISE: + isp->otg.state = OTG_STATE_A_HOST; + pr_debug(" --> a_host\n"); + otg_ctrl = omap_readl(OTG_CTRL); + otg_ctrl |= OTG_A_BUSREQ; + otg_ctrl &= ~(OTG_BUSDROP|OTG_B_BUSREQ) + & OTG_CTRL_MASK; + omap_writel(otg_ctrl, OTG_CTRL); + break; + case OTG_STATE_B_WAIT_ACON: + isp->otg.state = OTG_STATE_B_HOST; + pr_debug(" --> b_host (acon)\n"); + break; + case OTG_STATE_B_HOST: + case OTG_STATE_B_IDLE: + case OTG_STATE_A_IDLE: + break; + default: + pr_debug(" host resume in %s\n", + state_name(isp)); + } + host_resume(isp); + // mdelay(10); + put_device(&isp->client->dev); + } + + if (test_and_clear_bit(WORK_TIMER, &isp->todo)) { +#ifdef VERBOSE + dump_regs(isp, "timer"); + if (!stop) + mod_timer(&isp->timer, jiffies + TIMER_JIFFIES); +#endif + put_device(&isp->client->dev); + } + + if (isp->todo) + dev_vdbg(&isp->client->dev, + "work done, todo = 0x%lx\n", + isp->todo); + if (stop) { + dev_dbg(&isp->client->dev, "stop\n"); + break; + } + } while (isp->todo); + isp->working = 0; +} + +static irqreturn_t isp1301_irq(int irq, void *isp) +{ + isp1301_defer_work(isp, WORK_UPDATE_OTG); + return IRQ_HANDLED; +} + +static void isp1301_timer(unsigned long _isp) +{ + isp1301_defer_work((void *)_isp, WORK_TIMER); +} + +/*-------------------------------------------------------------------------*/ + +static void isp1301_release(struct device *dev) +{ + struct isp1301 *isp; + + isp = dev_get_drvdata(dev); + + /* FIXME -- not with a "new style" driver, it doesn't!! */ + + /* ugly -- i2c hijacks our memory hook to wait_for_completion() */ + if (isp->i2c_release) + isp->i2c_release(dev); + kfree (isp); +} + +static struct isp1301 *the_transceiver; + +static int __exit isp1301_remove(struct i2c_client *i2c) +{ + struct isp1301 *isp; + + isp = i2c_get_clientdata(i2c); + + isp1301_clear_bits(isp, ISP1301_INTERRUPT_FALLING, ~0); + isp1301_clear_bits(isp, ISP1301_INTERRUPT_RISING, ~0); + free_irq(i2c->irq, isp); +#ifdef CONFIG_USB_OTG + otg_unbind(isp); +#endif + if (machine_is_omap_h2()) + gpio_free(2); + + isp->timer.data = 0; + set_bit(WORK_STOP, &isp->todo); + del_timer_sync(&isp->timer); + flush_scheduled_work(); + + put_device(&i2c->dev); + the_transceiver = NULL; + + return 0; +} + +/*-------------------------------------------------------------------------*/ + +/* NOTE: three modes are possible here, only one of which + * will be standards-conformant on any given system: + * + * - OTG mode (dual-role), required if there's a Mini-AB connector + * - HOST mode, for when there's one or more A (host) connectors + * - DEVICE mode, for when there's a B/Mini-B (device) connector + * + * As a rule, you won't have an isp1301 chip unless it's there to + * support the OTG mode. Other modes help testing USB controllers + * in isolation from (full) OTG support, or maybe so later board + * revisions can help to support those feature. + */ + +#ifdef CONFIG_USB_OTG + +static int isp1301_otg_enable(struct isp1301 *isp) +{ + power_up(isp); + otg_init(isp); + + /* NOTE: since we don't change this, this provides + * a few more interrupts than are strictly needed. + */ + isp1301_set_bits(isp, ISP1301_INTERRUPT_RISING, + INTR_VBUS_VLD | INTR_SESS_VLD | INTR_ID_GND); + isp1301_set_bits(isp, ISP1301_INTERRUPT_FALLING, + INTR_VBUS_VLD | INTR_SESS_VLD | INTR_ID_GND); + + dev_info(&isp->client->dev, "ready for dual-role USB ...\n"); + + return 0; +} + +#endif + +/* add or disable the host device+driver */ +static int +isp1301_set_host(struct otg_transceiver *otg, struct usb_bus *host) +{ + struct isp1301 *isp = container_of(otg, struct isp1301, otg); + + if (!otg || isp != the_transceiver) + return -ENODEV; + + if (!host) { + omap_writew(0, OTG_IRQ_EN); + power_down(isp); + isp->otg.host = NULL; + return 0; + } + +#ifdef CONFIG_USB_OTG + isp->otg.host = host; + dev_dbg(&isp->client->dev, "registered host\n"); + host_suspend(isp); + if (isp->otg.gadget) + return isp1301_otg_enable(isp); + return 0; + +#elif !defined(CONFIG_USB_GADGET_OMAP) + // FIXME update its refcount + isp->otg.host = host; + + power_up(isp); + + if (machine_is_omap_h2()) + isp1301_set_bits(isp, ISP1301_MODE_CONTROL_1, MC1_DAT_SE0); + + dev_info(&isp->client->dev, "A-Host sessions ok\n"); + isp1301_set_bits(isp, ISP1301_INTERRUPT_RISING, + INTR_ID_GND); + isp1301_set_bits(isp, ISP1301_INTERRUPT_FALLING, + INTR_ID_GND); + + /* If this has a Mini-AB connector, this mode is highly + * nonstandard ... but can be handy for testing, especially with + * the Mini-A end of an OTG cable. (Or something nonstandard + * like MiniB-to-StandardB, maybe built with a gender mender.) + */ + isp1301_set_bits(isp, ISP1301_OTG_CONTROL_1, OTG1_VBUS_DRV); + + dump_regs(isp, __func__); + + return 0; + +#else + dev_dbg(&isp->client->dev, "host sessions not allowed\n"); + return -EINVAL; +#endif + +} + +static int +isp1301_set_peripheral(struct otg_transceiver *otg, struct usb_gadget *gadget) +{ + struct isp1301 *isp = container_of(otg, struct isp1301, otg); +#ifndef CONFIG_USB_OTG + u32 l; +#endif + + if (!otg || isp != the_transceiver) + return -ENODEV; + + if (!gadget) { + omap_writew(0, OTG_IRQ_EN); + if (!isp->otg.default_a) + enable_vbus_draw(isp, 0); + usb_gadget_vbus_disconnect(isp->otg.gadget); + isp->otg.gadget = NULL; + power_down(isp); + return 0; + } + +#ifdef CONFIG_USB_OTG + isp->otg.gadget = gadget; + dev_dbg(&isp->client->dev, "registered gadget\n"); + /* gadget driver may be suspended until vbus_connect () */ + if (isp->otg.host) + return isp1301_otg_enable(isp); + return 0; + +#elif !defined(CONFIG_USB_OHCI_HCD) && !defined(CONFIG_USB_OHCI_HCD_MODULE) + isp->otg.gadget = gadget; + // FIXME update its refcount + + l = omap_readl(OTG_CTRL) & OTG_CTRL_MASK; + l &= ~(OTG_XCEIV_OUTPUTS|OTG_CTRL_BITS); + l |= OTG_ID; + omap_writel(l, OTG_CTRL); + + power_up(isp); + isp->otg.state = OTG_STATE_B_IDLE; + + if (machine_is_omap_h2() || machine_is_omap_h3()) + isp1301_set_bits(isp, ISP1301_MODE_CONTROL_1, MC1_DAT_SE0); + + isp1301_set_bits(isp, ISP1301_INTERRUPT_RISING, + INTR_SESS_VLD); + isp1301_set_bits(isp, ISP1301_INTERRUPT_FALLING, + INTR_VBUS_VLD); + dev_info(&isp->client->dev, "B-Peripheral sessions ok\n"); + dump_regs(isp, __func__); + + /* If this has a Mini-AB connector, this mode is highly + * nonstandard ... but can be handy for testing, so long + * as you don't plug a Mini-A cable into the jack. + */ + if (isp1301_get_u8(isp, ISP1301_INTERRUPT_SOURCE) & INTR_VBUS_VLD) + b_peripheral(isp); + + return 0; + +#else + dev_dbg(&isp->client->dev, "peripheral sessions not allowed\n"); + return -EINVAL; +#endif +} + + +/*-------------------------------------------------------------------------*/ + +static int +isp1301_set_power(struct otg_transceiver *dev, unsigned mA) +{ + if (!the_transceiver) + return -ENODEV; + if (dev->state == OTG_STATE_B_PERIPHERAL) + enable_vbus_draw(the_transceiver, mA); + return 0; +} + +static int +isp1301_start_srp(struct otg_transceiver *dev) +{ + struct isp1301 *isp = container_of(dev, struct isp1301, otg); + u32 otg_ctrl; + + if (!dev || isp != the_transceiver + || isp->otg.state != OTG_STATE_B_IDLE) + return -ENODEV; + + otg_ctrl = omap_readl(OTG_CTRL); + if (!(otg_ctrl & OTG_BSESSEND)) + return -EINVAL; + + otg_ctrl |= OTG_B_BUSREQ; + otg_ctrl &= ~OTG_A_BUSREQ & OTG_CTRL_MASK; + omap_writel(otg_ctrl, OTG_CTRL); + isp->otg.state = OTG_STATE_B_SRP_INIT; + + pr_debug("otg: SRP, %s ... %06x\n", state_name(isp), + omap_readl(OTG_CTRL)); +#ifdef CONFIG_USB_OTG + check_state(isp, __func__); +#endif + return 0; +} + +static int +isp1301_start_hnp(struct otg_transceiver *dev) +{ +#ifdef CONFIG_USB_OTG + struct isp1301 *isp = container_of(dev, struct isp1301, otg); + u32 l; + + if (!dev || isp != the_transceiver) + return -ENODEV; + if (isp->otg.default_a && (isp->otg.host == NULL + || !isp->otg.host->b_hnp_enable)) + return -ENOTCONN; + if (!isp->otg.default_a && (isp->otg.gadget == NULL + || !isp->otg.gadget->b_hnp_enable)) + return -ENOTCONN; + + /* We want hardware to manage most HNP protocol timings. + * So do this part as early as possible... + */ + switch (isp->otg.state) { + case OTG_STATE_B_HOST: + isp->otg.state = OTG_STATE_B_PERIPHERAL; + /* caller will suspend next */ + break; + case OTG_STATE_A_HOST: +#if 0 + /* autoconnect mode avoids irq latency bugs */ + isp1301_set_bits(isp, ISP1301_MODE_CONTROL_1, + MC1_BDIS_ACON_EN); +#endif + /* caller must suspend then clear A_BUSREQ */ + usb_gadget_vbus_connect(isp->otg.gadget); + l = omap_readl(OTG_CTRL); + l |= OTG_A_SETB_HNPEN; + omap_writel(l, OTG_CTRL); + + break; + case OTG_STATE_A_PERIPHERAL: + /* initiated by B-Host suspend */ + break; + default: + return -EILSEQ; + } + pr_debug("otg: HNP %s, %06x ...\n", + state_name(isp), omap_readl(OTG_CTRL)); + check_state(isp, __func__); + return 0; +#else + /* srp-only */ + return -EINVAL; +#endif +} + +/*-------------------------------------------------------------------------*/ + +static int __init +isp1301_probe(struct i2c_client *i2c, const struct i2c_device_id *id) +{ + int status; + struct isp1301 *isp; + + if (the_transceiver) + return 0; + + isp = kzalloc(sizeof *isp, GFP_KERNEL); + if (!isp) + return 0; + + INIT_WORK(&isp->work, isp1301_work); + init_timer(&isp->timer); + isp->timer.function = isp1301_timer; + isp->timer.data = (unsigned long) isp; + + i2c_set_clientdata(i2c, isp); + isp->client = i2c; + + /* verify the chip (shouldn't be necesary) */ + status = isp1301_get_u16(isp, ISP1301_VENDOR_ID); + if (status != I2C_VENDOR_ID_PHILIPS) { + dev_dbg(&i2c->dev, "not philips id: %d\n", status); + goto fail; + } + status = isp1301_get_u16(isp, ISP1301_PRODUCT_ID); + if (status != I2C_PRODUCT_ID_PHILIPS_1301) { + dev_dbg(&i2c->dev, "not isp1301, %d\n", status); + goto fail; + } + isp->i2c_release = i2c->dev.release; + i2c->dev.release = isp1301_release; + + /* initial development used chiprev 2.00 */ + status = i2c_smbus_read_word_data(i2c, ISP1301_BCD_DEVICE); + dev_info(&i2c->dev, "chiprev %x.%02x, driver " DRIVER_VERSION "\n", + status >> 8, status & 0xff); + + /* make like power-on reset */ + isp1301_clear_bits(isp, ISP1301_MODE_CONTROL_1, MC1_MASK); + + isp1301_set_bits(isp, ISP1301_MODE_CONTROL_2, MC2_BI_DI); + isp1301_clear_bits(isp, ISP1301_MODE_CONTROL_2, ~MC2_BI_DI); + + isp1301_set_bits(isp, ISP1301_OTG_CONTROL_1, + OTG1_DM_PULLDOWN | OTG1_DP_PULLDOWN); + isp1301_clear_bits(isp, ISP1301_OTG_CONTROL_1, + ~(OTG1_DM_PULLDOWN | OTG1_DP_PULLDOWN)); + + isp1301_clear_bits(isp, ISP1301_INTERRUPT_LATCH, ~0); + isp1301_clear_bits(isp, ISP1301_INTERRUPT_FALLING, ~0); + isp1301_clear_bits(isp, ISP1301_INTERRUPT_RISING, ~0); + +#ifdef CONFIG_USB_OTG + status = otg_bind(isp); + if (status < 0) { + dev_dbg(&i2c->dev, "can't bind OTG\n"); + goto fail; + } +#endif + + if (machine_is_omap_h2()) { + /* full speed signaling by default */ + isp1301_set_bits(isp, ISP1301_MODE_CONTROL_1, + MC1_SPEED); + isp1301_set_bits(isp, ISP1301_MODE_CONTROL_2, + MC2_SPD_SUSP_CTRL); + + /* IRQ wired at M14 */ + omap_cfg_reg(M14_1510_GPIO2); + if (gpio_request(2, "isp1301") == 0) + gpio_direction_input(2); + isp->irq_type = IRQF_TRIGGER_FALLING; + } + + isp->irq_type |= IRQF_SAMPLE_RANDOM; + status = request_irq(i2c->irq, isp1301_irq, + isp->irq_type, DRIVER_NAME, isp); + if (status < 0) { + dev_dbg(&i2c->dev, "can't get IRQ %d, err %d\n", + i2c->irq, status); + goto fail; + } + + isp->otg.dev = &i2c->dev; + isp->otg.label = DRIVER_NAME; + + isp->otg.set_host = isp1301_set_host, + isp->otg.set_peripheral = isp1301_set_peripheral, + isp->otg.set_power = isp1301_set_power, + isp->otg.start_srp = isp1301_start_srp, + isp->otg.start_hnp = isp1301_start_hnp, + + enable_vbus_draw(isp, 0); + power_down(isp); + the_transceiver = isp; + +#ifdef CONFIG_USB_OTG + update_otg1(isp, isp1301_get_u8(isp, ISP1301_INTERRUPT_SOURCE)); + update_otg2(isp, isp1301_get_u8(isp, ISP1301_OTG_STATUS)); +#endif + + dump_regs(isp, __func__); + +#ifdef VERBOSE + mod_timer(&isp->timer, jiffies + TIMER_JIFFIES); + dev_dbg(&i2c->dev, "scheduled timer, %d min\n", TIMER_MINUTES); +#endif + + status = otg_set_transceiver(&isp->otg); + if (status < 0) + dev_err(&i2c->dev, "can't register transceiver, %d\n", + status); + + return 0; + +fail: + kfree(isp); + return -ENODEV; +} + +static const struct i2c_device_id isp1301_id[] = { + { "isp1301_omap", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, isp1301_id); + +static struct i2c_driver isp1301_driver = { + .driver = { + .name = "isp1301_omap", + }, + .probe = isp1301_probe, + .remove = __exit_p(isp1301_remove), + .id_table = isp1301_id, +}; + +/*-------------------------------------------------------------------------*/ + +static int __init isp_init(void) +{ + return i2c_add_driver(&isp1301_driver); +} +module_init(isp_init); + +static void __exit isp_exit(void) +{ + if (the_transceiver) + otg_set_transceiver(NULL); + i2c_del_driver(&isp1301_driver); +} +module_exit(isp_exit); + diff --git a/drivers/i2c/chips/max6875.c b/drivers/i2c/chips/max6875.c new file mode 100644 index 0000000..033d9d8 --- /dev/null +++ b/drivers/i2c/chips/max6875.c @@ -0,0 +1,246 @@ +/* + max6875.c - driver for MAX6874/MAX6875 + + Copyright (C) 2005 Ben Gardner <bgardner@wabtec.com> + + Based on i2c/chips/eeprom.c + + The MAX6875 has a bank of registers and two banks of EEPROM. + Address ranges are defined as follows: + * 0x0000 - 0x0046 = configuration registers + * 0x8000 - 0x8046 = configuration EEPROM + * 0x8100 - 0x82FF = user EEPROM + + This driver makes the user EEPROM available for read. + + The registers & config EEPROM should be accessed via i2c-dev. + + The MAX6875 ignores the lowest address bit, so each chip responds to + two addresses - 0x50/0x51 and 0x52/0x53. + + Note that the MAX6875 uses i2c_smbus_write_byte_data() to set the read + address, so this driver is destructive if loaded for the wrong EEPROM chip. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. +*/ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/mutex.h> + +/* Do not scan - the MAX6875 access method will write to some EEPROM chips */ +static const unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +/* Insmod parameters */ +I2C_CLIENT_INSMOD_1(max6875); + +/* The MAX6875 can only read/write 16 bytes at a time */ +#define SLICE_SIZE 16 +#define SLICE_BITS 4 + +/* USER EEPROM is at addresses 0x8100 - 0x82FF */ +#define USER_EEPROM_BASE 0x8100 +#define USER_EEPROM_SIZE 0x0200 +#define USER_EEPROM_SLICES 32 + +/* MAX6875 commands */ +#define MAX6875_CMD_BLK_READ 0x84 + +/* Each client has this additional data */ +struct max6875_data { + struct i2c_client *fake_client; + struct mutex update_lock; + + u32 valid; + u8 data[USER_EEPROM_SIZE]; + unsigned long last_updated[USER_EEPROM_SLICES]; +}; + +static void max6875_update_slice(struct i2c_client *client, int slice) +{ + struct max6875_data *data = i2c_get_clientdata(client); + int i, j, addr; + u8 *buf; + + if (slice >= USER_EEPROM_SLICES) + return; + + mutex_lock(&data->update_lock); + + buf = &data->data[slice << SLICE_BITS]; + + if (!(data->valid & (1 << slice)) || + time_after(jiffies, data->last_updated[slice])) { + + dev_dbg(&client->dev, "Starting update of slice %u\n", slice); + + data->valid &= ~(1 << slice); + + addr = USER_EEPROM_BASE + (slice << SLICE_BITS); + + /* select the eeprom address */ + if (i2c_smbus_write_byte_data(client, addr >> 8, addr & 0xFF)) { + dev_err(&client->dev, "address set failed\n"); + goto exit_up; + } + + if (i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { + if (i2c_smbus_read_i2c_block_data(client, + MAX6875_CMD_BLK_READ, + SLICE_SIZE, + buf) != SLICE_SIZE) { + goto exit_up; + } + } else { + for (i = 0; i < SLICE_SIZE; i++) { + j = i2c_smbus_read_byte(client); + if (j < 0) { + goto exit_up; + } + buf[i] = j; + } + } + data->last_updated[slice] = jiffies; + data->valid |= (1 << slice); + } +exit_up: + mutex_unlock(&data->update_lock); +} + +static ssize_t max6875_read(struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct i2c_client *client = kobj_to_i2c_client(kobj); + struct max6875_data *data = i2c_get_clientdata(client); + int slice, max_slice; + + if (off > USER_EEPROM_SIZE) + return 0; + + if (off + count > USER_EEPROM_SIZE) + count = USER_EEPROM_SIZE - off; + + /* refresh slices which contain requested bytes */ + max_slice = (off + count - 1) >> SLICE_BITS; + for (slice = (off >> SLICE_BITS); slice <= max_slice; slice++) + max6875_update_slice(client, slice); + + memcpy(buf, &data->data[off], count); + + return count; +} + +static struct bin_attribute user_eeprom_attr = { + .attr = { + .name = "eeprom", + .mode = S_IRUGO, + }, + .size = USER_EEPROM_SIZE, + .read = max6875_read, +}; + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int max6875_detect(struct i2c_client *client, int kind, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WRITE_BYTE_DATA + | I2C_FUNC_SMBUS_READ_BYTE)) + return -ENODEV; + + /* Only check even addresses */ + if (client->addr & 1) + return -ENODEV; + + strlcpy(info->type, "max6875", I2C_NAME_SIZE); + + return 0; +} + +static int max6875_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct max6875_data *data; + int err; + + if (!(data = kzalloc(sizeof(struct max6875_data), GFP_KERNEL))) + return -ENOMEM; + + /* A fake client is created on the odd address */ + data->fake_client = i2c_new_dummy(client->adapter, client->addr + 1); + if (!data->fake_client) { + err = -ENOMEM; + goto exit_kfree; + } + + /* Init real i2c_client */ + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + err = sysfs_create_bin_file(&client->dev.kobj, &user_eeprom_attr); + if (err) + goto exit_remove_fake; + + return 0; + +exit_remove_fake: + i2c_unregister_device(data->fake_client); +exit_kfree: + kfree(data); + return err; +} + +static int max6875_remove(struct i2c_client *client) +{ + struct max6875_data *data = i2c_get_clientdata(client); + + i2c_unregister_device(data->fake_client); + + sysfs_remove_bin_file(&client->dev.kobj, &user_eeprom_attr); + kfree(data); + + return 0; +} + +static const struct i2c_device_id max6875_id[] = { + { "max6875", 0 }, + { } +}; + +static struct i2c_driver max6875_driver = { + .driver = { + .name = "max6875", + }, + .probe = max6875_probe, + .remove = max6875_remove, + .id_table = max6875_id, + + .detect = max6875_detect, + .address_data = &addr_data, +}; + +static int __init max6875_init(void) +{ + return i2c_add_driver(&max6875_driver); +} + +static void __exit max6875_exit(void) +{ + i2c_del_driver(&max6875_driver); +} + + +MODULE_AUTHOR("Ben Gardner <bgardner@wabtec.com>"); +MODULE_DESCRIPTION("MAX6875 driver"); +MODULE_LICENSE("GPL"); + +module_init(max6875_init); +module_exit(max6875_exit); diff --git a/drivers/i2c/chips/mcu_mpc8349emitx.c b/drivers/i2c/chips/mcu_mpc8349emitx.c new file mode 100644 index 0000000..82a9bcb --- /dev/null +++ b/drivers/i2c/chips/mcu_mpc8349emitx.c @@ -0,0 +1,209 @@ +/* + * Power Management and GPIO expander driver for MPC8349E-mITX-compatible MCU + * + * Copyright (c) 2008 MontaVista Software, Inc. + * + * Author: Anton Vorontsov <avorontsov@ru.mvista.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/mutex.h> +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <asm/prom.h> +#include <asm/machdep.h> + +/* + * I don't have specifications for the MCU firmware, I found this register + * and bits positions by the trial&error method. + */ +#define MCU_REG_CTRL 0x20 +#define MCU_CTRL_POFF 0x40 + +#define MCU_NUM_GPIO 2 + +struct mcu { + struct mutex lock; + struct device_node *np; + struct i2c_client *client; + struct of_gpio_chip of_gc; + u8 reg_ctrl; +}; + +static struct mcu *glob_mcu; + +static void mcu_power_off(void) +{ + struct mcu *mcu = glob_mcu; + + pr_info("Sending power-off request to the MCU...\n"); + mutex_lock(&mcu->lock); + i2c_smbus_write_byte_data(glob_mcu->client, MCU_REG_CTRL, + mcu->reg_ctrl | MCU_CTRL_POFF); + mutex_unlock(&mcu->lock); +} + +static void mcu_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val) +{ + struct of_gpio_chip *of_gc = to_of_gpio_chip(gc); + struct mcu *mcu = container_of(of_gc, struct mcu, of_gc); + u8 bit = 1 << (4 + gpio); + + mutex_lock(&mcu->lock); + if (val) + mcu->reg_ctrl &= ~bit; + else + mcu->reg_ctrl |= bit; + + i2c_smbus_write_byte_data(mcu->client, MCU_REG_CTRL, mcu->reg_ctrl); + mutex_unlock(&mcu->lock); +} + +static int mcu_gpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val) +{ + mcu_gpio_set(gc, gpio, val); + return 0; +} + +static int mcu_gpiochip_add(struct mcu *mcu) +{ + struct device_node *np; + struct of_gpio_chip *of_gc = &mcu->of_gc; + struct gpio_chip *gc = &of_gc->gc; + int ret; + + np = of_find_compatible_node(NULL, NULL, "fsl,mcu-mpc8349emitx"); + if (!np) + return -ENODEV; + + gc->owner = THIS_MODULE; + gc->label = np->full_name; + gc->can_sleep = 1; + gc->ngpio = MCU_NUM_GPIO; + gc->base = -1; + gc->set = mcu_gpio_set; + gc->direction_output = mcu_gpio_dir_out; + of_gc->gpio_cells = 2; + of_gc->xlate = of_gpio_simple_xlate; + + np->data = of_gc; + mcu->np = np; + + /* + * We don't want to lose the node, its ->data and ->full_name... + * So, if succeeded, we don't put the node here. + */ + ret = gpiochip_add(gc); + if (ret) + of_node_put(np); + return ret; +} + +static int mcu_gpiochip_remove(struct mcu *mcu) +{ + int ret; + + ret = gpiochip_remove(&mcu->of_gc.gc); + if (ret) + return ret; + of_node_put(mcu->np); + + return 0; +} + +static int __devinit mcu_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct mcu *mcu; + int ret; + + mcu = kzalloc(sizeof(*mcu), GFP_KERNEL); + if (!mcu) + return -ENOMEM; + + mutex_init(&mcu->lock); + mcu->client = client; + i2c_set_clientdata(client, mcu); + + ret = i2c_smbus_read_byte_data(mcu->client, MCU_REG_CTRL); + if (ret < 0) + goto err; + mcu->reg_ctrl = ret; + + ret = mcu_gpiochip_add(mcu); + if (ret) + goto err; + + /* XXX: this is potentially racy, but there is no lock for ppc_md */ + if (!ppc_md.power_off) { + glob_mcu = mcu; + ppc_md.power_off = mcu_power_off; + dev_info(&client->dev, "will provide power-off service\n"); + } + + return 0; +err: + kfree(mcu); + return ret; +} + +static int __devexit mcu_remove(struct i2c_client *client) +{ + struct mcu *mcu = i2c_get_clientdata(client); + int ret; + + if (glob_mcu == mcu) { + ppc_md.power_off = NULL; + glob_mcu = NULL; + } + + ret = mcu_gpiochip_remove(mcu); + if (ret) + return ret; + i2c_set_clientdata(client, NULL); + kfree(mcu); + return 0; +} + +static const struct i2c_device_id mcu_ids[] = { + { "mcu-mpc8349emitx", }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, mcu_ids); + +static struct i2c_driver mcu_driver = { + .driver = { + .name = "mcu-mpc8349emitx", + .owner = THIS_MODULE, + }, + .probe = mcu_probe, + .remove = __devexit_p(mcu_remove), + .id_table = mcu_ids, +}; + +static int __init mcu_init(void) +{ + return i2c_add_driver(&mcu_driver); +} +module_init(mcu_init); + +static void __exit mcu_exit(void) +{ + i2c_del_driver(&mcu_driver); +} +module_exit(mcu_exit); + +MODULE_DESCRIPTION("Power Management and GPIO expander driver for " + "MPC8349E-mITX-compatible MCU"); +MODULE_AUTHOR("Anton Vorontsov <avorontsov@ru.mvista.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/chips/menelaus.c b/drivers/i2c/chips/menelaus.c new file mode 100644 index 0000000..4b364ba --- /dev/null +++ b/drivers/i2c/chips/menelaus.c @@ -0,0 +1,1285 @@ +/* + * Copyright (C) 2004 Texas Instruments, Inc. + * + * Some parts based tps65010.c: + * Copyright (C) 2004 Texas Instruments and + * Copyright (C) 2004-2005 David Brownell + * + * Some parts based on tlv320aic24.c: + * Copyright (C) by Kai Svahn <kai.svahn@nokia.com> + * + * Changes for interrupt handling and clean-up by + * Tony Lindgren <tony@atomide.com> and Imre Deak <imre.deak@nokia.com> + * Cleanup and generalized support for voltage setting by + * Juha Yrjola + * Added support for controlling VCORE and regulator sleep states, + * Amit Kucheria <amit.kucheria@nokia.com> + * Copyright (C) 2005, 2006 Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/sched.h> +#include <linux/mutex.h> +#include <linux/workqueue.h> +#include <linux/delay.h> +#include <linux/rtc.h> +#include <linux/bcd.h> + +#include <asm/mach/irq.h> + +#include <mach/gpio.h> +#include <mach/menelaus.h> + +#define DRIVER_NAME "menelaus" + +#define MENELAUS_I2C_ADDRESS 0x72 + +#define MENELAUS_REV 0x01 +#define MENELAUS_VCORE_CTRL1 0x02 +#define MENELAUS_VCORE_CTRL2 0x03 +#define MENELAUS_VCORE_CTRL3 0x04 +#define MENELAUS_VCORE_CTRL4 0x05 +#define MENELAUS_VCORE_CTRL5 0x06 +#define MENELAUS_DCDC_CTRL1 0x07 +#define MENELAUS_DCDC_CTRL2 0x08 +#define MENELAUS_DCDC_CTRL3 0x09 +#define MENELAUS_LDO_CTRL1 0x0A +#define MENELAUS_LDO_CTRL2 0x0B +#define MENELAUS_LDO_CTRL3 0x0C +#define MENELAUS_LDO_CTRL4 0x0D +#define MENELAUS_LDO_CTRL5 0x0E +#define MENELAUS_LDO_CTRL6 0x0F +#define MENELAUS_LDO_CTRL7 0x10 +#define MENELAUS_LDO_CTRL8 0x11 +#define MENELAUS_SLEEP_CTRL1 0x12 +#define MENELAUS_SLEEP_CTRL2 0x13 +#define MENELAUS_DEVICE_OFF 0x14 +#define MENELAUS_OSC_CTRL 0x15 +#define MENELAUS_DETECT_CTRL 0x16 +#define MENELAUS_INT_MASK1 0x17 +#define MENELAUS_INT_MASK2 0x18 +#define MENELAUS_INT_STATUS1 0x19 +#define MENELAUS_INT_STATUS2 0x1A +#define MENELAUS_INT_ACK1 0x1B +#define MENELAUS_INT_ACK2 0x1C +#define MENELAUS_GPIO_CTRL 0x1D +#define MENELAUS_GPIO_IN 0x1E +#define MENELAUS_GPIO_OUT 0x1F +#define MENELAUS_BBSMS 0x20 +#define MENELAUS_RTC_CTRL 0x21 +#define MENELAUS_RTC_UPDATE 0x22 +#define MENELAUS_RTC_SEC 0x23 +#define MENELAUS_RTC_MIN 0x24 +#define MENELAUS_RTC_HR 0x25 +#define MENELAUS_RTC_DAY 0x26 +#define MENELAUS_RTC_MON 0x27 +#define MENELAUS_RTC_YR 0x28 +#define MENELAUS_RTC_WKDAY 0x29 +#define MENELAUS_RTC_AL_SEC 0x2A +#define MENELAUS_RTC_AL_MIN 0x2B +#define MENELAUS_RTC_AL_HR 0x2C +#define MENELAUS_RTC_AL_DAY 0x2D +#define MENELAUS_RTC_AL_MON 0x2E +#define MENELAUS_RTC_AL_YR 0x2F +#define MENELAUS_RTC_COMP_MSB 0x30 +#define MENELAUS_RTC_COMP_LSB 0x31 +#define MENELAUS_S1_PULL_EN 0x32 +#define MENELAUS_S1_PULL_DIR 0x33 +#define MENELAUS_S2_PULL_EN 0x34 +#define MENELAUS_S2_PULL_DIR 0x35 +#define MENELAUS_MCT_CTRL1 0x36 +#define MENELAUS_MCT_CTRL2 0x37 +#define MENELAUS_MCT_CTRL3 0x38 +#define MENELAUS_MCT_PIN_ST 0x39 +#define MENELAUS_DEBOUNCE1 0x3A + +#define IH_MENELAUS_IRQS 12 +#define MENELAUS_MMC_S1CD_IRQ 0 /* MMC slot 1 card change */ +#define MENELAUS_MMC_S2CD_IRQ 1 /* MMC slot 2 card change */ +#define MENELAUS_MMC_S1D1_IRQ 2 /* MMC DAT1 low in slot 1 */ +#define MENELAUS_MMC_S2D1_IRQ 3 /* MMC DAT1 low in slot 2 */ +#define MENELAUS_LOWBAT_IRQ 4 /* Low battery */ +#define MENELAUS_HOTDIE_IRQ 5 /* Hot die detect */ +#define MENELAUS_UVLO_IRQ 6 /* UVLO detect */ +#define MENELAUS_TSHUT_IRQ 7 /* Thermal shutdown */ +#define MENELAUS_RTCTMR_IRQ 8 /* RTC timer */ +#define MENELAUS_RTCALM_IRQ 9 /* RTC alarm */ +#define MENELAUS_RTCERR_IRQ 10 /* RTC error */ +#define MENELAUS_PSHBTN_IRQ 11 /* Push button */ +#define MENELAUS_RESERVED12_IRQ 12 /* Reserved */ +#define MENELAUS_RESERVED13_IRQ 13 /* Reserved */ +#define MENELAUS_RESERVED14_IRQ 14 /* Reserved */ +#define MENELAUS_RESERVED15_IRQ 15 /* Reserved */ + +static void menelaus_work(struct work_struct *_menelaus); + +struct menelaus_chip { + struct mutex lock; + struct i2c_client *client; + struct work_struct work; +#ifdef CONFIG_RTC_DRV_TWL92330 + struct rtc_device *rtc; + u8 rtc_control; + unsigned uie:1; +#endif + unsigned vcore_hw_mode:1; + u8 mask1, mask2; + void (*handlers[16])(struct menelaus_chip *); + void (*mmc_callback)(void *data, u8 mask); + void *mmc_callback_data; +}; + +static struct menelaus_chip *the_menelaus; + +static int menelaus_write_reg(int reg, u8 value) +{ + int val = i2c_smbus_write_byte_data(the_menelaus->client, reg, value); + + if (val < 0) { + pr_err(DRIVER_NAME ": write error"); + return val; + } + + return 0; +} + +static int menelaus_read_reg(int reg) +{ + int val = i2c_smbus_read_byte_data(the_menelaus->client, reg); + + if (val < 0) + pr_err(DRIVER_NAME ": read error"); + + return val; +} + +static int menelaus_enable_irq(int irq) +{ + if (irq > 7) { + irq -= 8; + the_menelaus->mask2 &= ~(1 << irq); + return menelaus_write_reg(MENELAUS_INT_MASK2, + the_menelaus->mask2); + } else { + the_menelaus->mask1 &= ~(1 << irq); + return menelaus_write_reg(MENELAUS_INT_MASK1, + the_menelaus->mask1); + } +} + +static int menelaus_disable_irq(int irq) +{ + if (irq > 7) { + irq -= 8; + the_menelaus->mask2 |= (1 << irq); + return menelaus_write_reg(MENELAUS_INT_MASK2, + the_menelaus->mask2); + } else { + the_menelaus->mask1 |= (1 << irq); + return menelaus_write_reg(MENELAUS_INT_MASK1, + the_menelaus->mask1); + } +} + +static int menelaus_ack_irq(int irq) +{ + if (irq > 7) + return menelaus_write_reg(MENELAUS_INT_ACK2, 1 << (irq - 8)); + else + return menelaus_write_reg(MENELAUS_INT_ACK1, 1 << irq); +} + +/* Adds a handler for an interrupt. Does not run in interrupt context */ +static int menelaus_add_irq_work(int irq, + void (*handler)(struct menelaus_chip *)) +{ + int ret = 0; + + mutex_lock(&the_menelaus->lock); + the_menelaus->handlers[irq] = handler; + ret = menelaus_enable_irq(irq); + mutex_unlock(&the_menelaus->lock); + + return ret; +} + +/* Removes handler for an interrupt */ +static int menelaus_remove_irq_work(int irq) +{ + int ret = 0; + + mutex_lock(&the_menelaus->lock); + ret = menelaus_disable_irq(irq); + the_menelaus->handlers[irq] = NULL; + mutex_unlock(&the_menelaus->lock); + + return ret; +} + +/* + * Gets scheduled when a card detect interrupt happens. Note that in some cases + * this line is wired to card cover switch rather than the card detect switch + * in each slot. In this case the cards are not seen by menelaus. + * FIXME: Add handling for D1 too + */ +static void menelaus_mmc_cd_work(struct menelaus_chip *menelaus_hw) +{ + int reg; + unsigned char card_mask = 0; + + reg = menelaus_read_reg(MENELAUS_MCT_PIN_ST); + if (reg < 0) + return; + + if (!(reg & 0x1)) + card_mask |= (1 << 0); + + if (!(reg & 0x2)) + card_mask |= (1 << 1); + + if (menelaus_hw->mmc_callback) + menelaus_hw->mmc_callback(menelaus_hw->mmc_callback_data, + card_mask); +} + +/* + * Toggles the MMC slots between open-drain and push-pull mode. + */ +int menelaus_set_mmc_opendrain(int slot, int enable) +{ + int ret, val; + + if (slot != 1 && slot != 2) + return -EINVAL; + mutex_lock(&the_menelaus->lock); + ret = menelaus_read_reg(MENELAUS_MCT_CTRL1); + if (ret < 0) { + mutex_unlock(&the_menelaus->lock); + return ret; + } + val = ret; + if (slot == 1) { + if (enable) + val |= 1 << 2; + else + val &= ~(1 << 2); + } else { + if (enable) + val |= 1 << 3; + else + val &= ~(1 << 3); + } + ret = menelaus_write_reg(MENELAUS_MCT_CTRL1, val); + mutex_unlock(&the_menelaus->lock); + + return ret; +} +EXPORT_SYMBOL(menelaus_set_mmc_opendrain); + +int menelaus_set_slot_sel(int enable) +{ + int ret; + + mutex_lock(&the_menelaus->lock); + ret = menelaus_read_reg(MENELAUS_GPIO_CTRL); + if (ret < 0) + goto out; + ret |= 0x02; + if (enable) + ret |= 1 << 5; + else + ret &= ~(1 << 5); + ret = menelaus_write_reg(MENELAUS_GPIO_CTRL, ret); +out: + mutex_unlock(&the_menelaus->lock); + return ret; +} +EXPORT_SYMBOL(menelaus_set_slot_sel); + +int menelaus_set_mmc_slot(int slot, int enable, int power, int cd_en) +{ + int ret, val; + + if (slot != 1 && slot != 2) + return -EINVAL; + if (power >= 3) + return -EINVAL; + + mutex_lock(&the_menelaus->lock); + + ret = menelaus_read_reg(MENELAUS_MCT_CTRL2); + if (ret < 0) + goto out; + val = ret; + if (slot == 1) { + if (cd_en) + val |= (1 << 4) | (1 << 6); + else + val &= ~((1 << 4) | (1 << 6)); + } else { + if (cd_en) + val |= (1 << 5) | (1 << 7); + else + val &= ~((1 << 5) | (1 << 7)); + } + ret = menelaus_write_reg(MENELAUS_MCT_CTRL2, val); + if (ret < 0) + goto out; + + ret = menelaus_read_reg(MENELAUS_MCT_CTRL3); + if (ret < 0) + goto out; + val = ret; + if (slot == 1) { + if (enable) + val |= 1 << 0; + else + val &= ~(1 << 0); + } else { + int b; + + if (enable) + ret |= 1 << 1; + else + ret &= ~(1 << 1); + b = menelaus_read_reg(MENELAUS_MCT_CTRL2); + b &= ~0x03; + b |= power; + ret = menelaus_write_reg(MENELAUS_MCT_CTRL2, b); + if (ret < 0) + goto out; + } + /* Disable autonomous shutdown */ + val &= ~(0x03 << 2); + ret = menelaus_write_reg(MENELAUS_MCT_CTRL3, val); +out: + mutex_unlock(&the_menelaus->lock); + return ret; +} +EXPORT_SYMBOL(menelaus_set_mmc_slot); + +int menelaus_register_mmc_callback(void (*callback)(void *data, u8 card_mask), + void *data) +{ + int ret = 0; + + the_menelaus->mmc_callback_data = data; + the_menelaus->mmc_callback = callback; + ret = menelaus_add_irq_work(MENELAUS_MMC_S1CD_IRQ, + menelaus_mmc_cd_work); + if (ret < 0) + return ret; + ret = menelaus_add_irq_work(MENELAUS_MMC_S2CD_IRQ, + menelaus_mmc_cd_work); + if (ret < 0) + return ret; + ret = menelaus_add_irq_work(MENELAUS_MMC_S1D1_IRQ, + menelaus_mmc_cd_work); + if (ret < 0) + return ret; + ret = menelaus_add_irq_work(MENELAUS_MMC_S2D1_IRQ, + menelaus_mmc_cd_work); + + return ret; +} +EXPORT_SYMBOL(menelaus_register_mmc_callback); + +void menelaus_unregister_mmc_callback(void) +{ + menelaus_remove_irq_work(MENELAUS_MMC_S1CD_IRQ); + menelaus_remove_irq_work(MENELAUS_MMC_S2CD_IRQ); + menelaus_remove_irq_work(MENELAUS_MMC_S1D1_IRQ); + menelaus_remove_irq_work(MENELAUS_MMC_S2D1_IRQ); + + the_menelaus->mmc_callback = NULL; + the_menelaus->mmc_callback_data = 0; +} +EXPORT_SYMBOL(menelaus_unregister_mmc_callback); + +struct menelaus_vtg { + const char *name; + u8 vtg_reg; + u8 vtg_shift; + u8 vtg_bits; + u8 mode_reg; +}; + +struct menelaus_vtg_value { + u16 vtg; + u16 val; +}; + +static int menelaus_set_voltage(const struct menelaus_vtg *vtg, int mV, + int vtg_val, int mode) +{ + int val, ret; + struct i2c_client *c = the_menelaus->client; + + mutex_lock(&the_menelaus->lock); + if (vtg == 0) + goto set_voltage; + + ret = menelaus_read_reg(vtg->vtg_reg); + if (ret < 0) + goto out; + val = ret & ~(((1 << vtg->vtg_bits) - 1) << vtg->vtg_shift); + val |= vtg_val << vtg->vtg_shift; + + dev_dbg(&c->dev, "Setting voltage '%s'" + "to %d mV (reg 0x%02x, val 0x%02x)\n", + vtg->name, mV, vtg->vtg_reg, val); + + ret = menelaus_write_reg(vtg->vtg_reg, val); + if (ret < 0) + goto out; +set_voltage: + ret = menelaus_write_reg(vtg->mode_reg, mode); +out: + mutex_unlock(&the_menelaus->lock); + if (ret == 0) { + /* Wait for voltage to stabilize */ + msleep(1); + } + return ret; +} + +static int menelaus_get_vtg_value(int vtg, const struct menelaus_vtg_value *tbl, + int n) +{ + int i; + + for (i = 0; i < n; i++, tbl++) + if (tbl->vtg == vtg) + return tbl->val; + return -EINVAL; +} + +/* + * Vcore can be programmed in two ways: + * SW-controlled: Required voltage is programmed into VCORE_CTRL1 + * HW-controlled: Required range (roof-floor) is programmed into VCORE_CTRL3 + * and VCORE_CTRL4 + * + * Call correct 'set' function accordingly + */ + +static const struct menelaus_vtg_value vcore_values[] = { + { 1000, 0 }, + { 1025, 1 }, + { 1050, 2 }, + { 1075, 3 }, + { 1100, 4 }, + { 1125, 5 }, + { 1150, 6 }, + { 1175, 7 }, + { 1200, 8 }, + { 1225, 9 }, + { 1250, 10 }, + { 1275, 11 }, + { 1300, 12 }, + { 1325, 13 }, + { 1350, 14 }, + { 1375, 15 }, + { 1400, 16 }, + { 1425, 17 }, + { 1450, 18 }, +}; + +int menelaus_set_vcore_sw(unsigned int mV) +{ + int val, ret; + struct i2c_client *c = the_menelaus->client; + + val = menelaus_get_vtg_value(mV, vcore_values, + ARRAY_SIZE(vcore_values)); + if (val < 0) + return -EINVAL; + + dev_dbg(&c->dev, "Setting VCORE to %d mV (val 0x%02x)\n", mV, val); + + /* Set SW mode and the voltage in one go. */ + mutex_lock(&the_menelaus->lock); + ret = menelaus_write_reg(MENELAUS_VCORE_CTRL1, val); + if (ret == 0) + the_menelaus->vcore_hw_mode = 0; + mutex_unlock(&the_menelaus->lock); + msleep(1); + + return ret; +} + +int menelaus_set_vcore_hw(unsigned int roof_mV, unsigned int floor_mV) +{ + int fval, rval, val, ret; + struct i2c_client *c = the_menelaus->client; + + rval = menelaus_get_vtg_value(roof_mV, vcore_values, + ARRAY_SIZE(vcore_values)); + if (rval < 0) + return -EINVAL; + fval = menelaus_get_vtg_value(floor_mV, vcore_values, + ARRAY_SIZE(vcore_values)); + if (fval < 0) + return -EINVAL; + + dev_dbg(&c->dev, "Setting VCORE FLOOR to %d mV and ROOF to %d mV\n", + floor_mV, roof_mV); + + mutex_lock(&the_menelaus->lock); + ret = menelaus_write_reg(MENELAUS_VCORE_CTRL3, fval); + if (ret < 0) + goto out; + ret = menelaus_write_reg(MENELAUS_VCORE_CTRL4, rval); + if (ret < 0) + goto out; + if (!the_menelaus->vcore_hw_mode) { + val = menelaus_read_reg(MENELAUS_VCORE_CTRL1); + /* HW mode, turn OFF byte comparator */ + val |= ((1 << 7) | (1 << 5)); + ret = menelaus_write_reg(MENELAUS_VCORE_CTRL1, val); + the_menelaus->vcore_hw_mode = 1; + } + msleep(1); +out: + mutex_unlock(&the_menelaus->lock); + return ret; +} + +static const struct menelaus_vtg vmem_vtg = { + .name = "VMEM", + .vtg_reg = MENELAUS_LDO_CTRL1, + .vtg_shift = 0, + .vtg_bits = 2, + .mode_reg = MENELAUS_LDO_CTRL3, +}; + +static const struct menelaus_vtg_value vmem_values[] = { + { 1500, 0 }, + { 1800, 1 }, + { 1900, 2 }, + { 2500, 3 }, +}; + +int menelaus_set_vmem(unsigned int mV) +{ + int val; + + if (mV == 0) + return menelaus_set_voltage(&vmem_vtg, 0, 0, 0); + + val = menelaus_get_vtg_value(mV, vmem_values, ARRAY_SIZE(vmem_values)); + if (val < 0) + return -EINVAL; + return menelaus_set_voltage(&vmem_vtg, mV, val, 0x02); +} +EXPORT_SYMBOL(menelaus_set_vmem); + +static const struct menelaus_vtg vio_vtg = { + .name = "VIO", + .vtg_reg = MENELAUS_LDO_CTRL1, + .vtg_shift = 2, + .vtg_bits = 2, + .mode_reg = MENELAUS_LDO_CTRL4, +}; + +static const struct menelaus_vtg_value vio_values[] = { + { 1500, 0 }, + { 1800, 1 }, + { 2500, 2 }, + { 2800, 3 }, +}; + +int menelaus_set_vio(unsigned int mV) +{ + int val; + + if (mV == 0) + return menelaus_set_voltage(&vio_vtg, 0, 0, 0); + + val = menelaus_get_vtg_value(mV, vio_values, ARRAY_SIZE(vio_values)); + if (val < 0) + return -EINVAL; + return menelaus_set_voltage(&vio_vtg, mV, val, 0x02); +} +EXPORT_SYMBOL(menelaus_set_vio); + +static const struct menelaus_vtg_value vdcdc_values[] = { + { 1500, 0 }, + { 1800, 1 }, + { 2000, 2 }, + { 2200, 3 }, + { 2400, 4 }, + { 2800, 5 }, + { 3000, 6 }, + { 3300, 7 }, +}; + +static const struct menelaus_vtg vdcdc2_vtg = { + .name = "VDCDC2", + .vtg_reg = MENELAUS_DCDC_CTRL1, + .vtg_shift = 0, + .vtg_bits = 3, + .mode_reg = MENELAUS_DCDC_CTRL2, +}; + +static const struct menelaus_vtg vdcdc3_vtg = { + .name = "VDCDC3", + .vtg_reg = MENELAUS_DCDC_CTRL1, + .vtg_shift = 3, + .vtg_bits = 3, + .mode_reg = MENELAUS_DCDC_CTRL3, +}; + +int menelaus_set_vdcdc(int dcdc, unsigned int mV) +{ + const struct menelaus_vtg *vtg; + int val; + + if (dcdc != 2 && dcdc != 3) + return -EINVAL; + if (dcdc == 2) + vtg = &vdcdc2_vtg; + else + vtg = &vdcdc3_vtg; + + if (mV == 0) + return menelaus_set_voltage(vtg, 0, 0, 0); + + val = menelaus_get_vtg_value(mV, vdcdc_values, + ARRAY_SIZE(vdcdc_values)); + if (val < 0) + return -EINVAL; + return menelaus_set_voltage(vtg, mV, val, 0x03); +} + +static const struct menelaus_vtg_value vmmc_values[] = { + { 1850, 0 }, + { 2800, 1 }, + { 3000, 2 }, + { 3100, 3 }, +}; + +static const struct menelaus_vtg vmmc_vtg = { + .name = "VMMC", + .vtg_reg = MENELAUS_LDO_CTRL1, + .vtg_shift = 6, + .vtg_bits = 2, + .mode_reg = MENELAUS_LDO_CTRL7, +}; + +int menelaus_set_vmmc(unsigned int mV) +{ + int val; + + if (mV == 0) + return menelaus_set_voltage(&vmmc_vtg, 0, 0, 0); + + val = menelaus_get_vtg_value(mV, vmmc_values, ARRAY_SIZE(vmmc_values)); + if (val < 0) + return -EINVAL; + return menelaus_set_voltage(&vmmc_vtg, mV, val, 0x02); +} +EXPORT_SYMBOL(menelaus_set_vmmc); + + +static const struct menelaus_vtg_value vaux_values[] = { + { 1500, 0 }, + { 1800, 1 }, + { 2500, 2 }, + { 2800, 3 }, +}; + +static const struct menelaus_vtg vaux_vtg = { + .name = "VAUX", + .vtg_reg = MENELAUS_LDO_CTRL1, + .vtg_shift = 4, + .vtg_bits = 2, + .mode_reg = MENELAUS_LDO_CTRL6, +}; + +int menelaus_set_vaux(unsigned int mV) +{ + int val; + + if (mV == 0) + return menelaus_set_voltage(&vaux_vtg, 0, 0, 0); + + val = menelaus_get_vtg_value(mV, vaux_values, ARRAY_SIZE(vaux_values)); + if (val < 0) + return -EINVAL; + return menelaus_set_voltage(&vaux_vtg, mV, val, 0x02); +} +EXPORT_SYMBOL(menelaus_set_vaux); + +int menelaus_get_slot_pin_states(void) +{ + return menelaus_read_reg(MENELAUS_MCT_PIN_ST); +} +EXPORT_SYMBOL(menelaus_get_slot_pin_states); + +int menelaus_set_regulator_sleep(int enable, u32 val) +{ + int t, ret; + struct i2c_client *c = the_menelaus->client; + + mutex_lock(&the_menelaus->lock); + ret = menelaus_write_reg(MENELAUS_SLEEP_CTRL2, val); + if (ret < 0) + goto out; + + dev_dbg(&c->dev, "regulator sleep configuration: %02x\n", val); + + ret = menelaus_read_reg(MENELAUS_GPIO_CTRL); + if (ret < 0) + goto out; + t = ((1 << 6) | 0x04); + if (enable) + ret |= t; + else + ret &= ~t; + ret = menelaus_write_reg(MENELAUS_GPIO_CTRL, ret); +out: + mutex_unlock(&the_menelaus->lock); + return ret; +} + +/*-----------------------------------------------------------------------*/ + +/* Handles Menelaus interrupts. Does not run in interrupt context */ +static void menelaus_work(struct work_struct *_menelaus) +{ + struct menelaus_chip *menelaus = + container_of(_menelaus, struct menelaus_chip, work); + void (*handler)(struct menelaus_chip *menelaus); + + while (1) { + unsigned isr; + + isr = (menelaus_read_reg(MENELAUS_INT_STATUS2) + & ~menelaus->mask2) << 8; + isr |= menelaus_read_reg(MENELAUS_INT_STATUS1) + & ~menelaus->mask1; + if (!isr) + break; + + while (isr) { + int irq = fls(isr) - 1; + isr &= ~(1 << irq); + + mutex_lock(&menelaus->lock); + menelaus_disable_irq(irq); + menelaus_ack_irq(irq); + handler = menelaus->handlers[irq]; + if (handler) + handler(menelaus); + menelaus_enable_irq(irq); + mutex_unlock(&menelaus->lock); + } + } + enable_irq(menelaus->client->irq); +} + +/* + * We cannot use I2C in interrupt context, so we just schedule work. + */ +static irqreturn_t menelaus_irq(int irq, void *_menelaus) +{ + struct menelaus_chip *menelaus = _menelaus; + + disable_irq_nosync(irq); + (void)schedule_work(&menelaus->work); + + return IRQ_HANDLED; +} + +/*-----------------------------------------------------------------------*/ + +/* + * The RTC needs to be set once, then it runs on backup battery power. + * It supports alarms, including system wake alarms (from some modes); + * and 1/second IRQs if requested. + */ +#ifdef CONFIG_RTC_DRV_TWL92330 + +#define RTC_CTRL_RTC_EN (1 << 0) +#define RTC_CTRL_AL_EN (1 << 1) +#define RTC_CTRL_MODE12 (1 << 2) +#define RTC_CTRL_EVERY_MASK (3 << 3) +#define RTC_CTRL_EVERY_SEC (0 << 3) +#define RTC_CTRL_EVERY_MIN (1 << 3) +#define RTC_CTRL_EVERY_HR (2 << 3) +#define RTC_CTRL_EVERY_DAY (3 << 3) + +#define RTC_UPDATE_EVERY 0x08 + +#define RTC_HR_PM (1 << 7) + +static void menelaus_to_time(char *regs, struct rtc_time *t) +{ + t->tm_sec = bcd2bin(regs[0]); + t->tm_min = bcd2bin(regs[1]); + if (the_menelaus->rtc_control & RTC_CTRL_MODE12) { + t->tm_hour = bcd2bin(regs[2] & 0x1f) - 1; + if (regs[2] & RTC_HR_PM) + t->tm_hour += 12; + } else + t->tm_hour = bcd2bin(regs[2] & 0x3f); + t->tm_mday = bcd2bin(regs[3]); + t->tm_mon = bcd2bin(regs[4]) - 1; + t->tm_year = bcd2bin(regs[5]) + 100; +} + +static int time_to_menelaus(struct rtc_time *t, int regnum) +{ + int hour, status; + + status = menelaus_write_reg(regnum++, bin2bcd(t->tm_sec)); + if (status < 0) + goto fail; + + status = menelaus_write_reg(regnum++, bin2bcd(t->tm_min)); + if (status < 0) + goto fail; + + if (the_menelaus->rtc_control & RTC_CTRL_MODE12) { + hour = t->tm_hour + 1; + if (hour > 12) + hour = RTC_HR_PM | bin2bcd(hour - 12); + else + hour = bin2bcd(hour); + } else + hour = bin2bcd(t->tm_hour); + status = menelaus_write_reg(regnum++, hour); + if (status < 0) + goto fail; + + status = menelaus_write_reg(regnum++, bin2bcd(t->tm_mday)); + if (status < 0) + goto fail; + + status = menelaus_write_reg(regnum++, bin2bcd(t->tm_mon + 1)); + if (status < 0) + goto fail; + + status = menelaus_write_reg(regnum++, bin2bcd(t->tm_year - 100)); + if (status < 0) + goto fail; + + return 0; +fail: + dev_err(&the_menelaus->client->dev, "rtc write reg %02x, err %d\n", + --regnum, status); + return status; +} + +static int menelaus_read_time(struct device *dev, struct rtc_time *t) +{ + struct i2c_msg msg[2]; + char regs[7]; + int status; + + /* block read date and time registers */ + regs[0] = MENELAUS_RTC_SEC; + + msg[0].addr = MENELAUS_I2C_ADDRESS; + msg[0].flags = 0; + msg[0].len = 1; + msg[0].buf = regs; + + msg[1].addr = MENELAUS_I2C_ADDRESS; + msg[1].flags = I2C_M_RD; + msg[1].len = sizeof(regs); + msg[1].buf = regs; + + status = i2c_transfer(the_menelaus->client->adapter, msg, 2); + if (status != 2) { + dev_err(dev, "%s error %d\n", "read", status); + return -EIO; + } + + menelaus_to_time(regs, t); + t->tm_wday = bcd2bin(regs[6]); + + return 0; +} + +static int menelaus_set_time(struct device *dev, struct rtc_time *t) +{ + int status; + + /* write date and time registers */ + status = time_to_menelaus(t, MENELAUS_RTC_SEC); + if (status < 0) + return status; + status = menelaus_write_reg(MENELAUS_RTC_WKDAY, bin2bcd(t->tm_wday)); + if (status < 0) { + dev_err(&the_menelaus->client->dev, "rtc write reg %02x " + "err %d\n", MENELAUS_RTC_WKDAY, status); + return status; + } + + /* now commit the write */ + status = menelaus_write_reg(MENELAUS_RTC_UPDATE, RTC_UPDATE_EVERY); + if (status < 0) + dev_err(&the_menelaus->client->dev, "rtc commit time, err %d\n", + status); + + return 0; +} + +static int menelaus_read_alarm(struct device *dev, struct rtc_wkalrm *w) +{ + struct i2c_msg msg[2]; + char regs[6]; + int status; + + /* block read alarm registers */ + regs[0] = MENELAUS_RTC_AL_SEC; + + msg[0].addr = MENELAUS_I2C_ADDRESS; + msg[0].flags = 0; + msg[0].len = 1; + msg[0].buf = regs; + + msg[1].addr = MENELAUS_I2C_ADDRESS; + msg[1].flags = I2C_M_RD; + msg[1].len = sizeof(regs); + msg[1].buf = regs; + + status = i2c_transfer(the_menelaus->client->adapter, msg, 2); + if (status != 2) { + dev_err(dev, "%s error %d\n", "alarm read", status); + return -EIO; + } + + menelaus_to_time(regs, &w->time); + + w->enabled = !!(the_menelaus->rtc_control & RTC_CTRL_AL_EN); + + /* NOTE we *could* check if actually pending... */ + w->pending = 0; + + return 0; +} + +static int menelaus_set_alarm(struct device *dev, struct rtc_wkalrm *w) +{ + int status; + + if (the_menelaus->client->irq <= 0 && w->enabled) + return -ENODEV; + + /* clear previous alarm enable */ + if (the_menelaus->rtc_control & RTC_CTRL_AL_EN) { + the_menelaus->rtc_control &= ~RTC_CTRL_AL_EN; + status = menelaus_write_reg(MENELAUS_RTC_CTRL, + the_menelaus->rtc_control); + if (status < 0) + return status; + } + + /* write alarm registers */ + status = time_to_menelaus(&w->time, MENELAUS_RTC_AL_SEC); + if (status < 0) + return status; + + /* enable alarm if requested */ + if (w->enabled) { + the_menelaus->rtc_control |= RTC_CTRL_AL_EN; + status = menelaus_write_reg(MENELAUS_RTC_CTRL, + the_menelaus->rtc_control); + } + + return status; +} + +#ifdef CONFIG_RTC_INTF_DEV + +static void menelaus_rtc_update_work(struct menelaus_chip *m) +{ + /* report 1/sec update */ + local_irq_disable(); + rtc_update_irq(m->rtc, 1, RTC_IRQF | RTC_UF); + local_irq_enable(); +} + +static int menelaus_ioctl(struct device *dev, unsigned cmd, unsigned long arg) +{ + int status; + + if (the_menelaus->client->irq <= 0) + return -ENOIOCTLCMD; + + switch (cmd) { + /* alarm IRQ */ + case RTC_AIE_ON: + if (the_menelaus->rtc_control & RTC_CTRL_AL_EN) + return 0; + the_menelaus->rtc_control |= RTC_CTRL_AL_EN; + break; + case RTC_AIE_OFF: + if (!(the_menelaus->rtc_control & RTC_CTRL_AL_EN)) + return 0; + the_menelaus->rtc_control &= ~RTC_CTRL_AL_EN; + break; + /* 1/second "update" IRQ */ + case RTC_UIE_ON: + if (the_menelaus->uie) + return 0; + status = menelaus_remove_irq_work(MENELAUS_RTCTMR_IRQ); + status = menelaus_add_irq_work(MENELAUS_RTCTMR_IRQ, + menelaus_rtc_update_work); + if (status == 0) + the_menelaus->uie = 1; + return status; + case RTC_UIE_OFF: + if (!the_menelaus->uie) + return 0; + status = menelaus_remove_irq_work(MENELAUS_RTCTMR_IRQ); + if (status == 0) + the_menelaus->uie = 0; + return status; + default: + return -ENOIOCTLCMD; + } + return menelaus_write_reg(MENELAUS_RTC_CTRL, the_menelaus->rtc_control); +} + +#else +#define menelaus_ioctl NULL +#endif + +/* REVISIT no compensation register support ... */ + +static const struct rtc_class_ops menelaus_rtc_ops = { + .ioctl = menelaus_ioctl, + .read_time = menelaus_read_time, + .set_time = menelaus_set_time, + .read_alarm = menelaus_read_alarm, + .set_alarm = menelaus_set_alarm, +}; + +static void menelaus_rtc_alarm_work(struct menelaus_chip *m) +{ + /* report alarm */ + local_irq_disable(); + rtc_update_irq(m->rtc, 1, RTC_IRQF | RTC_AF); + local_irq_enable(); + + /* then disable it; alarms are oneshot */ + the_menelaus->rtc_control &= ~RTC_CTRL_AL_EN; + menelaus_write_reg(MENELAUS_RTC_CTRL, the_menelaus->rtc_control); +} + +static inline void menelaus_rtc_init(struct menelaus_chip *m) +{ + int alarm = (m->client->irq > 0); + + /* assume 32KDETEN pin is pulled high */ + if (!(menelaus_read_reg(MENELAUS_OSC_CTRL) & 0x80)) { + dev_dbg(&m->client->dev, "no 32k oscillator\n"); + return; + } + + /* support RTC alarm; it can issue wakeups */ + if (alarm) { + if (menelaus_add_irq_work(MENELAUS_RTCALM_IRQ, + menelaus_rtc_alarm_work) < 0) { + dev_err(&m->client->dev, "can't handle RTC alarm\n"); + return; + } + device_init_wakeup(&m->client->dev, 1); + } + + /* be sure RTC is enabled; allow 1/sec irqs; leave 12hr mode alone */ + m->rtc_control = menelaus_read_reg(MENELAUS_RTC_CTRL); + if (!(m->rtc_control & RTC_CTRL_RTC_EN) + || (m->rtc_control & RTC_CTRL_AL_EN) + || (m->rtc_control & RTC_CTRL_EVERY_MASK)) { + if (!(m->rtc_control & RTC_CTRL_RTC_EN)) { + dev_warn(&m->client->dev, "rtc clock needs setting\n"); + m->rtc_control |= RTC_CTRL_RTC_EN; + } + m->rtc_control &= ~RTC_CTRL_EVERY_MASK; + m->rtc_control &= ~RTC_CTRL_AL_EN; + menelaus_write_reg(MENELAUS_RTC_CTRL, m->rtc_control); + } + + m->rtc = rtc_device_register(DRIVER_NAME, + &m->client->dev, + &menelaus_rtc_ops, THIS_MODULE); + if (IS_ERR(m->rtc)) { + if (alarm) { + menelaus_remove_irq_work(MENELAUS_RTCALM_IRQ); + device_init_wakeup(&m->client->dev, 0); + } + dev_err(&m->client->dev, "can't register RTC: %d\n", + (int) PTR_ERR(m->rtc)); + the_menelaus->rtc = NULL; + } +} + +#else + +static inline void menelaus_rtc_init(struct menelaus_chip *m) +{ + /* nothing */ +} + +#endif + +/*-----------------------------------------------------------------------*/ + +static struct i2c_driver menelaus_i2c_driver; + +static int menelaus_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct menelaus_chip *menelaus; + int rev = 0, val; + int err = 0; + struct menelaus_platform_data *menelaus_pdata = + client->dev.platform_data; + + if (the_menelaus) { + dev_dbg(&client->dev, "only one %s for now\n", + DRIVER_NAME); + return -ENODEV; + } + + menelaus = kzalloc(sizeof *menelaus, GFP_KERNEL); + if (!menelaus) + return -ENOMEM; + + i2c_set_clientdata(client, menelaus); + + the_menelaus = menelaus; + menelaus->client = client; + + /* If a true probe check the device */ + rev = menelaus_read_reg(MENELAUS_REV); + if (rev < 0) { + pr_err(DRIVER_NAME ": device not found"); + err = -ENODEV; + goto fail1; + } + + /* Ack and disable all Menelaus interrupts */ + menelaus_write_reg(MENELAUS_INT_ACK1, 0xff); + menelaus_write_reg(MENELAUS_INT_ACK2, 0xff); + menelaus_write_reg(MENELAUS_INT_MASK1, 0xff); + menelaus_write_reg(MENELAUS_INT_MASK2, 0xff); + menelaus->mask1 = 0xff; + menelaus->mask2 = 0xff; + + /* Set output buffer strengths */ + menelaus_write_reg(MENELAUS_MCT_CTRL1, 0x73); + + if (client->irq > 0) { + err = request_irq(client->irq, menelaus_irq, IRQF_DISABLED, + DRIVER_NAME, menelaus); + if (err) { + dev_dbg(&client->dev, "can't get IRQ %d, err %d\n", + client->irq, err); + goto fail1; + } + } + + mutex_init(&menelaus->lock); + INIT_WORK(&menelaus->work, menelaus_work); + + pr_info("Menelaus rev %d.%d\n", rev >> 4, rev & 0x0f); + + val = menelaus_read_reg(MENELAUS_VCORE_CTRL1); + if (val < 0) + goto fail2; + if (val & (1 << 7)) + menelaus->vcore_hw_mode = 1; + else + menelaus->vcore_hw_mode = 0; + + if (menelaus_pdata != NULL && menelaus_pdata->late_init != NULL) { + err = menelaus_pdata->late_init(&client->dev); + if (err < 0) + goto fail2; + } + + menelaus_rtc_init(menelaus); + + return 0; +fail2: + free_irq(client->irq, menelaus); + flush_scheduled_work(); +fail1: + kfree(menelaus); + return err; +} + +static int __exit menelaus_remove(struct i2c_client *client) +{ + struct menelaus_chip *menelaus = i2c_get_clientdata(client); + + free_irq(client->irq, menelaus); + kfree(menelaus); + i2c_set_clientdata(client, NULL); + the_menelaus = NULL; + return 0; +} + +static const struct i2c_device_id menelaus_id[] = { + { "menelaus", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, menelaus_id); + +static struct i2c_driver menelaus_i2c_driver = { + .driver = { + .name = DRIVER_NAME, + }, + .probe = menelaus_probe, + .remove = __exit_p(menelaus_remove), + .id_table = menelaus_id, +}; + +static int __init menelaus_init(void) +{ + int res; + + res = i2c_add_driver(&menelaus_i2c_driver); + if (res < 0) { + pr_err(DRIVER_NAME ": driver registration failed\n"); + return res; + } + + return 0; +} + +static void __exit menelaus_exit(void) +{ + i2c_del_driver(&menelaus_i2c_driver); + + /* FIXME: Shutdown menelaus parts that can be shut down */ +} + +MODULE_AUTHOR("Texas Instruments, Inc. (and others)"); +MODULE_DESCRIPTION("I2C interface for Menelaus."); +MODULE_LICENSE("GPL"); + +module_init(menelaus_init); +module_exit(menelaus_exit); diff --git a/drivers/i2c/chips/pca9539.c b/drivers/i2c/chips/pca9539.c new file mode 100644 index 0000000..270de4e --- /dev/null +++ b/drivers/i2c/chips/pca9539.c @@ -0,0 +1,152 @@ +/* + pca9539.c - 16-bit I/O port with interrupt and reset + + Copyright (C) 2005 Ben Gardner <bgardner@wabtec.com> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. +*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/hwmon-sysfs.h> + +/* Addresses to scan: none, device is not autodetected */ +static const unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +/* Insmod parameters */ +I2C_CLIENT_INSMOD_1(pca9539); + +enum pca9539_cmd +{ + PCA9539_INPUT_0 = 0, + PCA9539_INPUT_1 = 1, + PCA9539_OUTPUT_0 = 2, + PCA9539_OUTPUT_1 = 3, + PCA9539_INVERT_0 = 4, + PCA9539_INVERT_1 = 5, + PCA9539_DIRECTION_0 = 6, + PCA9539_DIRECTION_1 = 7, +}; + +/* following are the sysfs callback functions */ +static ssize_t pca9539_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *psa = to_sensor_dev_attr(attr); + struct i2c_client *client = to_i2c_client(dev); + return sprintf(buf, "%d\n", i2c_smbus_read_byte_data(client, + psa->index)); +} + +static ssize_t pca9539_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *psa = to_sensor_dev_attr(attr); + struct i2c_client *client = to_i2c_client(dev); + unsigned long val = simple_strtoul(buf, NULL, 0); + if (val > 0xff) + return -EINVAL; + i2c_smbus_write_byte_data(client, psa->index, val); + return count; +} + +/* Define the device attributes */ + +#define PCA9539_ENTRY_RO(name, cmd_idx) \ + static SENSOR_DEVICE_ATTR(name, S_IRUGO, pca9539_show, NULL, cmd_idx) + +#define PCA9539_ENTRY_RW(name, cmd_idx) \ + static SENSOR_DEVICE_ATTR(name, S_IRUGO | S_IWUSR, pca9539_show, \ + pca9539_store, cmd_idx) + +PCA9539_ENTRY_RO(input0, PCA9539_INPUT_0); +PCA9539_ENTRY_RO(input1, PCA9539_INPUT_1); +PCA9539_ENTRY_RW(output0, PCA9539_OUTPUT_0); +PCA9539_ENTRY_RW(output1, PCA9539_OUTPUT_1); +PCA9539_ENTRY_RW(invert0, PCA9539_INVERT_0); +PCA9539_ENTRY_RW(invert1, PCA9539_INVERT_1); +PCA9539_ENTRY_RW(direction0, PCA9539_DIRECTION_0); +PCA9539_ENTRY_RW(direction1, PCA9539_DIRECTION_1); + +static struct attribute *pca9539_attributes[] = { + &sensor_dev_attr_input0.dev_attr.attr, + &sensor_dev_attr_input1.dev_attr.attr, + &sensor_dev_attr_output0.dev_attr.attr, + &sensor_dev_attr_output1.dev_attr.attr, + &sensor_dev_attr_invert0.dev_attr.attr, + &sensor_dev_attr_invert1.dev_attr.attr, + &sensor_dev_attr_direction0.dev_attr.attr, + &sensor_dev_attr_direction1.dev_attr.attr, + NULL +}; + +static struct attribute_group pca9539_defattr_group = { + .attrs = pca9539_attributes, +}; + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int pca9539_detect(struct i2c_client *client, int kind, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + strlcpy(info->type, "pca9539", I2C_NAME_SIZE); + + return 0; +} + +static int pca9539_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + /* Register sysfs hooks */ + return sysfs_create_group(&client->dev.kobj, + &pca9539_defattr_group); +} + +static int pca9539_remove(struct i2c_client *client) +{ + sysfs_remove_group(&client->dev.kobj, &pca9539_defattr_group); + return 0; +} + +static const struct i2c_device_id pca9539_id[] = { + { "pca9539", 0 }, + { } +}; + +static struct i2c_driver pca9539_driver = { + .driver = { + .name = "pca9539", + }, + .probe = pca9539_probe, + .remove = pca9539_remove, + .id_table = pca9539_id, + + .detect = pca9539_detect, + .address_data = &addr_data, +}; + +static int __init pca9539_init(void) +{ + return i2c_add_driver(&pca9539_driver); +} + +static void __exit pca9539_exit(void) +{ + i2c_del_driver(&pca9539_driver); +} + +MODULE_AUTHOR("Ben Gardner <bgardner@wabtec.com>"); +MODULE_DESCRIPTION("PCA9539 driver"); +MODULE_LICENSE("GPL"); + +module_init(pca9539_init); +module_exit(pca9539_exit); + diff --git a/drivers/i2c/chips/pcf8574.c b/drivers/i2c/chips/pcf8574.c new file mode 100644 index 0000000..6ec3098 --- /dev/null +++ b/drivers/i2c/chips/pcf8574.c @@ -0,0 +1,215 @@ +/* + Copyright (c) 2000 Frodo Looijaard <frodol@dds.nl>, + Philip Edelbrock <phil@netroedge.com>, + Dan Eaton <dan.eaton@rocketlogix.com> + Ported to Linux 2.6 by Aurelien Jarno <aurel32@debian.org> with + the help of Jean Delvare <khali@linux-fr.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* A few notes about the PCF8574: + +* The PCF8574 is an 8-bit I/O expander for the I2C bus produced by + Philips Semiconductors. It is designed to provide a byte I2C + interface to up to 8 separate devices. + +* The PCF8574 appears as a very simple SMBus device which can be + read from or written to with SMBUS byte read/write accesses. + + --Dan + +*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/i2c.h> + +/* Addresses to scan: none, device can't be detected */ +static const unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +/* Insmod parameters */ +I2C_CLIENT_INSMOD_2(pcf8574, pcf8574a); + +/* Each client has this additional data */ +struct pcf8574_data { + int write; /* Remember last written value */ +}; + +static void pcf8574_init_client(struct i2c_client *client); + +/* following are the sysfs callback functions */ +static ssize_t show_read(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + return sprintf(buf, "%u\n", i2c_smbus_read_byte(client)); +} + +static DEVICE_ATTR(read, S_IRUGO, show_read, NULL); + +static ssize_t show_write(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct pcf8574_data *data = i2c_get_clientdata(to_i2c_client(dev)); + + if (data->write < 0) + return data->write; + + return sprintf(buf, "%d\n", data->write); +} + +static ssize_t set_write(struct device *dev, struct device_attribute *attr, const char *buf, + size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pcf8574_data *data = i2c_get_clientdata(client); + unsigned long val = simple_strtoul(buf, NULL, 10); + + if (val > 0xff) + return -EINVAL; + + data->write = val; + i2c_smbus_write_byte(client, data->write); + return count; +} + +static DEVICE_ATTR(write, S_IWUSR | S_IRUGO, show_write, set_write); + +static struct attribute *pcf8574_attributes[] = { + &dev_attr_read.attr, + &dev_attr_write.attr, + NULL +}; + +static const struct attribute_group pcf8574_attr_group = { + .attrs = pcf8574_attributes, +}; + +/* + * Real code + */ + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int pcf8574_detect(struct i2c_client *client, int kind, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + const char *client_name; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + return -ENODEV; + + /* Now, we would do the remaining detection. But the PCF8574 is plainly + impossible to detect! Stupid chip. */ + + /* Determine the chip type */ + if (kind <= 0) { + if (client->addr >= 0x38 && client->addr <= 0x3f) + kind = pcf8574a; + else + kind = pcf8574; + } + + if (kind == pcf8574a) + client_name = "pcf8574a"; + else + client_name = "pcf8574"; + strlcpy(info->type, client_name, I2C_NAME_SIZE); + + return 0; +} + +static int pcf8574_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct pcf8574_data *data; + int err; + + data = kzalloc(sizeof(struct pcf8574_data), GFP_KERNEL); + if (!data) { + err = -ENOMEM; + goto exit; + } + + i2c_set_clientdata(client, data); + + /* Initialize the PCF8574 chip */ + pcf8574_init_client(client); + + /* Register sysfs hooks */ + err = sysfs_create_group(&client->dev.kobj, &pcf8574_attr_group); + if (err) + goto exit_free; + return 0; + + exit_free: + kfree(data); + exit: + return err; +} + +static int pcf8574_remove(struct i2c_client *client) +{ + sysfs_remove_group(&client->dev.kobj, &pcf8574_attr_group); + kfree(i2c_get_clientdata(client)); + return 0; +} + +/* Called when we have found a new PCF8574. */ +static void pcf8574_init_client(struct i2c_client *client) +{ + struct pcf8574_data *data = i2c_get_clientdata(client); + data->write = -EAGAIN; +} + +static const struct i2c_device_id pcf8574_id[] = { + { "pcf8574", 0 }, + { "pcf8574a", 0 }, + { } +}; + +static struct i2c_driver pcf8574_driver = { + .driver = { + .name = "pcf8574", + }, + .probe = pcf8574_probe, + .remove = pcf8574_remove, + .id_table = pcf8574_id, + + .detect = pcf8574_detect, + .address_data = &addr_data, +}; + +static int __init pcf8574_init(void) +{ + return i2c_add_driver(&pcf8574_driver); +} + +static void __exit pcf8574_exit(void) +{ + i2c_del_driver(&pcf8574_driver); +} + + +MODULE_AUTHOR + ("Frodo Looijaard <frodol@dds.nl>, " + "Philip Edelbrock <phil@netroedge.com>, " + "Dan Eaton <dan.eaton@rocketlogix.com> " + "and Aurelien Jarno <aurelien@aurel32.net>"); +MODULE_DESCRIPTION("PCF8574 driver"); +MODULE_LICENSE("GPL"); + +module_init(pcf8574_init); +module_exit(pcf8574_exit); diff --git a/drivers/i2c/chips/pcf8575.c b/drivers/i2c/chips/pcf8575.c new file mode 100644 index 0000000..07fd7cb --- /dev/null +++ b/drivers/i2c/chips/pcf8575.c @@ -0,0 +1,198 @@ +/* + pcf8575.c + + About the PCF8575 chip: the PCF8575 is a 16-bit I/O expander for the I2C bus + produced by a.o. Philips Semiconductors. + + Copyright (C) 2006 Michael Hennerich, Analog Devices Inc. + <hennerich@blackfin.uclinux.org> + Based on pcf8574.c. + + Copyright (c) 2007 Bart Van Assche <bart.vanassche@gmail.com>. + Ported this driver from ucLinux to the mainstream Linux kernel. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/slab.h> /* kzalloc() */ +#include <linux/sysfs.h> /* sysfs_create_group() */ + +/* Addresses to scan: none, device can't be detected */ +static const unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +/* Insmod parameters */ +I2C_CLIENT_INSMOD; + + +/* Each client has this additional data */ +struct pcf8575_data { + int write; /* last written value, or error code */ +}; + +/* following are the sysfs callback functions */ +static ssize_t show_read(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + u16 val; + u8 iopin_state[2]; + + i2c_master_recv(client, iopin_state, 2); + + val = iopin_state[0]; + val |= iopin_state[1] << 8; + + return sprintf(buf, "%u\n", val); +} + +static DEVICE_ATTR(read, S_IRUGO, show_read, NULL); + +static ssize_t show_write(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct pcf8575_data *data = dev_get_drvdata(dev); + if (data->write < 0) + return data->write; + return sprintf(buf, "%d\n", data->write); +} + +static ssize_t set_write(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pcf8575_data *data = i2c_get_clientdata(client); + unsigned long val = simple_strtoul(buf, NULL, 10); + u8 iopin_state[2]; + + if (val > 0xffff) + return -EINVAL; + + data->write = val; + + iopin_state[0] = val & 0xFF; + iopin_state[1] = val >> 8; + + i2c_master_send(client, iopin_state, 2); + + return count; +} + +static DEVICE_ATTR(write, S_IWUSR | S_IRUGO, show_write, set_write); + +static struct attribute *pcf8575_attributes[] = { + &dev_attr_read.attr, + &dev_attr_write.attr, + NULL +}; + +static const struct attribute_group pcf8575_attr_group = { + .attrs = pcf8575_attributes, +}; + +/* + * Real code + */ + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int pcf8575_detect(struct i2c_client *client, int kind, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + + if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) + return -ENODEV; + + /* This is the place to detect whether the chip at the specified + address really is a PCF8575 chip. However, there is no method known + to detect whether an I2C chip is a PCF8575 or any other I2C chip. */ + + strlcpy(info->type, "pcf8575", I2C_NAME_SIZE); + + return 0; +} + +static int pcf8575_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct pcf8575_data *data; + int err; + + data = kzalloc(sizeof(struct pcf8575_data), GFP_KERNEL); + if (!data) { + err = -ENOMEM; + goto exit; + } + + i2c_set_clientdata(client, data); + data->write = -EAGAIN; + + /* Register sysfs hooks */ + err = sysfs_create_group(&client->dev.kobj, &pcf8575_attr_group); + if (err) + goto exit_free; + + return 0; + +exit_free: + kfree(data); +exit: + return err; +} + +static int pcf8575_remove(struct i2c_client *client) +{ + sysfs_remove_group(&client->dev.kobj, &pcf8575_attr_group); + kfree(i2c_get_clientdata(client)); + return 0; +} + +static const struct i2c_device_id pcf8575_id[] = { + { "pcf8575", 0 }, + { } +}; + +static struct i2c_driver pcf8575_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "pcf8575", + }, + .probe = pcf8575_probe, + .remove = pcf8575_remove, + .id_table = pcf8575_id, + + .detect = pcf8575_detect, + .address_data = &addr_data, +}; + +static int __init pcf8575_init(void) +{ + return i2c_add_driver(&pcf8575_driver); +} + +static void __exit pcf8575_exit(void) +{ + i2c_del_driver(&pcf8575_driver); +} + +MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>, " + "Bart Van Assche <bart.vanassche@gmail.com>"); +MODULE_DESCRIPTION("pcf8575 driver"); +MODULE_LICENSE("GPL"); + +module_init(pcf8575_init); +module_exit(pcf8575_exit); diff --git a/drivers/i2c/chips/pcf8591.c b/drivers/i2c/chips/pcf8591.c new file mode 100644 index 0000000..16ce3e1 --- /dev/null +++ b/drivers/i2c/chips/pcf8591.c @@ -0,0 +1,325 @@ +/* + Copyright (C) 2001-2004 Aurelien Jarno <aurelien@aurel32.net> + Ported to Linux 2.6 by Aurelien Jarno <aurelien@aurel32.net> with + the help of Jean Delvare <khali@linux-fr.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/mutex.h> + +/* Addresses to scan */ +static const unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4b, 0x4c, + 0x4d, 0x4e, 0x4f, I2C_CLIENT_END }; + +/* Insmod parameters */ +I2C_CLIENT_INSMOD_1(pcf8591); + +static int input_mode; +module_param(input_mode, int, 0); +MODULE_PARM_DESC(input_mode, + "Analog input mode:\n" + " 0 = four single ended inputs\n" + " 1 = three differential inputs\n" + " 2 = single ended and differential mixed\n" + " 3 = two differential inputs\n"); + +/* The PCF8591 control byte + 7 6 5 4 3 2 1 0 + | 0 |AOEF| AIP | 0 |AINC| AICH | */ + +/* Analog Output Enable Flag (analog output active if 1) */ +#define PCF8591_CONTROL_AOEF 0x40 + +/* Analog Input Programming + 0x00 = four single ended inputs + 0x10 = three differential inputs + 0x20 = single ended and differential mixed + 0x30 = two differential inputs */ +#define PCF8591_CONTROL_AIP_MASK 0x30 + +/* Autoincrement Flag (switch on if 1) */ +#define PCF8591_CONTROL_AINC 0x04 + +/* Channel selection + 0x00 = channel 0 + 0x01 = channel 1 + 0x02 = channel 2 + 0x03 = channel 3 */ +#define PCF8591_CONTROL_AICH_MASK 0x03 + +/* Initial values */ +#define PCF8591_INIT_CONTROL ((input_mode << 4) | PCF8591_CONTROL_AOEF) +#define PCF8591_INIT_AOUT 0 /* DAC out = 0 */ + +/* Conversions */ +#define REG_TO_SIGNED(reg) (((reg) & 0x80)?((reg) - 256):(reg)) + +struct pcf8591_data { + struct mutex update_lock; + + u8 control; + u8 aout; +}; + +static void pcf8591_init_client(struct i2c_client *client); +static int pcf8591_read_channel(struct device *dev, int channel); + +/* following are the sysfs callback functions */ +#define show_in_channel(channel) \ +static ssize_t show_in##channel##_input(struct device *dev, struct device_attribute *attr, char *buf) \ +{ \ + return sprintf(buf, "%d\n", pcf8591_read_channel(dev, channel));\ +} \ +static DEVICE_ATTR(in##channel##_input, S_IRUGO, \ + show_in##channel##_input, NULL); + +show_in_channel(0); +show_in_channel(1); +show_in_channel(2); +show_in_channel(3); + +static ssize_t show_out0_ouput(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct pcf8591_data *data = i2c_get_clientdata(to_i2c_client(dev)); + return sprintf(buf, "%d\n", data->aout * 10); +} + +static ssize_t set_out0_output(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int value; + struct i2c_client *client = to_i2c_client(dev); + struct pcf8591_data *data = i2c_get_clientdata(client); + if ((value = (simple_strtoul(buf, NULL, 10) + 5) / 10) <= 255) { + data->aout = value; + i2c_smbus_write_byte_data(client, data->control, data->aout); + return count; + } + return -EINVAL; +} + +static DEVICE_ATTR(out0_output, S_IWUSR | S_IRUGO, + show_out0_ouput, set_out0_output); + +static ssize_t show_out0_enable(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct pcf8591_data *data = i2c_get_clientdata(to_i2c_client(dev)); + return sprintf(buf, "%u\n", !(!(data->control & PCF8591_CONTROL_AOEF))); +} + +static ssize_t set_out0_enable(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pcf8591_data *data = i2c_get_clientdata(client); + unsigned long val = simple_strtoul(buf, NULL, 10); + + mutex_lock(&data->update_lock); + if (val) + data->control |= PCF8591_CONTROL_AOEF; + else + data->control &= ~PCF8591_CONTROL_AOEF; + i2c_smbus_write_byte(client, data->control); + mutex_unlock(&data->update_lock); + return count; +} + +static DEVICE_ATTR(out0_enable, S_IWUSR | S_IRUGO, + show_out0_enable, set_out0_enable); + +static struct attribute *pcf8591_attributes[] = { + &dev_attr_out0_enable.attr, + &dev_attr_out0_output.attr, + &dev_attr_in0_input.attr, + &dev_attr_in1_input.attr, + NULL +}; + +static const struct attribute_group pcf8591_attr_group = { + .attrs = pcf8591_attributes, +}; + +static struct attribute *pcf8591_attributes_opt[] = { + &dev_attr_in2_input.attr, + &dev_attr_in3_input.attr, + NULL +}; + +static const struct attribute_group pcf8591_attr_group_opt = { + .attrs = pcf8591_attributes_opt, +}; + +/* + * Real code + */ + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int pcf8591_detect(struct i2c_client *client, int kind, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE + | I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) + return -ENODEV; + + /* Now, we would do the remaining detection. But the PCF8591 is plainly + impossible to detect! Stupid chip. */ + + strlcpy(info->type, "pcf8591", I2C_NAME_SIZE); + + return 0; +} + +static int pcf8591_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct pcf8591_data *data; + int err; + + if (!(data = kzalloc(sizeof(struct pcf8591_data), GFP_KERNEL))) { + err = -ENOMEM; + goto exit; + } + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + /* Initialize the PCF8591 chip */ + pcf8591_init_client(client); + + /* Register sysfs hooks */ + err = sysfs_create_group(&client->dev.kobj, &pcf8591_attr_group); + if (err) + goto exit_kfree; + + /* Register input2 if not in "two differential inputs" mode */ + if (input_mode != 3) { + if ((err = device_create_file(&client->dev, + &dev_attr_in2_input))) + goto exit_sysfs_remove; + } + + /* Register input3 only in "four single ended inputs" mode */ + if (input_mode == 0) { + if ((err = device_create_file(&client->dev, + &dev_attr_in3_input))) + goto exit_sysfs_remove; + } + + return 0; + +exit_sysfs_remove: + sysfs_remove_group(&client->dev.kobj, &pcf8591_attr_group_opt); + sysfs_remove_group(&client->dev.kobj, &pcf8591_attr_group); +exit_kfree: + kfree(data); +exit: + return err; +} + +static int pcf8591_remove(struct i2c_client *client) +{ + sysfs_remove_group(&client->dev.kobj, &pcf8591_attr_group_opt); + sysfs_remove_group(&client->dev.kobj, &pcf8591_attr_group); + kfree(i2c_get_clientdata(client)); + return 0; +} + +/* Called when we have found a new PCF8591. */ +static void pcf8591_init_client(struct i2c_client *client) +{ + struct pcf8591_data *data = i2c_get_clientdata(client); + data->control = PCF8591_INIT_CONTROL; + data->aout = PCF8591_INIT_AOUT; + + i2c_smbus_write_byte_data(client, data->control, data->aout); + + /* The first byte transmitted contains the conversion code of the + previous read cycle. FLUSH IT! */ + i2c_smbus_read_byte(client); +} + +static int pcf8591_read_channel(struct device *dev, int channel) +{ + u8 value; + struct i2c_client *client = to_i2c_client(dev); + struct pcf8591_data *data = i2c_get_clientdata(client); + + mutex_lock(&data->update_lock); + + if ((data->control & PCF8591_CONTROL_AICH_MASK) != channel) { + data->control = (data->control & ~PCF8591_CONTROL_AICH_MASK) + | channel; + i2c_smbus_write_byte(client, data->control); + + /* The first byte transmitted contains the conversion code of + the previous read cycle. FLUSH IT! */ + i2c_smbus_read_byte(client); + } + value = i2c_smbus_read_byte(client); + + mutex_unlock(&data->update_lock); + + if ((channel == 2 && input_mode == 2) || + (channel != 3 && (input_mode == 1 || input_mode == 3))) + return (10 * REG_TO_SIGNED(value)); + else + return (10 * value); +} + +static const struct i2c_device_id pcf8591_id[] = { + { "pcf8591", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, pcf8591_id); + +static struct i2c_driver pcf8591_driver = { + .driver = { + .name = "pcf8591", + }, + .probe = pcf8591_probe, + .remove = pcf8591_remove, + .id_table = pcf8591_id, + + .class = I2C_CLASS_HWMON, /* Nearest choice */ + .detect = pcf8591_detect, + .address_data = &addr_data, +}; + +static int __init pcf8591_init(void) +{ + if (input_mode < 0 || input_mode > 3) { + printk(KERN_WARNING "pcf8591: invalid input_mode (%d)\n", + input_mode); + input_mode = 0; + } + return i2c_add_driver(&pcf8591_driver); +} + +static void __exit pcf8591_exit(void) +{ + i2c_del_driver(&pcf8591_driver); +} + +MODULE_AUTHOR("Aurelien Jarno <aurelien@aurel32.net>"); +MODULE_DESCRIPTION("PCF8591 driver"); +MODULE_LICENSE("GPL"); + +module_init(pcf8591_init); +module_exit(pcf8591_exit); diff --git a/drivers/i2c/chips/tps65010.c b/drivers/i2c/chips/tps65010.c new file mode 100644 index 0000000..acf8b9d --- /dev/null +++ b/drivers/i2c/chips/tps65010.c @@ -0,0 +1,1072 @@ +/* + * tps65010 - driver for tps6501x power management chips + * + * Copyright (C) 2004 Texas Instruments + * Copyright (C) 2004-2005 David Brownell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/workqueue.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> + +#include <linux/i2c/tps65010.h> + +#include <asm/gpio.h> + + +/*-------------------------------------------------------------------------*/ + +#define DRIVER_VERSION "2 May 2005" +#define DRIVER_NAME (tps65010_driver.driver.name) + +MODULE_DESCRIPTION("TPS6501x Power Management Driver"); +MODULE_LICENSE("GPL"); + +static struct i2c_driver tps65010_driver; + +/*-------------------------------------------------------------------------*/ + +/* This driver handles a family of multipurpose chips, which incorporate + * voltage regulators, lithium ion/polymer battery charging, GPIOs, LEDs, + * and other features often needed in portable devices like cell phones + * or digital cameras. + * + * The tps65011 and tps65013 have different voltage settings compared + * to tps65010 and tps65012. The tps65013 has a NO_CHG status/irq. + * All except tps65010 have "wait" mode, possibly defaulted so that + * battery-insert != device-on. + * + * We could distinguish between some models by checking VDCDC1.UVLO or + * other registers, unless they've been changed already after powerup + * as part of board setup by a bootloader. + */ +enum tps_model { + TPS65010, + TPS65011, + TPS65012, + TPS65013, +}; + +struct tps65010 { + struct i2c_client *client; + struct mutex lock; + struct delayed_work work; + struct dentry *file; + unsigned charging:1; + unsigned por:1; + unsigned model:8; + u16 vbus; + unsigned long flags; +#define FLAG_VBUS_CHANGED 0 +#define FLAG_IRQ_ENABLE 1 + + /* copies of last register state */ + u8 chgstatus, regstatus, chgconf; + u8 nmask1, nmask2; + + u8 outmask; + struct gpio_chip chip; + struct platform_device *leds; +}; + +#define POWER_POLL_DELAY msecs_to_jiffies(5000) + +/*-------------------------------------------------------------------------*/ + +#if defined(DEBUG) || defined(CONFIG_DEBUG_FS) + +static void dbg_chgstat(char *buf, size_t len, u8 chgstatus) +{ + snprintf(buf, len, "%02x%s%s%s%s%s%s%s%s\n", + chgstatus, + (chgstatus & TPS_CHG_USB) ? " USB" : "", + (chgstatus & TPS_CHG_AC) ? " AC" : "", + (chgstatus & TPS_CHG_THERM) ? " therm" : "", + (chgstatus & TPS_CHG_TERM) ? " done" : + ((chgstatus & (TPS_CHG_USB|TPS_CHG_AC)) + ? " (charging)" : ""), + (chgstatus & TPS_CHG_TAPER_TMO) ? " taper_tmo" : "", + (chgstatus & TPS_CHG_CHG_TMO) ? " charge_tmo" : "", + (chgstatus & TPS_CHG_PRECHG_TMO) ? " prechg_tmo" : "", + (chgstatus & TPS_CHG_TEMP_ERR) ? " temp_err" : ""); +} + +static void dbg_regstat(char *buf, size_t len, u8 regstatus) +{ + snprintf(buf, len, "%02x %s%s%s%s%s%s%s%s\n", + regstatus, + (regstatus & TPS_REG_ONOFF) ? "off" : "(on)", + (regstatus & TPS_REG_COVER) ? " uncover" : "", + (regstatus & TPS_REG_UVLO) ? " UVLO" : "", + (regstatus & TPS_REG_NO_CHG) ? " NO_CHG" : "", + (regstatus & TPS_REG_PG_LD02) ? " ld02_bad" : "", + (regstatus & TPS_REG_PG_LD01) ? " ld01_bad" : "", + (regstatus & TPS_REG_PG_MAIN) ? " main_bad" : "", + (regstatus & TPS_REG_PG_CORE) ? " core_bad" : ""); +} + +static void dbg_chgconf(int por, char *buf, size_t len, u8 chgconfig) +{ + const char *hibit; + + if (por) + hibit = (chgconfig & TPS_CHARGE_POR) + ? "POR=69ms" : "POR=1sec"; + else + hibit = (chgconfig & TPS65013_AUA) ? "AUA" : ""; + + snprintf(buf, len, "%02x %s%s%s AC=%d%% USB=%dmA %sCharge\n", + chgconfig, hibit, + (chgconfig & TPS_CHARGE_RESET) ? " reset" : "", + (chgconfig & TPS_CHARGE_FAST) ? " fast" : "", + ({int p; switch ((chgconfig >> 3) & 3) { + case 3: p = 100; break; + case 2: p = 75; break; + case 1: p = 50; break; + default: p = 25; break; + }; p; }), + (chgconfig & TPS_VBUS_CHARGING) + ? ((chgconfig & TPS_VBUS_500MA) ? 500 : 100) + : 0, + (chgconfig & TPS_CHARGE_ENABLE) ? "" : "No"); +} + +#endif + +#ifdef DEBUG + +static void show_chgstatus(const char *label, u8 chgstatus) +{ + char buf [100]; + + dbg_chgstat(buf, sizeof buf, chgstatus); + pr_debug("%s: %s %s", DRIVER_NAME, label, buf); +} + +static void show_regstatus(const char *label, u8 regstatus) +{ + char buf [100]; + + dbg_regstat(buf, sizeof buf, regstatus); + pr_debug("%s: %s %s", DRIVER_NAME, label, buf); +} + +static void show_chgconfig(int por, const char *label, u8 chgconfig) +{ + char buf [100]; + + dbg_chgconf(por, buf, sizeof buf, chgconfig); + pr_debug("%s: %s %s", DRIVER_NAME, label, buf); +} + +#else + +static inline void show_chgstatus(const char *label, u8 chgstatus) { } +static inline void show_regstatus(const char *label, u8 chgstatus) { } +static inline void show_chgconfig(int por, const char *label, u8 chgconfig) { } + +#endif + +#ifdef CONFIG_DEBUG_FS + +static int dbg_show(struct seq_file *s, void *_) +{ + struct tps65010 *tps = s->private; + u8 value, v2; + unsigned i; + char buf[100]; + const char *chip; + + switch (tps->model) { + case TPS65010: chip = "tps65010"; break; + case TPS65011: chip = "tps65011"; break; + case TPS65012: chip = "tps65012"; break; + case TPS65013: chip = "tps65013"; break; + default: chip = NULL; break; + } + seq_printf(s, "driver %s\nversion %s\nchip %s\n\n", + DRIVER_NAME, DRIVER_VERSION, chip); + + mutex_lock(&tps->lock); + + /* FIXME how can we tell whether a battery is present? + * likely involves a charge gauging chip (like BQ26501). + */ + + seq_printf(s, "%scharging\n\n", tps->charging ? "" : "(not) "); + + + /* registers for monitoring battery charging and status; note + * that reading chgstat and regstat may ack IRQs... + */ + value = i2c_smbus_read_byte_data(tps->client, TPS_CHGCONFIG); + dbg_chgconf(tps->por, buf, sizeof buf, value); + seq_printf(s, "chgconfig %s", buf); + + value = i2c_smbus_read_byte_data(tps->client, TPS_CHGSTATUS); + dbg_chgstat(buf, sizeof buf, value); + seq_printf(s, "chgstat %s", buf); + value = i2c_smbus_read_byte_data(tps->client, TPS_MASK1); + dbg_chgstat(buf, sizeof buf, value); + seq_printf(s, "mask1 %s", buf); + /* ignore ackint1 */ + + value = i2c_smbus_read_byte_data(tps->client, TPS_REGSTATUS); + dbg_regstat(buf, sizeof buf, value); + seq_printf(s, "regstat %s", buf); + value = i2c_smbus_read_byte_data(tps->client, TPS_MASK2); + dbg_regstat(buf, sizeof buf, value); + seq_printf(s, "mask2 %s\n", buf); + /* ignore ackint2 */ + + (void) schedule_delayed_work(&tps->work, POWER_POLL_DELAY); + + + /* VMAIN voltage, enable lowpower, etc */ + value = i2c_smbus_read_byte_data(tps->client, TPS_VDCDC1); + seq_printf(s, "vdcdc1 %02x\n", value); + + /* VCORE voltage, vibrator on/off */ + value = i2c_smbus_read_byte_data(tps->client, TPS_VDCDC2); + seq_printf(s, "vdcdc2 %02x\n", value); + + /* both LD0s, and their lowpower behavior */ + value = i2c_smbus_read_byte_data(tps->client, TPS_VREGS1); + seq_printf(s, "vregs1 %02x\n\n", value); + + + /* LEDs and GPIOs */ + value = i2c_smbus_read_byte_data(tps->client, TPS_LED1_ON); + v2 = i2c_smbus_read_byte_data(tps->client, TPS_LED1_PER); + seq_printf(s, "led1 %s, on=%02x, per=%02x, %d/%d msec\n", + (value & 0x80) + ? ((v2 & 0x80) ? "on" : "off") + : ((v2 & 0x80) ? "blink" : "(nPG)"), + value, v2, + (value & 0x7f) * 10, (v2 & 0x7f) * 100); + + value = i2c_smbus_read_byte_data(tps->client, TPS_LED2_ON); + v2 = i2c_smbus_read_byte_data(tps->client, TPS_LED2_PER); + seq_printf(s, "led2 %s, on=%02x, per=%02x, %d/%d msec\n", + (value & 0x80) + ? ((v2 & 0x80) ? "on" : "off") + : ((v2 & 0x80) ? "blink" : "off"), + value, v2, + (value & 0x7f) * 10, (v2 & 0x7f) * 100); + + value = i2c_smbus_read_byte_data(tps->client, TPS_DEFGPIO); + v2 = i2c_smbus_read_byte_data(tps->client, TPS_MASK3); + seq_printf(s, "defgpio %02x mask3 %02x\n", value, v2); + + for (i = 0; i < 4; i++) { + if (value & (1 << (4 + i))) + seq_printf(s, " gpio%d-out %s\n", i + 1, + (value & (1 << i)) ? "low" : "hi "); + else + seq_printf(s, " gpio%d-in %s %s %s\n", i + 1, + (value & (1 << i)) ? "hi " : "low", + (v2 & (1 << i)) ? "no-irq" : "irq", + (v2 & (1 << (4 + i))) ? "rising" : "falling"); + } + + mutex_unlock(&tps->lock); + return 0; +} + +static int dbg_tps_open(struct inode *inode, struct file *file) +{ + return single_open(file, dbg_show, inode->i_private); +} + +static const struct file_operations debug_fops = { + .open = dbg_tps_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +#define DEBUG_FOPS &debug_fops + +#else +#define DEBUG_FOPS NULL +#endif + +/*-------------------------------------------------------------------------*/ + +/* handle IRQS in a task context, so we can use I2C calls */ +static void tps65010_interrupt(struct tps65010 *tps) +{ + u8 tmp = 0, mask, poll; + + /* IRQs won't trigger for certain events, but we can get + * others by polling (normally, with external power applied). + */ + poll = 0; + + /* regstatus irqs */ + if (tps->nmask2) { + tmp = i2c_smbus_read_byte_data(tps->client, TPS_REGSTATUS); + mask = tmp ^ tps->regstatus; + tps->regstatus = tmp; + mask &= tps->nmask2; + } else + mask = 0; + if (mask) { + tps->regstatus = tmp; + /* may need to shut something down ... */ + + /* "off" usually means deep sleep */ + if (tmp & TPS_REG_ONOFF) { + pr_info("%s: power off button\n", DRIVER_NAME); +#if 0 + /* REVISIT: this might need its own workqueue + * plus tweaks including deadlock avoidance ... + * also needs to get error handling and probably + * an #ifdef CONFIG_HIBERNATION + */ + hibernate(); +#endif + poll = 1; + } + } + + /* chgstatus irqs */ + if (tps->nmask1) { + tmp = i2c_smbus_read_byte_data(tps->client, TPS_CHGSTATUS); + mask = tmp ^ tps->chgstatus; + tps->chgstatus = tmp; + mask &= tps->nmask1; + } else + mask = 0; + if (mask) { + unsigned charging = 0; + + show_chgstatus("chg/irq", tmp); + if (tmp & (TPS_CHG_USB|TPS_CHG_AC)) + show_chgconfig(tps->por, "conf", tps->chgconf); + + /* Unless it was turned off or disabled, we charge any + * battery whenever there's power available for it + * and the charger hasn't been disabled. + */ + if (!(tps->chgstatus & ~(TPS_CHG_USB|TPS_CHG_AC)) + && (tps->chgstatus & (TPS_CHG_USB|TPS_CHG_AC)) + && (tps->chgconf & TPS_CHARGE_ENABLE) + ) { + if (tps->chgstatus & TPS_CHG_USB) { + /* VBUS options are readonly until reconnect */ + if (mask & TPS_CHG_USB) + set_bit(FLAG_VBUS_CHANGED, &tps->flags); + charging = 1; + } else if (tps->chgstatus & TPS_CHG_AC) + charging = 1; + } + if (charging != tps->charging) { + tps->charging = charging; + pr_info("%s: battery %scharging\n", + DRIVER_NAME, charging ? "" : + ((tps->chgstatus & (TPS_CHG_USB|TPS_CHG_AC)) + ? "NOT " : "dis")); + } + } + + /* always poll to detect (a) power removal, without tps65013 + * NO_CHG IRQ; or (b) restart of charging after stop. + */ + if ((tps->model != TPS65013 || !tps->charging) + && (tps->chgstatus & (TPS_CHG_USB|TPS_CHG_AC))) + poll = 1; + if (poll) + (void) schedule_delayed_work(&tps->work, POWER_POLL_DELAY); + + /* also potentially gpio-in rise or fall */ +} + +/* handle IRQs and polling using keventd for now */ +static void tps65010_work(struct work_struct *work) +{ + struct tps65010 *tps; + + tps = container_of(work, struct tps65010, work.work); + mutex_lock(&tps->lock); + + tps65010_interrupt(tps); + + if (test_and_clear_bit(FLAG_VBUS_CHANGED, &tps->flags)) { + int status; + u8 chgconfig, tmp; + + chgconfig = i2c_smbus_read_byte_data(tps->client, + TPS_CHGCONFIG); + chgconfig &= ~(TPS_VBUS_500MA | TPS_VBUS_CHARGING); + if (tps->vbus == 500) + chgconfig |= TPS_VBUS_500MA | TPS_VBUS_CHARGING; + else if (tps->vbus >= 100) + chgconfig |= TPS_VBUS_CHARGING; + + status = i2c_smbus_write_byte_data(tps->client, + TPS_CHGCONFIG, chgconfig); + + /* vbus update fails unless VBUS is connected! */ + tmp = i2c_smbus_read_byte_data(tps->client, TPS_CHGCONFIG); + tps->chgconf = tmp; + show_chgconfig(tps->por, "update vbus", tmp); + } + + if (test_and_clear_bit(FLAG_IRQ_ENABLE, &tps->flags)) + enable_irq(tps->client->irq); + + mutex_unlock(&tps->lock); +} + +static irqreturn_t tps65010_irq(int irq, void *_tps) +{ + struct tps65010 *tps = _tps; + + disable_irq_nosync(irq); + set_bit(FLAG_IRQ_ENABLE, &tps->flags); + (void) schedule_work(&tps->work.work); + return IRQ_HANDLED; +} + +/*-------------------------------------------------------------------------*/ + +/* offsets 0..3 == GPIO1..GPIO4 + * offsets 4..5 == LED1/nPG, LED2 (we set one of the non-BLINK modes) + * offset 6 == vibrator motor driver + */ +static void +tps65010_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + if (offset < 4) + tps65010_set_gpio_out_value(offset + 1, value); + else if (offset < 6) + tps65010_set_led(offset - 3, value ? ON : OFF); + else + tps65010_set_vib(value); +} + +static int +tps65010_output(struct gpio_chip *chip, unsigned offset, int value) +{ + /* GPIOs may be input-only */ + if (offset < 4) { + struct tps65010 *tps; + + tps = container_of(chip, struct tps65010, chip); + if (!(tps->outmask & (1 << offset))) + return -EINVAL; + tps65010_set_gpio_out_value(offset + 1, value); + } else if (offset < 6) + tps65010_set_led(offset - 3, value ? ON : OFF); + else + tps65010_set_vib(value); + + return 0; +} + +static int tps65010_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + int value; + struct tps65010 *tps; + + tps = container_of(chip, struct tps65010, chip); + + if (offset < 4) { + value = i2c_smbus_read_byte_data(tps->client, TPS_DEFGPIO); + if (value < 0) + return 0; + if (value & (1 << (offset + 4))) /* output */ + return !(value & (1 << offset)); + else /* input */ + return (value & (1 << offset)); + } + + /* REVISIT we *could* report LED1/nPG and LED2 state ... */ + return 0; +} + + +/*-------------------------------------------------------------------------*/ + +static struct tps65010 *the_tps; + +static int __exit tps65010_remove(struct i2c_client *client) +{ + struct tps65010 *tps = i2c_get_clientdata(client); + struct tps65010_board *board = client->dev.platform_data; + + if (board && board->teardown) { + int status = board->teardown(client, board->context); + if (status < 0) + dev_dbg(&client->dev, "board %s %s err %d\n", + "teardown", client->name, status); + } + if (client->irq > 0) + free_irq(client->irq, tps); + cancel_delayed_work(&tps->work); + flush_scheduled_work(); + debugfs_remove(tps->file); + kfree(tps); + i2c_set_clientdata(client, NULL); + the_tps = NULL; + return 0; +} + +static int tps65010_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct tps65010 *tps; + int status; + struct tps65010_board *board = client->dev.platform_data; + + if (the_tps) { + dev_dbg(&client->dev, "only one tps6501x chip allowed\n"); + return -ENODEV; + } + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EINVAL; + + tps = kzalloc(sizeof *tps, GFP_KERNEL); + if (!tps) + return -ENOMEM; + + mutex_init(&tps->lock); + INIT_DELAYED_WORK(&tps->work, tps65010_work); + tps->client = client; + tps->model = id->driver_data; + + /* the IRQ is active low, but many gpio lines can't support that + * so this driver uses falling-edge triggers instead. + */ + if (client->irq > 0) { + status = request_irq(client->irq, tps65010_irq, + IRQF_SAMPLE_RANDOM | IRQF_TRIGGER_FALLING, + DRIVER_NAME, tps); + if (status < 0) { + dev_dbg(&client->dev, "can't get IRQ %d, err %d\n", + client->irq, status); + goto fail1; + } + /* annoying race here, ideally we'd have an option + * to claim the irq now and enable it later. + * FIXME genirq IRQF_NOAUTOEN now solves that ... + */ + disable_irq(client->irq); + set_bit(FLAG_IRQ_ENABLE, &tps->flags); + } else + dev_warn(&client->dev, "IRQ not configured!\n"); + + + switch (tps->model) { + case TPS65010: + case TPS65012: + tps->por = 1; + break; + /* else CHGCONFIG.POR is replaced by AUA, enabling a WAIT mode */ + } + tps->chgconf = i2c_smbus_read_byte_data(client, TPS_CHGCONFIG); + show_chgconfig(tps->por, "conf/init", tps->chgconf); + + show_chgstatus("chg/init", + i2c_smbus_read_byte_data(client, TPS_CHGSTATUS)); + show_regstatus("reg/init", + i2c_smbus_read_byte_data(client, TPS_REGSTATUS)); + + pr_debug("%s: vdcdc1 0x%02x, vdcdc2 %02x, vregs1 %02x\n", DRIVER_NAME, + i2c_smbus_read_byte_data(client, TPS_VDCDC1), + i2c_smbus_read_byte_data(client, TPS_VDCDC2), + i2c_smbus_read_byte_data(client, TPS_VREGS1)); + pr_debug("%s: defgpio 0x%02x, mask3 0x%02x\n", DRIVER_NAME, + i2c_smbus_read_byte_data(client, TPS_DEFGPIO), + i2c_smbus_read_byte_data(client, TPS_MASK3)); + + i2c_set_clientdata(client, tps); + the_tps = tps; + +#if defined(CONFIG_USB_GADGET) && !defined(CONFIG_USB_OTG) + /* USB hosts can't draw VBUS. OTG devices could, later + * when OTG infrastructure enables it. USB peripherals + * could be relying on VBUS while booting, though. + */ + tps->vbus = 100; +#endif + + /* unmask the "interesting" irqs, then poll once to + * kickstart monitoring, initialize shadowed status + * registers, and maybe disable VBUS draw. + */ + tps->nmask1 = ~0; + (void) i2c_smbus_write_byte_data(client, TPS_MASK1, ~tps->nmask1); + + tps->nmask2 = TPS_REG_ONOFF; + if (tps->model == TPS65013) + tps->nmask2 |= TPS_REG_NO_CHG; + (void) i2c_smbus_write_byte_data(client, TPS_MASK2, ~tps->nmask2); + + (void) i2c_smbus_write_byte_data(client, TPS_MASK3, 0x0f + | i2c_smbus_read_byte_data(client, TPS_MASK3)); + + tps65010_work(&tps->work.work); + + tps->file = debugfs_create_file(DRIVER_NAME, S_IRUGO, NULL, + tps, DEBUG_FOPS); + + /* optionally register GPIOs */ + if (board && board->base > 0) { + tps->outmask = board->outmask; + + tps->chip.label = client->name; + tps->chip.dev = &client->dev; + tps->chip.owner = THIS_MODULE; + + tps->chip.set = tps65010_gpio_set; + tps->chip.direction_output = tps65010_output; + + /* NOTE: only partial support for inputs; nyet IRQs */ + tps->chip.get = tps65010_gpio_get; + + tps->chip.base = board->base; + tps->chip.ngpio = 7; + tps->chip.can_sleep = 1; + + status = gpiochip_add(&tps->chip); + if (status < 0) + dev_err(&client->dev, "can't add gpiochip, err %d\n", + status); + else if (board->setup) { + status = board->setup(client, board->context); + if (status < 0) { + dev_dbg(&client->dev, + "board %s %s err %d\n", + "setup", client->name, status); + status = 0; + } + } + } + + return 0; +fail1: + kfree(tps); + return status; +} + +static const struct i2c_device_id tps65010_id[] = { + { "tps65010", TPS65010 }, + { "tps65011", TPS65011 }, + { "tps65012", TPS65012 }, + { "tps65013", TPS65013 }, + { "tps65014", TPS65011 }, /* tps65011 charging at 6.5V max */ + { } +}; +MODULE_DEVICE_TABLE(i2c, tps65010_id); + +static struct i2c_driver tps65010_driver = { + .driver = { + .name = "tps65010", + }, + .probe = tps65010_probe, + .remove = __exit_p(tps65010_remove), + .id_table = tps65010_id, +}; + +/*-------------------------------------------------------------------------*/ + +/* Draw from VBUS: + * 0 mA -- DON'T DRAW (might supply power instead) + * 100 mA -- usb unit load (slowest charge rate) + * 500 mA -- usb high power (fast battery charge) + */ +int tps65010_set_vbus_draw(unsigned mA) +{ + unsigned long flags; + + if (!the_tps) + return -ENODEV; + + /* assumes non-SMP */ + local_irq_save(flags); + if (mA >= 500) + mA = 500; + else if (mA >= 100) + mA = 100; + else + mA = 0; + the_tps->vbus = mA; + if ((the_tps->chgstatus & TPS_CHG_USB) + && test_and_set_bit( + FLAG_VBUS_CHANGED, &the_tps->flags)) { + /* gadget drivers call this in_irq() */ + (void) schedule_work(&the_tps->work.work); + } + local_irq_restore(flags); + + return 0; +} +EXPORT_SYMBOL(tps65010_set_vbus_draw); + +/*-------------------------------------------------------------------------*/ +/* tps65010_set_gpio_out_value parameter: + * gpio: GPIO1, GPIO2, GPIO3 or GPIO4 + * value: LOW or HIGH + */ +int tps65010_set_gpio_out_value(unsigned gpio, unsigned value) +{ + int status; + unsigned defgpio; + + if (!the_tps) + return -ENODEV; + if ((gpio < GPIO1) || (gpio > GPIO4)) + return -EINVAL; + + mutex_lock(&the_tps->lock); + + defgpio = i2c_smbus_read_byte_data(the_tps->client, TPS_DEFGPIO); + + /* Configure GPIO for output */ + defgpio |= 1 << (gpio + 3); + + /* Writing 1 forces a logic 0 on that GPIO and vice versa */ + switch (value) { + case LOW: + defgpio |= 1 << (gpio - 1); /* set GPIO low by writing 1 */ + break; + /* case HIGH: */ + default: + defgpio &= ~(1 << (gpio - 1)); /* set GPIO high by writing 0 */ + break; + } + + status = i2c_smbus_write_byte_data(the_tps->client, + TPS_DEFGPIO, defgpio); + + pr_debug("%s: gpio%dout = %s, defgpio 0x%02x\n", DRIVER_NAME, + gpio, value ? "high" : "low", + i2c_smbus_read_byte_data(the_tps->client, TPS_DEFGPIO)); + + mutex_unlock(&the_tps->lock); + return status; +} +EXPORT_SYMBOL(tps65010_set_gpio_out_value); + +/*-------------------------------------------------------------------------*/ +/* tps65010_set_led parameter: + * led: LED1 or LED2 + * mode: ON, OFF or BLINK + */ +int tps65010_set_led(unsigned led, unsigned mode) +{ + int status; + unsigned led_on, led_per, offs; + + if (!the_tps) + return -ENODEV; + + if (led == LED1) + offs = 0; + else { + offs = 2; + led = LED2; + } + + mutex_lock(&the_tps->lock); + + pr_debug("%s: led%i_on 0x%02x\n", DRIVER_NAME, led, + i2c_smbus_read_byte_data(the_tps->client, + TPS_LED1_ON + offs)); + + pr_debug("%s: led%i_per 0x%02x\n", DRIVER_NAME, led, + i2c_smbus_read_byte_data(the_tps->client, + TPS_LED1_PER + offs)); + + switch (mode) { + case OFF: + led_on = 1 << 7; + led_per = 0 << 7; + break; + case ON: + led_on = 1 << 7; + led_per = 1 << 7; + break; + case BLINK: + led_on = 0x30 | (0 << 7); + led_per = 0x08 | (1 << 7); + break; + default: + printk(KERN_ERR "%s: Wrong mode parameter for set_led()\n", + DRIVER_NAME); + mutex_unlock(&the_tps->lock); + return -EINVAL; + } + + status = i2c_smbus_write_byte_data(the_tps->client, + TPS_LED1_ON + offs, led_on); + + if (status != 0) { + printk(KERN_ERR "%s: Failed to write led%i_on register\n", + DRIVER_NAME, led); + mutex_unlock(&the_tps->lock); + return status; + } + + pr_debug("%s: led%i_on 0x%02x\n", DRIVER_NAME, led, + i2c_smbus_read_byte_data(the_tps->client, TPS_LED1_ON + offs)); + + status = i2c_smbus_write_byte_data(the_tps->client, + TPS_LED1_PER + offs, led_per); + + if (status != 0) { + printk(KERN_ERR "%s: Failed to write led%i_per register\n", + DRIVER_NAME, led); + mutex_unlock(&the_tps->lock); + return status; + } + + pr_debug("%s: led%i_per 0x%02x\n", DRIVER_NAME, led, + i2c_smbus_read_byte_data(the_tps->client, + TPS_LED1_PER + offs)); + + mutex_unlock(&the_tps->lock); + + return status; +} +EXPORT_SYMBOL(tps65010_set_led); + +/*-------------------------------------------------------------------------*/ +/* tps65010_set_vib parameter: + * value: ON or OFF + */ +int tps65010_set_vib(unsigned value) +{ + int status; + unsigned vdcdc2; + + if (!the_tps) + return -ENODEV; + + mutex_lock(&the_tps->lock); + + vdcdc2 = i2c_smbus_read_byte_data(the_tps->client, TPS_VDCDC2); + vdcdc2 &= ~(1 << 1); + if (value) + vdcdc2 |= (1 << 1); + status = i2c_smbus_write_byte_data(the_tps->client, + TPS_VDCDC2, vdcdc2); + + pr_debug("%s: vibrator %s\n", DRIVER_NAME, value ? "on" : "off"); + + mutex_unlock(&the_tps->lock); + return status; +} +EXPORT_SYMBOL(tps65010_set_vib); + +/*-------------------------------------------------------------------------*/ +/* tps65010_set_low_pwr parameter: + * mode: ON or OFF + */ +int tps65010_set_low_pwr(unsigned mode) +{ + int status; + unsigned vdcdc1; + + if (!the_tps) + return -ENODEV; + + mutex_lock(&the_tps->lock); + + pr_debug("%s: %s low_pwr, vdcdc1 0x%02x\n", DRIVER_NAME, + mode ? "enable" : "disable", + i2c_smbus_read_byte_data(the_tps->client, TPS_VDCDC1)); + + vdcdc1 = i2c_smbus_read_byte_data(the_tps->client, TPS_VDCDC1); + + switch (mode) { + case OFF: + vdcdc1 &= ~TPS_ENABLE_LP; /* disable ENABLE_LP bit */ + break; + /* case ON: */ + default: + vdcdc1 |= TPS_ENABLE_LP; /* enable ENABLE_LP bit */ + break; + } + + status = i2c_smbus_write_byte_data(the_tps->client, + TPS_VDCDC1, vdcdc1); + + if (status != 0) + printk(KERN_ERR "%s: Failed to write vdcdc1 register\n", + DRIVER_NAME); + else + pr_debug("%s: vdcdc1 0x%02x\n", DRIVER_NAME, + i2c_smbus_read_byte_data(the_tps->client, TPS_VDCDC1)); + + mutex_unlock(&the_tps->lock); + + return status; +} +EXPORT_SYMBOL(tps65010_set_low_pwr); + +/*-------------------------------------------------------------------------*/ +/* tps65010_config_vregs1 parameter: + * value to be written to VREGS1 register + * Note: The complete register is written, set all bits you need + */ +int tps65010_config_vregs1(unsigned value) +{ + int status; + + if (!the_tps) + return -ENODEV; + + mutex_lock(&the_tps->lock); + + pr_debug("%s: vregs1 0x%02x\n", DRIVER_NAME, + i2c_smbus_read_byte_data(the_tps->client, TPS_VREGS1)); + + status = i2c_smbus_write_byte_data(the_tps->client, + TPS_VREGS1, value); + + if (status != 0) + printk(KERN_ERR "%s: Failed to write vregs1 register\n", + DRIVER_NAME); + else + pr_debug("%s: vregs1 0x%02x\n", DRIVER_NAME, + i2c_smbus_read_byte_data(the_tps->client, TPS_VREGS1)); + + mutex_unlock(&the_tps->lock); + + return status; +} +EXPORT_SYMBOL(tps65010_config_vregs1); + +/*-------------------------------------------------------------------------*/ +/* tps65013_set_low_pwr parameter: + * mode: ON or OFF + */ + +/* FIXME: Assumes AC or USB power is present. Setting AUA bit is not + required if power supply is through a battery */ + +int tps65013_set_low_pwr(unsigned mode) +{ + int status; + unsigned vdcdc1, chgconfig; + + if (!the_tps || the_tps->por) + return -ENODEV; + + mutex_lock(&the_tps->lock); + + pr_debug("%s: %s low_pwr, chgconfig 0x%02x vdcdc1 0x%02x\n", + DRIVER_NAME, + mode ? "enable" : "disable", + i2c_smbus_read_byte_data(the_tps->client, TPS_CHGCONFIG), + i2c_smbus_read_byte_data(the_tps->client, TPS_VDCDC1)); + + chgconfig = i2c_smbus_read_byte_data(the_tps->client, TPS_CHGCONFIG); + vdcdc1 = i2c_smbus_read_byte_data(the_tps->client, TPS_VDCDC1); + + switch (mode) { + case OFF: + chgconfig &= ~TPS65013_AUA; /* disable AUA bit */ + vdcdc1 &= ~TPS_ENABLE_LP; /* disable ENABLE_LP bit */ + break; + /* case ON: */ + default: + chgconfig |= TPS65013_AUA; /* enable AUA bit */ + vdcdc1 |= TPS_ENABLE_LP; /* enable ENABLE_LP bit */ + break; + } + + status = i2c_smbus_write_byte_data(the_tps->client, + TPS_CHGCONFIG, chgconfig); + if (status != 0) { + printk(KERN_ERR "%s: Failed to write chconfig register\n", + DRIVER_NAME); + mutex_unlock(&the_tps->lock); + return status; + } + + chgconfig = i2c_smbus_read_byte_data(the_tps->client, TPS_CHGCONFIG); + the_tps->chgconf = chgconfig; + show_chgconfig(0, "chgconf", chgconfig); + + status = i2c_smbus_write_byte_data(the_tps->client, + TPS_VDCDC1, vdcdc1); + + if (status != 0) + printk(KERN_ERR "%s: Failed to write vdcdc1 register\n", + DRIVER_NAME); + else + pr_debug("%s: vdcdc1 0x%02x\n", DRIVER_NAME, + i2c_smbus_read_byte_data(the_tps->client, TPS_VDCDC1)); + + mutex_unlock(&the_tps->lock); + + return status; +} +EXPORT_SYMBOL(tps65013_set_low_pwr); + +/*-------------------------------------------------------------------------*/ + +static int __init tps_init(void) +{ + u32 tries = 3; + int status = -ENODEV; + + printk(KERN_INFO "%s: version %s\n", DRIVER_NAME, DRIVER_VERSION); + + /* some boards have startup glitches */ + while (tries--) { + status = i2c_add_driver(&tps65010_driver); + if (the_tps) + break; + i2c_del_driver(&tps65010_driver); + if (!tries) { + printk(KERN_ERR "%s: no chip?\n", DRIVER_NAME); + return -ENODEV; + } + pr_debug("%s: re-probe ...\n", DRIVER_NAME); + msleep(10); + } + + return status; +} +/* NOTE: this MUST be initialized before the other parts of the system + * that rely on it ... but after the i2c bus on which this relies. + * That is, much earlier than on PC-type systems, which don't often use + * I2C as a core system bus. + */ +subsys_initcall(tps_init); + +static void __exit tps_exit(void) +{ + i2c_del_driver(&tps65010_driver); +} +module_exit(tps_exit); + diff --git a/drivers/i2c/chips/tsl2550.c b/drivers/i2c/chips/tsl2550.c new file mode 100644 index 0000000..1a9cc13 --- /dev/null +++ b/drivers/i2c/chips/tsl2550.c @@ -0,0 +1,489 @@ +/* + * tsl2550.c - Linux kernel modules for ambient light sensor + * + * Copyright (C) 2007 Rodolfo Giometti <giometti@linux.it> + * Copyright (C) 2007 Eurotech S.p.A. <info@eurotech.it> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/delay.h> + +#define TSL2550_DRV_NAME "tsl2550" +#define DRIVER_VERSION "1.1.1" + +/* + * Defines + */ + +#define TSL2550_POWER_DOWN 0x00 +#define TSL2550_POWER_UP 0x03 +#define TSL2550_STANDARD_RANGE 0x18 +#define TSL2550_EXTENDED_RANGE 0x1d +#define TSL2550_READ_ADC0 0x43 +#define TSL2550_READ_ADC1 0x83 + +/* + * Structs + */ + +struct tsl2550_data { + struct i2c_client *client; + struct mutex update_lock; + + unsigned int power_state : 1; + unsigned int operating_mode : 1; +}; + +/* + * Global data + */ + +static const u8 TSL2550_MODE_RANGE[2] = { + TSL2550_STANDARD_RANGE, TSL2550_EXTENDED_RANGE, +}; + +/* + * Management functions + */ + +static int tsl2550_set_operating_mode(struct i2c_client *client, int mode) +{ + struct tsl2550_data *data = i2c_get_clientdata(client); + + int ret = i2c_smbus_write_byte(client, TSL2550_MODE_RANGE[mode]); + + data->operating_mode = mode; + + return ret; +} + +static int tsl2550_set_power_state(struct i2c_client *client, int state) +{ + struct tsl2550_data *data = i2c_get_clientdata(client); + int ret; + + if (state == 0) + ret = i2c_smbus_write_byte(client, TSL2550_POWER_DOWN); + else { + ret = i2c_smbus_write_byte(client, TSL2550_POWER_UP); + + /* On power up we should reset operating mode also... */ + tsl2550_set_operating_mode(client, data->operating_mode); + } + + data->power_state = state; + + return ret; +} + +static int tsl2550_get_adc_value(struct i2c_client *client, u8 cmd) +{ + unsigned long end; + int loop = 0, ret = 0; + + /* + * Read ADC channel waiting at most 400ms (see data sheet for further + * info). + * To avoid long busy wait we spin for few milliseconds then + * start sleeping. + */ + end = jiffies + msecs_to_jiffies(400); + while (time_before(jiffies, end)) { + i2c_smbus_write_byte(client, cmd); + + if (loop++ < 5) + mdelay(1); + else + msleep(1); + + ret = i2c_smbus_read_byte(client); + if (ret < 0) + return ret; + else if (ret & 0x0080) + break; + } + if (!(ret & 0x80)) + return -EIO; + return ret & 0x7f; /* remove the "valid" bit */ +} + +/* + * LUX calculation + */ + +#define TSL2550_MAX_LUX 1846 + +static const u8 ratio_lut[] = { + 100, 100, 100, 100, 100, 100, 100, 100, + 100, 100, 100, 100, 100, 100, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 98, 98, 98, 98, 98, + 98, 98, 97, 97, 97, 97, 97, 96, + 96, 96, 96, 95, 95, 95, 94, 94, + 93, 93, 93, 92, 92, 91, 91, 90, + 89, 89, 88, 87, 87, 86, 85, 84, + 83, 82, 81, 80, 79, 78, 77, 75, + 74, 73, 71, 69, 68, 66, 64, 62, + 60, 58, 56, 54, 52, 49, 47, 44, + 42, 41, 40, 40, 39, 39, 38, 38, + 37, 37, 37, 36, 36, 36, 35, 35, + 35, 35, 34, 34, 34, 34, 33, 33, + 33, 33, 32, 32, 32, 32, 32, 31, + 31, 31, 31, 31, 30, 30, 30, 30, + 30, +}; + +static const u16 count_lut[] = { + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, + 16, 18, 20, 22, 24, 26, 28, 30, + 32, 34, 36, 38, 40, 42, 44, 46, + 49, 53, 57, 61, 65, 69, 73, 77, + 81, 85, 89, 93, 97, 101, 105, 109, + 115, 123, 131, 139, 147, 155, 163, 171, + 179, 187, 195, 203, 211, 219, 227, 235, + 247, 263, 279, 295, 311, 327, 343, 359, + 375, 391, 407, 423, 439, 455, 471, 487, + 511, 543, 575, 607, 639, 671, 703, 735, + 767, 799, 831, 863, 895, 927, 959, 991, + 1039, 1103, 1167, 1231, 1295, 1359, 1423, 1487, + 1551, 1615, 1679, 1743, 1807, 1871, 1935, 1999, + 2095, 2223, 2351, 2479, 2607, 2735, 2863, 2991, + 3119, 3247, 3375, 3503, 3631, 3759, 3887, 4015, +}; + +/* + * This function is described into Taos TSL2550 Designer's Notebook + * pages 2, 3. + */ +static int tsl2550_calculate_lux(u8 ch0, u8 ch1) +{ + unsigned int lux; + + /* Look up count from channel values */ + u16 c0 = count_lut[ch0]; + u16 c1 = count_lut[ch1]; + + /* + * Calculate ratio. + * Note: the "128" is a scaling factor + */ + u8 r = 128; + + /* Avoid division by 0 and count 1 cannot be greater than count 0 */ + if (c0 && (c1 <= c0)) + r = c1 * 128 / c0; + else + return -1; + + /* Calculate LUX */ + lux = ((c0 - c1) * ratio_lut[r]) / 256; + + /* LUX range check */ + return lux > TSL2550_MAX_LUX ? TSL2550_MAX_LUX : lux; +} + +/* + * SysFS support + */ + +static ssize_t tsl2550_show_power_state(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tsl2550_data *data = i2c_get_clientdata(to_i2c_client(dev)); + + return sprintf(buf, "%u\n", data->power_state); +} + +static ssize_t tsl2550_store_power_state(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct tsl2550_data *data = i2c_get_clientdata(client); + unsigned long val = simple_strtoul(buf, NULL, 10); + int ret; + + if (val < 0 || val > 1) + return -EINVAL; + + mutex_lock(&data->update_lock); + ret = tsl2550_set_power_state(client, val); + mutex_unlock(&data->update_lock); + + if (ret < 0) + return ret; + + return count; +} + +static DEVICE_ATTR(power_state, S_IWUSR | S_IRUGO, + tsl2550_show_power_state, tsl2550_store_power_state); + +static ssize_t tsl2550_show_operating_mode(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tsl2550_data *data = i2c_get_clientdata(to_i2c_client(dev)); + + return sprintf(buf, "%u\n", data->operating_mode); +} + +static ssize_t tsl2550_store_operating_mode(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct tsl2550_data *data = i2c_get_clientdata(client); + unsigned long val = simple_strtoul(buf, NULL, 10); + int ret; + + if (val < 0 || val > 1) + return -EINVAL; + + if (data->power_state == 0) + return -EBUSY; + + mutex_lock(&data->update_lock); + ret = tsl2550_set_operating_mode(client, val); + mutex_unlock(&data->update_lock); + + if (ret < 0) + return ret; + + return count; +} + +static DEVICE_ATTR(operating_mode, S_IWUSR | S_IRUGO, + tsl2550_show_operating_mode, tsl2550_store_operating_mode); + +static ssize_t __tsl2550_show_lux(struct i2c_client *client, char *buf) +{ + u8 ch0, ch1; + int ret; + + ret = tsl2550_get_adc_value(client, TSL2550_READ_ADC0); + if (ret < 0) + return ret; + ch0 = ret; + + mdelay(1); + + ret = tsl2550_get_adc_value(client, TSL2550_READ_ADC1); + if (ret < 0) + return ret; + ch1 = ret; + + /* Do the job */ + ret = tsl2550_calculate_lux(ch0, ch1); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", ret); +} + +static ssize_t tsl2550_show_lux1_input(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct tsl2550_data *data = i2c_get_clientdata(client); + int ret; + + /* No LUX data if not operational */ + if (!data->power_state) + return -EBUSY; + + mutex_lock(&data->update_lock); + ret = __tsl2550_show_lux(client, buf); + mutex_unlock(&data->update_lock); + + return ret; +} + +static DEVICE_ATTR(lux1_input, S_IRUGO, + tsl2550_show_lux1_input, NULL); + +static struct attribute *tsl2550_attributes[] = { + &dev_attr_power_state.attr, + &dev_attr_operating_mode.attr, + &dev_attr_lux1_input.attr, + NULL +}; + +static const struct attribute_group tsl2550_attr_group = { + .attrs = tsl2550_attributes, +}; + +/* + * Initialization function + */ + +static int tsl2550_init_client(struct i2c_client *client) +{ + struct tsl2550_data *data = i2c_get_clientdata(client); + int err; + + /* + * Probe the chip. To do so we try to power up the device and then to + * read back the 0x03 code + */ + err = i2c_smbus_write_byte(client, TSL2550_POWER_UP); + if (err < 0) + return err; + mdelay(1); + if (i2c_smbus_read_byte(client) != TSL2550_POWER_UP) + return -ENODEV; + data->power_state = 1; + + /* Set the default operating mode */ + err = i2c_smbus_write_byte(client, + TSL2550_MODE_RANGE[data->operating_mode]); + if (err < 0) + return err; + + return 0; +} + +/* + * I2C init/probing/exit functions + */ + +static struct i2c_driver tsl2550_driver; +static int __devinit tsl2550_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct tsl2550_data *data; + int *opmode, err = 0; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) { + err = -EIO; + goto exit; + } + + data = kzalloc(sizeof(struct tsl2550_data), GFP_KERNEL); + if (!data) { + err = -ENOMEM; + goto exit; + } + data->client = client; + i2c_set_clientdata(client, data); + + /* Check platform data */ + opmode = client->dev.platform_data; + if (opmode) { + if (*opmode < 0 || *opmode > 1) { + dev_err(&client->dev, "invalid operating_mode (%d)\n", + *opmode); + err = -EINVAL; + goto exit_kfree; + } + data->operating_mode = *opmode; + } else + data->operating_mode = 0; /* default mode is standard */ + dev_info(&client->dev, "%s operating mode\n", + data->operating_mode ? "extended" : "standard"); + + mutex_init(&data->update_lock); + + /* Initialize the TSL2550 chip */ + err = tsl2550_init_client(client); + if (err) + goto exit_kfree; + + /* Register sysfs hooks */ + err = sysfs_create_group(&client->dev.kobj, &tsl2550_attr_group); + if (err) + goto exit_kfree; + + dev_info(&client->dev, "support ver. %s enabled\n", DRIVER_VERSION); + + return 0; + +exit_kfree: + kfree(data); +exit: + return err; +} + +static int __devexit tsl2550_remove(struct i2c_client *client) +{ + sysfs_remove_group(&client->dev.kobj, &tsl2550_attr_group); + + /* Power down the device */ + tsl2550_set_power_state(client, 0); + + kfree(i2c_get_clientdata(client)); + + return 0; +} + +#ifdef CONFIG_PM + +static int tsl2550_suspend(struct i2c_client *client, pm_message_t mesg) +{ + return tsl2550_set_power_state(client, 0); +} + +static int tsl2550_resume(struct i2c_client *client) +{ + return tsl2550_set_power_state(client, 1); +} + +#else + +#define tsl2550_suspend NULL +#define tsl2550_resume NULL + +#endif /* CONFIG_PM */ + +static const struct i2c_device_id tsl2550_id[] = { + { "tsl2550", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tsl2550_id); + +static struct i2c_driver tsl2550_driver = { + .driver = { + .name = TSL2550_DRV_NAME, + .owner = THIS_MODULE, + }, + .suspend = tsl2550_suspend, + .resume = tsl2550_resume, + .probe = tsl2550_probe, + .remove = __devexit_p(tsl2550_remove), + .id_table = tsl2550_id, +}; + +static int __init tsl2550_init(void) +{ + return i2c_add_driver(&tsl2550_driver); +} + +static void __exit tsl2550_exit(void) +{ + i2c_del_driver(&tsl2550_driver); +} + +MODULE_AUTHOR("Rodolfo Giometti <giometti@linux.it>"); +MODULE_DESCRIPTION("TSL2550 ambient light sensor driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRIVER_VERSION); + +module_init(tsl2550_init); +module_exit(tsl2550_exit); diff --git a/drivers/i2c/i2c-boardinfo.c b/drivers/i2c/i2c-boardinfo.c new file mode 100644 index 0000000..ffb35f0 --- /dev/null +++ b/drivers/i2c/i2c-boardinfo.c @@ -0,0 +1,90 @@ +/* + * i2c-boardinfo.h - collect pre-declarations of I2C devices + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/kernel.h> +#include <linux/i2c.h> + +#include "i2c-core.h" + + +/* These symbols are exported ONLY FOR the i2c core. + * No other users will be supported. + */ +DEFINE_MUTEX(__i2c_board_lock); +EXPORT_SYMBOL_GPL(__i2c_board_lock); + +LIST_HEAD(__i2c_board_list); +EXPORT_SYMBOL_GPL(__i2c_board_list); + +int __i2c_first_dynamic_bus_num; +EXPORT_SYMBOL_GPL(__i2c_first_dynamic_bus_num); + + +/** + * i2c_register_board_info - statically declare I2C devices + * @busnum: identifies the bus to which these devices belong + * @info: vector of i2c device descriptors + * @len: how many descriptors in the vector; may be zero to reserve + * the specified bus number. + * + * Systems using the Linux I2C driver stack can declare tables of board info + * while they initialize. This should be done in board-specific init code + * near arch_initcall() time, or equivalent, before any I2C adapter driver is + * registered. For example, mainboard init code could define several devices, + * as could the init code for each daughtercard in a board stack. + * + * The I2C devices will be created later, after the adapter for the relevant + * bus has been registered. After that moment, standard driver model tools + * are used to bind "new style" I2C drivers to the devices. The bus number + * for any device declared using this routine is not available for dynamic + * allocation. + * + * The board info passed can safely be __initdata, but be careful of embedded + * pointers (for platform_data, functions, etc) since that won't be copied. + */ +int __init +i2c_register_board_info(int busnum, + struct i2c_board_info const *info, unsigned len) +{ + int status; + + mutex_lock(&__i2c_board_lock); + + /* dynamic bus numbers will be assigned after the last static one */ + if (busnum >= __i2c_first_dynamic_bus_num) + __i2c_first_dynamic_bus_num = busnum + 1; + + for (status = 0; len; len--, info++) { + struct i2c_devinfo *devinfo; + + devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL); + if (!devinfo) { + pr_debug("i2c-core: can't register boardinfo!\n"); + status = -ENOMEM; + break; + } + + devinfo->busnum = busnum; + devinfo->board_info = *info; + list_add_tail(&devinfo->list, &__i2c_board_list); + } + + mutex_unlock(&__i2c_board_lock); + + return status; +} diff --git a/drivers/i2c/i2c-core.c b/drivers/i2c/i2c-core.c new file mode 100644 index 0000000..06d9042 --- /dev/null +++ b/drivers/i2c/i2c-core.c @@ -0,0 +1,2063 @@ +/* i2c-core.c - a device driver for the iic-bus interface */ +/* ------------------------------------------------------------------------- */ +/* Copyright (C) 1995-99 Simon G. Vogl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +/* ------------------------------------------------------------------------- */ + +/* With some changes from Kyösti Mälkki <kmalkki@cc.hut.fi>. + All SMBus-related things are written by Frodo Looijaard <frodol@dds.nl> + SMBus 2.0 support by Mark Studebaker <mdsxyz123@yahoo.com> and + Jean Delvare <khali@linux-fr.org> */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/idr.h> +#include <linux/platform_device.h> +#include <linux/mutex.h> +#include <linux/completion.h> +#include <linux/hardirq.h> +#include <linux/irqflags.h> +#include <asm/uaccess.h> + +#include "i2c-core.h" + + +static DEFINE_MUTEX(core_lock); +static DEFINE_IDR(i2c_adapter_idr); + +#define is_newstyle_driver(d) ((d)->probe || (d)->remove || (d)->detect) + +static int i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver); + +/* ------------------------------------------------------------------------- */ + +static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id, + const struct i2c_client *client) +{ + while (id->name[0]) { + if (strcmp(client->name, id->name) == 0) + return id; + id++; + } + return NULL; +} + +static int i2c_device_match(struct device *dev, struct device_driver *drv) +{ + struct i2c_client *client = to_i2c_client(dev); + struct i2c_driver *driver = to_i2c_driver(drv); + + /* make legacy i2c drivers bypass driver model probing entirely; + * such drivers scan each i2c adapter/bus themselves. + */ + if (!is_newstyle_driver(driver)) + return 0; + + /* match on an id table if there is one */ + if (driver->id_table) + return i2c_match_id(driver->id_table, client) != NULL; + + return 0; +} + +#ifdef CONFIG_HOTPLUG + +/* uevent helps with hotplug: modprobe -q $(MODALIAS) */ +static int i2c_device_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct i2c_client *client = to_i2c_client(dev); + + /* by definition, legacy drivers can't hotplug */ + if (dev->driver) + return 0; + + if (add_uevent_var(env, "MODALIAS=%s%s", + I2C_MODULE_PREFIX, client->name)) + return -ENOMEM; + dev_dbg(dev, "uevent\n"); + return 0; +} + +#else +#define i2c_device_uevent NULL +#endif /* CONFIG_HOTPLUG */ + +static int i2c_device_probe(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct i2c_driver *driver = to_i2c_driver(dev->driver); + int status; + + if (!driver->probe || !driver->id_table) + return -ENODEV; + client->driver = driver; + if (!device_can_wakeup(&client->dev)) + device_init_wakeup(&client->dev, + client->flags & I2C_CLIENT_WAKE); + dev_dbg(dev, "probe\n"); + + status = driver->probe(client, i2c_match_id(driver->id_table, client)); + if (status) + client->driver = NULL; + return status; +} + +static int i2c_device_remove(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct i2c_driver *driver; + int status; + + if (!dev->driver) + return 0; + + driver = to_i2c_driver(dev->driver); + if (driver->remove) { + dev_dbg(dev, "remove\n"); + status = driver->remove(client); + } else { + dev->driver = NULL; + status = 0; + } + if (status == 0) + client->driver = NULL; + return status; +} + +static void i2c_device_shutdown(struct device *dev) +{ + struct i2c_driver *driver; + + if (!dev->driver) + return; + driver = to_i2c_driver(dev->driver); + if (driver->shutdown) + driver->shutdown(to_i2c_client(dev)); +} + +static int i2c_device_suspend(struct device * dev, pm_message_t mesg) +{ + struct i2c_driver *driver; + + if (!dev->driver) + return 0; + driver = to_i2c_driver(dev->driver); + if (!driver->suspend) + return 0; + return driver->suspend(to_i2c_client(dev), mesg); +} + +static int i2c_device_resume(struct device * dev) +{ + struct i2c_driver *driver; + + if (!dev->driver) + return 0; + driver = to_i2c_driver(dev->driver); + if (!driver->resume) + return 0; + return driver->resume(to_i2c_client(dev)); +} + +static void i2c_client_release(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + complete(&client->released); +} + +static void i2c_client_dev_release(struct device *dev) +{ + kfree(to_i2c_client(dev)); +} + +static ssize_t show_client_name(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + return sprintf(buf, "%s\n", client->name); +} + +static ssize_t show_modalias(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + return sprintf(buf, "%s%s\n", I2C_MODULE_PREFIX, client->name); +} + +static struct device_attribute i2c_dev_attrs[] = { + __ATTR(name, S_IRUGO, show_client_name, NULL), + /* modalias helps coldplug: modprobe $(cat .../modalias) */ + __ATTR(modalias, S_IRUGO, show_modalias, NULL), + { }, +}; + +struct bus_type i2c_bus_type = { + .name = "i2c", + .dev_attrs = i2c_dev_attrs, + .match = i2c_device_match, + .uevent = i2c_device_uevent, + .probe = i2c_device_probe, + .remove = i2c_device_remove, + .shutdown = i2c_device_shutdown, + .suspend = i2c_device_suspend, + .resume = i2c_device_resume, +}; +EXPORT_SYMBOL_GPL(i2c_bus_type); + + +/** + * i2c_verify_client - return parameter as i2c_client, or NULL + * @dev: device, probably from some driver model iterator + * + * When traversing the driver model tree, perhaps using driver model + * iterators like @device_for_each_child(), you can't assume very much + * about the nodes you find. Use this function to avoid oopses caused + * by wrongly treating some non-I2C device as an i2c_client. + */ +struct i2c_client *i2c_verify_client(struct device *dev) +{ + return (dev->bus == &i2c_bus_type) + ? to_i2c_client(dev) + : NULL; +} +EXPORT_SYMBOL(i2c_verify_client); + + +/** + * i2c_new_device - instantiate an i2c device for use with a new style driver + * @adap: the adapter managing the device + * @info: describes one I2C device; bus_num is ignored + * Context: can sleep + * + * Create a device to work with a new style i2c driver, where binding is + * handled through driver model probe()/remove() methods. This call is not + * appropriate for use by mainboad initialization logic, which usually runs + * during an arch_initcall() long before any i2c_adapter could exist. + * + * This returns the new i2c client, which may be saved for later use with + * i2c_unregister_device(); or NULL to indicate an error. + */ +struct i2c_client * +i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info) +{ + struct i2c_client *client; + int status; + + client = kzalloc(sizeof *client, GFP_KERNEL); + if (!client) + return NULL; + + client->adapter = adap; + + client->dev.platform_data = info->platform_data; + + if (info->archdata) + client->dev.archdata = *info->archdata; + + client->flags = info->flags; + client->addr = info->addr; + client->irq = info->irq; + + strlcpy(client->name, info->type, sizeof(client->name)); + + /* a new style driver may be bound to this device when we + * return from this function, or any later moment (e.g. maybe + * hotplugging will load the driver module). and the device + * refcount model is the standard driver model one. + */ + status = i2c_attach_client(client); + if (status < 0) { + kfree(client); + client = NULL; + } + return client; +} +EXPORT_SYMBOL_GPL(i2c_new_device); + + +/** + * i2c_unregister_device - reverse effect of i2c_new_device() + * @client: value returned from i2c_new_device() + * Context: can sleep + */ +void i2c_unregister_device(struct i2c_client *client) +{ + struct i2c_adapter *adapter = client->adapter; + struct i2c_driver *driver = client->driver; + + if (driver && !is_newstyle_driver(driver)) { + dev_err(&client->dev, "can't unregister devices " + "with legacy drivers\n"); + WARN_ON(1); + return; + } + + if (adapter->client_unregister) { + if (adapter->client_unregister(client)) { + dev_warn(&client->dev, + "client_unregister [%s] failed\n", + client->name); + } + } + + mutex_lock(&adapter->clist_lock); + list_del(&client->list); + mutex_unlock(&adapter->clist_lock); + + device_unregister(&client->dev); +} +EXPORT_SYMBOL_GPL(i2c_unregister_device); + + +static const struct i2c_device_id dummy_id[] = { + { "dummy", 0 }, + { }, +}; + +static int dummy_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + return 0; +} + +static int dummy_remove(struct i2c_client *client) +{ + return 0; +} + +static struct i2c_driver dummy_driver = { + .driver.name = "dummy", + .probe = dummy_probe, + .remove = dummy_remove, + .id_table = dummy_id, +}; + +/** + * i2c_new_dummy - return a new i2c device bound to a dummy driver + * @adapter: the adapter managing the device + * @address: seven bit address to be used + * Context: can sleep + * + * This returns an I2C client bound to the "dummy" driver, intended for use + * with devices that consume multiple addresses. Examples of such chips + * include various EEPROMS (like 24c04 and 24c08 models). + * + * These dummy devices have two main uses. First, most I2C and SMBus calls + * except i2c_transfer() need a client handle; the dummy will be that handle. + * And second, this prevents the specified address from being bound to a + * different driver. + * + * This returns the new i2c client, which should be saved for later use with + * i2c_unregister_device(); or NULL to indicate an error. + */ +struct i2c_client * +i2c_new_dummy(struct i2c_adapter *adapter, u16 address) +{ + struct i2c_board_info info = { + I2C_BOARD_INFO("dummy", address), + }; + + return i2c_new_device(adapter, &info); +} +EXPORT_SYMBOL_GPL(i2c_new_dummy); + +/* ------------------------------------------------------------------------- */ + +/* I2C bus adapters -- one roots each I2C or SMBUS segment */ + +static void i2c_adapter_dev_release(struct device *dev) +{ + struct i2c_adapter *adap = to_i2c_adapter(dev); + complete(&adap->dev_released); +} + +static ssize_t +show_adapter_name(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct i2c_adapter *adap = to_i2c_adapter(dev); + return sprintf(buf, "%s\n", adap->name); +} + +static struct device_attribute i2c_adapter_attrs[] = { + __ATTR(name, S_IRUGO, show_adapter_name, NULL), + { }, +}; + +static struct class i2c_adapter_class = { + .owner = THIS_MODULE, + .name = "i2c-adapter", + .dev_attrs = i2c_adapter_attrs, +}; + +static void i2c_scan_static_board_info(struct i2c_adapter *adapter) +{ + struct i2c_devinfo *devinfo; + + mutex_lock(&__i2c_board_lock); + list_for_each_entry(devinfo, &__i2c_board_list, list) { + if (devinfo->busnum == adapter->nr + && !i2c_new_device(adapter, + &devinfo->board_info)) + printk(KERN_ERR "i2c-core: can't create i2c%d-%04x\n", + i2c_adapter_id(adapter), + devinfo->board_info.addr); + } + mutex_unlock(&__i2c_board_lock); +} + +static int i2c_do_add_adapter(struct device_driver *d, void *data) +{ + struct i2c_driver *driver = to_i2c_driver(d); + struct i2c_adapter *adap = data; + + /* Detect supported devices on that bus, and instantiate them */ + i2c_detect(adap, driver); + + /* Let legacy drivers scan this bus for matching devices */ + if (driver->attach_adapter) { + /* We ignore the return code; if it fails, too bad */ + driver->attach_adapter(adap); + } + return 0; +} + +static int i2c_register_adapter(struct i2c_adapter *adap) +{ + int res = 0, dummy; + + /* Can't register until after driver model init */ + if (unlikely(WARN_ON(!i2c_bus_type.p))) + return -EAGAIN; + + mutex_init(&adap->bus_lock); + mutex_init(&adap->clist_lock); + INIT_LIST_HEAD(&adap->clients); + + mutex_lock(&core_lock); + + /* Add the adapter to the driver core. + * If the parent pointer is not set up, + * we add this adapter to the host bus. + */ + if (adap->dev.parent == NULL) { + adap->dev.parent = &platform_bus; + pr_debug("I2C adapter driver [%s] forgot to specify " + "physical device\n", adap->name); + } + sprintf(adap->dev.bus_id, "i2c-%d", adap->nr); + adap->dev.release = &i2c_adapter_dev_release; + adap->dev.class = &i2c_adapter_class; + res = device_register(&adap->dev); + if (res) + goto out_list; + + dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name); + + /* create pre-declared device nodes for new-style drivers */ + if (adap->nr < __i2c_first_dynamic_bus_num) + i2c_scan_static_board_info(adap); + + /* Notify drivers */ + dummy = bus_for_each_drv(&i2c_bus_type, NULL, adap, + i2c_do_add_adapter); + +out_unlock: + mutex_unlock(&core_lock); + return res; + +out_list: + idr_remove(&i2c_adapter_idr, adap->nr); + goto out_unlock; +} + +/** + * i2c_add_adapter - declare i2c adapter, use dynamic bus number + * @adapter: the adapter to add + * Context: can sleep + * + * This routine is used to declare an I2C adapter when its bus number + * doesn't matter. Examples: for I2C adapters dynamically added by + * USB links or PCI plugin cards. + * + * When this returns zero, a new bus number was allocated and stored + * in adap->nr, and the specified adapter became available for clients. + * Otherwise, a negative errno value is returned. + */ +int i2c_add_adapter(struct i2c_adapter *adapter) +{ + int id, res = 0; + +retry: + if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0) + return -ENOMEM; + + mutex_lock(&core_lock); + /* "above" here means "above or equal to", sigh */ + res = idr_get_new_above(&i2c_adapter_idr, adapter, + __i2c_first_dynamic_bus_num, &id); + mutex_unlock(&core_lock); + + if (res < 0) { + if (res == -EAGAIN) + goto retry; + return res; + } + + adapter->nr = id; + return i2c_register_adapter(adapter); +} +EXPORT_SYMBOL(i2c_add_adapter); + +/** + * i2c_add_numbered_adapter - declare i2c adapter, use static bus number + * @adap: the adapter to register (with adap->nr initialized) + * Context: can sleep + * + * This routine is used to declare an I2C adapter when its bus number + * matters. For example, use it for I2C adapters from system-on-chip CPUs, + * or otherwise built in to the system's mainboard, and where i2c_board_info + * is used to properly configure I2C devices. + * + * If no devices have pre-been declared for this bus, then be sure to + * register the adapter before any dynamically allocated ones. Otherwise + * the required bus ID may not be available. + * + * When this returns zero, the specified adapter became available for + * clients using the bus number provided in adap->nr. Also, the table + * of I2C devices pre-declared using i2c_register_board_info() is scanned, + * and the appropriate driver model device nodes are created. Otherwise, a + * negative errno value is returned. + */ +int i2c_add_numbered_adapter(struct i2c_adapter *adap) +{ + int id; + int status; + + if (adap->nr & ~MAX_ID_MASK) + return -EINVAL; + +retry: + if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0) + return -ENOMEM; + + mutex_lock(&core_lock); + /* "above" here means "above or equal to", sigh; + * we need the "equal to" result to force the result + */ + status = idr_get_new_above(&i2c_adapter_idr, adap, adap->nr, &id); + if (status == 0 && id != adap->nr) { + status = -EBUSY; + idr_remove(&i2c_adapter_idr, id); + } + mutex_unlock(&core_lock); + if (status == -EAGAIN) + goto retry; + + if (status == 0) + status = i2c_register_adapter(adap); + return status; +} +EXPORT_SYMBOL_GPL(i2c_add_numbered_adapter); + +static int i2c_do_del_adapter(struct device_driver *d, void *data) +{ + struct i2c_driver *driver = to_i2c_driver(d); + struct i2c_adapter *adapter = data; + struct i2c_client *client, *_n; + int res; + + /* Remove the devices we created ourselves */ + list_for_each_entry_safe(client, _n, &driver->clients, detected) { + if (client->adapter == adapter) { + dev_dbg(&adapter->dev, "Removing %s at 0x%x\n", + client->name, client->addr); + list_del(&client->detected); + i2c_unregister_device(client); + } + } + + if (!driver->detach_adapter) + return 0; + res = driver->detach_adapter(adapter); + if (res) + dev_err(&adapter->dev, "detach_adapter failed (%d) " + "for driver [%s]\n", res, driver->driver.name); + return res; +} + +/** + * i2c_del_adapter - unregister I2C adapter + * @adap: the adapter being unregistered + * Context: can sleep + * + * This unregisters an I2C adapter which was previously registered + * by @i2c_add_adapter or @i2c_add_numbered_adapter. + */ +int i2c_del_adapter(struct i2c_adapter *adap) +{ + struct i2c_client *client, *_n; + int res = 0; + + mutex_lock(&core_lock); + + /* First make sure that this adapter was ever added */ + if (idr_find(&i2c_adapter_idr, adap->nr) != adap) { + pr_debug("i2c-core: attempting to delete unregistered " + "adapter [%s]\n", adap->name); + res = -EINVAL; + goto out_unlock; + } + + /* Tell drivers about this removal */ + res = bus_for_each_drv(&i2c_bus_type, NULL, adap, + i2c_do_del_adapter); + if (res) + goto out_unlock; + + /* detach any active clients. This must be done first, because + * it can fail; in which case we give up. */ + list_for_each_entry_safe_reverse(client, _n, &adap->clients, list) { + struct i2c_driver *driver; + + driver = client->driver; + + /* new style, follow standard driver model */ + if (!driver || is_newstyle_driver(driver)) { + i2c_unregister_device(client); + continue; + } + + /* legacy drivers create and remove clients themselves */ + if ((res = driver->detach_client(client))) { + dev_err(&adap->dev, "detach_client failed for client " + "[%s] at address 0x%02x\n", client->name, + client->addr); + goto out_unlock; + } + } + + /* clean up the sysfs representation */ + init_completion(&adap->dev_released); + device_unregister(&adap->dev); + + /* wait for sysfs to drop all references */ + wait_for_completion(&adap->dev_released); + + /* free bus id */ + idr_remove(&i2c_adapter_idr, adap->nr); + + dev_dbg(&adap->dev, "adapter [%s] unregistered\n", adap->name); + + /* Clear the device structure in case this adapter is ever going to be + added again */ + memset(&adap->dev, 0, sizeof(adap->dev)); + + out_unlock: + mutex_unlock(&core_lock); + return res; +} +EXPORT_SYMBOL(i2c_del_adapter); + + +/* ------------------------------------------------------------------------- */ + +static int __attach_adapter(struct device *dev, void *data) +{ + struct i2c_adapter *adapter = to_i2c_adapter(dev); + struct i2c_driver *driver = data; + + i2c_detect(adapter, driver); + + /* Legacy drivers scan i2c busses directly */ + if (driver->attach_adapter) + driver->attach_adapter(adapter); + + return 0; +} + +/* + * An i2c_driver is used with one or more i2c_client (device) nodes to access + * i2c slave chips, on a bus instance associated with some i2c_adapter. There + * are two models for binding the driver to its device: "new style" drivers + * follow the standard Linux driver model and just respond to probe() calls + * issued if the driver core sees they match(); "legacy" drivers create device + * nodes themselves. + */ + +int i2c_register_driver(struct module *owner, struct i2c_driver *driver) +{ + int res; + + /* Can't register until after driver model init */ + if (unlikely(WARN_ON(!i2c_bus_type.p))) + return -EAGAIN; + + /* new style driver methods can't mix with legacy ones */ + if (is_newstyle_driver(driver)) { + if (driver->attach_adapter || driver->detach_adapter + || driver->detach_client) { + printk(KERN_WARNING + "i2c-core: driver [%s] is confused\n", + driver->driver.name); + return -EINVAL; + } + } + + /* add the driver to the list of i2c drivers in the driver core */ + driver->driver.owner = owner; + driver->driver.bus = &i2c_bus_type; + + /* for new style drivers, when registration returns the driver core + * will have called probe() for all matching-but-unbound devices. + */ + res = driver_register(&driver->driver); + if (res) + return res; + + mutex_lock(&core_lock); + + pr_debug("i2c-core: driver [%s] registered\n", driver->driver.name); + + INIT_LIST_HEAD(&driver->clients); + /* Walk the adapters that are already present */ + class_for_each_device(&i2c_adapter_class, NULL, driver, + __attach_adapter); + + mutex_unlock(&core_lock); + return 0; +} +EXPORT_SYMBOL(i2c_register_driver); + +static int __detach_adapter(struct device *dev, void *data) +{ + struct i2c_adapter *adapter = to_i2c_adapter(dev); + struct i2c_driver *driver = data; + struct i2c_client *client, *_n; + + list_for_each_entry_safe(client, _n, &driver->clients, detected) { + dev_dbg(&adapter->dev, "Removing %s at 0x%x\n", + client->name, client->addr); + list_del(&client->detected); + i2c_unregister_device(client); + } + + if (is_newstyle_driver(driver)) + return 0; + + /* Have a look at each adapter, if clients of this driver are still + * attached. If so, detach them to be able to kill the driver + * afterwards. + */ + if (driver->detach_adapter) { + if (driver->detach_adapter(adapter)) + dev_err(&adapter->dev, + "detach_adapter failed for driver [%s]\n", + driver->driver.name); + } else { + struct i2c_client *client, *_n; + + list_for_each_entry_safe(client, _n, &adapter->clients, list) { + if (client->driver != driver) + continue; + dev_dbg(&adapter->dev, + "detaching client [%s] at 0x%02x\n", + client->name, client->addr); + if (driver->detach_client(client)) + dev_err(&adapter->dev, "detach_client " + "failed for client [%s] at 0x%02x\n", + client->name, client->addr); + } + } + + return 0; +} + +/** + * i2c_del_driver - unregister I2C driver + * @driver: the driver being unregistered + * Context: can sleep + */ +void i2c_del_driver(struct i2c_driver *driver) +{ + mutex_lock(&core_lock); + + class_for_each_device(&i2c_adapter_class, NULL, driver, + __detach_adapter); + + driver_unregister(&driver->driver); + pr_debug("i2c-core: driver [%s] unregistered\n", driver->driver.name); + + mutex_unlock(&core_lock); +} +EXPORT_SYMBOL(i2c_del_driver); + +/* ------------------------------------------------------------------------- */ + +static int __i2c_check_addr(struct device *dev, void *addrp) +{ + struct i2c_client *client = i2c_verify_client(dev); + int addr = *(int *)addrp; + + if (client && client->addr == addr) + return -EBUSY; + return 0; +} + +static int i2c_check_addr(struct i2c_adapter *adapter, int addr) +{ + return device_for_each_child(&adapter->dev, &addr, __i2c_check_addr); +} + +int i2c_attach_client(struct i2c_client *client) +{ + struct i2c_adapter *adapter = client->adapter; + int res; + + /* Check for address business */ + res = i2c_check_addr(adapter, client->addr); + if (res) + return res; + + client->dev.parent = &client->adapter->dev; + client->dev.bus = &i2c_bus_type; + + if (client->driver) + client->dev.driver = &client->driver->driver; + + if (client->driver && !is_newstyle_driver(client->driver)) { + client->dev.release = i2c_client_release; + client->dev.uevent_suppress = 1; + } else + client->dev.release = i2c_client_dev_release; + + snprintf(&client->dev.bus_id[0], sizeof(client->dev.bus_id), + "%d-%04x", i2c_adapter_id(adapter), client->addr); + res = device_register(&client->dev); + if (res) + goto out_err; + + mutex_lock(&adapter->clist_lock); + list_add_tail(&client->list, &adapter->clients); + mutex_unlock(&adapter->clist_lock); + + dev_dbg(&adapter->dev, "client [%s] registered with bus id %s\n", + client->name, client->dev.bus_id); + + if (adapter->client_register) { + if (adapter->client_register(client)) { + dev_dbg(&adapter->dev, "client_register " + "failed for client [%s] at 0x%02x\n", + client->name, client->addr); + } + } + + return 0; + +out_err: + dev_err(&adapter->dev, "Failed to attach i2c client %s at 0x%02x " + "(%d)\n", client->name, client->addr, res); + return res; +} +EXPORT_SYMBOL(i2c_attach_client); + +int i2c_detach_client(struct i2c_client *client) +{ + struct i2c_adapter *adapter = client->adapter; + int res = 0; + + if (adapter->client_unregister) { + res = adapter->client_unregister(client); + if (res) { + dev_err(&client->dev, + "client_unregister [%s] failed, " + "client not detached\n", client->name); + goto out; + } + } + + mutex_lock(&adapter->clist_lock); + list_del(&client->list); + mutex_unlock(&adapter->clist_lock); + + init_completion(&client->released); + device_unregister(&client->dev); + wait_for_completion(&client->released); + + out: + return res; +} +EXPORT_SYMBOL(i2c_detach_client); + +/** + * i2c_use_client - increments the reference count of the i2c client structure + * @client: the client being referenced + * + * Each live reference to a client should be refcounted. The driver model does + * that automatically as part of driver binding, so that most drivers don't + * need to do this explicitly: they hold a reference until they're unbound + * from the device. + * + * A pointer to the client with the incremented reference counter is returned. + */ +struct i2c_client *i2c_use_client(struct i2c_client *client) +{ + if (client && get_device(&client->dev)) + return client; + return NULL; +} +EXPORT_SYMBOL(i2c_use_client); + +/** + * i2c_release_client - release a use of the i2c client structure + * @client: the client being no longer referenced + * + * Must be called when a user of a client is finished with it. + */ +void i2c_release_client(struct i2c_client *client) +{ + if (client) + put_device(&client->dev); +} +EXPORT_SYMBOL(i2c_release_client); + +struct i2c_cmd_arg { + unsigned cmd; + void *arg; +}; + +static int i2c_cmd(struct device *dev, void *_arg) +{ + struct i2c_client *client = i2c_verify_client(dev); + struct i2c_cmd_arg *arg = _arg; + + if (client && client->driver && client->driver->command) + client->driver->command(client, arg->cmd, arg->arg); + return 0; +} + +void i2c_clients_command(struct i2c_adapter *adap, unsigned int cmd, void *arg) +{ + struct i2c_cmd_arg cmd_arg; + + cmd_arg.cmd = cmd; + cmd_arg.arg = arg; + device_for_each_child(&adap->dev, &cmd_arg, i2c_cmd); +} +EXPORT_SYMBOL(i2c_clients_command); + +static int __init i2c_init(void) +{ + int retval; + + retval = bus_register(&i2c_bus_type); + if (retval) + return retval; + retval = class_register(&i2c_adapter_class); + if (retval) + goto bus_err; + retval = i2c_add_driver(&dummy_driver); + if (retval) + goto class_err; + return 0; + +class_err: + class_unregister(&i2c_adapter_class); +bus_err: + bus_unregister(&i2c_bus_type); + return retval; +} + +static void __exit i2c_exit(void) +{ + i2c_del_driver(&dummy_driver); + class_unregister(&i2c_adapter_class); + bus_unregister(&i2c_bus_type); +} + +/* We must initialize early, because some subsystems register i2c drivers + * in subsys_initcall() code, but are linked (and initialized) before i2c. + */ +postcore_initcall(i2c_init); +module_exit(i2c_exit); + +/* ---------------------------------------------------- + * the functional interface to the i2c busses. + * ---------------------------------------------------- + */ + +/** + * i2c_transfer - execute a single or combined I2C message + * @adap: Handle to I2C bus + * @msgs: One or more messages to execute before STOP is issued to + * terminate the operation; each message begins with a START. + * @num: Number of messages to be executed. + * + * Returns negative errno, else the number of messages executed. + * + * Note that there is no requirement that each message be sent to + * the same slave address, although that is the most common model. + */ +int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num) +{ + int ret; + + /* REVISIT the fault reporting model here is weak: + * + * - When we get an error after receiving N bytes from a slave, + * there is no way to report "N". + * + * - When we get a NAK after transmitting N bytes to a slave, + * there is no way to report "N" ... or to let the master + * continue executing the rest of this combined message, if + * that's the appropriate response. + * + * - When for example "num" is two and we successfully complete + * the first message but get an error part way through the + * second, it's unclear whether that should be reported as + * one (discarding status on the second message) or errno + * (discarding status on the first one). + */ + + if (adap->algo->master_xfer) { +#ifdef DEBUG + for (ret = 0; ret < num; ret++) { + dev_dbg(&adap->dev, "master_xfer[%d] %c, addr=0x%02x, " + "len=%d%s\n", ret, (msgs[ret].flags & I2C_M_RD) + ? 'R' : 'W', msgs[ret].addr, msgs[ret].len, + (msgs[ret].flags & I2C_M_RECV_LEN) ? "+" : ""); + } +#endif + + if (in_atomic() || irqs_disabled()) { + ret = mutex_trylock(&adap->bus_lock); + if (!ret) + /* I2C activity is ongoing. */ + return -EAGAIN; + } else { + mutex_lock_nested(&adap->bus_lock, adap->level); + } + + ret = adap->algo->master_xfer(adap,msgs,num); + mutex_unlock(&adap->bus_lock); + + return ret; + } else { + dev_dbg(&adap->dev, "I2C level transfers not supported\n"); + return -EOPNOTSUPP; + } +} +EXPORT_SYMBOL(i2c_transfer); + +#ifdef CONFIG_AST_I2C_SLAVE_RDWR +int i2c_slave_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) +{ + unsigned long orig_jiffies; + int ret, try; + + if (adap->algo->slave_xfer) { +#ifdef DEBUG + dev_dbg(&adap->dev, "slave_xfer %c, addr=0x%02x, " + "len=%d\n", (msgs->flags & I2C_S_RD) + ? 'R' : 'W', msgs->addr, msgs->len); +#endif + if (in_atomic() || irqs_disabled()) { + ret = mutex_trylock(&adap->bus_lock); + if (!ret) + /* I2C activity is ongoing. */ + return -EAGAIN; + } else { + mutex_lock_nested(&adap->bus_lock, adap->level); + } + + ret = adap->algo->slave_xfer(adap, msgs); + + mutex_unlock(&adap->bus_lock); + + return ret; + } else { + dev_dbg(&adap->dev, "I2C level transfers not supported\n"); + return -EOPNOTSUPP; + } +} +EXPORT_SYMBOL(i2c_slave_transfer); + +#endif + +/** + * i2c_master_send - issue a single I2C message in master transmit mode + * @client: Handle to slave device + * @buf: Data that will be written to the slave + * @count: How many bytes to write + * + * Returns negative errno, or else the number of bytes written. + */ +int i2c_master_send(struct i2c_client *client,const char *buf ,int count) +{ + int ret; + struct i2c_adapter *adap=client->adapter; + struct i2c_msg msg; + + msg.addr = client->addr; + msg.flags = client->flags & I2C_M_TEN; + msg.len = count; + msg.buf = (char *)buf; + + ret = i2c_transfer(adap, &msg, 1); + + /* If everything went ok (i.e. 1 msg transmitted), return #bytes + transmitted, else error code. */ + return (ret == 1) ? count : ret; +} +EXPORT_SYMBOL(i2c_master_send); + +/** + * i2c_master_recv - issue a single I2C message in master receive mode + * @client: Handle to slave device + * @buf: Where to store data read from slave + * @count: How many bytes to read + * + * Returns negative errno, or else the number of bytes read. + */ +int i2c_master_recv(struct i2c_client *client, char *buf ,int count) +{ + struct i2c_adapter *adap=client->adapter; + struct i2c_msg msg; + int ret; + + msg.addr = client->addr; + msg.flags = client->flags & I2C_M_TEN; + msg.flags |= I2C_M_RD; + msg.len = count; + msg.buf = buf; + + ret = i2c_transfer(adap, &msg, 1); + + /* If everything went ok (i.e. 1 msg transmitted), return #bytes + transmitted, else error code. */ + return (ret == 1) ? count : ret; +} +EXPORT_SYMBOL(i2c_master_recv); + +/* ---------------------------------------------------- + * the i2c address scanning function + * Will not work for 10-bit addresses! + * ---------------------------------------------------- + */ +static int i2c_probe_address(struct i2c_adapter *adapter, int addr, int kind, + int (*found_proc) (struct i2c_adapter *, int, int)) +{ + int err; + + /* Make sure the address is valid */ + if (addr < 0x03 || addr > 0x77) { + dev_warn(&adapter->dev, "Invalid probe address 0x%02x\n", + addr); + return -EINVAL; + } + + /* Skip if already in use */ + if (i2c_check_addr(adapter, addr)) + return 0; + + /* Make sure there is something at this address, unless forced */ + if (kind < 0) { + if (i2c_smbus_xfer(adapter, addr, 0, 0, 0, + I2C_SMBUS_QUICK, NULL) < 0) + return 0; + + /* prevent 24RF08 corruption */ + if ((addr & ~0x0f) == 0x50) + i2c_smbus_xfer(adapter, addr, 0, 0, 0, + I2C_SMBUS_QUICK, NULL); + } + + /* Finally call the custom detection function */ + err = found_proc(adapter, addr, kind); + /* -ENODEV can be returned if there is a chip at the given address + but it isn't supported by this chip driver. We catch it here as + this isn't an error. */ + if (err == -ENODEV) + err = 0; + + if (err) + dev_warn(&adapter->dev, "Client creation failed at 0x%x (%d)\n", + addr, err); + return err; +} + +int i2c_probe(struct i2c_adapter *adapter, + const struct i2c_client_address_data *address_data, + int (*found_proc) (struct i2c_adapter *, int, int)) +{ + int i, err; + int adap_id = i2c_adapter_id(adapter); + + /* Force entries are done first, and are not affected by ignore + entries */ + if (address_data->forces) { + const unsigned short * const *forces = address_data->forces; + int kind; + + for (kind = 0; forces[kind]; kind++) { + for (i = 0; forces[kind][i] != I2C_CLIENT_END; + i += 2) { + if (forces[kind][i] == adap_id + || forces[kind][i] == ANY_I2C_BUS) { + dev_dbg(&adapter->dev, "found force " + "parameter for adapter %d, " + "addr 0x%02x, kind %d\n", + adap_id, forces[kind][i + 1], + kind); + err = i2c_probe_address(adapter, + forces[kind][i + 1], + kind, found_proc); + if (err) + return err; + } + } + } + } + + /* Stop here if we can't use SMBUS_QUICK */ + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_QUICK)) { + if (address_data->probe[0] == I2C_CLIENT_END + && address_data->normal_i2c[0] == I2C_CLIENT_END) + return 0; + + dev_dbg(&adapter->dev, "SMBus Quick command not supported, " + "can't probe for chips\n"); + return -EOPNOTSUPP; + } + + /* Probe entries are done second, and are not affected by ignore + entries either */ + for (i = 0; address_data->probe[i] != I2C_CLIENT_END; i += 2) { + if (address_data->probe[i] == adap_id + || address_data->probe[i] == ANY_I2C_BUS) { + dev_dbg(&adapter->dev, "found probe parameter for " + "adapter %d, addr 0x%02x\n", adap_id, + address_data->probe[i + 1]); + err = i2c_probe_address(adapter, + address_data->probe[i + 1], + -1, found_proc); + if (err) + return err; + } + } + + /* Normal entries are done last, unless shadowed by an ignore entry */ + for (i = 0; address_data->normal_i2c[i] != I2C_CLIENT_END; i += 1) { + int j, ignore; + + ignore = 0; + for (j = 0; address_data->ignore[j] != I2C_CLIENT_END; + j += 2) { + if ((address_data->ignore[j] == adap_id || + address_data->ignore[j] == ANY_I2C_BUS) + && address_data->ignore[j + 1] + == address_data->normal_i2c[i]) { + dev_dbg(&adapter->dev, "found ignore " + "parameter for adapter %d, " + "addr 0x%02x\n", adap_id, + address_data->ignore[j + 1]); + ignore = 1; + break; + } + } + if (ignore) + continue; + + dev_dbg(&adapter->dev, "found normal entry for adapter %d, " + "addr 0x%02x\n", adap_id, + address_data->normal_i2c[i]); + err = i2c_probe_address(adapter, address_data->normal_i2c[i], + -1, found_proc); + if (err) + return err; + } + + return 0; +} +EXPORT_SYMBOL(i2c_probe); + +/* Separate detection function for new-style drivers */ +static int i2c_detect_address(struct i2c_client *temp_client, int kind, + struct i2c_driver *driver) +{ + struct i2c_board_info info; + struct i2c_adapter *adapter = temp_client->adapter; + int addr = temp_client->addr; + int err; + + /* Make sure the address is valid */ + if (addr < 0x03 || addr > 0x77) { + dev_warn(&adapter->dev, "Invalid probe address 0x%02x\n", + addr); + return -EINVAL; + } + + /* Skip if already in use */ + if (i2c_check_addr(adapter, addr)) + return 0; + + /* Make sure there is something at this address, unless forced */ + if (kind < 0) { + if (i2c_smbus_xfer(adapter, addr, 0, 0, 0, + I2C_SMBUS_QUICK, NULL) < 0) + return 0; + + /* prevent 24RF08 corruption */ + if ((addr & ~0x0f) == 0x50) + i2c_smbus_xfer(adapter, addr, 0, 0, 0, + I2C_SMBUS_QUICK, NULL); + } + + /* Finally call the custom detection function */ + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = addr; + err = driver->detect(temp_client, kind, &info); + if (err) { + /* -ENODEV is returned if the detection fails. We catch it + here as this isn't an error. */ + return err == -ENODEV ? 0 : err; + } + + /* Consistency check */ + if (info.type[0] == '\0') { + dev_err(&adapter->dev, "%s detection function provided " + "no name for 0x%x\n", driver->driver.name, + addr); + } else { + struct i2c_client *client; + + /* Detection succeeded, instantiate the device */ + dev_dbg(&adapter->dev, "Creating %s at 0x%02x\n", + info.type, info.addr); + client = i2c_new_device(adapter, &info); + if (client) + list_add_tail(&client->detected, &driver->clients); + else + dev_err(&adapter->dev, "Failed creating %s at 0x%02x\n", + info.type, info.addr); + } + return 0; +} + +static int i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver) +{ + const struct i2c_client_address_data *address_data; + struct i2c_client *temp_client; + int i, err = 0; + int adap_id = i2c_adapter_id(adapter); + + address_data = driver->address_data; + if (!driver->detect || !address_data) + return 0; + + /* Set up a temporary client to help detect callback */ + temp_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (!temp_client) + return -ENOMEM; + temp_client->adapter = adapter; + + /* Force entries are done first, and are not affected by ignore + entries */ + if (address_data->forces) { + const unsigned short * const *forces = address_data->forces; + int kind; + + for (kind = 0; forces[kind]; kind++) { + for (i = 0; forces[kind][i] != I2C_CLIENT_END; + i += 2) { + if (forces[kind][i] == adap_id + || forces[kind][i] == ANY_I2C_BUS) { + dev_dbg(&adapter->dev, "found force " + "parameter for adapter %d, " + "addr 0x%02x, kind %d\n", + adap_id, forces[kind][i + 1], + kind); + temp_client->addr = forces[kind][i + 1]; + err = i2c_detect_address(temp_client, + kind, driver); + if (err) + goto exit_free; + } + } + } + } + + /* Stop here if the classes do not match */ + if (!(adapter->class & driver->class)) + goto exit_free; + + /* Stop here if we can't use SMBUS_QUICK */ + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_QUICK)) { + if (address_data->probe[0] == I2C_CLIENT_END + && address_data->normal_i2c[0] == I2C_CLIENT_END) + goto exit_free; + + dev_warn(&adapter->dev, "SMBus Quick command not supported, " + "can't probe for chips\n"); + err = -EOPNOTSUPP; + goto exit_free; + } + + /* Probe entries are done second, and are not affected by ignore + entries either */ + for (i = 0; address_data->probe[i] != I2C_CLIENT_END; i += 2) { + if (address_data->probe[i] == adap_id + || address_data->probe[i] == ANY_I2C_BUS) { + dev_dbg(&adapter->dev, "found probe parameter for " + "adapter %d, addr 0x%02x\n", adap_id, + address_data->probe[i + 1]); + temp_client->addr = address_data->probe[i + 1]; + err = i2c_detect_address(temp_client, -1, driver); + if (err) + goto exit_free; + } + } + + /* Normal entries are done last, unless shadowed by an ignore entry */ + for (i = 0; address_data->normal_i2c[i] != I2C_CLIENT_END; i += 1) { + int j, ignore; + + ignore = 0; + for (j = 0; address_data->ignore[j] != I2C_CLIENT_END; + j += 2) { + if ((address_data->ignore[j] == adap_id || + address_data->ignore[j] == ANY_I2C_BUS) + && address_data->ignore[j + 1] + == address_data->normal_i2c[i]) { + dev_dbg(&adapter->dev, "found ignore " + "parameter for adapter %d, " + "addr 0x%02x\n", adap_id, + address_data->ignore[j + 1]); + ignore = 1; + break; + } + } + if (ignore) + continue; + + dev_dbg(&adapter->dev, "found normal entry for adapter %d, " + "addr 0x%02x\n", adap_id, + address_data->normal_i2c[i]); + temp_client->addr = address_data->normal_i2c[i]; + err = i2c_detect_address(temp_client, -1, driver); + if (err) + goto exit_free; + } + + exit_free: + kfree(temp_client); + return err; +} + +struct i2c_client * +i2c_new_probed_device(struct i2c_adapter *adap, + struct i2c_board_info *info, + unsigned short const *addr_list) +{ + int i; + + /* Stop here if the bus doesn't support probing */ + if (!i2c_check_functionality(adap, I2C_FUNC_SMBUS_READ_BYTE)) { + dev_err(&adap->dev, "Probing not supported\n"); + return NULL; + } + + for (i = 0; addr_list[i] != I2C_CLIENT_END; i++) { + /* Check address validity */ + if (addr_list[i] < 0x03 || addr_list[i] > 0x77) { + dev_warn(&adap->dev, "Invalid 7-bit address " + "0x%02x\n", addr_list[i]); + continue; + } + + /* Check address availability */ + if (i2c_check_addr(adap, addr_list[i])) { + dev_dbg(&adap->dev, "Address 0x%02x already in " + "use, not probing\n", addr_list[i]); + continue; + } + + /* Test address responsiveness + The default probe method is a quick write, but it is known + to corrupt the 24RF08 EEPROMs due to a state machine bug, + and could also irreversibly write-protect some EEPROMs, so + for address ranges 0x30-0x37 and 0x50-0x5f, we use a byte + read instead. Also, some bus drivers don't implement + quick write, so we fallback to a byte read it that case + too. */ + if ((addr_list[i] & ~0x07) == 0x30 + || (addr_list[i] & ~0x0f) == 0x50 + || !i2c_check_functionality(adap, I2C_FUNC_SMBUS_QUICK)) { + union i2c_smbus_data data; + + if (i2c_smbus_xfer(adap, addr_list[i], 0, + I2C_SMBUS_READ, 0, + I2C_SMBUS_BYTE, &data) >= 0) + break; + } else { + if (i2c_smbus_xfer(adap, addr_list[i], 0, + I2C_SMBUS_WRITE, 0, + I2C_SMBUS_QUICK, NULL) >= 0) + break; + } + } + + if (addr_list[i] == I2C_CLIENT_END) { + dev_dbg(&adap->dev, "Probing failed, no device found\n"); + return NULL; + } + + info->addr = addr_list[i]; + return i2c_new_device(adap, info); +} +EXPORT_SYMBOL_GPL(i2c_new_probed_device); + +struct i2c_adapter* i2c_get_adapter(int id) +{ + struct i2c_adapter *adapter; + + mutex_lock(&core_lock); + adapter = (struct i2c_adapter *)idr_find(&i2c_adapter_idr, id); + if (adapter && !try_module_get(adapter->owner)) + adapter = NULL; + + mutex_unlock(&core_lock); + return adapter; +} +EXPORT_SYMBOL(i2c_get_adapter); + +void i2c_put_adapter(struct i2c_adapter *adap) +{ + module_put(adap->owner); +} +EXPORT_SYMBOL(i2c_put_adapter); + +/* The SMBus parts */ + +#define POLY (0x1070U << 3) +static u8 +crc8(u16 data) +{ + int i; + + for(i = 0; i < 8; i++) { + if (data & 0x8000) + data = data ^ POLY; + data = data << 1; + } + return (u8)(data >> 8); +} + +/* Incremental CRC8 over count bytes in the array pointed to by p */ +static u8 i2c_smbus_pec(u8 crc, u8 *p, size_t count) +{ + int i; + + for(i = 0; i < count; i++) + crc = crc8((crc ^ p[i]) << 8); + return crc; +} + +/* Assume a 7-bit address, which is reasonable for SMBus */ +static u8 i2c_smbus_msg_pec(u8 pec, struct i2c_msg *msg) +{ + /* The address will be sent first */ + u8 addr = (msg->addr << 1) | !!(msg->flags & I2C_M_RD); + pec = i2c_smbus_pec(pec, &addr, 1); + + /* The data buffer follows */ + return i2c_smbus_pec(pec, msg->buf, msg->len); +} + +/* Used for write only transactions */ +static inline void i2c_smbus_add_pec(struct i2c_msg *msg) +{ + msg->buf[msg->len] = i2c_smbus_msg_pec(0, msg); + msg->len++; +} + +/* Return <0 on CRC error + If there was a write before this read (most cases) we need to take the + partial CRC from the write part into account. + Note that this function does modify the message (we need to decrease the + message length to hide the CRC byte from the caller). */ +static int i2c_smbus_check_pec(u8 cpec, struct i2c_msg *msg) +{ + u8 rpec = msg->buf[--msg->len]; + cpec = i2c_smbus_msg_pec(cpec, msg); + + if (rpec != cpec) { + pr_debug("i2c-core: Bad PEC 0x%02x vs. 0x%02x\n", + rpec, cpec); + return -EBADMSG; + } + return 0; +} + +/** + * i2c_smbus_read_byte - SMBus "receive byte" protocol + * @client: Handle to slave device + * + * This executes the SMBus "receive byte" protocol, returning negative errno + * else the byte received from the device. + */ +s32 i2c_smbus_read_byte(struct i2c_client *client) +{ + union i2c_smbus_data data; + int status; + + status = i2c_smbus_xfer(client->adapter, client->addr, client->flags, + I2C_SMBUS_READ, 0, + I2C_SMBUS_BYTE, &data); + return (status < 0) ? status : data.byte; +} +EXPORT_SYMBOL(i2c_smbus_read_byte); + +/** + * i2c_smbus_write_byte - SMBus "send byte" protocol + * @client: Handle to slave device + * @value: Byte to be sent + * + * This executes the SMBus "send byte" protocol, returning negative errno + * else zero on success. + */ +s32 i2c_smbus_write_byte(struct i2c_client *client, u8 value) +{ + return i2c_smbus_xfer(client->adapter,client->addr,client->flags, + I2C_SMBUS_WRITE, value, I2C_SMBUS_BYTE, NULL); +} +EXPORT_SYMBOL(i2c_smbus_write_byte); + +/** + * i2c_smbus_read_byte_data - SMBus "read byte" protocol + * @client: Handle to slave device + * @command: Byte interpreted by slave + * + * This executes the SMBus "read byte" protocol, returning negative errno + * else a data byte received from the device. + */ +s32 i2c_smbus_read_byte_data(struct i2c_client *client, u8 command) +{ + union i2c_smbus_data data; + int status; + + status = i2c_smbus_xfer(client->adapter, client->addr, client->flags, + I2C_SMBUS_READ, command, + I2C_SMBUS_BYTE_DATA, &data); + return (status < 0) ? status : data.byte; +} +EXPORT_SYMBOL(i2c_smbus_read_byte_data); + +/** + * i2c_smbus_write_byte_data - SMBus "write byte" protocol + * @client: Handle to slave device + * @command: Byte interpreted by slave + * @value: Byte being written + * + * This executes the SMBus "write byte" protocol, returning negative errno + * else zero on success. + */ +s32 i2c_smbus_write_byte_data(struct i2c_client *client, u8 command, u8 value) +{ + union i2c_smbus_data data; + data.byte = value; + return i2c_smbus_xfer(client->adapter,client->addr,client->flags, + I2C_SMBUS_WRITE,command, + I2C_SMBUS_BYTE_DATA,&data); +} +EXPORT_SYMBOL(i2c_smbus_write_byte_data); + +/** + * i2c_smbus_read_word_data - SMBus "read word" protocol + * @client: Handle to slave device + * @command: Byte interpreted by slave + * + * This executes the SMBus "read word" protocol, returning negative errno + * else a 16-bit unsigned "word" received from the device. + */ +s32 i2c_smbus_read_word_data(struct i2c_client *client, u8 command) +{ + union i2c_smbus_data data; + int status; + + status = i2c_smbus_xfer(client->adapter, client->addr, client->flags, + I2C_SMBUS_READ, command, + I2C_SMBUS_WORD_DATA, &data); + return (status < 0) ? status : data.word; +} +EXPORT_SYMBOL(i2c_smbus_read_word_data); + +/** + * i2c_smbus_write_word_data - SMBus "write word" protocol + * @client: Handle to slave device + * @command: Byte interpreted by slave + * @value: 16-bit "word" being written + * + * This executes the SMBus "write word" protocol, returning negative errno + * else zero on success. + */ +s32 i2c_smbus_write_word_data(struct i2c_client *client, u8 command, u16 value) +{ + union i2c_smbus_data data; + data.word = value; + return i2c_smbus_xfer(client->adapter,client->addr,client->flags, + I2C_SMBUS_WRITE,command, + I2C_SMBUS_WORD_DATA,&data); +} +EXPORT_SYMBOL(i2c_smbus_write_word_data); + +/** + * i2c_smbus_process_call - SMBus "process call" protocol + * @client: Handle to slave device + * @command: Byte interpreted by slave + * @value: 16-bit "word" being written + * + * This executes the SMBus "process call" protocol, returning negative errno + * else a 16-bit unsigned "word" received from the device. + */ +s32 i2c_smbus_process_call(struct i2c_client *client, u8 command, u16 value) +{ + union i2c_smbus_data data; + int status; + data.word = value; + + status = i2c_smbus_xfer(client->adapter, client->addr, client->flags, + I2C_SMBUS_WRITE, command, + I2C_SMBUS_PROC_CALL, &data); + return (status < 0) ? status : data.word; +} +EXPORT_SYMBOL(i2c_smbus_process_call); + +/** + * i2c_smbus_read_block_data - SMBus "block read" protocol + * @client: Handle to slave device + * @command: Byte interpreted by slave + * @values: Byte array into which data will be read; big enough to hold + * the data returned by the slave. SMBus allows at most 32 bytes. + * + * This executes the SMBus "block read" protocol, returning negative errno + * else the number of data bytes in the slave's response. + * + * Note that using this function requires that the client's adapter support + * the I2C_FUNC_SMBUS_READ_BLOCK_DATA functionality. Not all adapter drivers + * support this; its emulation through I2C messaging relies on a specific + * mechanism (I2C_M_RECV_LEN) which may not be implemented. + */ +s32 i2c_smbus_read_block_data(struct i2c_client *client, u8 command, + u8 *values) +{ + union i2c_smbus_data data; + int status; + + status = i2c_smbus_xfer(client->adapter, client->addr, client->flags, + I2C_SMBUS_READ, command, + I2C_SMBUS_BLOCK_DATA, &data); + if (status) + return status; + + memcpy(values, &data.block[1], data.block[0]); + return data.block[0]; +} +EXPORT_SYMBOL(i2c_smbus_read_block_data); + +/** + * i2c_smbus_write_block_data - SMBus "block write" protocol + * @client: Handle to slave device + * @command: Byte interpreted by slave + * @length: Size of data block; SMBus allows at most 32 bytes + * @values: Byte array which will be written. + * + * This executes the SMBus "block write" protocol, returning negative errno + * else zero on success. + */ +s32 i2c_smbus_write_block_data(struct i2c_client *client, u8 command, + u8 length, const u8 *values) +{ + union i2c_smbus_data data; + + if (length > I2C_SMBUS_BLOCK_MAX) + length = I2C_SMBUS_BLOCK_MAX; + data.block[0] = length; + memcpy(&data.block[1], values, length); + return i2c_smbus_xfer(client->adapter,client->addr,client->flags, + I2C_SMBUS_WRITE,command, + I2C_SMBUS_BLOCK_DATA,&data); +} +EXPORT_SYMBOL(i2c_smbus_write_block_data); + +/* Returns the number of read bytes */ +s32 i2c_smbus_read_i2c_block_data(struct i2c_client *client, u8 command, + u8 length, u8 *values) +{ + union i2c_smbus_data data; + int status; + + if (length > I2C_SMBUS_BLOCK_MAX) + length = I2C_SMBUS_BLOCK_MAX; + data.block[0] = length; + status = i2c_smbus_xfer(client->adapter, client->addr, client->flags, + I2C_SMBUS_READ, command, + I2C_SMBUS_I2C_BLOCK_DATA, &data); + if (status < 0) + return status; + + memcpy(values, &data.block[1], data.block[0]); + return data.block[0]; +} +EXPORT_SYMBOL(i2c_smbus_read_i2c_block_data); + +s32 i2c_smbus_write_i2c_block_data(struct i2c_client *client, u8 command, + u8 length, const u8 *values) +{ + union i2c_smbus_data data; + + if (length > I2C_SMBUS_BLOCK_MAX) + length = I2C_SMBUS_BLOCK_MAX; + data.block[0] = length; + memcpy(data.block + 1, values, length); + return i2c_smbus_xfer(client->adapter, client->addr, client->flags, + I2C_SMBUS_WRITE, command, + I2C_SMBUS_I2C_BLOCK_DATA, &data); +} +EXPORT_SYMBOL(i2c_smbus_write_i2c_block_data); + +/* Simulate a SMBus command using the i2c protocol + No checking of parameters is done! */ +static s32 i2c_smbus_xfer_emulated(struct i2c_adapter * adapter, u16 addr, + unsigned short flags, + char read_write, u8 command, int size, + union i2c_smbus_data * data) +{ + /* So we need to generate a series of msgs. In the case of writing, we + need to use only one message; when reading, we need two. We initialize + most things with sane defaults, to keep the code below somewhat + simpler. */ + unsigned char msgbuf0[I2C_SMBUS_BLOCK_LARGE_MAX+3]; + unsigned char msgbuf1[I2C_SMBUS_BLOCK_LARGE_MAX+2]; + int num = read_write == I2C_SMBUS_READ?2:1; + struct i2c_msg msg[2] = { { addr, flags, 1, msgbuf0 }, + { addr, flags | I2C_M_RD, 0, msgbuf1 } + }; + int i; + u8 partial_pec = 0; + int status; + + msgbuf0[0] = command; + switch(size) { + case I2C_SMBUS_QUICK: + msg[0].len = 0; + /* Special case: The read/write field is used as data */ + msg[0].flags = flags | (read_write == I2C_SMBUS_READ ? + I2C_M_RD : 0); + num = 1; + break; + case I2C_SMBUS_BYTE: + if (read_write == I2C_SMBUS_READ) { + /* Special case: only a read! */ + msg[0].flags = I2C_M_RD | flags; + num = 1; + } + break; + case I2C_SMBUS_BYTE_DATA: + if (read_write == I2C_SMBUS_READ) + msg[1].len = 1; + else { + msg[0].len = 2; + msgbuf0[1] = data->byte; + } + break; + case I2C_SMBUS_WORD_DATA: + if (read_write == I2C_SMBUS_READ) + msg[1].len = 2; + else { + msg[0].len=3; + msgbuf0[1] = data->word & 0xff; + msgbuf0[2] = data->word >> 8; + } + break; + case I2C_SMBUS_PROC_CALL: + num = 2; /* Special case */ + read_write = I2C_SMBUS_READ; + msg[0].len = 3; + msg[1].len = 2; + msgbuf0[1] = data->word & 0xff; + msgbuf0[2] = data->word >> 8; + break; + case I2C_SMBUS_BLOCK_DATA: + if (read_write == I2C_SMBUS_READ) { + msg[1].flags |= I2C_M_RECV_LEN; + msg[1].len = 1; /* block length will be added by + the underlying bus driver */ + } else { + msg[0].len = data->block[0] + 2; + if (msg[0].len > I2C_SMBUS_BLOCK_MAX + 2) { + dev_err(&adapter->dev, + "Invalid block write size %d\n", + data->block[0]); + return -EINVAL; + } + for (i = 1; i < msg[0].len; i++) + msgbuf0[i] = data->block[i-1]; + } + break; + case I2C_SMBUS_BLOCK_LARGE_DATA: + if (read_write == I2C_SMBUS_READ) { + msg[1].flags |= I2C_M_RECV_LEN; + msg[1].len = 1; /* block length will be added by + the underlying bus driver */ + } else { + msg[0].len = data->block[0] + 2; + if (msg[0].len > I2C_SMBUS_BLOCK_LARGE_MAX + 2) { + dev_err(&adapter->dev, + "Invalid large block write size %d\n", + data->block[0]); + return -EINVAL; + } + for (i = 1; i < msg[0].len; i++) + msgbuf0[i] = data->block[i-1]; + } + break; + case I2C_SMBUS_BLOCK_PROC_CALL: + num = 2; /* Another special case */ + read_write = I2C_SMBUS_READ; + if (data->block[0] > I2C_SMBUS_BLOCK_MAX) { + dev_err(&adapter->dev, + "Invalid block write size %d\n", + data->block[0]); + return -EINVAL; + } + msg[0].len = data->block[0] + 2; + for (i = 1; i < msg[0].len; i++) + msgbuf0[i] = data->block[i-1]; + msg[1].flags |= I2C_M_RECV_LEN; + msg[1].len = 1; /* block length will be added by + the underlying bus driver */ + break; + case I2C_SMBUS_I2C_BLOCK_DATA: + if (read_write == I2C_SMBUS_READ) { + msg[1].len = data->block[0]; + } else { + msg[0].len = data->block[0] + 1; + if (msg[0].len > I2C_SMBUS_BLOCK_MAX + 1) { + dev_err(&adapter->dev, + "Invalid block write size %d\n", + data->block[0]); + return -EINVAL; + } + for (i = 1; i <= data->block[0]; i++) + msgbuf0[i] = data->block[i]; + } + break; + default: + dev_err(&adapter->dev, "Unsupported transaction %d\n", size); + return -EOPNOTSUPP; + } + + i = ((flags & I2C_CLIENT_PEC) && size != I2C_SMBUS_QUICK + && size != I2C_SMBUS_I2C_BLOCK_DATA); + if (i) { + /* Compute PEC if first message is a write */ + if (!(msg[0].flags & I2C_M_RD)) { + if (num == 1) /* Write only */ + i2c_smbus_add_pec(&msg[0]); + else /* Write followed by read */ + partial_pec = i2c_smbus_msg_pec(0, &msg[0]); + } + /* Ask for PEC if last message is a read */ + if (msg[num-1].flags & I2C_M_RD) + msg[num-1].len++; + } + + status = i2c_transfer(adapter, msg, num); + if (status < 0) + return status; + + /* Check PEC if last message is a read */ + if (i && (msg[num-1].flags & I2C_M_RD)) { + status = i2c_smbus_check_pec(partial_pec, &msg[num-1]); + if (status < 0) + return status; + } + + if (read_write == I2C_SMBUS_READ) + switch(size) { + case I2C_SMBUS_BYTE: + data->byte = msgbuf0[0]; + break; + case I2C_SMBUS_BYTE_DATA: + data->byte = msgbuf1[0]; + break; + case I2C_SMBUS_WORD_DATA: + case I2C_SMBUS_PROC_CALL: + data->word = msgbuf1[0] | (msgbuf1[1] << 8); + break; + case I2C_SMBUS_I2C_BLOCK_DATA: + for (i = 0; i < data->block[0]; i++) + data->block[i+1] = msgbuf1[i]; + break; + case I2C_SMBUS_BLOCK_DATA: + case I2C_SMBUS_BLOCK_LARGE_DATA: + case I2C_SMBUS_BLOCK_PROC_CALL: + for (i = 0; i < msgbuf1[0] + 1; i++) + data->block[i] = msgbuf1[i]; + break; + } + return 0; +} + +/** + * i2c_smbus_xfer - execute SMBus protocol operations + * @adapter: Handle to I2C bus + * @addr: Address of SMBus slave on that bus + * @flags: I2C_CLIENT_* flags (usually zero or I2C_CLIENT_PEC) + * @read_write: I2C_SMBUS_READ or I2C_SMBUS_WRITE + * @command: Byte interpreted by slave, for protocols which use such bytes + * @protocol: SMBus protocol operation to execute, such as I2C_SMBUS_PROC_CALL + * @data: Data to be read or written + * + * This executes an SMBus protocol operation, and returns a negative + * errno code else zero on success. + */ +s32 i2c_smbus_xfer(struct i2c_adapter * adapter, u16 addr, unsigned short flags, + char read_write, u8 command, int protocol, + union i2c_smbus_data * data) +{ + s32 res; + + flags &= I2C_M_TEN | I2C_CLIENT_PEC; + + if (adapter->algo->smbus_xfer) { + mutex_lock(&adapter->bus_lock); + res = adapter->algo->smbus_xfer(adapter,addr,flags,read_write, + command, protocol, data); + mutex_unlock(&adapter->bus_lock); + } else + res = i2c_smbus_xfer_emulated(adapter,addr,flags,read_write, + command, protocol, data); + + return res; +} +EXPORT_SYMBOL(i2c_smbus_xfer); + +MODULE_AUTHOR("Simon G. Vogl <simon@tk.uni-linz.ac.at>"); +MODULE_DESCRIPTION("I2C-Bus main module"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/i2c-core.h b/drivers/i2c/i2c-core.h new file mode 100644 index 0000000..cd5bff8 --- /dev/null +++ b/drivers/i2c/i2c-core.h @@ -0,0 +1,31 @@ +/* + * i2c-core.h - interfaces internal to the I2C framework + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +struct i2c_devinfo { + struct list_head list; + int busnum; + struct i2c_board_info board_info; +}; + +/* board_lock protects board_list and first_dynamic_bus_num. + * only i2c core components are allowed to use these symbols. + */ +extern struct mutex __i2c_board_lock; +extern struct list_head __i2c_board_list; +extern int __i2c_first_dynamic_bus_num; + diff --git a/drivers/i2c/i2c-dev.c b/drivers/i2c/i2c-dev.c new file mode 100644 index 0000000..82f2c7c --- /dev/null +++ b/drivers/i2c/i2c-dev.c @@ -0,0 +1,762 @@ +/* + i2c-dev.c - i2c-bus driver, char device interface + + Copyright (C) 1995-97 Simon G. Vogl + Copyright (C) 1998-99 Frodo Looijaard <frodol@dds.nl> + Copyright (C) 2003 Greg Kroah-Hartman <greg@kroah.com> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* Note that this is a complete rewrite of Simon Vogl's i2c-dev module. + But I have used so much of his original code and ideas that it seems + only fair to recognize him as co-author -- Frodo */ + +/* The I2C_RDWR ioctl code is written by Kolja Waschk <waschk@telos.de> */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <linux/smp_lock.h> +#include <asm/uaccess.h> + +#ifdef CONFIG_AST_I2C_SLAVE_RDWR +#include <plat/ast_i2c.h> +#endif + +static struct i2c_driver i2cdev_driver; + +/* + * An i2c_dev represents an i2c_adapter ... an I2C or SMBus master, not a + * slave (i2c_client) with which messages will be exchanged. It's coupled + * with a character special file which is accessed by user mode drivers. + * + * The list of i2c_dev structures is parallel to the i2c_adapter lists + * maintained by the driver model, and is updated using notifications + * delivered to the i2cdev_driver. + */ +struct i2c_dev { + struct list_head list; + struct i2c_adapter *adap; + struct device *dev; +}; + +#define I2C_MINORS 256 +static LIST_HEAD(i2c_dev_list); +static DEFINE_SPINLOCK(i2c_dev_list_lock); + +static struct i2c_dev *i2c_dev_get_by_minor(unsigned index) +{ + struct i2c_dev *i2c_dev; + + spin_lock(&i2c_dev_list_lock); + list_for_each_entry(i2c_dev, &i2c_dev_list, list) { + if (i2c_dev->adap->nr == index) + goto found; + } + i2c_dev = NULL; +found: + spin_unlock(&i2c_dev_list_lock); + return i2c_dev; +} + +static struct i2c_dev *get_free_i2c_dev(struct i2c_adapter *adap) +{ + struct i2c_dev *i2c_dev; + + if (adap->nr >= I2C_MINORS) { + printk(KERN_ERR "i2c-dev: Out of device minors (%d)\n", + adap->nr); + return ERR_PTR(-ENODEV); + } + + i2c_dev = kzalloc(sizeof(*i2c_dev), GFP_KERNEL); + if (!i2c_dev) + return ERR_PTR(-ENOMEM); + i2c_dev->adap = adap; + + spin_lock(&i2c_dev_list_lock); + list_add_tail(&i2c_dev->list, &i2c_dev_list); + spin_unlock(&i2c_dev_list_lock); + return i2c_dev; +} + +static void return_i2c_dev(struct i2c_dev *i2c_dev) +{ + spin_lock(&i2c_dev_list_lock); + list_del(&i2c_dev->list); + spin_unlock(&i2c_dev_list_lock); + kfree(i2c_dev); +} + +static ssize_t show_adapter_name(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_dev *i2c_dev = i2c_dev_get_by_minor(MINOR(dev->devt)); + + if (!i2c_dev) + return -ENODEV; + return sprintf(buf, "%s\n", i2c_dev->adap->name); +} +static DEVICE_ATTR(name, S_IRUGO, show_adapter_name, NULL); + +/* ------------------------------------------------------------------------- */ + +/* + * After opening an instance of this character special file, a file + * descriptor starts out associated only with an i2c_adapter (and bus). + * + * Using the I2C_RDWR ioctl(), you can then *immediately* issue i2c_msg + * traffic to any devices on the bus used by that adapter. That's because + * the i2c_msg vectors embed all the addressing information they need, and + * are submitted directly to an i2c_adapter. However, SMBus-only adapters + * don't support that interface. + * + * To use read()/write() system calls on that file descriptor, or to use + * SMBus interfaces (and work with SMBus-only hosts!), you must first issue + * an I2C_SLAVE (or I2C_SLAVE_FORCE) ioctl. That configures an anonymous + * (never registered) i2c_client so it holds the addressing information + * needed by those system calls and by this SMBus interface. + */ + +static ssize_t i2cdev_read (struct file *file, char __user *buf, size_t count, + loff_t *offset) +{ + char *tmp; + int ret; + + struct i2c_client *client = (struct i2c_client *)file->private_data; + + if (count > 8192) + count = 8192; + + tmp = kmalloc(count,GFP_KERNEL); + if (tmp==NULL) + return -ENOMEM; + + pr_debug("i2c-dev: i2c-%d reading %zu bytes.\n", + iminor(file->f_path.dentry->d_inode), count); + + ret = i2c_master_recv(client,tmp,count); + if (ret >= 0) + ret = copy_to_user(buf,tmp,count)?-EFAULT:ret; + kfree(tmp); + return ret; +} + +static ssize_t i2cdev_write (struct file *file, const char __user *buf, size_t count, + loff_t *offset) +{ + int ret; + char *tmp; + struct i2c_client *client = (struct i2c_client *)file->private_data; + + if (count > 8192) + count = 8192; + + tmp = kmalloc(count,GFP_KERNEL); + if (tmp==NULL) + return -ENOMEM; + if (copy_from_user(tmp,buf,count)) { + kfree(tmp); + return -EFAULT; + } + + pr_debug("i2c-dev: i2c-%d writing %zu bytes.\n", + iminor(file->f_path.dentry->d_inode), count); + + ret = i2c_master_send(client,tmp,count); + kfree(tmp); + return ret; +} + +static int i2cdev_check(struct device *dev, void *addrp) +{ + struct i2c_client *client = i2c_verify_client(dev); + + if (!client || client->addr != *(unsigned int *)addrp) + return 0; + + return dev->driver ? -EBUSY : 0; +} + +/* This address checking function differs from the one in i2c-core + in that it considers an address with a registered device, but no + driver bound to it, as NOT busy. */ +static int i2cdev_check_addr(struct i2c_adapter *adapter, unsigned int addr) +{ + return device_for_each_child(&adapter->dev, &addr, i2cdev_check); +} + +static noinline int i2cdev_ioctl_rdrw(struct i2c_client *client, + unsigned long arg) +{ + struct i2c_rdwr_ioctl_data rdwr_arg; + struct i2c_msg *rdwr_pa; + u8 __user **data_ptrs; + int i, res; + + if (copy_from_user(&rdwr_arg, + (struct i2c_rdwr_ioctl_data __user *)arg, + sizeof(rdwr_arg))) + return -EFAULT; + + /* Put an arbitrary limit on the number of messages that can + * be sent at once */ + if (rdwr_arg.nmsgs > I2C_RDRW_IOCTL_MAX_MSGS) + return -EINVAL; + + rdwr_pa = (struct i2c_msg *) + kmalloc(rdwr_arg.nmsgs * sizeof(struct i2c_msg), + GFP_KERNEL); + if (!rdwr_pa) + return -ENOMEM; + + if (copy_from_user(rdwr_pa, rdwr_arg.msgs, + rdwr_arg.nmsgs * sizeof(struct i2c_msg))) { + kfree(rdwr_pa); + return -EFAULT; + } + + data_ptrs = kmalloc(rdwr_arg.nmsgs * sizeof(u8 __user *), GFP_KERNEL); + if (data_ptrs == NULL) { + kfree(rdwr_pa); + return -ENOMEM; + } + + res = 0; + for (i = 0; i < rdwr_arg.nmsgs; i++) { + /* Limit the size of the message to a sane amount; + * and don't let length change either. */ + if (rdwr_pa[i].len > 8192) { + res = -EINVAL; + break; + } + data_ptrs[i] = (u8 __user *)rdwr_pa[i].buf; + rdwr_pa[i].buf = kmalloc(rdwr_pa[i].len, GFP_KERNEL); + if (rdwr_pa[i].buf == NULL) { + res = -ENOMEM; + break; + } + if (copy_from_user(rdwr_pa[i].buf, data_ptrs[i], + rdwr_pa[i].len)) { + ++i; /* Needs to be kfreed too */ + res = -EFAULT; + break; + } + + /* From Linux 3.5: */ + /* + * If the message length is received from the slave (similar + * to SMBus block read), we must ensure that the buffer will + * be large enough to cope with a message length of + * I2C_SMBUS_BLOCK_MAX as this is the maximum underlying bus + * drivers allow. The first byte in the buffer must be + * pre-filled with the number of extra bytes, which must be + * at least one to hold the message length, but can be + * greater (for example to account for a checksum byte at + * the end of the message.) + */ + if (rdwr_pa[i].flags & I2C_M_RECV_LEN) { + if (!(rdwr_pa[i].flags & I2C_M_RD) || + rdwr_pa[i].buf[0] < 1 || + rdwr_pa[i].len < rdwr_pa[i].buf[0] + + I2C_SMBUS_BLOCK_MAX) { + res = -EINVAL; + break; + } + + rdwr_pa[i].len = rdwr_pa[i].buf[0]; + } + + } + if (res < 0) { + int j; + for (j = 0; j < i; ++j) + kfree(rdwr_pa[j].buf); + kfree(data_ptrs); + kfree(rdwr_pa); + return res; + } + + res = i2c_transfer(client->adapter, rdwr_pa, rdwr_arg.nmsgs); + while (i-- > 0) { + if (res >= 0 && (rdwr_pa[i].flags & I2C_M_RD)) { + if (copy_to_user(data_ptrs[i], rdwr_pa[i].buf, + rdwr_pa[i].len)) + res = -EFAULT; + } + kfree(rdwr_pa[i].buf); + } + kfree(data_ptrs); + kfree(rdwr_pa); + return res; +} + +static noinline int i2cdev_ioctl_slave_rdrw(struct i2c_client *client, + unsigned long arg) +{ + struct i2c_rdwr_ioctl_data rdwr_arg; + struct i2c_msg *rdwr_pa; + u8 __user **data_ptrs; + int i, res; + + if (copy_from_user(&rdwr_arg, + (struct i2c_rdwr_ioctl_data __user *)arg, + sizeof(rdwr_arg))) + return -EFAULT; + + /* Put an arbitrary limit on the number of messages that can + * be sent at once */ + if (rdwr_arg.nmsgs > I2C_RDRW_IOCTL_MAX_MSGS) + return -EINVAL; + + rdwr_pa = (struct i2c_msg *) + kmalloc(rdwr_arg.nmsgs * sizeof(struct i2c_msg), + GFP_KERNEL); + if (!rdwr_pa) + return -ENOMEM; + + if (copy_from_user(rdwr_pa, rdwr_arg.msgs, + rdwr_arg.nmsgs * sizeof(struct i2c_msg))) { + kfree(rdwr_pa); + return -EFAULT; + } + + data_ptrs = kmalloc(rdwr_arg.nmsgs * sizeof(u8 __user *), GFP_KERNEL); + if (data_ptrs == NULL) { + kfree(rdwr_pa); + return -ENOMEM; + } + + res = 0; + for (i = 0; i < rdwr_arg.nmsgs; i++) { + /* Limit the size of the message to a sane amount; + * and don't let length change either. */ + if (rdwr_pa[i].len > 8192) { + res = -EINVAL; + break; + } + data_ptrs[i] = (u8 __user *)rdwr_pa[i].buf; + rdwr_pa[i].buf = kmalloc(rdwr_pa[i].len, GFP_KERNEL); + if (rdwr_pa[i].buf == NULL) { + res = -ENOMEM; + break; + } + if (copy_from_user(rdwr_pa[i].buf, data_ptrs[i], + rdwr_pa[i].len)) { + ++i; /* Needs to be kfreed too */ + res = -EFAULT; + break; + } + + /* From Linux 3.5: */ + /* + * If the message length is received from the slave (similar + * to SMBus block read), we must ensure that the buffer will + * be large enough to cope with a message length of + * I2C_SMBUS_BLOCK_MAX as this is the maximum underlying bus + * drivers allow. The first byte in the buffer must be + * pre-filled with the number of extra bytes, which must be + * at least one to hold the message length, but can be + * greater (for example to account for a checksum byte at + * the end of the message.) + */ + if (rdwr_pa[i].flags & I2C_M_RECV_LEN) { + if (!(rdwr_pa[i].flags & I2C_M_RD) || + rdwr_pa[i].buf[0] < 1 || + rdwr_pa[i].len < rdwr_pa[i].buf[0] + + I2C_SMBUS_BLOCK_MAX) { + res = -EINVAL; + break; + } + + rdwr_pa[i].len = rdwr_pa[i].buf[0]; + } + + } + if (res < 0) { + int j; + for (j = 0; j < i; ++j) + kfree(rdwr_pa[j].buf); + kfree(data_ptrs); + kfree(rdwr_pa); + return res; + } + + res = i2c_slave_transfer(client->adapter, rdwr_pa, rdwr_arg.nmsgs); + while (i-- > 0) { + if (res >= 0 ) { + if (copy_to_user(data_ptrs[i], rdwr_pa[i].buf, + rdwr_pa[i].len)) + res = -EFAULT; + + rdwr_arg.msgs[i].len = rdwr_pa[i].len; + } + kfree(rdwr_pa[i].buf); + } + kfree(data_ptrs); + kfree(rdwr_pa); + return res; +} + +static noinline int i2cdev_ioctl_smbus(struct i2c_client *client, + unsigned long arg) +{ + struct i2c_smbus_ioctl_data data_arg; + union i2c_smbus_large_data temp; + int datasize, res; + + if (copy_from_user(&data_arg, + (struct i2c_smbus_ioctl_data __user *) arg, + sizeof(struct i2c_smbus_ioctl_data))) + return -EFAULT; + if ((data_arg.size != I2C_SMBUS_BYTE) && + (data_arg.size != I2C_SMBUS_QUICK) && + (data_arg.size != I2C_SMBUS_BYTE_DATA) && + (data_arg.size != I2C_SMBUS_WORD_DATA) && + (data_arg.size != I2C_SMBUS_PROC_CALL) && + (data_arg.size != I2C_SMBUS_BLOCK_DATA) && + (data_arg.size != I2C_SMBUS_BLOCK_LARGE_DATA) && + (data_arg.size != I2C_SMBUS_I2C_BLOCK_BROKEN) && + (data_arg.size != I2C_SMBUS_I2C_BLOCK_DATA) && + (data_arg.size != I2C_SMBUS_BLOCK_PROC_CALL)) { + dev_dbg(&client->adapter->dev, + "size out of range (%x) in ioctl I2C_SMBUS.\n", + data_arg.size); + return -EINVAL; + } + /* Note that I2C_SMBUS_READ and I2C_SMBUS_WRITE are 0 and 1, + so the check is valid if size==I2C_SMBUS_QUICK too. */ + if ((data_arg.read_write != I2C_SMBUS_READ) && + (data_arg.read_write != I2C_SMBUS_WRITE)) { + dev_dbg(&client->adapter->dev, + "read_write out of range (%x) in ioctl I2C_SMBUS.\n", + data_arg.read_write); + return -EINVAL; + } + + /* Note that command values are always valid! */ + + if ((data_arg.size == I2C_SMBUS_QUICK) || + ((data_arg.size == I2C_SMBUS_BYTE) && + (data_arg.read_write == I2C_SMBUS_WRITE))) + /* These are special: we do not use data */ + return i2c_smbus_xfer(client->adapter, client->addr, + client->flags, data_arg.read_write, + data_arg.command, data_arg.size, NULL); + + if (data_arg.data == NULL) { + dev_dbg(&client->adapter->dev, + "data is NULL pointer in ioctl I2C_SMBUS.\n"); + return -EINVAL; + } + + if ((data_arg.size == I2C_SMBUS_BYTE_DATA) || + (data_arg.size == I2C_SMBUS_BYTE)) + datasize = sizeof(data_arg.data->byte); + else if ((data_arg.size == I2C_SMBUS_WORD_DATA) || + (data_arg.size == I2C_SMBUS_PROC_CALL)) + datasize = sizeof(data_arg.data->word); + else if (data_arg.size == I2C_SMBUS_BLOCK_LARGE_DATA) + datasize = sizeof(union i2c_smbus_large_data); + else /* size == smbus block, i2c block, or block proc. call */ + datasize = sizeof(data_arg.data->block); + + if ((data_arg.size == I2C_SMBUS_PROC_CALL) || + (data_arg.size == I2C_SMBUS_BLOCK_PROC_CALL) || + (data_arg.size == I2C_SMBUS_I2C_BLOCK_DATA) || + (data_arg.read_write == I2C_SMBUS_WRITE)) { + if (copy_from_user(&temp, data_arg.data, datasize)) + return -EFAULT; + } + if (data_arg.size == I2C_SMBUS_I2C_BLOCK_BROKEN) { + /* Convert old I2C block commands to the new + convention. This preserves binary compatibility. */ + data_arg.size = I2C_SMBUS_I2C_BLOCK_DATA; + if (data_arg.read_write == I2C_SMBUS_READ) + temp.block[0] = I2C_SMBUS_BLOCK_MAX; + } + res = i2c_smbus_xfer(client->adapter, client->addr, client->flags, + data_arg.read_write, data_arg.command, data_arg.size, &temp); + if (!res && ((data_arg.size == I2C_SMBUS_PROC_CALL) || + (data_arg.size == I2C_SMBUS_BLOCK_PROC_CALL) || + (data_arg.read_write == I2C_SMBUS_READ))) { + if (copy_to_user(data_arg.data, &temp, datasize)) + return -EFAULT; + } + return res; +} + +static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct i2c_client *client = (struct i2c_client *)file->private_data; + unsigned long funcs; + + dev_dbg(&client->adapter->dev, "ioctl, cmd=0x%02x, arg=0x%02lx\n", + cmd, arg); + + switch ( cmd ) { + case I2C_SLAVE: + case I2C_SLAVE_FORCE: + /* NOTE: devices set up to work with "new style" drivers + * can't use I2C_SLAVE, even when the device node is not + * bound to a driver. Only I2C_SLAVE_FORCE will work. + * + * Setting the PEC flag here won't affect kernel drivers, + * which will be using the i2c_client node registered with + * the driver model core. Likewise, when that client has + * the PEC flag already set, the i2c-dev driver won't see + * (or use) this setting. + */ + if ((arg > 0x3ff) || + (((client->flags & I2C_M_TEN) == 0) && arg > 0x7f)) + return -EINVAL; + if (cmd == I2C_SLAVE && i2cdev_check_addr(client->adapter, arg)) + return -EBUSY; + /* REVISIT: address could become busy later */ + client->addr = arg; + return 0; + case I2C_TENBIT: + if (arg) + client->flags |= I2C_M_TEN; + else + client->flags &= ~I2C_M_TEN; + return 0; + case I2C_PEC: + if (arg) + client->flags |= I2C_CLIENT_PEC; + else + client->flags &= ~I2C_CLIENT_PEC; + return 0; + case I2C_FUNCS: + funcs = i2c_get_functionality(client->adapter); + return put_user(funcs, (unsigned long __user *)arg); + + case I2C_RDWR: + return i2cdev_ioctl_rdrw(client, arg); + +#ifdef CONFIG_AST_I2C_SLAVE_RDWR + case I2C_SLAVE_RDWR: + return i2cdev_ioctl_slave_rdrw(client, arg); +#endif + + case I2C_SMBUS: + return i2cdev_ioctl_smbus(client, arg); + + case I2C_RETRIES: + client->adapter->retries = arg; + break; + case I2C_TIMEOUT: + client->adapter->timeout = arg; + break; + default: + /* NOTE: returning a fault code here could cause trouble + * in buggy userspace code. Some old kernel bugs returned + * zero in this case, and userspace code might accidentally + * have depended on that bug. + */ + return -ENOTTY; + } + return 0; +} + +static int i2cdev_open(struct inode *inode, struct file *file) +{ + unsigned int minor = iminor(inode); + struct i2c_client *client; + struct i2c_adapter *adap; + struct i2c_dev *i2c_dev; + int ret = 0; + + lock_kernel(); + i2c_dev = i2c_dev_get_by_minor(minor); + if (!i2c_dev) { + ret = -ENODEV; + goto out; + } + + adap = i2c_get_adapter(i2c_dev->adap->nr); + if (!adap) { + ret = -ENODEV; + goto out; + } + + /* This creates an anonymous i2c_client, which may later be + * pointed to some address using I2C_SLAVE or I2C_SLAVE_FORCE. + * + * This client is ** NEVER REGISTERED ** with the driver model + * or I2C core code!! It just holds private copies of addressing + * information and maybe a PEC flag. + */ + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (!client) { + i2c_put_adapter(adap); + ret = -ENOMEM; + goto out; + } + snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr); + client->driver = &i2cdev_driver; + + client->adapter = adap; + file->private_data = client; + +out: + unlock_kernel(); + return ret; +} + +static int i2cdev_release(struct inode *inode, struct file *file) +{ + struct i2c_client *client = file->private_data; + + i2c_put_adapter(client->adapter); + kfree(client); + file->private_data = NULL; + + return 0; +} + +static const struct file_operations i2cdev_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = i2cdev_read, + .write = i2cdev_write, + .unlocked_ioctl = i2cdev_ioctl, + .open = i2cdev_open, + .release = i2cdev_release, +}; + +/* ------------------------------------------------------------------------- */ + +/* + * The legacy "i2cdev_driver" is used primarily to get notifications when + * I2C adapters are added or removed, so that each one gets an i2c_dev + * and is thus made available to userspace driver code. + */ + +static struct class *i2c_dev_class; + +static int i2cdev_attach_adapter(struct i2c_adapter *adap) +{ + struct i2c_dev *i2c_dev; + int res; + + i2c_dev = get_free_i2c_dev(adap); + if (IS_ERR(i2c_dev)) + return PTR_ERR(i2c_dev); + + /* register this i2c device with the driver core */ + i2c_dev->dev = device_create(i2c_dev_class, &adap->dev, + MKDEV(I2C_MAJOR, adap->nr), NULL, + "i2c-%d", adap->nr); + if (IS_ERR(i2c_dev->dev)) { + res = PTR_ERR(i2c_dev->dev); + goto error; + } + res = device_create_file(i2c_dev->dev, &dev_attr_name); + if (res) + goto error_destroy; + + pr_debug("i2c-dev: adapter [%s] registered as minor %d\n", + adap->name, adap->nr); + return 0; +error_destroy: + device_destroy(i2c_dev_class, MKDEV(I2C_MAJOR, adap->nr)); +error: + return_i2c_dev(i2c_dev); + return res; +} + +static int i2cdev_detach_adapter(struct i2c_adapter *adap) +{ + struct i2c_dev *i2c_dev; + + i2c_dev = i2c_dev_get_by_minor(adap->nr); + if (!i2c_dev) /* attach_adapter must have failed */ + return 0; + + device_remove_file(i2c_dev->dev, &dev_attr_name); + return_i2c_dev(i2c_dev); + device_destroy(i2c_dev_class, MKDEV(I2C_MAJOR, adap->nr)); + + pr_debug("i2c-dev: adapter [%s] unregistered\n", adap->name); + return 0; +} + +static struct i2c_driver i2cdev_driver = { + .driver = { + .name = "dev_driver", + }, + .attach_adapter = i2cdev_attach_adapter, + .detach_adapter = i2cdev_detach_adapter, +}; + +/* ------------------------------------------------------------------------- */ + +/* + * module load/unload record keeping + */ + +static int __init i2c_dev_init(void) +{ + int res; + + printk(KERN_INFO "i2c /dev entries driver\n"); + + res = register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops); + if (res) + goto out; + + i2c_dev_class = class_create(THIS_MODULE, "i2c-dev"); + if (IS_ERR(i2c_dev_class)) { + res = PTR_ERR(i2c_dev_class); + goto out_unreg_chrdev; + } + + res = i2c_add_driver(&i2cdev_driver); + if (res) + goto out_unreg_class; + + return 0; + +out_unreg_class: + class_destroy(i2c_dev_class); +out_unreg_chrdev: + unregister_chrdev(I2C_MAJOR, "i2c"); +out: + printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__); + return res; +} + +static void __exit i2c_dev_exit(void) +{ + i2c_del_driver(&i2cdev_driver); + class_destroy(i2c_dev_class); + unregister_chrdev(I2C_MAJOR,"i2c"); +} + +MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl> and " + "Simon G. Vogl <simon@tk.uni-linz.ac.at>"); +MODULE_DESCRIPTION("I2C /dev entries driver"); +MODULE_LICENSE("GPL"); + +module_init(i2c_dev_init); +module_exit(i2c_dev_exit); |