From: ubuntu Date: Mon, 28 Feb 2011 02:19:53 +0000 (-0800) Subject: cwz add tps65910 driver X-Git-Tag: firefly_0821_release~10737 X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=520c6215adbfb00ec56322d878e3e081530f876f;p=firefly-linux-kernel-4.4.55.git cwz add tps65910 driver --- diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 27553305bab0..f6f188669374 100755 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -155,6 +155,13 @@ config GPIO_TWL4030 Say yes here to access the GPIO signals of various multi-function power management chips from Texas Instruments. +config GPIO_TPS65910 + bool "TPS65910 GPIOs" + depends on TPS65910_CORE + help + Say yes here to access the GPIO signal of TPS65910x multi-function + power management chips from Texas Instruments. + config GPIO_WM831X tristate "WM831x GPIOs" depends on MFD_WM831X diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index dd031a14d055..e0b37e098e76 100755 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -14,6 +14,7 @@ obj-$(CONFIG_GPIO_PCA953X) += pca953x.o obj-$(CONFIG_GPIO_PCF857X) += pcf857x.o obj-$(CONFIG_GPIO_PL061) += pl061.o obj-$(CONFIG_GPIO_TWL4030) += twl4030-gpio.o +obj-$(CONFIG_GPIO_TPS65910) += tps65910-gpio.o obj-$(CONFIG_GPIO_UCB1400) += ucb1400_gpio.o obj-$(CONFIG_GPIO_XILINX) += xilinx_gpio.o obj-$(CONFIG_GPIO_BT8XX) += bt8xxgpio.o diff --git a/drivers/gpio/tps65910-gpio.c b/drivers/gpio/tps65910-gpio.c new file mode 100644 index 000000000000..36e1889767d3 --- /dev/null +++ b/drivers/gpio/tps65910-gpio.c @@ -0,0 +1,364 @@ +/* + * tps65910_gpio.c -- access to GPIOs on TPS65910x chips + * + * Copyright (C) 2010 Mistral solutions Pvt Ltd + * + * Based on twl4030-gpio.c + * + * 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 +#include + +#include + +static int gpio_tps65910_remove(struct platform_device *pdev); + +/* + * The GPIO "subchip" supports 1 GPIOs which can be configured as + * inputs or outputs, with pullups or pulldowns on each pin. Each + * GPIO can trigger interrupts on either or both edges. + */ + + +/* Data structures */ +static struct gpio_chip tps65910_gpiochip; +static DEFINE_MUTEX(gpio_lock); +static unsigned int gpio_usage_count; +static struct work_struct gpio_work; +static struct mutex work_lock; +/* + * To configure TPS65910 GPIO registers + */ +static inline int gpio_tps65910_write(u8 address, u8 data) +{ + return tps65910_i2c_write_u8(TPS65910_I2C_ID0, data, address); +} + + +/* + * To read a TPS65910 GPIO module register + */ +static inline int gpio_tps65910_read(u8 address) +{ + u8 data; + int ret = 0; + + ret = tps65910_i2c_read_u8(TPS65910_I2C_ID0, &data, address); + return (ret < 0) ? ret : data; +} + +static int tps65910_request(struct gpio_chip *chip, unsigned offset) +{ + int status = 0; + + mutex_lock(&gpio_lock); + + /* initialize TPS65910 GPIO */ + /* By default the GPIO_CKSYNC signal is GPIO */ + if (!gpio_usage_count) + gpio_usage_count++; + + mutex_unlock(&gpio_lock); + return status; +} + +static void tps65910_free(struct gpio_chip *chip, unsigned offset) +{ + mutex_lock(&gpio_lock); + + /* on last use, switch off GPIO module */ + if (!gpio_usage_count) + gpio_usage_count--; + + mutex_unlock(&gpio_lock); +} + +static int tps65910_direction_in(struct gpio_chip *chip, unsigned offset) +{ + /* Configure TPS65910 GPIO as input */ + u8 val; + + mutex_lock(&gpio_lock); + + val = gpio_tps65910_read(TPS65910_REG_GPIO0); + + val &= ~(TPS65910_GPIO_CFG_OUTPUT); + + val = gpio_tps65910_read(TPS65910_REG_GPIO0); + + mutex_unlock(&gpio_lock); + + return 0; +} + +static int tps65910_get(struct gpio_chip *chip, unsigned offset) +{ + int status = 0; + + mutex_lock(&gpio_lock); + + status = gpio_tps65910_read(TPS65910_REG_GPIO0); + + mutex_unlock(&gpio_lock); + if (status & 0x01) + return 1; + else + return 0; +} +static +int tps65910_direction_out(struct gpio_chip *chip, unsigned offset, int value) +{ + /* Configure TPS65910 GPIO as input */ + u8 val; + u32 ret; + mutex_lock(&gpio_lock); + val = gpio_tps65910_read(TPS65910_REG_GPIO0); + + val |= TPS65910_GPIO_CFG_OUTPUT; + + ret = gpio_tps65910_write(TPS65910_REG_GPIO0, val); + mutex_unlock(&gpio_lock); + + if (ret != 0) + return -EIO; + return 0; +} + +static void tps65910_set(struct gpio_chip *chip, unsigned offset, int value) +{ + int val = 0; + u32 ret; + + mutex_lock(&gpio_lock); + val = gpio_tps65910_read(TPS65910_REG_GPIO0); + + if (value == 1) + val |= 0x01; + else + val &= 0xFE; + + ret = gpio_tps65910_write(TPS65910_REG_GPIO0, val); + + mutex_unlock(&gpio_lock); +} + + + +static void tps65910_gpio_set_debounce(u8 debounce) +{ + u8 val; + + mutex_lock(&gpio_lock); + + val = gpio_tps65910_read(TPS65910_REG_GPIO0); + + if (debounce == TPS65910_DEBOUNCE_91_5_MS) + val = (0<<4); + else if (debounce == TPS65910_DEBOUNCE_150_MS) + val = (1<<4); + else + printk(KERN_ERR "Invalid argument to %s\n", __func__); + + gpio_tps65910_write(TPS65910_REG_GPIO0, val); + + mutex_unlock(&gpio_lock); +} +EXPORT_SYMBOL(tps65910_gpio_set_debounce); + + +static void tps65910_gpio_pullup_enable(void) +{ + u8 val; + u32 ret; + + mutex_lock(&gpio_lock); + + val = gpio_tps65910_read(TPS65910_REG_GPIO0); + + val = (1<<3); + + ret = gpio_tps65910_write(TPS65910_REG_GPIO0, val); + + mutex_unlock(&gpio_lock); + + if (ret != 0) + printk(KERN_ERR "Error writing to TPS65910_REG_GPIO0 in %s \n", + __func__); +} +EXPORT_SYMBOL(tps65910_gpio_pullup_enable); + +static void tps65910_gpio_pullup_disable(void) +{ + u8 val; + u32 ret; + + mutex_lock(&gpio_lock); + + val = gpio_tps65910_read(TPS65910_REG_GPIO0); + + val = (0<<3); + + ret = gpio_tps65910_write(TPS65910_REG_GPIO0, val); + + mutex_unlock(&gpio_lock); +} +EXPORT_SYMBOL(tps65910_gpio_pullup_disable); + +static void tps65910_gpio_work(struct work_struct *work) +{ + + /* Read the status register and take action */ + u8 status2; + int err; + mutex_lock(&work_lock); + err = tps65910_i2c_read_u8(TPS65910_I2C_ID0, &status2, + TPS65910_REG_INT_STS); + if (!err) { + switch (status2) { + case TPS65910_GPIO_F_IT: + printk(KERN_NOTICE "Received TPS65910 GPIO falling \ + edge interrupt \n"); + /* Clear interrupt */ + tps65910_i2c_write_u8(TPS65910_I2C_ID0, status2, + TPS65910_REG_INT_STS); + /* Add code accroding to board requirment */ + break; + case TPS65910_GPIO_R_IT: + printk(KERN_NOTICE "Received TPS65910 GPIO Raising \ + edge interrupt \n"); + /* Clear interrupt */ + tps65910_i2c_write_u8(TPS65910_I2C_ID0, status2, + TPS65910_REG_INT_STS); + /* Add code accroding to board requirment */ + break; + } + } else { + printk(KERN_ERR"Could not read TPS65910_REG_INT_STS\n"); + } + + mutex_unlock(&work_lock); + +} + + + +static irqreturn_t tps65910_gpio_isr(int irq, void *_tps65910) +{ + /* Disable IRQ, schedule work, enable IRQ and acknowledge */ + disable_irq(irq); + (void) schedule_work(&gpio_work); + enable_irq(irq); + return IRQ_HANDLED; +} + + +static struct gpio_chip tps65910_gpiochip = { + .label = "tps65910", + .owner = THIS_MODULE, + .request = tps65910_request, + .free = tps65910_free, + .direction_input = tps65910_direction_in, + .get = tps65910_get, + .direction_output = tps65910_direction_out, + .set = tps65910_set, +}; + + +static int __devinit gpio_tps65910_probe(struct platform_device *pdev) +{ + int ret = -1; + int status = 0; + + struct tps65910_gpio *pdata = pdev->dev.platform_data; + + if (pdata->gpio_mode == TPS65910_GPIO_AS_IRQ) { + + if (pdata->irq_num) { + status = request_irq(pdata->irq_num, tps65910_gpio_isr, + IRQF_SHARED, "tps65910_gpio", pdev); + if (status < 0) { + pr_err("tps65910: could not claim irq%d: %d\n", + pdata->irq_num, status); + } + + } + + INIT_WORK(&gpio_work, tps65910_gpio_work); + mutex_init(&work_lock); + + tps65910_gpiochip.ngpio = TPS65910_GPIO_MAX; + tps65910_gpiochip.dev = &pdev->dev; + + ret = gpiochip_add(&tps65910_gpiochip); + + if (ret < 0) { + dev_err(&pdev->dev, "could not register gpiochip \ + %d\n", ret); + tps65910_gpiochip.ngpio = 0; + gpio_tps65910_remove(pdev); + return -ENODEV; + } + if (pdata->gpio_setup) + pdata->gpio_setup(pdata); + } + return ret; +} + +static int gpio_tps65910_remove(struct platform_device *pdev) +{ + struct tps65910_gpio *pdata = pdev->dev.platform_data; + int status; + + if (pdata->gpio_taredown) + pdata->gpio_taredown(pdata); + if (pdata->gpio_mode == TPS65910_GPIO_AS_IRQ) + free_irq(pdata->irq_num, NULL); + + status = gpiochip_remove(&tps65910_gpiochip); + if (status < 0) + return status; + return 0; +} + +static struct platform_driver gpio_tps65910_driver = { + .driver.name = "tps65910_gpio", + .driver.owner = THIS_MODULE, + .probe = gpio_tps65910_probe, + .remove = gpio_tps65910_remove, +}; + +static int __init gpio_tps65910_init(void) +{ + return platform_driver_register(&gpio_tps65910_driver); +} +subsys_initcall(gpio_tps65910_init); + +static void __exit gpio_tps65910_exit(void) +{ + platform_driver_unregister(&gpio_tps65910_driver); +} +module_exit(gpio_tps65910_exit); + +MODULE_AUTHOR("Mistral Solutions Pvt Ltd."); +MODULE_DESCRIPTION("GPIO interface for TPS65910"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index 9ec984730f8e..b585257bc2f6 100755 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -220,6 +220,16 @@ config INPUT_TWL4030_PWRBUTTON To compile this driver as a module, choose M here. The module will be called twl4030_pwrbutton. +config INPUT_TPS65910_PWRBUTTON + tristate "TPS65910 Power button Driver" + depends on TPS65910_CORE + help + Say Y here if you want to enable power key reporting via the + TPS65910 family of chips. + + To compile this driver as a module, choose M here. The module will + be called tps65910_pwrbutton. + config INPUT_UINPUT tristate "User level driver support" help diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index 32460bdbc4df..a5ea67fdcb1c 100755 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_INPUT_GPIO_ROTARY_ENCODER) += rotary_encoder.o obj-$(CONFIG_INPUT_SGI_BTNS) += sgi_btns.o obj-$(CONFIG_INPUT_SPARCSPKR) += sparcspkr.o obj-$(CONFIG_INPUT_TWL4030_PWRBUTTON) += twl4030-pwrbutton.o +obj-$(CONFIG_INPUT_TPS65910_PWRBUTTON) += tps65910-pwrbutton.o obj-$(CONFIG_INPUT_UINPUT) += uinput.o obj-$(CONFIG_INPUT_WINBOND_CIR) += winbond-cir.o obj-$(CONFIG_INPUT_WISTRON_BTNS) += wistron_btns.o diff --git a/drivers/input/misc/tps65910-pwrbutton.c b/drivers/input/misc/tps65910-pwrbutton.c new file mode 100644 index 000000000000..587de971dc9a --- /dev/null +++ b/drivers/input/misc/tps65910-pwrbutton.c @@ -0,0 +1,148 @@ +/** + * tps65910-pwrbutton.c - TPS65910 Power Button Input Driver + * + * Copyright (C) 2010 Mistral Solutions Pvt Ltd + * + * Based on twl4030-pwrbutton.c + * + * Written by Srinath.R + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * 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 +#include + +#define TPS65910_PWR_PWRON_IRQ (1 << 2) + + +static irqreturn_t powerbutton_irq(int irq, void *_pwr) +{ + struct input_dev *pwr = _pwr; + int err; + u8 value; + +#ifdef CONFIG_LOCKDEP + /* WORKAROUND for lockdep forcing IRQF_DISABLED on us, which + * we don't want and can't tolerate since this is a threaded + * IRQ and can sleep due to the i2c reads it has to issue. + * Although it might be friendlier not to borrow this thread + * context... + */ + local_irq_enable(); +#endif + err = tps65910_i2c_read_u8(TPS65910_I2C_ID0, &value, + TPS65910_REG_INT_STS); + if (!err && (value & TPS65910_PWR_PWRON_IRQ)) { + + if (value & TPS65910_PWR_PWRON_IRQ) { + + input_report_key(pwr, KEY_POWER, + TPS65910_PWR_PWRON_IRQ); + input_sync(pwr); + return IRQ_HANDLED; + } + } else { + dev_err(pwr->dev.parent, "tps65910: i2c error %d while reading" + " TPS65910_REG_INT_STS register\n", err); + } + return IRQ_HANDLED; +} + +static int __devinit tps65910_pwrbutton_probe(struct platform_device *pdev) +{ + struct input_dev *pwr; + int irq = platform_get_irq(pdev, 0); + int err; + + pwr = input_allocate_device(); + if (!pwr) { + dev_dbg(&pdev->dev, "Can't allocate power button\n"); + return -ENOMEM; + } + + pwr->evbit[0] = BIT_MASK(EV_KEY); + pwr->keybit[BIT_WORD(KEY_POWER)] = BIT_MASK(KEY_POWER); + pwr->name = "tps65910_pwrbutton"; + pwr->phys = "tps65910_pwrbutton/input0"; + pwr->dev.parent = &pdev->dev; + + err = request_irq(irq, powerbutton_irq, + (IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | + IRQF_SHARED), "tps65910_pwrbutton", pwr); + if (err < 0) { + dev_dbg(&pdev->dev, "Can't get IRQ for pwrbutton: %d\n", err); + goto free_input_dev; + } + + err = input_register_device(pwr); + if (err) { + dev_dbg(&pdev->dev, "Can't register power button: %d\n", err); + goto free_irq; + } + + platform_set_drvdata(pdev, pwr); + + return 0; + +free_irq: + free_irq(irq, NULL); +free_input_dev: + input_free_device(pwr); + return err; +} + +static int __devexit tps65910_pwrbutton_remove(struct platform_device *pdev) +{ + struct input_dev *pwr = platform_get_drvdata(pdev); + int irq = platform_get_irq(pdev, 0); + + free_irq(irq, pwr); + input_unregister_device(pwr); + + return 0; +} + +struct platform_driver tps65910_pwrbutton_driver = { + .probe = tps65910_pwrbutton_probe, + .remove = __devexit_p(tps65910_pwrbutton_remove), + .driver = { + .name = "tps65910_pwrbutton", + .owner = THIS_MODULE, + }, +}; + +static int __init tps65910_pwrbutton_init(void) +{ + return platform_driver_register(&tps65910_pwrbutton_driver); +} +module_init(tps65910_pwrbutton_init); + +static void __exit tps65910_pwrbutton_exit(void) +{ + platform_driver_unregister(&tps65910_pwrbutton_driver); +} +module_exit(tps65910_pwrbutton_exit); + +MODULE_ALIAS("platform:tps65910_pwrbutton"); +MODULE_DESCRIPTION("TPS65910 Power Button"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Srinath R "); + diff --git a/drivers/input/touchscreen/eeti_egalax_i2c.c b/drivers/input/touchscreen/eeti_egalax_i2c.c index f16067d690c5..6a02cb5f701c 100644 --- a/drivers/input/touchscreen/eeti_egalax_i2c.c +++ b/drivers/input/touchscreen/eeti_egalax_i2c.c @@ -553,7 +553,7 @@ static int __devinit egalax_i2c_probe(struct i2c_client *client) int ret; int gpio = client->irq; struct eeti_egalax_platform_data *pdata = pdata = client->dev.platform_data; - DBG(); + printk(KERN_DEBUG "[egalax_i2c]: start probe\n"); p_egalax_i2c_dev = (struct _egalax_i2c *)kzalloc(sizeof(struct _egalax_i2c), GFP_KERNEL); diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index a111745fa291..cc4cf1274a3e 100755 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -121,6 +121,18 @@ config TWL4030_POWER and load scripts controling which resources are switched off/on or reset when a sleep, wakeup or warm reset event occurs. +config TPS65910_CORE + bool "Texas Instruments TPS65910 Support" + depends on I2C=y && GENERIC_HARDIRQS + help + Say yes here if you have TPS65910 family chip on your board. + This core driver provides register access and registers devices + for the various functions so that function-specific drivers can + bind to them. + + These multi-function chips are found on many AM35xx boards, + providing power management, RTC, GPIO features. + config MFD_TMIO bool default n diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index aa2270196611..9b2463df22b7 100755 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -29,6 +29,8 @@ obj-$(CONFIG_MENELAUS) += menelaus.o obj-$(CONFIG_TWL4030_CORE) += twl4030-core.o twl4030-irq.o obj-$(CONFIG_TWL4030_POWER) += twl4030-power.o +obj-$(CONFIG_TPS65910_CORE) += tps65910-core.o + obj-$(CONFIG_MFD_MC13783) += mc13783-core.o obj-$(CONFIG_MFD_CORE) += mfd-core.o diff --git a/drivers/mfd/tps65010.c b/drivers/mfd/tps65010.c index acf8b9d5f575..e5955306c2fa 100644 --- a/drivers/mfd/tps65010.c +++ b/drivers/mfd/tps65010.c @@ -637,7 +637,7 @@ static int tps65010_probe(struct i2c_client *client, tps, DEBUG_FOPS); /* optionally register GPIOs */ - if (board && board->base > 0) { + if (board && board->base != 0) { tps->outmask = board->outmask; tps->chip.label = client->name; @@ -964,6 +964,34 @@ int tps65010_config_vregs1(unsigned value) } EXPORT_SYMBOL(tps65010_config_vregs1); +int tps65010_config_vdcdc2(unsigned value) +{ + struct i2c_client *c; + int status; + + if (!the_tps) + return -ENODEV; + + c = the_tps->client; + mutex_lock(&the_tps->lock); + + pr_debug("%s: vdcdc2 0x%02x\n", DRIVER_NAME, + i2c_smbus_read_byte_data(c, TPS_VDCDC2)); + + status = i2c_smbus_write_byte_data(c, TPS_VDCDC2, value); + + if (status != 0) + printk(KERN_ERR "%s: Failed to write vdcdc2 register\n", + DRIVER_NAME); + else + pr_debug("%s: vregs1 0x%02x\n", DRIVER_NAME, + i2c_smbus_read_byte_data(c, TPS_VDCDC2)); + + mutex_unlock(&the_tps->lock); + return status; +} +EXPORT_SYMBOL(tps65010_config_vdcdc2); + /*-------------------------------------------------------------------------*/ /* tps65013_set_low_pwr parameter: * mode: ON or OFF diff --git a/drivers/mfd/tps65910-core.c b/drivers/mfd/tps65910-core.c new file mode 100644 index 000000000000..16163426a879 --- /dev/null +++ b/drivers/mfd/tps65910-core.c @@ -0,0 +1,732 @@ +/* + * tps65910-core.c -- Multifunction core driver for TPS65910x chips + * + * Copyright (C) 2010 Mistral solutions Pvt Ltd + * + * Based on twl-core.c + * + * 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 +#include +#include +#include +#include +#include +#include + +#if 1 +#define DBG(x...) printk(KERN_INFO x) +#else +#define DBG(x...) +#endif +#define TPS65910_SPEED 400 * 1000 + + +#define DRIVER_NAME "tps65910" + +#if defined(CONFIG_GPIO_TPS65910) +#define tps65910_has_gpio() true +#else +#define tps65910_has_gpio() false +#endif + +#if defined(CONFIG_REGULATOR_TPS65910) +#define tps65910_has_regulator() true +#else +#define tps65910_has_regulator() false +#endif + +#if defined(CONFIG_RTC_DRV_TPS65910) +#define tps65910_has_rtc() true +#else +#define tps65910_has_rtc() false +#endif + +#define TPS65910_GENERAL 0 +#define TPS65910_SMARTREFLEX 1 + + +struct tps65910_platform_data *the_tps65910; + +enum tps65910x_model { + TPS65910, /* TI processors OMAP3 family */ + TPS659101, /* Samsung - S5PV210, S5PC1xx */ + TPS659102, /* Samsung - S3C64xx */ + TPS659103, /* Reserved */ + TPS659104, /* Reserved */ + TPS659105, /* TI processors - DM643x, DM644x */ + TPS659106, /* Reserved */ + TPS659107, /* Reserved */ + TPS659108, /* Reserved */ + TPS659109, /* Freescale - i.MX51 */ + +}; + +static bool inuse; +static struct work_struct core_work; +static struct mutex work_lock; + +/* Structure for each TPS65910 Slave */ +struct tps65910_client { + struct i2c_client *client; + u8 address; + /* max numb of i2c_msg required for read = 2 */ + struct i2c_msg xfer_msg[2]; + /* To lock access to xfer_msg */ + struct mutex xfer_lock; +}; +static struct tps65910_client tps65910_modules[TPS65910_NUM_SLAVES]; + +/* bbch = Back-up battery charger control register */ +int tps65910_enable_bbch(u8 voltage) +{ + u8 val = 0; + int err; + + if (voltage == TPS65910_BBSEL_3P0 || voltage == TPS65910_BBSEL_2P52 || + voltage == TPS65910_BBSEL_3P15 || + voltage == TPS65910_BBSEL_VBAT) { + val = (voltage | TPS65910_BBCHEN); + err = tps65910_i2c_write_u8(TPS65910_I2C_ID0, val, + TPS65910_REG_BBCH); + if (err) { + printk(KERN_ERR "Unable write TPS65910_REG_BBCH reg\n"); + return -EIO; + } + } else { + printk(KERN_ERR"Invalid argumnet for %s \n", __func__); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL(tps65910_enable_bbch); + +int tps65910_disable_bbch(void) +{ + u8 val = 0; + int err; + + err = tps65910_i2c_read_u8(TPS65910_I2C_ID0, &val, TPS65910_REG_BBCH); + + if (!err) { + val &= ~TPS65910_BBCHEN; + + err = tps65910_i2c_write_u8(TPS65910_I2C_ID0, val, + TPS65910_REG_BBCH); + if (err) { + printk(KERN_ERR "Unable write TPS65910_REG_BBCH \ + reg\n"); + return -EIO; + } + } else { + printk(KERN_ERR "Unable to read TPS65910_REG_BBCH reg\n"); + return -EIO; + } + return 0; +} +EXPORT_SYMBOL(tps65910_disable_bbch); + +int tps65910_i2c_read_u8(u8 mod_no, u8 *value, u8 reg) +{ + struct tps65910_client *tps65910; + int ret; + + switch (mod_no) { + case TPS65910_I2C_ID0: + tps65910 = &tps65910_modules[0]; + tps65910->address = TPS65910_I2C_ID0; + break; + case TPS65910_I2C_ID1: + tps65910 = &tps65910_modules[1]; + tps65910->address = TPS65910_I2C_ID1; + break; + default: + printk(KERN_ERR "Invalid Slave address for TPS65910\n"); + return -ENODEV; + } + + ret = i2c_master_reg8_recv(tps65910->client, reg, (char *)value, 1, TPS65910_SPEED); + DBG("%s: ret=%d, slave_addr=0x%x, reg=0x%x, value=0x%x\n", __FUNCTION__, (ret > 0 ? 0: -EINVAL), mod_no, reg, *value); + + return (ret > 0 ? 0: -EINVAL); +} +EXPORT_SYMBOL(tps65910_i2c_read_u8); + +int tps65910_i2c_write_u8(u8 slave_addr, u8 value, u8 reg) +{ + struct tps65910_client *tps65910; + int ret; + + switch (slave_addr) { + case TPS65910_I2C_ID0: + tps65910 = &tps65910_modules[0]; + tps65910->address = TPS65910_I2C_ID0; + break; + case TPS65910_I2C_ID1: + tps65910 = &tps65910_modules[1]; + tps65910->address = TPS65910_I2C_ID1; + break; + default: + printk(KERN_ERR "Invalid Slave address for TPS65910\n"); + return -ENODEV; + } + + ret = i2c_master_reg8_send(tps65910->client, reg, (char *)&value, 1, TPS65910_SPEED); + DBG("%s: ret=%d, slave_addr=0x%x, reg=0x%x, value=0x%x\n", __FUNCTION__, ret, slave_addr, reg, value); + + if (ret < 0) + return -EIO; + else + return 0; +} +EXPORT_SYMBOL(tps65910_i2c_write_u8); + + +int tps65910_enable_irq(int irq) +{ + u8 mask = 0x00; + + if (irq > 7) { + irq -= 8; + tps65910_i2c_read_u8(TPS65910_I2C_ID0, + &mask, TPS65910_REG_INT_MSK2); + mask &= ~(1 << irq); + return tps65910_i2c_write_u8(TPS65910_I2C_ID0, + mask, TPS65910_REG_INT_MSK2); + } else { + tps65910_i2c_read_u8(TPS65910_I2C_ID0, + &mask, TPS65910_REG_INT_MSK); + mask &= ~(1 << irq); + return tps65910_i2c_write_u8(TPS65910_I2C_ID0, + mask, TPS65910_REG_INT_MSK); + } +} +EXPORT_SYMBOL(tps65910_enable_irq); + +int tps65910_disable_irq(int irq) +{ + u8 mask = 0x00; + + if (irq > 7) { + irq -= 8; + tps65910_i2c_read_u8(TPS65910_I2C_ID0, + &mask, TPS65910_REG_INT_MSK2); + mask |= (1 << irq); + return tps65910_i2c_write_u8(TPS65910_I2C_ID0, + mask, TPS65910_REG_INT_MSK2); + } else { + tps65910_i2c_read_u8(TPS65910_I2C_ID0, + &mask, TPS65910_REG_INT_MSK); + mask = (1 << irq); + return tps65910_i2c_write_u8(TPS65910_I2C_ID0, + mask, TPS65910_REG_INT_MSK); + } +} +EXPORT_SYMBOL(tps65910_disable_irq); + +int tps65910_add_irq_work(int irq, + void (*handler)(void *data)) +{ + int ret = 0; + the_tps65910->handlers[irq] = handler; + ret = tps65910_enable_irq(irq); + + return ret; +} +EXPORT_SYMBOL(tps65910_add_irq_work); + +int tps65910_remove_irq_work(int irq) +{ + int ret = 0; + ret = tps65910_disable_irq(irq); + the_tps65910->handlers[irq] = NULL; + return ret; +} +EXPORT_SYMBOL(tps65910_remove_irq_work); + +static void tps65910_core_work(struct work_struct *work) +{ + /* Read the status register and take action */ + u8 status = 0x00; + u8 status2 = 0x00; + u8 mask = 0x00; + u8 mask2 = 0x00; + u16 isr = 0x00; + u16 irq = 0; + void (*handler)(void *data) = NULL; + + mutex_lock(&work_lock); + while (1) { + tps65910_i2c_read_u8(TPS65910_I2C_ID0, &status2, + TPS65910_REG_INT_STS2); + tps65910_i2c_read_u8(TPS65910_I2C_ID0, &mask2, + TPS65910_REG_INT_MSK2); + status2 &= (~mask2); + isr = (status2 << 8); + tps65910_i2c_read_u8(TPS65910_I2C_ID0, &status, + TPS65910_REG_INT_STS); + tps65910_i2c_read_u8(TPS65910_I2C_ID0, &mask, + TPS65910_REG_INT_MSK); + status &= ~(mask); + isr |= status; + if (!isr) + break; + + while (isr) { + irq = fls(isr) - 1; + isr &= ~(1 << irq); + handler = the_tps65910->handlers[irq]; + if (handler) + handler(the_tps65910); + } + } + enable_irq(the_tps65910->irq_num); + mutex_unlock(&work_lock); +} + + +static irqreturn_t tps65910_isr(int irq, void *data) +{ + disable_irq_nosync(irq); + (void) schedule_work(&core_work); + return IRQ_HANDLED; +} + + +static struct device *add_numbered_child(unsigned chip, const char *name, + int num, void *pdata, unsigned pdata_len, bool can_wakeup, int irq) +{ + + struct platform_device *pdev; + struct tps65910_client *tps65910 = &tps65910_modules[chip]; + int status; + + pdev = platform_device_alloc(name, num); + if (!pdev) { + dev_dbg(&tps65910->client->dev, "can't alloc dev\n"); + status = -ENOMEM; + goto err; + } + device_init_wakeup(&pdev->dev, can_wakeup); + pdev->dev.parent = &tps65910->client->dev; + + if (pdata) { + status = platform_device_add_data(pdev, pdata, pdata_len); + if (status < 0) { + dev_dbg(&pdev->dev, "can't add platform_data\n"); + goto err; + } + } + status = platform_device_add(pdev); + +err: + if (status < 0) { + platform_device_put(pdev); + dev_err(&tps65910->client->dev, "can't add %s dev\n", name); + return ERR_PTR(status); + } + return &pdev->dev; + +} + +static inline struct device *add_child(unsigned chip, const char *name, + void *pdata, unsigned pdata_len, + bool can_wakeup, int irq) +{ + return add_numbered_child(chip, name, -1, pdata, pdata_len, + can_wakeup, irq); +} + +static +struct device *add_regulator_linked(int num, struct regulator_init_data *pdata, + struct regulator_consumer_supply *consumers, + unsigned num_consumers) +{ + /* regulator framework demands init_data */ + if (!pdata) + return NULL; + + if (consumers) { + pdata->consumer_supplies = consumers; + pdata->num_consumer_supplies = num_consumers; + } + return add_numbered_child(TPS65910_GENERAL, "tps65910_regulator", num, + pdata, sizeof(*pdata), false, TPS65910_HOST_IRQ); +} + + static struct device * +add_regulator(int num, struct regulator_init_data *pdata) +{ + return add_regulator_linked(num, pdata, NULL, 0); +} + +static int +add_children(struct tps65910_platform_data *pdata, unsigned long features) +{ + int status; + struct device *child; + + struct platform_device *pdev = NULL; + + if (tps65910_has_gpio() && (pdata->gpio != NULL)) { + + pdev = platform_device_alloc("tps65910_gpio", -1); + if (!pdev) { + status = -ENOMEM; + goto err; + } + pdev->dev.parent = &tps65910_modules[0].client->dev; + device_init_wakeup(&pdev->dev, 0); + if (pdata) { + status = platform_device_add_data(pdev, pdata, + sizeof(*pdata)); + if (status < 0) { + dev_dbg(&pdev->dev, + "can't add platform_data\n"); + goto err; + } + } + } + if (tps65910_has_rtc()) { + child = add_child(TPS65910_GENERAL, "tps65910_rtc", + NULL, 0, true, pdata->irq_num); + if (IS_ERR(child)) + return PTR_ERR(child); + } + + if (tps65910_has_regulator()) { + + child = add_regulator(TPS65910_VIO, pdata->vio); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TPS65910_VDD1, pdata->vdd1); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TPS65910_VDD2, pdata->vdd2); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TPS65910_VDD3, pdata->vdd3); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TPS65910_VDIG1, pdata->vdig1); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TPS65910_VDIG2, pdata->vdig2); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TPS65910_VAUX33, pdata->vaux33); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TPS65910_VMMC, pdata->vmmc); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TPS65910_VAUX1, pdata->vaux1); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TPS65910_VAUX2, pdata->vaux2); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TPS65910_VDAC, pdata->vdac); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TPS65910_VPLL, pdata->vpll); + if (IS_ERR(child)) + return PTR_ERR(child); + } + return 0; + +err: + return -1; + +} + +static int tps65910_remove(struct i2c_client *client) +{ + unsigned i; + + for (i = 0; i < TPS65910_NUM_SLAVES; i++) { + + struct tps65910_client *tps65910 = &tps65910_modules[i]; + + if (tps65910->client && tps65910->client != client) + i2c_unregister_device(tps65910->client); + + tps65910_modules[i].client = NULL; + } + inuse = false; + return 0; +} + +static int __init +tps65910_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + int status; + unsigned i; + struct tps65910_platform_data *pdata; + + pdata = client->dev.platform_data; + the_tps65910 = pdata; + + DBG("cwz: tps65910_i2c_probe\n"); + + if (!pdata) { + dev_dbg(&client->dev, "no platform data?\n"); + return -EINVAL; + } + + if (!i2c_check_functionality(client->adapter,I2C_FUNC_I2C)) { + dev_dbg(&client->dev, "can't talk I2C?\n"); + return -EIO; + } + + if (inuse) { + dev_dbg(&client->dev, "driver is already in use\n"); + return -EBUSY; + } + for (i = 0; i < TPS65910_NUM_SLAVES; i++) { + + struct tps65910_client *tps65910 = &tps65910_modules[i]; + + tps65910->address = client->addr; + + if (i == 0) + tps65910->client = client; + else { + tps65910->client = i2c_new_dummy(client->adapter, + tps65910->address); + + if (!tps65910->client) { + dev_err(&client->dev, + "can't attach client %d\n", i); + status = -ENOMEM; + goto fail; + } + } + mutex_init(&tps65910->xfer_lock); + } + + inuse = true; + + if (pdata->board_tps65910_config != NULL) + pdata->board_tps65910_config(pdata); + +#if 0 // cwz close, the tps65910_core_work may have some error. + if (pdata->irq_num) { + /* TPS65910 power ON interrupt(s) would have already been + * occurred, so immediately after request_irq the control will + * be transferred to tps65910_isr, if we do core_work + * initialization after requesting IRQ, the system crashes + * and does not boot; to avoid this we do core_work + * initialization before requesting IRQ + */ + mutex_init(&work_lock); + INIT_WORK(&core_work, tps65910_core_work); + + status = request_irq(pdata->irq_num, tps65910_isr, + IRQF_DISABLED, "tps65910", pdata); + if (status < 0) { + pr_err("tps65910: could not claim irq%d: %d\n", + pdata->irq_num, status); + goto fail; + } + } +#endif + + status = add_children(pdata, 0x00); + if (status < 0) + goto fail; + + return 0; + +fail: + if (status < 0) + tps65910_remove(client); + + return status; +} + + +static int tps65910_i2c_remove(struct i2c_client *client) +{ + unsigned i; + + for (i = 0; i < TPS65910_NUM_SLAVES; i++) { + + struct tps65910_client *tps65910 = &tps65910_modules[i]; + + if (tps65910->client && tps65910->client != client) + i2c_unregister_device(tps65910->client); + + tps65910_modules[i].client = NULL; + } + inuse = false; + return 0; +} + +/* chip-specific feature flags, for i2c_device_id.driver_data */ +static const struct i2c_device_id tps65910_i2c_ids[] = { + { "tps65910", TPS65910 }, + { "tps659101", TPS659101 }, + { "tps659102", TPS659102 }, + { "tps659103", TPS659103 }, + { "tps659104", TPS659104 }, + { "tps659105", TPS659105 }, + { "tps659106", TPS659106 }, + { "tps659107", TPS659107 }, + { "tps659108", TPS659108 }, + { "tps659109", TPS659109 }, + {/* end of list */ }, +}; +MODULE_DEVICE_TABLE(i2c, tps65910_i2c_ids); + +/* One Client Driver ,3 Clients - Regulator, RTC , GPIO */ +static struct i2c_driver tps65910_i2c_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, + .id_table = tps65910_i2c_ids, + .probe = tps65910_i2c_probe, + .remove = __devexit_p(tps65910_i2c_remove), +}; + +static int __init tps65910_init(void) +{ + int res; + + res = i2c_add_driver(&tps65910_i2c_driver); + if (res < 0) { + pr_err(DRIVER_NAME ": driver registration failed\n"); + return res; + } + + return 0; +} +subsys_initcall_sync(tps65910_init); + +static void __exit tps65910_exit(void) +{ + i2c_del_driver(&tps65910_i2c_driver); +} +module_exit(tps65910_exit); + + +#ifdef CONFIG_PROC_FS +#include +#include + +static int proc_tps65910_show(struct seq_file *s, void *v) +{ + u8 val = 0; + struct regulator *vldo; + + seq_printf(s, "\n\nTPS65910 Registers is:\n"); + + tps65910_i2c_read_u8(TPS65910_I2C_ID0, &val, TPS65910_REG_VDD1); + seq_printf(s, "VDD1_REG=0x%x, Value=0x%x\n", TPS65910_REG_VDD1, val); + tps65910_i2c_read_u8(TPS65910_I2C_ID0, &val, TPS65910_REG_VDD1_OP); + seq_printf(s, "VDD1_OP_REG=0x%x, Value=0x%x\n", TPS65910_REG_VDD1_OP, val); + + tps65910_i2c_read_u8(TPS65910_I2C_ID0, &val, TPS65910_REG_VDD2); + seq_printf(s, "VDD2_REG=0x%x, Value=0x%x\n", TPS65910_REG_VDD2, val); + tps65910_i2c_read_u8(TPS65910_I2C_ID0, &val, TPS65910_REG_VDD2_OP); + seq_printf(s, "VDD2_OP_REG=0x%x, Value=0x%x\n", TPS65910_REG_VDD2_OP, val); + + tps65910_i2c_read_u8(TPS65910_I2C_ID0, &val, TPS65910_REG_VIO); + seq_printf(s, "VIO_REG=0x%x, Value=0x%x\n", TPS65910_REG_VIO, val); + + tps65910_i2c_read_u8(TPS65910_I2C_ID0, &val, TPS65910_REG_VDIG1); + seq_printf(s, "VDIG1_REG=0x%x, Value=0x%x\n", TPS65910_REG_VDIG1, val); + tps65910_i2c_read_u8(TPS65910_I2C_ID0, &val, TPS65910_REG_VDIG2); + seq_printf(s, "VDIG2_REG=0x%x, Value=0x%x\n", TPS65910_REG_VDIG2, val); + tps65910_i2c_read_u8(TPS65910_I2C_ID0, &val, TPS65910_REG_VAUX1); + seq_printf(s, "VAUX1_REG=0x%x, Value=0x%x\n", TPS65910_REG_VAUX1, val); + tps65910_i2c_read_u8(TPS65910_I2C_ID0, &val, TPS65910_REG_VAUX2); + seq_printf(s, "VAUX2_REG=0x%x, Value=0x%x\n", TPS65910_REG_VAUX2, val); + tps65910_i2c_read_u8(TPS65910_I2C_ID0, &val, TPS65910_REG_VAUX33); + seq_printf(s, "VAUX33_REG=0x%x, Value=0x%x\n", TPS65910_REG_VAUX33, val); + tps65910_i2c_read_u8(TPS65910_I2C_ID0, &val, TPS65910_REG_VMMC); + seq_printf(s, "VMMC_REG=0x%x, Value=0x%x\n", TPS65910_REG_VMMC, val); + tps65910_i2c_read_u8(TPS65910_I2C_ID0, &val, TPS65910_REG_VPLL); + seq_printf(s, "VPLL_REG=0x%x, Value=0x%x\n", TPS65910_REG_VPLL, val); + tps65910_i2c_read_u8(TPS65910_I2C_ID0, &val, TPS65910_REG_VDAC); + seq_printf(s, "VDAC_REG=0x%x, Value=0x%x\n", TPS65910_REG_VDAC, val); + + tps65910_i2c_read_u8(TPS65910_I2C_ID0, &val, TPS65910_REG_DEVCTRL); + seq_printf(s, "DEVCTRL_REG=0x%x, Value=0x%x\n", TPS65910_REG_DEVCTRL, val); + tps65910_i2c_read_u8(TPS65910_I2C_ID0, &val, TPS65910_REG_DEVCTRL2); + seq_printf(s, "DEVCTRL2_REG=0x%x, Value=0x%x\n", TPS65910_REG_DEVCTRL2, val); + +#if 0 // cwz 1 test vcore + vldo = regulator_get(NULL, "vcore"); + if (vldo != NULL) + { + int uV = 0; + + seq_printf(s, "Set VCORE.\n"); + regulator_set_voltage(vldo,1100000,1100000); + + uV = regulator_get_voltage(vldo); + seq_printf(s, "Get VCORE=%d(uV).\n", uV); + } +#endif + return 0; +} + +static int proc_tps65910_open(struct inode *inode, struct file *file) +{ + return single_open(file, proc_tps65910_show, NULL); +} + +static const struct file_operations proc_tps65910_fops = { + .open = proc_tps65910_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int __init proc_tps65910_init(void) +{ + proc_create("tps65910", 0, NULL, &proc_tps65910_fops); + return 0; +} +late_initcall(proc_tps65910_init); +#endif /* CONFIG_PROC_FS */ + +MODULE_AUTHOR("cwz "); +MODULE_DESCRIPTION("I2C Core interface for TPS65910"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 0b93f4ad25f4..9c24042e6952 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -76,6 +76,13 @@ config REGULATOR_TWL4030 This driver supports the voltage regulators provided by this family of companion chips. +config REGULATOR_TPS65910 + bool "TI TPS69510x PMIC" + depends on TPS65910_CORE + help + This driver supports the voltage regulators provided by + this family of companion chips. + config REGULATOR_WM831X tristate "Wolfson Microelcronics WM831x PMIC regulators" depends on MFD_WM831X diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 77befcd24bf3..ec0e204b5541 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_REGULATOR_BQ24022) += bq24022.o obj-$(CONFIG_REGULATOR_LP3971) += lp3971.o obj-$(CONFIG_REGULATOR_MAX1586) += max1586.o obj-$(CONFIG_REGULATOR_TWL4030) += twl4030-regulator.o +obj-$(CONFIG_REGULATOR_TPS65910) += tps65910-regulator.o obj-$(CONFIG_REGULATOR_WM831X) += wm831x-dcdc.o obj-$(CONFIG_REGULATOR_WM831X) += wm831x-isink.o obj-$(CONFIG_REGULATOR_WM831X) += wm831x-ldo.o diff --git a/drivers/regulator/tps65910-regulator.c b/drivers/regulator/tps65910-regulator.c new file mode 100644 index 000000000000..67d6c1de5347 --- /dev/null +++ b/drivers/regulator/tps65910-regulator.c @@ -0,0 +1,760 @@ +/* + * tps65910-regulator.c -- support regulators in tps65910x family chips + * + * + * Copyright (C) 2010 Mistral Solutions Pvt Ltd + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any kind, + * whether express or implied; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if 1 +#define DBG(x...) printk(KERN_INFO x) +#else +#define DBG(x...) +#endif + +/* + * The TPS65910x family chips include power management, a GPIO + * RTC. These chips are often used in AM35xx-based systems. + * + * This driver implements software-based resource control for various + * voltage regulators. This is usually augmented with state machine + * based control. + */ + + +struct tps65910reg_info { + /* tps65910 resource ID, for resource control state machine */ + u8 id; + /* voltage in mV = table[VSEL]; table_len must be a power-of-two */ + u8 table_len; + const u16 *table; + + /* regulator specific turn-on delay */ + u32 delay; + /* chip constraints on regulator behavior */ + u16 min_mV; + u16 max_mV; + /* used by regulator core */ + struct regulator_desc desc; +}; + + +/* Supported voltage values for regulators */ + +/* TPS65910 VIO */ +static const u16 VIO_VSEL_table[] = { + 1500, 1800, 2500, 3300, +}; + +/* TPS65910 VDD1 and VDD2 */ +/* value round off 12.5 is made as 12 */ +static const u16 VDD1_VSEL_table[] = { + 0, 600, 600, 600, 612, 625, 637, 650, + 662, 675, 687, 700, 712, 725, 737, 750, + 762, 775, 787, 800, 812, 825, 837, 850, + 862, 875, 887, 900, 912, 925, 937, 950, + 962, 975, 987, 1000, 1012, 1025, 1037, 1050, + 1062, 1075, 1087, 1100, 1112, 1125, 1137, 1150, + 1162, 1175, 1187, 1200, 1212, 1225, 1237, 1250, + 1262, 1275, 1287, 1300, 1312, 1325, 1337, 1350, + 1362, 1375, 1387, 1400, 1412, 1425, 1437, 1450, + 1462, 1475, 1487, 1500, +}; + +static const u16 VDD2_VSEL_table[] = { + 0, 600, 600, 600, 612, 625, 637, 650, + 662, 675, 687, 700, 712, 725, 737, 750, + 762, 775, 787, 800, 812, 825, 837, 850, + 862, 875, 887, 900, 912, 925, 937, 950, + 962, 975, 987, 1000, 1012, 1025, 1037, 1050, + 1062, 1075, 1087, 1100, 1112, 1125, 1137, 1150, + 1162, 1175, 1187, 1200, 1212, 1225, 1237, 1250, + 1262, 1275, 1287, 1300, 1312, 1325, 1337, 1350, + 1362, 1375, 1387, 1400, 1412, 1425, 1437, 1450, + 1462, 1475, 1487, 1500, +}; + +/* TPS65910 VDD3 */ +static const u16 VDD3_VSEL_table[] = { + 5000, +}; + +/* VDIG1 */ +static const u16 VDIG1_VSEL_table[] = { + 1200, 1500, 1800, 2700, +}; + +/* VDIG2 */ +static const u16 VDIG2_VSEL_table[] = { + 1000, 1100, 1200, 1800, +}; + +/* VAUX33 */ +static const u16 VAUX33_VSEL_table[] = { + 1800, 2000, 2800, 3300, +}; + +/* VMMC */ +static const u16 VMMC_VSEL_table[] = { + 1800, 2800, 3000, 3300, +}; + +/* VAUX1 */ +static const u16 VAUX1_VSEL_table[] = { + 1800, 2000, 2800, 3300, +}; + +/* VAUX2 */ +static const u16 VAUX2_VSEL_table[] = { + 1800, 2800, 2900, 3300, +}; + +/* VDAC */ +static const u16 VDAC_VSEL_table[] = { + 1800, 2600, 2800, 2850, +}; + + +/* VPLL */ +static const u16 VPLL_VSEL_table[] = { + 1000, 1100, 1800, 2500, +}; + +/* VRTC, supports only enable/disable */ +static const u16 VRTC_VSEL_table[] = { + 1800, +}; + +static inline int +tps65910reg_read(struct tps65910reg_info *info, unsigned slave_addr, + u8 offset) +{ + u8 value; + int status; + status = tps65910_i2c_read_u8(slave_addr, &value, offset); + + return (status < 0) ? status : value; +} + +static inline int +tps65910reg_write(struct tps65910reg_info *info, unsigned slave_addr, + u8 offset, u8 value) +{ + if (0 == tps65910_i2c_write_u8(slave_addr, value, offset)) + return 0; + else + return -1; +} + +static u8 tps65910reg_find_offset(u8 regulator_id) +{ + u8 offset = 0; + + switch (regulator_id) { + + case TPS65910_VIO: + offset = TPS65910_REG_VIO; + break; + case TPS65910_VDD1: + offset = TPS65910_REG_VDD1_OP; + break; + case TPS65910_VDD2: + offset = TPS65910_REG_VDD2_OP; + break; + case TPS65910_VDD3: + offset = TPS65910_REG_VDD3; + break; + case TPS65910_VDIG1: + offset = TPS65910_REG_VDIG1; + break; + case TPS65910_VDIG2: + offset = TPS65910_REG_VDIG2; + break; + case TPS65910_VAUX33: + offset = TPS65910_REG_VAUX33; + break; + case TPS65910_VMMC: + offset = TPS65910_REG_VMMC; + break; + case TPS65910_VAUX1: + offset = TPS65910_REG_VAUX1; + break; + case TPS65910_VAUX2: + offset = TPS65910_REG_VAUX2; + break; + case TPS65910_VDAC: + offset = TPS65910_REG_VDAC; + break; + case TPS65910_VPLL: + offset = TPS65910_REG_VPLL; + break; + } + return offset; +} + +static int tps65910reg_is_enabled(struct regulator_dev *rdev) +{ + int val; + u8 offset; + + struct tps65910reg_info *info = rdev_get_drvdata(rdev); + + offset = tps65910reg_find_offset(info->id); + + val = tps65910reg_read(info, TPS65910_I2C_ID0, offset); + if (val < 0) { + printk(KERN_ERR "Unable to read TPS65910 Reg at offset 0x%x= \ + \n", offset); + return -EIO; + } + if ((val & TPS65910_REG_OHP) || (val & TPS65910_REG_OLP)) + return 1; + else + return 0; +} + + +static int tps65910reg_enable(struct regulator_dev *rdev) +{ + int val; + u8 offset; + struct tps65910reg_info *info = rdev_get_drvdata(rdev); + + offset = tps65910reg_find_offset(info->id); + + val = tps65910reg_read(info, TPS65910_I2C_ID0, offset); + + if (val < 0) { + + printk(KERN_ERR "Unable to read TPS65910 Reg at offset = 0x%x \ + \n", offset); + return -EIO; + } + val |= TPS65910_REG_OHP; + + return tps65910reg_write(info, TPS65910_I2C_ID0, offset, val); +} + +static int tps65910reg_disable(struct regulator_dev *rdev) +{ + int val; + u8 offset; + + struct tps65910reg_info *info = rdev_get_drvdata(rdev); + + offset = tps65910reg_find_offset(info->id); + + val = tps65910reg_read(info, TPS65910_I2C_ID0, offset); + + if (val < 0) { + + printk(KERN_ERR "Unable to read TPS65910 Reg at offset = \ + 0x%x\n", offset); + return -EIO; + } + val &= TPS65910_REG_OFF_00; + + return tps65910reg_write(info, TPS65910_I2C_ID0, offset, val); +} + +static int tps65910reg_get_status(struct regulator_dev *rdev) +{ + int val; + u8 offset; + u8 ret; + struct tps65910reg_info *info = rdev_get_drvdata(rdev); + + offset = tps65910reg_find_offset(info->id); + + val = tps65910reg_read(info, TPS65910_I2C_ID0, offset); + + if (val < 0) { + + printk(KERN_ERR "Unable to read TPS65910 Reg at offset = \ + 0x%x\n", offset); + return -EIO; + } + switch ((val & SUPPLY_STATE_FLAG)) { + + case TPS65910_REG_OFF_00: + case TPS65910_REG_OFF_10: + ret = REGULATOR_STATUS_OFF; + break; + case TPS65910_REG_OHP: + case TPS65910_REG_OLP: + ret = REGULATOR_STATUS_ON; + break; + default: + ret = REGULATOR_STATUS_OFF; + } + return ret; +} + + +static int tps65910reg_set_mode(struct regulator_dev *rdev, unsigned mode) +{ + struct tps65910reg_info *info = rdev_get_drvdata(rdev); + u8 offset; + u8 val; + + offset = tps65910reg_find_offset(info->id); + val = tps65910reg_read(info, TPS65910_I2C_ID0, offset); + + if (val < 0) { + printk(KERN_ERR"Unable to read TPS65910 Reg at offset \ + = 0x%x\n", offset); + return -EIO; + } + + switch (mode) { + case REGULATOR_MODE_NORMAL: + return tps65910reg_write(info, TPS65910_I2C_ID0, offset, + (val | TPS65910_REG_OHP)); + case REGULATOR_MODE_STANDBY: + return tps65910reg_write(info, TPS65910_I2C_ID0, offset, + (val | TPS65910_REG_OLP)); + default: + return -EINVAL; + } +} + +static +int tps65910_ldo_list_voltage(struct regulator_dev *rdev, unsigned index) +{ + struct tps65910reg_info *info = rdev_get_drvdata(rdev); + int mV = info->table[index]; + return mV * 1000; +} + +static int get_voltage_index(int ldo_id, int uv) +{ + int i = 0; + int size = 0; + u16 *ptr = NULL; + + uv = uv/1000; + + if (((ldo_id == TPS65910_VDD1) || (ldo_id == TPS65910_VDD2))) { + for (i = 0; i < ARRAY_SIZE(VDD1_VSEL_table); i++) { + if (VDD1_VSEL_table[i] == uv) { + DBG("%s: regulator id=%d, volage=%d, get index=%d\n", __FUNCTION__, ldo_id, uv, i); + return i; + } + } + if (i == ARRAY_SIZE(VDD1_VSEL_table)) { + DBG("%s: regulator id=%d, can't find volage index\n", __FUNCTION__, ldo_id); + return -1; + } + } + + /* Lookup table to match LDO volatge to Index*/ + switch (ldo_id) { + + case TPS65910_VIO: + ptr = (u16 *)&VIO_VSEL_table[0]; + size = ARRAY_SIZE(VIO_VSEL_table); + break; + case TPS65910_VDIG1: + ptr = (u16 *)&VDIG1_VSEL_table[0]; + size = ARRAY_SIZE(VDIG1_VSEL_table); + break; + case TPS65910_VDIG2: + ptr = (u16 *)&VDIG2_VSEL_table[0]; + size = ARRAY_SIZE(VDIG2_VSEL_table); + break; + case TPS65910_VAUX33: + ptr = (u16 *)&VAUX33_VSEL_table[0]; + size = ARRAY_SIZE(VAUX33_VSEL_table); + break; + case TPS65910_VMMC: + ptr = (u16 *)&VMMC_VSEL_table[0]; + size = ARRAY_SIZE(VMMC_VSEL_table); + break; + case TPS65910_VAUX1: + ptr = (u16 *)&VAUX1_VSEL_table[0]; + size = ARRAY_SIZE(VAUX1_VSEL_table); + break; + case TPS65910_VAUX2: + ptr = (u16 *)&VAUX2_VSEL_table[0]; + size = ARRAY_SIZE(VAUX2_VSEL_table); + break; + case TPS65910_VDAC: + ptr = (u16 *)&VDAC_VSEL_table[0]; + size = ARRAY_SIZE(VDAC_VSEL_table); + break; + case TPS65910_VPLL: + ptr = (u16 *)&VPLL_VSEL_table[0]; + size = ARRAY_SIZE(VPLL_VSEL_table); + break; + default: + ptr = NULL; + break; + } + + if (ptr != NULL) { + for (i = 0; i < size; i++) { + if (*ptr++ == uv) { + DBG("%s: regulator id=%d, volage=%d, get index=%d\n", __FUNCTION__, ldo_id, uv, i); + return i; + } + } + } + DBG("%s: regulator id=%d, can't find volage index\n", __FUNCTION__, ldo_id); + + if (ptr == NULL || i == size) + return -1; + /* For warning */ + return -1; +} + +static int get_index_voltage(int ldo_id, int index) +{ + int vsel; + int size = 0; + u16 *ptr = NULL; + + /* Get the index of voltage value from Reg and map to table */ + if (ldo_id == TPS65910_VDD1 || ldo_id == TPS65910_VDD2) { + index &= 0x7F; + ptr = (u16 *)&VDD1_VSEL_table[0]; + size = ARRAY_SIZE(VDD1_VSEL_table); + /* For VDD1 and VDD2 */ + if (index >= size) { + vsel = size - 1; + } else { + vsel = index; + } + } else { + vsel = (index & 0xF3); + vsel = (vsel >> 2); + + /* Lookup table to match LDO volatge to Index*/ + switch (ldo_id) { + + case TPS65910_VIO: + ptr = (u16 *)&VIO_VSEL_table[0]; + size = ARRAY_SIZE(VIO_VSEL_table); + break; + case TPS65910_VDIG1: + ptr = (u16 *)&VDIG1_VSEL_table[0]; + size = ARRAY_SIZE(VDIG1_VSEL_table); + break; + case TPS65910_VDIG2: + ptr = (u16 *)&VDIG2_VSEL_table[0]; + size = ARRAY_SIZE(VDIG2_VSEL_table); + break; + case TPS65910_VAUX33: + ptr = (u16 *)&VAUX33_VSEL_table[0]; + size = ARRAY_SIZE(VAUX33_VSEL_table); + break; + case TPS65910_VMMC: + ptr = (u16 *)&VMMC_VSEL_table[0]; + size = ARRAY_SIZE(VMMC_VSEL_table); + break; + case TPS65910_VAUX1: + ptr = (u16 *)&VAUX1_VSEL_table[0]; + size = ARRAY_SIZE(VAUX1_VSEL_table); + break; + case TPS65910_VAUX2: + ptr = (u16 *)&VAUX2_VSEL_table[0]; + size = ARRAY_SIZE(VAUX2_VSEL_table); + break; + case TPS65910_VDAC: + ptr = (u16 *)&VDAC_VSEL_table[0]; + size = ARRAY_SIZE(VDAC_VSEL_table); + break; + case TPS65910_VPLL: + ptr = (u16 *)&VPLL_VSEL_table[0]; + size = ARRAY_SIZE(VPLL_VSEL_table); + break; + default: + ptr = NULL; + break; + } + + if (vsel >= size) + vsel = size - 1; + } + + if (ptr != NULL) { + DBG("%s: regulator id=%d index=%d, get volage=%d(mV)\n", __FUNCTION__, ldo_id, index, ptr[vsel]); + return ptr[vsel] * 1000; + } + /* For warning */ + DBG("%s: regulator id=%d index=%d, get volage error\n", __FUNCTION__, ldo_id, index); + return -1; +} + +static int +tps65910_ldo_set_voltage(struct regulator_dev *rdev, int min_uV, int max_uV) +{ + struct tps65910reg_info *info = rdev_get_drvdata(rdev); + int vsel; + u8 offset; + u8 val; + u8 index; + + if (info == NULL) + return 0; + + DBG("%s: regulator(%s) id=%d, set volage=(min_uV:%d, max_uV:%d)\n", __FUNCTION__, info->desc.name, info->id, min_uV, max_uV); + if (rdev->constraints) { + if (min_uV < rdev->constraints->min_uV || min_uV > rdev->constraints->max_uV) + return -EINVAL; + if (max_uV < rdev->constraints->min_uV || max_uV > rdev->constraints->max_uV) + return -EINVAL; + } + + for (vsel = 0; vsel < info->table_len; vsel++) { + + int mV = info->table[vsel]; + int uV; + + uV = mV * 1000; + + /* Break at the first in-range value */ + if (min_uV <= uV && uV <= max_uV) { + offset = tps65910reg_find_offset(info->id); + val = tps65910reg_read(info, TPS65910_I2C_ID0, offset); + if (val < 0) { + printk(KERN_ERR"Unable to read TPS65910 Reg at offset = 0x%x\n", + offset); + return -EIO; + } + + index = get_voltage_index(info->id, uV); + /* For VDD1 and VDD2 */ + if (info->id == TPS65910_VDD1 || info->id == TPS65910_VDD2) { + val = index; + return tps65910reg_write(info, TPS65910_I2C_ID0, offset, val); + } + + val &= 0xF3; + val = (index << 2); + val |= 0x01; + if (index < 0) { + printk(KERN_ERR "Invaild voltage for LDO \n"); + return -EINVAL; + } + + return tps65910reg_write(info, TPS65910_I2C_ID0, offset, val); + } + } + + return -EINVAL; +} + +static int tps65910_ldo_get_voltage(struct regulator_dev *rdev) +{ + struct tps65910reg_info *info = rdev_get_drvdata(rdev); + int vsel; + u8 offset; + + offset = tps65910reg_find_offset(info->id); + vsel = tps65910reg_read(info, TPS65910_I2C_ID0, offset); + if (vsel < 0) { + printk(KERN_ERR"Unable to read TPS65910 Reg at offset = \ + 0x%x\n", offset); + return -EIO; + } + + /* Get the index of voltage value from Reg and map to table */ + return get_index_voltage(info->id, vsel); +} + + +static struct regulator_ops tps65910_ldo_ops = { + .list_voltage = tps65910_ldo_list_voltage, + .set_voltage = tps65910_ldo_set_voltage, + .get_voltage = tps65910_ldo_get_voltage, + .enable = tps65910reg_enable, + .disable = tps65910reg_disable, + .is_enabled = tps65910reg_is_enabled, + .set_mode = tps65910reg_set_mode, + .get_status = tps65910reg_get_status, +}; + +static +int tps65910_fixed_list_voltage(struct regulator_dev *rdev, unsigned index) +{ + struct tps65910reg_info *info = rdev_get_drvdata(rdev); + + return info->min_mV * 1000; +} + +static int tps65910_fixed_get_voltage(struct regulator_dev *rdev) +{ + struct tps65910reg_info *info = rdev_get_drvdata(rdev); + + return info->min_mV * 1000; +} + +static struct regulator_ops tps65910_fixed_ops = { + .list_voltage = tps65910_fixed_list_voltage, + .get_voltage = tps65910_fixed_get_voltage, + .enable = tps65910reg_enable, + .disable = tps65910reg_disable, + .is_enabled = tps65910reg_is_enabled, + .set_mode = tps65910reg_set_mode, + .get_status = tps65910reg_get_status, +}; + +#define TPS65910_ADJUSTABLE_LDO(label, num, min_mVolts, max_mVolts,\ + turnon_delay) { \ + .id = num, \ + .table_len = ARRAY_SIZE(label##_VSEL_table), \ + .table = label##_VSEL_table, \ + .min_mV = min_mVolts, \ + .max_mV = max_mVolts, \ + .delay = turnon_delay, \ + .desc = { \ + .name = #label, \ + .id = TPS65910_##label, \ + .n_voltages = ARRAY_SIZE(label##_VSEL_table), \ + .ops = &tps65910_ldo_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + }, \ +} + +#define TPS65910_FIXED_LDO(label, num, mVolts, turnon_delay) { \ + .id = num, \ + .min_mV = mVolts, \ + .delay = turnon_delay, \ + .desc = { \ + .name = #label, \ + .id = TPS65910_##label, \ + .n_voltages = 1, \ + .ops = &tps65910_fixed_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + }, \ +} + +/* + * We list regulators here if systems need some level of + * software control over them after boot. + */ +static struct tps65910reg_info tps65910_regs[] = { + + TPS65910_ADJUSTABLE_LDO(VDD1, TPS65910_VDD1, 950, 1400, 1500), + TPS65910_FIXED_LDO(VDD2, TPS65910_VDD2, 1200, 1500), + TPS65910_FIXED_LDO(VIO, TPS65910_VIO, 3300, 3300), + + TPS65910_FIXED_LDO(VDD3, TPS65910_VDD3, 5000, 200), + + TPS65910_FIXED_LDO(VDIG1, TPS65910_VDIG1, 2700, 2700), + TPS65910_FIXED_LDO(VDIG2, TPS65910_VDIG2, 1200, 1800), + TPS65910_FIXED_LDO(VAUX33, TPS65910_VAUX33, 3300, 3300), + TPS65910_FIXED_LDO(VMMC, TPS65910_VMMC, 3000, 3300), + TPS65910_FIXED_LDO(VAUX1, TPS65910_VAUX1, 2800, 3300), + TPS65910_FIXED_LDO(VAUX2, TPS65910_VAUX1, 2900, 3300), + TPS65910_FIXED_LDO(VDAC, TPS65910_VDAC, 1800, 2850), + TPS65910_FIXED_LDO(VPLL, TPS65910_VPLL, 2500, 2500), +}; + +static int tps65910_regulator_probe(struct platform_device *pdev) +{ + int i; + struct tps65910reg_info *info; + struct regulator_init_data *initdata; + struct regulation_constraints *c; + struct regulator_dev *rdev; + + + DBG("%s\n", __FUNCTION__); + for (i = 0, info = NULL; i < ARRAY_SIZE(tps65910_regs); i++) { + if (tps65910_regs[i].desc.id != pdev->id) + continue; + info = tps65910_regs + i; + break; + } + if (!info) + return -ENODEV; + + DBG("%s: reguloter(%s) id=%d\n", __FUNCTION__, info->desc.name, info->id); + + initdata = pdev->dev.platform_data; + if (!initdata) + return -EINVAL; + + /* Constrain board-specific capabilities according to what + * this driver and the chip itself can actually do. + */ + c = &initdata->constraints; + c->valid_modes_mask &= REGULATOR_MODE_NORMAL | REGULATOR_MODE_STANDBY; + c->valid_ops_mask &= REGULATOR_CHANGE_VOLTAGE + | REGULATOR_CHANGE_MODE + | REGULATOR_CHANGE_STATUS; + + switch (pdev->id) { + case TPS65910_REG_VDD1: + case TPS65910_REG_VIO: + case TPS65910_REG_VDD2: + case TPS65910_REG_VPLL: + case TPS65910_REG_VDIG2: + c->always_on = true; + break; + default: + break; + } + + rdev = regulator_register(&info->desc, &pdev->dev, initdata, info); + + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "can't register %s, %ld\n", + info->desc.name, PTR_ERR(rdev)); + return PTR_ERR(rdev); + } + platform_set_drvdata(pdev, rdev); + + DBG("%s: reguloter register OK.\n", __FUNCTION__); + return 0; +} + +static int __devexit tps65910_regulator_remove(struct platform_device *pdev) +{ + regulator_unregister(platform_get_drvdata(pdev)); + return 0; +} + +static struct platform_driver tps65910_regulator_driver = { + .probe = tps65910_regulator_probe, + .remove = tps65910_regulator_remove, + .driver.name = "tps65910_regulator", + .driver.owner = THIS_MODULE, +}; + +static int __init tps65910_regulator_init(void) +{ + return platform_driver_register(&tps65910_regulator_driver); +} +module_init(tps65910_regulator_init); + +static void __exit tps65910_regulator_exit(void) +{ + platform_driver_unregister(&tps65910_regulator_driver); +} +module_exit(tps65910_regulator_exit) + +MODULE_AUTHOR("cwz "); +MODULE_DESCRIPTION("TPS65910 voltage regulator driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 9a1495575e22..3c6c44b138ac 100755 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -294,6 +294,13 @@ config RTC_DRV_TWL4030 This driver can also be built as a module. If so, the module will be called rtc-twl4030. +config RTC_DRV_TPS65910 + boolean "TI TPS65910" + depends on RTC_CLASS && TPS65910_CORE + help + If you say yes here you get support for the RTC on the + TPS65910 family chips, used mostly with OMAP3/AM35xx platforms. + config RTC_DRV_S35390A tristate "Seiko Instruments S-35390A" select BITREVERSE diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index a0ec07e5ab2c..984c79a95049 100755 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -82,6 +82,7 @@ obj-$(CONFIG_RTC_DRV_STMP) += rtc-stmp3xxx.o obj-$(CONFIG_RTC_DRV_SUN4V) += rtc-sun4v.o obj-$(CONFIG_RTC_DRV_TEST) += rtc-test.o obj-$(CONFIG_RTC_DRV_TWL4030) += rtc-twl4030.o +obj-$(CONFIG_RTC_DRV_TPS65910) += rtc-tps65910.o obj-$(CONFIG_RTC_DRV_TX4939) += rtc-tx4939.o obj-$(CONFIG_RTC_DRV_V3020) += rtc-v3020.o obj-$(CONFIG_RTC_DRV_VR41XX) += rtc-vr41xx.o diff --git a/drivers/rtc/rtc-tps65910.c b/drivers/rtc/rtc-tps65910.c new file mode 100644 index 000000000000..4b7666d1da8b --- /dev/null +++ b/drivers/rtc/rtc-tps65910.c @@ -0,0 +1,695 @@ +/* + * rtc-tps65910.c -- TPS65910 Real Time Clock interface + * + * Copyright (C) 2010 Mistral Solutions Pvt Ltd. + * Author: Umesh K + * + * Based on rtc-twl.c + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if 0 +#define DBG(x...) printk(KERN_INFO x) +#else +#define DBG(x...) +#endif + +/* RTC Definitions */ +/* RTC_CTRL_REG bitfields */ +#define BIT_RTC_CTRL_REG_STOP_RTC_M 0x01 +#define BIT_RTC_CTRL_REG_ROUND_30S_M 0x02 +#define BIT_RTC_CTRL_REG_AUTO_COMP_M 0x04 +#define BIT_RTC_CTRL_REG_MODE_12_24_M 0x08 +#define BIT_RTC_CTRL_REG_TEST_MODE_M 0x10 +#define BIT_RTC_CTRL_REG_SET_32_COUNTER_M 0x20 +#define BIT_RTC_CTRL_REG_GET_TIME_M 0x40 +#define BIT_RTC_CTRL_REG_RTC_V_OPT_M 0x80 + +/* RTC_STATUS_REG bitfields */ +#define BIT_RTC_STATUS_REG_RUN_M 0x02 +#define BIT_RTC_STATUS_REG_1S_EVENT_M 0x04 +#define BIT_RTC_STATUS_REG_1M_EVENT_M 0x08 +#define BIT_RTC_STATUS_REG_1H_EVENT_M 0x10 +#define BIT_RTC_STATUS_REG_1D_EVENT_M 0x20 +#define BIT_RTC_STATUS_REG_ALARM_M 0x40 +#define BIT_RTC_STATUS_REG_POWER_UP_M 0x80 + +/* RTC_INTERRUPTS_REG bitfields */ +#define BIT_RTC_INTERRUPTS_REG_EVERY_M 0x03 +#define BIT_RTC_INTERRUPTS_REG_IT_TIMER_M 0x04 +#define BIT_RTC_INTERRUPTS_REG_IT_ALARM_M 0x08 + +/* DEVCTRL bitfields */ +#define BIT_RTC_PWDN 0x40 + +/* REG_SECONDS_REG through REG_YEARS_REG is how many registers? */ +#define ALL_TIME_REGS 6 + +/* + * Supports 1 byte read from TPS65910 RTC register. + */ +static int tps65910_rtc_read_u8(u8 *data, u8 reg) +{ + int ret; + + ret = tps65910_i2c_read_u8(TPS65910_I2C_ID0, data, reg); + + if (ret < 0) + pr_err("tps65910_rtc: Could not read TPS65910" + "register %X - error %d\n", reg, ret); + return ret; +} + +/* + * Supports 1 byte write to TPS65910 RTC registers. + */ +static int tps65910_rtc_write_u8(u8 data, u8 reg) +{ + int ret; + + ret = tps65910_i2c_write_u8(TPS65910_I2C_ID0, data, reg); + if (ret < 0) + pr_err("tps65910_rtc: Could not write TPS65910" + "register %X - error %d\n", reg, ret); + return ret; +} + +/* + * Cache the value for timer/alarm interrupts register; this is + * only changed by callers holding rtc ops lock (or resume). + */ +static unsigned char rtc_irq_bits; + +/* + * Enable 1/second update and/or alarm interrupts. + */ +static int set_rtc_irq_bit(unsigned char bit) +{ + unsigned char val; + int ret; + + val = rtc_irq_bits | bit; + val |= bit; + ret = tps65910_rtc_write_u8(val, TPS65910_REG_RTC_INTERRUPTS); + if (ret == 0) + rtc_irq_bits = val; + + return ret; +} + +/* + * Disable update and/or alarm interrupts. + */ +static int mask_rtc_irq_bit(unsigned char bit) +{ + unsigned char val; + int ret; + + val = rtc_irq_bits & ~bit; + ret = tps65910_rtc_write_u8(val, TPS65910_REG_RTC_INTERRUPTS); + if (ret == 0) + rtc_irq_bits = val; + + return ret; +} + +static int tps65910_rtc_alarm_irq_enable(struct device *dev, unsigned enabled) +{ + int ret; + + if (enabled) + ret = set_rtc_irq_bit(BIT_RTC_INTERRUPTS_REG_IT_ALARM_M); + else + ret = mask_rtc_irq_bit(BIT_RTC_INTERRUPTS_REG_IT_ALARM_M); + + return ret; +} + +static int tps65910_rtc_update_irq_enable(struct device *dev, unsigned enabled) +{ + int ret; + + if (enabled) + ret = set_rtc_irq_bit(BIT_RTC_INTERRUPTS_REG_IT_TIMER_M); + else + ret = mask_rtc_irq_bit(BIT_RTC_INTERRUPTS_REG_IT_TIMER_M); + + return ret; +} + +#if 1 /* Debugging periodic interrupts */ +/* + * We will just handle setting the frequency and make use the framework for + * reading the periodic interupts. + * + * @freq: Current periodic IRQ freq: + * bit 0: every second + * bit 1: every minute + * bit 2: every hour + * bit 3: every day + */ + +static int tps65910_rtc_irq_set_freq(struct device *dev, int freq) +{ + struct rtc_device *rtc = dev_get_drvdata(dev); + + if (freq < 0 || freq > 3) + return -EINVAL; + + rtc->irq_freq = freq; + /* set rtc irq freq to user defined value */ + set_rtc_irq_bit(freq); + + return 0; +} +#endif + +/* + * Gets current TPS65910 RTC time and date parameters. + * + * The RTC's time/alarm representation is not what gmtime(3) requires + * Linux to use: + * + * - Months are 1..12 vs Linux 0-11 + * - Years are 0..99 vs Linux 1900..N (we assume 21st century) + */ +static int tps65910_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + unsigned char rtc_data[ALL_TIME_REGS + 1]; + int ret; + u8 save_control; + + tps65910_rtc_read_u8(&save_control, TPS65910_REG_RTC_CTRL); + ret = tps65910_rtc_read_u8(&save_control, TPS65910_REG_RTC_CTRL); + if (ret < 0) + return ret; + + save_control &= ~BIT_RTC_CTRL_REG_RTC_V_OPT_M; + + ret = tps65910_rtc_write_u8(save_control, TPS65910_REG_RTC_CTRL); + if (ret < 0) + return ret; + + ret = tps65910_rtc_read_u8(&rtc_data[0], TPS65910_REG_SECONDS); + if (ret < 0) { + dev_err(dev, "rtc_read_time error %d\n", ret); + return ret; + } + ret = tps65910_rtc_read_u8(&rtc_data[1], TPS65910_REG_MINUTES); + if (ret < 0) { + dev_err(dev, "rtc_read_time error %d\n", ret); + return ret; + } + ret = tps65910_rtc_read_u8(&rtc_data[2], TPS65910_REG_HOURS); + if (ret < 0) { + dev_err(dev, "rtc_read_time error %d\n", ret); + return ret; + } + ret = tps65910_rtc_read_u8(&rtc_data[3], TPS65910_REG_DAYS); + if (ret < 0) { + dev_err(dev, "rtc_read_time error %d\n", ret); + return ret; + } + ret = tps65910_rtc_read_u8(&rtc_data[4], TPS65910_REG_MONTHS); + if (ret < 0) { + dev_err(dev, "rtc_read_time error %d\n", ret); + return ret; + } + ret = tps65910_rtc_read_u8(&rtc_data[5], TPS65910_REG_YEARS); + if (ret < 0) { + dev_err(dev, "rtc_read_time error %d\n", ret); + return ret; + } + + tm->tm_sec = bcd2bin(rtc_data[0]); + tm->tm_min = bcd2bin(rtc_data[1]); + tm->tm_hour = bcd2bin(rtc_data[2]); + tm->tm_mday = bcd2bin(rtc_data[3]); + tm->tm_mon = bcd2bin(rtc_data[4]) - 1; + tm->tm_year = bcd2bin(rtc_data[5]) + 100; + + DBG("%s [%d]tm_wday=%d \n",__FUNCTION__,__LINE__,tm->tm_wday); + DBG("%s [%d]tm_sec=%d \n",__FUNCTION__,__LINE__,tm->tm_sec); + DBG("%s [%d]tm_min=%d \n",__FUNCTION__,__LINE__,tm->tm_min); + DBG("%s [%d]tm_hour=%d \n",__FUNCTION__,__LINE__,tm->tm_hour); + DBG("%s [%d]tm_mday=%d \n",__FUNCTION__,__LINE__,tm->tm_mday); + DBG("%s [%d]tm_mon=%d \n",__FUNCTION__,__LINE__,tm->tm_mon); + DBG("%s [%d]tm_year=%d \n",__FUNCTION__,__LINE__,tm->tm_year); + + return ret; +} + +static int tps65910_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + unsigned char save_control; + unsigned char rtc_data[ALL_TIME_REGS + 1]; + int ret; + + DBG("%s [%d]tm_wday=%d \n",__FUNCTION__,__LINE__,tm->tm_wday); + DBG("%s [%d]tm_sec=%d \n",__FUNCTION__,__LINE__,tm->tm_sec); + DBG("%s [%d]tm_min=%d \n",__FUNCTION__,__LINE__,tm->tm_min); + DBG("%s [%d]tm_hour=%d \n",__FUNCTION__,__LINE__,tm->tm_hour); + DBG("%s [%d]tm_mday=%d \n",__FUNCTION__,__LINE__,tm->tm_mday); + DBG("%s [%d]tm_mon=%d \n",__FUNCTION__,__LINE__,tm->tm_mon); + DBG("%s [%d]tm_year=%d \n",__FUNCTION__,__LINE__,tm->tm_year); + + rtc_data[1] = bin2bcd(tm->tm_sec); + rtc_data[2] = bin2bcd(tm->tm_min); + rtc_data[3] = bin2bcd(tm->tm_hour); + rtc_data[4] = bin2bcd(tm->tm_mday); + rtc_data[5] = bin2bcd(tm->tm_mon + 1); + rtc_data[6] = bin2bcd(tm->tm_year - 100); + + /*Dummy read*/ + ret = tps65910_rtc_read_u8(&save_control, TPS65910_REG_RTC_CTRL); + + /* Stop RTC while updating the TC registers */ + ret = tps65910_rtc_read_u8(&save_control, TPS65910_REG_RTC_CTRL); + if (ret < 0) + goto out; + + save_control &= ~BIT_RTC_CTRL_REG_STOP_RTC_M; + + tps65910_rtc_write_u8(save_control, TPS65910_REG_RTC_CTRL); + + /* update all the time registers in one shot */ + ret = tps65910_rtc_write_u8(rtc_data[1], TPS65910_REG_SECONDS); + if (ret < 0) { + dev_err(dev, "rtc_write_time error %d\n", ret); + return ret; + } + ret = tps65910_rtc_write_u8(rtc_data[2], TPS65910_REG_MINUTES); + if (ret < 0) { + dev_err(dev, "rtc_write_time error %d\n", ret); + return ret; + } + ret = tps65910_rtc_write_u8(rtc_data[3], TPS65910_REG_HOURS); + if (ret < 0) { + dev_err(dev, "rtc_write_time error %d\n", ret); + return ret; + } + ret = tps65910_rtc_write_u8(rtc_data[4], TPS65910_REG_DAYS); + if (ret < 0) { + dev_err(dev, "rtc_write_time error %d\n", ret); + return ret; + } + ret = tps65910_rtc_write_u8(rtc_data[5], TPS65910_REG_MONTHS); + if (ret < 0) { + dev_err(dev, "rtc_write_time error %d\n", ret); + return ret; + } + ret = tps65910_rtc_write_u8(rtc_data[6], TPS65910_REG_YEARS); + if (ret < 0) { + dev_err(dev, "rtc_write_time error %d\n", ret); + return ret; + } + + /*Dummy read*/ + ret = tps65910_rtc_read_u8(&save_control, TPS65910_REG_RTC_CTRL); + + ret = tps65910_rtc_read_u8(&save_control, TPS65910_REG_RTC_CTRL); + if (ret < 0) + goto out; + /* Start back RTC */ + save_control |= BIT_RTC_CTRL_REG_STOP_RTC_M; + ret = tps65910_rtc_write_u8(save_control, TPS65910_REG_RTC_CTRL); + +out: + return ret; +} + +/* + * Gets current TPS65910 RTC alarm time. + */ +static int tps65910_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alm) +{ + unsigned char rtc_data[ALL_TIME_REGS + 1]; + int ret; + + ret = tps65910_rtc_read_u8(&rtc_data[0], TPS65910_REG_ALARM_SECONDS); + if (ret < 0) { + dev_err(dev, "rtc_read_time error %d\n", ret); + return ret; + } + ret = tps65910_rtc_read_u8(&rtc_data[1], TPS65910_REG_ALARM_MINUTES); + if (ret < 0) { + dev_err(dev, "rtc_read_time error %d\n", ret); + return ret; + } + ret = tps65910_rtc_read_u8(&rtc_data[2], TPS65910_REG_ALARM_HOURS); + if (ret < 0) { + dev_err(dev, "rtc_read_time error %d\n", ret); + return ret; + } + ret = tps65910_rtc_read_u8(&rtc_data[3], TPS65910_REG_ALARM_DAYS); + if (ret < 0) { + dev_err(dev, "rtc_read_time error %d\n", ret); + return ret; + } + ret = tps65910_rtc_read_u8(&rtc_data[4], TPS65910_REG_ALARM_MONTHS); + if (ret < 0) { + dev_err(dev, "rtc_read_time error %d\n", ret); + return ret; + } + ret = tps65910_rtc_read_u8(&rtc_data[5], TPS65910_REG_ALARM_YEARS); + if (ret < 0) { + dev_err(dev, "rtc_read_time error %d\n", ret); + return ret; + } + + /* some of these fields may be wildcard/"match all" */ + alm->time.tm_sec = bcd2bin(rtc_data[0]); + alm->time.tm_min = bcd2bin(rtc_data[1]); + alm->time.tm_hour = bcd2bin(rtc_data[2]); + alm->time.tm_mday = bcd2bin(rtc_data[3]); + alm->time.tm_mon = bcd2bin(rtc_data[4]) - 1; + alm->time.tm_year = bcd2bin(rtc_data[5]) + 100; + + /* report cached alarm enable state */ + if (rtc_irq_bits & BIT_RTC_INTERRUPTS_REG_IT_ALARM_M) + alm->enabled = 1; + + return ret; +} + +static int tps65910_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm) +{ + unsigned char alarm_data[ALL_TIME_REGS + 1]; + int ret; + + ret = tps65910_rtc_alarm_irq_enable(dev, 0); + if (ret) + goto out; + + alarm_data[1] = bin2bcd(alm->time.tm_sec); + alarm_data[2] = bin2bcd(alm->time.tm_min); + alarm_data[3] = bin2bcd(alm->time.tm_hour); + alarm_data[4] = bin2bcd(alm->time.tm_mday); + alarm_data[5] = bin2bcd(alm->time.tm_mon + 1); + alarm_data[6] = bin2bcd(alm->time.tm_year - 100); + + /* update all the alarm registers in one shot */ + ret = tps65910_rtc_write_u8(alarm_data[1], TPS65910_REG_ALARM_SECONDS); + if (ret < 0) { + dev_err(dev, "rtc_write_time error %d\n", ret); + return ret; + } + ret = tps65910_rtc_write_u8(alarm_data[2], TPS65910_REG_ALARM_MINUTES); + if (ret < 0) { + dev_err(dev, "rtc_write_time error %d\n", ret); + return ret; + } + ret = tps65910_rtc_write_u8(alarm_data[3], TPS65910_REG_ALARM_HOURS); + if (ret < 0) { + dev_err(dev, "rtc_write_time error %d\n", ret); + return ret; + } + ret = tps65910_rtc_write_u8(alarm_data[4], TPS65910_REG_ALARM_DAYS); + if (ret < 0) { + dev_err(dev, "rtc_write_time error %d\n", ret); + return ret; + } + ret = tps65910_rtc_write_u8(alarm_data[5], TPS65910_REG_ALARM_MONTHS); + if (ret < 0) { + dev_err(dev, "rtc_write_time error %d\n", ret); + return ret; + } + ret = tps65910_rtc_write_u8(alarm_data[6], TPS65910_REG_ALARM_YEARS); + if (ret < 0) { + dev_err(dev, "rtc_write_time error %d\n", ret); + return ret; + } + + if (alm->enabled) + ret = tps65910_rtc_alarm_irq_enable(dev, 1); +out: + return ret; +} + + +struct work_struct rtc_wq; +unsigned long rtc_events; +struct rtc_device *global_rtc; + +void rtc_work(void *data) +{ + + int res; + u8 rd_reg; + unsigned long events = 0; + + res = tps65910_rtc_read_u8(&rd_reg, TPS65910_REG_INT_STS); + + if (res < 0) + goto out; + /* + * Figure out source of interrupt: ALARM or TIMER in RTC_STATUS_REG. + * only one (ALARM or RTC) interrupt source may be enabled + * at time, we also could check our results + * by reading RTS_INTERRUPTS_REGISTER[IT_TIMER,IT_ALARM] + */ + if (rd_reg & TPS65910_RTC_ALARM_IT) { + res = tps65910_rtc_write_u8(rd_reg | TPS65910_RTC_ALARM_IT, + TPS65910_REG_INT_STS); + if (res < 0) + goto out; + + /*Dummy read -- mandatory for status register*/ + res = tps65910_rtc_read_u8(&rd_reg, TPS65910_REG_RTC_STATUS); + mdelay(100); + res = tps65910_rtc_read_u8(&rd_reg, TPS65910_REG_RTC_STATUS); + res = tps65910_rtc_write_u8(rd_reg, TPS65910_REG_RTC_STATUS); + + rtc_events |= RTC_IRQF | RTC_AF; + } else if (rd_reg & TPS65910_RTC_PERIOD_IT) { + res = tps65910_rtc_write_u8(rd_reg | TPS65910_RTC_PERIOD_IT, + TPS65910_REG_INT_STS); + if (res < 0) + goto out; + + /*Dummy read -- mandatory for status register*/ + res = tps65910_rtc_read_u8(&rd_reg, TPS65910_REG_RTC_STATUS); + mdelay(100); + res = tps65910_rtc_read_u8(&rd_reg, TPS65910_REG_RTC_STATUS); + rd_reg &= 0xC3; + res = tps65910_rtc_write_u8(rd_reg, TPS65910_REG_RTC_STATUS); + rtc_events |= RTC_IRQF | RTC_UF; + } +out: + /* Notify RTC core on event */ + events = rtc_events; + rtc_update_irq(global_rtc, 1, events); +} + +static struct rtc_class_ops tps65910_rtc_ops = { + .read_time = tps65910_rtc_read_time, + .set_time = tps65910_rtc_set_time, + .read_alarm = tps65910_rtc_read_alarm, + .set_alarm = tps65910_rtc_set_alarm, + .alarm_irq_enable = tps65910_rtc_alarm_irq_enable, + .update_irq_enable = tps65910_rtc_update_irq_enable, + .irq_set_freq = tps65910_rtc_irq_set_freq, +}; + +static int __devinit tps65910_rtc_probe(struct platform_device *pdev) +{ + struct rtc_device *rtc; + int ret = 0; + u8 rd_reg; + struct rtc_time tm_def = { // 2011.1.1 12:00 Saturday + .tm_wday = 6, + .tm_year = 111, + .tm_mon = 0, + .tm_mday = 1, + .tm_hour = 12, + .tm_min = 0, + .tm_sec = 0, + }; + + rtc = rtc_device_register(pdev->name, + &pdev->dev, &tps65910_rtc_ops, THIS_MODULE); + + if (IS_ERR(rtc)) { + ret = PTR_ERR(rtc); + dev_err(&pdev->dev, "can't register TPS65910 RTC device,\ + err %ld\n", PTR_ERR(rtc)); + goto out0; + + } + printk(KERN_INFO "TPS65910 RTC device successfully registered\n"); + + platform_set_drvdata(pdev, rtc); + + /* Take rtc out of reset */ + tps65910_rtc_read_u8(&rd_reg, TPS65910_REG_DEVCTRL); + rd_reg &= ~BIT_RTC_PWDN; + ret = tps65910_rtc_write_u8(rd_reg, TPS65910_REG_DEVCTRL); + + /* Dummy read to ensure that the register gets updated. + * Please refer tps65910 TRM table:25 for details + */ + tps65910_rtc_read_u8(&rd_reg, TPS65910_REG_RTC_STATUS); + + ret = tps65910_rtc_read_u8(&rd_reg, TPS65910_REG_RTC_STATUS); + if (ret < 0) { + printk(KERN_ERR "TPS65910 RTC STATUS REG READ FAILED\n"); + goto out1; + } + + if (rd_reg & BIT_RTC_STATUS_REG_POWER_UP_M) { + dev_warn(&pdev->dev, "Power up reset detected.\n"); + // cwz:if rtc power up reset, set default time. + printk(KERN_INFO "TPS65910 RTC set to default time\n"); + tps65910_rtc_set_time(rtc, &tm_def); + } + + if (rd_reg & BIT_RTC_STATUS_REG_ALARM_M) + dev_warn(&pdev->dev, "Pending Alarm interrupt detected.\n"); + + /* Clear RTC Power up reset and pending alarm interrupts */ + ret = tps65910_rtc_write_u8(rd_reg, TPS65910_REG_RTC_STATUS); + if (ret < 0) + goto out1; + ret = tps65910_rtc_read_u8(&rd_reg, TPS65910_REG_INT_STS); + if (ret < 0) { + printk(KERN_ERR "TPS65910 RTC STATUS REG READ FAILED\n"); + goto out1; + } + + if (rd_reg & 0x40) { + printk(KERN_INFO "pending alarm interrupt!!! clearing!!!"); + tps65910_rtc_write_u8(rd_reg, TPS65910_REG_INT_STS); + } + + global_rtc = rtc; + + /* Link RTC IRQ handler to TPS65910 Core */ + tps65910_add_irq_work(TPS65910_RTC_ALARM_IRQ, rtc_work); + tps65910_add_irq_work(TPS65910_RTC_PERIOD_IRQ, rtc_work); + + /* Check RTC module status, Enable if it is off */ + ret = tps65910_rtc_read_u8(&rd_reg, TPS65910_REG_RTC_CTRL); + if (ret < 0) + goto out1; + + if (!(rd_reg & BIT_RTC_CTRL_REG_STOP_RTC_M)) { + dev_info(&pdev->dev, "Enabling TPS65910-RTC.\n"); + // cwz:if rtc stop, set default time, then enable rtc + printk(KERN_INFO "TPS65910 RTC set to default time\n"); + tps65910_rtc_set_time(rtc, &tm_def); + rd_reg |= BIT_RTC_CTRL_REG_STOP_RTC_M; + ret = tps65910_rtc_write_u8(rd_reg, TPS65910_REG_RTC_CTRL); + if (ret < 0) + goto out1; + } + + /* init cached IRQ enable bits */ + ret = tps65910_rtc_read_u8(&rtc_irq_bits, TPS65910_REG_RTC_INTERRUPTS); + if (ret < 0) + goto out1; + + tps65910_rtc_write_u8(0x3F, TPS65910_REG_INT_MSK); + return ret; + +out1: + rtc_device_unregister(rtc); +out0: + return ret; +} + +/* + * Disable all TPS65910 RTC module interrupts. + * Sets status flag to free. + */ +static int __devexit tps65910_rtc_remove(struct platform_device *pdev) +{ + /* leave rtc running, but disable irqs */ + struct rtc_device *rtc = platform_get_drvdata(pdev); + int irq = platform_get_irq(pdev, 0); + + mask_rtc_irq_bit(BIT_RTC_INTERRUPTS_REG_IT_ALARM_M); + mask_rtc_irq_bit(BIT_RTC_INTERRUPTS_REG_IT_TIMER_M); + + + free_irq(irq, rtc); + + rtc_device_unregister(rtc); + platform_set_drvdata(pdev, NULL); + return 0; +} + +static void tps65910_rtc_shutdown(struct platform_device *pdev) +{ + /* mask timer interrupts, but leave alarm interrupts on to enable + * power-on when alarm is triggered + */ + mask_rtc_irq_bit(BIT_RTC_INTERRUPTS_REG_IT_TIMER_M); +} + +#ifdef CONFIG_PM + +static unsigned char irqstat; + +static +int tps65910_rtc_suspend(struct platform_device *pdev, pm_message_t state) +{ + irqstat = rtc_irq_bits; + mask_rtc_irq_bit(BIT_RTC_INTERRUPTS_REG_IT_TIMER_M); + return 0; +} + +static int tps65910_rtc_resume(struct platform_device *pdev) +{ + set_rtc_irq_bit(irqstat); + return 0; +} + +#else +#define tps65910_rtc_suspend NULL +#define tps65910_rtc_resume NULL +#endif + + +static struct platform_driver tps65910rtc_driver = { + .probe = tps65910_rtc_probe, + .remove = __devexit_p(tps65910_rtc_remove), + .shutdown = tps65910_rtc_shutdown, + .suspend = tps65910_rtc_suspend, + .resume = tps65910_rtc_resume, + .driver = { + .owner = THIS_MODULE, + .name = "tps65910_rtc", + }, +}; +static int __init tps65910_rtc_init(void) +{ + return platform_driver_register(&tps65910rtc_driver); +} +module_init(tps65910_rtc_init); + +static void __exit tps65910_rtc_exit(void) +{ + platform_driver_unregister(&tps65910rtc_driver); +} +module_exit(tps65910_rtc_exit); + +MODULE_ALIAS("platform:tps65910_rtc"); +MODULE_AUTHOR("cwz + * + * 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. + */ + +#ifndef __LINUX_I2C_TPS65910_H +#define __LINUX_I2C_TPS65910_H + +#define TPS65910_NUM_SLAVES 1 +/* I2C Slave Address 7-bit */ +#define TPS65910_I2C_ID0 0x2D /* general-purpose */ +#define TPS65910_I2C_ID1 0x12 /* Smart Reflex */ + +/* TPS65910 to host IRQ */ +#define TPS65910_HOST_IRQ RK29_PIN6_PD3 + +/* TPS65910 MAX GPIOs */ +#define TPS65910_GPIO_MAX 1 + +/* + * ---------------------------------------------------------------------------- + * Registers, all 8 bits + * ---------------------------------------------------------------------------- + */ +#define TPS65910_REG_SECONDS 0x00 +#define TPS65910_REG_MINUTES 0x01 +#define TPS65910_REG_HOURS 0x02 +#define TPS65910_REG_DAYS 0x03 +#define TPS65910_REG_MONTHS 0x04 +#define TPS65910_REG_YEARS 0x05 +#define TPS65910_REG_WEEKS 0x06 +#define TPS65910_REG_ALARM_SECONDS 0x08 +#define TPS65910_REG_ALARM_MINUTES 0x09 +#define TPS65910_REG_ALARM_HOURS 0x0A +#define TPS65910_REG_ALARM_DAYS 0x0B +#define TPS65910_REG_ALARM_MONTHS 0x0C +#define TPS65910_REG_ALARM_YEARS 0x0D + +#define TPS65910_REG_RTC_CTRL 0x10 +#define TPS65910_REG_RTC_STATUS 0x11 +#define TPS65910_REG_RTC_INTERRUPTS 0x12 +#define TPS65910_REG_RTC_COMP_LSB 0x13 +#define TPS65910_REG_RTC_COMP_MSB 0x14 +#define TPS65910_REG_RTC_RES_PROG 0x15 +#define TPS65910_REG_RTC_RESET_STATUS 0x16 +#define TPS65910_REG_BCK1 0x17 +#define TPS65910_REG_BCK2 0x18 +#define TPS65910_REG_BCK3 0x19 +#define TPS65910_REG_BCK4 0x1A +#define TPS65910_REG_BCK5 0x1B +#define TPS65910_REG_PUADEN 0x1C +#define TPS65910_REG_REF 0x1D +#define TPS65910_REG_VRTC 0x1E + +#define TPS65910_REG_VIO 0x20 +#define TPS65910_REG_VDD1 0x21 +#define TPS65910_REG_VDD1_OP 0x22 +#define TPS65910_REG_VDD1_SR 0x23 +#define TPS65910_REG_VDD2 0x24 +#define TPS65910_REG_VDD2_OP 0x25 +#define TPS65910_REG_VDD2_SR 0x26 +#define TPS65910_REG_VDD3 0x27 + +#define TPS65910_REG_VDIG1 0x30 +#define TPS65910_REG_VDIG2 0x31 +#define TPS65910_REG_VAUX1 0x32 +#define TPS65910_REG_VAUX2 0x33 +#define TPS65910_REG_VAUX33 0x34 +#define TPS65910_REG_VMMC 0x35 +#define TPS65910_REG_VPLL 0x36 +#define TPS65910_REG_VDAC 0x37 +#define TPS65910_REG_THERM 0x38 +#define TPS65910_REG_BBCH 0x39 + +#define TPS65910_REG_DCDCCTRL 0x3E +#define TPS65910_REG_DEVCTRL 0x3F +#define TPS65910_REG_DEVCTRL2 0x40 +#define TPS65910_REG_SLEEP_KEEP_LDO_ON 0x41 +#define TPS65910_REG_SLEEP_KEEP_RES_ON 0x42 +#define TPS65910_REG_SLEEP_SET_LDO_OFF 0x43 +#define TPS65910_REG_SLEEP_SET_RES_OFF 0x44 +#define TPS65910_REG_EN1_LDO_ASS 0x45 +#define TPS65910_REG_EN1_SMPS_ASS 0x46 +#define TPS65910_REG_EN2_LDO_ASS 0x47 +#define TPS65910_REG_EN2_SMPS_ASS 0x48 +#define TPS65910_REG_EN3_LDO_ASS 0x49 +#define TPS65910_REG_SPARE 0x4A + +#define TPS65910_REG_INT_STS 0x50 +#define TPS65910_REG_INT_MSK 0x51 +#define TPS65910_REG_INT_STS2 0x52 +#define TPS65910_REG_INT_MSK2 0x53 +#define TPS65910_REG_INT_STS3 0x54 +#define TPS65910_REG_INT_MSK3 0x55 + +#define TPS65910_REG_GPIO0 0x60 + +#define TPS65910_REG_JTAGVERNUM 0x80 + +/* TPS65910 GPIO Specific flags */ +#define TPS65910_GPIO_INT_FALLING 0 +#define TPS65910_GPIO_INT_RISING 1 + +#define TPS65910_DEBOUNCE_91_5_MS 0 +#define TPS65910_DEBOUNCE_150_MS 1 + +#define TPS65910_GPIO_PUDIS (1 << 3) +#define TPS65910_GPIO_CFG_OUTPUT (1 << 2) + + + +/* TPS65910 Interrupt events */ + +/* RTC Driver */ +#define TPS65910_RTC_ALARM_IT 0x80 +#define TPS65910_RTC_PERIOD_IT 0x40 + +/*Core Driver */ +#define TPS65910_HOT_DIE_IT 0x20 +#define TPS65910_PWRHOLD_IT 0x10 +#define TPS65910_PWRON_LP_IT 0x08 +#define TPS65910_PWRON_IT 0x04 +#define TPS65910_VMBHI_IT 0x02 +#define TPS65910_VMBGCH_IT 0x01 + +/* GPIO driver */ +#define TPS65910_GPIO_F_IT 0x02 +#define TPS65910_GPIO_R_IT 0x01 + + +#define TPS65910_VRTC_OFFMASK (1<<3) + +/* Back-up battery charger control */ +#define TPS65910_BBCHEN 0x01 + +/* Back-up battery charger voltage */ +#define TPS65910_BBSEL_3P0 0x00 +#define TPS65910_BBSEL_2P52 0x02 +#define TPS65910_BBSEL_3P15 0x04 +#define TPS65910_BBSEL_VBAT 0x06 + +/* DEVCTRL_REG flags */ +#define TPS65910_RTC_PWDNN 0x40 +#define TPS65910_CK32K_CTRL 0x20 +#define TPS65910_SR_CTL_I2C_SEL 0x10 +#define TPS65910_DEV_OFF_RST 0x08 +#define TPS65910_DEV_ON 0x04 +#define TPS65910_DEV_SLP 0x02 +#define TPS65910_DEV_OFF 0x01 + +/* DEVCTRL2_REG flags */ +#define TPS65910_DEV2_TSLOT_LENGTH 0x30 +#define TPS65910_DEV2_SLEEPSIG_POL 0x08 +#define TPS65910_DEV2_PWON_LP_OFF 0x04 +#define TPS65910_DEV2_PWON_LP_RST 0x02 +#define TPS65910_DEV2_IT_POL 0x01 + +/* TPS65910 SMPS/LDO's */ +#define TPS65910_VIO 0 +#define TPS65910_VDD1 1 +#define TPS65910_VDD2 2 +#define TPS65910_VDD3 3 +/* LDOs */ +#define TPS65910_VDIG1 4 +#define TPS65910_VDIG2 5 +#define TPS65910_VAUX33 6 +#define TPS65910_VMMC 7 +#define TPS65910_VAUX1 8 +#define TPS65910_VAUX2 9 +#define TPS65910_VDAC 10 +#define TPS65910_VPLL 11 +/* Internal LDO */ +#define TPS65910_VRTC 12 + +/* Number of step-down/up converters available */ +#define TPS65910_NUM_DCDC 4 + +/* Number of LDO voltage regulators available */ +#define TPS65910_NUM_LDO 9 + +/* Number of total regulators available */ +#define TPS65910_NUM_REGULATOR (TPS65910_NUM_DCDC + TPS65910_NUM_LDO) + + +/* Regulator Supply state */ +#define SUPPLY_STATE_FLAG 0x03 +/* OFF States */ +#define TPS65910_REG_OFF_00 0x00 +#define TPS65910_REG_OFF_10 0x02 +/* OHP - on High Power */ +#define TPS65910_REG_OHP 0x01 +/* OLP - on Low Power */ +#define TPS65910_REG_OLP 0x03 + +#define TPS65910_MAX_IRQS 10 +#define TPS65910_VMBDCH_IRQ 0 +#define TPS65910_VMBHI_IRQ 1 +#define TPS65910_PWRON_IRQ 2 +#define TPS65910_PWRON_LP_IRQ 3 +#define TPS65910_PWRHOLD_IRQ 4 +#define TPS65910_HOTDIE_IRQ 5 +#define TPS65910_RTC_ALARM_IRQ 6 +#define TPS65910_RTC_PERIOD_IRQ 7 +#define TPS65910_GPIO0_R_IRQ 8 +#define TPS65910_GPIO0_F_IRQ 9 + +/* TPS65910 has 1 GPIO */ +struct tps65910_gpio { + u8 debounce; + u8 pullup_pulldown; + u8 gpio_config; /* Input or output */ + u8 gpio_val; /* Output value */ + int (*gpio_setup)(struct tps65910_gpio *pdata); + int (*gpio_taredown)(struct tps65910_gpio *pdata); +}; + +struct tps65910_platform_data { + + unsigned irq_num; /* TPS65910 to Host IRQ Number */ + struct tps65910_gpio *gpio; + + /* plaform specific data to be initialised in board file */ + struct regulator_init_data *vio; + struct regulator_init_data *vdd1; + struct regulator_init_data *vdd2; + struct regulator_init_data *vdd3; + struct regulator_init_data *vdig1; + struct regulator_init_data *vdig2; + struct regulator_init_data *vaux33; + struct regulator_init_data *vmmc; + struct regulator_init_data *vaux1; + struct regulator_init_data *vaux2; + struct regulator_init_data *vdac; + struct regulator_init_data *vpll; + + void (*handlers[TPS65910_MAX_IRQS]) (void *data); + /* Configure TP65910 to board specific usage*/ + int (*board_tps65910_config)(struct tps65910_platform_data *pdata); +}; + +int tps65910_enable_bbch(u8 voltage); +int tps65910_disable_bbch(void); + +int tps65910_remove_irq_work(int irq); +int tps65910_add_irq_work(int irq, void (*handler)(void *data)); + +int tps65910_i2c_write_u8(u8 slave_addr, u8 val, u8 reg); +int tps65910_i2c_read_u8(u8 slave_addr, u8 *val, u8 reg); + +#endif /* __LINUX_I2C_TPS65910_H */ +