diff options
Diffstat (limited to 'drivers/i2c/chips')
-rw-r--r-- | drivers/i2c/chips/Kconfig | 188 | ||||
-rw-r--r-- | drivers/i2c/chips/Makefile | 30 | ||||
-rw-r--r-- | drivers/i2c/chips/at24.c | 582 | ||||
-rw-r--r-- | drivers/i2c/chips/ds1682.c | 267 | ||||
-rw-r--r-- | drivers/i2c/chips/eeprom.c | 257 | ||||
-rw-r--r-- | drivers/i2c/chips/isp1301_omap.c | 1683 | ||||
-rw-r--r-- | drivers/i2c/chips/max6875.c | 246 | ||||
-rw-r--r-- | drivers/i2c/chips/mcu_mpc8349emitx.c | 209 | ||||
-rw-r--r-- | drivers/i2c/chips/menelaus.c | 1285 | ||||
-rw-r--r-- | drivers/i2c/chips/pca9539.c | 152 | ||||
-rw-r--r-- | drivers/i2c/chips/pcf8574.c | 215 | ||||
-rw-r--r-- | drivers/i2c/chips/pcf8575.c | 198 | ||||
-rw-r--r-- | drivers/i2c/chips/pcf8591.c | 325 | ||||
-rw-r--r-- | drivers/i2c/chips/tps65010.c | 1072 | ||||
-rw-r--r-- | drivers/i2c/chips/tsl2550.c | 489 |
15 files changed, 7198 insertions, 0 deletions
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); |