From 55e6d0152fa87ca30c3529077ba2ec859aa50709 Mon Sep 17 00:00:00 2001 From: Tony Lindgren Date: Mon, 9 May 2005 14:24:48 -0700 Subject: [PATCH] Add various OMAP I2C drivers Adds various OMAP I2C drivers. Signed-off-by: Tony Lindgren --- drivers/i2c/Kconfig | 1 + drivers/i2c/busses/Kconfig | 7 + drivers/i2c/busses/Makefile | 1 + drivers/i2c/busses/i2c-omap.c | 631 +++++++++++++ drivers/i2c/busses/i2c-omap.h | 114 +++ drivers/i2c/chips/Kconfig | 23 + drivers/i2c/chips/Makefile | 3 + drivers/i2c/chips/gpio_expander_omap.c | 82 ++ drivers/i2c/chips/isp1301_omap.c | 66 +- drivers/i2c/chips/tlv320aic23.c | 172 ++++ drivers/i2c/chips/tps65010.c | 1072 ++++++++++++++++++++++ include/asm-arm/arch-omap/gpioexpander.h | 24 + 12 files changed, 2181 insertions(+), 15 deletions(-) create mode 100644 drivers/i2c/busses/i2c-omap.c create mode 100644 drivers/i2c/busses/i2c-omap.h create mode 100644 drivers/i2c/chips/gpio_expander_omap.c create mode 100644 drivers/i2c/chips/tlv320aic23.c create mode 100644 drivers/i2c/chips/tps65010.c create mode 100644 include/asm-arm/arch-omap/gpioexpander.h diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig index 24383afdda7..8105b07e2ad 100644 --- a/drivers/i2c/Kconfig +++ b/drivers/i2c/Kconfig @@ -6,6 +6,7 @@ menu "I2C support" config I2C tristate "I2C support" + default y if MACH_OMAP_H3 || MACH_OMAP_OSK ---help--- I2C (pronounce: I-square-C) is a slow serial bus protocol used in many micro controller applications and developed by Philips. SMBus, diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index a0018de3bef..863b1ad1603 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -497,4 +497,11 @@ config I2C_MV64XXX This driver can also be built as a module. If so, the module will be called i2c-mv64xxx. +config I2C_OMAP + tristate "OMAP I2C adapter" + depends on I2C + default y if MACH_OMAP_H3 || MACH_OMAP_OSK + help + Support for TI OMAP I2C driver. Say yes if you want to use the OMAP + I2C interface. endmenu diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index 42d6d814da7..1d40384c1d1 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -41,6 +41,7 @@ obj-$(CONFIG_I2C_VIAPRO) += i2c-viapro.o obj-$(CONFIG_I2C_VOODOO3) += i2c-voodoo3.o obj-$(CONFIG_SCx200_ACB) += scx200_acb.o obj-$(CONFIG_SCx200_I2C) += scx200_i2c.o +obj-$(CONFIG_I2C_OMAP) += i2c-omap.o ifeq ($(CONFIG_I2C_DEBUG_BUS),y) EXTRA_CFLAGS += -DDEBUG diff --git a/drivers/i2c/busses/i2c-omap.c b/drivers/i2c/busses/i2c-omap.c new file mode 100644 index 00000000000..0ebfac452c7 --- /dev/null +++ b/drivers/i2c/busses/i2c-omap.c @@ -0,0 +1,631 @@ +/* + * linux/drivers/i2c/i2c-omap.c + * + * TI OMAP I2C unified algorith+adapter driver (inspired by i2c-ibm_iic.c and i2c-omap1510.c) + * + * Copyright (C) 2003 MontaVista Software, Inc. + * + * Copyright (C) 2004 Texas Instruments. + * + * ---------------------------------------------------------------------------- + * This file was highly leveraged from i2c-elektor.c, which was created + * by Simon G. Vogl and Hans Berglund: + * + * + * Copyright 1995-97 Simon G. Vogl + * 1998-99 Hans Berglund + * + * With some changes from Kysti M�kki and even + * Frodo Looijaard + * + * 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. + * ---------------------------------------------------------------------------- + Modifications: + ver. 1.1: Nov 2003, MontaVista Software + - added DPM support + ver. 1.2: Feb 2004, Texas Instruments + - Ported to 2.6 kernel (Feb 2004) + - Added support for I2C_M_IGNORE_NAK option. + ver. 1.3: Mar 2004, Juha Yrjölä + - Cleaned up + ver. 1.4: Aug 2004, Thiago Radicchi DCC-UFMG / iNdT + - Updated omap_i2c_isr to remove messages of too much work in one IRQ, + by reading the interrupt vector, as specified on ref [1] + ver. 1.5: Oct 2004, Tuukka Tikkanen + - Changed clock handling + * + * REFERENCES: + * + * 1. OMAP5910 Dual-Core Processor Inter-Integrated Circuit (I2C) + * Controller Reference Guide + * Document number: spru681 + * Date: October 2003 + * http://www-s.ti.com/sc/psheets/spru681/spru681.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "i2c-omap.h" + +/* ----- global defines ----------------------------------------------- */ +#define MODULE_NAME "OMAP I2C" +#define OMAP_I2C_TIMEOUT (1*HZ) /* timeout waiting for an I2C transaction */ + +#define I2C_OMAP_DEBUG +#ifdef I2C_OMAP_DEBUG +static int i2c_debug = 0; +#define DEB0(format, arg...) printk(KERN_DEBUG MODULE_NAME " DEBUG: " format "\n", ## arg ) +#define DEB1(format, arg...) \ + if (i2c_debug>=1) { \ + printk(KERN_DEBUG MODULE_NAME " DEBUG: " format "\n", ## arg ); \ + } +#define DEB2(format, arg...) \ + if (i2c_debug>=2) { \ + printk(KERN_DEBUG MODULE_NAME " DEBUG: " format "\n", ## arg ); \ + } +#define DEB3(format, arg...) \ + if (i2c_debug>=3) { \ + printk(KERN_DEBUG MODULE_NAME " DEBUG: " format "\n", ## arg ); \ + } +#define DEB9(format, arg...) \ + /* debug the protocol by showing transferred bits */ \ + if (i2c_debug>=9) { \ + printk(KERN_DEBUG MODULE_NAME " DEBUG: " format "\n", ## arg ); \ + } +#else +#define DEB0(fmt, args...) +#define DEB1(fmt, args...) +#define DEB2(fmt, args...) +#define DEB3(fmt, args...) +#define DEB9(fmt, args...) +#endif + +#define err(format, arg...) printk(KERN_ERR MODULE_NAME " ERROR: " format "\n", ## arg ) +#define info(format, arg...) printk(KERN_INFO MODULE_NAME ": " format "\n", ## arg ) +#define warn(format, arg...) printk(KERN_WARNING MODULE_NAME " WARNING: " format "\n", ## arg ) +#define emerg(format, arg...) printk(KERN_EMERG MODULE_NAME " EMERGENCY: " format "\n", ## arg ) + +#ifdef CONFIG_ARCH_OMAP1510 +#define omap_i2c_rev1() (readw(OMAP_I2C_REV) < 0x20) +#else +#define omap_i2c_rev1() 0 +#endif + +#define DEFAULT_OWN 1 /*default own I2C address */ +#define MAX_MESSAGES 65536 /* max number of messages */ + +static int clock = 100; /* Default: Fast Mode = 400 KHz, Standard Mode = 100 KHz */ +static int own; +static int i2c_scan; /* have a look at what's hanging 'round */ + +static struct omap_i2c_dev { + int cmd_complete, cmd_err; + wait_queue_head_t cmd_wait; + u8 *buf; + size_t buf_len; +} omap_i2c_dev; + + +static int omap_i2c_reset(void) +{ + unsigned long timeout; + u16 psc; + struct clk *armxor_ck; + unsigned long armxor_rate; + + if(!cpu_is_omap1510()) { + + writew(OMAP_I2C_SYSC_SRST, OMAP_I2C_SYSC); /*soft reset */ + } + else { + writew(OMAP_I2C_CON_RST, OMAP_I2C_CON); /* reset */ + } + + armxor_ck = clk_get(0, "armxor_ck"); + if (IS_ERR(armxor_ck)) { + printk(KERN_WARNING "i2c: Could not obtain armxor_ck rate.\n"); + armxor_rate = 12000000; + } else { + armxor_rate = clk_get_rate(armxor_ck); + clk_put(armxor_ck); + } + + if (armxor_rate <= 16000000) + psc = 0; + else + psc = (armxor_rate + 8000000) / 12000000; + + /* Setup clock prescaler to obtain approx 12MHz I2C module clock: */ + writew(psc, OMAP_I2C_PSC); + + /* Program desired operating rate */ + armxor_rate /= (psc + 1) * 1000; + if (psc > 2) + psc = 2; + writew(armxor_rate / (clock * 2) - 7 + psc, OMAP_I2C_SCLL); + writew(armxor_rate / (clock * 2) - 7 + psc, OMAP_I2C_SCLH); + + + /* Set Own Address: */ + writew(own, OMAP_I2C_OA); + + /* Enable interrupts */ + writew((OMAP_I2C_IE_XRDY_IE | OMAP_I2C_IE_RRDY_IE | OMAP_I2C_IE_ARDY_IE | + OMAP_I2C_IE_NACK_IE | OMAP_I2C_IE_AL_IE), OMAP_I2C_IE); + + /* Take the I2C module out of reset: */ + writew(OMAP_I2C_CON_EN, OMAP_I2C_CON); + + if(!cpu_is_omap1510()){ + timeout = jiffies + OMAP_I2C_TIMEOUT; + while (!(readw(OMAP_I2C_SYSS) & OMAP_I2C_SYSS_RDONE)) { + if (time_after(jiffies, timeout)) { + err("timeout waiting for I2C reset complete"); + return -EFAULT; + } + schedule_timeout(1); + } + } + + return 0; + +} + +/* + * Waiting on Bus Busy + */ +static int +omap_i2c_wait_for_bb(char allow_sleep) +{ + unsigned long timeout; + + timeout = jiffies + OMAP_I2C_TIMEOUT; + while (readw(OMAP_I2C_STAT) & OMAP_I2C_STAT_BB) { + if (time_after(jiffies, timeout)) { + warn("timeout waiting for bus ready"); + return -ETIMEDOUT; + } + if (allow_sleep) + schedule_timeout(1); + } + + return 0; +} + +/* + * Low level master read/write transaction. + */ +static int +omap_i2c_xfer_msg(struct i2c_adapter *adap, struct i2c_msg *msg, int stop) +{ + struct omap_i2c_dev *dev = i2c_get_adapdata(adap); + u8 zero_byte = 0; + int r; + u16 w; + + DEB2("addr: 0x%04x, len: %d, flags: 0x%x, stop: %d", + msg->addr, msg->len, msg->flags, stop); + + writew(msg->addr, OMAP_I2C_SA); + + /* Sigh, seems we can't do zero length transactions. Thus, we + * can't probe for devices w/o actually sending/receiving at least + * a single byte. So we'll set count to 1 for the zero length + * transaction case and hope we don't cause grief for some + * arbitrary device due to random byte write/read during + * probes. + */ + if (msg->len == 0) { + dev->buf = &zero_byte; + dev->buf_len = 1; + } else { + dev->buf = msg->buf; + dev->buf_len = msg->len; + } + writew(dev->buf_len, OMAP_I2C_CNT); + dev->cmd_complete = 0; + dev->cmd_err = 0; + w = OMAP_I2C_CON_EN | OMAP_I2C_CON_MST | OMAP_I2C_CON_STT; + if (msg->flags & I2C_M_TEN) + w |= OMAP_I2C_CON_XA; + if (!(msg->flags & I2C_M_RD)) + w |= OMAP_I2C_CON_TRX; + if (stop) + w |= OMAP_I2C_CON_STP; + writew(w, OMAP_I2C_CON); + + r = wait_event_interruptible_timeout(dev->cmd_wait, + dev->cmd_complete, + OMAP_I2C_TIMEOUT); + dev->buf_len = 0; + if (r < 0) + return r; + if (!dev->cmd_complete) { + omap_i2c_reset(); + return -ETIMEDOUT; + } + if (!dev->cmd_err) + return msg->len; + + /* We have an error */ + if (dev->cmd_err & OMAP_I2C_STAT_NACK) { + if (msg->flags & I2C_M_IGNORE_NAK) + return msg->len; + if (stop) + writew(readw(OMAP_I2C_CON) | OMAP_I2C_CON_STP, OMAP_I2C_CON); + return -EREMOTEIO; + } + if (dev->cmd_err & OMAP_I2C_STAT_AL || + dev->cmd_err & OMAP_I2C_STAT_ROVR || + dev->cmd_err & OMAP_I2C_STAT_XUDF) { + omap_i2c_reset(); + return -EIO; + } + return msg->len; +} + +/* + * Prepare controller for a transaction and call omap_i2c_rxbytes + * to do the work. + */ +static int +omap_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) +{ + int i; + int r = 0; + + DEB1("msgs: %d", num); + + if (num < 1 || num > MAX_MESSAGES) + return -EINVAL; + + /* Check for valid parameters in messages */ + for (i = 0; i < num; i++) + if (msgs[i].buf == NULL) + return -EINVAL; + + if ((r = omap_i2c_wait_for_bb(1)) < 0) + return r; + + for (i = 0; i < num; i++) { + DEB2("msg: %d, addr: 0x%04x, len: %d, flags: 0x%x", + i, msgs[i].addr, msgs[i].len, msgs[i].flags); + + r = omap_i2c_xfer_msg(adap, &msgs[i], (i == (num - 1))); + + DEB2("r: %d", r); + + if (r != msgs[i].len) + break; + } + + if (r >= 0 && num > 1) + r = num; + + DEB1("r: %d", r); + + return r; +} + +/* + * Sanity check for the adapter hardware - check the reaction of + * the bus lines only if it seems to be idle. + * + * Scan the I2C bus for valid 7 bit addresses + * (ie things that ACK on 1byte read) + * if i2c_debug is off we print everything on one line. + * if i2c_debug is on we do a newline per print so we don't + * clash too much with printf's in the other functions. + * TODO: check for 10-bit mode and never run as a slave. + */ +static int +omap_i2c_scan_bus(struct i2c_adapter *adap) +{ + int found = 0; + int i; + struct i2c_msg msg; + char data[1]; + + info("scanning for active I2C devices on the bus..."); + + for (i = 1; i < 0x7f; i++) { + if (readw(OMAP_I2C_OA) == i) + continue; + + msg.addr = i; + msg.buf = data; + msg.len = 0; + msg.flags = I2C_M_RD; + + if (omap_i2c_xfer(adap, &msg, 1) == 0) { + info("I2C device 0x%02x found", i); + found++; + } + } + + if (!found) + info("found nothing"); + + return found; +} + + +static u32 +omap_i2c_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static inline void +omap_i2c_complete_cmd(struct omap_i2c_dev *dev) +{ + dev->cmd_complete = 1; + wake_up(&dev->cmd_wait); +} + +static irqreturn_t +omap_i2c_isr(int this_irq, void *dev_id, struct pt_regs *regs) +{ + struct omap_i2c_dev *dev = dev_id; + u16 bits; + u16 stat, w; + int count = 0; + u16 iv_read; + + bits = readw(OMAP_I2C_IE); + while ((stat = readw(OMAP_I2C_STAT)) & bits) { + + if (count++ == 100) { + warn("Too much work in one IRQ"); + break; + } + + writew(stat, OMAP_I2C_STAT); + if (stat & OMAP_I2C_STAT_ARDY) { + omap_i2c_complete_cmd(dev); + writew(OMAP_I2C_STAT_ARDY, OMAP_I2C_STAT); + if (omap_i2c_rev1()) + iv_read = readw(OMAP_I2C_IV); + continue; + } + if (stat & OMAP_I2C_STAT_RRDY) { + w = readw(OMAP_I2C_DATA); + if (dev->buf_len) { + *dev->buf++ = w; + dev->buf_len--; + if (dev->buf_len) { + *dev->buf++ = w >> 8; + dev->buf_len--; + } + if (omap_i2c_rev1() && !dev->buf_len) + omap_i2c_complete_cmd(dev); + } else + err("RRDY IRQ while no data requested"); + writew(OMAP_I2C_STAT_RRDY, OMAP_I2C_STAT); + if (omap_i2c_rev1()) + iv_read = readw(OMAP_I2C_IV); + continue; + } + if (stat & OMAP_I2C_STAT_XRDY) { + w = 0; + if (dev->buf_len) { + w = *dev->buf++; + dev->buf_len--; + if (dev->buf_len) { + w |= *dev->buf++ << 8; + dev->buf_len--; + } + } else { + err("XRDY IRQ while no data to send"); + } + writew(w, OMAP_I2C_DATA); + /* We have to make sure the XRDY bit is reset */ + writew(OMAP_I2C_STAT_XRDY, OMAP_I2C_STAT); + if (omap_i2c_rev1()) { + iv_read = readw(OMAP_I2C_IV); + if (!dev->buf_len) + omap_i2c_complete_cmd(dev); + } + continue; + } + if (stat & OMAP_I2C_STAT_ROVR) { + warn("Receive overrun"); + dev->cmd_err |= OMAP_I2C_STAT_ROVR; + } + if (stat & OMAP_I2C_STAT_XUDF) { + warn("Transmit overflow"); + dev->cmd_err |= OMAP_I2C_STAT_XUDF; + } + if (stat & OMAP_I2C_STAT_NACK) { + dev->cmd_err |= OMAP_I2C_STAT_NACK; + omap_i2c_complete_cmd(dev); + writew(OMAP_I2C_CON_STP, OMAP_I2C_CON); + } + if (stat & OMAP_I2C_STAT_AL) { + warn("Arbitration lost"); + dev->cmd_err |= OMAP_I2C_STAT_AL; + omap_i2c_complete_cmd(dev); + } + if (omap_i2c_rev1()) + iv_read = readw(OMAP_I2C_IV); + + } + return IRQ_HANDLED; +} + +static int omap_i2c_remove(struct device *dev) +{ + return 0; +} + +static void omap_i2c_device_release(struct device *dev) +{ + /* Nothing */ +} + +static struct i2c_algorithm omap_i2c_algo = { + .name = "OMAP I2C algorithm", + .id = I2C_ALGO_EXP, + .master_xfer = omap_i2c_xfer, + .smbus_xfer = NULL, + .slave_send = NULL, + .slave_recv = NULL, + .algo_control = NULL, + .functionality = omap_i2c_func, +}; + +static struct i2c_adapter omap_i2c_adap = { + .owner = THIS_MODULE, + .name = "OMAP I2C adapter", + .id = I2C_ALGO_EXP, /* REVISIT: register for id */ + .algo = &omap_i2c_algo, + .algo_data = NULL, + .client_register = NULL, + .client_unregister = NULL, +}; + +static struct device_driver omap_i2c_driver = { + .name = "omap_i2c", + .bus = &platform_bus_type, + .remove = omap_i2c_remove, +}; + +static struct platform_device omap_i2c_device = { + .name = "i2c", + .id = -1, + .dev = { + .driver = &omap_i2c_driver, + .release = omap_i2c_device_release, + }, +}; + +static int __init +omap_i2c_init(void) +{ + int r; + + info("Driver ver. 1.3"); + DEB0("%s %s", __TIME__, __DATE__); + + if (clock > 200) + clock = 400; /*Fast mode */ + else + clock = 100; /*Standard mode */ + + if (own < 1 || own > 0x7f) + own = DEFAULT_OWN; + + memset(&omap_i2c_dev, 0, sizeof(omap_i2c_dev)); + init_waitqueue_head(&omap_i2c_dev.cmd_wait); + + r = (int) request_region(OMAP_I2C_BASE, OMAP_I2C_IOSIZE, MODULE_NAME); + if (!r) { + err("I2C is already in use"); + return -ENODEV; + } + + r = request_irq(INT_I2C, omap_i2c_isr, 0, MODULE_NAME, &omap_i2c_dev); + if (r) { + err("failed to request I2C IRQ"); + goto do_release_region; + } + + i2c_set_adapdata(&omap_i2c_adap, &omap_i2c_dev); + r = i2c_add_adapter(&omap_i2c_adap); + if (r) { + err("failed to add adapter"); + goto do_free_irq; + return r; + } + + /* configure I/O pin multiplexing */ + /* FIXME: This should be done in bootloader */ + omap_cfg_reg(I2C_SCL); + omap_cfg_reg(I2C_SDA); + + omap_i2c_reset(); + + if (i2c_scan) + omap_i2c_scan_bus(&omap_i2c_adap); + if(driver_register(&omap_i2c_driver) != 0) + printk(KERN_ERR "Driver register failed for omap_i2c\n"); + if(platform_device_register(&omap_i2c_device) != 0) { + printk(KERN_ERR "Device register failed for i2c\n"); + driver_unregister(&omap_i2c_driver); + } + + return 0; + +do_free_irq: + free_irq(INT_I2C, &omap_i2c_dev); +do_release_region: + release_region(OMAP_I2C_BASE, OMAP_I2C_IOSIZE); + + return r; +} + +static void __exit +omap_i2c_exit(void) +{ + i2c_del_adapter(&omap_i2c_adap); + writew(0, OMAP_I2C_CON); + free_irq(INT_I2C, &omap_i2c_dev); + release_region(OMAP_I2C_BASE, OMAP_I2C_IOSIZE); + driver_unregister(&omap_i2c_driver); + platform_device_unregister(&omap_i2c_device); +} + +MODULE_AUTHOR("MontaVista Software, Inc."); +MODULE_DESCRIPTION("TI OMAP I2C bus adapter"); +MODULE_LICENSE("GPL"); + +module_param(clock, int, 0); +MODULE_PARM_DESC(clock, + "Set I2C clock in KHz: 100 (Standard Mode) or 400 (Fast Mode)"); + +module_param(own, int, 0); + +module_param(i2c_scan, int, 0); +MODULE_PARM_DESC(i2c_scan, "Scan for active I2C clients on the bus"); + +#ifdef I2C_OMAP_DEBUG +module_param(i2c_debug, int, 0); +MODULE_PARM_DESC(i2c_debug, + "debug level - 0 off; 1 normal; 2,3 more verbose; " + "9 omap-protocol"); +#endif + +/* i2c may be needed to bring up other drivers */ +subsys_initcall(omap_i2c_init); +module_exit(omap_i2c_exit); diff --git a/drivers/i2c/busses/i2c-omap.h b/drivers/i2c/busses/i2c-omap.h new file mode 100644 index 00000000000..67164210940 --- /dev/null +++ b/drivers/i2c/busses/i2c-omap.h @@ -0,0 +1,114 @@ +/* + * linux/drivers/i2c/i2c-omap1610.h + * + * BRIEF MODULE DESCRIPTION + * OMAP I2C register definitions + * + * Copyright (C) 2004 Texas Instruments. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + * HISTORY: + * + * 20040824: Thiago Radicchi DCC-UFMG / iNdT + * Removed some ifdefs which broke compilation for some platforms. + * Added new defintion for interrupt vector. + */ + +/* I2C Registers: */ + +#define OMAP_I2C_BASE IO_ADDRESS(0xfffb3800) +#define OMAP_I2C_IOSIZE (0x40) +#define OMAP_I2C_REV (OMAP_I2C_BASE + 0x00) +#define OMAP_I2C_IE (OMAP_I2C_BASE + 0x04) +#define OMAP_I2C_STAT (OMAP_I2C_BASE + 0x08) +#define OMAP_I2C_IV (OMAP_I2C_BASE + 0x0c) +#define OMAP_I2C_SYSS (OMAP_I2C_BASE + 0x10) +#define OMAP_I2C_BUF (OMAP_I2C_BASE + 0x14) +#define OMAP_I2C_CNT (OMAP_I2C_BASE + 0x18) +#define OMAP_I2C_DATA (OMAP_I2C_BASE + 0x1c) +#define OMAP_I2C_SYSC (OMAP_I2C_BASE + 0x20) +#define OMAP_I2C_CON (OMAP_I2C_BASE + 0x24) +#define OMAP_I2C_OA (OMAP_I2C_BASE + 0x28) +#define OMAP_I2C_SA (OMAP_I2C_BASE + 0x2c) +#define OMAP_I2C_PSC (OMAP_I2C_BASE + 0x30) +#define OMAP_I2C_SCLL (OMAP_I2C_BASE + 0x34) +#define OMAP_I2C_SCLH (OMAP_I2C_BASE + 0x38) +#define OMAP_I2C_SYSTEST (OMAP_I2C_BASE + 0x3c) + +/* I2C Interrupt Enable Register (OMAP_I2C_IE): */ + +#define OMAP_I2C_IE_XRDY_IE (1 << 4) /* Transmit data ready interrupt enable */ +#define OMAP_I2C_IE_RRDY_IE (1 << 3) /* Receive data ready interrupt enable */ +#define OMAP_I2C_IE_ARDY_IE (1 << 2) /* Register access ready interrupt enable */ +#define OMAP_I2C_IE_NACK_IE (1 << 1) /* No acknowledgment interrupt enable */ +#define OMAP_I2C_IE_AL_IE (1 << 0) /* Arbitration lost interrupt enable */ + +/* I2C Status Register (OMAP_I2C_STAT): */ + +#define OMAP_I2C_STAT_SBD (1 << 15) /* Single byte data */ +#define OMAP_I2C_STAT_BB (1 << 12) /* Bus busy */ +#define OMAP_I2C_STAT_ROVR (1 << 11) /* Receive overrun */ +#define OMAP_I2C_STAT_XUDF (1 << 10) /* Transmit underflow */ +#define OMAP_I2C_STAT_AAS (1 << 9) /* Address as slave */ +#define OMAP_I2C_STAT_AD0 (1 << 8) /* Address zero */ +#define OMAP_I2C_STAT_XRDY (1 << 4) /* Transmit data ready */ +#define OMAP_I2C_STAT_RRDY (1 << 3) /* Receive data ready */ +#define OMAP_I2C_STAT_ARDY (1 << 2) /* Register access ready */ +#define OMAP_I2C_STAT_NACK (1 << 1) /* No acknowledgment interrupt enable */ +#define OMAP_I2C_STAT_AL (1 << 0) /* Arbitration lost interrupt enable */ + +/* I2C Buffer Configuration Register (OMAP_I2C_BUF): */ + +#define OMAP_I2C_BUF_RDMA_EN (1 << 15) /* Receive DMA channel enable */ +#define OMAP_I2C_BUF_XDMA_EN (1 << 7) /* Transmit DMA channel enable */ + +/* I2C Configuration Register (OMAP_I2C_CON): */ + +#define OMAP_I2C_CON_EN (1 << 15) /* I2C module enable */ +#define OMAP_I2C_CON_RST (0 << 15) /* I2C module reset */ +#define OMAP_I2C_CON_BE (1 << 14) /* Big endian mode */ +#define OMAP_I2C_CON_STB (1 << 11) /* Start byte mode (master mode only) */ +#define OMAP_I2C_CON_MST (1 << 10) /* Master/slave mode */ +#define OMAP_I2C_CON_TRX (1 << 9) /* Transmitter/receiver mode (master mode only) */ +#define OMAP_I2C_CON_XA (1 << 8) /* Expand address */ +#define OMAP_I2C_CON_RM (1 << 2) /* Repeat mode (master mode only) */ +#define OMAP_I2C_CON_STP (1 << 1) /* Stop condition (master mode only) */ +#define OMAP_I2C_CON_STT (1 << 0) /* Start condition (master mode only) */ + +/* I2C System Test Register (OMAP_I2C_SYSTEST): */ + +#define OMAP_I2C_SYSTEST_ST_EN (1 << 15) /* System test enable */ +#define OMAP_I2C_SYSTEST_FREE (1 << 14) /* Free running mode (on breakpoint) */ +#define OMAP_I2C_SYSTEST_TMODE_MASK (3 << 12) /* Test mode select */ +#define OMAP_I2C_SYSTEST_TMODE_SHIFT (12) /* Test mode select */ +#define OMAP_I2C_SYSTEST_SCL_I (1 << 3) /* SCL line sense input value */ +#define OMAP_I2C_SYSTEST_SCL_O (1 << 2) /* SCL line drive output value */ +#define OMAP_I2C_SYSTEST_SDA_I (1 << 1) /* SDA line sense input value */ +#define OMAP_I2C_SYSTEST_SDA_O (1 << 0) /* SDA line drive output value */ + +/* I2C System Status register (OMAP_I2C_SYSS): */ + +#define OMAP_I2C_SYSS_RDONE 1 /* Reset Done */ + +/* I2C System Configuration Register (OMAP_I2C_SYSC): */ + +#define OMAP_I2C_SYSC_SRST (1 << 1) /* Soft Reset */ diff --git a/drivers/i2c/chips/Kconfig b/drivers/i2c/chips/Kconfig index 74d23cfce2a..2d901e1bac0 100644 --- a/drivers/i2c/chips/Kconfig +++ b/drivers/i2c/chips/Kconfig @@ -440,4 +440,27 @@ config SENSORS_M41T00 This driver can also be built as a module. If so, the module will be called m41t00. +config TPS65010 + tristate "TPS65010 Power management chip" + depends on I2C + default y if MACH_OMAP_H3 || MACH_OMAP_OSK + help + If you say yes here you get support for the TPS65010 Power management + chip. + + This driver can also be built as a module. If so, the module + will be called tps65010. + +config SENSORS_TLV320AIC23 + tristate "Texas Instruments TLV320AIC23 Codec" + depends on I2C && I2C_OMAP + help + If you say yes here you get support for the I2C control + interface for Texas Instruments TLV320AIC23 audio codec. + +config GPIOEXPANDER_OMAP + bool "GPIO Expander PCF8574PWR for OMAP" + depends on I2C && ARCH_OMAP16XX + help + If you say yes here you get support for I/O expander calls to configure IrDA, Camera and audio devices endmenu diff --git a/drivers/i2c/chips/Makefile b/drivers/i2c/chips/Makefile index 65599161a17..4a026691703 100644 --- a/drivers/i2c/chips/Makefile +++ b/drivers/i2c/chips/Makefile @@ -40,7 +40,10 @@ obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc47b397.o obj-$(CONFIG_SENSORS_SMSC47M1) += smsc47m1.o obj-$(CONFIG_SENSORS_VIA686A) += via686a.o obj-$(CONFIG_SENSORS_W83L785TS) += w83l785ts.o +obj-$(CONFIG_TPS65010) += tps65010.o obj-$(CONFIG_ISP1301_OMAP) += isp1301_omap.o +obj-$(CONFIG_SENSORS_TLV320AIC23) += tlv320aic23.o +obj-$(CONFIG_GPIOEXPANDER_OMAP) += gpio_expander_omap.o ifeq ($(CONFIG_I2C_DEBUG_CHIP),y) EXTRA_CFLAGS += -DDEBUG diff --git a/drivers/i2c/chips/gpio_expander_omap.c b/drivers/i2c/chips/gpio_expander_omap.c new file mode 100644 index 00000000000..7f428641400 --- /dev/null +++ b/drivers/i2c/chips/gpio_expander_omap.c @@ -0,0 +1,82 @@ +/* + * drivers/i2c/chips/gpio_expander_omap.c + * + * Copyright (C) 2004 Texas Instruments Inc + * Author: + * + * gpio expander is used to configure IrDA, camera and audio devices on omap 1710 processor. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include + +int read_gpio_expa(u8 * val, int addr); +int write_gpio_expa(u8 val, int addr); + +#define OMAP_IRDA_DEBUG 0 + +#if (OMAP_IRDA_DEBUG > 0) +#define DBG(format, args...) printk(KERN_ERR "%s(): " format, __FUNCTION__, ## args); +#define DBG_IRQ(format, args...) printk(KERN_ERR "%s(): " format, __FUNCTION__, ## args); +#else +#define DBG(format, args...) +#define DBG_IRQ(format, args...) +#endif + +int write_gpio_expa(u8 val, int addr) +{ + struct i2c_adapter *adap; + int err; + struct i2c_msg msg[1]; + unsigned char data[1]; + + adap = i2c_get_adapter(0); + if (!adap) + return -ENODEV; + msg->addr = addr; /* I2C address of GPIO EXPA */ + msg->flags = 0; + msg->len = 1; + msg->buf = data; + data[0] = val; + err = i2c_transfer(adap, msg, 1); + if (err >= 0) + return 0; + return err; +} + +/* Read from I/O EXPANDER on the H3 board. + * The IO expanders need an independent I2C client driver. + */ + +int read_gpio_expa(u8 * val, int addr) +{ + struct i2c_adapter *adap; + int err; + struct i2c_msg msg[1]; + unsigned char data[1]; + + adap = i2c_get_adapter(0); + if (!adap) + return -ENODEV; + msg->addr = addr; /* I2C address of GPIO EXPA */ + msg->flags = I2C_M_RD; + msg->len = 2; + msg->buf = data; + err = i2c_transfer(adap, msg, 1); + *val = data[0]; + + DBG("I2C: Read data is %x\n", (u8) * data); + if (err >= 0) + return 0; + return err; +} + +EXPORT_SYMBOL(read_gpio_expa); +EXPORT_SYMBOL(write_gpio_expa); + diff --git a/drivers/i2c/chips/isp1301_omap.c b/drivers/i2c/chips/isp1301_omap.c index 7f29a8aff16..554701eaf63 100644 --- a/drivers/i2c/chips/isp1301_omap.c +++ b/drivers/i2c/chips/isp1301_omap.c @@ -36,7 +36,11 @@ #include #include +#include + +#include #include +#include #ifndef DEBUG @@ -91,14 +95,11 @@ struct isp1301 { /*-------------------------------------------------------------------------*/ -#ifdef CONFIG_MACH_OMAP_H2 +#if defined(CONFIG_MACH_OMAP_H2) || \ + defined(CONFIG_MACH_OMAP_H3) /* board-specific PM hooks */ -#include -#include -#include - #if defined(CONFIG_TPS65010) || defined(CONFIG_TPS65010_MODULE) @@ -129,17 +130,30 @@ static void enable_vbus_source(struct isp1301 *isp) } -/* products will deliver OTG messages with LEDs, GUI, etc */ -static inline void notresponding(struct isp1301 *isp) +#else + +static void enable_vbus_draw(struct isp1301 *isp, unsigned mA) { - printk(KERN_NOTICE "OTG device not responding.\n"); + pr_debug("%s UNIMPL\n", __FUNCTION__); } +static void enable_vbus_source(struct isp1301 *isp) +{ + pr_debug("%s UNIMPL\n", __FUNCTION__); +} #endif /*-------------------------------------------------------------------------*/ +/* products will deliver OTG messages with LEDs, GUI, etc */ +static inline void notresponding(struct isp1301 *isp) +{ + printk(KERN_NOTICE "OTG device not responding.\n"); +} + +/*-------------------------------------------------------------------------*/ + /* only two addresses possible */ #define ISP_BASE 0x2c static unsigned short normal_i2c[] = { @@ -516,6 +530,7 @@ static inline void check_state(struct isp1301 *isp, const char *tag) { } static void update_otg1(struct isp1301 *isp, u8 int_src) { u32 otg_ctrl; + u8 int_id; otg_ctrl = OTG_CTRL_REG & OTG_CTRL_MASK @@ -529,7 +544,10 @@ static void update_otg1(struct isp1301 *isp, u8 int_src) } if (int_src & INTR_VBUS_VLD) otg_ctrl |= OTG_VBUSVLD; - if (int_src & INTR_ID_GND) { /* default-A */ + + int_id = isp1301_get_u8(isp, ISP1301_INTERRUPT_SOURCE); + + if (int_id & INTR_ID_GND) { /* default-A */ if (isp->otg.state == OTG_STATE_B_IDLE || isp->otg.state == OTG_STATE_UNDEFINED) { a_idle(isp, "init"); @@ -1082,7 +1100,7 @@ static void isp_update_otg(struct isp1301 *isp, u8 stat) /* 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_otg1(isp, stat); // pass the actual interrupt latch status update_otg2(isp, isp_bstat); check_state(isp, __FUNCTION__); #endif @@ -1223,6 +1241,9 @@ static int isp1301_detach_client(struct i2c_client *i2c) if (machine_is_omap_h2()) omap_free_gpio(2); + if (machine_is_omap_h3()) + omap_free_gpio(14); + isp->timer.data = 0; set_bit(WORK_STOP, &isp->todo); del_timer_sync(&isp->timer); @@ -1301,7 +1322,7 @@ isp1301_set_host(struct otg_transceiver *otg, struct usb_bus *host) power_up(isp); - if (machine_is_omap_h2()) + if (machine_is_omap_h2() || machine_is_omap_h3()) isp1301_set_bits(isp, ISP1301_MODE_CONTROL_1, MC1_DAT_SE0); dev_info(&isp->client.dev, "A-Host sessions ok\n"); @@ -1364,13 +1385,13 @@ isp1301_set_peripheral(struct otg_transceiver *otg, struct usb_gadget *gadget) power_up(isp); isp->otg.state = OTG_STATE_B_IDLE; - if (machine_is_omap_h2()) + 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); + INTR_SESS_VLD | INTR_VBUS_VLD); isp1301_set_bits(isp, ISP1301_INTERRUPT_FALLING, - INTR_VBUS_VLD); + INTR_VBUS_VLD | INTR_SESS_VLD); dev_info(&isp->client.dev, "B-Peripheral sessions ok\n"); dump_regs(isp, __FUNCTION__); @@ -1447,6 +1468,10 @@ isp1301_start_hnp(struct otg_transceiver *dev) * So do this part as early as possible... */ switch (isp->otg.state) { + case OTG_STATE_B_PERIPHERAL: + isp->otg.state = OTG_STATE_B_WAIT_ACON; + isp1301_defer_work(isp, WORK_UPDATE_ISP); + break; case OTG_STATE_B_HOST: isp->otg.state = OTG_STATE_B_PERIPHERAL; /* caller will suspend next */ @@ -1562,13 +1587,15 @@ fail1: } #endif - if (machine_is_omap_h2()) { + if (machine_is_omap_h2() || machine_is_omap_h3()) { /* full speed signaling by default */ isp1301_set_bits(isp, ISP1301_MODE_CONTROL_1, MC1_SPEED_REG); isp1301_set_bits(isp, ISP1301_MODE_CONTROL_2, MC2_SPD_SUSP_CTRL); + } + if (machine_is_omap_h2()) { /* IRQ wired at M14 */ omap_cfg_reg(M14_1510_GPIO2); isp->irq = OMAP_GPIO_IRQ(2); @@ -1577,6 +1604,15 @@ fail1: omap_set_gpio_edge_ctrl(2, OMAP_GPIO_FALLING_EDGE); } + if (machine_is_omap_h3()) { + /* IRQ wired at N21 */ + omap_cfg_reg(N21_1710_GPIO14); + isp->irq = OMAP_GPIO_IRQ(14); + omap_request_gpio(14); + omap_set_gpio_direction(14, 1); + omap_set_gpio_edge_ctrl(14, OMAP_GPIO_FALLING_EDGE); + } + status = request_irq(isp->irq, isp1301_irq, SA_SAMPLE_RANDOM, DRIVER_NAME, isp); if (status < 0) { diff --git a/drivers/i2c/chips/tlv320aic23.c b/drivers/i2c/chips/tlv320aic23.c new file mode 100644 index 00000000000..586dc8f2ef9 --- /dev/null +++ b/drivers/i2c/chips/tlv320aic23.c @@ -0,0 +1,172 @@ +/* + * Texas Instrumens TLV320AIC23 audio codec's i2c interface. + * + * Copyright (c) by Kai Svahn + * + * 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 +#include +#include +#include +#include +#include +#include + +#define TLV320AIC23_VERSION "0.1" +#define TLV320AIC23_DATE "12-Aug-2004" + +/* I2C Addresses to scan */ +static unsigned short normal_i2c[] = { TLV320AIC23ID1, TLV320AIC23ID2, I2C_CLIENT_END }; +static unsigned short normal_i2c_range[] = { I2C_CLIENT_END }; + +/* This makes all addr_data:s */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver tlv320aic23_driver; +static struct i2c_client *new_client; +//static struct i2c_client *client; + +static int _tlv320aic23_write_value(struct i2c_client *client, u8 reg, u16 value) +{ + u8 val, wreg; + + /* TLV320AIC23 has 7 bit address and 9 bits of data + * so we need to switch one data bit into reg and rest + * of data into val + */ + + wreg = (reg << 1); + val = (0x01 & (value >> 8)); + wreg = (wreg | val); + val = (0x00ff & value); + + return i2c_smbus_write_byte_data(client, wreg, val); +} + +int tlv320aic23_write_value(u8 reg, u16 value) +{ + static struct i2c_client *client; + client = new_client; + _tlv320aic23_write_value(client, reg, value); + + return 0; +} + +static int tlv320aic23_detect_client(struct i2c_adapter *adapter, int address, + int kind) +{ + int err = 0; + const char *client_name = "TLV320AIC23 Audio Codec"; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_WRITE_BYTE)) { + printk(KERN_WARNING "%s functinality check failed\n", client_name); + return err; + } + + if (!(new_client = kmalloc(sizeof(struct i2c_client), + GFP_KERNEL))) { + err = -ENOMEM; + printk(KERN_WARNING "Couldn't allocate memory for %s\n", client_name); + return err; + } + + memset(new_client, 0x00, sizeof(struct i2c_client)); + new_client->addr = address; + new_client->adapter = adapter; + new_client->driver = &tlv320aic23_driver; + new_client->flags = 0; + strlcpy(new_client->name, client_name, I2C_NAME_SIZE); + + if ((err = i2c_attach_client(new_client))) { + printk(KERN_WARNING "Couldn't attach %s\n", client_name); + kfree(new_client); + return err; + } + + return 0; +} + +static int tlv320aic23_detach_client(struct i2c_client *client) +{ + int err; + + if ((err = i2c_detach_client(client))) { + printk("tlv320aic23.o: Client deregistration failed, client not detached.\n"); + return err; + } + + kfree(client); + return 0; +} + +static int tlv320aic23_attach_adapter(struct i2c_adapter *adapter) +{ + int res; + + res = i2c_probe(adapter, &addr_data, &tlv320aic23_detect_client); + return res; +} + +/*-----------------------------------------------------------------------*/ + +static struct i2c_driver tlv320aic23_driver = { + .owner = THIS_MODULE, + .name = "OMAP+TLV320AIC23 codec", + .id = I2C_DRIVERID_EXP0, /* Experimental ID */ + .flags = I2C_DF_NOTIFY, + .attach_adapter = tlv320aic23_attach_adapter, + .detach_client = tlv320aic23_detach_client, +}; + +/* + * INIT part + */ + +static int __init tlv320aic23_init(void) +{ + int res; + struct i2c_client *client = client; + + if ((res = i2c_add_driver(&tlv320aic23_driver))) { + printk("tlv320aic23 i2c: Driver registration failed, module not inserted.\n"); + return res; + } + + printk("TLV320AIC23 I2C version %s (%s)\n", TLV320AIC23_VERSION, + TLV320AIC23_DATE); + + return 0; +} + +static void __exit tlv320aic23_exit(void) +{ + int res; + + if ((res = i2c_del_driver(&tlv320aic23_driver))) + printk("tlv320aic23 i2c: Driver remove failed, module not removed.\n"); +} + +MODULE_AUTHOR("Kai Svahn "); +MODULE_DESCRIPTION("I2C interface for TLV320AIC23 codec."); +MODULE_LICENSE("GPL"); + +module_init(tlv320aic23_init) +module_exit(tlv320aic23_exit) + +EXPORT_SYMBOL(tlv320aic23_write_value); diff --git a/drivers/i2c/chips/tps65010.c b/drivers/i2c/chips/tps65010.c new file mode 100644 index 00000000000..d2eceaae1eb --- /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. + */ +#undef DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +/*-------------------------------------------------------------------------*/ + +#define DRIVER_VERSION "2 May 2005" +#define DRIVER_NAME (tps65010_driver.name) + +MODULE_DESCRIPTION("TPS6501x Power Management Driver"); +MODULE_LICENSE("GPL"); + +/* only two addresses possible */ +#define TPS_BASE 0x48 +static unsigned short normal_i2c[] = { + TPS_BASE, + I2C_CLIENT_END }; +static unsigned short normal_i2c_range[] = { I2C_CLIENT_END }; + +I2C_CLIENT_INSMOD; + +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 { + TPS_UNKNOWN = 0, + TPS65010, + TPS65011, + TPS65012, + TPS65013, +}; + +struct tps65010 { + struct i2c_client client; + struct semaphore lock; + int irq; + struct work_struct 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; + + /* plus four GPIOs, probably used to switch power */ +}; + +#define POWER_POLL_DELAY msecs_to_jiffies(800) + +/*-------------------------------------------------------------------------*/ + +#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) ? " ld01_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) +{ + 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); + + down(&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"); + } + + up(&tps->lock); + return 0; +} + +static int dbg_tps_open(struct inode *inode, struct file *file) +{ + return single_open(file, dbg_show, inode->u.generic_ip); +} + +static 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 irqs 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 ... + */ + software_suspend(); +#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(void *_tps) +{ + struct tps65010 *tps = _tps; + + down(&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->irq); + + up(&tps->lock); +} + +static irqreturn_t tps65010_irq(int irq, void *_tps, struct pt_regs *regs) +{ + struct tps65010 *tps = _tps; + + disable_irq_nosync(irq); + set_bit(FLAG_IRQ_ENABLE, &tps->flags); + (void) schedule_work(&tps->work); + return IRQ_HANDLED; +} + +/*-------------------------------------------------------------------------*/ + +static struct tps65010 *the_tps; + +static int __exit tps65010_detach_client(struct i2c_client *client) +{ + struct tps65010 *tps; + + tps = container_of(client, struct tps65010, client); +#ifdef CONFIG_ARM + if (machine_is_omap_h2()) + omap_free_gpio(58); + if (machine_is_omap_osk()) + omap_free_gpio(OMAP_MPUIO(1)); +#endif + free_irq(tps->irq, tps); + debugfs_remove(tps->file); + if (i2c_detach_client(client) == 0) + kfree(tps); + the_tps = 0; + return 0; +} + +static int tps65010_noscan(struct i2c_adapter *bus) +{ + /* pure paranoia, in case someone adds another i2c bus + * after our init section's gone... + */ + return -ENODEV; +} + +/* no error returns, they'd just make bus scanning stop */ +static int __init +tps65010_probe(struct i2c_adapter *bus, int address, int kind) +{ + struct tps65010 *tps; + int status; + + if (the_tps) { + dev_dbg(&bus->dev, "only one %s for now\n", DRIVER_NAME); + return 0; + } + + tps = kmalloc(sizeof *tps, GFP_KERNEL); + if (!tps) + return 0; + + memset(tps, 0, sizeof *tps); + init_MUTEX(&tps->lock); + INIT_WORK(&tps->work, tps65010_work, tps); + tps->irq = -1; + tps->client.addr = address; + i2c_set_clientdata(&tps->client, tps); + tps->client.adapter = bus; + tps->client.driver = &tps65010_driver; + strlcpy(tps->client.name, DRIVER_NAME, I2C_NAME_SIZE); + + status = i2c_attach_client(&tps->client); + if (status < 0) { + dev_dbg(&bus->dev, "can't attach %s to device %d, err %d\n", + DRIVER_NAME, address, status); +fail1: + kfree(tps); + return 0; + } + +#ifdef CONFIG_ARM + if (machine_is_omap_h2()) { + tps->model = TPS65010; + omap_cfg_reg(W4_GPIO58); + tps->irq = OMAP_GPIO_IRQ(58); + omap_request_gpio(58); + omap_set_gpio_direction(58, 1); + omap_set_gpio_edge_ctrl(58, OMAP_GPIO_FALLING_EDGE); + } + if (machine_is_omap_osk()) { + tps->model = TPS65010; + // omap_cfg_reg(U19_1610_MPUIO1); + tps->irq = OMAP_GPIO_IRQ(OMAP_MPUIO(1)); + omap_request_gpio(OMAP_MPUIO(1)); + omap_set_gpio_direction(OMAP_MPUIO(1), 1); + omap_set_gpio_edge_ctrl(OMAP_MPUIO(1), OMAP_GPIO_FALLING_EDGE); + } + if (machine_is_omap_h3()) { + tps->model = TPS65013; + + // FIXME set up this board's IRQ ... + } +#else +#define set_irq_type(num,trigger) do{}while(0) +#endif + + if (tps->irq > 0) { + set_irq_type(tps->irq, IRQT_LOW); + status = request_irq(tps->irq, tps65010_irq, + SA_SAMPLE_RANDOM, DRIVER_NAME, tps); + if (status < 0) { + dev_dbg(&tps->client.dev, "can't get IRQ %d, err %d\n", + tps->irq, status); + i2c_detach_client(&tps->client); + goto fail1; + } +#ifdef CONFIG_ARM + /* annoying race here, ideally we'd have an option + * to claim the irq now and enable it later. + */ + disable_irq(tps->irq); + set_bit(FLAG_IRQ_ENABLE, &tps->flags); +#endif + } else + printk(KERN_WARNING "%s: IRQ not configured!\n", + DRIVER_NAME); + + + switch (tps->model) { + case TPS65010: + case TPS65012: + tps->por = 1; + break; + case TPS_UNKNOWN: + printk(KERN_WARNING "%s: unknown TPS chip\n", DRIVER_NAME); + break; + /* else CHGCONFIG.POR is replaced by AUA, enabling a WAIT mode */ + } + tps->chgconf = i2c_smbus_read_byte_data(&tps->client, TPS_CHGCONFIG); + show_chgconfig(tps->por, "conf/init", tps->chgconf); + + show_chgstatus("chg/init", + i2c_smbus_read_byte_data(&tps->client, TPS_CHGSTATUS)); + show_regstatus("reg/init", + i2c_smbus_read_byte_data(&tps->client, TPS_REGSTATUS)); + + pr_debug("%s: vdcdc1 0x%02x, vdcdc2 %02x, vregs1 %02x\n", DRIVER_NAME, + i2c_smbus_read_byte_data(&tps->client, TPS_VDCDC1), + i2c_smbus_read_byte_data(&tps->client, TPS_VDCDC2), + i2c_smbus_read_byte_data(&tps->client, TPS_VREGS1)); + pr_debug("%s: defgpio 0x%02x, mask3 0x%02x\n", DRIVER_NAME, + i2c_smbus_read_byte_data(&tps->client, TPS_DEFGPIO), + i2c_smbus_read_byte_data(&tps->client, TPS_MASK3)); + + tps65010_driver.attach_adapter = tps65010_noscan; + 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(&tps->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(&tps->client, TPS_MASK2, ~tps->nmask2); + + (void) i2c_smbus_write_byte_data(&tps->client, TPS_MASK3, 0x0f + | i2c_smbus_read_byte_data(&tps->client, TPS_MASK3)); + + tps65010_work(tps); + + tps->file = debugfs_create_file(DRIVER_NAME, S_IRUGO, NULL, + tps, DEBUG_FOPS); + return 0; +} + +static int __init tps65010_scan_bus(struct i2c_adapter *bus) +{ + if (!i2c_check_functionality(bus, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EINVAL; + return i2c_probe(bus, &addr_data, tps65010_probe); +} + +static struct i2c_driver tps65010_driver = { + .owner = THIS_MODULE, + .name = "tps65010", + .id = 888, /* FIXME assign "official" value */ + .flags = I2C_DF_NOTIFY, + .attach_adapter = tps65010_scan_bus, + .detach_client = __exit_p(tps65010_detach_client), +}; + +/*-------------------------------------------------------------------------*/ + +/* 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); + } + 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; + + down(&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)); + + up(&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; + } + + down(&the_tps->lock); + + dev_dbg (&the_tps->client.dev, "led%i_on 0x%02x\n", led, + i2c_smbus_read_byte_data(&the_tps->client, TPS_LED1_ON + offs)); + + dev_dbg (&the_tps->client.dev, "led%i_per 0x%02x\n", 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 tps65010_set_led()\n", + DRIVER_NAME); + up(&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); + up(&the_tps->lock); + return status; + } + + dev_dbg (&the_tps->client.dev, "led%i_on 0x%02x\n", 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); + up(&the_tps->lock); + return status; + } + + dev_dbg (&the_tps->client.dev, "led%i_per 0x%02x\n", led, + i2c_smbus_read_byte_data(&the_tps->client, TPS_LED1_PER + offs)); + + up(&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; + + down(&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"); + + up(&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; + + down(&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)); + + up(&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; + + down(&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)); + + up(&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; + + down(&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); + up(&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)); + + up(&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); + } + +#if defined(CONFIG_ARM) + if (machine_is_omap_osk()) { + + // FIXME: More should be placed in the initialization code + // of the submodules (DSP, ethernet, power management, + // board-osk.c). Careful: I2C is initialized "late". + + /* Let LED1 (D9) blink */ + tps65010_set_led(LED1, BLINK); + + /* Disable LED 2 (D2) */ + tps65010_set_led(LED2, OFF); + + /* Set GPIO 1 HIGH to disable VBUS power supply; + * OHCI driver powers it up/down as needed. + */ + tps65010_set_gpio_out_value(GPIO1, HIGH); + + /* Set GPIO 2 low to turn on LED D3 */ + tps65010_set_gpio_out_value(GPIO2, HIGH); + + /* Set GPIO 3 low to take ethernet out of reset */ + tps65010_set_gpio_out_value(GPIO3, LOW); + + /* gpio4 for VDD_DSP */ + + /* Enable LOW_PWR */ + tps65010_set_low_pwr(ON); + + /* Switch VLDO2 to 3.0V for AIC23 */ + tps65010_config_vregs1(TPS_LDO2_ENABLE | TPS_VLDO2_3_0V | TPS_LDO1_ENABLE); + + } else if (machine_is_omap_h2()) { + /* gpio3 for SD, gpio4 for VDD_DSP */ + + /* Enable LOW_PWR */ + tps65010_set_low_pwr(ON); + } else if (machine_is_omap_h3()) { + /* gpio4 for SD, gpio3 for VDD_DSP */ +#ifdef CONFIG_PM + /* Enable LOW_PWR */ + tps65013_set_low_pwr(ON); +#endif + } +#endif + + 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/include/asm-arm/arch-omap/gpioexpander.h b/include/asm-arm/arch-omap/gpioexpander.h new file mode 100644 index 00000000000..7a43b0a912e --- /dev/null +++ b/include/asm-arm/arch-omap/gpioexpander.h @@ -0,0 +1,24 @@ +/* + * linux/include/asm-arm/arch-omap/gpioexpander.h + * + * + * Copyright (C) 2004 Texas Instruments, Inc. + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#ifndef __ASM_ARCH_OMAP_GPIOEXPANDER_H +#define __ASM_ARCH_OMAP_GPIOEXPANDER_H + +/* Function Prototypes for GPIO Expander functions */ + +int read_gpio_expa(u8 *, int); +int write_gpio_expa(u8 , int); + +#endif /* __ASM_ARCH_OMAP_GPIOEXPANDER_H */ -- 2.41.1