From: 张晴 Date: Mon, 7 May 2012 08:17:52 +0000 (+0800) Subject: rk30:phone:modify the config file and add battery function X-Git-Tag: firefly_0821_release~9247 X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=7fdb181c23f2fba565b467f6a3ca62e1151797dc;p=firefly-linux-kernel-4.4.55.git rk30:phone:modify the config file and add battery function --- diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index b7638f47a784..bd569e97306c 100755 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -39,8 +39,8 @@ obj-$(CONFIG_TPS6507X) += tps6507x.o obj-$(CONFIG_MENELAUS) += menelaus.o obj-$(CONFIG_TWL4030_CORE) += twl-core.o twl4030-irq.o twl6030-irq.o -#obj-$(CONFIG_TWL4030_MADC) += twl4030-madc.o -obj-$(CONFIG_TWL4030_MADC) += twl6030-madc.o +obj-$(CONFIG_TWL4030_MADC) += twl4030-madc.o +obj-$(CONFIG_TWL6030_MADC) += twl6030-madc.o obj-$(CONFIG_TWL4030_POWER) += twl4030-power.o obj-$(CONFIG_TWL4030_CODEC) += twl4030-codec.o obj-$(CONFIG_TWL6030_PWM) += twl6030-pwm.o diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index caacf8bd6473..fb797b949ef0 100755 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -282,6 +282,13 @@ config CHARGER_TWL4030 help Say Y here to enable support for TWL4030 Battery Charge Interface. +config TWL6030_BCI_BATTERY + tristate "OMAP TWL6030 BCI Battery driver" + depends on TWL4030_CORE && TWL6030_GPADC + help + Support for OMAP TWL6030 BCI Battery driver. + This driver can give support for TWL6030 Battery Charge Interface. + config CHARGER_GPIO tristate "GPIO charger" depends on GPIOLIB diff --git a/drivers/power/Makefile b/drivers/power/Makefile index d1b1674671ee..6d77beaacf0a 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -41,6 +41,7 @@ obj-$(CONFIG_CHARGER_ISP1704) += isp1704_charger.o obj-$(CONFIG_CHARGER_MAX8903) += max8903_charger.o obj-$(CONFIG_CHARGER_TWL4030) += twl4030_charger.o obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o +obj-$(CONFIG_TWL6030_BCI_BATTERY) += twl6030_bci_battery.o obj-$(CONFIG_BATTERY_RK29_ADC) += rk29_adc_battery.o obj-$(CONFIG_BATTERY_RK30_ADC) += rk30_adc_battery.o obj-$(CONFIG_POWER_ON_CHARGER_DISPLAY) += rk29_charger_display.o diff --git a/drivers/power/twl6030_bci_battery.c b/drivers/power/twl6030_bci_battery.c new file mode 100644 index 000000000000..cbfd2f0c7522 --- /dev/null +++ b/drivers/power/twl6030_bci_battery.c @@ -0,0 +1,2936 @@ +/* + * linux/drivers/power/twl6030_bci_battery.c + * + * OMAP4:TWL6030 battery driver for Linux + * + * Copyright (C) 2008-2009 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CONTROLLER_INT_MASK 0x00 +#define CONTROLLER_CTRL1 0x01 +#define CONTROLLER_WDG 0x02 +#define CONTROLLER_STAT1 0x03 +#define CHARGERUSB_INT_STATUS 0x04 +#define CHARGERUSB_INT_MASK 0x05 +#define CHARGERUSB_STATUS_INT1 0x06 +#define CHARGERUSB_STATUS_INT2 0x07 +#define CHARGERUSB_CTRL1 0x08 +#define CHARGERUSB_CTRL2 0x09 +#define CHARGERUSB_CTRL3 0x0A +#define CHARGERUSB_STAT1 0x0B +#define CHARGERUSB_VOREG 0x0C +#define CHARGERUSB_VICHRG 0x0D +#define CHARGERUSB_CINLIMIT 0x0E +#define CHARGERUSB_CTRLLIMIT1 0x0F +#define CHARGERUSB_CTRLLIMIT2 0x10 +#define ANTICOLLAPSE_CTRL1 0x11 +#define ANTICOLLAPSE_CTRL2 0x12 + +/* TWL6032 registers 0xDA to 0xDE - TWL6032_MODULE_CHARGER */ +#define CONTROLLER_CTRL2 0x00 +#define CONTROLLER_VSEL_COMP 0x01 +#define CHARGERUSB_VSYSREG 0x02 +#define CHARGERUSB_VICHRG_PC 0x03 +#define LINEAR_CHRG_STS 0x04 + +/* TWL6032 Charger Mode Register */ +#define CHARGER_MODE_REG 0xD4 +#define CHARGER_MODE_POWERPATH BIT(3) +#define CHARGER_MODE_AUTOCHARGE BIT(6) + +#define LINEAR_CHRG_STS_CRYSTL_OSC_OK 0x40 +#define LINEAR_CHRG_STS_END_OF_CHARGE 0x20 +#define LINEAR_CHRG_STS_VBATOV 0x10 +#define LINEAR_CHRG_STS_VSYSOV 0x08 +#define LINEAR_CHRG_STS_DPPM_STS 0x04 +#define LINEAR_CHRG_STS_CV_STS 0x02 +#define LINEAR_CHRG_STS_CC_STS 0x01 + +#define FG_REG_00 0x00 +#define FG_REG_01 0x01 +#define FG_REG_02 0x02 +#define FG_REG_03 0x03 +#define FG_REG_04 0x04 +#define FG_REG_05 0x05 +#define FG_REG_06 0x06 +#define FG_REG_07 0x07 +#define FG_REG_08 0x08 +#define FG_REG_09 0x09 +#define FG_REG_10 0x0A +#define FG_REG_11 0x0B + +/* CONTROLLER_INT_MASK */ +#define MVAC_FAULT (1 << 7) +#define MAC_EOC (1 << 6) +#define LINCH_GATED (1 << 5) +#define MBAT_REMOVED (1 << 4) +#define MFAULT_WDG (1 << 3) +#define MBAT_TEMP (1 << 2) +#define MVBUS_DET (1 << 1) +#define MVAC_DET (1 << 0) + +/* CONTROLLER_CTRL1 */ +#define CONTROLLER_CTRL1_EN_LINCH (1 << 5) +#define CONTROLLER_CTRL1_EN_CHARGER (1 << 4) +#define CONTROLLER_CTRL1_SEL_CHARGER (1 << 3) + +/* CONTROLLER_STAT1 */ +#define CONTROLLER_STAT1_EXTCHRG_STATZ (1 << 7) +#define CONTROLLER_STAT1_LINCH_GATED (1 << 6) +#define CONTROLLER_STAT1_CHRG_DET_N (1 << 5) +#define CONTROLLER_STAT1_FAULT_WDG (1 << 4) +#define CONTROLLER_STAT1_VAC_DET (1 << 3) +#define VAC_DET (1 << 3) +#define CONTROLLER_STAT1_VBUS_DET (1 << 2) +#define VBUS_DET (1 << 2) +#define CONTROLLER_STAT1_BAT_REMOVED (1 << 1) +#define CONTROLLER_STAT1_BAT_TEMP_OVRANGE (1 << 0) + +/* CHARGERUSB_INT_STATUS */ +#define EN_LINCH (1 << 4) +#define CURRENT_TERM_INT (1 << 3) +#define CHARGERUSB_STAT (1 << 2) +#define CHARGERUSB_THMREG (1 << 1) +#define CHARGERUSB_FAULT (1 << 0) + +/* CHARGERUSB_INT_MASK */ +#define MASK_MCURRENT_TERM (1 << 3) +#define MASK_MCHARGERUSB_STAT (1 << 2) +#define MASK_MCHARGERUSB_THMREG (1 << 1) +#define MASK_MCHARGERUSB_FAULT (1 << 0) + +/* CHARGERUSB_STATUS_INT1 */ +#define CHARGERUSB_STATUS_INT1_TMREG (1 << 7) +#define CHARGERUSB_STATUS_INT1_NO_BAT (1 << 6) +#define CHARGERUSB_STATUS_INT1_BST_OCP (1 << 5) +#define CHARGERUSB_STATUS_INT1_TH_SHUTD (1 << 4) +#define CHARGERUSB_STATUS_INT1_BAT_OVP (1 << 3) +#define CHARGERUSB_STATUS_INT1_POOR_SRC (1 << 2) +#define CHARGERUSB_STATUS_INT1_SLP_MODE (1 << 1) +#define CHARGERUSB_STATUS_INT1_VBUS_OVP (1 << 0) + +/* CHARGERUSB_STATUS_INT2 */ +#define ICCLOOP (1 << 3) +#define CURRENT_TERM (1 << 2) +#define CHARGE_DONE (1 << 1) +#define ANTICOLLAPSE (1 << 0) + +/* CHARGERUSB_CTRL1 */ +#define SUSPEND_BOOT (1 << 7) +#define OPA_MODE (1 << 6) +#define HZ_MODE (1 << 5) +#define TERM (1 << 4) + +/* CHARGERUSB_CTRL2 */ +#define CHARGERUSB_CTRL2_VITERM_50 (0 << 5) +#define CHARGERUSB_CTRL2_VITERM_100 (1 << 5) +#define CHARGERUSB_CTRL2_VITERM_150 (2 << 5) +#define CHARGERUSB_CTRL2_VITERM_400 (7 << 5) + +/* CHARGERUSB_CTRL3 */ +#define VBUSCHRG_LDO_OVRD (1 << 7) +#define CHARGE_ONCE (1 << 6) +#define BST_HW_PR_DIS (1 << 5) +#define AUTOSUPPLY (1 << 3) +#define BUCK_HSILIM (1 << 0) + +/* CHARGERUSB_VOREG */ +#define CHARGERUSB_VOREG_3P52 0x01 +#define CHARGERUSB_VOREG_4P0 0x19 +#define CHARGERUSB_VOREG_4P2 0x23 +#define CHARGERUSB_VOREG_4P76 0x3F + +/* CHARGERUSB_VICHRG */ +#define CHARGERUSB_VICHRG_300 0x0 +#define CHARGERUSB_VICHRG_500 0x4 +#define CHARGERUSB_VICHRG_1500 0xE + +/* CHARGERUSB_CINLIMIT */ +#define CHARGERUSB_CIN_LIMIT_100 0x1 +#define CHARGERUSB_CIN_LIMIT_300 0x5 +#define CHARGERUSB_CIN_LIMIT_500 0x9 +#define CHARGERUSB_CIN_LIMIT_NONE 0xF + +/* CHARGERUSB_CTRLLIMIT1 */ +#define VOREGL_4P16 0x21 +#define VOREGL_4P56 0x35 + +/* CHARGERUSB_CTRLLIMIT2 */ +#define CHARGERUSB_CTRLLIMIT2_1500 0x0E +#define LOCK_LIMIT (1 << 4) + +/* ANTICOLLAPSE_CTRL2 */ +#define BUCK_VTH_SHIFT 5 + +/* FG_REG_00 */ +#define CC_ACTIVE_MODE_SHIFT 6 +#define CC_AUTOCLEAR (1 << 2) +#define CC_CAL_EN (1 << 1) +#define CC_PAUSE (1 << 0) + +#define REG_TOGGLE1 0x90 +#define FGDITHS (1 << 7) +#define FGDITHR (1 << 6) +#define FGS (1 << 5) +#define FGR (1 << 4) + +/* TWL6030_GPADC_CTRL */ +#define GPADC_CTRL_TEMP1_EN (1 << 0) /* input ch 1 */ +#define GPADC_CTRL_TEMP2_EN (1 << 1) /* input ch 4 */ +#define GPADC_CTRL_SCALER_EN (1 << 2) /* input ch 2 */ +#define GPADC_CTRL_SCALER_DIV4 (1 << 3) +#define GPADC_CTRL_SCALER_EN_CH11 (1 << 4) /* input ch 11 */ +#define GPADC_CTRL_TEMP1_EN_MONITOR (1 << 5) +#define GPADC_CTRL_TEMP2_EN_MONITOR (1 << 6) +#define GPADC_CTRL_ISOURCE_EN (1 << 7) + +#define GPADC_ISOURCE_22uA 22 +#define GPADC_ISOURCE_7uA 7 + +/* TWL6030/6032 BATTERY VOLTAGE GPADC CHANNELS */ + +#define TWL6030_GPADC_VBAT_CHNL 0x07 +#define TWL6032_GPADC_VBAT_CHNL 0x12 + +/* TWL6030_GPADC_CTRL2 */ +#define GPADC_CTRL2_CH18_SCALER_EN BIT(2) + +#define ENABLE_ISOURCE 0x80 + +#define REG_MISC1 0xE4 +#define VAC_MEAS 0x04 +#define VBAT_MEAS 0x02 +#define BB_MEAS 0x01 + +#define REG_USB_VBUS_CTRL_SET 0x04 +#define VBUS_MEAS 0x01 +#define REG_USB_ID_CTRL_SET 0x06 +#define ID_MEAS 0x01 + +#define BBSPOR_CFG 0xE6 +#define BB_CHG_EN (1 << 3) + +#define STS_HW_CONDITIONS 0x21 +#define STS_USB_ID (1 << 2) /* Level status of USB ID */ + +#define BATTERY_RESISTOR 10000 +#define SIMULATOR_RESISTOR 5000 +#define BATTERY_DETECT_THRESHOLD ((BATTERY_RESISTOR + SIMULATOR_RESISTOR) / 2) +#define CHARGING_CAPACITY_UPDATE_PERIOD (1000 * 60 * 10) + +/* To get VBUS input limit from twl6030_usb */ +#if CONFIG_TWL6030_USB +extern unsigned int twl6030_get_usb_max_power(struct otg_transceiver *x); +#else +static inline unsigned int twl6030_get_usb_max_power(struct otg_transceiver *x) +{ + return 0; +}; +#endif + +/* Ptr to thermistor table */ +static const unsigned int fuelgauge_rate[4] = {1, 4, 16, 64}; +static struct wake_lock chrg_lock; + + +struct twl6030_bci_device_info { + struct device *dev; + + int voltage_mV; + int bk_voltage_mV; + int current_uA; + int current_avg_uA; + int temp_C; + int charge_status; + int vac_priority; + int bat_health; + int charger_source; + + int fuelgauge_mode; + int timer_n2; + int timer_n1; + s32 charge_n1; + s32 charge_n2; + s16 cc_offset; + u8 usb_online; + u8 ac_online; + u8 stat1; + u8 linear_stat; + u8 status_int1; + u8 status_int2; + + u8 gpadc_vbat_chnl; + u8 watchdog_duration; + u16 current_avg_interval; + u16 monitoring_interval; + unsigned int min_vbus; + + struct twl4030_bci_platform_data *platform_data; + + unsigned int charger_incurrentmA; + unsigned int charger_outcurrentmA; + unsigned long usb_max_power; + unsigned long event; + + unsigned int capacity; + unsigned int capacity_debounce_count; + unsigned long ac_next_refresh; + unsigned int prev_capacity; + unsigned int wakelock_enabled; + + struct power_supply bat; + struct power_supply usb; + struct power_supply ac; + struct power_supply bk_bat; + + struct otg_transceiver *otg; + struct notifier_block nb; + struct work_struct usb_work; + + struct delayed_work twl6030_bci_monitor_work; + struct delayed_work twl6030_current_avg_work; + + unsigned long features; + + int use_hw_charger; + int use_power_path; + + /* max scale current based on sense resitor */ + int current_max_scale; +}; + +static BLOCKING_NOTIFIER_HEAD(notifier_list); +extern u32 wakeup_timer_seconds; + +static void twl6030_config_min_vbus_reg(struct twl6030_bci_device_info *di, + unsigned int value) +{ + u8 rd_reg = 0; + int ret; + + /* not required on TWL6032 */ + if (di->features & TWL6032_SUBCLASS) + return; + + if (value > 4760 || value < 4200) { + dev_dbg(di->dev, "invalid min vbus\n"); + return; + } + + ret = twl_i2c_read_u8(TWL6030_MODULE_CHARGER, &rd_reg, + ANTICOLLAPSE_CTRL2); + if (ret) + goto err; + rd_reg = rd_reg & 0x1F; + rd_reg = rd_reg | (((value - 4200)/80) << BUCK_VTH_SHIFT); + ret = twl_i2c_write_u8(TWL6030_MODULE_CHARGER, rd_reg, + ANTICOLLAPSE_CTRL2); + + if (!ret) + return; +err: + pr_err("%s: Error access to TWL6030 (%d)\n", __func__, ret); +} + +static void twl6030_config_iterm_reg(struct twl6030_bci_device_info *di, + unsigned int term_currentmA) +{ + int ret; + + if ((term_currentmA > 400) || (term_currentmA < 50)) { + dev_dbg(di->dev, "invalid termination current\n"); + return; + } + + term_currentmA = ((term_currentmA - 50)/50) << 5; + ret = twl_i2c_write_u8(TWL6030_MODULE_CHARGER, term_currentmA, + CHARGERUSB_CTRL2); + if (ret) + pr_err("%s: Error access to TWL6030 (%d)\n", __func__, ret); +} + +static unsigned int twl6030_get_iterm_reg(struct twl6030_bci_device_info *di) +{ + int ret; + unsigned int currentmA; + u8 val = 0; + + ret = twl_i2c_read_u8(TWL6030_MODULE_CHARGER, &val, CHARGERUSB_CTRL2); + if (ret) { + pr_err("%s: Error access to TWL6030 (%d)\n", __func__, ret); + currentmA = 0; + } else + currentmA = 50 + (val >> 5) * 50; + + return currentmA; +} + +static void twl6030_config_voreg_reg(struct twl6030_bci_device_info *di, + unsigned int voltagemV) +{ + int ret; + + if ((voltagemV < 3500) || (voltagemV > 4760)) { + dev_dbg(di->dev, "invalid charger_voltagemV\n"); + return; + } + + voltagemV = (voltagemV - 3500) / 20; + ret = twl_i2c_write_u8(TWL6030_MODULE_CHARGER, voltagemV, + CHARGERUSB_VOREG); + if (ret) + pr_err("%s: Error access to TWL6030 (%d)\n", __func__, ret); +} + +static unsigned int twl6030_get_voreg_reg(struct twl6030_bci_device_info *di) +{ + int ret; + unsigned int voltagemV; + u8 val = 0; + + ret = twl_i2c_read_u8(TWL6030_MODULE_CHARGER, &val, CHARGERUSB_VOREG); + if (ret) { + pr_err("%s: Error access to TWL6030 (%d)\n", __func__, ret); + voltagemV = 0; + } else + voltagemV = 3500 + (val * 20); + + return voltagemV; +} + +static void twl6030_config_vichrg_reg(struct twl6030_bci_device_info *di, + unsigned int currentmA) +{ + int ret; + + if ((currentmA >= 300) && (currentmA <= 450)) + currentmA = (currentmA - 300) / 50; + else if ((currentmA >= 500) && (currentmA <= 1500)) + currentmA = (currentmA - 500) / 100 + 4; + else { + dev_dbg(di->dev, "invalid charger_currentmA\n"); + return; + } + + ret = twl_i2c_write_u8(TWL6030_MODULE_CHARGER, currentmA, + CHARGERUSB_VICHRG); + if (ret) + pr_err("%s: Error access to TWL6030 (%d)\n", __func__, ret); +} + +static void twl6030_config_cinlimit_reg(struct twl6030_bci_device_info *di, + unsigned int currentmA) +{ + int ret; + + if ((currentmA >= 50) && (currentmA <= 750)) + currentmA = (currentmA - 50) / 50; + else if ((currentmA > 750) && (currentmA <= 1500) && + (di->features & TWL6032_SUBCLASS)) { + currentmA = ((currentmA % 100) ? 0x30 : 0x20) + + ((currentmA - 100) / 100); + } else if (currentmA < 50) { + dev_dbg(di->dev, "invalid input current limit\n"); + return; + } else { + /* This is no current limit */ + currentmA = 0x0F; + } + + ret = twl_i2c_write_u8(TWL6030_MODULE_CHARGER, currentmA, + CHARGERUSB_CINLIMIT); + if (ret) + pr_err("%s: Error access to TWL6030 (%d)\n", __func__, ret); +} + +static void twl6030_config_limit1_reg(struct twl6030_bci_device_info *di, + unsigned int voltagemV) +{ + int ret; + + if ((voltagemV < 3500) || (voltagemV > 4760)) { + dev_dbg(di->dev, "invalid max_charger_voltagemV\n"); + return; + } + + voltagemV = (voltagemV - 3500) / 20; + ret = twl_i2c_write_u8(TWL6030_MODULE_CHARGER, voltagemV, + CHARGERUSB_CTRLLIMIT1); + if (ret) + pr_err("%s: Error access to TWL6030 (%d)\n", __func__, ret); +} + +static unsigned int twl6030_get_limit1_reg(struct twl6030_bci_device_info *di) +{ + int ret; + unsigned int voltagemV; + u8 val = 0; + + ret = twl_i2c_read_u8(TWL6030_MODULE_CHARGER, &val, + CHARGERUSB_CTRLLIMIT1); + if (ret) { + pr_err("%s: Error access to TWL6030 (%d)\n", __func__, ret); + voltagemV = 0; + } else + voltagemV = 3500 + (val * 20); + + return voltagemV; +} + +static void twl6030_config_limit2_reg(struct twl6030_bci_device_info *di, + unsigned int currentmA) +{ + int ret; + + if ((currentmA >= 300) && (currentmA <= 450)) + currentmA = (currentmA - 300) / 50; + else if ((currentmA >= 500) && (currentmA <= 1500)) + currentmA = (currentmA - 500) / 100 + 4; + else { + dev_dbg(di->dev, "invalid max_charger_currentmA\n"); + return; + } + + currentmA |= LOCK_LIMIT; + ret = twl_i2c_write_u8(TWL6030_MODULE_CHARGER, currentmA, + CHARGERUSB_CTRLLIMIT2); + if (ret) + pr_err("%s: Error access to TWL6030 (%d)\n", __func__, ret); +} + +static const int vichrg[] = { + 300, 350, 400, 450, 500, 600, 700, 800, + 900, 1000, 1100, 1200, 1300, 1400, 1500, 300 +}; + +static unsigned int twl6030_get_limit2_reg(struct twl6030_bci_device_info *di) +{ + int ret; + unsigned int currentmA; + u8 val = 0; + + ret = twl_i2c_read_u8(TWL6030_MODULE_CHARGER, &val, + CHARGERUSB_CTRLLIMIT2); + if (ret) { + pr_err("%s: Error access to TWL6030 (%d)\n", __func__, ret); + currentmA = 0; + } else + currentmA = vichrg[val & 0xF]; + + return currentmA; +} + +/* + * Return channel value + * Or < 0 on failure. + */ +static int twl6030_get_gpadc_conversion(struct twl6030_bci_device_info *di, + int channel_no) +{ + struct twl6030_gpadc_request req; + int temp = 0; + int ret; + + req.channels = (1 << channel_no); + req.method = TWL6030_GPADC_SW2; + req.active = 0; + req.func_cb = NULL; + ret = twl6030_gpadc_conversion(&req); + if (ret < 0) + return ret; + + if (req.rbuf[channel_no] > 0) + temp = req.rbuf[channel_no]; + + return temp; +} + +static int is_battery_present(struct twl6030_bci_device_info *di) +{ + int val; + static unsigned int current_src_val; + + /* + * Prevent charging on batteries were id resistor is + * less than 5K. + */ + val = twl6030_get_gpadc_conversion(di, 0); + + /* + * twl6030_get_gpadc_conversion for + * 6030 return resistance, for 6032 - voltage and + * it should be converted to resistance before + * using. + */ + if (!current_src_val) { + u8 reg = 0; + + if (twl_i2c_read_u8(TWL_MODULE_MADC, ®, + TWL6030_GPADC_CTRL)) + pr_err("%s: Error reading TWL6030_GPADC_CTRL\n", + __func__); + + current_src_val = (reg & GPADC_CTRL_ISOURCE_EN) ? + GPADC_ISOURCE_22uA : + GPADC_ISOURCE_7uA; + } + + val = (val * 1000) / current_src_val; + + if (val < BATTERY_DETECT_THRESHOLD) + return 0; + + return 1; +} + +static void twl6030_stop_usb_charger(struct twl6030_bci_device_info *di) +{ + int ret; + u8 reg; + + if (di->use_hw_charger) { + ret = twl_i2c_read_u8(TWL6030_MODULE_CHARGER, ®, + CHARGERUSB_CTRL1); + if (ret) + goto err; + reg |= HZ_MODE; + ret = twl_i2c_write_u8(TWL6030_MODULE_CHARGER, reg, + CHARGERUSB_CTRL1); + if (ret) + goto err; + + return; + } + + di->charger_source = 0; + di->charge_status = POWER_SUPPLY_STATUS_DISCHARGING; + + ret = twl_i2c_write_u8(TWL6030_MODULE_CHARGER, 0, CONTROLLER_CTRL1); + +err: + if (ret) + pr_err("%s: Error access to TWL6030 (%d)\n", __func__, ret); +} + +static void twl6030_start_usb_charger(struct twl6030_bci_device_info *di) +{ + int ret; + u8 reg; + + if (di->use_hw_charger) { + ret = twl_i2c_read_u8(TWL6030_MODULE_CHARGER, ®, + CHARGERUSB_CTRL1); + if (ret) + goto err; + + reg &= ~HZ_MODE; + ret = twl_i2c_write_u8(TWL6030_MODULE_CHARGER, reg, + CHARGERUSB_CTRL1); + if (ret) + goto err; + + return; + } + + if (!is_battery_present(di)) { + dev_dbg(di->dev, "BATTERY NOT DETECTED!\n"); + return; + } + + if (di->charger_source == POWER_SUPPLY_TYPE_MAINS) + return; + + dev_dbg(di->dev, "USB input current limit %dmA\n", + di->charger_incurrentmA); + if (di->charger_incurrentmA < 50) { + ret = twl_i2c_write_u8(TWL6030_MODULE_CHARGER, + 0, CONTROLLER_CTRL1); + if (ret) + goto err; + + return; + } + + twl6030_config_vichrg_reg(di, di->charger_outcurrentmA); + twl6030_config_cinlimit_reg(di, di->charger_incurrentmA); + twl6030_config_voreg_reg(di, di->platform_data->max_bat_voltagemV); + twl6030_config_iterm_reg(di, di->platform_data->termination_currentmA); + + if (di->charger_incurrentmA >= 50) { + reg = CONTROLLER_CTRL1_EN_CHARGER; + + if (di->use_power_path) + reg |= CONTROLLER_CTRL1_EN_LINCH; + + ret = twl_i2c_write_u8(TWL6030_MODULE_CHARGER, reg, + CONTROLLER_CTRL1); + if (ret) + goto err; + + di->charge_status = POWER_SUPPLY_STATUS_CHARGING; + } + return; +err: + pr_err("%s: Error access to TWL6030 (%d)\n", __func__, ret); +} + +static void twl6030_stop_ac_charger(struct twl6030_bci_device_info *di) +{ + long int events; + int ret; + + di->charger_source = 0; + di->charge_status = POWER_SUPPLY_STATUS_DISCHARGING; + events = BQ2415x_STOP_CHARGING; + + if (di->use_hw_charger) + return; + + blocking_notifier_call_chain(¬ifier_list, events, NULL); + + ret = twl_i2c_write_u8(TWL6030_MODULE_CHARGER, 0, CONTROLLER_CTRL1); + if (ret) + pr_err("%s: Error access to TWL6030 (%d)\n", __func__, ret); + + if (di->wakelock_enabled) + wake_unlock(&chrg_lock); +} + +static void twl6030_start_ac_charger(struct twl6030_bci_device_info *di) +{ + long int events; + int ret; + + if (!is_battery_present(di)) { + dev_dbg(di->dev, "BATTERY NOT DETECTED!\n"); + return; + } + dev_dbg(di->dev, "AC charger detected\n"); + di->charger_source = POWER_SUPPLY_TYPE_MAINS; + di->charge_status = POWER_SUPPLY_STATUS_CHARGING; + events = BQ2415x_START_CHARGING; + + if (di->use_hw_charger) + return; + + blocking_notifier_call_chain(¬ifier_list, events, NULL); + + ret = twl_i2c_write_u8(TWL6030_MODULE_CHARGER, + CONTROLLER_CTRL1_EN_CHARGER | + CONTROLLER_CTRL1_SEL_CHARGER, + CONTROLLER_CTRL1); + if (ret) + pr_err("%s: Error access to TWL6030 (%d)\n", __func__, ret); + + if (di->wakelock_enabled) + wake_lock(&chrg_lock); +} + +static void twl6030_stop_charger(struct twl6030_bci_device_info *di) +{ + if (di->charger_source == POWER_SUPPLY_TYPE_MAINS) + twl6030_stop_ac_charger(di); + else if (di->charger_source == POWER_SUPPLY_TYPE_USB) + twl6030_stop_usb_charger(di); +} + +static void twl6032_charger_ctrl_interrupt(struct twl6030_bci_device_info *di) +{ + u8 stat_toggle, stat_reset, stat_set = 0; + u8 present_state = 0, linear_state; + u8 present_status = 0; + int err; + + err = twl_i2c_read_u8(TWL6032_MODULE_CHARGER, &present_state, + LINEAR_CHRG_STS); + if (err < 0) { + dev_err(di->dev, "%s: Error access to TWL6030 (%d)\n", + __func__, err); + return; + } + + err = twl_i2c_read_u8(TWL6032_MODULE_CHARGER, &present_status, + CHARGERUSB_INT_STATUS); + if (err < 0) { + dev_err(di->dev, "%s: Error access to TWL6030 (%d)\n", + __func__, err); + return; + } + + linear_state = di->linear_stat; + + stat_toggle = linear_state ^ present_state; + stat_set = stat_toggle & present_state; + stat_reset = stat_toggle & linear_state; + di->linear_stat = present_state; + + if (stat_set & LINEAR_CHRG_STS_CRYSTL_OSC_OK) + dev_dbg(di->dev, "Linear status: CRYSTAL OSC OK\n"); + if (present_state & LINEAR_CHRG_STS_END_OF_CHARGE) { + dev_dbg(di->dev, "Linear status: END OF CHARGE\n"); + di->charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + } + if (present_status & EN_LINCH) { + dev_dbg(di->dev, "Linear status: START OF CHARGE\n"); + di->charge_status = POWER_SUPPLY_STATUS_CHARGING; + } + + if (stat_set & LINEAR_CHRG_STS_VBATOV) { + dev_dbg(di->dev, "Linear Status: VBATOV\n"); + di->bat_health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + } + if (stat_reset & LINEAR_CHRG_STS_VBATOV) { + dev_dbg(di->dev, "Linear Status: VBATOV\n"); + di->bat_health = POWER_SUPPLY_HEALTH_GOOD; + } + + if (stat_set & LINEAR_CHRG_STS_VSYSOV) + dev_dbg(di->dev, "Linear Status: VSYSOV\n"); + if (stat_set & LINEAR_CHRG_STS_DPPM_STS) + dev_dbg(di->dev, "Linear Status: DPPM STS\n"); + if (stat_set & LINEAR_CHRG_STS_CV_STS) + dev_dbg(di->dev, "Linear Status: CV STS\n"); + if (stat_set & LINEAR_CHRG_STS_CC_STS) + dev_dbg(di->dev, "Linear Status: CC STS\n"); +} + +/* + * Interrupt service routine + * + * Attends to TWL 6030 power module interruptions events, specifically + * USB_PRES (USB charger presence) CHG_PRES (AC charger presence) events + * + */ +static irqreturn_t twl6030charger_ctrl_interrupt(int irq, void *_di) +{ + struct twl6030_bci_device_info *di = _di; + int ret; + int charger_fault = 0; + long int events; + u8 stat_toggle, stat_reset, stat_set = 0; + u8 charge_state = 0; + u8 present_charge_state = 0; + u8 ac_or_vbus, no_ac_and_vbus = 0; + u8 hw_state = 0, temp = 0; + + /* read charger controller_stat1 */ + ret = twl_i2c_read_u8(TWL6030_MODULE_CHARGER, &present_charge_state, + CONTROLLER_STAT1); + if (ret) { + /* + * Since present state read failed, charger_state is no + * longer valid, reset to zero inorder to detect next events + */ + charge_state = 0; + return IRQ_NONE; + } + + ret = twl_i2c_read_u8(TWL6030_MODULE_ID0, &hw_state, STS_HW_CONDITIONS); + if (ret) + goto err; + + charge_state = di->stat1; + + stat_toggle = charge_state ^ present_charge_state; + stat_set = stat_toggle & present_charge_state; + stat_reset = stat_toggle & charge_state; + + no_ac_and_vbus = !((present_charge_state) & (VBUS_DET | VAC_DET)); + ac_or_vbus = charge_state & (VBUS_DET | VAC_DET); + if (no_ac_and_vbus && ac_or_vbus) { + di->charger_source = 0; + dev_dbg(di->dev, "No Charging source\n"); + /* disable charging when no source present */ + } + + charge_state = present_charge_state; + di->stat1 = present_charge_state; + if ((charge_state & VAC_DET) && + (charge_state & CONTROLLER_STAT1_EXTCHRG_STATZ)) { + events = BQ2415x_CHARGER_FAULT; + blocking_notifier_call_chain(¬ifier_list, events, NULL); + } + + if (stat_reset & VBUS_DET) { + /* On a USB detach, UNMASK VBUS OVP if masked*/ + ret = twl_i2c_read_u8(TWL6030_MODULE_CHARGER, &temp, + CHARGERUSB_INT_MASK); + if (ret) + goto err; + + if (temp & MASK_MCHARGERUSB_FAULT) { + ret = twl_i2c_write_u8(TWL6030_MODULE_CHARGER, + (temp & ~MASK_MCHARGERUSB_FAULT), + CHARGERUSB_INT_MASK); + if (ret) + goto err; + } + di->usb_online = 0; + dev_dbg(di->dev, "usb removed\n"); + twl6030_stop_usb_charger(di); + if (present_charge_state & VAC_DET) + twl6030_start_ac_charger(di); + + } + + if (stat_set & VBUS_DET) { + /* In HOST mode (ID GROUND) when a device is connected, + * Mask VBUS OVP interrupt and do no enable usb + * charging + */ + if (hw_state & STS_USB_ID) { + ret = twl_i2c_read_u8(TWL6030_MODULE_CHARGER, + &temp, + CHARGERUSB_INT_MASK); + if (ret) + goto err; + + if (!(temp & MASK_MCHARGERUSB_FAULT)) { + ret = twl_i2c_write_u8( + TWL6030_MODULE_CHARGER, + (temp | MASK_MCHARGERUSB_FAULT), + CHARGERUSB_INT_MASK); + if (ret) + goto err; + } + } else { + di->usb_online = POWER_SUPPLY_TYPE_USB; + if ((present_charge_state & VAC_DET) && + (di->vac_priority == 2)) + dev_dbg(di->dev, "USB charger detected" + ", continue with VAC\n"); + else { + di->charger_source = + POWER_SUPPLY_TYPE_USB; + di->charge_status = + POWER_SUPPLY_STATUS_CHARGING; + } + dev_dbg(di->dev, "vbus detect\n"); + } + } + + if (stat_reset & VAC_DET) { + di->ac_online = 0; + dev_dbg(di->dev, "vac removed\n"); + twl6030_stop_ac_charger(di); + if (present_charge_state & VBUS_DET) { + di->charger_source = POWER_SUPPLY_TYPE_USB; + di->charge_status = + POWER_SUPPLY_STATUS_CHARGING; + twl6030_start_usb_charger(di); + } + } + if (stat_set & VAC_DET) { + di->ac_online = POWER_SUPPLY_TYPE_MAINS; + if ((present_charge_state & VBUS_DET) && + (di->vac_priority == 3)) + dev_dbg(di->dev, + "AC charger detected" + ", continue with VBUS\n"); + else + twl6030_start_ac_charger(di); + } + + if (stat_set & CONTROLLER_STAT1_FAULT_WDG) { + charger_fault = 1; + dev_dbg(di->dev, "Fault watchdog fired\n"); + } + if (stat_reset & CONTROLLER_STAT1_FAULT_WDG) + dev_dbg(di->dev, "Fault watchdog recovered\n"); + if (stat_set & CONTROLLER_STAT1_BAT_REMOVED) + dev_dbg(di->dev, "Battery removed\n"); + if (stat_reset & CONTROLLER_STAT1_BAT_REMOVED) + dev_dbg(di->dev, "Battery inserted\n"); + if (stat_set & CONTROLLER_STAT1_BAT_TEMP_OVRANGE) { + dev_dbg(di->dev, "Battery temperature overrange\n"); + di->bat_health = POWER_SUPPLY_HEALTH_OVERHEAT; + } + if (stat_reset & CONTROLLER_STAT1_BAT_TEMP_OVRANGE) { + dev_dbg(di->dev, "Battery temperature within range\n"); + di->bat_health = POWER_SUPPLY_HEALTH_GOOD; + } + if (di->features & TWL6032_SUBCLASS) + twl6032_charger_ctrl_interrupt(di); + + if (charger_fault) { + twl6030_stop_usb_charger(di); + di->charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + dev_err(di->dev, "Charger Fault stop charging\n"); + } + + if (di->capacity != -1) + power_supply_changed(&di->bat); + else { + cancel_delayed_work(&di->twl6030_bci_monitor_work); + schedule_delayed_work(&di->twl6030_bci_monitor_work, 0); + } +err: + return IRQ_HANDLED; +} + +static irqreturn_t twl6030charger_fault_interrupt(int irq, void *_di) +{ + struct twl6030_bci_device_info *di = _di; + int charger_fault = 0; + int ret; + + u8 usb_charge_sts = 0, usb_charge_sts1 = 0, usb_charge_sts2 = 0; + + ret = twl_i2c_read_u8(TWL6030_MODULE_CHARGER, &usb_charge_sts, + CHARGERUSB_INT_STATUS); + if (ret) + goto err; + + ret = twl_i2c_read_u8(TWL6030_MODULE_CHARGER, &usb_charge_sts1, + CHARGERUSB_STATUS_INT1); + if (ret) + goto err; + + ret = twl_i2c_read_u8(TWL6030_MODULE_CHARGER, &usb_charge_sts2, + CHARGERUSB_STATUS_INT2); + if (ret) + goto err; + + di->status_int1 = usb_charge_sts1; + di->status_int2 = usb_charge_sts2; + if (usb_charge_sts & CURRENT_TERM_INT) + dev_dbg(di->dev, "USB CURRENT_TERM_INT\n"); + if (usb_charge_sts & CHARGERUSB_THMREG) + dev_dbg(di->dev, "USB CHARGERUSB_THMREG\n"); + if (usb_charge_sts & CHARGERUSB_FAULT) + dev_dbg(di->dev, "USB CHARGERUSB_FAULT\n"); + + if (usb_charge_sts1 & CHARGERUSB_STATUS_INT1_TMREG) + dev_dbg(di->dev, "USB CHARGER Thermal regulation activated\n"); + if (usb_charge_sts1 & CHARGERUSB_STATUS_INT1_NO_BAT) + dev_dbg(di->dev, "No Battery Present\n"); + if (usb_charge_sts1 & CHARGERUSB_STATUS_INT1_BST_OCP) + dev_dbg(di->dev, "USB CHARGER Boost Over current protection\n"); + if (usb_charge_sts1 & CHARGERUSB_STATUS_INT1_TH_SHUTD) { + charger_fault = 1; + dev_dbg(di->dev, "USB CHARGER Thermal Shutdown\n"); + } + if (usb_charge_sts1 & CHARGERUSB_STATUS_INT1_BAT_OVP) + dev_dbg(di->dev, "USB CHARGER Bat Over Voltage Protection\n"); + if (usb_charge_sts1 & CHARGERUSB_STATUS_INT1_POOR_SRC) + dev_dbg(di->dev, "USB CHARGER Poor input source\n"); + if (usb_charge_sts1 & CHARGERUSB_STATUS_INT1_SLP_MODE) + dev_dbg(di->dev, "USB CHARGER Sleep mode\n"); + if (usb_charge_sts1 & CHARGERUSB_STATUS_INT1_VBUS_OVP) + dev_dbg(di->dev, "USB CHARGER VBUS over voltage\n"); + + if (usb_charge_sts2 & CHARGE_DONE) { + di->charge_status = POWER_SUPPLY_STATUS_FULL; + dev_dbg(di->dev, "USB charge done\n"); + } + if (usb_charge_sts2 & CURRENT_TERM) + dev_dbg(di->dev, "USB CURRENT_TERM\n"); + if (usb_charge_sts2 & ICCLOOP) + dev_dbg(di->dev, "USB ICCLOOP\n"); + if (usb_charge_sts2 & ANTICOLLAPSE) + dev_dbg(di->dev, "USB ANTICOLLAPSE\n"); + + if (charger_fault) { + twl6030_stop_usb_charger(di); + di->charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + dev_err(di->dev, "Charger Fault stop charging\n"); + } + dev_dbg(di->dev, "Charger fault detected STS, INT1, INT2 %x %x %x\n", + usb_charge_sts, usb_charge_sts1, usb_charge_sts2); + + power_supply_changed(&di->bat); +err: + return IRQ_HANDLED; +} + +/* + * In HW charger mode on 6032 irq routines must only deal with updating + * state of charger. The hardware deals with start/stop conditions + * automatically. + */ +static irqreturn_t twl6032charger_ctrl_interrupt_hw(int irq, void *_di) +{ + struct twl6030_bci_device_info *di = _di; + u8 stat1, linear; + int charger_stop = 0, end_of_charge = 0; + int ret; + + /* read charger controller_stat1 */ + ret = twl_i2c_read_u8(TWL6030_MODULE_CHARGER, &stat1, + CONTROLLER_STAT1); + if (ret) + goto out; + + ret = twl_i2c_read_u8(TWL6032_MODULE_CHARGER, &linear, + LINEAR_CHRG_STS); + if (ret < 0) + goto out; + + if (!(stat1 & (VBUS_DET | VAC_DET))) { + charger_stop = 1; + di->ac_online = di->usb_online = 0; + } + + if (!(di->usb_online || di->ac_online)) { + if (stat1 & VBUS_DET) { + di->usb_online = 1; + di->bat_health = POWER_SUPPLY_HEALTH_GOOD; + } else if (stat1 & VAC_DET) { + di->ac_online = 1; + di->bat_health = POWER_SUPPLY_HEALTH_GOOD; + } + } + + if (stat1 & CONTROLLER_STAT1_FAULT_WDG) { + charger_stop = 1; + di->bat_health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + dev_dbg(di->dev, "Charger error : Fault watchdog\n"); + } + if (stat1 & CONTROLLER_STAT1_BAT_REMOVED) { + charger_stop = 1; + di->bat_health = POWER_SUPPLY_HEALTH_DEAD; + dev_dbg(di->dev, "Battery removed\n"); + } + if (stat1 & CONTROLLER_STAT1_BAT_TEMP_OVRANGE) { + charger_stop = 1; + dev_dbg(di->dev, + "Charger error : Battery temperature overrange\n"); + di->bat_health = POWER_SUPPLY_HEALTH_OVERHEAT; + } + + if ((stat1 & CONTROLLER_STAT1_LINCH_GATED) && + di->use_power_path) { + + charger_stop = 1; + + if (linear & LINEAR_CHRG_STS_CRYSTL_OSC_OK) { + di->bat_health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + dev_dbg(di->dev, "Charger error: CRYSTAL OSC OK\n"); + } + + if (linear & LINEAR_CHRG_STS_END_OF_CHARGE) { + end_of_charge = 1; + di->bat_health = POWER_SUPPLY_HEALTH_GOOD; + dev_dbg(di->dev, "Charger: Full charge\n"); + } + + if (linear & LINEAR_CHRG_STS_VBATOV) { + di->bat_health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + dev_dbg(di->dev, + "Charger error : Linear Status: VBATOV\n"); + } + + if (linear & LINEAR_CHRG_STS_VSYSOV) { + di->bat_health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + dev_dbg(di->dev, + "Charger error : Linear Status: VSYSOV\n"); + } + } + + if (charger_stop) { + if (!(stat1 & (VBUS_DET | VAC_DET))) { + di->charge_status = POWER_SUPPLY_STATUS_DISCHARGING; + } else { + if (end_of_charge) + di->charge_status = + POWER_SUPPLY_STATUS_FULL; + else + di->charge_status = + POWER_SUPPLY_STATUS_NOT_CHARGING; + } + } + + power_supply_changed(&di->bat); + +out: + return IRQ_HANDLED; +} + +static irqreturn_t twl6032charger_fault_interrupt_hw(int irq, void *_di) +{ + struct twl6030_bci_device_info *di = _di; + int charger_stop = 0, charger_start = 0; + int ret; + u8 sts, sts_int1, sts_int2, stat1; + + ret = twl_i2c_read_u8(TWL6030_MODULE_CHARGER, &sts, + CHARGERUSB_INT_STATUS); + if (ret) + goto out; + + ret = twl_i2c_read_u8(TWL6030_MODULE_CHARGER, &sts_int1, + CHARGERUSB_STATUS_INT1); + if (ret) + goto out; + + ret = twl_i2c_read_u8(TWL6030_MODULE_CHARGER, &sts_int2, + CHARGERUSB_STATUS_INT2); + if (ret) + goto out; + + ret = twl_i2c_read_u8(TWL6030_MODULE_CHARGER, &stat1, + CONTROLLER_STAT1); + if (ret) + goto out; + + if (sts & EN_LINCH) { + charger_start = 1; + dev_dbg(di->dev, "Charger: EN_LINCH\n"); + goto out; + } + + if ((sts & CURRENT_TERM_INT) && !di->use_power_path) { + dev_dbg(di->dev, "Charger: CURRENT_TERM_INT\n"); + + if (sts_int2 & CURRENT_TERM) { + charger_stop = 1; + dev_dbg(di->dev, "Charger error: CURRENT_TERM\n"); + } + } + + if (sts & CHARGERUSB_STAT) { + dev_dbg(di->dev, "Charger: CHARGEUSB_STAT\n"); + + if (sts_int2 & ANTICOLLAPSE) + dev_dbg(di->dev, "Charger error: ANTICOLLAPSE\n"); + } + + if (sts & CHARGERUSB_THMREG) { + dev_dbg(di->dev, "Charger: CHARGERUSB_THMREG\n"); + + if (sts_int1 & CHARGERUSB_STATUS_INT1_TMREG) + dev_dbg(di->dev, "Charger error: TMREG\n"); + } + + if (sts & CHARGERUSB_FAULT) { + dev_dbg(di->dev, "Charger: CHARGERUSB_FAULT\n"); + + charger_stop = 1; + + if (!di->use_power_path) { + if (sts_int1 & CHARGERUSB_STATUS_INT1_NO_BAT) { + di->bat_health = POWER_SUPPLY_HEALTH_DEAD; + dev_dbg(di->dev, + "Charger error : NO_BAT\n"); + } + if (sts_int1 & CHARGERUSB_STATUS_INT1_BAT_OVP) { + di->bat_health = + POWER_SUPPLY_HEALTH_OVERVOLTAGE; + dev_dbg(di->dev, "Charger error : BAT_OVP\n"); + } + } + + if (sts_int1 & CHARGERUSB_STATUS_INT1_BST_OCP) { + di->bat_health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + printk(KERN_ERR "Charger error : BST_OCP\n"); + } + if (sts_int1 & CHARGERUSB_STATUS_INT1_TH_SHUTD) { + di->bat_health = POWER_SUPPLY_HEALTH_OVERHEAT; + printk(KERN_ERR "Charger error : TH_SHUTD\n"); + } + if (sts_int1 & CHARGERUSB_STATUS_INT1_POOR_SRC) { + di->bat_health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + printk(KERN_ERR "Charger error : POOR_SRC\n"); + } + if (sts_int1 & CHARGERUSB_STATUS_INT1_SLP_MODE) + dev_dbg(di->dev, "Charger error: SLP_MODE\n"); + + if (sts_int1 & CHARGERUSB_STATUS_INT1_VBUS_OVP) { + di->bat_health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + printk(KERN_ERR "Charger error : VBUS_OVP\n"); + } + } + + if (charger_stop) { + if (!(stat1 & (VBUS_DET | VAC_DET))) + di->charge_status = POWER_SUPPLY_STATUS_DISCHARGING; + else + di->charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + } + +out: + if (charger_start) { + di->charge_status = POWER_SUPPLY_STATUS_CHARGING; + di->bat_health = POWER_SUPPLY_HEALTH_GOOD; + } + + power_supply_changed(&di->bat); + + return IRQ_HANDLED; +} + +static void twl6030battery_current(struct twl6030_bci_device_info *di) +{ + int ret = 0; + u16 read_value = 0; + s16 temp = 0; + int current_now = 0; + + /* FG_REG_10, 11 is 14 bit signed instantaneous current sample value */ + ret = twl_i2c_read(TWL6030_MODULE_GASGAUGE, (u8 *)&read_value, + FG_REG_10, 2); + if (ret < 0) { + dev_dbg(di->dev, "failed to read FG_REG_10: current_now\n"); + return; + } + + temp = ((s16)(read_value << 2) >> 2); + current_now = temp - di->cc_offset; + + /* current drawn per sec */ + current_now = current_now * fuelgauge_rate[di->fuelgauge_mode]; + /* current in mAmperes */ + current_now = (current_now * di->current_max_scale) >> 13; + /* current in uAmperes */ + current_now = current_now * 1000; + di->current_uA = current_now; + + return; +} + +/* + * Setup the twl6030 BCI module to enable backup + * battery charging. + */ +static int twl6030backupbatt_setup(void) +{ + int ret; + u8 rd_reg = 0; + + ret = twl_i2c_read_u8(TWL6030_MODULE_ID0, &rd_reg, BBSPOR_CFG); + if (ret) + return ret; + + rd_reg |= BB_CHG_EN; + ret = twl_i2c_write_u8(TWL6030_MODULE_ID0, rd_reg, BBSPOR_CFG); + + return ret; +} + +/* + * Setup the twl6030 BCI module to measure battery + * temperature + */ +static int twl6030battery_temp_setup(bool enable) +{ + int ret; + u8 rd_reg = 0; + + ret = twl_i2c_read_u8(TWL_MODULE_MADC, &rd_reg, TWL6030_GPADC_CTRL); + if (ret) + return ret; + + if (enable) + rd_reg |= (GPADC_CTRL_TEMP1_EN | GPADC_CTRL_TEMP2_EN | + GPADC_CTRL_TEMP1_EN_MONITOR | + GPADC_CTRL_TEMP2_EN_MONITOR | GPADC_CTRL_SCALER_DIV4); + else + rd_reg ^= (GPADC_CTRL_TEMP1_EN | GPADC_CTRL_TEMP2_EN | + GPADC_CTRL_TEMP1_EN_MONITOR | + GPADC_CTRL_TEMP2_EN_MONITOR | GPADC_CTRL_SCALER_DIV4); + + ret = twl_i2c_write_u8(TWL_MODULE_MADC, rd_reg, TWL6030_GPADC_CTRL); + + return ret; +} + +static int twl6030battery_voltage_setup(struct twl6030_bci_device_info *di) +{ + int ret; + u8 rd_reg = 0; + + ret = twl_i2c_read_u8(TWL6030_MODULE_ID0, &rd_reg, REG_MISC1); + if (ret) + return ret; + + rd_reg = rd_reg | VAC_MEAS | VBAT_MEAS | BB_MEAS; + ret = twl_i2c_write_u8(TWL6030_MODULE_ID0, rd_reg, REG_MISC1); + if (ret) + return ret; + + ret = twl_i2c_read_u8(TWL_MODULE_USB, &rd_reg, REG_USB_VBUS_CTRL_SET); + if (ret) + return ret; + + rd_reg = rd_reg | VBUS_MEAS; + ret = twl_i2c_write_u8(TWL_MODULE_USB, rd_reg, REG_USB_VBUS_CTRL_SET); + if (ret) + return ret; + + ret = twl_i2c_read_u8(TWL_MODULE_USB, &rd_reg, REG_USB_ID_CTRL_SET); + if (ret) + return ret; + + rd_reg = rd_reg | ID_MEAS; + ret = twl_i2c_write_u8(TWL_MODULE_USB, rd_reg, REG_USB_ID_CTRL_SET); + if (ret) + return ret; + + if (di->features & TWL6032_SUBCLASS) + ret = twl_i2c_write_u8(TWL_MODULE_MADC, + GPADC_CTRL2_CH18_SCALER_EN, + TWL6030_GPADC_CTRL2); + + return ret; +} + +static int twl6030battery_current_setup(bool enable) +{ + int ret = 0; + u8 reg = 0; + + /* + * Writing 0 to REG_TOGGLE1 has no effect, so + * can directly set/reset FG. + */ + if (enable) + reg = FGDITHS | FGS; + else + reg = FGDITHR | FGR; + + ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, reg, REG_TOGGLE1); + if (ret) + return ret; + + ret = twl_i2c_write_u8(TWL6030_MODULE_GASGAUGE, CC_CAL_EN, FG_REG_00); + + return ret; +} + +static enum power_supply_property twl6030_bci_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TEMP, +}; + +static enum power_supply_property twl6030_usb_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + +static enum power_supply_property twl6030_ac_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + +static enum power_supply_property twl6030_bk_bci_battery_props[] = { + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + +static void twl6030_current_avg(struct work_struct *work) +{ + s32 samples = 0; + s16 cc_offset = 0; + int current_avg_uA = 0; + int ret; + struct twl6030_bci_device_info *di = container_of(work, + struct twl6030_bci_device_info, + twl6030_current_avg_work.work); + + di->charge_n2 = di->charge_n1; + di->timer_n2 = di->timer_n1; + + /* FG_REG_01, 02, 03 is 24 bit unsigned sample counter value */ + ret = twl_i2c_read(TWL6030_MODULE_GASGAUGE, (u8 *) &di->timer_n1, + FG_REG_01, 3); + if (ret < 0) + goto err; + /* + * FG_REG_04, 5, 6, 7 is 32 bit signed accumulator value + * accumulates instantaneous current value + */ + ret = twl_i2c_read(TWL6030_MODULE_GASGAUGE, (u8 *) &di->charge_n1, + FG_REG_04, 4); + if (ret < 0) + goto err; + /* FG_REG_08, 09 is 10 bit signed calibration offset value */ + ret = twl_i2c_read(TWL6030_MODULE_GASGAUGE, (u8 *) &cc_offset, + FG_REG_08, 2); + if (ret < 0) + goto err; + cc_offset = ((s16)(cc_offset << 6) >> 6); + di->cc_offset = cc_offset; + + samples = di->timer_n1 - di->timer_n2; + /* check for timer overflow */ + if (di->timer_n1 < di->timer_n2) + samples = samples + (1 << 24); + + /* offset is accumulative over number of samples */ + cc_offset = cc_offset * samples; + + current_avg_uA = ((di->charge_n1 - di->charge_n2 - cc_offset) + * di->current_max_scale) / + fuelgauge_rate[di->fuelgauge_mode]; + /* clock is a fixed 32Khz */ + current_avg_uA >>= 15; + + /* Correct for the fuelguage sampling rate */ + samples /= fuelgauge_rate[di->fuelgauge_mode] * 4; + + /* + * Only update the current average if we have had a valid number + * of samples in the accumulation. + */ + if (samples) { + current_avg_uA = current_avg_uA / samples; + di->current_avg_uA = current_avg_uA * 1000; + } + + schedule_delayed_work(&di->twl6030_current_avg_work, + msecs_to_jiffies(1000 * di->current_avg_interval)); + return; +err: + pr_err("%s: Error access to TWL6030 (%d)\n", __func__, ret); +} + +static int capacity_changed(struct twl6030_bci_device_info *di) +{ + int curr_capacity = di->capacity; + int charger_source = di->charger_source; + int charging_disabled = 0; + + /* Because system load is always greater than + * termination current, we will never get a CHARGE DONE + * int from BQ. And charging will alwys be in progress. + * We consider Vbat>3900 to be a full battery. + * Since Voltage measured during charging is Voreg ~4.2v, + * we dont update capacity if we are charging. + */ + + /* if it has been more than 10 minutes since our last update + * and we are charging we force a update. + */ + + if (time_after(jiffies, di->ac_next_refresh) + && (di->charger_source != POWER_SUPPLY_TYPE_BATTERY)) { + + charging_disabled = 1; + di->ac_next_refresh = jiffies + + msecs_to_jiffies(CHARGING_CAPACITY_UPDATE_PERIOD); + di->capacity = -1; + + /* We have to disable charging to read correct + * voltages. + */ + twl6030_stop_charger(di); + /*voltage setteling time*/ + msleep(200); + + di->voltage_mV = twl6030_get_gpadc_conversion(di, + di->gpadc_vbat_chnl); + } + + /* Setting the capacity level only makes sense when on + * the battery is powering the board. + */ + if (di->charge_status == POWER_SUPPLY_STATUS_DISCHARGING) { + if (di->voltage_mV < 3500) + curr_capacity = 5; + else if (di->voltage_mV < 3600 && di->voltage_mV >= 3500) + curr_capacity = 20; + else if (di->voltage_mV < 3700 && di->voltage_mV >= 3600) + curr_capacity = 50; + else if (di->voltage_mV < 3800 && di->voltage_mV >= 3700) + curr_capacity = 75; + else if (di->voltage_mV < 3900 && di->voltage_mV >= 3800) + curr_capacity = 90; + else if (di->voltage_mV >= 3900) + curr_capacity = 100; + } + + /* if we disabled charging to check capacity, + * enable it again after we read the + * correct voltage. + */ + if (charging_disabled) { + if (charger_source == POWER_SUPPLY_TYPE_MAINS) + twl6030_start_ac_charger(di); + else if (charger_source == POWER_SUPPLY_TYPE_USB) + twl6030_start_usb_charger(di); + } + + /* if battery is not present we assume it is on battery simulator and + * current capacity is set to 100% + */ + if (!is_battery_present(di)) + curr_capacity = 100; + + /* Debouncing of voltage change. */ + if (di->capacity == -1) { + di->capacity = curr_capacity; + di->capacity_debounce_count = 0; + return 1; + } + + if (curr_capacity != di->prev_capacity) { + di->prev_capacity = curr_capacity; + di->capacity_debounce_count = 0; + } else if (++di->capacity_debounce_count >= 4) { + di->capacity = curr_capacity; + di->capacity_debounce_count = 0; + return 1; + } + + return 0; +} + +static int twl6030_set_watchdog(struct twl6030_bci_device_info *di, int val) +{ + di->watchdog_duration = val; + + dev_dbg(di->dev, "Watchdog reset %d", val); + + return twl_i2c_write_u8(TWL6030_MODULE_CHARGER, val, CONTROLLER_WDG); + +} + +static void twl6030_bci_battery_work(struct work_struct *work) +{ + struct twl6030_bci_device_info *di = container_of(work, + struct twl6030_bci_device_info, twl6030_bci_monitor_work.work); + struct twl6030_gpadc_request req; + int adc_code; + int temp; + int ret; + + /* Kick the charger watchdog */ + if (di->charge_status == POWER_SUPPLY_STATUS_CHARGING) + twl6030_set_watchdog(di, di->watchdog_duration); + + req.method = TWL6030_GPADC_SW2; + req.channels = (1 << 1) | (1 << di->gpadc_vbat_chnl) | (1 << 8); + + req.active = 0; + req.func_cb = NULL; + ret = twl6030_gpadc_conversion(&req); + + schedule_delayed_work(&di->twl6030_bci_monitor_work, + msecs_to_jiffies(1000 * di->monitoring_interval)); + if (ret < 0) { + dev_dbg(di->dev, "gpadc conversion failed: %d\n", ret); + return; + } + + if (req.rbuf[di->gpadc_vbat_chnl] > 0) + di->voltage_mV = req.rbuf[di->gpadc_vbat_chnl]; + + if (req.rbuf[8] > 0) + di->bk_voltage_mV = req.rbuf[8]; + + if (di->platform_data->battery_tmp_tbl == NULL) + return; + + adc_code = req.rbuf[1]; + for (temp = 0; temp < di->platform_data->tblsize; temp++) { + if (adc_code >= di->platform_data-> + battery_tmp_tbl[temp]) + break; + } + + /* first 2 values are for negative temperature */ + di->temp_C = (temp - 2); /* in degrees Celsius */ + + if (capacity_changed(di)) + power_supply_changed(&di->bat); +} + +static void twl6030_current_mode_changed(struct twl6030_bci_device_info *di) +{ + int ret; + + /* FG_REG_01, 02, 03 is 24 bit unsigned sample counter value */ + ret = twl_i2c_read(TWL6030_MODULE_GASGAUGE, (u8 *) &di->timer_n1, + FG_REG_01, 3); + if (ret < 0) + goto err; + /* + * FG_REG_04, 5, 6, 7 is 32 bit signed accumulator value + * accumulates instantaneous current value + */ + ret = twl_i2c_read(TWL6030_MODULE_GASGAUGE, (u8 *) &di->charge_n1, + FG_REG_04, 4); + if (ret < 0) + goto err; + + cancel_delayed_work(&di->twl6030_current_avg_work); + schedule_delayed_work(&di->twl6030_current_avg_work, + msecs_to_jiffies(1000 * di->current_avg_interval)); + return; +err: + pr_err("%s: Error access to TWL6030 (%d)\n", __func__, ret); +} + +static void twl6030_work_interval_changed(struct twl6030_bci_device_info *di) +{ + cancel_delayed_work(&di->twl6030_bci_monitor_work); + schedule_delayed_work(&di->twl6030_bci_monitor_work, + msecs_to_jiffies(1000 * di->monitoring_interval)); +} + +#define to_twl6030_bci_device_info(x) container_of((x), \ + struct twl6030_bci_device_info, bat); + +static void twl6030_bci_battery_external_power_changed(struct power_supply *psy) +{ + struct twl6030_bci_device_info *di = to_twl6030_bci_device_info(psy); + + cancel_delayed_work(&di->twl6030_bci_monitor_work); + schedule_delayed_work(&di->twl6030_bci_monitor_work, 0); +} + +#define to_twl6030_ac_device_info(x) container_of((x), \ + struct twl6030_bci_device_info, ac); + +static int twl6030_ac_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct twl6030_bci_device_info *di = to_twl6030_ac_device_info(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = di->ac_online; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = twl6030_get_gpadc_conversion(di, 9) * 1000; + break; + default: + return -EINVAL; + } + + return 0; +} + +#define to_twl6030_usb_device_info(x) container_of((x), \ + struct twl6030_bci_device_info, usb); + +static int twl6030_usb_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct twl6030_bci_device_info *di = to_twl6030_usb_device_info(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = di->usb_online; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = twl6030_get_gpadc_conversion(di, 10) * 1000; + break; + default: + return -EINVAL; + } + + return 0; +} + +#define to_twl6030_bk_bci_device_info(x) container_of((x), \ + struct twl6030_bci_device_info, bk_bat); + +static int twl6030_bk_bci_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct twl6030_bci_device_info *di = to_twl6030_bk_bci_device_info(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = di->bk_voltage_mV * 1000; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int twl6030_bci_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct twl6030_bci_device_info *di; + + di = to_twl6030_bci_device_info(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = di->charge_status; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + di->voltage_mV = twl6030_get_gpadc_conversion(di, + di->gpadc_vbat_chnl); + val->intval = di->voltage_mV * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + twl6030battery_current(di); + val->intval = di->current_uA; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = di->temp_C; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = di->charger_source; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + val->intval = di->current_avg_uA; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = di->bat_health; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = di->capacity; + break; + default: + return -EINVAL; + } + return 0; +} + +int twl6030_register_notifier(struct notifier_block *nb, + unsigned int events) +{ + return blocking_notifier_chain_register(¬ifier_list, nb); +} +EXPORT_SYMBOL_GPL(twl6030_register_notifier); + +int twl6030_unregister_notifier(struct notifier_block *nb, + unsigned int events) +{ + return blocking_notifier_chain_unregister(¬ifier_list, nb); +} +EXPORT_SYMBOL_GPL(twl6030_unregister_notifier); + +static void twl6030_usb_charger_work(struct work_struct *work) +{ + struct twl6030_bci_device_info *di = + container_of(work, struct twl6030_bci_device_info, usb_work); + + switch (di->event) { + case USB_EVENT_CHARGER: + /* POWER_SUPPLY_TYPE_USB_DCP */ + di->usb_online = POWER_SUPPLY_TYPE_USB_DCP; + di->charger_incurrentmA = 1800; + break; + case USB_EVENT_VBUS: + switch (di->usb_online) { + case POWER_SUPPLY_TYPE_USB_CDP: + /* + * Only 500mA here or high speed chirp + * handshaking may break + */ + di->charger_incurrentmA = 500; + case POWER_SUPPLY_TYPE_USB: + break; + } + break; + case USB_EVENT_NONE: + di->usb_online = 0; + di->charger_incurrentmA = 0; + break; + case USB_EVENT_ENUMERATED: + if (di->usb_online == POWER_SUPPLY_TYPE_USB_CDP) + di->charger_incurrentmA = 560; + else + di->charger_incurrentmA = di->usb_max_power; + break; + default: + return; + } + twl6030_start_usb_charger(di); + power_supply_changed(&di->usb); +} + +static int twl6030_usb_notifier_call(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct twl6030_bci_device_info *di = + container_of(nb, struct twl6030_bci_device_info, nb); + + di->event = event; + switch (event) { + case USB_EVENT_VBUS: + di->usb_online = *((unsigned int *) data); + break; + case USB_EVENT_ENUMERATED: + di->usb_max_power = *((unsigned int *) data); + break; + case USB_EVENT_CHARGER: + case USB_EVENT_NONE: + break; + case USB_EVENT_ID: + default: + return NOTIFY_OK; + } + + schedule_work(&di->usb_work); + + return NOTIFY_OK; +} + +static ssize_t set_fg_mode(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + long val; + int status = count; + int ret; + struct twl6030_bci_device_info *di = dev_get_drvdata(dev); + + if ((strict_strtol(buf, 10, &val) < 0) || (val > 3)) + return -EINVAL; + di->fuelgauge_mode = val; + ret = twl_i2c_write_u8(TWL6030_MODULE_GASGAUGE, (val << 6) | CC_CAL_EN, + FG_REG_00); + if (ret) + return -EIO; + twl6030_current_mode_changed(di); + return status; +} + +static ssize_t show_fg_mode(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int val; + struct twl6030_bci_device_info *di = dev_get_drvdata(dev); + + val = di->fuelgauge_mode; + return sprintf(buf, "%d\n", val); +} + +static ssize_t set_charge_src(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + long val; + int status = count; + struct twl6030_bci_device_info *di = dev_get_drvdata(dev); + + if ((strict_strtol(buf, 10, &val) < 0) || (val < 2) || (val > 3)) + return -EINVAL; + di->vac_priority = val; + return status; +} + +static ssize_t show_charge_src(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int val; + struct twl6030_bci_device_info *di = dev_get_drvdata(dev); + + val = di->vac_priority; + return sprintf(buf, "%d\n", val); +} + +static ssize_t show_vbus_voltage(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int val; + struct twl6030_bci_device_info *di = dev_get_drvdata(dev); + + val = twl6030_get_gpadc_conversion(di, 10); + + return sprintf(buf, "%d\n", val); +} + +static ssize_t show_id_level(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int val; + struct twl6030_bci_device_info *di = dev_get_drvdata(dev); + + val = twl6030_get_gpadc_conversion(di, 14); + + return sprintf(buf, "%d\n", val); +} + +static ssize_t set_watchdog(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + long val; + int status = count; + int ret; + struct twl6030_bci_device_info *di = dev_get_drvdata(dev); + + if ((strict_strtol(buf, 10, &val) < 0) || (val < 1) || (val > 127)) + return -EINVAL; + ret = twl6030_set_watchdog(di, val); + if (ret) + return -EIO; + + return status; +} + +static ssize_t show_watchdog(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int val; + struct twl6030_bci_device_info *di = dev_get_drvdata(dev); + + val = di->watchdog_duration; + return sprintf(buf, "%d\n", val); +} + +static ssize_t show_fg_counter(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int fg_counter = 0; + int ret; + + ret = twl_i2c_read(TWL6030_MODULE_GASGAUGE, (u8 *) &fg_counter, + FG_REG_01, 3); + if (ret < 0) + return -EIO; + return sprintf(buf, "%d\n", fg_counter); +} + +static ssize_t show_fg_accumulator(struct device *dev, + struct device_attribute *attr, char *buf) +{ + long fg_accum = 0; + int ret; + + ret = twl_i2c_read(TWL6030_MODULE_GASGAUGE, (u8 *) &fg_accum, + FG_REG_04, 4); + if (ret > 0) + return -EIO; + + return sprintf(buf, "%ld\n", fg_accum); +} + +static ssize_t show_fg_offset(struct device *dev, + struct device_attribute *attr, char *buf) +{ + s16 fg_offset = 0; + int ret; + + ret = twl_i2c_read(TWL6030_MODULE_GASGAUGE, (u8 *) &fg_offset, + FG_REG_08, 2); + if (ret < 0) + return -EIO; + fg_offset = ((s16)(fg_offset << 6) >> 6); + + return sprintf(buf, "%d\n", fg_offset); +} + +static ssize_t set_fg_clear(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + long val; + int status = count; + int ret; + + if ((strict_strtol(buf, 10, &val) < 0) || (val != 1)) + return -EINVAL; + ret = twl_i2c_write_u8(TWL6030_MODULE_GASGAUGE, CC_AUTOCLEAR, + FG_REG_00); + if (ret) + return -EIO; + + return status; +} + +static ssize_t set_fg_cal(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + long val; + int status = count; + int ret; + + if ((strict_strtol(buf, 10, &val) < 0) || (val != 1)) + return -EINVAL; + ret = twl_i2c_write_u8(TWL6030_MODULE_GASGAUGE, CC_CAL_EN, FG_REG_00); + if (ret) + return -EIO; + + return status; +} + +static ssize_t set_charging(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int status = count; + struct twl6030_bci_device_info *di = dev_get_drvdata(dev); + + if (strncmp(buf, "startac", 7) == 0) { + if (di->charger_source == POWER_SUPPLY_TYPE_USB) + twl6030_stop_usb_charger(di); + twl6030_start_ac_charger(di); + } else if (strncmp(buf, "startusb", 8) == 0) { + if (di->charger_source == POWER_SUPPLY_TYPE_MAINS) + twl6030_stop_ac_charger(di); + di->charger_source = POWER_SUPPLY_TYPE_USB; + di->charge_status = POWER_SUPPLY_STATUS_CHARGING; + twl6030_start_usb_charger(di); + } else if (strncmp(buf, "stop" , 4) == 0) + twl6030_stop_charger(di); + else + return -EINVAL; + + return status; +} + +static ssize_t set_regulation_voltage(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + long val; + int status = count; + struct twl6030_bci_device_info *di = dev_get_drvdata(dev); + + if ((strict_strtol(buf, 10, &val) < 0) || (val < 3500) + || (val > di->platform_data->max_charger_voltagemV)) + return -EINVAL; + di->platform_data->max_bat_voltagemV = val; + twl6030_config_voreg_reg(di, val); + + return status; +} + +static ssize_t show_regulation_voltage(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned int val; + struct twl6030_bci_device_info *di = dev_get_drvdata(dev); + + val = di->platform_data->max_bat_voltagemV; + return sprintf(buf, "%u\n", val); +} + +static ssize_t set_termination_current(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + long val; + int status = count; + struct twl6030_bci_device_info *di = dev_get_drvdata(dev); + + if ((strict_strtol(buf, 10, &val) < 0) || (val < 50) || (val > 400)) + return -EINVAL; + di->platform_data->termination_currentmA = val; + twl6030_config_iterm_reg(di, val); + + return status; +} + +static ssize_t show_termination_current(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned int val; + struct twl6030_bci_device_info *di = dev_get_drvdata(dev); + + val = di->platform_data->termination_currentmA; + return sprintf(buf, "%u\n", val); +} + +static ssize_t set_cin_limit(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + long val; + int status = count; + struct twl6030_bci_device_info *di = dev_get_drvdata(dev); + + if ((strict_strtol(buf, 10, &val) < 0) || (val < 50) || (val > 1500)) + return -EINVAL; + di->charger_incurrentmA = val; + twl6030_config_cinlimit_reg(di, val); + + return status; +} + +static ssize_t show_cin_limit(struct device *dev, struct device_attribute *attr, + char *buf) +{ + unsigned int val; + struct twl6030_bci_device_info *di = dev_get_drvdata(dev); + + val = di->charger_incurrentmA; + return sprintf(buf, "%u\n", val); +} + +static ssize_t set_charge_current(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + long val; + int status = count; + struct twl6030_bci_device_info *di = dev_get_drvdata(dev); + + if ((strict_strtol(buf, 10, &val) < 0) || (val < 300) + || (val > di->platform_data->max_charger_currentmA)) + return -EINVAL; + di->charger_outcurrentmA = val; + twl6030_config_vichrg_reg(di, val); + + return status; +} + +static ssize_t show_charge_current(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned int val; + struct twl6030_bci_device_info *di = dev_get_drvdata(dev); + + val = di->charger_outcurrentmA; + return sprintf(buf, "%u\n", val); +} + +static ssize_t set_min_vbus(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + long val; + int status = count; + struct twl6030_bci_device_info *di = dev_get_drvdata(dev); + + if ((strict_strtol(buf, 10, &val) < 0) || (val < 4200) || (val > 4760)) + return -EINVAL; + di->min_vbus = val; + twl6030_config_min_vbus_reg(di, val); + + return status; +} + +static ssize_t show_min_vbus(struct device *dev, struct device_attribute *attr, + char *buf) +{ + unsigned int val; + struct twl6030_bci_device_info *di = dev_get_drvdata(dev); + + val = di->min_vbus; + return sprintf(buf, "%u\n", val); +} + +static ssize_t set_current_avg_interval(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + long val; + int status = count; + struct twl6030_bci_device_info *di = dev_get_drvdata(dev); + + if ((strict_strtol(buf, 10, &val) < 0) || (val < 10) || (val > 3600)) + return -EINVAL; + di->current_avg_interval = val; + twl6030_current_mode_changed(di); + + return status; +} + +static ssize_t show_current_avg_interval(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned int val; + struct twl6030_bci_device_info *di = dev_get_drvdata(dev); + + val = di->current_avg_interval; + return sprintf(buf, "%u\n", val); +} + +static ssize_t set_wakelock_enable(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + long val; + int status = count; + struct twl6030_bci_device_info *di = dev_get_drvdata(dev); + + if ((strict_strtol(buf, 10, &val) < 0) || (val < 0) || (val > 1)) + return -EINVAL; + + if ((val) && (di->charger_source == POWER_SUPPLY_TYPE_MAINS)) + wake_lock(&chrg_lock); + else + wake_unlock(&chrg_lock); + + di->wakelock_enabled = val; + return status; +} + +static ssize_t show_wakelock_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned int val; + struct twl6030_bci_device_info *di = dev_get_drvdata(dev); + + val = di->wakelock_enabled; + return sprintf(buf, "%u\n", val); +} + +static ssize_t set_monitoring_interval(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + long val; + int status = count; + struct twl6030_bci_device_info *di = dev_get_drvdata(dev); + + if ((strict_strtol(buf, 10, &val) < 0) || (val < 10) || (val > 3600)) + return -EINVAL; + di->monitoring_interval = val; + twl6030_work_interval_changed(di); + + return status; +} + +static ssize_t show_monitoring_interval(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned int val; + struct twl6030_bci_device_info *di = dev_get_drvdata(dev); + + val = di->monitoring_interval; + return sprintf(buf, "%u\n", val); +} + +static ssize_t show_bsi(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int val; + struct twl6030_bci_device_info *di = dev_get_drvdata(dev); + + val = twl6030_get_gpadc_conversion(di, 0); + return sprintf(buf, "%d\n", val); +} + +static ssize_t show_stat1(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned val; + struct twl6030_bci_device_info *di = dev_get_drvdata(dev); + + val = di->stat1; + return sprintf(buf, "%u\n", val); +} + +static ssize_t show_status_int1(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned val; + struct twl6030_bci_device_info *di = dev_get_drvdata(dev); + + val = di->status_int1; + return sprintf(buf, "%u\n", val); +} + +static ssize_t show_status_int2(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned val; + struct twl6030_bci_device_info *di = dev_get_drvdata(dev); + + val = di->status_int2; + return sprintf(buf, "%u\n", val); +} + +static DEVICE_ATTR(fg_mode, S_IWUSR | S_IRUGO, show_fg_mode, set_fg_mode); +static DEVICE_ATTR(charge_src, S_IWUSR | S_IRUGO, show_charge_src, + set_charge_src); +static DEVICE_ATTR(vbus_voltage, S_IRUGO, show_vbus_voltage, NULL); +static DEVICE_ATTR(id_level, S_IRUGO, show_id_level, NULL); +static DEVICE_ATTR(watchdog, S_IWUSR | S_IRUGO, show_watchdog, set_watchdog); +static DEVICE_ATTR(fg_counter, S_IRUGO, show_fg_counter, NULL); +static DEVICE_ATTR(fg_accumulator, S_IRUGO, show_fg_accumulator, NULL); +static DEVICE_ATTR(fg_offset, S_IRUGO, show_fg_offset, NULL); +static DEVICE_ATTR(fg_clear, S_IWUSR, NULL, set_fg_clear); +static DEVICE_ATTR(fg_cal, S_IWUSR, NULL, set_fg_cal); +static DEVICE_ATTR(charging, S_IWUSR | S_IRUGO, NULL, set_charging); +static DEVICE_ATTR(regulation_voltage, S_IWUSR | S_IRUGO, + show_regulation_voltage, set_regulation_voltage); +static DEVICE_ATTR(termination_current, S_IWUSR | S_IRUGO, + show_termination_current, set_termination_current); +static DEVICE_ATTR(cin_limit, S_IWUSR | S_IRUGO, show_cin_limit, + set_cin_limit); +static DEVICE_ATTR(charge_current, S_IWUSR | S_IRUGO, show_charge_current, + set_charge_current); +static DEVICE_ATTR(min_vbus, S_IWUSR | S_IRUGO, show_min_vbus, set_min_vbus); +static DEVICE_ATTR(monitoring_interval, S_IWUSR | S_IRUGO, + show_monitoring_interval, set_monitoring_interval); +static DEVICE_ATTR(current_avg_interval, S_IWUSR | S_IRUGO, + show_current_avg_interval, set_current_avg_interval); +static DEVICE_ATTR(wakelock_enable, S_IWUSR | S_IRUGO, + show_wakelock_enable, set_wakelock_enable); +static DEVICE_ATTR(bsi, S_IRUGO, show_bsi, NULL); +static DEVICE_ATTR(stat1, S_IRUGO, show_stat1, NULL); +static DEVICE_ATTR(status_int1, S_IRUGO, show_status_int1, NULL); +static DEVICE_ATTR(status_int2, S_IRUGO, show_status_int2, NULL); + +static struct attribute *twl6030_bci_attributes[] = { + &dev_attr_fg_mode.attr, + &dev_attr_charge_src.attr, + &dev_attr_vbus_voltage.attr, + &dev_attr_id_level.attr, + &dev_attr_watchdog.attr, + &dev_attr_fg_counter.attr, + &dev_attr_fg_accumulator.attr, + &dev_attr_fg_offset.attr, + &dev_attr_fg_clear.attr, + &dev_attr_fg_cal.attr, + &dev_attr_charging.attr, + &dev_attr_regulation_voltage.attr, + &dev_attr_termination_current.attr, + &dev_attr_cin_limit.attr, + &dev_attr_charge_current.attr, + &dev_attr_min_vbus.attr, + &dev_attr_monitoring_interval.attr, + &dev_attr_current_avg_interval.attr, + &dev_attr_bsi.attr, + &dev_attr_stat1.attr, + &dev_attr_status_int1.attr, + &dev_attr_status_int2.attr, + &dev_attr_wakelock_enable.attr, + NULL, +}; + +static const struct attribute_group twl6030_bci_attr_group = { + .attrs = twl6030_bci_attributes, +}; + +static char *twl6030_bci_supplied_to[] = { + "twl6030_battery", +}; + +static int __devinit twl6030_bci_battery_probe(struct platform_device *pdev) +{ + struct twl4030_bci_platform_data *pdata = pdev->dev.platform_data; + struct twl6030_bci_device_info *di; + int irq; + int ret; + u8 controller_stat = 0; + u8 chargerusb_ctrl1 = 0; + u8 hw_state = 0; + u8 reg = 0; + + if (!pdata) { + dev_dbg(&pdev->dev, "platform_data not available\n"); + return -EINVAL; + } + + di = kzalloc(sizeof(*di), GFP_KERNEL); + if (!di) + return -ENOMEM; + + di->platform_data = kmemdup(pdata, sizeof(*pdata), GFP_KERNEL); + if (!di->platform_data) { + kfree(di); + return -ENOMEM; + } + + if (pdata->monitoring_interval == 0) { + di->monitoring_interval = 10; + di->current_avg_interval = 10; + } else { + di->monitoring_interval = pdata->monitoring_interval; + di->current_avg_interval = pdata->monitoring_interval; + } + + di->platform_data = pdata; + di->features = pdata->features; + di->dev = &pdev->dev; + + if (di->features & TWL6032_SUBCLASS) { + ret = twl_i2c_read_u8(TWL_MODULE_RTC, ®, CHARGER_MODE_REG); + if (ret) + goto temp_setup_fail; + + if (reg & CHARGER_MODE_POWERPATH) { + dev_dbg(di->dev, "Charger: PowerPath\n"); + di->use_power_path = 1; + } else { + dev_dbg(di->dev, "Charger: NON PowerPath\n"); + di->use_power_path = 0; + } + + if (reg & CHARGER_MODE_AUTOCHARGE) { + dev_dbg(di->dev, "Charger: AutoCharge\n"); + di->use_hw_charger = 1; + } else { + dev_dbg(di->dev, "Charger: NON AutoCharge\n"); + di->use_hw_charger = 0; + } + } else { + di->use_power_path = 0; + di->use_hw_charger = 0; + } + + if (di->use_hw_charger) { + di->platform_data->max_charger_currentmA = + twl6030_get_limit2_reg(di); + di->platform_data->max_charger_voltagemV = + twl6030_get_limit1_reg(di); + di->platform_data->termination_currentmA = + twl6030_get_iterm_reg(di); + di->platform_data->max_bat_voltagemV = + twl6030_get_voreg_reg(di); + } + + di->bat.name = "twl6030_battery"; + di->bat.supplied_to = twl6030_bci_supplied_to; + di->bat.num_supplicants = ARRAY_SIZE(twl6030_bci_supplied_to); + di->bat.type = POWER_SUPPLY_TYPE_BATTERY; + di->bat.properties = twl6030_bci_battery_props; + di->bat.num_properties = ARRAY_SIZE(twl6030_bci_battery_props); + di->bat.get_property = twl6030_bci_battery_get_property; + di->bat.external_power_changed = + twl6030_bci_battery_external_power_changed; + di->bat_health = POWER_SUPPLY_HEALTH_GOOD; + + di->usb.name = "twl6030_usb"; + di->usb.type = POWER_SUPPLY_TYPE_USB; + di->usb.properties = twl6030_usb_props; + di->usb.num_properties = ARRAY_SIZE(twl6030_usb_props); + di->usb.get_property = twl6030_usb_get_property; + + di->ac.name = "twl6030_ac"; + di->ac.type = POWER_SUPPLY_TYPE_MAINS; + di->ac.properties = twl6030_ac_props; + di->ac.num_properties = ARRAY_SIZE(twl6030_ac_props); + di->ac.get_property = twl6030_ac_get_property; + + di->charge_status = POWER_SUPPLY_STATUS_DISCHARGING; + + di->bk_bat.name = "twl6030_bk_battery"; + di->bk_bat.type = POWER_SUPPLY_TYPE_BATTERY; + di->bk_bat.properties = twl6030_bk_bci_battery_props; + di->bk_bat.num_properties = ARRAY_SIZE(twl6030_bk_bci_battery_props); + di->bk_bat.get_property = twl6030_bk_bci_battery_get_property; + + di->vac_priority = 2; + di->capacity = -1; + di->capacity_debounce_count = 0; + di->ac_next_refresh = jiffies - 1; + platform_set_drvdata(pdev, di); + + /* calculate current max scale from sense */ + if (pdata->sense_resistor_mohm) { + di->current_max_scale = (62000) / pdata->sense_resistor_mohm; + } else { + /* Set sensible defaults if platform data is missing */ + if (di->features & TWL6032_SUBCLASS) + di->current_max_scale = 3100; + else + di->current_max_scale = 6200; + } + + wake_lock_init(&chrg_lock, WAKE_LOCK_SUSPEND, "ac_chrg_wake_lock"); + /* settings for temperature sensing */ + ret = twl6030battery_temp_setup(true); + if (ret) + goto temp_setup_fail; + + /* request charger fault interruption choosing between sw/hw mode */ + irq = platform_get_irq(pdev, 1); + if (!di->use_hw_charger) + ret = request_threaded_irq(irq, NULL, + twl6030charger_fault_interrupt, + 0, "twl_bci_fault", di); + else + ret = request_threaded_irq(irq, NULL, + twl6032charger_fault_interrupt_hw, + 0, "twl_bci_fault", di); + + if (ret) { + dev_dbg(&pdev->dev, "could not request irq %d, status %d\n", + irq, ret); + goto temp_setup_fail; + } + + /* request charger ctrl interruption choosing between sw/hw mode */ + irq = platform_get_irq(pdev, 0); + if (!di->use_hw_charger) + ret = request_threaded_irq(irq, NULL, + twl6030charger_ctrl_interrupt, + 0, "twl_bci_ctrl", di); + else + ret = request_threaded_irq(irq, NULL, + twl6032charger_ctrl_interrupt_hw, + 0, "twl_bci_ctrl", 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; + } + + ret = power_supply_register(&pdev->dev, &di->usb); + if (ret) { + dev_dbg(&pdev->dev, "failed to register usb power supply\n"); + goto usb_failed; + } + + ret = power_supply_register(&pdev->dev, &di->ac); + if (ret) { + dev_dbg(&pdev->dev, "failed to register ac power supply\n"); + goto ac_failed; + } + + 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; + } + di->charge_n1 = 0; + di->timer_n1 = 0; + + INIT_DELAYED_WORK_DEFERRABLE(&di->twl6030_bci_monitor_work, + twl6030_bci_battery_work); + schedule_delayed_work(&di->twl6030_bci_monitor_work, 0); + + INIT_DELAYED_WORK_DEFERRABLE(&di->twl6030_current_avg_work, + twl6030_current_avg); + schedule_delayed_work(&di->twl6030_current_avg_work, 500); + + ret = twl6030battery_voltage_setup(di); + if (ret) + dev_dbg(&pdev->dev, "voltage measurement setup failed\n"); + + ret = twl6030battery_current_setup(true); + if (ret) + dev_dbg(&pdev->dev, "current measurement setup failed\n"); + + /* initialize for USB charging */ + if (!di->use_hw_charger) { + twl6030_config_limit1_reg(di, pdata->max_charger_voltagemV); + twl6030_config_limit2_reg(di, + di->platform_data->max_charger_currentmA); + } + ret = twl_i2c_write_u8(TWL6030_MODULE_CHARGER, MBAT_TEMP, + CONTROLLER_INT_MASK); + if (ret) + goto bk_batt_failed; + + ret = twl_i2c_write_u8(TWL6030_MODULE_CHARGER, MASK_MCHARGERUSB_THMREG, + CHARGERUSB_INT_MASK); + if (ret) + goto bk_batt_failed; + + ret = twl_i2c_read_u8(TWL6030_MODULE_CHARGER, &controller_stat, + CONTROLLER_STAT1); + if (ret) + goto bk_batt_failed; + + di->stat1 = controller_stat; + di->charger_outcurrentmA = di->platform_data->max_charger_currentmA; + + twl6030_set_watchdog(di, 32); + + INIT_WORK(&di->usb_work, twl6030_usb_charger_work); + di->nb.notifier_call = twl6030_usb_notifier_call; + di->otg = otg_get_transceiver(); + if (di->otg) { + ret = otg_register_notifier(di->otg, &di->nb); + if (ret) + dev_err(&pdev->dev, "otg register notifier" + " failed %d\n", ret); + } else + dev_err(&pdev->dev, "otg_get_transceiver failed %d\n", ret); + + if (di->features & TWL6032_SUBCLASS) { + di->charger_incurrentmA = 100; + di->gpadc_vbat_chnl = TWL6032_GPADC_VBAT_CHNL; + } else { + di->charger_incurrentmA = twl6030_get_usb_max_power(di->otg); + di->gpadc_vbat_chnl = TWL6030_GPADC_VBAT_CHNL; + } + + di->voltage_mV = twl6030_get_gpadc_conversion(di, di->gpadc_vbat_chnl); + dev_info(&pdev->dev, "Battery Voltage at Bootup is %d mV\n", + di->voltage_mV); + + ret = twl_i2c_read_u8(TWL6030_MODULE_ID0, &hw_state, STS_HW_CONDITIONS); + if (ret) + goto bk_batt_failed; + if (!is_battery_present(di)) { + if (!(hw_state & STS_USB_ID)) { + dev_dbg(di->dev, "Put USB in HZ mode\n"); + ret = twl_i2c_read_u8(TWL6030_MODULE_CHARGER, + &chargerusb_ctrl1, CHARGERUSB_CTRL1); + if (ret) + goto bk_batt_failed; + + chargerusb_ctrl1 |= HZ_MODE; + ret = twl_i2c_write_u8(TWL6030_MODULE_CHARGER, + chargerusb_ctrl1, CHARGERUSB_CTRL1); + if (ret) + goto bk_batt_failed; + } + } else if (!di->use_hw_charger) { + if (controller_stat & VAC_DET) { + di->ac_online = POWER_SUPPLY_TYPE_MAINS; + twl6030_start_ac_charger(di); + } else if (controller_stat & VBUS_DET) { + /* + * In HOST mode (ID GROUND) with a device connected, + * do no enable usb charging + */ + if (!(hw_state & STS_USB_ID)) { + di->usb_online = POWER_SUPPLY_TYPE_USB; + di->charger_source = POWER_SUPPLY_TYPE_USB; + di->charge_status = + POWER_SUPPLY_STATUS_CHARGING; + di->event = USB_EVENT_VBUS; + schedule_work(&di->usb_work); + } + } + } else { + int fault, charge_usb, charge_ac; + + twl_i2c_read_u8(TWL6032_MODULE_CHARGER, ®, + CHARGERUSB_INT_STATUS); + + fault = !(di->stat1 & CONTROLLER_STAT1_LINCH_GATED) && + !(di->stat1 & CONTROLLER_STAT1_FAULT_WDG); + charge_usb = (di->stat1 & VBUS_DET) && + !(reg & CHARGERUSB_FAULT); + charge_ac = (di->stat1 & VAC_DET) && + !(di->stat1 & CONTROLLER_STAT1_EXTCHRG_STATZ); + + dev_dbg(di->dev, "boot charge state fault %d, usb %d, ac %d\n", + fault, charge_usb, charge_ac); + + if (fault && (charge_usb || charge_ac)) + di->charge_status = POWER_SUPPLY_STATUS_CHARGING; + else { + if (di->stat1 & (VBUS_DET | VAC_DET)) + di->charge_status = + POWER_SUPPLY_STATUS_NOT_CHARGING; + else + di->charge_status = + POWER_SUPPLY_STATUS_DISCHARGING; + } + } + + ret = twl6030backupbatt_setup(); + if (ret) + dev_dbg(&pdev->dev, "Backup Bat charging setup failed\n"); + + twl6030_interrupt_unmask(TWL6030_CHARGER_CTRL_INT_MASK, + REG_INT_MSK_LINE_C); + twl6030_interrupt_unmask(TWL6030_CHARGER_CTRL_INT_MASK, + REG_INT_MSK_STS_C); + twl6030_interrupt_unmask(TWL6030_CHARGER_FAULT_INT_MASK, + REG_INT_MSK_LINE_C); + twl6030_interrupt_unmask(TWL6030_CHARGER_FAULT_INT_MASK, + REG_INT_MSK_STS_C); + + ret = sysfs_create_group(&pdev->dev.kobj, &twl6030_bci_attr_group); + if (ret) + dev_dbg(&pdev->dev, "could not create sysfs files\n"); + + return 0; + +bk_batt_failed: + cancel_delayed_work(&di->twl6030_bci_monitor_work); + power_supply_unregister(&di->ac); +ac_failed: + power_supply_unregister(&di->usb); +usb_failed: + power_supply_unregister(&di->bat); +batt_failed: + free_irq(irq, di); +chg_irq_fail: + irq = platform_get_irq(pdev, 1); + free_irq(irq, di); +temp_setup_fail: + wake_lock_destroy(&chrg_lock); + platform_set_drvdata(pdev, NULL); + kfree(di); + + return ret; +} + +static int __devexit twl6030_bci_battery_remove(struct platform_device *pdev) +{ + struct twl6030_bci_device_info *di = platform_get_drvdata(pdev); + int irq; + + twl6030_interrupt_mask(TWL6030_CHARGER_CTRL_INT_MASK, + REG_INT_MSK_LINE_C); + twl6030_interrupt_mask(TWL6030_CHARGER_CTRL_INT_MASK, + REG_INT_MSK_STS_C); + twl6030_interrupt_mask(TWL6030_CHARGER_FAULT_INT_MASK, + REG_INT_MSK_LINE_C); + twl6030_interrupt_mask(TWL6030_CHARGER_FAULT_INT_MASK, + REG_INT_MSK_STS_C); + + irq = platform_get_irq(pdev, 0); + free_irq(irq, di); + + irq = platform_get_irq(pdev, 1); + free_irq(irq, di); + + otg_unregister_notifier(di->otg, &di->nb); + sysfs_remove_group(&pdev->dev.kobj, &twl6030_bci_attr_group); + cancel_delayed_work(&di->twl6030_bci_monitor_work); + cancel_delayed_work(&di->twl6030_current_avg_work); + flush_scheduled_work(); + power_supply_unregister(&di->bat); + power_supply_unregister(&di->usb); + power_supply_unregister(&di->ac); + power_supply_unregister(&di->bk_bat); + wake_lock_destroy(&chrg_lock); + platform_set_drvdata(pdev, NULL); + kfree(di->platform_data); + kfree(di); + + return 0; +} + +#ifdef CONFIG_PM +static int twl6030_bci_battery_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct twl6030_bci_device_info *di = platform_get_drvdata(pdev); + long int events; + u8 rd_reg = 0; + int ret; + + /* mask to prevent wakeup due to 32s timeout from External charger */ + ret = twl_i2c_read_u8(TWL6030_MODULE_CHARGER, &rd_reg, + CONTROLLER_INT_MASK); + if (ret) + goto err; + + rd_reg |= MVAC_FAULT; + ret = twl_i2c_write_u8(TWL6030_MODULE_CHARGER, MBAT_TEMP, + CONTROLLER_INT_MASK); + if (ret) + goto err; + + cancel_delayed_work(&di->twl6030_bci_monitor_work); + cancel_delayed_work(&di->twl6030_current_avg_work); + + /* We cannot tolarate a sleep longer than 30 seconds + * while on ac charging we have to reset the BQ watchdog timer. + */ + if ((di->charger_source == POWER_SUPPLY_TYPE_MAINS) && + ((wakeup_timer_seconds > 25) || !wakeup_timer_seconds)) { + wakeup_timer_seconds = 25; + } + + /*reset the BQ watch dog*/ + events = BQ2415x_RESET_TIMER; + blocking_notifier_call_chain(¬ifier_list, events, NULL); + + ret = twl6030battery_temp_setup(false); + if (ret) { + pr_err("%s: Temp measurement setup failed (%d)!\n", + __func__, ret); + return ret; + } + + ret = twl6030battery_current_setup(false); + if (ret) { + pr_err("%s: Current measurement setup failed (%d)!\n", + __func__, ret); + return ret; + } + + return 0; +err: + pr_err("%s: Error access to TWL6030 (%d)\n", __func__, ret); + return ret; +} + +static int twl6030_bci_battery_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct twl6030_bci_device_info *di = platform_get_drvdata(pdev); + long int events; + u8 rd_reg = 0; + int ret; + + ret = twl6030battery_temp_setup(true); + if (ret) { + pr_err("%s: Temp measurement setup failed (%d)!\n", + __func__, ret); + return ret; + } + + ret = twl6030battery_current_setup(true); + if (ret) { + pr_err("%s: Current measurement setup failed (%d)!\n", + __func__, ret); + return ret; + } + + ret = twl_i2c_read_u8(TWL6030_MODULE_CHARGER, &rd_reg, CONTROLLER_INT_MASK); + if (ret) + goto err; + + rd_reg &= ~(0xFF & MVAC_FAULT); + ret = twl_i2c_write_u8(TWL6030_MODULE_CHARGER, MBAT_TEMP, + CONTROLLER_INT_MASK); + if (ret) + goto err; + + schedule_delayed_work(&di->twl6030_bci_monitor_work, 0); + schedule_delayed_work(&di->twl6030_current_avg_work, 50); + + events = BQ2415x_RESET_TIMER; + blocking_notifier_call_chain(¬ifier_list, events, NULL); + + return 0; +err: + pr_err("%s: Error access to TWL6030 (%d)\n", __func__, ret); + return ret; +} +#else +#define twl6030_bci_battery_suspend NULL +#define twl6030_bci_battery_resume NULL +#endif /* CONFIG_PM */ + +static const struct dev_pm_ops pm_ops = { + .suspend = twl6030_bci_battery_suspend, + .resume = twl6030_bci_battery_resume, +}; + +static struct platform_driver twl6030_bci_battery_driver = { + .probe = twl6030_bci_battery_probe, + .remove = __devexit_p(twl6030_bci_battery_remove), + .driver = { + .name = "twl6030_bci", + .pm = &pm_ops, + }, +}; + +static int __init twl6030_battery_init(void) +{ + return platform_driver_register(&twl6030_bci_battery_driver); +} +module_init(twl6030_battery_init); + +static void __exit twl6030_battery_exit(void) +{ + platform_driver_unregister(&twl6030_bci_battery_driver); +} +module_exit(twl6030_battery_exit); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:twl6030_bci"); +MODULE_AUTHOR("Texas Instruments Inc"); diff --git a/include/linux/i2c/bq2415x.h b/include/linux/i2c/bq2415x.h new file mode 100644 index 000000000000..859e3cfb6f5f --- /dev/null +++ b/include/linux/i2c/bq2415x.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2010 Texas Instruments + * Author: Balaji T K + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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, see . + */ + +#ifndef _LINUX_BQ2415X_I2C_H +#define _LINUX_BQ2415X_I2C_H + +#define BQ2415x_START_CHARGING (1 << 0) +#define BQ2415x_STOP_CHARGING (1 << 1) +#define BQ2415x_CHARGER_FAULT (1 << 2) + +#define BQ2415x_CHARGE_DONE 0x20 +#define BQ2415x_FAULT_VBUS_OVP 0x31 +#define BQ2415x_FAULT_SLEEP 0x32 +#define BQ2415x_FAULT_BAD_ADAPTOR 0x33 +#define BQ2415x_FAULT_BAT_OVP 0x34 +#define BQ2415x_FAULT_THERMAL_SHUTDOWN 0x35 +#define BQ2415x_FAULT_TIMER 0x36 +#define BQ2415x_FAULT_NO_BATTERY 0x37 + +/* not a bq generated event,we use this to reset the + * the timer from the twl driver. + */ +#define BQ2415x_RESET_TIMER 0x38 + +/* BQ24153 / BQ24156 / BQ24158 */ +/* Status/Control Register */ +#define REG_STATUS_CONTROL 0x00 +#define TIMER_RST (1 << 7) +#define ENABLE_STAT_PIN (1 << 6) + +/* Control Register */ +#define REG_CONTROL_REGISTER 0x01 +#define INPUT_CURRENT_LIMIT_SHIFT 6 +#define ENABLE_ITERM_SHIFT 3 + +/* Control/Battery Voltage Register */ +#define REG_BATTERY_VOLTAGE 0x02 +#define VOLTAGE_SHIFT 2 + +/* Vender/Part/Revision Register */ +#define REG_PART_REVISION 0x03 + +/* Battery Termination/Fast Charge Current Register */ +#define REG_BATTERY_CURRENT 0x04 +#define BQ24156_CURRENT_SHIFT 3 +#define BQ24153_CURRENT_SHIFT 4 + +/* Special Charger Voltage/Enable Pin Status Register */ +#define REG_SPECIAL_CHARGER_VOLTAGE 0x05 + +/* Safety Limit Register */ +#define REG_SAFETY_LIMIT 0x06 +#define MAX_CURRENT_SHIFT 4 + +#define BQ24153 (1 << 3) +#define BQ24156 (1 << 6) +#define BQ24158 (1 << 8) + +#define BQ2415x_WATCHDOG_TIMEOUT 20000 + +struct bq2415x_platform_data { + int max_charger_currentmA; + int max_charger_voltagemV; + int termination_currentmA; +}; + +#endif diff --git a/include/linux/i2c/twl6030-gpadc.h b/include/linux/i2c/twl6030-gpadc.h new file mode 100644 index 000000000000..8d952a7a692a --- /dev/null +++ b/include/linux/i2c/twl6030-gpadc.h @@ -0,0 +1,152 @@ +/* + * include/linux/i2c/twl6030-gpadc.h + * + * TWL6030 GPADC module driver header + * + * Copyright (C) 2009 Texas Instruments Inc. + * Nishant Kamat + * + * Based on twl4030-madc.h + * Copyright (C) 2008 Nokia Corporation + * Mikko Ylinen + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef _TWL6030_GPADC_H +#define _TWL6030_GPADC_H + +struct twl6030_gpadc_conversion_method { + u8 sel; + u8 rbase; + u8 ctrl; + u8 enable; + u8 mask; +}; + +#define TWL6030_GPADC_MAX_CHANNELS 17 +#define TWL6032_GPADC_MAX_CHANNELS 19 +/* Define this as the biggest of all chips using this driver */ +#define GPADC_MAX_CHANNELS TWL6032_GPADC_MAX_CHANNELS + +/* + * raw_code - raw adc value + * raw_channel_value - adc * channel gain + * code - calibrated adc value + */ +struct twl6030_value { + int raw_code; + int raw_channel_value; + int code; +}; + +struct twl6030_gpadc_request { + u32 channels; + u16 do_avg; + u16 method; + u16 type; + int active; + int result_pending; + int rbuf[TWL6032_GPADC_MAX_CHANNELS]; + void (*func_cb)(struct twl6030_gpadc_request *req); + struct twl6030_value buf[TWL6032_GPADC_MAX_CHANNELS]; +}; + +enum conversion_methods { + TWL6030_GPADC_RT, + TWL6030_GPADC_SW2, + TWL6030_GPADC_NUM_METHODS +}; + +enum sample_type { + TWL6030_GPADC_WAIT, + TWL6030_GPADC_IRQ_ONESHOT, + TWL6030_GPADC_IRQ_REARM +}; + +#define TWL6030_GPADC_CTRL 0x00 /* 0x2e */ +#define TWL6030_GPADC_CTRL2 0x01 /* 0x2f */ + +#define TWL6030_GPADC_RTSELECT_LSB 0x02 /* 0x30 */ +#define TWL6030_GPADC_RTSELECT_ISB 0x03 +#define TWL6030_GPADC_RTSELECT_MSB 0x04 + +#define TWL6032_GPADC_RTSELECT_LSB 0x04 /* 0x32 */ +#define TWL6032_GPADC_RTSELECT_ISB 0x05 +#define TWL6032_GPADC_RTSELECT_MSB 0x06 + +#define TWL6030_GPADC_CTRL_P1 0x05 +#define TWL6030_GPADC_CTRL_P2 0x06 + +#define TWL6032_GPADC_GPSELECT_ISB 0x07 +#define TWL6032_GPADC_CTRL_P1 0x08 + +#define TWL6032_RTCH0_LSB 0x09 +#define TWL6032_RTCH0_MSB 0x0a +#define TWL6032_RTCH1_LSB 0x0b +#define TWL6032_RTCH1_MSB 0x0c +#define TWL6032_GPCH0_LSB 0x0d +#define TWL6032_GPCH0_MSB 0x0e + +#define TWL6030_GPADC_CTRL_P1_SP1 (1 << 3) +#define TWL6030_GPADC_CTRL_P1_EOCRT (1 << 2) +#define TWL6030_GPADC_CTRL_P1_EOCP1 (1 << 1) +#define TWL6030_GPADC_CTRL_P1_BUSY (1 << 0) + +#define TWL6030_GPADC_CTRL_P2_SP2 (1 << 2) +#define TWL6030_GPADC_CTRL_P2_EOCP2 (1 << 1) +#define TWL6030_GPADC_CTRL_P1_BUSY (1 << 0) + +#define TWL6030_GPADC_EOC_SW (1 << 1) +#define TWL6030_GPADC_BUSY (1 << 0) + +#define TWL6030_GPADC_RTCH0_LSB (0x07) +#define TWL6030_GPADC_GPCH0_LSB (0x29) + +/* Fixed channels */ +#define TWL6030_GPADC_CTRL_TEMP1_EN (1 << 0) /* input ch 1 */ +#define TWL6030_GPADC_CTRL_TEMP2_EN (1 << 1) /* input ch 4 */ +#define TWL6030_GPADC_CTRL_SCALER_EN (1 << 2) /* input ch 2 */ +#define TWL6030_GPADC_CTRL_SCALER_DIV4 (1 << 3) +#define TWL6030_GPADC_CTRL_SCALER_EN_CH11 (1 << 4) /* input ch 11 */ +#define TWL6030_GPADC_CTRL_TEMP1_EN_MONITOR (1 << 5) +#define TWL6030_GPADC_CTRL_TEMP2_EN_MONITOR (1 << 6) +#define TWL6030_GPADC_CTRL_ISOURCE_EN (1 << 7) + +#define TWL6030_GPADC_CTRL2_REMSENSE_0 (1 << 0) +#define TWL6030_GPADC_CTRL2_REMSENSE_1 (1 << 1) +#define TWL6030_GPADC_CTRL2_SCALER_EN_CH18 (1 << 2) +#define TWL6030_GPADC_CTRL2_VBAT_SCALER_DIV4 (1 << 3) + +#define TWL6030_GPADC_RT_SW1_EOC_MASK (1 << 5) +#define TWL6030_GPADC_SW2_EOC_MASK (1 << 6) + +#define TWL6032_GPADC_RT_EOC_MASK (1 << 4) +#define TWL6032_GPADC_SW_EOC_MASK (1 << 5) + +#define TWL6030_GPADC_IOC_MAGIC '`' +#define TWL6030_GPADC_IOCX_ADC_RAW_READ _IO(TWL6030_GPADC_IOC_MAGIC, 0) +#define TWL6030_GPADC_IOCX_ADC_READ _IO(TWL6030_GPADC_IOC_MAGIC+1, 0) + +struct twl6030_gpadc_user_parms { + int channel; + int status; + u16 result; +}; + +int twl6030_gpadc_conversion(struct twl6030_gpadc_request *conv); + +#endif diff --git a/include/linux/i2c/twl6030-madc.h b/include/linux/i2c/twl6030-madc.h new file mode 100644 index 000000000000..81b946478d8a --- /dev/null +++ b/include/linux/i2c/twl6030-madc.h @@ -0,0 +1,86 @@ +/* + * twl6030_madc.h - Header for TWL6030 MADC + * + * Copyright (C) 2011 Samsung Telecommunications of America + * + * Based on twl4030-madc.h + * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/ + * J Keerthy + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef _TWL6030_MADC_H +#define _TWL6030_MADC_H + +#define TWL6030_MADC_MAX_CHANNELS 17 +/* + * twl6030 madc occupies the same offset in the twl6030 map that + * twl4030 madc does in the twl4030 map. + * likewise the charger + */ +#define TWL6030_MODULE_MADC TWL4030_MODULE_MADC +#define TWL6030_MODULE_MAIN_CHARGE TWL4030_MODULE_MAIN_CHARGE + +#define TWL6030_MADC_CTRL 0x00 +#define TWL6030_MADC_TEMP1_EN (1 << 0) +#define TWL6030_MADC_TEMP2_EN (1 << 1) +#define TWL6030_MADC_SCALER_EN_CH2 (1 << 2) +#define TWL6030_MADC_VBAT_SCALER_DIV (1 << 3) +#define TWL6030_MADC_SCALER_EN_CH11 (1 << 4) +#define TWL6030_MADC_TMP1_EN_MONITOR (1 << 5) +#define TWL6030_MADC_TMP2_EN_MONITOR (1 << 6) +#define TWL6030_MADC_ISOURCE_EN (1 << 7) + +#define TWL6030_MADC_RTSELECT_LSB 0x02 +#define TWL6030_MADC_ADCIN0 (1 << 0) +#define TWL6030_MADC_ADCIN1 (1 << 1) +#define TWL6030_MADC_ADCIN2 (1 << 2) +#define TWL6030_MADC_ADCIN3 (1 << 3) +#define TWL6030_MADC_ADCIN4 (1 << 4) +#define TWL6030_MADC_ADCIN5 (1 << 5) +#define TWL6030_MADC_ADCIN6 (1 << 6) +#define TWL6030_MADC_ADCIN7 (1 << 7) + +#define TWL6030_MADC_RTSELECT_ISB 0x03 +#define TWL6030_MADC_ADCIN8 (1 << 0) +#define TWL6030_MADC_ADCIN9 (1 << 1) +#define TWL6030_MADC_ADCIN10 (1 << 2) +#define TWL6030_MADC_ADCIN11 (1 << 3) +#define TWL6030_MADC_ADCIN12 (1 << 4) +#define TWL6030_MADC_ADCIN13 (1 << 5) +#define TWL6030_MADC_ADCIN14 (1 << 6) +#define TWL6030_MADC_ADCIN15 (1 << 7) + +#define TWL6030_MADC_RTSELECT_MSB 0x04 +#define TWL6030_MADC_ADCIN16 (1 << 0) + +#define TWL6030_MADC_CTRL_P1 0x05 +#define TWL6030_MADC_BUSY (1 << 0) +#define TWL6030_MADC_EOCP1 (1 << 1) +#define TWL6030_MADC_EOCRT (1 << 2) +#define TWL6030_MADC_SP1 (1 << 3) + +#define TWL6030_MADC_CTRL_P2 0x06 +#define TWL6030_MADC_BUSYB (1 << 0) +#define TWL6030_MADC_EOCP2 (1 << 1) +#define TWL6030_MADC_SP2 (1 << 2) + +#define TWL6030_MADC_RTCH0_LSB 0x07 +#define TWL6030_MADC_GPCH0_LSB 0x29 + +int twl6030_get_madc_conversion(int channel_no); +#endif diff --git a/include/linux/usb/otg.h b/include/linux/usb/otg.h old mode 100644 new mode 100755 index d87f44f5b04e..7a6677fe3dcb --- a/include/linux/usb/otg.h +++ b/include/linux/usb/otg.h @@ -97,6 +97,10 @@ struct otg_transceiver { int (*set_power)(struct otg_transceiver *otg, unsigned mA); + /* set/reset USB charger in High impedence mode on VBUS */ + int (*set_hz_mode)(struct otg_transceiver *otg, + bool enabled); + /* effective for A-peripheral, ignored for B devices */ int (*set_vbus)(struct otg_transceiver *otg, bool enabled); @@ -192,6 +196,16 @@ otg_start_hnp(struct otg_transceiver *otg) return otg->start_hnp(otg); } +/* Context: can sleep */ +static inline int +otg_set_hz_mode(struct otg_transceiver *otg, bool enabled) +{ + if (otg->set_hz_mode) + return otg->set_hz_mode(otg, enabled); + + return -EINVAL; +} + /* Context: can sleep */ static inline int otg_set_vbus(struct otg_transceiver *otg, bool enabled)