From c3a08f3d696866508ef2b5e2fd065b8295b3e1a8 Mon Sep 17 00:00:00 2001 From: Tim Yamin Date: Sun, 9 May 2010 10:14:23 +0200 Subject: [PATCH 15/17] Forward port TWL4030 BCI driver from 2.6.29 to 2.6.31 with AI enhancements. Signed-off-by: Tim Yamin --- drivers/power/Kconfig | 7 + drivers/power/Makefile | 1 + drivers/power/twl4030_bci_battery.c | 1307 +++++++++++++++++++++++++++++++++++ include/linux/i2c/twl.h | 1 + 4 files changed, 1316 insertions(+), 0 deletions(-) create mode 100644 drivers/power/twl4030_bci_battery.c diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index d4b3d67..8345b3f 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -124,4 +124,11 @@ config CHARGER_PCF50633 help Say Y to include support for NXP PCF50633 Main Battery Charger. +config TWL4030_BCI_BATTERY + tristate "OMAP TWL4030 BCI Battery driver" + depends on TWL4030_CORE && TWL4030_MADC + help + Support for OMAP TWL4030 BCI Battery driver. + This driver can give support for TWL4030 Battery Charge Interface. + endif # POWER_SUPPLY diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 573597c..7801da7 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -31,3 +31,4 @@ obj-$(CONFIG_BATTERY_BQ27x00) += bq27x00_battery.o obj-$(CONFIG_BATTERY_DA9030) += da9030_battery.o obj-$(CONFIG_BATTERY_MAX17040) += max17040_battery.o obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o +obj-$(CONFIG_TWL4030_BCI_BATTERY) += twl4030_bci_battery.o diff --git a/drivers/power/twl4030_bci_battery.c b/drivers/power/twl4030_bci_battery.c new file mode 100644 index 0000000..0876fc3 --- /dev/null +++ b/drivers/power/twl4030_bci_battery.c @@ -0,0 +1,1307 @@ +/* + * linux/drivers/power/twl4030_bci_battery.c + * + * OMAP2430/3430 BCI battery driver for Linux + * + * Copyright (C) 2008 Texas Instruments, Inc. + * Author: Texas Instruments, Inc. + * + * Copyright (C) 2010 Always Innovating + * Author: Tim Yamin + * + * 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. + */ + +/* Boot with automatic charge */ +#define CHARGE_MODE 1 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 CVENAC 0x004 +#define BCIAUTOUSB 0x002 +#define BCIAUTOAC 0x001 +#define BCIMSTAT_MASK 0x03F + +/* Boot BCI register */ +#define REG_BOOT_BCI 0x007 +#define REG_CTRL1 0x00 +#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 REG_BB_CFG 0x012 +#define BBCHEN 0x010 + +/* GPBR */ +#define REG_GPBR1 0x0c +#define MADC_HFCLK_EN 0x80 +#define DEFAULT_MADC_CLK_EN 0x10 + +/* 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 + +/* BCIIREF1 */ +#define REG_BCIIREF1 0x027 +#define REG_BCIIREF2 0x028 + +/* BCIMFTH1 */ +#define REG_BCIMFTH1 0x016 + +/* Key */ +#define KEY_IIREF 0xE7 +#define KEY_FTH1 0xD2 +#define REG_BCIMFKEY 0x011 + +/* 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 + +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; + + struct twl4030_bci_platform_data *pdata; +}; + +static int usb_charger_flag; +static int LVL_1, LVL_2, LVL_3, LVL_4; + +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); + +/* + * 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 = twl_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &chg_sts, + REG_STS_HW_CONDITIONS); + if (ret) + return IRQ_NONE; + + if (chg_sts & STS_CHG) { /* If the AC charger have been connected */ + /* configuring falling edge detection for CHG_PRES */ + set = CHG_PRES_FALLING; + clear = CHG_PRES_RISING; + } else { /* If the AC charger have been disconnected */ + /* 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 *_di) +{ + struct twl4030_bci_device_info *di = _di; + +#ifdef CONFIG_LOCKDEP + /* WORKAROUND for lockdep forcing IRQF_DISABLED on us, which + * we don't want and can't tolerate. Although it might be + * friendlier not to borrow this thread context... + */ + local_irq_enable(); +#endif + + 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 = twl_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, + &batstsmchg, REG_BCIMFSTS3); + if (ret) + return ret; + + /* check for the battery presence in precharge */ + ret = twl_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. + */ + if ((batstspchg & BATSTSPCHG) || (batstsmchg & BATSTSMCHG)) { + /* In case of the battery insertion event */ + ret = clear_n_set(TWL4030_MODULE_INTERRUPTS, BATSTS_EDRRISIN, + BATSTS_EDRFALLING, REG_BCIEDR2); + if (ret) + return ret; + } else { + /* In case of the battery removal event */ + 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 = twl_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, + &mfst, REG_BCIMFSTS2); + if (ret) + return ret; + + /* REVISIT could use a bitmap */ + if (mfst & VBATOV4) { + LVL_4 = 1; + LVL_3 = 0; + LVL_2 = 0; + LVL_1 = 0; + } else if (mfst & VBATOV3) { + LVL_4 = 0; + LVL_3 = 1; + LVL_2 = 0; + LVL_1 = 0; + } else if (mfst & VBATOV2) { + LVL_4 = 0; + LVL_3 = 0; + LVL_2 = 1; + LVL_1 = 0; + } else { + LVL_4 = 0; + LVL_3 = 0; + LVL_2 = 0; + LVL_1 = 1; + } + + 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 *_di) +{ + u8 isr1a_val, isr2a_val, clear_2a, clear_1a; + int ret; + +#ifdef CONFIG_LOCKDEP + /* WORKAROUND for lockdep forcing IRQF_DISABLED on us, which + * we don't want and can't tolerate. Although it might be + * friendlier not to borrow this thread context... + */ + local_irq_enable(); +#endif + + ret = twl_i2c_read_u8(TWL4030_MODULE_INTERRUPTS, &isr1a_val, + REG_BCIISR1A); + if (ret) + return IRQ_NONE; + + ret = twl_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 = twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, + clear_1a , REG_BCIISR1A); + if (ret) + return IRQ_NONE; + + ret = twl_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 automatic) +{ + int ret; + + if (enable) { + /* forcing the field BCIAUTOAC (BOOT_BCI[0) to 1 */ + if(!automatic) { + ret = clear_n_set(TWL4030_MODULE_PM_MASTER, BCIAUTOAC | CVENAC, + (CONFIG_DONE | BCIAUTOWEN), + REG_BOOT_BCI); + } else { + ret = clear_n_set(TWL4030_MODULE_PM_MASTER, 0, + (CONFIG_DONE | BCIAUTOWEN | BCIAUTOAC | CVENAC), + 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 = twl_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(struct twl4030_bci_device_info *di) +{ + u8 val; + int temp, curr, volt, res, ret; + + /* Is a temperature table specified? */ + if (!di->pdata->tblsize) + return 0; + + /* 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 = twl_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 = 58; temp >= 0; temp--) { + int actual = di->pdata->battery_tmp_tbl[temp]; + if ((actual - res) >= 0) + break; + } + + /* Negative temperature */ + if (temp < 3) { + if (temp == 2) + temp = -1; + else if (temp == 1) + temp = -2; + else + temp = -3; + } + + 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; +} + +/* + * Get latest battery voltage (using MADC) + * + * When the BCI is not charging, the BCI voltage registers are not + * updated and are 'frozen' but the data can be read through the + * MADC. + */ +static int twl4030battery_voltage_madc(void) +{ + struct twl4030_madc_request req; + + req.channels = (1 << 12); + req.do_avg = 0; + req.method = TWL4030_MADC_SW1; + req.active = 0; + req.func_cb = NULL; + twl4030_madc_conversion(&req); + + return (((int) req.rbuf[12]) * 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 = twl_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) +{ + struct twl4030_madc_request req; + int temp; + + req.channels = (1 << 9); + req.do_avg = 0; + req.method = TWL4030_MADC_SW1; + req.active = 0; + req.func_cb = NULL; + twl4030_madc_conversion(&req); + temp = (u16)req.rbuf[9]; + + 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 = twl_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &hwsts, + REG_STS_HW_CONDITIONS); + if (ret) { + pr_err("twl4030_bci: 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 = twl_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, + &status, REG_BCIMSTATEC); + if (ret) { + pr_err("twl4030_bci: error reading BCIMSTATEC\n"); + return ret; + } + +#ifdef DEBUG + printk("BCI DEBUG: BCIMSTATEC Charge state is 0x%x\n", status); +#endif + return (int) (status & BCIMSTAT_MASK); +} + +static int read_bci_val(u8 reg) +{ + int ret, temp; + u8 val; + + /* reading MSB */ + ret = twl_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, &val, + reg + 1); + if (ret) + return ret; + + temp = ((int)(val & 0x03)) << 8; + + /* reading LSB */ + ret = twl_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, &val, + reg); + if (ret) + return ret; + + return temp | val; +} + +/* + * Settup the twl4030 BCI module to enable backup + * battery charging. + */ +static int twl4030backupbatt_voltage_setup(void) +{ + int 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 module to measure battery + * temperature + */ +static int twl4030battery_temp_setup(void) +{ +#ifdef DEBUG + u8 i; +#endif + u8 ret; + + /* Enabling thermistor current */ + ret = clear_n_set(TWL4030_MODULE_MAIN_CHARGE, 0, 0x1B, + REG_BCICTL1); + if (ret) + return ret; + +#ifdef DEBUG + twl_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &ret, REG_BOOT_BCI); + printk("BCI DEBUG: BOOT_BCI Value is 0x%x\n", ret); + + twl_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &ret, REG_STS_HW_CONDITIONS); + printk("BCI DEBUG: STS_HW_CONDITIONS Value is 0x%x\n", ret); + + twl_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, &ret, REG_BCICTL1); + printk("BCI DEBUG: BCICTL1 Value is 0x%x\n", ret); + + twl_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, &ret, REG_BCICTL2); + printk("BCI DEBUG: BCICTL2 Value is 0x%x\n", ret); + + twl_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, &ret, 0x0); + printk("BCI DEBUG: BCIMDEN Value is 0x%x\n", ret); + + twl_i2c_read_u8(TWL4030_MODULE_INTBR, &ret, REG_GPBR1); + printk("BCI DEBUG: GPBR1 Value is 0x%x\n", ret); + + for(i = 0x0; i <= 0x32; i++) + { + twl_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, &ret, i); + printk("BCI DEBUG: BCI 0x%x Value is 0x%x\n", i, ret); + } +#endif + + 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 = twl_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 = twl_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(); +} + +static void twl4030_bk_bci_battery_work(struct work_struct *work) +{ + struct twl4030_bci_device_info *di = container_of(work, + struct twl4030_bci_device_info, + twl4030_bk_bci_monitor_work.work); + + if(!di->pdata->no_backup_battery) + twl4030_bk_bci_battery_read_status(di); + schedule_delayed_work(&di->twl4030_bk_bci_monitor_work, 500); +} + +static void twl4030_bci_battery_read_status(struct twl4030_bci_device_info *di) +{ + if(di->charge_status != POWER_SUPPLY_STATUS_DISCHARGING) { + di->temp_C = twl4030battery_temperature(di); + di->voltage_uV = twl4030battery_voltage(); + di->current_uA = twl4030battery_current(); + } +} + +static void +twl4030_bci_battery_update_status(struct twl4030_bci_device_info *di) +{ + if (power_supply_am_i_supplied(&di->bat)) + di->charge_status = POWER_SUPPLY_STATUS_CHARGING; + else + di->charge_status = POWER_SUPPLY_STATUS_DISCHARGING; + twl4030_bci_battery_read_status(di); +} + +static void twl4030_bci_battery_work(struct work_struct *work) +{ + struct twl4030_bci_device_info *di = container_of(work, + struct twl4030_bci_device_info, twl4030_bci_monitor_work.work); + + twl4030_bci_battery_update_status(di); + schedule_delayed_work(&di->twl4030_bci_monitor_work, 100); +} + + +#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 ssize_t +show_charge_current(struct device *dev, struct device_attribute *attr, char *buf) +{ + u8 ctl; + int ret = read_bci_val(REG_BCIIREF1) & 0x1FF; + twl_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, &ctl, REG_BCICTL1); + + if (ctl & CGAIN) + ret |= 0x200; + +#ifdef DEBUG + /* Dump debug */ + twl4030battery_temp_setup(); +#endif + + return sprintf(buf, "%d\n", ret); +} + +static ssize_t +set_charge_current(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned long newCurrent; + int ret; + + ret = strict_strtoul(buf, 10, &newCurrent); + if (ret) + return -EINVAL; + + ret = twl_i2c_write_u8(TWL4030_MODULE_MAIN_CHARGE, KEY_IIREF, REG_BCIMFKEY); + if (ret) + return ret; + + ret = twl_i2c_write_u8(TWL4030_MODULE_MAIN_CHARGE, newCurrent & 0xff, REG_BCIIREF1); + if (ret) + return ret; + + ret = twl_i2c_write_u8(TWL4030_MODULE_MAIN_CHARGE, KEY_IIREF, REG_BCIMFKEY); + if (ret) + return ret; + + ret = twl_i2c_write_u8(TWL4030_MODULE_MAIN_CHARGE, (newCurrent >> 8) & 0x1, REG_BCIIREF2); + if (ret) + return ret; + + /* Set software-controlled charge */ + twl4030charger_ac_en(ENABLE, 0); + + /* Set CGAIN = 0 or 1 */ + if(newCurrent > 511) { + u8 tmp; + + /* Set CGAIN = 1 -- need to wait until automatic charge turns off */ + while(!ret) { + clear_n_set(TWL4030_MODULE_MAIN_CHARGE, 0, CGAIN | 0x1B, REG_BCICTL1); + twl_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, &tmp, REG_BCICTL1); + + ret = tmp & CGAIN; + if(!ret) + mdelay(50); + } + } else { + u8 tmp; + + /* Set CGAIN = 0 -- need to wait until automatic charge turns off */ + while(!ret) { + clear_n_set(TWL4030_MODULE_MAIN_CHARGE, CGAIN, 0x1B, REG_BCICTL1); + twl_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, &tmp, REG_BCICTL1); + + ret = !(tmp & CGAIN); + if(!ret) + mdelay(50); + } + } + + /* Set automatic charge (CGAIN = 0/1 persists) */ + twl4030charger_ac_en(ENABLE, 1); + + return count; +} + +static ssize_t +show_voltage(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", twl4030battery_voltage_madc()); +} + +static DEVICE_ATTR(charge_current, S_IRUGO | S_IWUGO, show_charge_current, set_charge_current); +static DEVICE_ATTR(voltage_now_madc, S_IRUGO, show_voltage, NULL); + +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; + int status = 0; + + di = to_twl4030_bci_device_info(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = di->charge_status; + return 0; + default: + break; + } + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + { + /* Get latest data from MADC -- not done periodically by + worker as this is more expensive, so only do it when we + are actually asked for the data... */ + if(di->charge_status == POWER_SUPPLY_STATUS_DISCHARGING) + val->intval = twl4030battery_voltage_madc(); + else + val->intval = di->voltage_uV; + + break; + } + case POWER_SUPPLY_PROP_CURRENT_NOW: + /* FIXME: Get from MADC */ + if(di->charge_status == POWER_SUPPLY_STATUS_DISCHARGING) + val->intval = 0; + else + 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: + /* Get latest data from MADC -- not done periodically by + worker as this is more expensive, so only do it when we + are actually asked for the data... */ + if(di->charge_status == POWER_SUPPLY_STATUS_DISCHARGING) + di->voltage_uV = twl4030battery_voltage_madc(); + + /* + * 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 __init twl4030_bci_battery_probe(struct platform_device *pdev) +{ + struct twl4030_bci_platform_data *pdata = pdev->dev.platform_data; + struct twl4030_bci_device_info *di; + int irq; + int ret; + + di = kzalloc(sizeof(*di), GFP_KERNEL); + if (!di) + return -ENOMEM; + + di->dev = &pdev->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; + di->pdata = pdata; + + /* Set up clocks */ + twl_i2c_write_u8(TWL4030_MODULE_INTBR, MADC_HFCLK_EN | DEFAULT_MADC_CLK_EN, REG_GPBR1); + + twl4030charger_ac_en(ENABLE, CHARGE_MODE); + twl4030charger_usb_en(ENABLE); + twl4030battery_hw_level_en(ENABLE); + twl4030battery_hw_presence_en(ENABLE); + + platform_set_drvdata(pdev, di); + + /* settings for temperature sensing */ + ret = twl4030battery_temp_setup(); + if (ret) + goto temp_setup_fail; + + /* enabling GPCH09 for read back battery voltage */ + if(!di->pdata->no_backup_battery) + { + ret = twl4030backupbatt_voltage_setup(); + if (ret) + goto voltage_setup_fail; + } + + /* REVISIT do we need to request both IRQs ?? */ + + /* request BCI interruption */ + irq = platform_get_irq(pdev, 1); + ret = request_irq(irq, twl4030battery_interrupt, + 0, pdev->name, NULL); + if (ret) { + dev_dbg(&pdev->dev, "could not request irq %d, status %d\n", + irq, ret); + goto batt_irq_fail; + } + + /* request Power interruption */ + irq = platform_get_irq(pdev, 0); + ret = request_irq(irq, twl4030charger_interrupt, + 0, pdev->name, di); + + if (ret) { + dev_dbg(&pdev->dev, "could not request irq %d, status %d\n", + irq, ret); + goto chg_irq_fail; + } + + ret = power_supply_register(&pdev->dev, &di->bat); + if (ret) { + dev_dbg(&pdev->dev, "failed to register main battery\n"); + goto batt_failed; + } + + INIT_DELAYED_WORK_DEFERRABLE(&di->twl4030_bci_monitor_work, + twl4030_bci_battery_work); + schedule_delayed_work(&di->twl4030_bci_monitor_work, 0); + + if(!pdata->no_backup_battery) + { + ret = power_supply_register(&pdev->dev, &di->bk_bat); + if (ret) { + dev_dbg(&pdev->dev, "failed to register backup battery\n"); + goto bk_batt_failed; + } + } + + ret = device_create_file(di->bat.dev, &dev_attr_voltage_now_madc); + ret = device_create_file(di->bat.dev, &dev_attr_charge_current); + if (ret) { + dev_err(&pdev->dev, "failed to create sysfs entries\n"); + goto bk_batt_failed; + } + + INIT_DELAYED_WORK_DEFERRABLE(&di->twl4030_bk_bci_monitor_work, + twl4030_bk_bci_battery_work); + schedule_delayed_work(&di->twl4030_bk_bci_monitor_work, 500); + + set_charge_current (NULL, NULL, "1023", 4); + return 0; + +bk_batt_failed: + if(!pdata->no_backup_battery) + power_supply_unregister(&di->bat); +batt_failed: + free_irq(irq, di); +chg_irq_fail: + irq = platform_get_irq(pdev, 1); + free_irq(irq, NULL); +batt_irq_fail: +voltage_setup_fail: +temp_setup_fail: + twl4030charger_ac_en(DISABLE, CHARGE_MODE); + twl4030charger_usb_en(DISABLE); + twl4030battery_hw_level_en(DISABLE); + twl4030battery_hw_presence_en(DISABLE); + kfree(di); + + return ret; +} + +static int __exit twl4030_bci_battery_remove(struct platform_device *pdev) +{ + struct twl4030_bci_device_info *di = platform_get_drvdata(pdev); + int irq; + + twl4030charger_ac_en(DISABLE, CHARGE_MODE); + twl4030charger_usb_en(DISABLE); + twl4030battery_hw_level_en(DISABLE); + twl4030battery_hw_presence_en(DISABLE); + + irq = platform_get_irq(pdev, 0); + free_irq(irq, di); + + irq = platform_get_irq(pdev, 1); + free_irq(irq, NULL); + + flush_scheduled_work(); + power_supply_unregister(&di->bat); + power_supply_unregister(&di->bk_bat); + platform_set_drvdata(pdev, NULL); + kfree(di); + + return 0; +} + +#ifdef CONFIG_PM +static int twl4030_bci_battery_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct twl4030_bci_device_info *di = platform_get_drvdata(pdev); + + 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 *pdev) +{ + struct twl4030_bci_device_info *di = platform_get_drvdata(pdev); + + schedule_delayed_work(&di->twl4030_bci_monitor_work, 0); + schedule_delayed_work(&di->twl4030_bk_bci_monitor_work, 50); + return 0; +} +#else +#define twl4030_bci_battery_suspend NULL +#define twl4030_bci_battery_resume NULL +#endif /* CONFIG_PM */ + +static struct platform_driver twl4030_bci_battery_driver = { + .probe = twl4030_bci_battery_probe, + .remove = __exit_p(twl4030_bci_battery_remove), + .suspend = twl4030_bci_battery_suspend, + .resume = twl4030_bci_battery_resume, + .driver = { + .name = "twl4030_bci", + }, +}; + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:twl4030_bci"); +MODULE_AUTHOR("Texas Instruments Inc"); + +static int __init twl4030_battery_init(void) +{ + return platform_driver_register(&twl4030_bci_battery_driver); +} +module_init(twl4030_battery_init); + +static void __exit twl4030_battery_exit(void) +{ + platform_driver_unregister(&twl4030_bci_battery_driver); +} +module_exit(twl4030_battery_exit); + diff --git a/include/linux/i2c/twl.h b/include/linux/i2c/twl.h index d975c5b..a3470ce 100644 --- a/include/linux/i2c/twl.h +++ b/include/linux/i2c/twl.h @@ -442,6 +442,7 @@ struct twl4030_clock_init_data { struct twl4030_bci_platform_data { int *battery_tmp_tbl; unsigned int tblsize; + bool no_backup_battery; }; /* TWL4030_GPIO_MAX (18) GPIOs, with interrupts */ -- 1.6.6.1