From: Madhusudhan Chikkature <madhu.cr@xxxxxx> ARM: OMAP: Triton Battery Charger Interface This patch provides a OMAP Triton battery charger interface driver to moniter the battery through power class subsystem. Signed-off-by: Madhusudhan Chikkature <madhu.cr@xxxxxx> --- arch/arm/mach-omap2/devices.c | 17 drivers/power/Kconfig | 8 drivers/power/Makefile | 1 drivers/power/twl4030_bci_battery.c | 1144 ++++++++++++++++++++++++++++++++++++ 4 files changed, 1170 insertions(+) Index: linux-omap-2.6/arch/arm/mach-omap2/devices.c =================================================================== --- linux-omap-2.6.orig/arch/arm/mach-omap2/devices.c 2008-06-20 15:39:56.000000000 +0530 +++ linux-omap-2.6/arch/arm/mach-omap2/devices.c 2008-06-20 15:42:05.000000000 +0530 @@ -358,6 +358,22 @@ static inline void omap_hdq_init(void) {} #endif +#ifdef CONFIG_TWL4030_BCI_BATTERY +static struct platform_device omap_bci_battery_device = { + .name = "twl4030-bci-battery", + .id = 1, + .num_resources = 0, + .resource = NULL, +}; + +static inline void omap_bci_battery_init(void) +{ + (void) platform_device_register(&omap_bci_battery_device); +} +#else +static inline void omap_bci_battery_init(void) {} +#endif + /*-------------------------------------------------------------------------*/ static int __init omap2_init_devices(void) @@ -369,6 +385,7 @@ omap_init_mbox(); omap_init_mcspi(); omap_hdq_init(); + omap_bci_battery_init(); omap_init_sti(); omap_init_sha1_md5(); Index: linux-omap-2.6/drivers/power/Kconfig =================================================================== --- linux-omap-2.6.orig/drivers/power/Kconfig 2008-06-20 15:39:56.000000000 +0530 +++ linux-omap-2.6/drivers/power/Kconfig 2008-06-20 15:42:05.000000000 +0530 @@ -70,4 +70,12 @@ help Say Y here to enable support for batteries with BQ27200(I2C) chip. +config TWL4030_BCI_BATTERY + bool "OMAP TWL4030 BCI Battery driver" + depends on (MACH_OMAP_2430SDP || MACH_OMAP_3430SDP) && TWL4030_CORE + default y + help + Support for OMAP TWL4030 BCI Battery driver. + This driver can give support for TWL4030 Battery Charge Interface. + endif # POWER_SUPPLY Index: linux-omap-2.6/drivers/power/Makefile =================================================================== --- linux-omap-2.6.orig/drivers/power/Makefile 2008-06-20 15:39:56.000000000 +0530 +++ linux-omap-2.6/drivers/power/Makefile 2008-06-20 15:42:05.000000000 +0530 @@ -21,3 +21,4 @@ obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o obj-$(CONFIG_BATTERY_BQ27x00) += bq27x00_battery.o +obj-$(CONFIG_TWL4030_BCI_BATTERY) += twl4030_bci_battery.o Index: linux-omap-2.6/drivers/power/twl4030_bci_battery.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-omap-2.6/drivers/power/twl4030_bci_battery.c 2008-06-20 15:42:05.000000000 +0530 @@ -0,0 +1,1144 @@ +/* + * linux/drivers/power/twl4030_bci_battery.c + * + * OMAP2430/3430 BCI battery driver for Linux + * + * Copyright (C) 2008 Texas Instruments, Inc. + * Author: 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. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/i2c/twl4030.h> +#include <linux/power_supply.h> + +#define T2_BATTERY_VOLT 0x04 +#define T2_BATTERY_TEMP 0x06 +#define T2_BATTERY_CUR 0x08 + +/* charger constants */ +#define NO_PW_CONN 0 +#define AC_PW_CONN 0x01 +#define USB_PW_CONN 0x02 + +/* TWL4030_MODULE_USB */ +#define REG_POWER_CTRL 0x0AC +#define OTG_EN 0x020 +#define REG_PHY_CLK_CTRL 0x0FE +#define REG_PHY_CLK_CTRL_STS 0x0FF +#define PHY_DPLL_CLK 0x01 + +#define REG_BCICTL1 0x023 +#define REG_BCICTL2 0x024 +#define CGAIN 0x020 +#define ITHEN 0x010 +#define ITHSENS 0x007 + +/* Boot BCI flag bits */ +#define BCIAUTOWEN 0x020 +#define CONFIG_DONE 0x010 +#define BCIAUTOUSB 0x002 +#define BCIAUTOAC 0x001 +#define BCIMSTAT_MASK 0x03F + +/* Boot BCI register */ +#define REG_BOOT_BCI 0x007 +#define REG_CTRL1 0x00 +#define MADC_ON 0x01 +#define REG_SW1SELECT_MSB 0x07 +#define SW1_CH9_SEL 0x02 +#define REG_CTRL_SW1 0x012 +#define SW1_TRIGGER 0x020 +#define EOC_SW1 0x002 +#define REG_GPCH9 0x049 +#define REG_STS_HW_CONDITIONS 0x0F +#define STS_VBUS 0x080 +#define STS_CHG 0x02 +#define REG_BCIMSTATEC 0x02 +#define REG_BCIMFSTS4 0x010 +#define REG_BCIMFSTS2 0x00E +#define REG_BCIMFSTS3 0x00F +#define REG_BCIMFSTS1 0x001 +#define USBFASTMCHG 0x004 +#define BATSTSPCHG 0x004 +#define BATSTSMCHG 0x040 +#define VBATOV4 0x020 +#define VBATOV3 0x010 +#define VBATOV2 0x008 +#define VBATOV1 0x004 +#define MADC_LSB_MASK 0xC0 +#define REG_BB_CFG 0x012 +#define BBCHEN 0x010 + +/* Power supply charge interrupt */ +#define REG_PWR_ISR1 0x00 +#define REG_PWR_IMR1 0x01 +#define REG_PWR_EDR1 0x05 +#define REG_PWR_SIH_CTRL 0x007 + +#define USB_PRES 0x004 +#define CHG_PRES 0x002 + +#define USB_PRES_RISING 0x020 +#define USB_PRES_FALLING 0x010 +#define CHG_PRES_RISING 0x008 +#define CHG_PRES_FALLING 0x004 +#define AC_STATEC 0x20 +#define COR 0x004 + +/* interrupt status registers */ +#define REG_BCIISR1A 0x0 +#define REG_BCIISR2A 0x01 + +/* Interrupt flags bits BCIISR1 */ +#define BATSTS_ISR1 0x080 +#define VBATLVL_ISR1 0x001 + +/* Interrupt mask registers for int1*/ +#define REG_BCIIMR1A 0x002 +#define REG_BCIIMR2A 0x003 + + /* Interrupt masks for BCIIMR1 */ +#define BATSTS_IMR1 0x080 +#define VBATLVL_IMR1 0x001 + +/* Interrupt edge detection register */ +#define REG_BCIEDR1 0x00A +#define REG_BCIEDR2 0x00B +#define REG_BCIEDR3 0x00C + +/* BCIEDR2 */ +#define BATSTS_EDRRISIN 0x080 +#define BATSTS_EDRFALLING 0x040 + +/* BCIEDR3 */ +#define VBATLVL_EDRRISIN 0x02 + +/* Step size and prescaler ratio */ +#define TEMP_STEP_SIZE 147 +#define TEMP_PSR_R 100 + +#define VOLT_STEP_SIZE 588 +#define VOLT_PSR_R 100 + +#define CURR_STEP_SIZE 147 +#define CURR_PSR_R1 44 +#define CURR_PSR_R2 80 + +#define BK_VOLT_STEP_SIZE 441 +#define BK_VOLT_PSR_R 100 + +#define ENABLE 1 +#define DISABLE 1 + +static int twl4030_bci_battery_probe(struct platform_device *dev); +static int twl4030_bci_battery_remove(struct platform_device *dev); +#ifdef CONFIG_PM +static int twl4030_bci_battery_suspend(struct platform_device *dev, + pm_message_t state); +static int twl4030_bci_battery_resume(struct platform_device *dev); +#endif + +struct twl4030_bci_device_info { + struct device *dev; + + unsigned long update_time; + int voltage_uV; + int bk_voltage_uV; + int current_uA; + int temp_C; + int charge_rsoc; + int charge_status; + + struct power_supply bat; + struct power_supply bk_bat; + struct delayed_work twl4030_bci_monitor_work; + struct delayed_work twl4030_bk_bci_monitor_work; +}; + +static struct platform_driver twl4030_bci_battery_driver = { + .probe = twl4030_bci_battery_probe, + .remove = twl4030_bci_battery_remove, +#ifdef CONFIG_PM + .suspend = twl4030_bci_battery_suspend, + .resume = twl4030_bci_battery_resume, +#endif + .driver = { + .name = "twl4030-bci-battery", + }, +}; + +static int usb_charger_flag; +static int LVL_1, LVL_2, LVL_3, LVL_4; + +static int twl4030madc_sw1_trigger(void); +static int read_bci_val(u8 reg_1); +static inline int clear_n_set(u8 mod_no, u8 clear, u8 set, u8 reg); +static int twl4030charger_presence(void); + +/* + * Twl4030 battery temperature lookup table. + */ +const int twl4030battery_temp_tbl [] = +{ +/* 0 C*/ +27100, +26000, 24900, 23900, 22900, 22000, 21100, 20300, 19400, 18700, 17900, +17200, 16500, 15900, 15300, 14700, 14100, 13600, 13100, 12600, 12100, +11600, 11200, 10800, 10400, 10000, 9630, 9280, 8950, 8620, 8310, +8020, 7730, 7460, 7200, 6950, 6710, 6470, 6250, 6040, 5830, +5640, 5450, 5260, 5090, 4920, 4760, 4600, 4450, 4310, 4170, +4040, 3910, 3790, 3670, 3550 +}; + +/* + * Report and clear the charger presence event. + */ +static inline int twl4030charger_presence_evt(void) +{ + int ret; + u8 chg_sts, set = 0, clear = 0; + + /* read charger power supply status */ + ret = twl4030_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &chg_sts, + REG_STS_HW_CONDITIONS); + if (ret) + return IRQ_NONE; + + /* If the AC charger have been connected */ + if (chg_sts & STS_CHG) { + /* configuring falling edge detection for CHG_PRES */ + set = CHG_PRES_FALLING; + clear = CHG_PRES_RISING; + } + /* If the AC charger have been disconnected */ + else { + /* configuring rising edge detection for CHG_PRES */ + set = CHG_PRES_RISING; + clear = CHG_PRES_FALLING; + } + + /* Update the interrupt edge detection register */ + clear_n_set(TWL4030_MODULE_INT, clear, set, REG_PWR_EDR1); + + return 0; +} + +/* + * Interrupt service routine + * + * Attends to TWL 4030 power module interruptions events, specifically + * USB_PRES (USB charger presence) CHG_PRES (AC charger presence) events + * + */ +static irqreturn_t twl4030charger_interrupt(int irq, void *dev_id) +{ + struct twl4030_bci_device_info *di = + (struct twl4030_bci_device_info *)dev_id; + + twl4030charger_presence_evt(); + power_supply_changed(&di->bat); + + return IRQ_HANDLED; +} + +/* + * This function handles the twl4030 battery presence interrupt + */ +static int twl4030battery_presence_evt(void) +{ + int ret; + u8 batstsmchg, batstspchg; + + /* check for the battery presence in main charge*/ + ret = twl4030_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, + &batstsmchg, REG_BCIMFSTS3); + if (ret) + return ret; + + /* check for the battery presence in precharge */ + ret = twl4030_i2c_read_u8(TWL4030_MODULE_PRECHARGE, + &batstspchg, REG_BCIMFSTS1); + if (ret) + return ret; + + /* + * REVISIT: Physically inserting/removing the batt + * does not seem to generate an int on 3430ES2 SDP. + */ + + /* In case of the battery insertion event */ + if ((batstspchg & BATSTSPCHG) || (batstsmchg & BATSTSMCHG)) { + ret = clear_n_set(TWL4030_MODULE_INTERRUPTS, BATSTS_EDRRISIN, + BATSTS_EDRFALLING, REG_BCIEDR2); + if (ret) + return ret; + } + + /* In case of the battery removal event */ + else { + ret = clear_n_set(TWL4030_MODULE_INTERRUPTS, BATSTS_EDRFALLING, + BATSTS_EDRRISIN, REG_BCIEDR2); + if (ret) + return ret; + } + + return 0; +} + +/* + * This function handles the twl4030 battery voltage level interrupt. + */ +static int twl4030battery_level_evt(void) +{ + int ret; + u8 mfst; + + /* checking for threshold event */ + ret = twl4030_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, + &mfst, REG_BCIMFSTS2); + if (ret) + return ret; + + if (mfst & VBATOV4) { + LVL_4 = 1; + LVL_3 = LVL_2 = LVL_1 = 0; + } else if (mfst & VBATOV3) { + LVL_3 = 1; + LVL_4 = LVL_2 = LVL_1 = 0; + } else if (mfst & VBATOV2) { + LVL_2 = 1; + LVL_4 = LVL_3 = LVL_1 = 0; + } else { + LVL_1 = 1; + LVL_4 = LVL_3 = LVL_2 = 0; + } + + return 0; +} + +/* + * Interrupt service routine + * + * Attends to BCI interruptions events, + * specifically BATSTS (battery connection and removal) + * VBATOV (main battery voltage threshold) events + * + */ +static irqreturn_t twl4030battery_interrupt(int irq, void *dev_id) +{ + int ret; + u8 isr1a_val, isr2a_val, clear_2a, clear_1a; + + ret = twl4030_i2c_read_u8(TWL4030_MODULE_INTERRUPTS, &isr1a_val, + REG_BCIISR1A); + if (ret) + return IRQ_NONE; + + ret = twl4030_i2c_read_u8(TWL4030_MODULE_INTERRUPTS, &isr2a_val, + REG_BCIISR2A); + if (ret) + return IRQ_NONE; + + clear_2a = (isr2a_val & VBATLVL_ISR1)? (VBATLVL_ISR1): 0; + clear_1a = (isr1a_val & BATSTS_ISR1)? (BATSTS_ISR1): 0; + + /* cleaning BCI interrupt status flags */ + ret = twl4030_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, + clear_1a , REG_BCIISR1A); + if (ret) + return IRQ_NONE; + + ret = twl4030_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, + clear_2a , REG_BCIISR2A); + if (ret) + return IRQ_NONE; + + /* battery connetion or removal event */ + if (isr1a_val & BATSTS_ISR1) + twl4030battery_presence_evt(); + /* battery voltage threshold event*/ + else if (isr2a_val & VBATLVL_ISR1) + twl4030battery_level_evt(); + else + return IRQ_NONE; + + return IRQ_HANDLED; +} + +/* + * Enable/Disable hardware battery level event notifications. + */ +static int twl4030battery_hw_level_en(int enable) +{ + int ret; + + if (enable) { + /* unmask VBATOV interrupt for INT1 */ + ret = clear_n_set(TWL4030_MODULE_INTERRUPTS, VBATLVL_IMR1, + 0, REG_BCIIMR2A); + if (ret) + return ret; + + /* configuring interrupt edge detection for VBATOv */ + ret = clear_n_set(TWL4030_MODULE_INTERRUPTS, 0, + VBATLVL_EDRRISIN, REG_BCIEDR3); + if (ret) + return ret; + } else { + /* mask VBATOV interrupt for INT1 */ + ret = clear_n_set(TWL4030_MODULE_INTERRUPTS, 0, + VBATLVL_IMR1, REG_BCIIMR2A); + if (ret) + return ret; + } + + return 0; +} + +/* + * Enable/disable hardware battery presence event notifications. + */ +static int twl4030battery_hw_presence_en(int enable) +{ + int ret; + + if (enable) { + /* unmask BATSTS interrupt for INT1 */ + ret = clear_n_set(TWL4030_MODULE_INTERRUPTS, BATSTS_IMR1, + 0, REG_BCIIMR1A); + if (ret) + return ret; + + /* configuring interrupt edge for BATSTS */ + ret = clear_n_set(TWL4030_MODULE_INTERRUPTS, 0, + BATSTS_EDRRISIN | BATSTS_EDRFALLING, REG_BCIEDR2); + if (ret) + return ret; + } else { + /* mask BATSTS interrupt for INT1 */ + ret = clear_n_set(TWL4030_MODULE_INTERRUPTS, 0, + BATSTS_IMR1, REG_BCIIMR1A); + if (ret) + return ret; + } + + return 0; +} + +/* + * Enable/Disable AC Charge funtionality. + */ +static int twl4030charger_ac_en(int enable) +{ + int ret; + + if (enable) { + /* forcing the field BCIAUTOAC (BOOT_BCI[0) to 1 */ + ret = clear_n_set(TWL4030_MODULE_PM_MASTER, 0, + (CONFIG_DONE | BCIAUTOWEN | BCIAUTOAC), + REG_BOOT_BCI); + if (ret) + return ret; + } else { + /* forcing the field BCIAUTOAC (BOOT_BCI[0) to 0*/ + ret = clear_n_set(TWL4030_MODULE_PM_MASTER, BCIAUTOAC, + (CONFIG_DONE | BCIAUTOWEN), + REG_BOOT_BCI); + if (ret) + return ret; + } + return 0; +} + +/* + * Enable/Disable USB Charge funtionality. + */ +int twl4030charger_usb_en(int enable) +{ + u8 value; + int ret; + unsigned long timeout; + + if (enable) { + /* Check for USB charger conneted */ + ret = twl4030charger_presence(); + if (ret < 0) + return ret; + + if (!(ret & USB_PW_CONN)) + return -ENXIO; + + /* forcing the field BCIAUTOUSB (BOOT_BCI[1]) to 1 */ + ret = clear_n_set(TWL4030_MODULE_PM_MASTER, 0, + (CONFIG_DONE | BCIAUTOWEN | BCIAUTOUSB), + REG_BOOT_BCI); + if (ret) + return ret; + + ret = clear_n_set(TWL4030_MODULE_USB, 0, PHY_DPLL_CLK, + REG_PHY_CLK_CTRL); + if (ret) + return ret; + + value = 0; + timeout = jiffies + msecs_to_jiffies(50); + + while ((!(value & PHY_DPLL_CLK)) && + time_before(jiffies, timeout)) { + udelay(10); + ret = twl4030_i2c_read_u8(TWL4030_MODULE_USB, &value, + REG_PHY_CLK_CTRL_STS); + if (ret) + return ret; + } + + /* OTG_EN (POWER_CTRL[5]) to 1 */ + ret = clear_n_set(TWL4030_MODULE_USB, 0, OTG_EN, + REG_POWER_CTRL); + if (ret) + return ret; + + mdelay(50); + + /* forcing USBFASTMCHG(BCIMFSTS4[2]) to 1 */ + ret = clear_n_set(TWL4030_MODULE_MAIN_CHARGE, 0, + USBFASTMCHG, REG_BCIMFSTS4); + if (ret) + return ret; + } else { + twl4030charger_presence(); + ret = clear_n_set(TWL4030_MODULE_PM_MASTER, BCIAUTOUSB, + (CONFIG_DONE | BCIAUTOWEN), REG_BOOT_BCI); + if (ret) + return ret; + } + + return 0; +} + +/* + * Return battery temperature + * Or < 0 on failure. + */ +static int twl4030battery_temperature(void) +{ + u8 val; + int temp, curr, volt, res, ret; + + /* Getting and calculating the thermistor voltage */ + ret = read_bci_val(T2_BATTERY_TEMP); + if (ret < 0) + return ret; + + volt = (ret * TEMP_STEP_SIZE) / TEMP_PSR_R; + + /* Getting and calculating the supply current in micro ampers */ + ret = twl4030_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, &val, + REG_BCICTL2); + if (ret) + return 0; + + curr = ((val & ITHSENS) + 1) * 10; + + /* Getting and calculating the thermistor resistance in ohms*/ + res = volt * 1000 / curr; + + /*calculating temperature*/ + for (temp = 55; temp >= 0; temp--) { + int actual = twl4030battery_temp_tbl [temp]; + if ((actual - res) >= 0) + break; + } + + return temp + 1; +} + +/* + * Return battery voltage + * Or < 0 on failure. + */ +static int twl4030battery_voltage(void) +{ + int volt = read_bci_val(T2_BATTERY_VOLT); + + return (volt * VOLT_STEP_SIZE) / VOLT_PSR_R; +} + +/* + * Return the battery current + * Or < 0 on failure. + */ +static int twl4030battery_current(void) +{ + int ret, curr = read_bci_val(T2_BATTERY_CUR); + u8 val; + + ret = twl4030_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, &val, + REG_BCICTL1); + if (ret) + return ret; + + if (val & CGAIN) /* slope of 0.44 mV/mA */ + return (curr * CURR_STEP_SIZE) / CURR_PSR_R1; + else /* slope of 0.88 mV/mA */ + return (curr * CURR_STEP_SIZE) / CURR_PSR_R2; +} + +/* + * Return the battery backup voltage + * Or < 0 on failure. + */ +static int twl4030backupbatt_voltage(void) +{ + int ret, temp; + u8 volt; + + /* trigger MADC convertion */ + twl4030madc_sw1_trigger(); + + ret = twl4030_i2c_read_u8(TWL4030_MODULE_MADC, &volt, + REG_GPCH9 + 1); + if (ret) + return ret; + + temp = ((int) volt) << 2; + + ret = twl4030_i2c_read_u8(TWL4030_MODULE_MADC, &volt, + REG_GPCH9); + if (ret) + return ret; + + temp = temp + ((int) ((volt & MADC_LSB_MASK) >> 6)); + + return (temp * BK_VOLT_STEP_SIZE) / BK_VOLT_PSR_R; +} + +/* + * Returns an integer value, that means, + * NO_PW_CONN no power supply is connected + * AC_PW_CONN if the AC power supply is connected + * USB_PW_CONN if the USB power supply is connected + * AC_PW_CONN + USB_PW_CONN if USB and AC power supplies are both connected + * + * Or < 0 on failure. + */ +static int twl4030charger_presence(void) +{ + int ret; + u8 hwsts; + + ret = twl4030_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &hwsts, + REG_STS_HW_CONDITIONS); + if (ret) { + pr_err("BATTERY DRIVER: error reading STS_HW_CONDITIONS \n"); + return ret; + } + + ret = (hwsts & STS_CHG)? AC_PW_CONN: NO_PW_CONN; + ret += (hwsts & STS_VBUS)? USB_PW_CONN: NO_PW_CONN; + + if (ret & USB_PW_CONN) + usb_charger_flag = 1; + else + usb_charger_flag = 0; + + return ret; + +} + +/* + * Returns the main charge FSM status + * Or < 0 on failure. + */ +static int twl4030bci_status(void) +{ + int ret; + u8 status; + + ret = twl4030_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, + &status, REG_BCIMSTATEC); + if (ret) { + pr_err("BATTERY DRIVER: error reading BCIMSTATEC \n"); + return ret; + } + + return (int) (status & BCIMSTAT_MASK); +} + +static int read_bci_val(u8 reg) +{ + int ret, temp; + u8 val; + + /* reading MSB */ + ret = twl4030_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, &val, + reg + 1); + if (ret) + return ret; + + temp = ((int)(val & 0x03)) << 8; + + /* reading LSB */ + ret = twl4030_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, &val, + reg); + if (ret) + return ret; + + return temp | val; +} + +/* + * Triggers the sw1 request for the twl4030 module to measure the sw1 selected + * channels + */ +static int twl4030madc_sw1_trigger(void) +{ + u8 val; + int ret; + + /* Triggering SW1 MADC convertion */ + ret = twl4030_i2c_read_u8(TWL4030_MODULE_MADC, &val, + REG_CTRL_SW1); + if (ret) + return ret; + + val |= SW1_TRIGGER; + + ret = twl4030_i2c_write_u8(TWL4030_MODULE_MADC, val, + REG_CTRL_SW1); + if (ret) + return ret; + + /* Waiting until the SW1 conversion ends*/ + val = 0; + + while (!(val & EOC_SW1)) { + ret = twl4030_i2c_read_u8(TWL4030_MODULE_MADC, &val, + REG_CTRL_SW1); + if (ret) + return ret; + mdelay(10); + } + + return 0; +} + +/* + * Settup the twl4030 MADC module to measure the backup + * battery voltage. + */ +static int twl4030backupbatt_voltage_setup(void) +{ + int ret; + + /* turning adc_on */ + ret = twl4030_i2c_write_u8(TWL4030_MODULE_MADC, MADC_ON, + REG_CTRL1); + if (ret) + return ret; + + /*setting MDC channel 9 to trigger by SW1*/ + ret = clear_n_set(TWL4030_MODULE_MADC, 0, SW1_CH9_SEL, + REG_SW1SELECT_MSB); + if (ret) + return ret; + + /* Starting backup batery charge */ + ret = clear_n_set(TWL4030_MODULE_PM_RECEIVER, 0, BBCHEN, + REG_BB_CFG); + if (ret) + return ret; + + return 0; +} + +/* + * Settup the twl4030 BCI and MADC module to measure battery + * temperature + */ +static int twl4030battery_temp_setup(void) +{ + int ret; + + /* Enabling thermistor current */ + ret = clear_n_set(TWL4030_MODULE_MAIN_CHARGE, 0, ITHEN, + REG_BCICTL1); + if (ret) + return ret; + + return 0; +} + +/* + * Sets and clears bits on an given register on a given module + */ +static inline int clear_n_set(u8 mod_no, u8 clear, u8 set, u8 reg) +{ + int ret; + u8 val = 0; + + /* Gets the initial register value */ + ret = twl4030_i2c_read_u8(mod_no, &val, reg); + if (ret) + return ret; + + /* Clearing all those bits to clear */ + val &= ~(clear); + + /* Setting all those bits to set */ + val |= set; + + /* Update the register */ + ret = twl4030_i2c_write_u8(mod_no, val, reg); + if (ret) + return ret; + + return 0; +} + +static enum power_supply_property twl4030_bci_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TEMP, +}; + +static enum power_supply_property twl4030_bk_bci_battery_props[] = { + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + +static void +twl4030_bk_bci_battery_read_status(struct twl4030_bci_device_info *di) +{ + di->bk_voltage_uV = twl4030backupbatt_voltage(); + return; +} + +static void twl4030_bk_bci_battery_work(struct delayed_work *work) +{ + struct twl4030_bci_device_info *di = container_of(work, + struct twl4030_bci_device_info, twl4030_bk_bci_monitor_work); + + twl4030_bk_bci_battery_read_status(di); + schedule_delayed_work(&di->twl4030_bk_bci_monitor_work, 500); + return; +} + +static void twl4030_bci_battery_read_status(struct twl4030_bci_device_info *di) +{ + di->temp_C = twl4030battery_temperature(); + di->voltage_uV = twl4030battery_voltage(); + di->current_uA = twl4030battery_current(); + + return; +} + +static void +twl4030_bci_battery_update_status(struct twl4030_bci_device_info *di) +{ + twl4030_bci_battery_read_status(di); + di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN; + + if (power_supply_am_i_supplied(&di->bat)) + di->charge_status = POWER_SUPPLY_STATUS_CHARGING; + else + di->charge_status = POWER_SUPPLY_STATUS_DISCHARGING; + + return; +} + +static void twl4030_bci_battery_work(struct delayed_work *work) +{ + struct twl4030_bci_device_info *di = container_of(work, + struct twl4030_bci_device_info, twl4030_bci_monitor_work); + + twl4030_bci_battery_update_status(di); + schedule_delayed_work(&di->twl4030_bci_monitor_work, 100); + return; +} + + +#define to_twl4030_bci_device_info(x) container_of((x), \ + struct twl4030_bci_device_info, bat); + +static void twl4030_bci_battery_external_power_changed(struct power_supply *psy) +{ + struct twl4030_bci_device_info *di = to_twl4030_bci_device_info(psy); + + cancel_delayed_work(&di->twl4030_bci_monitor_work); + schedule_delayed_work(&di->twl4030_bci_monitor_work, 0); +} + +#define to_twl4030_bk_bci_device_info(x) container_of((x), \ + struct twl4030_bci_device_info, bk_bat); + +static int twl4030_bk_bci_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct twl4030_bci_device_info *di = to_twl4030_bk_bci_device_info(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = di->bk_voltage_uV; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int twl4030_bci_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct twl4030_bci_device_info *di = to_twl4030_bci_device_info(psy); + int status = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = di->charge_status; + return 0; + default: + break; + } + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = di->voltage_uV; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = di->current_uA; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = di->temp_C; + break; + case POWER_SUPPLY_PROP_ONLINE: + status = twl4030bci_status(); + if ((status & AC_STATEC) == AC_STATEC) + val->intval = POWER_SUPPLY_TYPE_MAINS; + else if (usb_charger_flag) + val->intval = POWER_SUPPLY_TYPE_USB; + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_CAPACITY: + /* + * need to get the correct percentage value per the + * battery characteristics. Approx values for now. + */ + if (di->voltage_uV < 2894 || LVL_1) { + val->intval = 5; + LVL_1 = 0; + } else if ((di->voltage_uV < 3451 && di->voltage_uV > 2894) + || LVL_2) { + val->intval = 20; + LVL_2 = 0; + } else if ((di->voltage_uV < 3902 && di->voltage_uV > 3451) + || LVL_3) { + val->intval = 50; + LVL_3 = 0; + } else if ((di->voltage_uV < 3949 && di->voltage_uV > 3902) + || LVL_4) { + val->intval = 75; + LVL_4 = 0; + } else if (di->voltage_uV > 3949) + val->intval = 90; + break; + default: + return -EINVAL; + } + return 0; +} + +static char *twl4030_bci_supplied_to[] = { + "twl4030_bci_battery", +}; + +static int twl4030_bci_battery_probe(struct platform_device *dev) +{ + struct twl4030_bci_device_info *di; + int ret; + + di = kzalloc(sizeof(*di), GFP_KERNEL); + if (!di) + return -ENOMEM; + + platform_set_drvdata(dev, di); + + di->dev = &dev->dev; + di->bat.name = "twl4030_bci_battery"; + di->bat.supplied_to = twl4030_bci_supplied_to; + di->bat.num_supplicants = ARRAY_SIZE(twl4030_bci_supplied_to); + di->bat.type = POWER_SUPPLY_TYPE_BATTERY; + di->bat.properties = twl4030_bci_battery_props; + di->bat.num_properties = ARRAY_SIZE(twl4030_bci_battery_props); + di->bat.get_property = twl4030_bci_battery_get_property; + di->bat.external_power_changed = + twl4030_bci_battery_external_power_changed; + + di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN; + + di->bk_bat.name = "twl4030_bci_bk_battery"; + di->bk_bat.type = POWER_SUPPLY_TYPE_BATTERY; + di->bk_bat.properties = twl4030_bk_bci_battery_props; + di->bk_bat.num_properties = ARRAY_SIZE(twl4030_bk_bci_battery_props); + di->bk_bat.get_property = twl4030_bk_bci_battery_get_property; + di->bk_bat.external_power_changed = NULL; + + twl4030charger_ac_en(ENABLE); + twl4030charger_usb_en(ENABLE); + twl4030battery_hw_level_en(ENABLE); + twl4030battery_hw_presence_en(ENABLE); + + /* settings for temperature sensing */ + ret = twl4030battery_temp_setup(); + if (ret) + goto temp_setup_fail; + + /* enabling GPCH09 for read back battery voltage */ + ret = twl4030backupbatt_voltage_setup(); + if (ret) + goto voltage_setup_fail; + + /* request BCI interruption */ + ret = request_irq(TWL4030_MODIRQ_BCI, twl4030battery_interrupt, + IRQF_DISABLED, dev->name, NULL); + if (ret) { + pr_err("BATTERY DRIVER: (BCI) IRQ%d is not free.\n", + TWL4030_MODIRQ_PWR); + goto batt_irq_fail; + } + + /* request Power interruption */ + ret = request_irq(TWL4030_PWRIRQ_CHG_PRES, twl4030charger_interrupt, + 0, dev->name, di); + + if (ret) { + pr_err("BATTERY DRIVER: (POWER) IRQ%d is not free.\n", + TWL4030_MODIRQ_PWR); + goto chg_irq_fail; + } + + ret = power_supply_register(&dev->dev, &di->bat); + if (ret) { + pr_err("BATTERY DRIVER: failed to register main battery\n"); + goto batt_failed; + } + + INIT_DELAYED_WORK(&di->twl4030_bci_monitor_work, + twl4030_bci_battery_work); + schedule_delayed_work(&di->twl4030_bci_monitor_work, 0); + + ret = power_supply_register(&dev->dev, &di->bk_bat); + if (ret) { + pr_err("BATTERY DRIVER: failed to register backup battery\n"); + goto bk_batt_failed; + } + + INIT_DELAYED_WORK(&di->twl4030_bk_bci_monitor_work, + twl4030_bk_bci_battery_work); + schedule_delayed_work(&di->twl4030_bk_bci_monitor_work, 500); + + return 0; + +bk_batt_failed: + power_supply_unregister(&di->bat); +batt_failed: + free_irq(TWL4030_MODIRQ_PWR, di); +chg_irq_fail: +prev_setup_err: + free_irq(TWL4030_MODIRQ_BCI, NULL); +batt_irq_fail: +voltage_setup_fail: +temp_setup_fail: + twl4030charger_ac_en(DISABLE); + twl4030charger_usb_en(DISABLE); + twl4030battery_hw_level_en(DISABLE); + twl4030battery_hw_presence_en(DISABLE); + kfree(di); + + return ret; +} + +static int twl4030_bci_battery_remove(struct platform_device *dev) +{ + struct twl4030_bci_device_info *di = platform_get_drvdata(dev); + + twl4030charger_ac_en(DISABLE); + twl4030charger_usb_en(DISABLE); + twl4030battery_hw_level_en(DISABLE); + twl4030battery_hw_presence_en(DISABLE); + + free_irq(TWL4030_MODIRQ_BCI, NULL); + free_irq(TWL4030_MODIRQ_PWR, di); + + flush_scheduled_work(); + power_supply_unregister(&di->bat); + power_supply_unregister(&di->bk_bat); + platform_set_drvdata(dev, NULL); + kfree(di); + + return 0; +} + +#ifdef CONFIG_PM +static int twl4030_bci_battery_suspend(struct platform_device *dev, + pm_message_t state) +{ + struct twl4030_bci_device_info *di = platform_get_drvdata(dev); + + di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN; + cancel_delayed_work(&di->twl4030_bci_monitor_work); + cancel_delayed_work(&di->twl4030_bk_bci_monitor_work); + return 0; +} + +static int twl4030_bci_battery_resume(struct platform_device *dev) +{ + struct twl4030_bci_device_info *di = platform_get_drvdata(dev); + + schedule_delayed_work(&di->twl4030_bci_monitor_work, 0); + schedule_delayed_work(&di->twl4030_bk_bci_monitor_work, 50); + return 0; +} +#endif /* CONFIG_PM */ + +/* + * Battery driver module initializer function + * registers battery driver structure + */ +static int __init twl4030_battery_init(void) +{ + return platform_driver_register(&twl4030_bci_battery_driver); + +} + +/* + * Battery driver module exit function + * unregister battery driver structure + */ +static void __exit twl4030_battery_exit(void) +{ + platform_driver_unregister(&twl4030_bci_battery_driver); +} + +module_init(twl4030_battery_init); +module_exit(twl4030_battery_exit); +MODULE_LICENSE("GPL"); -- To unsubscribe from this list: send the line "unsubscribe linux-omap" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html