From 849369d6c66d3054688672f97d31fceb8e8230fb Mon Sep 17 00:00:00 2001 From: root Date: Fri, 25 Dec 2015 04:40:36 +0000 Subject: initial_commit --- drivers/power/Kconfig | 260 ++ drivers/power/Makefile | 41 + drivers/power/apm_power.c | 375 +++ drivers/power/bq20z75.c | 709 ++++ drivers/power/bq27x00_battery.c | 862 +++++ drivers/power/collie_battery.c | 417 +++ drivers/power/da9030_battery.c | 606 ++++ drivers/power/da9052-battery.c | 844 +++++ drivers/power/ds2760_battery.c | 657 ++++ drivers/power/ds2780_battery.c | 869 +++++ drivers/power/ds2782_battery.c | 421 +++ drivers/power/gpio-charger.c | 203 ++ drivers/power/intel_mid_battery.c | 797 +++++ drivers/power/isp1704_charger.c | 512 +++ drivers/power/jz4740-battery.c | 459 +++ drivers/power/max17040_battery.c | 308 ++ drivers/power/max17042_battery.c | 239 ++ drivers/power/max8903_battery.c | 748 +++++ drivers/power/max8903_charger.c | 506 +++ drivers/power/max8925_power.c | 529 +++ drivers/power/olpc_battery.c | 618 ++++ drivers/power/pcf50633-charger.c | 492 +++ drivers/power/pda_power.c | 515 +++ drivers/power/pmu_battery.c | 216 ++ drivers/power/power_supply.h | 38 + drivers/power/power_supply_core.c | 288 ++ drivers/power/power_supply_leds.c | 179 + drivers/power/power_supply_sysfs.c | 305 ++ drivers/power/ricoh619-battery.c | 6361 ++++++++++++++++++++++++++++++++++++ drivers/power/s3c_adc_battery.c | 437 +++ drivers/power/sabresd_battery.c | 985 ++++++ drivers/power/test_power.c | 419 +++ drivers/power/tosa_battery.c | 485 +++ drivers/power/twl4030_charger.c | 578 ++++ drivers/power/wm831x_backup.c | 234 ++ drivers/power/wm831x_power.c | 641 ++++ drivers/power/wm8350_power.c | 539 +++ drivers/power/wm97xx_battery.c | 309 ++ drivers/power/z2_battery.c | 335 ++ 39 files changed, 24336 insertions(+) create mode 100755 drivers/power/Kconfig create mode 100755 drivers/power/Makefile create mode 100644 drivers/power/apm_power.c create mode 100644 drivers/power/bq20z75.c create mode 100644 drivers/power/bq27x00_battery.c create mode 100644 drivers/power/collie_battery.c create mode 100644 drivers/power/da9030_battery.c create mode 100644 drivers/power/da9052-battery.c create mode 100644 drivers/power/ds2760_battery.c create mode 100644 drivers/power/ds2780_battery.c create mode 100644 drivers/power/ds2782_battery.c create mode 100644 drivers/power/gpio-charger.c create mode 100644 drivers/power/intel_mid_battery.c create mode 100644 drivers/power/isp1704_charger.c create mode 100644 drivers/power/jz4740-battery.c create mode 100644 drivers/power/max17040_battery.c create mode 100644 drivers/power/max17042_battery.c create mode 100755 drivers/power/max8903_battery.c create mode 100644 drivers/power/max8903_charger.c create mode 100644 drivers/power/max8925_power.c create mode 100644 drivers/power/olpc_battery.c create mode 100644 drivers/power/pcf50633-charger.c create mode 100644 drivers/power/pda_power.c create mode 100644 drivers/power/pmu_battery.c create mode 100644 drivers/power/power_supply.h create mode 100644 drivers/power/power_supply_core.c create mode 100644 drivers/power/power_supply_leds.c create mode 100644 drivers/power/power_supply_sysfs.c create mode 100755 drivers/power/ricoh619-battery.c create mode 100644 drivers/power/s3c_adc_battery.c create mode 100755 drivers/power/sabresd_battery.c create mode 100644 drivers/power/test_power.c create mode 100644 drivers/power/tosa_battery.c create mode 100644 drivers/power/twl4030_charger.c create mode 100644 drivers/power/wm831x_backup.c create mode 100644 drivers/power/wm831x_power.c create mode 100644 drivers/power/wm8350_power.c create mode 100644 drivers/power/wm97xx_battery.c create mode 100644 drivers/power/z2_battery.c (limited to 'drivers/power') diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig new file mode 100755 index 00000000..9a7d529d --- /dev/null +++ b/drivers/power/Kconfig @@ -0,0 +1,260 @@ +menuconfig POWER_SUPPLY + tristate "Power supply class support" + help + Say Y here to enable power supply class support. This allows + power supply (batteries, AC, USB) monitoring by userspace + via sysfs and uevent (if available) and/or APM kernel interface + (if selected below). + +if POWER_SUPPLY + +config POWER_SUPPLY_DEBUG + bool "Power supply debug" + help + Say Y here to enable debugging messages for power supply class + and drivers. + +config PDA_POWER + tristate "Generic PDA/phone power driver" + depends on !S390 + help + Say Y here to enable generic power driver for PDAs and phones with + one or two external power supplies (AC/USB) connected to main and + backup batteries, and optional builtin charger. + +config APM_POWER + tristate "APM emulation for class batteries" + depends on APM_EMULATION + help + Say Y here to enable support APM status emulation using + battery class devices. + +config MAX8925_POWER + tristate "MAX8925 battery charger support" + depends on MFD_MAX8925 + help + Say Y here to enable support for the battery charger in the Maxim + MAX8925 PMIC. + +config WM831X_BACKUP + tristate "WM831X backup battery charger support" + depends on MFD_WM831X + help + Say Y here to enable support for the backup battery charger + in the Wolfson Microelectronics WM831x PMICs. + +config WM831X_POWER + tristate "WM831X PMU support" + depends on MFD_WM831X + help + Say Y here to enable support for the power management unit + provided by Wolfson Microelectronics WM831x PMICs. + +config WM8350_POWER + tristate "WM8350 PMU support" + depends on MFD_WM8350 + help + Say Y here to enable support for the power management unit + provided by the Wolfson Microelectronics WM8350 PMIC. + +config TEST_POWER + tristate "Test power driver" + help + This driver is used for testing. It's safe to say M here. + +config BATTERY_DS2760 + tristate "DS2760 battery driver (HP iPAQ & others)" + depends on W1 && W1_SLAVE_DS2760 + help + Say Y here to enable support for batteries with ds2760 chip. + +config BATTERY_DS2780 + tristate "DS2780 battery driver" + select W1 + select W1_SLAVE_DS2780 + help + Say Y here to enable support for batteries with ds2780 chip. + +config BATTERY_DS2782 + tristate "DS2782/DS2786 standalone gas-gauge" + depends on I2C + help + Say Y here to enable support for the DS2782/DS2786 standalone battery + gas-gauge. + +config BATTERY_PMU + tristate "Apple PMU battery" + depends on PPC32 && ADB_PMU + help + Say Y here to expose battery information on Apple machines + through the generic battery class. + +config BATTERY_OLPC + tristate "One Laptop Per Child battery" + depends on X86_32 && OLPC + help + Say Y to enable support for the battery on the OLPC laptop. + +config BATTERY_TOSA + tristate "Sharp SL-6000 (tosa) battery" + depends on MACH_TOSA && MFD_TC6393XB && TOUCHSCREEN_WM97XX + help + Say Y to enable support for the battery on the Sharp Zaurus + SL-6000 (tosa) models. + +config BATTERY_COLLIE + tristate "Sharp SL-5500 (collie) battery" + depends on SA1100_COLLIE && MCP_UCB1200 + help + Say Y to enable support for the battery on the Sharp Zaurus + SL-5500 (collie) models. + +config BATTERY_WM97XX + bool "WM97xx generic battery driver" + depends on TOUCHSCREEN_WM97XX=y + help + Say Y to enable support for battery measured by WM97xx aux port. + +config BATTERY_BQ20Z75 + tristate "TI BQ20z75 gas gauge" + depends on I2C + help + Say Y to include support for TI BQ20z75 SBS-compliant + gas gauge and protection IC. + +config BATTERY_BQ27x00 + tristate "BQ27x00 battery driver" + help + Say Y here to enable support for batteries with BQ27x00 (I2C/HDQ) chips. + +config BATTERY_BQ27X00_I2C + bool "BQ27200/BQ27500 support" + depends on BATTERY_BQ27x00 + depends on I2C + default y + help + Say Y here to enable support for batteries with BQ27x00 (I2C) chips. + +config BATTERY_BQ27X00_PLATFORM + bool "BQ27000 support" + depends on BATTERY_BQ27x00 + default y + help + Say Y here to enable support for batteries with BQ27000 (HDQ) chips. + +config BATTERY_DA9030 + tristate "DA9030 battery driver" + depends on PMIC_DA903X + help + Say Y here to enable support for batteries charger integrated into + DA9030 PMIC. + +config BATTERY_MAX17040 + tristate "Maxim MAX17040 Fuel Gauge" + depends on I2C + help + MAX17040 is fuel-gauge systems for lithium-ion (Li+) batteries + in handheld and portable equipment. The MAX17040 is configured + to operate with a single lithium cell + +config BATTERY_MAX17042 + tristate "Maxim MAX17042/8997/8966 Fuel Gauge" + depends on I2C + help + MAX17042 is fuel-gauge systems for lithium-ion (Li+) batteries + in handheld and portable equipment. The MAX17042 is configured + to operate with a single lithium cell. MAX8997 and MAX8966 are + multi-function devices that include fuel gauages that are compatible + with MAX17042. + +config BATTERY_Z2 + tristate "Z2 battery driver" + depends on I2C && MACH_ZIPIT2 + help + Say Y to include support for the battery on the Zipit Z2. + +config BATTERY_S3C_ADC + tristate "Battery driver for Samsung ADC based monitoring" + depends on S3C_ADC + help + Say Y here to enable support for iPAQ h1930/h1940/rx1950 battery + +config CHARGER_PCF50633 + tristate "NXP PCF50633 MBC" + depends on MFD_PCF50633 + help + Say Y to include support for NXP PCF50633 Main Battery Charger. + +config BATTERY_JZ4740 + tristate "Ingenic JZ4740 battery" + depends on MACH_JZ4740 + depends on MFD_JZ4740_ADC + help + Say Y to enable support for the battery on Ingenic JZ4740 based + boards. + + This driver can be build as a module. If so, the module will be + called jz4740-battery. + +config BATTERY_INTEL_MID + tristate "Battery driver for Intel MID platforms" + depends on INTEL_SCU_IPC && SPI + help + Say Y here to enable the battery driver on Intel MID + platforms. + +config CHARGER_ISP1704 + tristate "ISP1704 USB Charger Detection" + depends on USB_OTG_UTILS + help + Say Y to enable support for USB Charger Detection with + ISP1707/ISP1704 USB transceivers. + +config CHARGER_MAX8903 + tristate "MAX8903 Battery DC-DC Charger for USB and Adapter Power" + depends on GENERIC_HARDIRQS + help + Say Y to enable support for the MAX8903 DC-DC charger and sysfs. + The driver supports controlling charger-enable and current-limit + pins based on the status of charger connections with interrupt + handlers. + +config SABRESD_MAX8903 + tristate "Sabresd Board Battery DC-DC Charger for USB and Adapter Power" + depends on GENERIC_HARDIRQS + help + Say Y to enable support for the MAX8903 DC-DC charger and sysfs on + sabresd board.The driver supports controlling charger and battery + based on the status of charger connections with interrupt handlers. + +config CHARGER_TWL4030 + tristate "OMAP TWL4030 BCI charger driver" + depends on TWL4030_CORE + help + Say Y here to enable support for TWL4030 Battery Charge Interface. + +config CHARGER_GPIO + tristate "GPIO charger" + depends on GPIOLIB + help + Say Y to include support for chargers which report their online status + through a GPIO pin. + + This driver can be build as a module. If so, the module will be + called gpio-charger. + +config BATTERY_DA9052 + tristate "Dialog DA9052 Battery" + depends on PMIC_DIALOG + help + Say Y here to enable support for batteries charger integrated into + DA9052 PMIC. + +config BATTERY_RICOH619 + tristate "Ricoh R5T619 PMIC battery driver" + depends on MFD_RICOH619 && I2C && GENERIC_HARDIRQS + help + Say Y to enable support for the battery control of the Ricoh R5T619 + Power Management device. + +endif # POWER_SUPPLY diff --git a/drivers/power/Makefile b/drivers/power/Makefile new file mode 100755 index 00000000..64fdfdba --- /dev/null +++ b/drivers/power/Makefile @@ -0,0 +1,41 @@ +ccflags-$(CONFIG_POWER_SUPPLY_DEBUG) := -DDEBUG + +power_supply-y := power_supply_core.o +power_supply-$(CONFIG_SYSFS) += power_supply_sysfs.o +power_supply-$(CONFIG_LEDS_TRIGGERS) += power_supply_leds.o + +obj-$(CONFIG_POWER_SUPPLY) += power_supply.o + +obj-$(CONFIG_PDA_POWER) += pda_power.o +obj-$(CONFIG_APM_POWER) += apm_power.o +obj-$(CONFIG_MAX8925_POWER) += max8925_power.o +obj-$(CONFIG_WM831X_BACKUP) += wm831x_backup.o +obj-$(CONFIG_WM831X_POWER) += wm831x_power.o +obj-$(CONFIG_WM8350_POWER) += wm8350_power.o +obj-$(CONFIG_TEST_POWER) += test_power.o + +obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o +obj-$(CONFIG_BATTERY_DS2780) += ds2780_battery.o +obj-$(CONFIG_BATTERY_DS2782) += ds2782_battery.o +obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o +obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o +obj-$(CONFIG_BATTERY_TOSA) += tosa_battery.o +obj-$(CONFIG_BATTERY_COLLIE) += collie_battery.o +obj-$(CONFIG_BATTERY_WM97XX) += wm97xx_battery.o +obj-$(CONFIG_BATTERY_BQ20Z75) += bq20z75.o +obj-$(CONFIG_BATTERY_BQ27x00) += bq27x00_battery.o +obj-$(CONFIG_BATTERY_DA9030) += da9030_battery.o +obj-$(CONFIG_BATTERY_MAX17040) += max17040_battery.o +obj-$(CONFIG_BATTERY_MAX17042) += max17042_battery.o +obj-$(CONFIG_BATTERY_Z2) += z2_battery.o +obj-$(CONFIG_BATTERY_S3C_ADC) += s3c_adc_battery.o +obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o +obj-$(CONFIG_BATTERY_JZ4740) += jz4740-battery.o +obj-$(CONFIG_BATTERY_INTEL_MID) += intel_mid_battery.o +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_BATTERY_DA9052) += da9052-battery.o +obj-$(CONFIG_SABRESD_MAX8903) += sabresd_battery.o +obj-$(CONFIG_BATTERY_RICOH619) += ricoh619-battery.o diff --git a/drivers/power/apm_power.c b/drivers/power/apm_power.c new file mode 100644 index 00000000..dc628cb2 --- /dev/null +++ b/drivers/power/apm_power.c @@ -0,0 +1,375 @@ +/* + * Copyright © 2007 Anton Vorontsov + * Copyright © 2007 Eugeny Boger + * + * Author: Eugeny Boger + * + * Use consistent with the GNU GPL is permitted, + * provided that this copyright notice is + * preserved in its entirety in all copies and derived works. + */ + +#include +#include +#include + + +#define PSY_PROP(psy, prop, val) psy->get_property(psy, \ + POWER_SUPPLY_PROP_##prop, val) + +#define _MPSY_PROP(prop, val) main_battery->get_property(main_battery, \ + prop, val) + +#define MPSY_PROP(prop, val) _MPSY_PROP(POWER_SUPPLY_PROP_##prop, val) + +static DEFINE_MUTEX(apm_mutex); +static struct power_supply *main_battery; + +enum apm_source { + SOURCE_ENERGY, + SOURCE_CHARGE, + SOURCE_VOLTAGE, +}; + +struct find_bat_param { + struct power_supply *main; + struct power_supply *bat; + struct power_supply *max_charge_bat; + struct power_supply *max_energy_bat; + union power_supply_propval full; + int max_charge; + int max_energy; +}; + +static int __find_main_battery(struct device *dev, void *data) +{ + struct find_bat_param *bp = (struct find_bat_param *)data; + + bp->bat = dev_get_drvdata(dev); + + if (bp->bat->use_for_apm) { + /* nice, we explicitly asked to report this battery. */ + bp->main = bp->bat; + return 1; + } + + if (!PSY_PROP(bp->bat, CHARGE_FULL_DESIGN, &bp->full) || + !PSY_PROP(bp->bat, CHARGE_FULL, &bp->full)) { + if (bp->full.intval > bp->max_charge) { + bp->max_charge_bat = bp->bat; + bp->max_charge = bp->full.intval; + } + } else if (!PSY_PROP(bp->bat, ENERGY_FULL_DESIGN, &bp->full) || + !PSY_PROP(bp->bat, ENERGY_FULL, &bp->full)) { + if (bp->full.intval > bp->max_energy) { + bp->max_energy_bat = bp->bat; + bp->max_energy = bp->full.intval; + } + } + return 0; +} + +static void find_main_battery(void) +{ + struct find_bat_param bp; + int error; + + memset(&bp, 0, sizeof(struct find_bat_param)); + main_battery = NULL; + bp.main = main_battery; + + error = class_for_each_device(power_supply_class, NULL, &bp, + __find_main_battery); + if (error) { + main_battery = bp.main; + return; + } + + if ((bp.max_energy_bat && bp.max_charge_bat) && + (bp.max_energy_bat != bp.max_charge_bat)) { + /* try guess battery with more capacity */ + if (!PSY_PROP(bp.max_charge_bat, VOLTAGE_MAX_DESIGN, + &bp.full)) { + if (bp.max_energy > bp.max_charge * bp.full.intval) + main_battery = bp.max_energy_bat; + else + main_battery = bp.max_charge_bat; + } else if (!PSY_PROP(bp.max_energy_bat, VOLTAGE_MAX_DESIGN, + &bp.full)) { + if (bp.max_charge > bp.max_energy / bp.full.intval) + main_battery = bp.max_charge_bat; + else + main_battery = bp.max_energy_bat; + } else { + /* give up, choice any */ + main_battery = bp.max_energy_bat; + } + } else if (bp.max_charge_bat) { + main_battery = bp.max_charge_bat; + } else if (bp.max_energy_bat) { + main_battery = bp.max_energy_bat; + } else { + /* give up, try the last if any */ + main_battery = bp.bat; + } +} + +static int do_calculate_time(int status, enum apm_source source) +{ + union power_supply_propval full; + union power_supply_propval empty; + union power_supply_propval cur; + union power_supply_propval I; + enum power_supply_property full_prop; + enum power_supply_property full_design_prop; + enum power_supply_property empty_prop; + enum power_supply_property empty_design_prop; + enum power_supply_property cur_avg_prop; + enum power_supply_property cur_now_prop; + + if (MPSY_PROP(CURRENT_AVG, &I)) { + /* if battery can't report average value, use momentary */ + if (MPSY_PROP(CURRENT_NOW, &I)) + return -1; + } + + if (!I.intval) + return 0; + + switch (source) { + case SOURCE_CHARGE: + full_prop = POWER_SUPPLY_PROP_CHARGE_FULL; + full_design_prop = POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN; + empty_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY; + empty_design_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY; + cur_avg_prop = POWER_SUPPLY_PROP_CHARGE_AVG; + cur_now_prop = POWER_SUPPLY_PROP_CHARGE_NOW; + break; + case SOURCE_ENERGY: + full_prop = POWER_SUPPLY_PROP_ENERGY_FULL; + full_design_prop = POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN; + empty_prop = POWER_SUPPLY_PROP_ENERGY_EMPTY; + empty_design_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY; + cur_avg_prop = POWER_SUPPLY_PROP_ENERGY_AVG; + cur_now_prop = POWER_SUPPLY_PROP_ENERGY_NOW; + break; + case SOURCE_VOLTAGE: + full_prop = POWER_SUPPLY_PROP_VOLTAGE_MAX; + full_design_prop = POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN; + empty_prop = POWER_SUPPLY_PROP_VOLTAGE_MIN; + empty_design_prop = POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN; + cur_avg_prop = POWER_SUPPLY_PROP_VOLTAGE_AVG; + cur_now_prop = POWER_SUPPLY_PROP_VOLTAGE_NOW; + break; + default: + printk(KERN_ERR "Unsupported source: %d\n", source); + return -1; + } + + if (_MPSY_PROP(full_prop, &full)) { + /* if battery can't report this property, use design value */ + if (_MPSY_PROP(full_design_prop, &full)) + return -1; + } + + if (_MPSY_PROP(empty_prop, &empty)) { + /* if battery can't report this property, use design value */ + if (_MPSY_PROP(empty_design_prop, &empty)) + empty.intval = 0; + } + + if (_MPSY_PROP(cur_avg_prop, &cur)) { + /* if battery can't report average value, use momentary */ + if (_MPSY_PROP(cur_now_prop, &cur)) + return -1; + } + + if (status == POWER_SUPPLY_STATUS_CHARGING) + return ((cur.intval - full.intval) * 60L) / I.intval; + else + return -((cur.intval - empty.intval) * 60L) / I.intval; +} + +static int calculate_time(int status) +{ + int time; + + time = do_calculate_time(status, SOURCE_ENERGY); + if (time != -1) + return time; + + time = do_calculate_time(status, SOURCE_CHARGE); + if (time != -1) + return time; + + time = do_calculate_time(status, SOURCE_VOLTAGE); + if (time != -1) + return time; + + return -1; +} + +static int calculate_capacity(enum apm_source source) +{ + enum power_supply_property full_prop, empty_prop; + enum power_supply_property full_design_prop, empty_design_prop; + enum power_supply_property now_prop, avg_prop; + union power_supply_propval empty, full, cur; + int ret; + + switch (source) { + case SOURCE_CHARGE: + full_prop = POWER_SUPPLY_PROP_CHARGE_FULL; + empty_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY; + full_design_prop = POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN; + empty_design_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN; + now_prop = POWER_SUPPLY_PROP_CHARGE_NOW; + avg_prop = POWER_SUPPLY_PROP_CHARGE_AVG; + break; + case SOURCE_ENERGY: + full_prop = POWER_SUPPLY_PROP_ENERGY_FULL; + empty_prop = POWER_SUPPLY_PROP_ENERGY_EMPTY; + full_design_prop = POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN; + empty_design_prop = POWER_SUPPLY_PROP_ENERGY_EMPTY_DESIGN; + now_prop = POWER_SUPPLY_PROP_ENERGY_NOW; + avg_prop = POWER_SUPPLY_PROP_ENERGY_AVG; + break; + case SOURCE_VOLTAGE: + full_prop = POWER_SUPPLY_PROP_VOLTAGE_MAX; + empty_prop = POWER_SUPPLY_PROP_VOLTAGE_MIN; + full_design_prop = POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN; + empty_design_prop = POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN; + now_prop = POWER_SUPPLY_PROP_VOLTAGE_NOW; + avg_prop = POWER_SUPPLY_PROP_VOLTAGE_AVG; + break; + default: + printk(KERN_ERR "Unsupported source: %d\n", source); + return -1; + } + + if (_MPSY_PROP(full_prop, &full)) { + /* if battery can't report this property, use design value */ + if (_MPSY_PROP(full_design_prop, &full)) + return -1; + } + + if (_MPSY_PROP(avg_prop, &cur)) { + /* if battery can't report average value, use momentary */ + if (_MPSY_PROP(now_prop, &cur)) + return -1; + } + + if (_MPSY_PROP(empty_prop, &empty)) { + /* if battery can't report this property, use design value */ + if (_MPSY_PROP(empty_design_prop, &empty)) + empty.intval = 0; + } + + if (full.intval - empty.intval) + ret = ((cur.intval - empty.intval) * 100L) / + (full.intval - empty.intval); + else + return -1; + + if (ret > 100) + return 100; + else if (ret < 0) + return 0; + + return ret; +} + +static void apm_battery_apm_get_power_status(struct apm_power_info *info) +{ + union power_supply_propval status; + union power_supply_propval capacity, time_to_full, time_to_empty; + + mutex_lock(&apm_mutex); + find_main_battery(); + if (!main_battery) { + mutex_unlock(&apm_mutex); + return; + } + + /* status */ + + if (MPSY_PROP(STATUS, &status)) + status.intval = POWER_SUPPLY_STATUS_UNKNOWN; + + /* ac line status */ + + if ((status.intval == POWER_SUPPLY_STATUS_CHARGING) || + (status.intval == POWER_SUPPLY_STATUS_NOT_CHARGING) || + (status.intval == POWER_SUPPLY_STATUS_FULL)) + info->ac_line_status = APM_AC_ONLINE; + else + info->ac_line_status = APM_AC_OFFLINE; + + /* battery life (i.e. capacity, in percents) */ + + if (MPSY_PROP(CAPACITY, &capacity) == 0) { + info->battery_life = capacity.intval; + } else { + /* try calculate using energy */ + info->battery_life = calculate_capacity(SOURCE_ENERGY); + /* if failed try calculate using charge instead */ + if (info->battery_life == -1) + info->battery_life = calculate_capacity(SOURCE_CHARGE); + if (info->battery_life == -1) + info->battery_life = calculate_capacity(SOURCE_VOLTAGE); + } + + /* charging status */ + + if (status.intval == POWER_SUPPLY_STATUS_CHARGING) { + info->battery_status = APM_BATTERY_STATUS_CHARGING; + } else { + if (info->battery_life > 50) + info->battery_status = APM_BATTERY_STATUS_HIGH; + else if (info->battery_life > 5) + info->battery_status = APM_BATTERY_STATUS_LOW; + else + info->battery_status = APM_BATTERY_STATUS_CRITICAL; + } + info->battery_flag = info->battery_status; + + /* time */ + + info->units = APM_UNITS_MINS; + + if (status.intval == POWER_SUPPLY_STATUS_CHARGING) { + if (!MPSY_PROP(TIME_TO_FULL_AVG, &time_to_full) || + !MPSY_PROP(TIME_TO_FULL_NOW, &time_to_full)) + info->time = time_to_full.intval / 60; + else + info->time = calculate_time(status.intval); + } else { + if (!MPSY_PROP(TIME_TO_EMPTY_AVG, &time_to_empty) || + !MPSY_PROP(TIME_TO_EMPTY_NOW, &time_to_empty)) + info->time = time_to_empty.intval / 60; + else + info->time = calculate_time(status.intval); + } + + mutex_unlock(&apm_mutex); +} + +static int __init apm_battery_init(void) +{ + printk(KERN_INFO "APM Battery Driver\n"); + + apm_get_power_status = apm_battery_apm_get_power_status; + return 0; +} + +static void __exit apm_battery_exit(void) +{ + apm_get_power_status = NULL; +} + +module_init(apm_battery_init); +module_exit(apm_battery_exit); + +MODULE_AUTHOR("Eugeny Boger "); +MODULE_DESCRIPTION("APM emulation driver for battery monitoring class"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/bq20z75.c b/drivers/power/bq20z75.c new file mode 100644 index 00000000..506585e3 --- /dev/null +++ b/drivers/power/bq20z75.c @@ -0,0 +1,709 @@ +/* + * Gas Gauge driver for TI's BQ20Z75 + * + * Copyright (c) 2010, NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +enum { + REG_MANUFACTURER_DATA, + REG_TEMPERATURE, + REG_VOLTAGE, + REG_CURRENT, + REG_CAPACITY, + REG_TIME_TO_EMPTY, + REG_TIME_TO_FULL, + REG_STATUS, + REG_CYCLE_COUNT, + REG_SERIAL_NUMBER, + REG_REMAINING_CAPACITY, + REG_REMAINING_CAPACITY_CHARGE, + REG_FULL_CHARGE_CAPACITY, + REG_FULL_CHARGE_CAPACITY_CHARGE, + REG_DESIGN_CAPACITY, + REG_DESIGN_CAPACITY_CHARGE, + REG_DESIGN_VOLTAGE, +}; + +/* Battery Mode defines */ +#define BATTERY_MODE_OFFSET 0x03 +#define BATTERY_MODE_MASK 0x8000 +enum bq20z75_battery_mode { + BATTERY_MODE_AMPS, + BATTERY_MODE_WATTS +}; + +/* manufacturer access defines */ +#define MANUFACTURER_ACCESS_STATUS 0x0006 +#define MANUFACTURER_ACCESS_SLEEP 0x0011 + +/* battery status value bits */ +#define BATTERY_DISCHARGING 0x40 +#define BATTERY_FULL_CHARGED 0x20 +#define BATTERY_FULL_DISCHARGED 0x10 + +#define BQ20Z75_DATA(_psp, _addr, _min_value, _max_value) { \ + .psp = _psp, \ + .addr = _addr, \ + .min_value = _min_value, \ + .max_value = _max_value, \ +} + +static const struct bq20z75_device_data { + enum power_supply_property psp; + u8 addr; + int min_value; + int max_value; +} bq20z75_data[] = { + [REG_MANUFACTURER_DATA] = + BQ20Z75_DATA(POWER_SUPPLY_PROP_PRESENT, 0x00, 0, 65535), + [REG_TEMPERATURE] = + BQ20Z75_DATA(POWER_SUPPLY_PROP_TEMP, 0x08, 0, 65535), + [REG_VOLTAGE] = + BQ20Z75_DATA(POWER_SUPPLY_PROP_VOLTAGE_NOW, 0x09, 0, 20000), + [REG_CURRENT] = + BQ20Z75_DATA(POWER_SUPPLY_PROP_CURRENT_NOW, 0x0A, -32768, + 32767), + [REG_CAPACITY] = + BQ20Z75_DATA(POWER_SUPPLY_PROP_CAPACITY, 0x0E, 0, 100), + [REG_REMAINING_CAPACITY] = + BQ20Z75_DATA(POWER_SUPPLY_PROP_ENERGY_NOW, 0x0F, 0, 65535), + [REG_REMAINING_CAPACITY_CHARGE] = + BQ20Z75_DATA(POWER_SUPPLY_PROP_CHARGE_NOW, 0x0F, 0, 65535), + [REG_FULL_CHARGE_CAPACITY] = + BQ20Z75_DATA(POWER_SUPPLY_PROP_ENERGY_FULL, 0x10, 0, 65535), + [REG_FULL_CHARGE_CAPACITY_CHARGE] = + BQ20Z75_DATA(POWER_SUPPLY_PROP_CHARGE_FULL, 0x10, 0, 65535), + [REG_TIME_TO_EMPTY] = + BQ20Z75_DATA(POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, 0x12, 0, + 65535), + [REG_TIME_TO_FULL] = + BQ20Z75_DATA(POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, 0x13, 0, + 65535), + [REG_STATUS] = + BQ20Z75_DATA(POWER_SUPPLY_PROP_STATUS, 0x16, 0, 65535), + [REG_CYCLE_COUNT] = + BQ20Z75_DATA(POWER_SUPPLY_PROP_CYCLE_COUNT, 0x17, 0, 65535), + [REG_DESIGN_CAPACITY] = + BQ20Z75_DATA(POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, 0x18, 0, + 65535), + [REG_DESIGN_CAPACITY_CHARGE] = + BQ20Z75_DATA(POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, 0x18, 0, + 65535), + [REG_DESIGN_VOLTAGE] = + BQ20Z75_DATA(POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, 0x19, 0, + 65535), + [REG_SERIAL_NUMBER] = + BQ20Z75_DATA(POWER_SUPPLY_PROP_SERIAL_NUMBER, 0x1C, 0, 65535), +}; + +static enum power_supply_property bq20z75_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, + POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, + POWER_SUPPLY_PROP_SERIAL_NUMBER, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_ENERGY_NOW, + POWER_SUPPLY_PROP_ENERGY_FULL, + POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, +}; + +struct bq20z75_info { + struct i2c_client *client; + struct power_supply power_supply; + struct bq20z75_platform_data *pdata; + bool is_present; + bool gpio_detect; + bool enable_detection; + int irq; +}; + +static int bq20z75_read_word_data(struct i2c_client *client, u8 address) +{ + struct bq20z75_info *bq20z75_device = i2c_get_clientdata(client); + s32 ret = 0; + int retries = 1; + + if (bq20z75_device->pdata) + retries = max(bq20z75_device->pdata->i2c_retry_count + 1, 1); + + while (retries > 0) { + ret = i2c_smbus_read_word_data(client, address); + if (ret >= 0) + break; + retries--; + } + + if (ret < 0) { + dev_dbg(&client->dev, + "%s: i2c read at address 0x%x failed\n", + __func__, address); + return ret; + } + + return le16_to_cpu(ret); +} + +static int bq20z75_write_word_data(struct i2c_client *client, u8 address, + u16 value) +{ + struct bq20z75_info *bq20z75_device = i2c_get_clientdata(client); + s32 ret = 0; + int retries = 1; + + if (bq20z75_device->pdata) + retries = max(bq20z75_device->pdata->i2c_retry_count + 1, 1); + + while (retries > 0) { + ret = i2c_smbus_write_word_data(client, address, + le16_to_cpu(value)); + if (ret >= 0) + break; + retries--; + } + + if (ret < 0) { + dev_dbg(&client->dev, + "%s: i2c write to address 0x%x failed\n", + __func__, address); + return ret; + } + + return 0; +} + +static int bq20z75_get_battery_presence_and_health( + struct i2c_client *client, enum power_supply_property psp, + union power_supply_propval *val) +{ + s32 ret; + struct bq20z75_info *bq20z75_device = i2c_get_clientdata(client); + + if (psp == POWER_SUPPLY_PROP_PRESENT && + bq20z75_device->gpio_detect) { + ret = gpio_get_value( + bq20z75_device->pdata->battery_detect); + if (ret == bq20z75_device->pdata->battery_detect_present) + val->intval = 1; + else + val->intval = 0; + bq20z75_device->is_present = val->intval; + return ret; + } + + /* Write to ManufacturerAccess with + * ManufacturerAccess command and then + * read the status */ + ret = bq20z75_write_word_data(client, + bq20z75_data[REG_MANUFACTURER_DATA].addr, + MANUFACTURER_ACCESS_STATUS); + if (ret < 0) { + if (psp == POWER_SUPPLY_PROP_PRESENT) + val->intval = 0; /* battery removed */ + return ret; + } + + ret = bq20z75_read_word_data(client, + bq20z75_data[REG_MANUFACTURER_DATA].addr); + if (ret < 0) + return ret; + + if (ret < bq20z75_data[REG_MANUFACTURER_DATA].min_value || + ret > bq20z75_data[REG_MANUFACTURER_DATA].max_value) { + val->intval = 0; + return 0; + } + + /* Mask the upper nibble of 2nd byte and + * lower byte of response then + * shift the result by 8 to get status*/ + ret &= 0x0F00; + ret >>= 8; + if (psp == POWER_SUPPLY_PROP_PRESENT) { + if (ret == 0x0F) + /* battery removed */ + val->intval = 0; + else + val->intval = 1; + } else if (psp == POWER_SUPPLY_PROP_HEALTH) { + if (ret == 0x09) + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + else if (ret == 0x0B) + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (ret == 0x0C) + val->intval = POWER_SUPPLY_HEALTH_DEAD; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; + } + + return 0; +} + +static int bq20z75_get_battery_property(struct i2c_client *client, + int reg_offset, enum power_supply_property psp, + union power_supply_propval *val) +{ + s32 ret; + + ret = bq20z75_read_word_data(client, + bq20z75_data[reg_offset].addr); + if (ret < 0) + return ret; + + /* returned values are 16 bit */ + if (bq20z75_data[reg_offset].min_value < 0) + ret = (s16)ret; + + if (ret >= bq20z75_data[reg_offset].min_value && + ret <= bq20z75_data[reg_offset].max_value) { + val->intval = ret; + if (psp == POWER_SUPPLY_PROP_STATUS) { + if (ret & BATTERY_FULL_CHARGED) + val->intval = POWER_SUPPLY_STATUS_FULL; + else if (ret & BATTERY_FULL_DISCHARGED) + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + else if (ret & BATTERY_DISCHARGING) + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + else + val->intval = POWER_SUPPLY_STATUS_CHARGING; + } + } else { + if (psp == POWER_SUPPLY_PROP_STATUS) + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + else + val->intval = 0; + } + + return 0; +} + +static void bq20z75_unit_adjustment(struct i2c_client *client, + enum power_supply_property psp, union power_supply_propval *val) +{ +#define BASE_UNIT_CONVERSION 1000 +#define BATTERY_MODE_CAP_MULT_WATT (10 * BASE_UNIT_CONVERSION) +#define TIME_UNIT_CONVERSION 60 +#define TEMP_KELVIN_TO_CELSIUS 2731 + switch (psp) { + case POWER_SUPPLY_PROP_ENERGY_NOW: + case POWER_SUPPLY_PROP_ENERGY_FULL: + case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: + /* bq20z75 provides energy in units of 10mWh. + * Convert to µWh + */ + val->intval *= BATTERY_MODE_CAP_MULT_WATT; + break; + + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + case POWER_SUPPLY_PROP_CURRENT_NOW: + case POWER_SUPPLY_PROP_CHARGE_NOW: + case POWER_SUPPLY_PROP_CHARGE_FULL: + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval *= BASE_UNIT_CONVERSION; + break; + + case POWER_SUPPLY_PROP_TEMP: + /* bq20z75 provides battery temperature in 0.1K + * so convert it to 0.1°C + */ + val->intval -= TEMP_KELVIN_TO_CELSIUS; + break; + + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: + case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: + /* bq20z75 provides time to empty and time to full in minutes. + * Convert to seconds + */ + val->intval *= TIME_UNIT_CONVERSION; + break; + + default: + dev_dbg(&client->dev, + "%s: no need for unit conversion %d\n", __func__, psp); + } +} + +static enum bq20z75_battery_mode +bq20z75_set_battery_mode(struct i2c_client *client, + enum bq20z75_battery_mode mode) +{ + int ret, original_val; + + original_val = bq20z75_read_word_data(client, BATTERY_MODE_OFFSET); + if (original_val < 0) + return original_val; + + if ((original_val & BATTERY_MODE_MASK) == mode) + return mode; + + if (mode == BATTERY_MODE_AMPS) + ret = original_val & ~BATTERY_MODE_MASK; + else + ret = original_val | BATTERY_MODE_MASK; + + ret = bq20z75_write_word_data(client, BATTERY_MODE_OFFSET, ret); + if (ret < 0) + return ret; + + return original_val & BATTERY_MODE_MASK; +} + +static int bq20z75_get_battery_capacity(struct i2c_client *client, + int reg_offset, enum power_supply_property psp, + union power_supply_propval *val) +{ + s32 ret; + enum bq20z75_battery_mode mode = BATTERY_MODE_WATTS; + + if (power_supply_is_amp_property(psp)) + mode = BATTERY_MODE_AMPS; + + mode = bq20z75_set_battery_mode(client, mode); + if (mode < 0) + return mode; + + ret = bq20z75_read_word_data(client, bq20z75_data[reg_offset].addr); + if (ret < 0) + return ret; + + if (psp == POWER_SUPPLY_PROP_CAPACITY) { + /* bq20z75 spec says that this can be >100 % + * even if max value is 100 % */ + val->intval = min(ret, 100); + } else + val->intval = ret; + + ret = bq20z75_set_battery_mode(client, mode); + if (ret < 0) + return ret; + + return 0; +} + +static char bq20z75_serial[5]; +static int bq20z75_get_battery_serial_number(struct i2c_client *client, + union power_supply_propval *val) +{ + int ret; + + ret = bq20z75_read_word_data(client, + bq20z75_data[REG_SERIAL_NUMBER].addr); + if (ret < 0) + return ret; + + ret = sprintf(bq20z75_serial, "%04x", ret); + val->strval = bq20z75_serial; + + return 0; +} + +static int bq20z75_get_property_index(struct i2c_client *client, + enum power_supply_property psp) +{ + int count; + for (count = 0; count < ARRAY_SIZE(bq20z75_data); count++) + if (psp == bq20z75_data[count].psp) + return count; + + dev_warn(&client->dev, + "%s: Invalid Property - %d\n", __func__, psp); + + return -EINVAL; +} + +static int bq20z75_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret = 0; + struct bq20z75_info *bq20z75_device = container_of(psy, + struct bq20z75_info, power_supply); + struct i2c_client *client = bq20z75_device->client; + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + case POWER_SUPPLY_PROP_HEALTH: + ret = bq20z75_get_battery_presence_and_health(client, psp, val); + if (psp == POWER_SUPPLY_PROP_PRESENT) + return 0; + break; + + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + + case POWER_SUPPLY_PROP_ENERGY_NOW: + case POWER_SUPPLY_PROP_ENERGY_FULL: + case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: + case POWER_SUPPLY_PROP_CHARGE_NOW: + case POWER_SUPPLY_PROP_CHARGE_FULL: + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + case POWER_SUPPLY_PROP_CAPACITY: + ret = bq20z75_get_property_index(client, psp); + if (ret < 0) + break; + + ret = bq20z75_get_battery_capacity(client, ret, psp, val); + break; + + case POWER_SUPPLY_PROP_SERIAL_NUMBER: + ret = bq20z75_get_battery_serial_number(client, val); + break; + + case POWER_SUPPLY_PROP_STATUS: + case POWER_SUPPLY_PROP_CYCLE_COUNT: + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + case POWER_SUPPLY_PROP_CURRENT_NOW: + case POWER_SUPPLY_PROP_TEMP: + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: + case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + ret = bq20z75_get_property_index(client, psp); + if (ret < 0) + break; + + ret = bq20z75_get_battery_property(client, ret, psp, val); + break; + + default: + dev_err(&client->dev, + "%s: INVALID property\n", __func__); + return -EINVAL; + } + + if (!bq20z75_device->enable_detection) + goto done; + + if (!bq20z75_device->gpio_detect && + bq20z75_device->is_present != (ret >= 0)) { + bq20z75_device->is_present = (ret >= 0); + power_supply_changed(&bq20z75_device->power_supply); + } + +done: + if (!ret) { + /* Convert units to match requirements for power supply class */ + bq20z75_unit_adjustment(client, psp, val); + } + + dev_dbg(&client->dev, + "%s: property = %d, value = %x\n", __func__, psp, val->intval); + + if (ret && bq20z75_device->is_present) + return ret; + + /* battery not present, so return NODATA for properties */ + if (ret) + return -ENODATA; + + return 0; +} + +static irqreturn_t bq20z75_irq(int irq, void *devid) +{ + struct power_supply *battery = devid; + + power_supply_changed(battery); + + return IRQ_HANDLED; +} + +static int __devinit bq20z75_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct bq20z75_info *bq20z75_device; + struct bq20z75_platform_data *pdata = client->dev.platform_data; + int rc; + int irq; + + bq20z75_device = kzalloc(sizeof(struct bq20z75_info), GFP_KERNEL); + if (!bq20z75_device) + return -ENOMEM; + + bq20z75_device->client = client; + bq20z75_device->enable_detection = false; + bq20z75_device->gpio_detect = false; + bq20z75_device->power_supply.name = "battery"; + bq20z75_device->power_supply.type = POWER_SUPPLY_TYPE_BATTERY; + bq20z75_device->power_supply.properties = bq20z75_properties; + bq20z75_device->power_supply.num_properties = + ARRAY_SIZE(bq20z75_properties); + bq20z75_device->power_supply.get_property = bq20z75_get_property; + + if (pdata) { + bq20z75_device->gpio_detect = + gpio_is_valid(pdata->battery_detect); + bq20z75_device->pdata = pdata; + } + + i2c_set_clientdata(client, bq20z75_device); + + if (!bq20z75_device->gpio_detect) + goto skip_gpio; + + rc = gpio_request(pdata->battery_detect, dev_name(&client->dev)); + if (rc) { + dev_warn(&client->dev, "Failed to request gpio: %d\n", rc); + bq20z75_device->gpio_detect = false; + goto skip_gpio; + } + + rc = gpio_direction_input(pdata->battery_detect); + if (rc) { + dev_warn(&client->dev, "Failed to get gpio as input: %d\n", rc); + gpio_free(pdata->battery_detect); + bq20z75_device->gpio_detect = false; + goto skip_gpio; + } + + irq = gpio_to_irq(pdata->battery_detect); + if (irq <= 0) { + dev_warn(&client->dev, "Failed to get gpio as irq: %d\n", irq); + gpio_free(pdata->battery_detect); + bq20z75_device->gpio_detect = false; + goto skip_gpio; + } + + rc = request_irq(irq, bq20z75_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + dev_name(&client->dev), &bq20z75_device->power_supply); + if (rc) { + dev_warn(&client->dev, "Failed to request irq: %d\n", rc); + gpio_free(pdata->battery_detect); + bq20z75_device->gpio_detect = false; + goto skip_gpio; + } + + bq20z75_device->irq = irq; + +skip_gpio: + + rc = power_supply_register(&client->dev, &bq20z75_device->power_supply); + if (rc) { + dev_err(&client->dev, + "%s: Failed to register power supply\n", __func__); + goto exit_psupply; + } + + dev_info(&client->dev, + "%s: battery gas gauge device registered\n", client->name); + + return 0; + +exit_psupply: + if (bq20z75_device->irq) + free_irq(bq20z75_device->irq, &bq20z75_device->power_supply); + if (bq20z75_device->gpio_detect) + gpio_free(pdata->battery_detect); + + kfree(bq20z75_device); + + return rc; +} + +static int __devexit bq20z75_remove(struct i2c_client *client) +{ + struct bq20z75_info *bq20z75_device = i2c_get_clientdata(client); + + if (bq20z75_device->irq) + free_irq(bq20z75_device->irq, &bq20z75_device->power_supply); + if (bq20z75_device->gpio_detect) + gpio_free(bq20z75_device->pdata->battery_detect); + + power_supply_unregister(&bq20z75_device->power_supply); + kfree(bq20z75_device); + bq20z75_device = NULL; + + return 0; +} + +#if defined CONFIG_PM +static int bq20z75_suspend(struct i2c_client *client, + pm_message_t state) +{ + struct bq20z75_info *bq20z75_device = i2c_get_clientdata(client); + s32 ret; + + /* write to manufacturer access with sleep command */ + ret = bq20z75_write_word_data(client, + bq20z75_data[REG_MANUFACTURER_DATA].addr, + MANUFACTURER_ACCESS_SLEEP); + if (bq20z75_device->is_present && ret < 0) + return ret; + + return 0; +} +#else +#define bq20z75_suspend NULL +#endif +/* any smbus transaction will wake up bq20z75 */ +#define bq20z75_resume NULL + +static const struct i2c_device_id bq20z75_id[] = { + { "bq20z75", 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, bq20z75_id); + +static struct i2c_driver bq20z75_battery_driver = { + .probe = bq20z75_probe, + .remove = __devexit_p(bq20z75_remove), + .suspend = bq20z75_suspend, + .resume = bq20z75_resume, + .id_table = bq20z75_id, + .driver = { + .name = "bq20z75-battery", + }, +}; + +static int __init bq20z75_battery_init(void) +{ + return i2c_add_driver(&bq20z75_battery_driver); +} +module_init(bq20z75_battery_init); + +static void __exit bq20z75_battery_exit(void) +{ + i2c_del_driver(&bq20z75_battery_driver); +} +module_exit(bq20z75_battery_exit); + +MODULE_DESCRIPTION("BQ20z75 battery monitor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/bq27x00_battery.c b/drivers/power/bq27x00_battery.c new file mode 100644 index 00000000..bb16f5b7 --- /dev/null +++ b/drivers/power/bq27x00_battery.c @@ -0,0 +1,862 @@ +/* + * BQ27x00 battery driver + * + * Copyright (C) 2008 Rodolfo Giometti + * Copyright (C) 2008 Eurotech S.p.A. + * Copyright (C) 2010-2011 Lars-Peter Clausen + * Copyright (C) 2011 Pali Rohár + * + * Based on a previous work by Copyright (C) 2008 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. + * + */ + +/* + * Datasheets: + * http://focus.ti.com/docs/prod/folders/print/bq27000.html + * http://focus.ti.com/docs/prod/folders/print/bq27500.html + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define DRIVER_VERSION "1.2.0" + +#define BQ27x00_REG_TEMP 0x06 +#define BQ27x00_REG_VOLT 0x08 +#define BQ27x00_REG_AI 0x14 +#define BQ27x00_REG_FLAGS 0x0A +#define BQ27x00_REG_TTE 0x16 +#define BQ27x00_REG_TTF 0x18 +#define BQ27x00_REG_TTECP 0x26 +#define BQ27x00_REG_NAC 0x0C /* Nominal available capaciy */ +#define BQ27x00_REG_LMD 0x12 /* Last measured discharge */ +#define BQ27x00_REG_CYCT 0x2A /* Cycle count total */ +#define BQ27x00_REG_AE 0x22 /* Available enery */ + +#define BQ27000_REG_RSOC 0x0B /* Relative State-of-Charge */ +#define BQ27000_REG_ILMD 0x76 /* Initial last measured discharge */ +#define BQ27000_FLAG_CHGS BIT(7) +#define BQ27000_FLAG_FC BIT(5) + +#define BQ27500_REG_SOC 0x2C +#define BQ27500_REG_DCAP 0x3C /* Design capacity */ +#define BQ27500_FLAG_DSC BIT(0) +#define BQ27500_FLAG_FC BIT(9) + +#define BQ27000_RS 20 /* Resistor sense */ + +struct bq27x00_device_info; +struct bq27x00_access_methods { + int (*read)(struct bq27x00_device_info *di, u8 reg, bool single); +}; + +enum bq27x00_chip { BQ27000, BQ27500 }; + +struct bq27x00_reg_cache { + int temperature; + int time_to_empty; + int time_to_empty_avg; + int time_to_full; + int charge_full; + int cycle_count; + int capacity; + int flags; + + int current_now; +}; + +struct bq27x00_device_info { + struct device *dev; + int id; + enum bq27x00_chip chip; + + struct bq27x00_reg_cache cache; + int charge_design_full; + + unsigned long last_update; + struct delayed_work work; + + struct power_supply bat; + + struct bq27x00_access_methods bus; + + struct mutex lock; +}; + +static enum power_supply_property bq27x00_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, + POWER_SUPPLY_PROP_TIME_TO_FULL_NOW, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_ENERGY_NOW, +}; + +static unsigned int poll_interval = 360; +module_param(poll_interval, uint, 0644); +MODULE_PARM_DESC(poll_interval, "battery poll interval in seconds - " \ + "0 disables polling"); + +/* + * Common code for BQ27x00 devices + */ + +static inline int bq27x00_read(struct bq27x00_device_info *di, u8 reg, + bool single) +{ + return di->bus.read(di, reg, single); +} + +/* + * Return the battery Relative State-of-Charge + * Or < 0 if something fails. + */ +static int bq27x00_battery_read_rsoc(struct bq27x00_device_info *di) +{ + int rsoc; + + if (di->chip == BQ27500) + rsoc = bq27x00_read(di, BQ27500_REG_SOC, false); + else + rsoc = bq27x00_read(di, BQ27000_REG_RSOC, true); + + if (rsoc < 0) + dev_err(di->dev, "error reading relative State-of-Charge\n"); + + return rsoc; +} + +/* + * Return a battery charge value in µAh + * Or < 0 if something fails. + */ +static int bq27x00_battery_read_charge(struct bq27x00_device_info *di, u8 reg) +{ + int charge; + + charge = bq27x00_read(di, reg, false); + if (charge < 0) { + dev_err(di->dev, "error reading nominal available capacity\n"); + return charge; + } + + if (di->chip == BQ27500) + charge *= 1000; + else + charge = charge * 3570 / BQ27000_RS; + + return charge; +} + +/* + * Return the battery Nominal available capaciy in µAh + * Or < 0 if something fails. + */ +static inline int bq27x00_battery_read_nac(struct bq27x00_device_info *di) +{ + return bq27x00_battery_read_charge(di, BQ27x00_REG_NAC); +} + +/* + * Return the battery Last measured discharge in µAh + * Or < 0 if something fails. + */ +static inline int bq27x00_battery_read_lmd(struct bq27x00_device_info *di) +{ + return bq27x00_battery_read_charge(di, BQ27x00_REG_LMD); +} + +/* + * Return the battery Initial last measured discharge in µAh + * Or < 0 if something fails. + */ +static int bq27x00_battery_read_ilmd(struct bq27x00_device_info *di) +{ + int ilmd; + + if (di->chip == BQ27500) + ilmd = bq27x00_read(di, BQ27500_REG_DCAP, false); + else + ilmd = bq27x00_read(di, BQ27000_REG_ILMD, true); + + if (ilmd < 0) { + dev_err(di->dev, "error reading initial last measured discharge\n"); + return ilmd; + } + + if (di->chip == BQ27500) + ilmd *= 1000; + else + ilmd = ilmd * 256 * 3570 / BQ27000_RS; + + return ilmd; +} + +/* + * Return the battery Cycle count total + * Or < 0 if something fails. + */ +static int bq27x00_battery_read_cyct(struct bq27x00_device_info *di) +{ + int cyct; + + cyct = bq27x00_read(di, BQ27x00_REG_CYCT, false); + if (cyct < 0) + dev_err(di->dev, "error reading cycle count total\n"); + + return cyct; +} + +/* + * Read a time register. + * Return < 0 if something fails. + */ +static int bq27x00_battery_read_time(struct bq27x00_device_info *di, u8 reg) +{ + int tval; + + tval = bq27x00_read(di, reg, false); + if (tval < 0) { + dev_err(di->dev, "error reading register %02x: %d\n", reg, tval); + return tval; + } + + if (tval == 65535) + return -ENODATA; + + return tval * 60; +} + +static void bq27x00_update(struct bq27x00_device_info *di) +{ + struct bq27x00_reg_cache cache = {0, }; + bool is_bq27500 = di->chip == BQ27500; + + cache.flags = bq27x00_read(di, BQ27x00_REG_FLAGS, is_bq27500); + if (cache.flags >= 0) { + cache.capacity = bq27x00_battery_read_rsoc(di); + cache.temperature = bq27x00_read(di, BQ27x00_REG_TEMP, false); + cache.time_to_empty = bq27x00_battery_read_time(di, BQ27x00_REG_TTE); + cache.time_to_empty_avg = bq27x00_battery_read_time(di, BQ27x00_REG_TTECP); + cache.time_to_full = bq27x00_battery_read_time(di, BQ27x00_REG_TTF); + cache.charge_full = bq27x00_battery_read_lmd(di); + cache.cycle_count = bq27x00_battery_read_cyct(di); + + if (!is_bq27500) + cache.current_now = bq27x00_read(di, BQ27x00_REG_AI, false); + + /* We only have to read charge design full once */ + if (di->charge_design_full <= 0) + di->charge_design_full = bq27x00_battery_read_ilmd(di); + } + + /* Ignore current_now which is a snapshot of the current battery state + * and is likely to be different even between two consecutive reads */ + if (memcmp(&di->cache, &cache, sizeof(cache) - sizeof(int)) != 0) { + di->cache = cache; + power_supply_changed(&di->bat); + } + + di->last_update = jiffies; +} + +static void bq27x00_battery_poll(struct work_struct *work) +{ + struct bq27x00_device_info *di = + container_of(work, struct bq27x00_device_info, work.work); + + bq27x00_update(di); + + if (poll_interval > 0) { + /* The timer does not have to be accurate. */ + set_timer_slack(&di->work.timer, poll_interval * HZ / 4); + schedule_delayed_work(&di->work, poll_interval * HZ); + } +} + + +/* + * Return the battery temperature in tenths of degree Celsius + * Or < 0 if something fails. + */ +static int bq27x00_battery_temperature(struct bq27x00_device_info *di, + union power_supply_propval *val) +{ + if (di->cache.temperature < 0) + return di->cache.temperature; + + if (di->chip == BQ27500) + val->intval = di->cache.temperature - 2731; + else + val->intval = ((di->cache.temperature * 5) - 5463) / 2; + + return 0; +} + +/* + * Return the battery average current in µA + * Note that current can be negative signed as well + * Or 0 if something fails. + */ +static int bq27x00_battery_current(struct bq27x00_device_info *di, + union power_supply_propval *val) +{ + int curr; + + if (di->chip == BQ27500) + curr = bq27x00_read(di, BQ27x00_REG_AI, false); + else + curr = di->cache.current_now; + + if (curr < 0) + return curr; + + if (di->chip == BQ27500) { + /* bq27500 returns signed value */ + val->intval = (int)((s16)curr) * 1000; + } else { + if (di->cache.flags & BQ27000_FLAG_CHGS) { + dev_dbg(di->dev, "negative current!\n"); + curr = -curr; + } + + val->intval = curr * 3570 / BQ27000_RS; + } + + return 0; +} + +static int bq27x00_battery_status(struct bq27x00_device_info *di, + union power_supply_propval *val) +{ + int status; + + if (di->chip == BQ27500) { + if (di->cache.flags & BQ27500_FLAG_FC) + status = POWER_SUPPLY_STATUS_FULL; + else if (di->cache.flags & BQ27500_FLAG_DSC) + status = POWER_SUPPLY_STATUS_DISCHARGING; + else + status = POWER_SUPPLY_STATUS_CHARGING; + } else { + if (di->cache.flags & BQ27000_FLAG_FC) + status = POWER_SUPPLY_STATUS_FULL; + else if (di->cache.flags & BQ27000_FLAG_CHGS) + status = POWER_SUPPLY_STATUS_CHARGING; + else if (power_supply_am_i_supplied(&di->bat)) + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + else + status = POWER_SUPPLY_STATUS_DISCHARGING; + } + + val->intval = status; + + return 0; +} + +/* + * Return the battery Voltage in milivolts + * Or < 0 if something fails. + */ +static int bq27x00_battery_voltage(struct bq27x00_device_info *di, + union power_supply_propval *val) +{ + int volt; + + volt = bq27x00_read(di, BQ27x00_REG_VOLT, false); + if (volt < 0) + return volt; + + val->intval = volt * 1000; + + return 0; +} + +/* + * Return the battery Available energy in µWh + * Or < 0 if something fails. + */ +static int bq27x00_battery_energy(struct bq27x00_device_info *di, + union power_supply_propval *val) +{ + int ae; + + ae = bq27x00_read(di, BQ27x00_REG_AE, false); + if (ae < 0) { + dev_err(di->dev, "error reading available energy\n"); + return ae; + } + + if (di->chip == BQ27500) + ae *= 1000; + else + ae = ae * 29200 / BQ27000_RS; + + val->intval = ae; + + return 0; +} + + +static int bq27x00_simple_value(int value, + union power_supply_propval *val) +{ + if (value < 0) + return value; + + val->intval = value; + + return 0; +} + +#define to_bq27x00_device_info(x) container_of((x), \ + struct bq27x00_device_info, bat); + +static int bq27x00_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret = 0; + struct bq27x00_device_info *di = to_bq27x00_device_info(psy); + + mutex_lock(&di->lock); + if (time_is_before_jiffies(di->last_update + 5 * HZ)) { + cancel_delayed_work_sync(&di->work); + bq27x00_battery_poll(&di->work.work); + } + mutex_unlock(&di->lock); + + if (psp != POWER_SUPPLY_PROP_PRESENT && di->cache.flags < 0) + return -ENODEV; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + ret = bq27x00_battery_status(di, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = bq27x00_battery_voltage(di, val); + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = di->cache.flags < 0 ? 0 : 1; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = bq27x00_battery_current(di, val); + break; + case POWER_SUPPLY_PROP_CAPACITY: + ret = bq27x00_simple_value(di->cache.capacity, val); + break; + case POWER_SUPPLY_PROP_TEMP: + ret = bq27x00_battery_temperature(di, val); + break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: + ret = bq27x00_simple_value(di->cache.time_to_empty, val); + break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: + ret = bq27x00_simple_value(di->cache.time_to_empty_avg, val); + break; + case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW: + ret = bq27x00_simple_value(di->cache.time_to_full, val); + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + ret = bq27x00_simple_value(bq27x00_battery_read_nac(di), val); + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + ret = bq27x00_simple_value(di->cache.charge_full, val); + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + ret = bq27x00_simple_value(di->charge_design_full, val); + break; + case POWER_SUPPLY_PROP_CYCLE_COUNT: + ret = bq27x00_simple_value(di->cache.cycle_count, val); + break; + case POWER_SUPPLY_PROP_ENERGY_NOW: + ret = bq27x00_battery_energy(di, val); + break; + default: + return -EINVAL; + } + + return ret; +} + +static void bq27x00_external_power_changed(struct power_supply *psy) +{ + struct bq27x00_device_info *di = to_bq27x00_device_info(psy); + + cancel_delayed_work_sync(&di->work); + schedule_delayed_work(&di->work, 0); +} + +static int bq27x00_powersupply_init(struct bq27x00_device_info *di) +{ + int ret; + + di->bat.type = POWER_SUPPLY_TYPE_BATTERY; + di->bat.properties = bq27x00_battery_props; + di->bat.num_properties = ARRAY_SIZE(bq27x00_battery_props); + di->bat.get_property = bq27x00_battery_get_property; + di->bat.external_power_changed = bq27x00_external_power_changed; + + INIT_DELAYED_WORK(&di->work, bq27x00_battery_poll); + mutex_init(&di->lock); + + ret = power_supply_register(di->dev, &di->bat); + if (ret) { + dev_err(di->dev, "failed to register battery: %d\n", ret); + return ret; + } + + dev_info(di->dev, "support ver. %s enabled\n", DRIVER_VERSION); + + bq27x00_update(di); + + return 0; +} + +static void bq27x00_powersupply_unregister(struct bq27x00_device_info *di) +{ + cancel_delayed_work_sync(&di->work); + + power_supply_unregister(&di->bat); + + mutex_destroy(&di->lock); +} + + +/* i2c specific code */ +#ifdef CONFIG_BATTERY_BQ27X00_I2C + +/* If the system has several batteries we need a different name for each + * of them... + */ +static DEFINE_IDR(battery_id); +static DEFINE_MUTEX(battery_mutex); + +static int bq27x00_read_i2c(struct bq27x00_device_info *di, u8 reg, bool single) +{ + struct i2c_client *client = to_i2c_client(di->dev); + struct i2c_msg msg[2]; + unsigned char data[2]; + int ret; + + if (!client->adapter) + return -ENODEV; + + msg[0].addr = client->addr; + msg[0].flags = 0; + msg[0].buf = ® + msg[0].len = sizeof(reg); + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].buf = data; + if (single) + msg[1].len = 1; + else + msg[1].len = 2; + + ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + if (ret < 0) + return ret; + + if (!single) + ret = get_unaligned_le16(data); + else + ret = data[0]; + + return ret; +} + +static int bq27x00_battery_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + char *name; + struct bq27x00_device_info *di; + int num; + int retval = 0; + + /* Get new ID for the new battery device */ + retval = idr_pre_get(&battery_id, GFP_KERNEL); + if (retval == 0) + return -ENOMEM; + mutex_lock(&battery_mutex); + retval = idr_get_new(&battery_id, client, &num); + mutex_unlock(&battery_mutex); + if (retval < 0) + return retval; + + name = kasprintf(GFP_KERNEL, "%s-%d", id->name, num); + if (!name) { + dev_err(&client->dev, "failed to allocate device name\n"); + retval = -ENOMEM; + goto batt_failed_1; + } + + di = kzalloc(sizeof(*di), GFP_KERNEL); + if (!di) { + dev_err(&client->dev, "failed to allocate device info data\n"); + retval = -ENOMEM; + goto batt_failed_2; + } + + di->id = num; + di->dev = &client->dev; + di->chip = id->driver_data; + di->bat.name = name; + di->bus.read = &bq27x00_read_i2c; + + if (bq27x00_powersupply_init(di)) + goto batt_failed_3; + + i2c_set_clientdata(client, di); + + return 0; + +batt_failed_3: + kfree(di); +batt_failed_2: + kfree(name); +batt_failed_1: + mutex_lock(&battery_mutex); + idr_remove(&battery_id, num); + mutex_unlock(&battery_mutex); + + return retval; +} + +static int bq27x00_battery_remove(struct i2c_client *client) +{ + struct bq27x00_device_info *di = i2c_get_clientdata(client); + + bq27x00_powersupply_unregister(di); + + kfree(di->bat.name); + + mutex_lock(&battery_mutex); + idr_remove(&battery_id, di->id); + mutex_unlock(&battery_mutex); + + kfree(di); + + return 0; +} + +static const struct i2c_device_id bq27x00_id[] = { + { "bq27200", BQ27000 }, /* bq27200 is same as bq27000, but with i2c */ + { "bq27500", BQ27500 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, bq27x00_id); + +static struct i2c_driver bq27x00_battery_driver = { + .driver = { + .name = "bq27x00-battery", + }, + .probe = bq27x00_battery_probe, + .remove = bq27x00_battery_remove, + .id_table = bq27x00_id, +}; + +static inline int bq27x00_battery_i2c_init(void) +{ + int ret = i2c_add_driver(&bq27x00_battery_driver); + if (ret) + printk(KERN_ERR "Unable to register BQ27x00 i2c driver\n"); + + return ret; +} + +static inline void bq27x00_battery_i2c_exit(void) +{ + i2c_del_driver(&bq27x00_battery_driver); +} + +#else + +static inline int bq27x00_battery_i2c_init(void) { return 0; } +static inline void bq27x00_battery_i2c_exit(void) {}; + +#endif + +/* platform specific code */ +#ifdef CONFIG_BATTERY_BQ27X00_PLATFORM + +static int bq27000_read_platform(struct bq27x00_device_info *di, u8 reg, + bool single) +{ + struct device *dev = di->dev; + struct bq27000_platform_data *pdata = dev->platform_data; + unsigned int timeout = 3; + int upper, lower; + int temp; + + if (!single) { + /* Make sure the value has not changed in between reading the + * lower and the upper part */ + upper = pdata->read(dev, reg + 1); + do { + temp = upper; + if (upper < 0) + return upper; + + lower = pdata->read(dev, reg); + if (lower < 0) + return lower; + + upper = pdata->read(dev, reg + 1); + } while (temp != upper && --timeout); + + if (timeout == 0) + return -EIO; + + return (upper << 8) | lower; + } + + return pdata->read(dev, reg); +} + +static int __devinit bq27000_battery_probe(struct platform_device *pdev) +{ + struct bq27x00_device_info *di; + struct bq27000_platform_data *pdata = pdev->dev.platform_data; + int ret; + + if (!pdata) { + dev_err(&pdev->dev, "no platform_data supplied\n"); + return -EINVAL; + } + + if (!pdata->read) { + dev_err(&pdev->dev, "no hdq read callback supplied\n"); + return -EINVAL; + } + + di = kzalloc(sizeof(*di), GFP_KERNEL); + if (!di) { + dev_err(&pdev->dev, "failed to allocate device info data\n"); + return -ENOMEM; + } + + platform_set_drvdata(pdev, di); + + di->dev = &pdev->dev; + di->chip = BQ27000; + + di->bat.name = pdata->name ?: dev_name(&pdev->dev); + di->bus.read = &bq27000_read_platform; + + ret = bq27x00_powersupply_init(di); + if (ret) + goto err_free; + + return 0; + +err_free: + platform_set_drvdata(pdev, NULL); + kfree(di); + + return ret; +} + +static int __devexit bq27000_battery_remove(struct platform_device *pdev) +{ + struct bq27x00_device_info *di = platform_get_drvdata(pdev); + + bq27x00_powersupply_unregister(di); + + platform_set_drvdata(pdev, NULL); + kfree(di); + + return 0; +} + +static struct platform_driver bq27000_battery_driver = { + .probe = bq27000_battery_probe, + .remove = __devexit_p(bq27000_battery_remove), + .driver = { + .name = "bq27000-battery", + .owner = THIS_MODULE, + }, +}; + +static inline int bq27x00_battery_platform_init(void) +{ + int ret = platform_driver_register(&bq27000_battery_driver); + if (ret) + printk(KERN_ERR "Unable to register BQ27000 platform driver\n"); + + return ret; +} + +static inline void bq27x00_battery_platform_exit(void) +{ + platform_driver_unregister(&bq27000_battery_driver); +} + +#else + +static inline int bq27x00_battery_platform_init(void) { return 0; } +static inline void bq27x00_battery_platform_exit(void) {}; + +#endif + +/* + * Module stuff + */ + +static int __init bq27x00_battery_init(void) +{ + int ret; + + ret = bq27x00_battery_i2c_init(); + if (ret) + return ret; + + ret = bq27x00_battery_platform_init(); + if (ret) + bq27x00_battery_i2c_exit(); + + return ret; +} +module_init(bq27x00_battery_init); + +static void __exit bq27x00_battery_exit(void) +{ + bq27x00_battery_platform_exit(); + bq27x00_battery_i2c_exit(); +} +module_exit(bq27x00_battery_exit); + +MODULE_AUTHOR("Rodolfo Giometti "); +MODULE_DESCRIPTION("BQ27x00 battery monitor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/collie_battery.c b/drivers/power/collie_battery.c new file mode 100644 index 00000000..548d263b --- /dev/null +++ b/drivers/power/collie_battery.c @@ -0,0 +1,417 @@ +/* + * Battery and Power Management code for the Sharp SL-5x00 + * + * Copyright (C) 2009 Thomas Kunze + * + * based on tosa_battery.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +static DEFINE_MUTEX(bat_lock); /* protects gpio pins */ +static struct work_struct bat_work; +static struct ucb1x00 *ucb; + +struct collie_bat { + int status; + struct power_supply psy; + int full_chrg; + + struct mutex work_lock; /* protects data */ + + bool (*is_present)(struct collie_bat *bat); + int gpio_full; + int gpio_charge_on; + + int technology; + + int gpio_bat; + int adc_bat; + int adc_bat_divider; + int bat_max; + int bat_min; + + int gpio_temp; + int adc_temp; + int adc_temp_divider; +}; + +static struct collie_bat collie_bat_main; + +static unsigned long collie_read_bat(struct collie_bat *bat) +{ + unsigned long value = 0; + + if (bat->gpio_bat < 0 || bat->adc_bat < 0) + return 0; + mutex_lock(&bat_lock); + gpio_set_value(bat->gpio_bat, 1); + msleep(5); + ucb1x00_adc_enable(ucb); + value = ucb1x00_adc_read(ucb, bat->adc_bat, UCB_SYNC); + ucb1x00_adc_disable(ucb); + gpio_set_value(bat->gpio_bat, 0); + mutex_unlock(&bat_lock); + value = value * 1000000 / bat->adc_bat_divider; + + return value; +} + +static unsigned long collie_read_temp(struct collie_bat *bat) +{ + unsigned long value = 0; + if (bat->gpio_temp < 0 || bat->adc_temp < 0) + return 0; + + mutex_lock(&bat_lock); + gpio_set_value(bat->gpio_temp, 1); + msleep(5); + ucb1x00_adc_enable(ucb); + value = ucb1x00_adc_read(ucb, bat->adc_temp, UCB_SYNC); + ucb1x00_adc_disable(ucb); + gpio_set_value(bat->gpio_temp, 0); + mutex_unlock(&bat_lock); + + value = value * 10000 / bat->adc_temp_divider; + + return value; +} + +static int collie_bat_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret = 0; + struct collie_bat *bat = container_of(psy, struct collie_bat, psy); + + if (bat->is_present && !bat->is_present(bat) + && psp != POWER_SUPPLY_PROP_PRESENT) { + return -ENODEV; + } + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = bat->status; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = bat->technology; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = collie_read_bat(bat); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + if (bat->full_chrg == -1) + val->intval = bat->bat_max; + else + val->intval = bat->full_chrg; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = bat->bat_max; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = bat->bat_min; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = collie_read_temp(bat); + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = bat->is_present ? bat->is_present(bat) : 1; + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static void collie_bat_external_power_changed(struct power_supply *psy) +{ + schedule_work(&bat_work); +} + +static irqreturn_t collie_bat_gpio_isr(int irq, void *data) +{ + pr_info("collie_bat_gpio irq: %d\n", gpio_get_value(irq_to_gpio(irq))); + schedule_work(&bat_work); + return IRQ_HANDLED; +} + +static void collie_bat_update(struct collie_bat *bat) +{ + int old; + struct power_supply *psy = &bat->psy; + + mutex_lock(&bat->work_lock); + + old = bat->status; + + if (bat->is_present && !bat->is_present(bat)) { + printk(KERN_NOTICE "%s not present\n", psy->name); + bat->status = POWER_SUPPLY_STATUS_UNKNOWN; + bat->full_chrg = -1; + } else if (power_supply_am_i_supplied(psy)) { + if (bat->status == POWER_SUPPLY_STATUS_DISCHARGING) { + gpio_set_value(bat->gpio_charge_on, 1); + mdelay(15); + } + + if (gpio_get_value(bat->gpio_full)) { + if (old == POWER_SUPPLY_STATUS_CHARGING || + bat->full_chrg == -1) + bat->full_chrg = collie_read_bat(bat); + + gpio_set_value(bat->gpio_charge_on, 0); + bat->status = POWER_SUPPLY_STATUS_FULL; + } else { + gpio_set_value(bat->gpio_charge_on, 1); + bat->status = POWER_SUPPLY_STATUS_CHARGING; + } + } else { + gpio_set_value(bat->gpio_charge_on, 0); + bat->status = POWER_SUPPLY_STATUS_DISCHARGING; + } + + if (old != bat->status) + power_supply_changed(psy); + + mutex_unlock(&bat->work_lock); +} + +static void collie_bat_work(struct work_struct *work) +{ + collie_bat_update(&collie_bat_main); +} + + +static enum power_supply_property collie_bat_main_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TEMP, +}; + +static enum power_supply_property collie_bat_bu_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_PRESENT, +}; + +static struct collie_bat collie_bat_main = { + .status = POWER_SUPPLY_STATUS_DISCHARGING, + .full_chrg = -1, + .psy = { + .name = "main-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = collie_bat_main_props, + .num_properties = ARRAY_SIZE(collie_bat_main_props), + .get_property = collie_bat_get_property, + .external_power_changed = collie_bat_external_power_changed, + .use_for_apm = 1, + }, + + .gpio_full = COLLIE_GPIO_CO, + .gpio_charge_on = COLLIE_GPIO_CHARGE_ON, + + .technology = POWER_SUPPLY_TECHNOLOGY_LIPO, + + .gpio_bat = COLLIE_GPIO_MBAT_ON, + .adc_bat = UCB_ADC_INP_AD1, + .adc_bat_divider = 155, + .bat_max = 4310000, + .bat_min = 1551 * 1000000 / 414, + + .gpio_temp = COLLIE_GPIO_TMP_ON, + .adc_temp = UCB_ADC_INP_AD0, + .adc_temp_divider = 10000, +}; + +static struct collie_bat collie_bat_bu = { + .status = POWER_SUPPLY_STATUS_UNKNOWN, + .full_chrg = -1, + + .psy = { + .name = "backup-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = collie_bat_bu_props, + .num_properties = ARRAY_SIZE(collie_bat_bu_props), + .get_property = collie_bat_get_property, + .external_power_changed = collie_bat_external_power_changed, + }, + + .gpio_full = -1, + .gpio_charge_on = -1, + + .technology = POWER_SUPPLY_TECHNOLOGY_LiMn, + + .gpio_bat = COLLIE_GPIO_BBAT_ON, + .adc_bat = UCB_ADC_INP_AD1, + .adc_bat_divider = 155, + .bat_max = 3000000, + .bat_min = 1900000, + + .gpio_temp = -1, + .adc_temp = -1, + .adc_temp_divider = -1, +}; + +static struct { + int gpio; + char *name; + bool output; + int value; +} gpios[] = { + { COLLIE_GPIO_CO, "main battery full", 0, 0 }, + { COLLIE_GPIO_MAIN_BAT_LOW, "main battery low", 0, 0 }, + { COLLIE_GPIO_CHARGE_ON, "main charge on", 1, 0 }, + { COLLIE_GPIO_MBAT_ON, "main battery", 1, 0 }, + { COLLIE_GPIO_TMP_ON, "main battery temp", 1, 0 }, + { COLLIE_GPIO_BBAT_ON, "backup battery", 1, 0 }, +}; + +#ifdef CONFIG_PM +static int collie_bat_suspend(struct ucb1x00_dev *dev, pm_message_t state) +{ + /* flush all pending status updates */ + flush_work_sync(&bat_work); + return 0; +} + +static int collie_bat_resume(struct ucb1x00_dev *dev) +{ + /* things may have changed while we were away */ + schedule_work(&bat_work); + return 0; +} +#else +#define collie_bat_suspend NULL +#define collie_bat_resume NULL +#endif + +static int __devinit collie_bat_probe(struct ucb1x00_dev *dev) +{ + int ret; + int i; + + if (!machine_is_collie()) + return -ENODEV; + + ucb = dev->ucb; + + for (i = 0; i < ARRAY_SIZE(gpios); i++) { + ret = gpio_request(gpios[i].gpio, gpios[i].name); + if (ret) { + i--; + goto err_gpio; + } + + if (gpios[i].output) + ret = gpio_direction_output(gpios[i].gpio, + gpios[i].value); + else + ret = gpio_direction_input(gpios[i].gpio); + + if (ret) + goto err_gpio; + } + + mutex_init(&collie_bat_main.work_lock); + + INIT_WORK(&bat_work, collie_bat_work); + + ret = power_supply_register(&dev->ucb->dev, &collie_bat_main.psy); + if (ret) + goto err_psy_reg_main; + ret = power_supply_register(&dev->ucb->dev, &collie_bat_bu.psy); + if (ret) + goto err_psy_reg_bu; + + ret = request_irq(gpio_to_irq(COLLIE_GPIO_CO), + collie_bat_gpio_isr, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "main full", &collie_bat_main); + if (!ret) { + schedule_work(&bat_work); + return 0; + } + power_supply_unregister(&collie_bat_bu.psy); +err_psy_reg_bu: + power_supply_unregister(&collie_bat_main.psy); +err_psy_reg_main: + + /* see comment in collie_bat_remove */ + cancel_work_sync(&bat_work); + + i--; +err_gpio: + for (; i >= 0; i--) + gpio_free(gpios[i].gpio); + + return ret; +} + +static void __devexit collie_bat_remove(struct ucb1x00_dev *dev) +{ + int i; + + free_irq(gpio_to_irq(COLLIE_GPIO_CO), &collie_bat_main); + + power_supply_unregister(&collie_bat_bu.psy); + power_supply_unregister(&collie_bat_main.psy); + + /* + * Now cancel the bat_work. We won't get any more schedules, + * since all sources (isr and external_power_changed) are + * unregistered now. + */ + cancel_work_sync(&bat_work); + + for (i = ARRAY_SIZE(gpios) - 1; i >= 0; i--) + gpio_free(gpios[i].gpio); +} + +static struct ucb1x00_driver collie_bat_driver = { + .add = collie_bat_probe, + .remove = __devexit_p(collie_bat_remove), + .suspend = collie_bat_suspend, + .resume = collie_bat_resume, +}; + +static int __init collie_bat_init(void) +{ + return ucb1x00_register_driver(&collie_bat_driver); +} + +static void __exit collie_bat_exit(void) +{ + ucb1x00_unregister_driver(&collie_bat_driver); +} + +module_init(collie_bat_init); +module_exit(collie_bat_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Thomas Kunze"); +MODULE_DESCRIPTION("Collie battery driver"); diff --git a/drivers/power/da9030_battery.c b/drivers/power/da9030_battery.c new file mode 100644 index 00000000..d2c793cf --- /dev/null +++ b/drivers/power/da9030_battery.c @@ -0,0 +1,606 @@ +/* + * Battery charger driver for Dialog Semiconductor DA9030 + * + * Copyright (C) 2008 Compulab, Ltd. + * Mike Rapoport + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define DA9030_FAULT_LOG 0x0a +#define DA9030_FAULT_LOG_OVER_TEMP (1 << 7) +#define DA9030_FAULT_LOG_VBAT_OVER (1 << 4) + +#define DA9030_CHARGE_CONTROL 0x28 +#define DA9030_CHRG_CHARGER_ENABLE (1 << 7) + +#define DA9030_ADC_MAN_CONTROL 0x30 +#define DA9030_ADC_TBATREF_ENABLE (1 << 5) +#define DA9030_ADC_LDO_INT_ENABLE (1 << 4) + +#define DA9030_ADC_AUTO_CONTROL 0x31 +#define DA9030_ADC_TBAT_ENABLE (1 << 5) +#define DA9030_ADC_VBAT_IN_TXON (1 << 4) +#define DA9030_ADC_VCH_ENABLE (1 << 3) +#define DA9030_ADC_ICH_ENABLE (1 << 2) +#define DA9030_ADC_VBAT_ENABLE (1 << 1) +#define DA9030_ADC_AUTO_SLEEP_ENABLE (1 << 0) + +#define DA9030_VBATMON 0x32 +#define DA9030_VBATMONTXON 0x33 +#define DA9030_TBATHIGHP 0x34 +#define DA9030_TBATHIGHN 0x35 +#define DA9030_TBATLOW 0x36 + +#define DA9030_VBAT_RES 0x41 +#define DA9030_VBATMIN_RES 0x42 +#define DA9030_VBATMINTXON_RES 0x43 +#define DA9030_ICHMAX_RES 0x44 +#define DA9030_ICHMIN_RES 0x45 +#define DA9030_ICHAVERAGE_RES 0x46 +#define DA9030_VCHMAX_RES 0x47 +#define DA9030_VCHMIN_RES 0x48 +#define DA9030_TBAT_RES 0x49 + +struct da9030_adc_res { + uint8_t vbat_res; + uint8_t vbatmin_res; + uint8_t vbatmintxon; + uint8_t ichmax_res; + uint8_t ichmin_res; + uint8_t ichaverage_res; + uint8_t vchmax_res; + uint8_t vchmin_res; + uint8_t tbat_res; + uint8_t adc_in4_res; + uint8_t adc_in5_res; +}; + +struct da9030_battery_thresholds { + int tbat_low; + int tbat_high; + int tbat_restart; + + int vbat_low; + int vbat_crit; + int vbat_charge_start; + int vbat_charge_stop; + int vbat_charge_restart; + + int vcharge_min; + int vcharge_max; +}; + +struct da9030_charger { + struct power_supply psy; + + struct device *master; + + struct da9030_adc_res adc; + struct delayed_work work; + unsigned int interval; + + struct power_supply_info *battery_info; + + struct da9030_battery_thresholds thresholds; + + unsigned int charge_milliamp; + unsigned int charge_millivolt; + + /* charger status */ + bool chdet; + uint8_t fault; + int mA; + int mV; + bool is_on; + + struct notifier_block nb; + + /* platform callbacks for battery low and critical events */ + void (*battery_low)(void); + void (*battery_critical)(void); + + struct dentry *debug_file; +}; + +static inline int da9030_reg_to_mV(int reg) +{ + return ((reg * 2650) >> 8) + 2650; +} + +static inline int da9030_millivolt_to_reg(int mV) +{ + return ((mV - 2650) << 8) / 2650; +} + +static inline int da9030_reg_to_mA(int reg) +{ + return ((reg * 24000) >> 8) / 15; +} + +#ifdef CONFIG_DEBUG_FS +static int bat_debug_show(struct seq_file *s, void *data) +{ + struct da9030_charger *charger = s->private; + + seq_printf(s, "charger is %s\n", charger->is_on ? "on" : "off"); + if (charger->chdet) { + seq_printf(s, "iset = %dmA, vset = %dmV\n", + charger->mA, charger->mV); + } + + seq_printf(s, "vbat_res = %d (%dmV)\n", + charger->adc.vbat_res, + da9030_reg_to_mV(charger->adc.vbat_res)); + seq_printf(s, "vbatmin_res = %d (%dmV)\n", + charger->adc.vbatmin_res, + da9030_reg_to_mV(charger->adc.vbatmin_res)); + seq_printf(s, "vbatmintxon = %d (%dmV)\n", + charger->adc.vbatmintxon, + da9030_reg_to_mV(charger->adc.vbatmintxon)); + seq_printf(s, "ichmax_res = %d (%dmA)\n", + charger->adc.ichmax_res, + da9030_reg_to_mV(charger->adc.ichmax_res)); + seq_printf(s, "ichmin_res = %d (%dmA)\n", + charger->adc.ichmin_res, + da9030_reg_to_mA(charger->adc.ichmin_res)); + seq_printf(s, "ichaverage_res = %d (%dmA)\n", + charger->adc.ichaverage_res, + da9030_reg_to_mA(charger->adc.ichaverage_res)); + seq_printf(s, "vchmax_res = %d (%dmV)\n", + charger->adc.vchmax_res, + da9030_reg_to_mA(charger->adc.vchmax_res)); + seq_printf(s, "vchmin_res = %d (%dmV)\n", + charger->adc.vchmin_res, + da9030_reg_to_mV(charger->adc.vchmin_res)); + + return 0; +} + +static int debug_open(struct inode *inode, struct file *file) +{ + return single_open(file, bat_debug_show, inode->i_private); +} + +static const struct file_operations bat_debug_fops = { + .open = debug_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static struct dentry *da9030_bat_create_debugfs(struct da9030_charger *charger) +{ + charger->debug_file = debugfs_create_file("charger", 0666, 0, charger, + &bat_debug_fops); + return charger->debug_file; +} + +static void da9030_bat_remove_debugfs(struct da9030_charger *charger) +{ + debugfs_remove(charger->debug_file); +} +#else +static inline struct dentry *da9030_bat_create_debugfs(struct da9030_charger *charger) +{ + return NULL; +} +static inline void da9030_bat_remove_debugfs(struct da9030_charger *charger) +{ +} +#endif + +static inline void da9030_read_adc(struct da9030_charger *charger, + struct da9030_adc_res *adc) +{ + da903x_reads(charger->master, DA9030_VBAT_RES, + sizeof(*adc), (uint8_t *)adc); +} + +static void da9030_charger_update_state(struct da9030_charger *charger) +{ + uint8_t val; + + da903x_read(charger->master, DA9030_CHARGE_CONTROL, &val); + charger->is_on = (val & DA9030_CHRG_CHARGER_ENABLE) ? 1 : 0; + charger->mA = ((val >> 3) & 0xf) * 100; + charger->mV = (val & 0x7) * 50 + 4000; + + da9030_read_adc(charger, &charger->adc); + da903x_read(charger->master, DA9030_FAULT_LOG, &charger->fault); + charger->chdet = da903x_query_status(charger->master, + DA9030_STATUS_CHDET); +} + +static void da9030_set_charge(struct da9030_charger *charger, int on) +{ + uint8_t val; + + if (on) { + val = DA9030_CHRG_CHARGER_ENABLE; + val |= (charger->charge_milliamp / 100) << 3; + val |= (charger->charge_millivolt - 4000) / 50; + charger->is_on = 1; + } else { + val = 0; + charger->is_on = 0; + } + + da903x_write(charger->master, DA9030_CHARGE_CONTROL, val); + + power_supply_changed(&charger->psy); +} + +static void da9030_charger_check_state(struct da9030_charger *charger) +{ + da9030_charger_update_state(charger); + + /* we wake or boot with external power on */ + if (!charger->is_on) { + if ((charger->chdet) && + (charger->adc.vbat_res < + charger->thresholds.vbat_charge_start)) { + da9030_set_charge(charger, 1); + } + } else { + /* Charger has been pulled out */ + if (!charger->chdet) { + da9030_set_charge(charger, 0); + return; + } + + if (charger->adc.vbat_res >= + charger->thresholds.vbat_charge_stop) { + da9030_set_charge(charger, 0); + da903x_write(charger->master, DA9030_VBATMON, + charger->thresholds.vbat_charge_restart); + } else if (charger->adc.vbat_res > + charger->thresholds.vbat_low) { + /* we are charging and passed LOW_THRESH, + so upate DA9030 VBAT threshold + */ + da903x_write(charger->master, DA9030_VBATMON, + charger->thresholds.vbat_low); + } + if (charger->adc.vchmax_res > charger->thresholds.vcharge_max || + charger->adc.vchmin_res < charger->thresholds.vcharge_min || + /* Tempreture readings are negative */ + charger->adc.tbat_res < charger->thresholds.tbat_high || + charger->adc.tbat_res > charger->thresholds.tbat_low) { + /* disable charger */ + da9030_set_charge(charger, 0); + } + } +} + +static void da9030_charging_monitor(struct work_struct *work) +{ + struct da9030_charger *charger; + + charger = container_of(work, struct da9030_charger, work.work); + + da9030_charger_check_state(charger); + + /* reschedule for the next time */ + schedule_delayed_work(&charger->work, charger->interval); +} + +static enum power_supply_property da9030_battery_props[] = { + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, +}; + +static void da9030_battery_check_status(struct da9030_charger *charger, + union power_supply_propval *val) +{ + if (charger->chdet) { + if (charger->is_on) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + } else { + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + } +} + +static void da9030_battery_check_health(struct da9030_charger *charger, + union power_supply_propval *val) +{ + if (charger->fault & DA9030_FAULT_LOG_OVER_TEMP) + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (charger->fault & DA9030_FAULT_LOG_VBAT_OVER) + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; +} + +static int da9030_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct da9030_charger *charger; + charger = container_of(psy, struct da9030_charger, psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + da9030_battery_check_status(charger, val); + break; + case POWER_SUPPLY_PROP_HEALTH: + da9030_battery_check_health(charger, val); + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = charger->battery_info->technology; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = charger->battery_info->voltage_max_design; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = charger->battery_info->voltage_min_design; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = da9030_reg_to_mV(charger->adc.vbat_res) * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + val->intval = + da9030_reg_to_mA(charger->adc.ichaverage_res) * 1000; + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = charger->battery_info->name; + break; + default: + break; + } + + return 0; +} + +static void da9030_battery_vbat_event(struct da9030_charger *charger) +{ + da9030_read_adc(charger, &charger->adc); + + if (charger->is_on) + return; + + if (charger->adc.vbat_res < charger->thresholds.vbat_low) { + /* set VBAT threshold for critical */ + da903x_write(charger->master, DA9030_VBATMON, + charger->thresholds.vbat_crit); + if (charger->battery_low) + charger->battery_low(); + } else if (charger->adc.vbat_res < + charger->thresholds.vbat_crit) { + /* notify the system of battery critical */ + if (charger->battery_critical) + charger->battery_critical(); + } +} + +static int da9030_battery_event(struct notifier_block *nb, unsigned long event, + void *data) +{ + struct da9030_charger *charger = + container_of(nb, struct da9030_charger, nb); + + switch (event) { + case DA9030_EVENT_CHDET: + cancel_delayed_work_sync(&charger->work); + schedule_work(&charger->work.work); + break; + case DA9030_EVENT_VBATMON: + da9030_battery_vbat_event(charger); + break; + case DA9030_EVENT_CHIOVER: + case DA9030_EVENT_TBAT: + da9030_set_charge(charger, 0); + break; + } + + return 0; +} + +static void da9030_battery_convert_thresholds(struct da9030_charger *charger, + struct da9030_battery_info *pdata) +{ + charger->thresholds.tbat_low = pdata->tbat_low; + charger->thresholds.tbat_high = pdata->tbat_high; + charger->thresholds.tbat_restart = pdata->tbat_restart; + + charger->thresholds.vbat_low = + da9030_millivolt_to_reg(pdata->vbat_low); + charger->thresholds.vbat_crit = + da9030_millivolt_to_reg(pdata->vbat_crit); + charger->thresholds.vbat_charge_start = + da9030_millivolt_to_reg(pdata->vbat_charge_start); + charger->thresholds.vbat_charge_stop = + da9030_millivolt_to_reg(pdata->vbat_charge_stop); + charger->thresholds.vbat_charge_restart = + da9030_millivolt_to_reg(pdata->vbat_charge_restart); + + charger->thresholds.vcharge_min = + da9030_millivolt_to_reg(pdata->vcharge_min); + charger->thresholds.vcharge_max = + da9030_millivolt_to_reg(pdata->vcharge_max); +} + +static void da9030_battery_setup_psy(struct da9030_charger *charger) +{ + struct power_supply *psy = &charger->psy; + struct power_supply_info *info = charger->battery_info; + + psy->name = info->name; + psy->use_for_apm = info->use_for_apm; + psy->type = POWER_SUPPLY_TYPE_BATTERY; + psy->get_property = da9030_battery_get_property; + + psy->properties = da9030_battery_props; + psy->num_properties = ARRAY_SIZE(da9030_battery_props); +}; + +static int da9030_battery_charger_init(struct da9030_charger *charger) +{ + char v[5]; + int ret; + + v[0] = v[1] = charger->thresholds.vbat_low; + v[2] = charger->thresholds.tbat_high; + v[3] = charger->thresholds.tbat_restart; + v[4] = charger->thresholds.tbat_low; + + ret = da903x_writes(charger->master, DA9030_VBATMON, 5, v); + if (ret) + return ret; + + /* + * Enable reference voltage supply for ADC from the LDO_INTERNAL + * regulator. Must be set before ADC measurements can be made. + */ + ret = da903x_write(charger->master, DA9030_ADC_MAN_CONTROL, + DA9030_ADC_LDO_INT_ENABLE | + DA9030_ADC_TBATREF_ENABLE); + if (ret) + return ret; + + /* enable auto ADC measuremnts */ + return da903x_write(charger->master, DA9030_ADC_AUTO_CONTROL, + DA9030_ADC_TBAT_ENABLE | DA9030_ADC_VBAT_IN_TXON | + DA9030_ADC_VCH_ENABLE | DA9030_ADC_ICH_ENABLE | + DA9030_ADC_VBAT_ENABLE | + DA9030_ADC_AUTO_SLEEP_ENABLE); +} + +static int da9030_battery_probe(struct platform_device *pdev) +{ + struct da9030_charger *charger; + struct da9030_battery_info *pdata = pdev->dev.platform_data; + int ret; + + if (pdata == NULL) + return -EINVAL; + + if (pdata->charge_milliamp >= 1500 || + pdata->charge_millivolt < 4000 || + pdata->charge_millivolt > 4350) + return -EINVAL; + + charger = kzalloc(sizeof(*charger), GFP_KERNEL); + if (charger == NULL) + return -ENOMEM; + + charger->master = pdev->dev.parent; + + /* 10 seconds between monitor runs unless platform defines other + interval */ + charger->interval = msecs_to_jiffies( + (pdata->batmon_interval ? : 10) * 1000); + + charger->charge_milliamp = pdata->charge_milliamp; + charger->charge_millivolt = pdata->charge_millivolt; + charger->battery_info = pdata->battery_info; + charger->battery_low = pdata->battery_low; + charger->battery_critical = pdata->battery_critical; + + da9030_battery_convert_thresholds(charger, pdata); + + ret = da9030_battery_charger_init(charger); + if (ret) + goto err_charger_init; + + INIT_DELAYED_WORK(&charger->work, da9030_charging_monitor); + schedule_delayed_work(&charger->work, charger->interval); + + charger->nb.notifier_call = da9030_battery_event; + ret = da903x_register_notifier(charger->master, &charger->nb, + DA9030_EVENT_CHDET | + DA9030_EVENT_VBATMON | + DA9030_EVENT_CHIOVER | + DA9030_EVENT_TBAT); + if (ret) + goto err_notifier; + + da9030_battery_setup_psy(charger); + ret = power_supply_register(&pdev->dev, &charger->psy); + if (ret) + goto err_ps_register; + + charger->debug_file = da9030_bat_create_debugfs(charger); + platform_set_drvdata(pdev, charger); + return 0; + +err_ps_register: + da903x_unregister_notifier(charger->master, &charger->nb, + DA9030_EVENT_CHDET | DA9030_EVENT_VBATMON | + DA9030_EVENT_CHIOVER | DA9030_EVENT_TBAT); +err_notifier: + cancel_delayed_work(&charger->work); + +err_charger_init: + kfree(charger); + + return ret; +} + +static int da9030_battery_remove(struct platform_device *dev) +{ + struct da9030_charger *charger = platform_get_drvdata(dev); + + da9030_bat_remove_debugfs(charger); + + da903x_unregister_notifier(charger->master, &charger->nb, + DA9030_EVENT_CHDET | DA9030_EVENT_VBATMON | + DA9030_EVENT_CHIOVER | DA9030_EVENT_TBAT); + cancel_delayed_work_sync(&charger->work); + da9030_set_charge(charger, 0); + power_supply_unregister(&charger->psy); + + kfree(charger); + + return 0; +} + +static struct platform_driver da903x_battery_driver = { + .driver = { + .name = "da903x-battery", + .owner = THIS_MODULE, + }, + .probe = da9030_battery_probe, + .remove = da9030_battery_remove, +}; + +static int da903x_battery_init(void) +{ + return platform_driver_register(&da903x_battery_driver); +} + +static void da903x_battery_exit(void) +{ + platform_driver_unregister(&da903x_battery_driver); +} + +module_init(da903x_battery_init); +module_exit(da903x_battery_exit); + +MODULE_DESCRIPTION("DA9030 battery charger driver"); +MODULE_AUTHOR("Mike Rapoport, CompuLab"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/da9052-battery.c b/drivers/power/da9052-battery.c new file mode 100644 index 00000000..85561b46 --- /dev/null +++ b/drivers/power/da9052-battery.c @@ -0,0 +1,844 @@ +/* + * da9052-battery.c -- Batttery Driver for Dialog DA9052 + * + * Copyright(c) 2009 Dialog Semiconductor Ltd. + + * Author: Dialog Semiconductor Ltd + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define DA9052_BAT_DEVICE_NAME "da9052-bat" + +static const char __initdata banner[] = KERN_INFO "DA9052 BAT, (c) \ +2009 Dialog semiconductor Ltd.\n"; + +static struct da9052_bat_hysteresis bat_hysteresis; +static struct da9052_bat_event_registration event_status; + + +static u16 array_hys_batvoltage[2]; +static u16 bat_volt_arr[3]; +static u8 hys_flag = FALSE; + +static int da9052_read(struct da9052 *da9052, u8 reg_address, u8 *reg_data) +{ + struct da9052_ssc_msg msg; + int ret; + + msg.addr = reg_address; + msg.data = 0; + + da9052_lock(da9052); + ret = da9052->read(da9052, &msg); + if (ret) + goto ssc_comm_err; + da9052_unlock(da9052); + + *reg_data = msg.data; + return 0; +ssc_comm_err: + da9052_unlock(da9052); + return ret; +} + +static s32 da9052_adc_read_ich(struct da9052 *da9052, u16 *data) +{ + struct da9052_ssc_msg msg; + da9052_lock(da9052); + /* Read charging conversion register */ + msg.addr = DA9052_ICHGAV_REG; + msg.data = 0; + if (da9052->read(da9052, &msg)) { + da9052_unlock(da9052); + return DA9052_SSC_FAIL; + } + da9052_unlock(da9052); + + *data = (u16)msg.data; + DA9052_DEBUG( + "In function: %s, ICHGAV_REG value read (1)= 0x%X \n", + __func__, msg.data); + return SUCCESS; +} + + +static s32 da9052_adc_read_tbat(struct da9052 *da9052, u16 *data) +{ + s32 ret; + u8 reg_data; + + ret = da9052_read(da9052, DA9052_TBATRES_REG, ®_data); + if (ret) + return ret; + *data = (u16)reg_data; + + DA9052_DEBUG("In function: %s, TBATRES_REG value read (1)= 0x%X \n", + __func__, msg.data); + return SUCCESS; +} + +s32 da9052_adc_read_vbat(struct da9052 *da9052, u16 *data) +{ + s32 ret; + + ret = da9052_manual_read(da9052, DA9052_ADC_VBAT); + DA9052_DEBUG("In function: %s, VBAT value read (1)= 0x%X \n", + __func__, temp); + if (ret == -EIO) { + *data = 0; + return ret; + } else { + *data = ret; + return 0; + } + return 0; +} + + +static u16 filter_sample(u16 *buffer) +{ + u8 count; + u16 tempvalue = 0; + u16 ret; + + if (buffer == NULL) + return -EINVAL; + + for (count = 0; count < DA9052_FILTER_SIZE; count++) + tempvalue = tempvalue + *(buffer + count); + + ret = tempvalue/DA9052_FILTER_SIZE; + return ret; +} + +static s32 da9052_bat_get_battery_temperature(struct da9052_charger_device + *chg_device, u16 *buffer) +{ + + u8 count; + u16 filterqueue[DA9052_FILTER_SIZE]; + + /* Measure the battery temperature using ADC function. + Number of read equal to average filter size*/ + + for (count = 0; count < DA9052_FILTER_SIZE; count++) + if (da9052_adc_read_tbat(chg_device->da9052, &filterqueue[count])) + return -EIO; + + /* Apply Average filter */ + filterqueue[0] = filter_sample(filterqueue); + + chg_device->bat_temp = filterqueue[0]; + *buffer = chg_device->bat_temp; + + return SUCCESS; +} + +static s32 da9052_bat_get_chg_current(struct da9052_charger_device + *chg_device, u16 *buffer) +{ + if (chg_device->status == POWER_SUPPLY_STATUS_DISCHARGING) + return -EIO; + + /* Measure the Charger current using ADC function */ + if (da9052_adc_read_ich(chg_device->da9052, buffer)) + return -EIO; + + /* Convert the raw value in terms of mA */ + chg_device->chg_current = ichg_reg_to_mA(*buffer); + *buffer = chg_device->chg_current; + + return 0; +} + + +s32 da9052_bat_get_battery_voltage(struct da9052_charger_device *chg_device, + u16 *buffer) +{ + u8 count; + u16 filterqueue[DA9052_FILTER_SIZE]; + + /* Measure the battery voltage using ADC function. + Number of read equal to average filter size*/ + for (count = 0; count < DA9052_FILTER_SIZE; count++) + if (da9052_adc_read_vbat(chg_device->da9052, &filterqueue[count])) + return -EIO; + + /* Apply average filter */ + filterqueue[0] = filter_sample(filterqueue); + + /* Convert battery voltage raw value in terms of mV */ + chg_device->bat_voltage = volt_reg_to_mV(filterqueue[0]); + *buffer = chg_device->bat_voltage; + return 0; +} + +static void da9052_bat_status_update(struct da9052_charger_device + *chg_device) +{ + struct da9052_ssc_msg msg; + u16 current_value = 0; + u16 buffer =0; + u8 regvalue = 0; + u8 old_status = chg_device->status; + + DA9052_DEBUG("FUNCTION = %s \n", __func__); + + /* Read Status A register */ + msg.addr = DA9052_STATUSA_REG; + msg.data = 0; + da9052_lock(chg_device->da9052); + + if (chg_device->da9052->read(chg_device->da9052, &msg)) { + DA9052_DEBUG("%s : failed\n", __func__); + da9052_unlock(chg_device->da9052); + return; + } + regvalue = msg.data; + + /* Read Status B register */ + msg.addr = DA9052_STATUSB_REG; + msg.data = 0; + if (chg_device->da9052->read(chg_device->da9052, &msg)) { + DA9052_DEBUG("%s : failed\n", __func__); + da9052_unlock(chg_device->da9052); + return; + } + da9052_unlock(chg_device->da9052); + + /* If DCINDET and DCINSEL are set then connected charger is + WALL Charger unit */ + if( (regvalue & DA9052_STATUSA_DCINSEL) + && (regvalue & DA9052_STATUSA_DCINDET) ) { + + chg_device->charger_type = DA9052_WALL_CHARGER; + } + /* If VBUS_DET and VBUSEL are set then connected charger is + USB Type */ + else if((regvalue & DA9052_STATUSA_VBUSSEL) + && (regvalue & DA9052_STATUSA_VBUSDET)) { + if (regvalue & DA9052_STATUSA_VDATDET) { + chg_device->charger_type = DA9052_USB_CHARGER; + } + else { + /* Else it has to be USB Host charger */ + chg_device->charger_type = DA9052_USB_HUB; + } + } + /* Battery is discharging since charging device is not present */ + else + { + chg_device->charger_type = DA9052_NOCHARGER; + /* Eqv to DISCHARGING_WITHOUT_CHARGER state */ + chg_device->status = POWER_SUPPLY_STATUS_DISCHARGING; + } + + + if( chg_device->charger_type != DA9052_NOCHARGER ) { + /* if Charging end flag is set and Charging current is greater + than charging end limit then battery is charging */ + if ((msg.data & DA9052_STATUSB_CHGEND) != 0) { + + if(da9052_bat_get_chg_current(chg_device,¤t_value)) { + return; + } + + if( current_value >= chg_device->chg_end_current ) { + chg_device->status = POWER_SUPPLY_STATUS_CHARGING; + } + else { + /* Eqv to DISCHARGING_WITH_CHARGER state*/ + chg_device->status = POWER_SUPPLY_STATUS_NOT_CHARGING; + } + } + /* if Charging end flag is cleared then battery is charging */ + else { + chg_device->status = POWER_SUPPLY_STATUS_CHARGING; + } + + if( POWER_SUPPLY_STATUS_CHARGING == chg_device->status){ + if(msg.data != DA9052_STATUSB_CHGPRE) { + /* Measure battery voltage. if battery voltage is greater than + (VCHG_BAT - VCHG_DROP) then battery is in the termintation mode. + */ + if(da9052_bat_get_battery_voltage(chg_device,&buffer)) { + DA9052_DEBUG("%s : failed\n",__FUNCTION__); + return ; + } + if(buffer > (chg_device->bat_target_voltage - + chg_device->charger_voltage_drop) && + ( chg_device->cal_capacity >= 99 ) ){ + chg_device->status = POWER_SUPPLY_STATUS_FULL; + } + + } + } + } + + if(chg_device->illegal) + chg_device->health = POWER_SUPPLY_HEALTH_UNKNOWN; + else if (chg_device->cal_capacity < chg_device->bat_capacity_limit_low) + chg_device->health = POWER_SUPPLY_HEALTH_DEAD; + else + chg_device->health = POWER_SUPPLY_HEALTH_GOOD; + + if ( chg_device->status != old_status) + power_supply_changed(&chg_device->psy); + + return; +} + +static s32 da9052_bat_suspend_charging(struct da9052_charger_device *chg_device) +{ + struct da9052_ssc_msg msg; + + if ((chg_device->status == POWER_SUPPLY_STATUS_DISCHARGING) || + (chg_device->status == POWER_SUPPLY_STATUS_NOT_CHARGING)) + return 0; + + msg.addr = DA9052_INPUTCONT_REG; + msg.data = 0; + da9052_lock(chg_device->da9052); + /* Read Input condition register */ + if (chg_device->da9052->read(chg_device->da9052, &msg)) { + da9052_unlock(chg_device->da9052); + return DA9052_SSC_FAIL; + } + + /* set both Wall charger and USB charger suspend bit */ + msg.data = set_bits(msg.data, DA9052_INPUTCONT_DCINSUSP); + msg.data = set_bits(msg.data, DA9052_INPUTCONT_VBUSSUSP); + + /* Write to Input control register */ + if (chg_device->da9052->write(chg_device->da9052, &msg)) { + da9052_unlock(chg_device->da9052); + DA9052_DEBUG("%s : failed\n", __func__); + return DA9052_SSC_FAIL; + } + da9052_unlock(chg_device->da9052); + + DA9052_DEBUG("%s : Sucess\n", __func__); + return 0; +} + +u32 interpolated(u32 vbat_lower, u32 vbat_upper, u32 level_lower, + u32 level_upper, u32 bat_voltage) +{ + s32 temp; + /*apply formula y= yk + (x - xk) * (yk+1 -yk)/(xk+1 -xk) */ + temp = ((level_upper - level_lower) * 1000)/(vbat_upper - vbat_lower); + temp = level_lower + (((bat_voltage - vbat_lower) * temp)/1000); + + return temp; +} + +s32 capture_first_correct_vbat_sample(struct da9052_charger_device *chg_device, +u16 *battery_voltage) +{ + static u8 count; + s32 ret = 0; + u32 temp_data = 0; + + ret = da9052_bat_get_battery_voltage(chg_device, + &bat_volt_arr[count]); + if (ret) + return ret; + count++; + + if (count < chg_device->vbat_first_valid_detect_iteration) + return FAILURE; + for (count = 0; count < + (chg_device->vbat_first_valid_detect_iteration - 1); + count++) { + temp_data = (bat_volt_arr[count] * + (chg_device->hysteresis_window_size))/100; + bat_hysteresis.upper_limit = bat_volt_arr[count] + temp_data; + bat_hysteresis.lower_limit = bat_volt_arr[count] - temp_data; + + if ((bat_volt_arr[count + 1] < bat_hysteresis.upper_limit) && + (bat_volt_arr[count + 1] > + bat_hysteresis.lower_limit)) { + *battery_voltage = (bat_volt_arr[count] + + bat_volt_arr[count+1]) / 2; + hys_flag = TRUE; + return 0; + } + } + + for (count = 0; count < + (chg_device->vbat_first_valid_detect_iteration - 1); + count++) + bat_volt_arr[count] = bat_volt_arr[count + 1]; + + return FAILURE; +} + + +s32 check_hystersis(struct da9052_charger_device *chg_device, u16 *bat_voltage) +{ + u8 ret = 0; + u32 offset = 0; + + /* Measure battery voltage using BAT internal function*/ + if (hys_flag == FALSE) { + ret = capture_first_correct_vbat_sample + (chg_device, &array_hys_batvoltage[0]); + if (ret) + return ret; + } + + ret = da9052_bat_get_battery_voltage + (chg_device, &array_hys_batvoltage[1]); + if (ret) + return ret; + *bat_voltage = array_hys_batvoltage[1]; + +#if DA9052_BAT_FILTER_HYS + printk(KERN_CRIT "\nBAT_LOG: Previous Battery Voltage = %d mV\n", + array_hys_batvoltage[0]); + printk(KERN_CRIT "\nBAT_LOG:Battery Voltage Before Filter = %d mV\n", + array_hys_batvoltage[1]); +#endif + /* Check if measured battery voltage value is within the hysteresis + window limit using measured battey votlage value */ + if ((bat_hysteresis.upper_limit < *bat_voltage) || + (bat_hysteresis.lower_limit > *bat_voltage)) { + + bat_hysteresis.index++; + if (bat_hysteresis.index == + chg_device->hysteresis_no_of_reading) { + /* Hysteresis Window is set to +- of + HYSTERESIS_WINDOW_SIZE percentage of current VBAT */ + bat_hysteresis.index = 0; + offset = ((*bat_voltage) * + chg_device->hysteresis_window_size)/ + 100; + bat_hysteresis.upper_limit = (*bat_voltage) + offset; + bat_hysteresis.lower_limit = (*bat_voltage) - offset; + } else { +#if DA9052_BAT_FILTER_HYS + printk(KERN_CRIT "CheckHystersis: Failed\n"); +#endif + return -EIO; + } + } else { + bat_hysteresis.index = 0; + offset = ((*bat_voltage) * + chg_device->hysteresis_window_size)/100; + bat_hysteresis.upper_limit = (*bat_voltage) + offset; + bat_hysteresis.lower_limit = (*bat_voltage) - offset; + } + + /* Digital C Filter, formula Yn = k Yn-1 + (1-k) Xn */ + *bat_voltage = ((chg_device->chg_hysteresis_const * + array_hys_batvoltage[0])/100) + + (((100 - chg_device->chg_hysteresis_const) * + array_hys_batvoltage[1])/100); + + if ((chg_device->status == POWER_SUPPLY_STATUS_DISCHARGING) && + (*bat_voltage > array_hys_batvoltage[0])) { + *bat_voltage = array_hys_batvoltage[0]; + } + + array_hys_batvoltage[0] = *bat_voltage; + +#if DA9052_BAT_FILTER_HYS + printk(KERN_CRIT "\nBAT_LOG:Battery Voltage After Filter = %d mV\n",\ + *bat_voltage); + +#endif + return 0; +} + +u8 select_temperature(u8 temp_index, u16 bat_temperature) +{ + u16 temp_temperature = 0; + temp_temperature = (temperature_lookup_ref[temp_index] + + temperature_lookup_ref[temp_index+1]) / 2; + + if (bat_temperature >= temp_temperature) { + temp_index += 1; + return temp_index; + } else + return temp_index; +} + +s32 da9052_bat_level_update(struct da9052_charger_device *chg_device) +{ + u16 bat_temperature; + u16 bat_voltage; + u32 vbat_lower, vbat_upper, level_upper, level_lower, level; + u8 access_index = 0; + u8 index = 0, ret; + u8 flag = FALSE; + + ret = 0; + vbat_lower = 0; + vbat_upper = 0; + level_upper = 0; + level_lower = 0; + + ret = check_hystersis(chg_device, &bat_voltage); + if (ret) + return ret; + + ret = da9052_bat_get_battery_temperature(chg_device, + &bat_temperature); + if (ret) + return ret; + + for (index = 0; index < (DA9052_NO_OF_LOOKUP_TABLE-1); index++) { + if (bat_temperature <= temperature_lookup_ref[0]) { + access_index = 0; + break; + } else if (bat_temperature > + temperature_lookup_ref[DA9052_NO_OF_LOOKUP_TABLE]){ + access_index = DA9052_NO_OF_LOOKUP_TABLE - 1; + break; + } else if ((bat_temperature >= temperature_lookup_ref[index]) && + (bat_temperature >= temperature_lookup_ref[index+1])) { + access_index = select_temperature(index, + bat_temperature); + break; + } + } + if (bat_voltage >= vbat_vs_capacity_look_up[access_index][0][0]) { + chg_device->cal_capacity = 100; + return 0; + } + if (bat_voltage <= vbat_vs_capacity_look_up[access_index] + [DA9052_LOOK_UP_TABLE_SIZE-1][0]){ + chg_device->cal_capacity = 0; + return 0; + } + flag = FALSE; + + for (index = 0; index < (DA9052_LOOK_UP_TABLE_SIZE-1); index++) { + if ((bat_voltage <= + vbat_vs_capacity_look_up[access_index][index][0]) && + (bat_voltage >= + vbat_vs_capacity_look_up[access_index][index+1][0])) { + vbat_upper = + vbat_vs_capacity_look_up[access_index][index][0]; + vbat_lower = + vbat_vs_capacity_look_up[access_index][index+1][0]; + level_upper = + vbat_vs_capacity_look_up[access_index][index][1]; + level_lower = + vbat_vs_capacity_look_up[access_index][index+1][1]; + flag = TRUE; + break; + } + } + if (!flag) + return -EIO; + + level = interpolated(vbat_lower, vbat_upper, level_lower, + level_upper, bat_voltage); + chg_device->cal_capacity = level; + DA9052_DEBUG(" TOTAl_BAT_CAPACITY : %d\n", chg_device->cal_capacity); + return 0; +} + +void da9052_bat_tbat_handler(struct da9052_eh_nb *eh_data, unsigned int event) +{ + struct da9052_charger_device *chg_device = + container_of(eh_data, struct da9052_charger_device, tbat_eh_data); + + chg_device->health = POWER_SUPPLY_HEALTH_OVERHEAT; + +} + +static s32 da9052_bat_register_event(struct da9052_charger_device *chg_device) +{ + s32 ret; + + if (event_status.da9052_event_tbat == FALSE) { + chg_device->tbat_eh_data.eve_type = TBAT_EVE; + chg_device->tbat_eh_data.call_back =da9052_bat_tbat_handler; + DA9052_DEBUG("events = %d\n",TBAT_EVE); + ret = chg_device->da9052->register_event_notifier + (chg_device->da9052, &chg_device->tbat_eh_data); + if (ret) + return -EIO; + event_status.da9052_event_tbat = TRUE; + } + + return 0; +} + +static s32 da9052_bat_unregister_event(struct da9052_charger_device *chg_device) +{ + s32 ret; + + if (event_status.da9052_event_tbat) { + ret = + chg_device->da9052->unregister_event_notifier + (chg_device->da9052, &chg_device->tbat_eh_data); + if (ret) + return -EIO; + event_status.da9052_event_tbat = FALSE; + } + + return 0; +} + +static int da9052_bat_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct da9052_charger_device *chg_device = + container_of(psy, struct da9052_charger_device, psy); + + /* Validate battery presence */ + if( chg_device->illegal && psp != POWER_SUPPLY_PROP_PRESENT ) { + return -ENODEV; + } + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = chg_device->status; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = (chg_device->charger_type == DA9052_NOCHARGER) ? 0: 1; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = chg_device->illegal; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = chg_device->health; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = chg_device->bat_target_voltage * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = chg_device->bat_volt_cutoff * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + val->intval = chg_device->bat_voltage * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + val->intval = chg_device->chg_current * 1000; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = chg_device->cal_capacity; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = bat_temp_reg_to_C(chg_device->bat_temp); + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = chg_device->technology; + break; + default: + return -EINVAL; + break; + } + return 0; +} + + +static u8 detect_illegal_battery(struct da9052_charger_device *chg_device) +{ + u16 buffer = 0; + s32 ret = 0; + + /* Measure battery temeperature */ + ret = da9052_bat_get_battery_temperature(chg_device, &buffer); + if (ret) { + DA9052_DEBUG("%s: Battery temperature measurement failed \n", + __func__); + return ret; + } + + if (buffer > chg_device->bat_with_no_resistor) + chg_device->illegal = TRUE; + else + chg_device->illegal = FALSE; + + + /* suspend charging of battery if illegal battey is detected */ + if (chg_device->illegal) + da9052_bat_suspend_charging(chg_device); + + return chg_device->illegal; +} + +void da9052_update_bat_properties(struct da9052_charger_device *chg_device) +{ + /* Get Bat status and type */ + da9052_bat_status_update(chg_device); + da9052_bat_level_update(chg_device); +} + +static void da9052_bat_external_power_changed(struct power_supply *psy) +{ + struct da9052_charger_device *chg_device = + container_of(psy, struct da9052_charger_device, psy); + + cancel_delayed_work(&chg_device->monitor_work); + queue_delayed_work(chg_device->monitor_wqueue, &chg_device->monitor_work, HZ/10); +} + + +static void da9052_bat_work(struct work_struct *work) +{ + struct da9052_charger_device *chg_device = container_of(work, + struct da9052_charger_device,monitor_work.work); + + da9052_update_bat_properties(chg_device); + queue_delayed_work(chg_device->monitor_wqueue, &chg_device->monitor_work, HZ * 8); +} + +static enum power_supply_property da9052_bat_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TECHNOLOGY, + +}; + +static s32 __devinit da9052_bat_probe(struct platform_device *pdev) +{ + struct da9052_charger_device *chg_device; + u8 reg_data; + int ret; + + chg_device = kzalloc(sizeof(*chg_device), GFP_KERNEL); + if (!chg_device) + return -ENOMEM; + + chg_device->da9052 = dev_get_drvdata(pdev->dev.parent); + platform_set_drvdata(pdev, chg_device); + + chg_device->psy.name = DA9052_BAT_DEVICE_NAME; + chg_device->psy.type = POWER_SUPPLY_TYPE_BATTERY; + chg_device->psy.properties = da9052_bat_props; + chg_device->psy.num_properties = ARRAY_SIZE(da9052_bat_props); + chg_device->psy.get_property = da9052_bat_get_property; + chg_device->psy.external_power_changed = da9052_bat_external_power_changed; + chg_device->psy.use_for_apm = 1; + chg_device->charger_type = DA9052_NOCHARGER; + chg_device->status = POWER_SUPPLY_STATUS_UNKNOWN; + chg_device->health = POWER_SUPPLY_HEALTH_UNKNOWN; + chg_device->technology = POWER_SUPPLY_TECHNOLOGY_LION; + chg_device->bat_with_no_resistor = 62; + chg_device->bat_capacity_limit_low = 4; + chg_device->bat_capacity_limit_high = 70; + chg_device->bat_capacity_full = 100; + chg_device->bat_volt_cutoff = 2800; + chg_device->vbat_first_valid_detect_iteration = 3; + chg_device->hysteresis_window_size =1; + chg_device->chg_hysteresis_const =89; + chg_device->hysteresis_reading_interval =1000; + chg_device->hysteresis_no_of_reading =10; + + ret = da9052_read(chg_device->da9052, DA9052_CHGCONT_REG, ®_data); + if (ret) + goto err_charger_init; + chg_device->charger_voltage_drop = bat_drop_reg_to_mV(reg_data && + DA9052_CHGCONT_TCTR); + chg_device->bat_target_voltage = + bat_reg_to_mV(reg_data && DA9052_CHGCONT_VCHGBAT); + + ret = da9052_read(chg_device->da9052, DA9052_ICHGEND_REG, ®_data); + if (ret) + goto err_charger_init; + chg_device->chg_end_current = ichg_reg_to_mA(reg_data); + + bat_hysteresis.upper_limit = 0; + bat_hysteresis.lower_limit = 0; + bat_hysteresis.hys_flag = 0; + + chg_device->illegal = FALSE; + detect_illegal_battery(chg_device); + + da9052_bat_register_event(chg_device); + if (ret) + goto err_charger_init; + + ret = power_supply_register(&pdev->dev, &chg_device->psy); + if (ret) + goto err_charger_init; + + INIT_DELAYED_WORK(&chg_device->monitor_work, da9052_bat_work); + chg_device->monitor_wqueue = create_singlethread_workqueue(pdev->dev.bus_id); + if (!chg_device->monitor_wqueue) { + goto err_charger_init; + } + queue_delayed_work(chg_device->monitor_wqueue, &chg_device->monitor_work, HZ * 1); + + return 0; + +err_charger_init: + platform_set_drvdata(pdev, NULL); + kfree(chg_device); + return ret; +} +static int __devexit da9052_bat_remove(struct platform_device *dev) +{ + struct da9052_charger_device *chg_device = platform_get_drvdata(dev); + + /* unregister the events.*/ + da9052_bat_unregister_event(chg_device); + + cancel_rearming_delayed_workqueue(chg_device->monitor_wqueue, + &chg_device->monitor_work); + destroy_workqueue(chg_device->monitor_wqueue); + + power_supply_unregister(&chg_device->psy); + + return 0; +} + +static struct platform_driver da9052_bat_driver = { + .probe = da9052_bat_probe, + .remove = __devexit_p(da9052_bat_remove), + .driver.name = DA9052_BAT_DEVICE_NAME, + .driver.owner = THIS_MODULE, +}; + +static int __init da9052_bat_init(void) +{ + printk(banner); + return platform_driver_register(&da9052_bat_driver); +} + +static void __exit da9052_bat_exit(void) +{ + // To remove printk("DA9052: Unregistering BAT device.\n"); + platform_driver_unregister(&da9052_bat_driver); +} + +module_init(da9052_bat_init); +module_exit(da9052_bat_exit); + +MODULE_AUTHOR("Dialog Semiconductor Ltd"); +MODULE_DESCRIPTION("DA9052 BAT Device Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/ds2760_battery.c b/drivers/power/ds2760_battery.c new file mode 100644 index 00000000..f2c9cc33 --- /dev/null +++ b/drivers/power/ds2760_battery.c @@ -0,0 +1,657 @@ +/* + * Driver for batteries with DS2760 chips inside. + * + * Copyright © 2007 Anton Vorontsov + * 2004-2007 Matt Reimer + * 2004 Szabolcs Gyurko + * + * Use consistent with the GNU GPL is permitted, + * provided that this copyright notice is + * preserved in its entirety in all copies and derived works. + * + * Author: Anton Vorontsov + * February 2007 + * + * Matt Reimer + * April 2004, 2005, 2007 + * + * Szabolcs Gyurko + * September 2004 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../w1/w1.h" +#include "../w1/slaves/w1_ds2760.h" + +struct ds2760_device_info { + struct device *dev; + + /* DS2760 data, valid after calling ds2760_battery_read_status() */ + unsigned long update_time; /* jiffies when data read */ + char raw[DS2760_DATA_SIZE]; /* raw DS2760 data */ + int voltage_raw; /* units of 4.88 mV */ + int voltage_uV; /* units of µV */ + int current_raw; /* units of 0.625 mA */ + int current_uA; /* units of µA */ + int accum_current_raw; /* units of 0.25 mAh */ + int accum_current_uAh; /* units of µAh */ + int temp_raw; /* units of 0.125 °C */ + int temp_C; /* units of 0.1 °C */ + int rated_capacity; /* units of µAh */ + int rem_capacity; /* percentage */ + int full_active_uAh; /* units of µAh */ + int empty_uAh; /* units of µAh */ + int life_sec; /* units of seconds */ + int charge_status; /* POWER_SUPPLY_STATUS_* */ + + int full_counter; + struct power_supply bat; + struct device *w1_dev; + struct workqueue_struct *monitor_wqueue; + struct delayed_work monitor_work; + struct delayed_work set_charged_work; +}; + +static unsigned int cache_time = 1000; +module_param(cache_time, uint, 0644); +MODULE_PARM_DESC(cache_time, "cache time in milliseconds"); + +static unsigned int pmod_enabled; +module_param(pmod_enabled, bool, 0644); +MODULE_PARM_DESC(pmod_enabled, "PMOD enable bit"); + +static unsigned int rated_capacity; +module_param(rated_capacity, uint, 0644); +MODULE_PARM_DESC(rated_capacity, "rated battery capacity, 10*mAh or index"); + +static unsigned int current_accum; +module_param(current_accum, uint, 0644); +MODULE_PARM_DESC(current_accum, "current accumulator value"); + +/* Some batteries have their rated capacity stored a N * 10 mAh, while + * others use an index into this table. */ +static int rated_capacities[] = { + 0, + 920, /* Samsung */ + 920, /* BYD */ + 920, /* Lishen */ + 920, /* NEC */ + 1440, /* Samsung */ + 1440, /* BYD */ +#ifdef CONFIG_MACH_H4700 + 1800, /* HP iPAQ hx4700 3.7V 1800mAh (359113-001) */ +#else + 1440, /* Lishen */ +#endif + 1440, /* NEC */ + 2880, /* Samsung */ + 2880, /* BYD */ + 2880, /* Lishen */ + 2880 /* NEC */ +}; + +/* array is level at temps 0°C, 10°C, 20°C, 30°C, 40°C + * temp is in Celsius */ +static int battery_interpolate(int array[], int temp) +{ + int index, dt; + + if (temp <= 0) + return array[0]; + if (temp >= 40) + return array[4]; + + index = temp / 10; + dt = temp % 10; + + return array[index] + (((array[index + 1] - array[index]) * dt) / 10); +} + +static int ds2760_battery_read_status(struct ds2760_device_info *di) +{ + int ret, i, start, count, scale[5]; + + if (di->update_time && time_before(jiffies, di->update_time + + msecs_to_jiffies(cache_time))) + return 0; + + /* The first time we read the entire contents of SRAM/EEPROM, + * but after that we just read the interesting bits that change. */ + if (di->update_time == 0) { + start = 0; + count = DS2760_DATA_SIZE; + } else { + start = DS2760_VOLTAGE_MSB; + count = DS2760_TEMP_LSB - start + 1; + } + + ret = w1_ds2760_read(di->w1_dev, di->raw + start, start, count); + if (ret != count) { + dev_warn(di->dev, "call to w1_ds2760_read failed (0x%p)\n", + di->w1_dev); + return 1; + } + + di->update_time = jiffies; + + /* DS2760 reports voltage in units of 4.88mV, but the battery class + * reports in units of uV, so convert by multiplying by 4880. */ + di->voltage_raw = (di->raw[DS2760_VOLTAGE_MSB] << 3) | + (di->raw[DS2760_VOLTAGE_LSB] >> 5); + di->voltage_uV = di->voltage_raw * 4880; + + /* DS2760 reports current in signed units of 0.625mA, but the battery + * class reports in units of µA, so convert by multiplying by 625. */ + di->current_raw = + (((signed char)di->raw[DS2760_CURRENT_MSB]) << 5) | + (di->raw[DS2760_CURRENT_LSB] >> 3); + di->current_uA = di->current_raw * 625; + + /* DS2760 reports accumulated current in signed units of 0.25mAh. */ + di->accum_current_raw = + (((signed char)di->raw[DS2760_CURRENT_ACCUM_MSB]) << 8) | + di->raw[DS2760_CURRENT_ACCUM_LSB]; + di->accum_current_uAh = di->accum_current_raw * 250; + + /* DS2760 reports temperature in signed units of 0.125°C, but the + * battery class reports in units of 1/10 °C, so we convert by + * multiplying by .125 * 10 = 1.25. */ + di->temp_raw = (((signed char)di->raw[DS2760_TEMP_MSB]) << 3) | + (di->raw[DS2760_TEMP_LSB] >> 5); + di->temp_C = di->temp_raw + (di->temp_raw / 4); + + /* At least some battery monitors (e.g. HP iPAQ) store the battery's + * maximum rated capacity. */ + if (di->raw[DS2760_RATED_CAPACITY] < ARRAY_SIZE(rated_capacities)) + di->rated_capacity = rated_capacities[ + (unsigned int)di->raw[DS2760_RATED_CAPACITY]]; + else + di->rated_capacity = di->raw[DS2760_RATED_CAPACITY] * 10; + + di->rated_capacity *= 1000; /* convert to µAh */ + + /* Calculate the full level at the present temperature. */ + di->full_active_uAh = di->raw[DS2760_ACTIVE_FULL] << 8 | + di->raw[DS2760_ACTIVE_FULL + 1]; + + /* If the full_active_uAh value is not given, fall back to the rated + * capacity. This is likely to happen when chips are not part of the + * battery pack and is therefore not bootstrapped. */ + if (di->full_active_uAh == 0) + di->full_active_uAh = di->rated_capacity / 1000L; + + scale[0] = di->full_active_uAh; + for (i = 1; i < 5; i++) + scale[i] = scale[i - 1] + di->raw[DS2760_ACTIVE_FULL + 1 + i]; + + di->full_active_uAh = battery_interpolate(scale, di->temp_C / 10); + di->full_active_uAh *= 1000; /* convert to µAh */ + + /* Calculate the empty level at the present temperature. */ + scale[4] = di->raw[DS2760_ACTIVE_EMPTY + 4]; + for (i = 3; i >= 0; i--) + scale[i] = scale[i + 1] + di->raw[DS2760_ACTIVE_EMPTY + i]; + + di->empty_uAh = battery_interpolate(scale, di->temp_C / 10); + di->empty_uAh *= 1000; /* convert to µAh */ + + if (di->full_active_uAh == di->empty_uAh) + di->rem_capacity = 0; + else + /* From Maxim Application Note 131: remaining capacity = + * ((ICA - Empty Value) / (Full Value - Empty Value)) x 100% */ + di->rem_capacity = ((di->accum_current_uAh - di->empty_uAh) * 100L) / + (di->full_active_uAh - di->empty_uAh); + + if (di->rem_capacity < 0) + di->rem_capacity = 0; + if (di->rem_capacity > 100) + di->rem_capacity = 100; + + if (di->current_uA < -100L) + di->life_sec = -((di->accum_current_uAh - di->empty_uAh) * 36L) + / (di->current_uA / 100L); + else + di->life_sec = 0; + + return 0; +} + +static void ds2760_battery_set_current_accum(struct ds2760_device_info *di, + unsigned int acr_val) +{ + unsigned char acr[2]; + + /* acr is in units of 0.25 mAh */ + acr_val *= 4L; + acr_val /= 1000; + + acr[0] = acr_val >> 8; + acr[1] = acr_val & 0xff; + + if (w1_ds2760_write(di->w1_dev, acr, DS2760_CURRENT_ACCUM_MSB, 2) < 2) + dev_warn(di->dev, "ACR write failed\n"); +} + +static void ds2760_battery_update_status(struct ds2760_device_info *di) +{ + int old_charge_status = di->charge_status; + + ds2760_battery_read_status(di); + + if (di->charge_status == POWER_SUPPLY_STATUS_UNKNOWN) + di->full_counter = 0; + + if (power_supply_am_i_supplied(&di->bat)) { + if (di->current_uA > 10000) { + di->charge_status = POWER_SUPPLY_STATUS_CHARGING; + di->full_counter = 0; + } else if (di->current_uA < -5000) { + if (di->charge_status != POWER_SUPPLY_STATUS_NOT_CHARGING) + dev_notice(di->dev, "not enough power to " + "charge\n"); + di->charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + di->full_counter = 0; + } else if (di->current_uA < 10000 && + di->charge_status != POWER_SUPPLY_STATUS_FULL) { + + /* Don't consider the battery to be full unless + * we've seen the current < 10 mA at least two + * consecutive times. */ + + di->full_counter++; + + if (di->full_counter < 2) { + di->charge_status = POWER_SUPPLY_STATUS_CHARGING; + } else { + di->charge_status = POWER_SUPPLY_STATUS_FULL; + ds2760_battery_set_current_accum(di, + di->full_active_uAh); + } + } + } else { + di->charge_status = POWER_SUPPLY_STATUS_DISCHARGING; + di->full_counter = 0; + } + + if (di->charge_status != old_charge_status) + power_supply_changed(&di->bat); +} + +static void ds2760_battery_write_status(struct ds2760_device_info *di, + char status) +{ + if (status == di->raw[DS2760_STATUS_REG]) + return; + + w1_ds2760_write(di->w1_dev, &status, DS2760_STATUS_WRITE_REG, 1); + w1_ds2760_store_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1); + w1_ds2760_recall_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1); +} + +static void ds2760_battery_write_rated_capacity(struct ds2760_device_info *di, + unsigned char rated_capacity) +{ + if (rated_capacity == di->raw[DS2760_RATED_CAPACITY]) + return; + + w1_ds2760_write(di->w1_dev, &rated_capacity, DS2760_RATED_CAPACITY, 1); + w1_ds2760_store_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1); + w1_ds2760_recall_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1); +} + +static void ds2760_battery_write_active_full(struct ds2760_device_info *di, + int active_full) +{ + unsigned char tmp[2] = { + active_full >> 8, + active_full & 0xff + }; + + if (tmp[0] == di->raw[DS2760_ACTIVE_FULL] && + tmp[1] == di->raw[DS2760_ACTIVE_FULL + 1]) + return; + + w1_ds2760_write(di->w1_dev, tmp, DS2760_ACTIVE_FULL, sizeof(tmp)); + w1_ds2760_store_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK0); + w1_ds2760_recall_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK0); + + /* Write to the di->raw[] buffer directly - the DS2760_ACTIVE_FULL + * values won't be read back by ds2760_battery_read_status() */ + di->raw[DS2760_ACTIVE_FULL] = tmp[0]; + di->raw[DS2760_ACTIVE_FULL + 1] = tmp[1]; +} + +static void ds2760_battery_work(struct work_struct *work) +{ + struct ds2760_device_info *di = container_of(work, + struct ds2760_device_info, monitor_work.work); + const int interval = HZ * 60; + + dev_dbg(di->dev, "%s\n", __func__); + + ds2760_battery_update_status(di); + queue_delayed_work(di->monitor_wqueue, &di->monitor_work, interval); +} + +#define to_ds2760_device_info(x) container_of((x), struct ds2760_device_info, \ + bat); + +static void ds2760_battery_external_power_changed(struct power_supply *psy) +{ + struct ds2760_device_info *di = to_ds2760_device_info(psy); + + dev_dbg(di->dev, "%s\n", __func__); + + cancel_delayed_work(&di->monitor_work); + queue_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ/10); +} + + +static void ds2760_battery_set_charged_work(struct work_struct *work) +{ + char bias; + struct ds2760_device_info *di = container_of(work, + struct ds2760_device_info, set_charged_work.work); + + dev_dbg(di->dev, "%s\n", __func__); + + ds2760_battery_read_status(di); + + /* When we get notified by external circuitry that the battery is + * considered fully charged now, we know that there is no current + * flow any more. However, the ds2760's internal current meter is + * too inaccurate to rely on - spec say something ~15% failure. + * Hence, we use the current offset bias register to compensate + * that error. + */ + + if (!power_supply_am_i_supplied(&di->bat)) + return; + + bias = (signed char) di->current_raw + + (signed char) di->raw[DS2760_CURRENT_OFFSET_BIAS]; + + dev_dbg(di->dev, "%s: bias = %d\n", __func__, bias); + + w1_ds2760_write(di->w1_dev, &bias, DS2760_CURRENT_OFFSET_BIAS, 1); + w1_ds2760_store_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1); + w1_ds2760_recall_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1); + + /* Write to the di->raw[] buffer directly - the CURRENT_OFFSET_BIAS + * value won't be read back by ds2760_battery_read_status() */ + di->raw[DS2760_CURRENT_OFFSET_BIAS] = bias; +} + +static void ds2760_battery_set_charged(struct power_supply *psy) +{ + struct ds2760_device_info *di = to_ds2760_device_info(psy); + + /* postpone the actual work by 20 secs. This is for debouncing GPIO + * signals and to let the current value settle. See AN4188. */ + cancel_delayed_work(&di->set_charged_work); + queue_delayed_work(di->monitor_wqueue, &di->set_charged_work, HZ * 20); +} + +static int ds2760_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ds2760_device_info *di = to_ds2760_device_info(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = di->charge_status; + return 0; + default: + break; + } + + ds2760_battery_read_status(di); + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = di->voltage_uV; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = di->current_uA; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval = di->rated_capacity; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + val->intval = di->full_active_uAh; + break; + case POWER_SUPPLY_PROP_CHARGE_EMPTY: + val->intval = di->empty_uAh; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + val->intval = di->accum_current_uAh; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = di->temp_C; + break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: + val->intval = di->life_sec; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = di->rem_capacity; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ds2760_battery_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct ds2760_device_info *di = to_ds2760_device_info(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_FULL: + /* the interface counts in uAh, convert the value */ + ds2760_battery_write_active_full(di, val->intval / 1000L); + break; + + case POWER_SUPPLY_PROP_CHARGE_NOW: + /* ds2760_battery_set_current_accum() does the conversion */ + ds2760_battery_set_current_accum(di, val->intval); + break; + + default: + return -EPERM; + } + + return 0; +} + +static int ds2760_battery_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_FULL: + case POWER_SUPPLY_PROP_CHARGE_NOW: + return 1; + + default: + break; + } + + return 0; +} + +static enum power_supply_property ds2760_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_EMPTY, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, + POWER_SUPPLY_PROP_CAPACITY, +}; + +static int ds2760_battery_probe(struct platform_device *pdev) +{ + char status; + int retval = 0; + struct ds2760_device_info *di; + + di = kzalloc(sizeof(*di), GFP_KERNEL); + if (!di) { + retval = -ENOMEM; + goto di_alloc_failed; + } + + platform_set_drvdata(pdev, di); + + di->dev = &pdev->dev; + di->w1_dev = pdev->dev.parent; + di->bat.name = dev_name(&pdev->dev); + di->bat.type = POWER_SUPPLY_TYPE_BATTERY; + di->bat.properties = ds2760_battery_props; + di->bat.num_properties = ARRAY_SIZE(ds2760_battery_props); + di->bat.get_property = ds2760_battery_get_property; + di->bat.set_property = ds2760_battery_set_property; + di->bat.property_is_writeable = + ds2760_battery_property_is_writeable; + di->bat.set_charged = ds2760_battery_set_charged; + di->bat.external_power_changed = + ds2760_battery_external_power_changed; + + di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN; + + /* enable sleep mode feature */ + ds2760_battery_read_status(di); + status = di->raw[DS2760_STATUS_REG]; + if (pmod_enabled) + status |= DS2760_STATUS_PMOD; + else + status &= ~DS2760_STATUS_PMOD; + + ds2760_battery_write_status(di, status); + + /* set rated capacity from module param */ + if (rated_capacity) + ds2760_battery_write_rated_capacity(di, rated_capacity); + + /* set current accumulator if given as parameter. + * this should only be done for bootstrapping the value */ + if (current_accum) + ds2760_battery_set_current_accum(di, current_accum); + + retval = power_supply_register(&pdev->dev, &di->bat); + if (retval) { + dev_err(di->dev, "failed to register battery\n"); + goto batt_failed; + } + + INIT_DELAYED_WORK(&di->monitor_work, ds2760_battery_work); + INIT_DELAYED_WORK(&di->set_charged_work, + ds2760_battery_set_charged_work); + di->monitor_wqueue = create_singlethread_workqueue(dev_name(&pdev->dev)); + if (!di->monitor_wqueue) { + retval = -ESRCH; + goto workqueue_failed; + } + queue_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ * 1); + + goto success; + +workqueue_failed: + power_supply_unregister(&di->bat); +batt_failed: + kfree(di); +di_alloc_failed: +success: + return retval; +} + +static int ds2760_battery_remove(struct platform_device *pdev) +{ + struct ds2760_device_info *di = platform_get_drvdata(pdev); + + cancel_delayed_work_sync(&di->monitor_work); + cancel_delayed_work_sync(&di->set_charged_work); + destroy_workqueue(di->monitor_wqueue); + power_supply_unregister(&di->bat); + kfree(di); + + return 0; +} + +#ifdef CONFIG_PM + +static int ds2760_battery_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct ds2760_device_info *di = platform_get_drvdata(pdev); + + di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN; + + return 0; +} + +static int ds2760_battery_resume(struct platform_device *pdev) +{ + struct ds2760_device_info *di = platform_get_drvdata(pdev); + + di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN; + power_supply_changed(&di->bat); + + cancel_delayed_work(&di->monitor_work); + queue_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ); + + return 0; +} + +#else + +#define ds2760_battery_suspend NULL +#define ds2760_battery_resume NULL + +#endif /* CONFIG_PM */ + +MODULE_ALIAS("platform:ds2760-battery"); + +static struct platform_driver ds2760_battery_driver = { + .driver = { + .name = "ds2760-battery", + }, + .probe = ds2760_battery_probe, + .remove = ds2760_battery_remove, + .suspend = ds2760_battery_suspend, + .resume = ds2760_battery_resume, +}; + +static int __init ds2760_battery_init(void) +{ + return platform_driver_register(&ds2760_battery_driver); +} + +static void __exit ds2760_battery_exit(void) +{ + platform_driver_unregister(&ds2760_battery_driver); +} + +module_init(ds2760_battery_init); +module_exit(ds2760_battery_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Szabolcs Gyurko , " + "Matt Reimer , " + "Anton Vorontsov "); +MODULE_DESCRIPTION("ds2760 battery driver"); diff --git a/drivers/power/ds2780_battery.c b/drivers/power/ds2780_battery.c new file mode 100644 index 00000000..91a783d7 --- /dev/null +++ b/drivers/power/ds2780_battery.c @@ -0,0 +1,869 @@ +/* + * 1-wire client/driver for the Maxim/Dallas DS2780 Stand-Alone Fuel Gauge IC + * + * Copyright (C) 2010 Indesign, LLC + * + * Author: Clifton Barnes + * + * Based on ds2760_battery and ds2782_battery drivers + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "../w1/w1.h" +#include "../w1/slaves/w1_ds2780.h" + +/* Current unit measurement in uA for a 1 milli-ohm sense resistor */ +#define DS2780_CURRENT_UNITS 1563 +/* Charge unit measurement in uAh for a 1 milli-ohm sense resistor */ +#define DS2780_CHARGE_UNITS 6250 +/* Number of bytes in user EEPROM space */ +#define DS2780_USER_EEPROM_SIZE (DS2780_EEPROM_BLOCK0_END - \ + DS2780_EEPROM_BLOCK0_START + 1) +/* Number of bytes in parameter EEPROM space */ +#define DS2780_PARAM_EEPROM_SIZE (DS2780_EEPROM_BLOCK1_END - \ + DS2780_EEPROM_BLOCK1_START + 1) + +struct ds2780_device_info { + struct device *dev; + struct power_supply bat; + struct device *w1_dev; + struct task_struct *mutex_holder; +}; + +enum current_types { + CURRENT_NOW, + CURRENT_AVG, +}; + +static const char model[] = "DS2780"; +static const char manufacturer[] = "Maxim/Dallas"; + +static inline struct ds2780_device_info * +to_ds2780_device_info(struct power_supply *psy) +{ + return container_of(psy, struct ds2780_device_info, bat); +} + +static inline struct power_supply *to_power_supply(struct device *dev) +{ + return dev_get_drvdata(dev); +} + +static inline int ds2780_battery_io(struct ds2780_device_info *dev_info, + char *buf, int addr, size_t count, int io) +{ + if (dev_info->mutex_holder == current) + return w1_ds2780_io_nolock(dev_info->w1_dev, buf, addr, count, io); + else + return w1_ds2780_io(dev_info->w1_dev, buf, addr, count, io); +} + +static inline int ds2780_read8(struct ds2780_device_info *dev_info, u8 *val, + int addr) +{ + return ds2780_battery_io(dev_info, val, addr, sizeof(u8), 0); +} + +static int ds2780_read16(struct ds2780_device_info *dev_info, s16 *val, + int addr) +{ + int ret; + u8 raw[2]; + + ret = ds2780_battery_io(dev_info, raw, addr, sizeof(raw), 0); + if (ret < 0) + return ret; + + *val = (raw[0] << 8) | raw[1]; + + return 0; +} + +static inline int ds2780_read_block(struct ds2780_device_info *dev_info, + u8 *val, int addr, size_t count) +{ + return ds2780_battery_io(dev_info, val, addr, count, 0); +} + +static inline int ds2780_write(struct ds2780_device_info *dev_info, u8 *val, + int addr, size_t count) +{ + return ds2780_battery_io(dev_info, val, addr, count, 1); +} + +static inline int ds2780_store_eeprom(struct device *dev, int addr) +{ + return w1_ds2780_eeprom_cmd(dev, addr, W1_DS2780_COPY_DATA); +} + +static inline int ds2780_recall_eeprom(struct device *dev, int addr) +{ + return w1_ds2780_eeprom_cmd(dev, addr, W1_DS2780_RECALL_DATA); +} + +static int ds2780_save_eeprom(struct ds2780_device_info *dev_info, int reg) +{ + int ret; + + ret = ds2780_store_eeprom(dev_info->w1_dev, reg); + if (ret < 0) + return ret; + + ret = ds2780_recall_eeprom(dev_info->w1_dev, reg); + if (ret < 0) + return ret; + + return 0; +} + +/* Set sense resistor value in mhos */ +static int ds2780_set_sense_register(struct ds2780_device_info *dev_info, + u8 conductance) +{ + int ret; + + ret = ds2780_write(dev_info, &conductance, + DS2780_RSNSP_REG, sizeof(u8)); + if (ret < 0) + return ret; + + return ds2780_save_eeprom(dev_info, DS2780_RSNSP_REG); +} + +/* Get RSGAIN value from 0 to 1.999 in steps of 0.001 */ +static int ds2780_get_rsgain_register(struct ds2780_device_info *dev_info, + u16 *rsgain) +{ + return ds2780_read16(dev_info, rsgain, DS2780_RSGAIN_MSB_REG); +} + +/* Set RSGAIN value from 0 to 1.999 in steps of 0.001 */ +static int ds2780_set_rsgain_register(struct ds2780_device_info *dev_info, + u16 rsgain) +{ + int ret; + u8 raw[] = {rsgain >> 8, rsgain & 0xFF}; + + ret = ds2780_write(dev_info, raw, + DS2780_RSGAIN_MSB_REG, sizeof(raw)); + if (ret < 0) + return ret; + + return ds2780_save_eeprom(dev_info, DS2780_RSGAIN_MSB_REG); +} + +static int ds2780_get_voltage(struct ds2780_device_info *dev_info, + int *voltage_uV) +{ + int ret; + s16 voltage_raw; + + /* + * The voltage value is located in 10 bits across the voltage MSB + * and LSB registers in two's compliment form + * Sign bit of the voltage value is in bit 7 of the voltage MSB register + * Bits 9 - 3 of the voltage value are in bits 6 - 0 of the + * voltage MSB register + * Bits 2 - 0 of the voltage value are in bits 7 - 5 of the + * voltage LSB register + */ + ret = ds2780_read16(dev_info, &voltage_raw, + DS2780_VOLT_MSB_REG); + if (ret < 0) + return ret; + + /* + * DS2780 reports voltage in units of 4.88mV, but the battery class + * reports in units of uV, so convert by multiplying by 4880. + */ + *voltage_uV = (voltage_raw / 32) * 4880; + return 0; +} + +static int ds2780_get_temperature(struct ds2780_device_info *dev_info, + int *temperature) +{ + int ret; + s16 temperature_raw; + + /* + * The temperature value is located in 10 bits across the temperature + * MSB and LSB registers in two's compliment form + * Sign bit of the temperature value is in bit 7 of the temperature + * MSB register + * Bits 9 - 3 of the temperature value are in bits 6 - 0 of the + * temperature MSB register + * Bits 2 - 0 of the temperature value are in bits 7 - 5 of the + * temperature LSB register + */ + ret = ds2780_read16(dev_info, &temperature_raw, + DS2780_TEMP_MSB_REG); + if (ret < 0) + return ret; + + /* + * Temperature is measured in units of 0.125 degrees celcius, the + * power_supply class measures temperature in tenths of degrees + * celsius. The temperature value is stored as a 10 bit number, plus + * sign in the upper bits of a 16 bit register. + */ + *temperature = ((temperature_raw / 32) * 125) / 100; + return 0; +} + +static int ds2780_get_current(struct ds2780_device_info *dev_info, + enum current_types type, int *current_uA) +{ + int ret, sense_res; + s16 current_raw; + u8 sense_res_raw, reg_msb; + + /* + * The units of measurement for current are dependent on the value of + * the sense resistor. + */ + ret = ds2780_read8(dev_info, &sense_res_raw, DS2780_RSNSP_REG); + if (ret < 0) + return ret; + + if (sense_res_raw == 0) { + dev_err(dev_info->dev, "sense resistor value is 0\n"); + return -EINVAL; + } + sense_res = 1000 / sense_res_raw; + + if (type == CURRENT_NOW) + reg_msb = DS2780_CURRENT_MSB_REG; + else if (type == CURRENT_AVG) + reg_msb = DS2780_IAVG_MSB_REG; + else + return -EINVAL; + + /* + * The current value is located in 16 bits across the current MSB + * and LSB registers in two's compliment form + * Sign bit of the current value is in bit 7 of the current MSB register + * Bits 14 - 8 of the current value are in bits 6 - 0 of the current + * MSB register + * Bits 7 - 0 of the current value are in bits 7 - 0 of the current + * LSB register + */ + ret = ds2780_read16(dev_info, ¤t_raw, reg_msb); + if (ret < 0) + return ret; + + *current_uA = current_raw * (DS2780_CURRENT_UNITS / sense_res); + return 0; +} + +static int ds2780_get_accumulated_current(struct ds2780_device_info *dev_info, + int *accumulated_current) +{ + int ret, sense_res; + s16 current_raw; + u8 sense_res_raw; + + /* + * The units of measurement for accumulated current are dependent on + * the value of the sense resistor. + */ + ret = ds2780_read8(dev_info, &sense_res_raw, DS2780_RSNSP_REG); + if (ret < 0) + return ret; + + if (sense_res_raw == 0) { + dev_err(dev_info->dev, "sense resistor value is 0\n"); + return -ENXIO; + } + sense_res = 1000 / sense_res_raw; + + /* + * The ACR value is located in 16 bits across the ACR MSB and + * LSB registers + * Bits 15 - 8 of the ACR value are in bits 7 - 0 of the ACR + * MSB register + * Bits 7 - 0 of the ACR value are in bits 7 - 0 of the ACR + * LSB register + */ + ret = ds2780_read16(dev_info, ¤t_raw, DS2780_ACR_MSB_REG); + if (ret < 0) + return ret; + + *accumulated_current = current_raw * (DS2780_CHARGE_UNITS / sense_res); + return 0; +} + +static int ds2780_get_capacity(struct ds2780_device_info *dev_info, + int *capacity) +{ + int ret; + u8 raw; + + ret = ds2780_read8(dev_info, &raw, DS2780_RARC_REG); + if (ret < 0) + return ret; + + *capacity = raw; + return raw; +} + +static int ds2780_get_status(struct ds2780_device_info *dev_info, int *status) +{ + int ret, current_uA, capacity; + + ret = ds2780_get_current(dev_info, CURRENT_NOW, ¤t_uA); + if (ret < 0) + return ret; + + ret = ds2780_get_capacity(dev_info, &capacity); + if (ret < 0) + return ret; + + if (capacity == 100) + *status = POWER_SUPPLY_STATUS_FULL; + else if (current_uA == 0) + *status = POWER_SUPPLY_STATUS_NOT_CHARGING; + else if (current_uA < 0) + *status = POWER_SUPPLY_STATUS_DISCHARGING; + else + *status = POWER_SUPPLY_STATUS_CHARGING; + + return 0; +} + +static int ds2780_get_charge_now(struct ds2780_device_info *dev_info, + int *charge_now) +{ + int ret; + u16 charge_raw; + + /* + * The RAAC value is located in 16 bits across the RAAC MSB and + * LSB registers + * Bits 15 - 8 of the RAAC value are in bits 7 - 0 of the RAAC + * MSB register + * Bits 7 - 0 of the RAAC value are in bits 7 - 0 of the RAAC + * LSB register + */ + ret = ds2780_read16(dev_info, &charge_raw, DS2780_RAAC_MSB_REG); + if (ret < 0) + return ret; + + *charge_now = charge_raw * 1600; + return 0; +} + +static int ds2780_get_control_register(struct ds2780_device_info *dev_info, + u8 *control_reg) +{ + return ds2780_read8(dev_info, control_reg, DS2780_CONTROL_REG); +} + +static int ds2780_set_control_register(struct ds2780_device_info *dev_info, + u8 control_reg) +{ + int ret; + + ret = ds2780_write(dev_info, &control_reg, + DS2780_CONTROL_REG, sizeof(u8)); + if (ret < 0) + return ret; + + return ds2780_save_eeprom(dev_info, DS2780_CONTROL_REG); +} + +static int ds2780_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret = 0; + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = ds2780_get_voltage(dev_info, &val->intval); + break; + + case POWER_SUPPLY_PROP_TEMP: + ret = ds2780_get_temperature(dev_info, &val->intval); + break; + + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = model; + break; + + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = manufacturer; + break; + + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = ds2780_get_current(dev_info, CURRENT_NOW, &val->intval); + break; + + case POWER_SUPPLY_PROP_CURRENT_AVG: + ret = ds2780_get_current(dev_info, CURRENT_AVG, &val->intval); + break; + + case POWER_SUPPLY_PROP_STATUS: + ret = ds2780_get_status(dev_info, &val->intval); + break; + + case POWER_SUPPLY_PROP_CAPACITY: + ret = ds2780_get_capacity(dev_info, &val->intval); + break; + + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + ret = ds2780_get_accumulated_current(dev_info, &val->intval); + break; + + case POWER_SUPPLY_PROP_CHARGE_NOW: + ret = ds2780_get_charge_now(dev_info, &val->intval); + break; + + default: + ret = -EINVAL; + } + + return ret; +} + +static enum power_supply_property ds2780_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CHARGE_COUNTER, + POWER_SUPPLY_PROP_CHARGE_NOW, +}; + +static ssize_t ds2780_get_pmod_enabled(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 control_reg; + struct power_supply *psy = to_power_supply(dev); + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + + /* Get power mode */ + ret = ds2780_get_control_register(dev_info, &control_reg); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", + !!(control_reg & DS2780_CONTROL_REG_PMOD)); +} + +static ssize_t ds2780_set_pmod_enabled(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + int ret; + u8 control_reg, new_setting; + struct power_supply *psy = to_power_supply(dev); + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + + /* Set power mode */ + ret = ds2780_get_control_register(dev_info, &control_reg); + if (ret < 0) + return ret; + + ret = kstrtou8(buf, 0, &new_setting); + if (ret < 0) + return ret; + + if ((new_setting != 0) && (new_setting != 1)) { + dev_err(dev_info->dev, "Invalid pmod setting (0 or 1)\n"); + return -EINVAL; + } + + if (new_setting) + control_reg |= DS2780_CONTROL_REG_PMOD; + else + control_reg &= ~DS2780_CONTROL_REG_PMOD; + + ret = ds2780_set_control_register(dev_info, control_reg); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t ds2780_get_sense_resistor_value(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 sense_resistor; + struct power_supply *psy = to_power_supply(dev); + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + + ret = ds2780_read8(dev_info, &sense_resistor, DS2780_RSNSP_REG); + if (ret < 0) + return ret; + + ret = sprintf(buf, "%d\n", sense_resistor); + return ret; +} + +static ssize_t ds2780_set_sense_resistor_value(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + int ret; + u8 new_setting; + struct power_supply *psy = to_power_supply(dev); + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + + ret = kstrtou8(buf, 0, &new_setting); + if (ret < 0) + return ret; + + ret = ds2780_set_sense_register(dev_info, new_setting); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t ds2780_get_rsgain_setting(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u16 rsgain; + struct power_supply *psy = to_power_supply(dev); + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + + ret = ds2780_get_rsgain_register(dev_info, &rsgain); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", rsgain); +} + +static ssize_t ds2780_set_rsgain_setting(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + int ret; + u16 new_setting; + struct power_supply *psy = to_power_supply(dev); + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + + ret = kstrtou16(buf, 0, &new_setting); + if (ret < 0) + return ret; + + /* Gain can only be from 0 to 1.999 in steps of .001 */ + if (new_setting > 1999) { + dev_err(dev_info->dev, "Invalid rsgain setting (0 - 1999)\n"); + return -EINVAL; + } + + ret = ds2780_set_rsgain_register(dev_info, new_setting); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t ds2780_get_pio_pin(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 sfr; + struct power_supply *psy = to_power_supply(dev); + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + + ret = ds2780_read8(dev_info, &sfr, DS2780_SFR_REG); + if (ret < 0) + return ret; + + ret = sprintf(buf, "%d\n", sfr & DS2780_SFR_REG_PIOSC); + return ret; +} + +static ssize_t ds2780_set_pio_pin(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + int ret; + u8 new_setting; + struct power_supply *psy = to_power_supply(dev); + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + + ret = kstrtou8(buf, 0, &new_setting); + if (ret < 0) + return ret; + + if ((new_setting != 0) && (new_setting != 1)) { + dev_err(dev_info->dev, "Invalid pio_pin setting (0 or 1)\n"); + return -EINVAL; + } + + ret = ds2780_write(dev_info, &new_setting, + DS2780_SFR_REG, sizeof(u8)); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t ds2780_read_param_eeprom_bin(struct file *filp, + struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct power_supply *psy = to_power_supply(dev); + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + + count = min_t(loff_t, count, + DS2780_EEPROM_BLOCK1_END - + DS2780_EEPROM_BLOCK1_START + 1 - off); + + return ds2780_read_block(dev_info, buf, + DS2780_EEPROM_BLOCK1_START + off, count); +} + +static ssize_t ds2780_write_param_eeprom_bin(struct file *filp, + struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct power_supply *psy = to_power_supply(dev); + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + int ret; + + count = min_t(loff_t, count, + DS2780_EEPROM_BLOCK1_END - + DS2780_EEPROM_BLOCK1_START + 1 - off); + + ret = ds2780_write(dev_info, buf, + DS2780_EEPROM_BLOCK1_START + off, count); + if (ret < 0) + return ret; + + ret = ds2780_save_eeprom(dev_info, DS2780_EEPROM_BLOCK1_START); + if (ret < 0) + return ret; + + return count; +} + +static struct bin_attribute ds2780_param_eeprom_bin_attr = { + .attr = { + .name = "param_eeprom", + .mode = S_IRUGO | S_IWUSR, + }, + .size = DS2780_EEPROM_BLOCK1_END - DS2780_EEPROM_BLOCK1_START + 1, + .read = ds2780_read_param_eeprom_bin, + .write = ds2780_write_param_eeprom_bin, +}; + +static ssize_t ds2780_read_user_eeprom_bin(struct file *filp, + struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct power_supply *psy = to_power_supply(dev); + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + + count = min_t(loff_t, count, + DS2780_EEPROM_BLOCK0_END - + DS2780_EEPROM_BLOCK0_START + 1 - off); + + return ds2780_read_block(dev_info, buf, + DS2780_EEPROM_BLOCK0_START + off, count); +} + +static ssize_t ds2780_write_user_eeprom_bin(struct file *filp, + struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct power_supply *psy = to_power_supply(dev); + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + int ret; + + count = min_t(loff_t, count, + DS2780_EEPROM_BLOCK0_END - + DS2780_EEPROM_BLOCK0_START + 1 - off); + + ret = ds2780_write(dev_info, buf, + DS2780_EEPROM_BLOCK0_START + off, count); + if (ret < 0) + return ret; + + ret = ds2780_save_eeprom(dev_info, DS2780_EEPROM_BLOCK0_START); + if (ret < 0) + return ret; + + return count; +} + +static struct bin_attribute ds2780_user_eeprom_bin_attr = { + .attr = { + .name = "user_eeprom", + .mode = S_IRUGO | S_IWUSR, + }, + .size = DS2780_EEPROM_BLOCK0_END - DS2780_EEPROM_BLOCK0_START + 1, + .read = ds2780_read_user_eeprom_bin, + .write = ds2780_write_user_eeprom_bin, +}; + +static DEVICE_ATTR(pmod_enabled, S_IRUGO | S_IWUSR, ds2780_get_pmod_enabled, + ds2780_set_pmod_enabled); +static DEVICE_ATTR(sense_resistor_value, S_IRUGO | S_IWUSR, + ds2780_get_sense_resistor_value, ds2780_set_sense_resistor_value); +static DEVICE_ATTR(rsgain_setting, S_IRUGO | S_IWUSR, ds2780_get_rsgain_setting, + ds2780_set_rsgain_setting); +static DEVICE_ATTR(pio_pin, S_IRUGO | S_IWUSR, ds2780_get_pio_pin, + ds2780_set_pio_pin); + + +static struct attribute *ds2780_attributes[] = { + &dev_attr_pmod_enabled.attr, + &dev_attr_sense_resistor_value.attr, + &dev_attr_rsgain_setting.attr, + &dev_attr_pio_pin.attr, + NULL +}; + +static const struct attribute_group ds2780_attr_group = { + .attrs = ds2780_attributes, +}; + +static int __devinit ds2780_battery_probe(struct platform_device *pdev) +{ + int ret = 0; + struct ds2780_device_info *dev_info; + + dev_info = kzalloc(sizeof(*dev_info), GFP_KERNEL); + if (!dev_info) { + ret = -ENOMEM; + goto fail; + } + + platform_set_drvdata(pdev, dev_info); + + dev_info->dev = &pdev->dev; + dev_info->w1_dev = pdev->dev.parent; + dev_info->bat.name = dev_name(&pdev->dev); + dev_info->bat.type = POWER_SUPPLY_TYPE_BATTERY; + dev_info->bat.properties = ds2780_battery_props; + dev_info->bat.num_properties = ARRAY_SIZE(ds2780_battery_props); + dev_info->bat.get_property = ds2780_battery_get_property; + dev_info->mutex_holder = current; + + ret = power_supply_register(&pdev->dev, &dev_info->bat); + if (ret) { + dev_err(dev_info->dev, "failed to register battery\n"); + goto fail_free_info; + } + + ret = sysfs_create_group(&dev_info->bat.dev->kobj, &ds2780_attr_group); + if (ret) { + dev_err(dev_info->dev, "failed to create sysfs group\n"); + goto fail_unregister; + } + + ret = sysfs_create_bin_file(&dev_info->bat.dev->kobj, + &ds2780_param_eeprom_bin_attr); + if (ret) { + dev_err(dev_info->dev, + "failed to create param eeprom bin file"); + goto fail_remove_group; + } + + ret = sysfs_create_bin_file(&dev_info->bat.dev->kobj, + &ds2780_user_eeprom_bin_attr); + if (ret) { + dev_err(dev_info->dev, + "failed to create user eeprom bin file"); + goto fail_remove_bin_file; + } + + dev_info->mutex_holder = NULL; + + return 0; + +fail_remove_bin_file: + sysfs_remove_bin_file(&dev_info->bat.dev->kobj, + &ds2780_param_eeprom_bin_attr); +fail_remove_group: + sysfs_remove_group(&dev_info->bat.dev->kobj, &ds2780_attr_group); +fail_unregister: + power_supply_unregister(&dev_info->bat); +fail_free_info: + kfree(dev_info); +fail: + return ret; +} + +static int __devexit ds2780_battery_remove(struct platform_device *pdev) +{ + struct ds2780_device_info *dev_info = platform_get_drvdata(pdev); + + dev_info->mutex_holder = current; + + /* remove attributes */ + sysfs_remove_group(&dev_info->bat.dev->kobj, &ds2780_attr_group); + + power_supply_unregister(&dev_info->bat); + + kfree(dev_info); + return 0; +} + +MODULE_ALIAS("platform:ds2780-battery"); + +static struct platform_driver ds2780_battery_driver = { + .driver = { + .name = "ds2780-battery", + }, + .probe = ds2780_battery_probe, + .remove = ds2780_battery_remove, +}; + +static int __init ds2780_battery_init(void) +{ + return platform_driver_register(&ds2780_battery_driver); +} + +static void __exit ds2780_battery_exit(void) +{ + platform_driver_unregister(&ds2780_battery_driver); +} + +module_init(ds2780_battery_init); +module_exit(ds2780_battery_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Clifton Barnes "); +MODULE_DESCRIPTION("Maxim/Dallas DS2780 Stand-Alone Fuel Gauage IC driver"); diff --git a/drivers/power/ds2782_battery.c b/drivers/power/ds2782_battery.c new file mode 100644 index 00000000..4d2dc4fa --- /dev/null +++ b/drivers/power/ds2782_battery.c @@ -0,0 +1,421 @@ +/* + * I2C client/driver for the Maxim/Dallas DS2782 Stand-Alone Fuel Gauge IC + * + * Copyright (C) 2009 Bluewater Systems Ltd + * + * Author: Ryan Mallon + * + * DS2786 added by Yulia Vilensky + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DS2782_REG_RARC 0x06 /* Remaining active relative capacity */ + +#define DS278x_REG_VOLT_MSB 0x0c +#define DS278x_REG_TEMP_MSB 0x0a +#define DS278x_REG_CURRENT_MSB 0x0e + +/* EEPROM Block */ +#define DS2782_REG_RSNSP 0x69 /* Sense resistor value */ + +/* Current unit measurement in uA for a 1 milli-ohm sense resistor */ +#define DS2782_CURRENT_UNITS 1563 + +#define DS2786_REG_RARC 0x02 /* Remaining active relative capacity */ + +#define DS2786_CURRENT_UNITS 25 + +struct ds278x_info; + +struct ds278x_battery_ops { + int (*get_battery_current)(struct ds278x_info *info, int *current_uA); + int (*get_battery_voltage)(struct ds278x_info *info, int *voltage_uV); + int (*get_battery_capacity)(struct ds278x_info *info, int *capacity); +}; + +#define to_ds278x_info(x) container_of(x, struct ds278x_info, battery) + +struct ds278x_info { + struct i2c_client *client; + struct power_supply battery; + struct ds278x_battery_ops *ops; + int id; + int rsns; +}; + +static DEFINE_IDR(battery_id); +static DEFINE_MUTEX(battery_lock); + +static inline int ds278x_read_reg(struct ds278x_info *info, int reg, u8 *val) +{ + int ret; + + ret = i2c_smbus_read_byte_data(info->client, reg); + if (ret < 0) { + dev_err(&info->client->dev, "register read failed\n"); + return ret; + } + + *val = ret; + return 0; +} + +static inline int ds278x_read_reg16(struct ds278x_info *info, int reg_msb, + s16 *val) +{ + int ret; + + ret = swab16(i2c_smbus_read_word_data(info->client, reg_msb)); + if (ret < 0) { + dev_err(&info->client->dev, "register read failed\n"); + return ret; + } + + *val = ret; + return 0; +} + +static int ds278x_get_temp(struct ds278x_info *info, int *temp) +{ + s16 raw; + int err; + + /* + * Temperature is measured in units of 0.125 degrees celcius, the + * power_supply class measures temperature in tenths of degrees + * celsius. The temperature value is stored as a 10 bit number, plus + * sign in the upper bits of a 16 bit register. + */ + err = ds278x_read_reg16(info, DS278x_REG_TEMP_MSB, &raw); + if (err) + return err; + *temp = ((raw / 32) * 125) / 100; + return 0; +} + +static int ds2782_get_current(struct ds278x_info *info, int *current_uA) +{ + int sense_res; + int err; + u8 sense_res_raw; + s16 raw; + + /* + * The units of measurement for current are dependent on the value of + * the sense resistor. + */ + err = ds278x_read_reg(info, DS2782_REG_RSNSP, &sense_res_raw); + if (err) + return err; + if (sense_res_raw == 0) { + dev_err(&info->client->dev, "sense resistor value is 0\n"); + return -ENXIO; + } + sense_res = 1000 / sense_res_raw; + + dev_dbg(&info->client->dev, "sense resistor = %d milli-ohms\n", + sense_res); + err = ds278x_read_reg16(info, DS278x_REG_CURRENT_MSB, &raw); + if (err) + return err; + *current_uA = raw * (DS2782_CURRENT_UNITS / sense_res); + return 0; +} + +static int ds2782_get_voltage(struct ds278x_info *info, int *voltage_uV) +{ + s16 raw; + int err; + + /* + * Voltage is measured in units of 4.88mV. The voltage is stored as + * a 10-bit number plus sign, in the upper bits of a 16-bit register + */ + err = ds278x_read_reg16(info, DS278x_REG_VOLT_MSB, &raw); + if (err) + return err; + *voltage_uV = (raw / 32) * 4800; + return 0; +} + +static int ds2782_get_capacity(struct ds278x_info *info, int *capacity) +{ + int err; + u8 raw; + + err = ds278x_read_reg(info, DS2782_REG_RARC, &raw); + if (err) + return err; + *capacity = raw; + return 0; +} + +static int ds2786_get_current(struct ds278x_info *info, int *current_uA) +{ + int err; + s16 raw; + + err = ds278x_read_reg16(info, DS278x_REG_CURRENT_MSB, &raw); + if (err) + return err; + *current_uA = (raw / 16) * (DS2786_CURRENT_UNITS / info->rsns); + return 0; +} + +static int ds2786_get_voltage(struct ds278x_info *info, int *voltage_uV) +{ + s16 raw; + int err; + + /* + * Voltage is measured in units of 1.22mV. The voltage is stored as + * a 10-bit number plus sign, in the upper bits of a 16-bit register + */ + err = ds278x_read_reg16(info, DS278x_REG_VOLT_MSB, &raw); + if (err) + return err; + *voltage_uV = (raw / 8) * 1220; + return 0; +} + +static int ds2786_get_capacity(struct ds278x_info *info, int *capacity) +{ + int err; + u8 raw; + + err = ds278x_read_reg(info, DS2786_REG_RARC, &raw); + if (err) + return err; + /* Relative capacity is displayed with resolution 0.5 % */ + *capacity = raw/2 ; + return 0; +} + +static int ds278x_get_status(struct ds278x_info *info, int *status) +{ + int err; + int current_uA; + int capacity; + + err = info->ops->get_battery_current(info, ¤t_uA); + if (err) + return err; + + err = info->ops->get_battery_capacity(info, &capacity); + if (err) + return err; + + if (capacity == 100) + *status = POWER_SUPPLY_STATUS_FULL; + else if (current_uA == 0) + *status = POWER_SUPPLY_STATUS_NOT_CHARGING; + else if (current_uA < 0) + *status = POWER_SUPPLY_STATUS_DISCHARGING; + else + *status = POWER_SUPPLY_STATUS_CHARGING; + + return 0; +} + +static int ds278x_battery_get_property(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *val) +{ + struct ds278x_info *info = to_ds278x_info(psy); + int ret; + + switch (prop) { + case POWER_SUPPLY_PROP_STATUS: + ret = ds278x_get_status(info, &val->intval); + break; + + case POWER_SUPPLY_PROP_CAPACITY: + ret = info->ops->get_battery_capacity(info, &val->intval); + break; + + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = info->ops->get_battery_voltage(info, &val->intval); + break; + + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = info->ops->get_battery_current(info, &val->intval); + break; + + case POWER_SUPPLY_PROP_TEMP: + ret = ds278x_get_temp(info, &val->intval); + break; + + default: + ret = -EINVAL; + } + + return ret; +} + +static enum power_supply_property ds278x_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_TEMP, +}; + +static void ds278x_power_supply_init(struct power_supply *battery) +{ + battery->type = POWER_SUPPLY_TYPE_BATTERY; + battery->properties = ds278x_battery_props; + battery->num_properties = ARRAY_SIZE(ds278x_battery_props); + battery->get_property = ds278x_battery_get_property; + battery->external_power_changed = NULL; +} + +static int ds278x_battery_remove(struct i2c_client *client) +{ + struct ds278x_info *info = i2c_get_clientdata(client); + + power_supply_unregister(&info->battery); + kfree(info->battery.name); + + mutex_lock(&battery_lock); + idr_remove(&battery_id, info->id); + mutex_unlock(&battery_lock); + + kfree(info); + return 0; +} + +enum ds278x_num_id { + DS2782 = 0, + DS2786, +}; + +static struct ds278x_battery_ops ds278x_ops[] = { + [DS2782] = { + .get_battery_current = ds2782_get_current, + .get_battery_voltage = ds2782_get_voltage, + .get_battery_capacity = ds2782_get_capacity, + }, + [DS2786] = { + .get_battery_current = ds2786_get_current, + .get_battery_voltage = ds2786_get_voltage, + .get_battery_capacity = ds2786_get_capacity, + } +}; + +static int ds278x_battery_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ds278x_platform_data *pdata = client->dev.platform_data; + struct ds278x_info *info; + int ret; + int num; + + /* + * ds2786 should have the sense resistor value set + * in the platform data + */ + if (id->driver_data == DS2786 && !pdata) { + dev_err(&client->dev, "missing platform data for ds2786\n"); + return -EINVAL; + } + + /* Get an ID for this battery */ + ret = idr_pre_get(&battery_id, GFP_KERNEL); + if (ret == 0) { + ret = -ENOMEM; + goto fail_id; + } + + mutex_lock(&battery_lock); + ret = idr_get_new(&battery_id, client, &num); + mutex_unlock(&battery_lock); + if (ret < 0) + goto fail_id; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + ret = -ENOMEM; + goto fail_info; + } + + info->battery.name = kasprintf(GFP_KERNEL, "%s-%d", client->name, num); + if (!info->battery.name) { + ret = -ENOMEM; + goto fail_name; + } + + if (id->driver_data == DS2786) + info->rsns = pdata->rsns; + + i2c_set_clientdata(client, info); + info->client = client; + info->id = num; + info->ops = &ds278x_ops[id->driver_data]; + ds278x_power_supply_init(&info->battery); + + ret = power_supply_register(&client->dev, &info->battery); + if (ret) { + dev_err(&client->dev, "failed to register battery\n"); + goto fail_register; + } + + return 0; + +fail_register: + kfree(info->battery.name); +fail_name: + kfree(info); +fail_info: + mutex_lock(&battery_lock); + idr_remove(&battery_id, num); + mutex_unlock(&battery_lock); +fail_id: + return ret; +} + +static const struct i2c_device_id ds278x_id[] = { + {"ds2782", DS2782}, + {"ds2786", DS2786}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, ds278x_id); + +static struct i2c_driver ds278x_battery_driver = { + .driver = { + .name = "ds2782-battery", + }, + .probe = ds278x_battery_probe, + .remove = ds278x_battery_remove, + .id_table = ds278x_id, +}; + +static int __init ds278x_init(void) +{ + return i2c_add_driver(&ds278x_battery_driver); +} +module_init(ds278x_init); + +static void __exit ds278x_exit(void) +{ + i2c_del_driver(&ds278x_battery_driver); +} +module_exit(ds278x_exit); + +MODULE_AUTHOR("Ryan Mallon "); +MODULE_DESCRIPTION("Maxim/Dallas DS2782 Stand-Alone Fuel Gauage IC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/gpio-charger.c b/drivers/power/gpio-charger.c new file mode 100644 index 00000000..718f2c53 --- /dev/null +++ b/drivers/power/gpio-charger.c @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2010, Lars-Peter Clausen + * Driver for chargers which report their online status through a GPIO pin + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +struct gpio_charger { + const struct gpio_charger_platform_data *pdata; + unsigned int irq; + + struct power_supply charger; +}; + +static irqreturn_t gpio_charger_irq(int irq, void *devid) +{ + struct power_supply *charger = devid; + + power_supply_changed(charger); + + return IRQ_HANDLED; +} + +static inline struct gpio_charger *psy_to_gpio_charger(struct power_supply *psy) +{ + return container_of(psy, struct gpio_charger, charger); +} + +static int gpio_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, union power_supply_propval *val) +{ + struct gpio_charger *gpio_charger = psy_to_gpio_charger(psy); + const struct gpio_charger_platform_data *pdata = gpio_charger->pdata; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = gpio_get_value(pdata->gpio); + val->intval ^= pdata->gpio_active_low; + break; + default: + return -EINVAL; + } + + return 0; +} + +static enum power_supply_property gpio_charger_properties[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static int __devinit gpio_charger_probe(struct platform_device *pdev) +{ + const struct gpio_charger_platform_data *pdata = pdev->dev.platform_data; + struct gpio_charger *gpio_charger; + struct power_supply *charger; + int ret; + int irq; + + if (!pdata) { + dev_err(&pdev->dev, "No platform data\n"); + return -EINVAL; + } + + if (!gpio_is_valid(pdata->gpio)) { + dev_err(&pdev->dev, "Invalid gpio pin\n"); + return -EINVAL; + } + + gpio_charger = kzalloc(sizeof(*gpio_charger), GFP_KERNEL); + if (!gpio_charger) { + dev_err(&pdev->dev, "Failed to alloc driver structure\n"); + return -ENOMEM; + } + + charger = &gpio_charger->charger; + + charger->name = pdata->name ? pdata->name : "gpio-charger"; + charger->type = pdata->type; + charger->properties = gpio_charger_properties; + charger->num_properties = ARRAY_SIZE(gpio_charger_properties); + charger->get_property = gpio_charger_get_property; + charger->supplied_to = pdata->supplied_to; + charger->num_supplicants = pdata->num_supplicants; + + ret = gpio_request(pdata->gpio, dev_name(&pdev->dev)); + if (ret) { + dev_err(&pdev->dev, "Failed to request gpio pin: %d\n", ret); + goto err_free; + } + ret = gpio_direction_input(pdata->gpio); + if (ret) { + dev_err(&pdev->dev, "Failed to set gpio to input: %d\n", ret); + goto err_gpio_free; + } + + gpio_charger->pdata = pdata; + + ret = power_supply_register(&pdev->dev, charger); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to register power supply: %d\n", + ret); + goto err_gpio_free; + } + + irq = gpio_to_irq(pdata->gpio); + if (irq > 0) { + ret = request_any_context_irq(irq, gpio_charger_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + dev_name(&pdev->dev), charger); + if (ret) + dev_warn(&pdev->dev, "Failed to request irq: %d\n", ret); + else + gpio_charger->irq = irq; + } + + platform_set_drvdata(pdev, gpio_charger); + + return 0; + +err_gpio_free: + gpio_free(pdata->gpio); +err_free: + kfree(gpio_charger); + return ret; +} + +static int __devexit gpio_charger_remove(struct platform_device *pdev) +{ + struct gpio_charger *gpio_charger = platform_get_drvdata(pdev); + + if (gpio_charger->irq) + free_irq(gpio_charger->irq, &gpio_charger->charger); + + power_supply_unregister(&gpio_charger->charger); + + gpio_free(gpio_charger->pdata->gpio); + + platform_set_drvdata(pdev, NULL); + kfree(gpio_charger); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int gpio_charger_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct gpio_charger *gpio_charger = platform_get_drvdata(pdev); + + power_supply_changed(&gpio_charger->charger); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(gpio_charger_pm_ops, NULL, gpio_charger_resume); + +static struct platform_driver gpio_charger_driver = { + .probe = gpio_charger_probe, + .remove = __devexit_p(gpio_charger_remove), + .driver = { + .name = "gpio-charger", + .owner = THIS_MODULE, + .pm = &gpio_charger_pm_ops, + }, +}; + +static int __init gpio_charger_init(void) +{ + return platform_driver_register(&gpio_charger_driver); +} +module_init(gpio_charger_init); + +static void __exit gpio_charger_exit(void) +{ + platform_driver_unregister(&gpio_charger_driver); +} +module_exit(gpio_charger_exit); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("Driver for chargers which report their online status through a GPIO"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:gpio-charger"); diff --git a/drivers/power/intel_mid_battery.c b/drivers/power/intel_mid_battery.c new file mode 100644 index 00000000..cffcb7c0 --- /dev/null +++ b/drivers/power/intel_mid_battery.c @@ -0,0 +1,797 @@ +/* + * intel_mid_battery.c - Intel MID PMIC Battery Driver + * + * Copyright (C) 2009 Intel Corporation + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Author: Nithish Mahalingam + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define DRIVER_NAME "pmic_battery" + +/********************************************************************* + * Generic defines + *********************************************************************/ + +static int debug; +module_param(debug, int, 0444); +MODULE_PARM_DESC(debug, "Flag to enable PMIC Battery debug messages."); + +#define PMIC_BATT_DRV_INFO_UPDATED 1 +#define PMIC_BATT_PRESENT 1 +#define PMIC_BATT_NOT_PRESENT 0 +#define PMIC_USB_PRESENT PMIC_BATT_PRESENT +#define PMIC_USB_NOT_PRESENT PMIC_BATT_NOT_PRESENT + +/* pmic battery register related */ +#define PMIC_BATT_CHR_SCHRGINT_ADDR 0xD2 +#define PMIC_BATT_CHR_SBATOVP_MASK (1 << 1) +#define PMIC_BATT_CHR_STEMP_MASK (1 << 2) +#define PMIC_BATT_CHR_SCOMP_MASK (1 << 3) +#define PMIC_BATT_CHR_SUSBDET_MASK (1 << 4) +#define PMIC_BATT_CHR_SBATDET_MASK (1 << 5) +#define PMIC_BATT_CHR_SDCLMT_MASK (1 << 6) +#define PMIC_BATT_CHR_SUSBOVP_MASK (1 << 7) +#define PMIC_BATT_CHR_EXCPT_MASK 0xC6 +#define PMIC_BATT_ADC_ACCCHRG_MASK (1 << 31) +#define PMIC_BATT_ADC_ACCCHRGVAL_MASK 0x7FFFFFFF + +/* pmic ipc related */ +#define PMIC_BATT_CHR_IPC_FCHRG_SUBID 0x4 +#define PMIC_BATT_CHR_IPC_TCHRG_SUBID 0x6 + +/* types of battery charging */ +enum batt_charge_type { + BATT_USBOTG_500MA_CHARGE, + BATT_USBOTG_TRICKLE_CHARGE, +}; + +/* valid battery events */ +enum batt_event { + BATT_EVENT_BATOVP_EXCPT, + BATT_EVENT_USBOVP_EXCPT, + BATT_EVENT_TEMP_EXCPT, + BATT_EVENT_DCLMT_EXCPT, + BATT_EVENT_EXCPT +}; + + +/********************************************************************* + * Battery properties + *********************************************************************/ + +/* + * pmic battery info + */ +struct pmic_power_module_info { + bool is_dev_info_updated; + struct device *dev; + /* pmic battery data */ + unsigned long update_time; /* jiffies when data read */ + unsigned int usb_is_present; + unsigned int batt_is_present; + unsigned int batt_health; + unsigned int usb_health; + unsigned int batt_status; + unsigned int batt_charge_now; /* in mAS */ + unsigned int batt_prev_charge_full; /* in mAS */ + unsigned int batt_charge_rate; /* in units per second */ + + struct power_supply usb; + struct power_supply batt; + int irq; /* GPE_ID or IRQ# */ + struct workqueue_struct *monitor_wqueue; + struct delayed_work monitor_battery; + struct work_struct handler; +}; + +static unsigned int delay_time = 2000; /* in ms */ + +/* + * pmic ac properties + */ +static enum power_supply_property pmic_usb_props[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_HEALTH, +}; + +/* + * pmic battery properties + */ +static enum power_supply_property pmic_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL, +}; + + +/* + * Glue functions for talking to the IPC + */ + +struct battery_property { + u32 capacity; /* Charger capacity */ + u8 crnt; /* Quick charge current value*/ + u8 volt; /* Fine adjustment of constant charge voltage */ + u8 prot; /* CHRGPROT register value */ + u8 prot2; /* CHRGPROT1 register value */ + u8 timer; /* Charging timer */ +}; + +#define IPCMSG_BATTERY 0xEF + +/* Battery coulomb counter accumulator commands */ +#define IPC_CMD_CC_WR 0 /* Update coulomb counter value */ +#define IPC_CMD_CC_RD 1 /* Read coulomb counter value */ +#define IPC_CMD_BATTERY_PROPERTY 2 /* Read Battery property */ + +/** + * pmic_scu_ipc_battery_cc_read - read battery cc + * @value: battery coulomb counter read + * + * Reads the battery couloumb counter value, returns 0 on success, or + * an error code + * + * This function may sleep. Locking for SCU accesses is handled for + * the caller. + */ +static int pmic_scu_ipc_battery_cc_read(u32 *value) +{ + return intel_scu_ipc_command(IPCMSG_BATTERY, IPC_CMD_CC_RD, + NULL, 0, value, 1); +} + +/** + * pmic_scu_ipc_battery_property_get - fetch properties + * @prop: battery properties + * + * Retrieve the battery properties from the power management + * + * This function may sleep. Locking for SCU accesses is handled for + * the caller. + */ +static int pmic_scu_ipc_battery_property_get(struct battery_property *prop) +{ + u32 data[3]; + u8 *p = (u8 *)&data[1]; + int err = intel_scu_ipc_command(IPCMSG_BATTERY, + IPC_CMD_BATTERY_PROPERTY, NULL, 0, data, 3); + + prop->capacity = data[0]; + prop->crnt = *p++; + prop->volt = *p++; + prop->prot = *p++; + prop->prot2 = *p++; + prop->timer = *p++; + + return err; +} + +/** + * pmic_scu_ipc_set_charger - set charger + * @charger: charger to select + * + * Switch the charging mode for the SCU + */ + +static int pmic_scu_ipc_set_charger(int charger) +{ + return intel_scu_ipc_simple_command(IPCMSG_BATTERY, charger); +} + +/** + * pmic_battery_log_event - log battery events + * @event: battery event to be logged + * Context: can sleep + * + * There are multiple battery events which may be of interest to users; + * this battery function logs the different battery events onto the + * kernel log messages. + */ +static void pmic_battery_log_event(enum batt_event event) +{ + printk(KERN_WARNING "pmic-battery: "); + switch (event) { + case BATT_EVENT_BATOVP_EXCPT: + printk(KERN_CONT "battery overvoltage condition\n"); + break; + case BATT_EVENT_USBOVP_EXCPT: + printk(KERN_CONT "usb charger overvoltage condition\n"); + break; + case BATT_EVENT_TEMP_EXCPT: + printk(KERN_CONT "high battery temperature condition\n"); + break; + case BATT_EVENT_DCLMT_EXCPT: + printk(KERN_CONT "over battery charge current condition\n"); + break; + default: + printk(KERN_CONT "charger/battery exception %d\n", event); + break; + } +} + +/** + * pmic_battery_read_status - read battery status information + * @pbi: device info structure to update the read information + * Context: can sleep + * + * PMIC power source information need to be updated based on the data read + * from the PMIC battery registers. + * + */ +static void pmic_battery_read_status(struct pmic_power_module_info *pbi) +{ + unsigned int update_time_intrvl; + unsigned int chrg_val; + u32 ccval; + u8 r8; + struct battery_property batt_prop; + int batt_present = 0; + int usb_present = 0; + int batt_exception = 0; + + /* make sure the last batt_status read happened delay_time before */ + if (pbi->update_time && time_before(jiffies, pbi->update_time + + msecs_to_jiffies(delay_time))) + return; + + update_time_intrvl = jiffies_to_msecs(jiffies - pbi->update_time); + pbi->update_time = jiffies; + + /* read coulomb counter registers and schrgint register */ + if (pmic_scu_ipc_battery_cc_read(&ccval)) { + dev_warn(pbi->dev, "%s(): ipc config cmd failed\n", + __func__); + return; + } + + if (intel_scu_ipc_ioread8(PMIC_BATT_CHR_SCHRGINT_ADDR, &r8)) { + dev_warn(pbi->dev, "%s(): ipc pmic read failed\n", + __func__); + return; + } + + /* + * set pmic_power_module_info members based on pmic register values + * read. + */ + + /* set batt_is_present */ + if (r8 & PMIC_BATT_CHR_SBATDET_MASK) { + pbi->batt_is_present = PMIC_BATT_PRESENT; + batt_present = 1; + } else { + pbi->batt_is_present = PMIC_BATT_NOT_PRESENT; + pbi->batt_health = POWER_SUPPLY_HEALTH_UNKNOWN; + pbi->batt_status = POWER_SUPPLY_STATUS_UNKNOWN; + } + + /* set batt_health */ + if (batt_present) { + if (r8 & PMIC_BATT_CHR_SBATOVP_MASK) { + pbi->batt_health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + pbi->batt_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + pmic_battery_log_event(BATT_EVENT_BATOVP_EXCPT); + batt_exception = 1; + } else if (r8 & PMIC_BATT_CHR_SDCLMT_MASK) { + pbi->batt_health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + pbi->batt_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + pmic_battery_log_event(BATT_EVENT_DCLMT_EXCPT); + batt_exception = 1; + } else if (r8 & PMIC_BATT_CHR_STEMP_MASK) { + pbi->batt_health = POWER_SUPPLY_HEALTH_OVERHEAT; + pbi->batt_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + pmic_battery_log_event(BATT_EVENT_TEMP_EXCPT); + batt_exception = 1; + } else { + pbi->batt_health = POWER_SUPPLY_HEALTH_GOOD; + } + } + + /* set usb_is_present */ + if (r8 & PMIC_BATT_CHR_SUSBDET_MASK) { + pbi->usb_is_present = PMIC_USB_PRESENT; + usb_present = 1; + } else { + pbi->usb_is_present = PMIC_USB_NOT_PRESENT; + pbi->usb_health = POWER_SUPPLY_HEALTH_UNKNOWN; + } + + if (usb_present) { + if (r8 & PMIC_BATT_CHR_SUSBOVP_MASK) { + pbi->usb_health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + pmic_battery_log_event(BATT_EVENT_USBOVP_EXCPT); + } else { + pbi->usb_health = POWER_SUPPLY_HEALTH_GOOD; + } + } + + chrg_val = ccval & PMIC_BATT_ADC_ACCCHRGVAL_MASK; + + /* set batt_prev_charge_full to battery capacity the first time */ + if (!pbi->is_dev_info_updated) { + if (pmic_scu_ipc_battery_property_get(&batt_prop)) { + dev_warn(pbi->dev, "%s(): ipc config cmd failed\n", + __func__); + return; + } + pbi->batt_prev_charge_full = batt_prop.capacity; + } + + /* set batt_status */ + if (batt_present && !batt_exception) { + if (r8 & PMIC_BATT_CHR_SCOMP_MASK) { + pbi->batt_status = POWER_SUPPLY_STATUS_FULL; + pbi->batt_prev_charge_full = chrg_val; + } else if (ccval & PMIC_BATT_ADC_ACCCHRG_MASK) { + pbi->batt_status = POWER_SUPPLY_STATUS_DISCHARGING; + } else { + pbi->batt_status = POWER_SUPPLY_STATUS_CHARGING; + } + } + + /* set batt_charge_rate */ + if (pbi->is_dev_info_updated && batt_present && !batt_exception) { + if (pbi->batt_status == POWER_SUPPLY_STATUS_DISCHARGING) { + if (pbi->batt_charge_now - chrg_val) { + pbi->batt_charge_rate = ((pbi->batt_charge_now - + chrg_val) * 1000 * 60) / + update_time_intrvl; + } + } else if (pbi->batt_status == POWER_SUPPLY_STATUS_CHARGING) { + if (chrg_val - pbi->batt_charge_now) { + pbi->batt_charge_rate = ((chrg_val - + pbi->batt_charge_now) * 1000 * 60) / + update_time_intrvl; + } + } else + pbi->batt_charge_rate = 0; + } else { + pbi->batt_charge_rate = -1; + } + + /* batt_charge_now */ + if (batt_present && !batt_exception) + pbi->batt_charge_now = chrg_val; + else + pbi->batt_charge_now = -1; + + pbi->is_dev_info_updated = PMIC_BATT_DRV_INFO_UPDATED; +} + +/** + * pmic_usb_get_property - usb power source get property + * @psy: usb power supply context + * @psp: usb power source property + * @val: usb power source property value + * Context: can sleep + * + * PMIC usb power source property needs to be provided to power_supply + * subsytem for it to provide the information to users. + */ +static int pmic_usb_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct pmic_power_module_info *pbi = container_of(psy, + struct pmic_power_module_info, usb); + + /* update pmic_power_module_info members */ + pmic_battery_read_status(pbi); + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + val->intval = pbi->usb_is_present; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = pbi->usb_health; + break; + default: + return -EINVAL; + } + + return 0; +} + +static inline unsigned long mAStouAh(unsigned long v) +{ + /* seconds to hours, mA to µA */ + return (v * 1000) / 3600; +} + +/** + * pmic_battery_get_property - battery power source get property + * @psy: battery power supply context + * @psp: battery power source property + * @val: battery power source property value + * Context: can sleep + * + * PMIC battery power source property needs to be provided to power_supply + * subsytem for it to provide the information to users. + */ +static int pmic_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct pmic_power_module_info *pbi = container_of(psy, + struct pmic_power_module_info, batt); + + /* update pmic_power_module_info members */ + pmic_battery_read_status(pbi); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = pbi->batt_status; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = pbi->batt_health; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = pbi->batt_is_present; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + val->intval = mAStouAh(pbi->batt_charge_now); + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + val->intval = mAStouAh(pbi->batt_prev_charge_full); + break; + default: + return -EINVAL; + } + + return 0; +} + +/** + * pmic_battery_monitor - monitor battery status + * @work: work structure + * Context: can sleep + * + * PMIC battery status needs to be monitored for any change + * and information needs to be frequently updated. + */ +static void pmic_battery_monitor(struct work_struct *work) +{ + struct pmic_power_module_info *pbi = container_of(work, + struct pmic_power_module_info, monitor_battery.work); + + /* update pmic_power_module_info members */ + pmic_battery_read_status(pbi); + queue_delayed_work(pbi->monitor_wqueue, &pbi->monitor_battery, HZ * 10); +} + +/** + * pmic_battery_set_charger - set battery charger + * @pbi: device info structure + * @chrg: charge mode to set battery charger in + * Context: can sleep + * + * PMIC battery charger needs to be enabled based on the usb charge + * capabilities connected to the platform. + */ +static int pmic_battery_set_charger(struct pmic_power_module_info *pbi, + enum batt_charge_type chrg) +{ + int retval; + + /* set usblmt bits and chrgcntl register bits appropriately */ + switch (chrg) { + case BATT_USBOTG_500MA_CHARGE: + retval = pmic_scu_ipc_set_charger(PMIC_BATT_CHR_IPC_FCHRG_SUBID); + break; + case BATT_USBOTG_TRICKLE_CHARGE: + retval = pmic_scu_ipc_set_charger(PMIC_BATT_CHR_IPC_TCHRG_SUBID); + break; + default: + dev_warn(pbi->dev, "%s(): out of range usb charger " + "charge detected\n", __func__); + return -EINVAL; + } + + if (retval) { + dev_warn(pbi->dev, "%s(): ipc pmic read failed\n", + __func__); + return retval; + } + + return 0; +} + +/** + * pmic_battery_interrupt_handler - pmic battery interrupt handler + * Context: interrupt context + * + * PMIC battery interrupt handler which will be called with either + * battery full condition occurs or usb otg & battery connect + * condition occurs. + */ +static irqreturn_t pmic_battery_interrupt_handler(int id, void *dev) +{ + struct pmic_power_module_info *pbi = dev; + + schedule_work(&pbi->handler); + + return IRQ_HANDLED; +} + +/** + * pmic_battery_handle_intrpt - pmic battery service interrupt + * @work: work structure + * Context: can sleep + * + * PMIC battery needs to either update the battery status as full + * if it detects battery full condition caused the interrupt or needs + * to enable battery charger if it detects usb and battery detect + * caused the source of interrupt. + */ +static void pmic_battery_handle_intrpt(struct work_struct *work) +{ + struct pmic_power_module_info *pbi = container_of(work, + struct pmic_power_module_info, handler); + enum batt_charge_type chrg; + u8 r8; + + if (intel_scu_ipc_ioread8(PMIC_BATT_CHR_SCHRGINT_ADDR, &r8)) { + dev_warn(pbi->dev, "%s(): ipc pmic read failed\n", + __func__); + return; + } + /* find the cause of the interrupt */ + if (r8 & PMIC_BATT_CHR_SBATDET_MASK) { + pbi->batt_is_present = PMIC_BATT_PRESENT; + } else { + pbi->batt_is_present = PMIC_BATT_NOT_PRESENT; + pbi->batt_health = POWER_SUPPLY_HEALTH_UNKNOWN; + pbi->batt_status = POWER_SUPPLY_STATUS_UNKNOWN; + return; + } + + if (r8 & PMIC_BATT_CHR_EXCPT_MASK) { + pbi->batt_health = POWER_SUPPLY_HEALTH_UNKNOWN; + pbi->batt_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + pbi->usb_health = POWER_SUPPLY_HEALTH_UNKNOWN; + pmic_battery_log_event(BATT_EVENT_EXCPT); + return; + } else { + pbi->batt_health = POWER_SUPPLY_HEALTH_GOOD; + pbi->usb_health = POWER_SUPPLY_HEALTH_GOOD; + } + + if (r8 & PMIC_BATT_CHR_SCOMP_MASK) { + u32 ccval; + pbi->batt_status = POWER_SUPPLY_STATUS_FULL; + + if (pmic_scu_ipc_battery_cc_read(&ccval)) { + dev_warn(pbi->dev, "%s(): ipc config cmd " + "failed\n", __func__); + return; + } + pbi->batt_prev_charge_full = ccval & + PMIC_BATT_ADC_ACCCHRGVAL_MASK; + return; + } + + if (r8 & PMIC_BATT_CHR_SUSBDET_MASK) { + pbi->usb_is_present = PMIC_USB_PRESENT; + } else { + pbi->usb_is_present = PMIC_USB_NOT_PRESENT; + pbi->usb_health = POWER_SUPPLY_HEALTH_UNKNOWN; + return; + } + + /* setup battery charging */ + +#if 0 + /* check usb otg power capability and set charger accordingly */ + retval = langwell_udc_maxpower(&power); + if (retval) { + dev_warn(pbi->dev, + "%s(): usb otg power query failed with error code %d\n", + __func__, retval); + return; + } + + if (power >= 500) + chrg = BATT_USBOTG_500MA_CHARGE; + else +#endif + chrg = BATT_USBOTG_TRICKLE_CHARGE; + + /* enable battery charging */ + if (pmic_battery_set_charger(pbi, chrg)) { + dev_warn(pbi->dev, + "%s(): failed to set up battery charging\n", __func__); + return; + } + + dev_dbg(pbi->dev, + "pmic-battery: %s() - setting up battery charger successful\n", + __func__); +} + +/** + * pmic_battery_probe - pmic battery initialize + * @irq: pmic battery device irq + * @dev: pmic battery device structure + * Context: can sleep + * + * PMIC battery initializes its internal data structue and other + * infrastructure components for it to work as expected. + */ +static __devinit int probe(int irq, struct device *dev) +{ + int retval = 0; + struct pmic_power_module_info *pbi; + + dev_dbg(dev, "pmic-battery: found pmic battery device\n"); + + pbi = kzalloc(sizeof(*pbi), GFP_KERNEL); + if (!pbi) { + dev_err(dev, "%s(): memory allocation failed\n", + __func__); + return -ENOMEM; + } + + pbi->dev = dev; + pbi->irq = irq; + dev_set_drvdata(dev, pbi); + + /* initialize all required framework before enabling interrupts */ + INIT_WORK(&pbi->handler, pmic_battery_handle_intrpt); + INIT_DELAYED_WORK(&pbi->monitor_battery, pmic_battery_monitor); + pbi->monitor_wqueue = + create_singlethread_workqueue(dev_name(dev)); + if (!pbi->monitor_wqueue) { + dev_err(dev, "%s(): wqueue init failed\n", __func__); + retval = -ESRCH; + goto wqueue_failed; + } + + /* register interrupt */ + retval = request_irq(pbi->irq, pmic_battery_interrupt_handler, + 0, DRIVER_NAME, pbi); + if (retval) { + dev_err(dev, "%s(): cannot get IRQ\n", __func__); + goto requestirq_failed; + } + + /* register pmic-batt with power supply subsystem */ + pbi->batt.name = "pmic-batt"; + pbi->batt.type = POWER_SUPPLY_TYPE_BATTERY; + pbi->batt.properties = pmic_battery_props; + pbi->batt.num_properties = ARRAY_SIZE(pmic_battery_props); + pbi->batt.get_property = pmic_battery_get_property; + retval = power_supply_register(dev, &pbi->batt); + if (retval) { + dev_err(dev, + "%s(): failed to register pmic battery device with power supply subsystem\n", + __func__); + goto power_reg_failed; + } + + dev_dbg(dev, "pmic-battery: %s() - pmic battery device " + "registration with power supply subsystem successful\n", + __func__); + + queue_delayed_work(pbi->monitor_wqueue, &pbi->monitor_battery, HZ * 1); + + /* register pmic-usb with power supply subsystem */ + pbi->usb.name = "pmic-usb"; + pbi->usb.type = POWER_SUPPLY_TYPE_USB; + pbi->usb.properties = pmic_usb_props; + pbi->usb.num_properties = ARRAY_SIZE(pmic_usb_props); + pbi->usb.get_property = pmic_usb_get_property; + retval = power_supply_register(dev, &pbi->usb); + if (retval) { + dev_err(dev, + "%s(): failed to register pmic usb device with power supply subsystem\n", + __func__); + goto power_reg_failed_1; + } + + if (debug) + printk(KERN_INFO "pmic-battery: %s() - pmic usb device " + "registration with power supply subsystem successful\n", + __func__); + + return retval; + +power_reg_failed_1: + power_supply_unregister(&pbi->batt); +power_reg_failed: + cancel_delayed_work_sync(&pbi->monitor_battery); +requestirq_failed: + destroy_workqueue(pbi->monitor_wqueue); +wqueue_failed: + kfree(pbi); + + return retval; +} + +static int __devinit platform_pmic_battery_probe(struct platform_device *pdev) +{ + return probe(pdev->id, &pdev->dev); +} + +/** + * pmic_battery_remove - pmic battery finalize + * @dev: pmic battery device structure + * Context: can sleep + * + * PMIC battery finalizes its internal data structue and other + * infrastructure components that it initialized in + * pmic_battery_probe. + */ + +static int __devexit platform_pmic_battery_remove(struct platform_device *pdev) +{ + struct pmic_power_module_info *pbi = dev_get_drvdata(&pdev->dev); + + free_irq(pbi->irq, pbi); + cancel_delayed_work_sync(&pbi->monitor_battery); + destroy_workqueue(pbi->monitor_wqueue); + + power_supply_unregister(&pbi->usb); + power_supply_unregister(&pbi->batt); + + cancel_work_sync(&pbi->handler); + kfree(pbi); + return 0; +} + +static struct platform_driver platform_pmic_battery_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, + .probe = platform_pmic_battery_probe, + .remove = __devexit_p(platform_pmic_battery_remove), +}; + +static int __init platform_pmic_battery_module_init(void) +{ + return platform_driver_register(&platform_pmic_battery_driver); +} + +static void __exit platform_pmic_battery_module_exit(void) +{ + platform_driver_unregister(&platform_pmic_battery_driver); +} + +module_init(platform_pmic_battery_module_init); +module_exit(platform_pmic_battery_module_exit); + +MODULE_AUTHOR("Nithish Mahalingam "); +MODULE_DESCRIPTION("Intel Moorestown PMIC Battery Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/isp1704_charger.c b/drivers/power/isp1704_charger.c new file mode 100644 index 00000000..f6d72b40 --- /dev/null +++ b/drivers/power/isp1704_charger.c @@ -0,0 +1,512 @@ +/* + * ISP1704 USB Charger Detection driver + * + * Copyright (C) 2010 Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/* Vendor specific Power Control register */ +#define ISP1704_PWR_CTRL 0x3d +#define ISP1704_PWR_CTRL_SWCTRL (1 << 0) +#define ISP1704_PWR_CTRL_DET_COMP (1 << 1) +#define ISP1704_PWR_CTRL_BVALID_RISE (1 << 2) +#define ISP1704_PWR_CTRL_BVALID_FALL (1 << 3) +#define ISP1704_PWR_CTRL_DP_WKPU_EN (1 << 4) +#define ISP1704_PWR_CTRL_VDAT_DET (1 << 5) +#define ISP1704_PWR_CTRL_DPVSRC_EN (1 << 6) +#define ISP1704_PWR_CTRL_HWDETECT (1 << 7) + +#define NXP_VENDOR_ID 0x04cc + +static u16 isp170x_id[] = { + 0x1704, + 0x1707, +}; + +struct isp1704_charger { + struct device *dev; + struct power_supply psy; + struct otg_transceiver *otg; + struct notifier_block nb; + struct work_struct work; + + /* properties */ + char model[8]; + unsigned present:1; + unsigned online:1; + unsigned current_max; + + /* temp storage variables */ + unsigned long event; + unsigned max_power; +}; + +/* + * Disable/enable the power from the isp1704 if a function for it + * has been provided with platform data. + */ +static void isp1704_charger_set_power(struct isp1704_charger *isp, bool on) +{ + struct isp1704_charger_data *board = isp->dev->platform_data; + + if (board->set_power) + board->set_power(on); +} + +/* + * Determine is the charging port DCP (dedicated charger) or CDP (Host/HUB + * chargers). + * + * REVISIT: The method is defined in Battery Charging Specification and is + * applicable to any ULPI transceiver. Nothing isp170x specific here. + */ +static inline int isp1704_charger_type(struct isp1704_charger *isp) +{ + u8 reg; + u8 func_ctrl; + u8 otg_ctrl; + int type = POWER_SUPPLY_TYPE_USB_DCP; + + func_ctrl = otg_io_read(isp->otg, ULPI_FUNC_CTRL); + otg_ctrl = otg_io_read(isp->otg, ULPI_OTG_CTRL); + + /* disable pulldowns */ + reg = ULPI_OTG_CTRL_DM_PULLDOWN | ULPI_OTG_CTRL_DP_PULLDOWN; + otg_io_write(isp->otg, ULPI_CLR(ULPI_OTG_CTRL), reg); + + /* full speed */ + otg_io_write(isp->otg, ULPI_CLR(ULPI_FUNC_CTRL), + ULPI_FUNC_CTRL_XCVRSEL_MASK); + otg_io_write(isp->otg, ULPI_SET(ULPI_FUNC_CTRL), + ULPI_FUNC_CTRL_FULL_SPEED); + + /* Enable strong pull-up on DP (1.5K) and reset */ + reg = ULPI_FUNC_CTRL_TERMSELECT | ULPI_FUNC_CTRL_RESET; + otg_io_write(isp->otg, ULPI_SET(ULPI_FUNC_CTRL), reg); + usleep_range(1000, 2000); + + reg = otg_io_read(isp->otg, ULPI_DEBUG); + if ((reg & 3) != 3) + type = POWER_SUPPLY_TYPE_USB_CDP; + + /* recover original state */ + otg_io_write(isp->otg, ULPI_FUNC_CTRL, func_ctrl); + otg_io_write(isp->otg, ULPI_OTG_CTRL, otg_ctrl); + + return type; +} + +/* + * ISP1704 detects PS/2 adapters as charger. To make sure the detected charger + * is actually a dedicated charger, the following steps need to be taken. + */ +static inline int isp1704_charger_verify(struct isp1704_charger *isp) +{ + int ret = 0; + u8 r; + + /* Reset the transceiver */ + r = otg_io_read(isp->otg, ULPI_FUNC_CTRL); + r |= ULPI_FUNC_CTRL_RESET; + otg_io_write(isp->otg, ULPI_FUNC_CTRL, r); + usleep_range(1000, 2000); + + /* Set normal mode */ + r &= ~(ULPI_FUNC_CTRL_RESET | ULPI_FUNC_CTRL_OPMODE_MASK); + otg_io_write(isp->otg, ULPI_FUNC_CTRL, r); + + /* Clear the DP and DM pull-down bits */ + r = ULPI_OTG_CTRL_DP_PULLDOWN | ULPI_OTG_CTRL_DM_PULLDOWN; + otg_io_write(isp->otg, ULPI_CLR(ULPI_OTG_CTRL), r); + + /* Enable strong pull-up on DP (1.5K) and reset */ + r = ULPI_FUNC_CTRL_TERMSELECT | ULPI_FUNC_CTRL_RESET; + otg_io_write(isp->otg, ULPI_SET(ULPI_FUNC_CTRL), r); + usleep_range(1000, 2000); + + /* Read the line state */ + if (!otg_io_read(isp->otg, ULPI_DEBUG)) { + /* Disable strong pull-up on DP (1.5K) */ + otg_io_write(isp->otg, ULPI_CLR(ULPI_FUNC_CTRL), + ULPI_FUNC_CTRL_TERMSELECT); + return 1; + } + + /* Is it a charger or PS/2 connection */ + + /* Enable weak pull-up resistor on DP */ + otg_io_write(isp->otg, ULPI_SET(ISP1704_PWR_CTRL), + ISP1704_PWR_CTRL_DP_WKPU_EN); + + /* Disable strong pull-up on DP (1.5K) */ + otg_io_write(isp->otg, ULPI_CLR(ULPI_FUNC_CTRL), + ULPI_FUNC_CTRL_TERMSELECT); + + /* Enable weak pull-down resistor on DM */ + otg_io_write(isp->otg, ULPI_SET(ULPI_OTG_CTRL), + ULPI_OTG_CTRL_DM_PULLDOWN); + + /* It's a charger if the line states are clear */ + if (!(otg_io_read(isp->otg, ULPI_DEBUG))) + ret = 1; + + /* Disable weak pull-up resistor on DP */ + otg_io_write(isp->otg, ULPI_CLR(ISP1704_PWR_CTRL), + ISP1704_PWR_CTRL_DP_WKPU_EN); + + return ret; +} + +static inline int isp1704_charger_detect(struct isp1704_charger *isp) +{ + unsigned long timeout; + u8 pwr_ctrl; + int ret = 0; + + pwr_ctrl = otg_io_read(isp->otg, ISP1704_PWR_CTRL); + + /* set SW control bit in PWR_CTRL register */ + otg_io_write(isp->otg, ISP1704_PWR_CTRL, + ISP1704_PWR_CTRL_SWCTRL); + + /* enable manual charger detection */ + otg_io_write(isp->otg, ULPI_SET(ISP1704_PWR_CTRL), + ISP1704_PWR_CTRL_SWCTRL + | ISP1704_PWR_CTRL_DPVSRC_EN); + usleep_range(1000, 2000); + + timeout = jiffies + msecs_to_jiffies(300); + do { + /* Check if there is a charger */ + if (otg_io_read(isp->otg, ISP1704_PWR_CTRL) + & ISP1704_PWR_CTRL_VDAT_DET) { + ret = isp1704_charger_verify(isp); + break; + } + } while (!time_after(jiffies, timeout) && isp->online); + + /* recover original state */ + otg_io_write(isp->otg, ISP1704_PWR_CTRL, pwr_ctrl); + + return ret; +} + +static void isp1704_charger_work(struct work_struct *data) +{ + int detect; + unsigned long event; + unsigned power; + struct isp1704_charger *isp = + container_of(data, struct isp1704_charger, work); + static DEFINE_MUTEX(lock); + + event = isp->event; + power = isp->max_power; + + mutex_lock(&lock); + + if (event != USB_EVENT_NONE) + isp1704_charger_set_power(isp, 1); + + switch (event) { + case USB_EVENT_VBUS: + isp->online = true; + + /* detect charger */ + detect = isp1704_charger_detect(isp); + + if (detect) { + isp->present = detect; + isp->psy.type = isp1704_charger_type(isp); + } + + switch (isp->psy.type) { + case POWER_SUPPLY_TYPE_USB_DCP: + isp->current_max = 1800; + break; + case POWER_SUPPLY_TYPE_USB_CDP: + /* + * Only 500mA here or high speed chirp + * handshaking may break + */ + isp->current_max = 500; + /* FALLTHROUGH */ + case POWER_SUPPLY_TYPE_USB: + default: + /* enable data pullups */ + if (isp->otg->gadget) + usb_gadget_connect(isp->otg->gadget); + } + break; + case USB_EVENT_NONE: + isp->online = false; + isp->current_max = 0; + isp->present = 0; + isp->current_max = 0; + isp->psy.type = POWER_SUPPLY_TYPE_USB; + + /* + * Disable data pullups. We need to prevent the controller from + * enumerating. + * + * FIXME: This is here to allow charger detection with Host/HUB + * chargers. The pullups may be enabled elsewhere, so this can + * not be the final solution. + */ + if (isp->otg->gadget) + usb_gadget_disconnect(isp->otg->gadget); + + isp1704_charger_set_power(isp, 0); + break; + case USB_EVENT_ENUMERATED: + if (isp->present) + isp->current_max = 1800; + else + isp->current_max = power; + break; + default: + goto out; + } + + power_supply_changed(&isp->psy); +out: + mutex_unlock(&lock); +} + +static int isp1704_notifier_call(struct notifier_block *nb, + unsigned long event, void *power) +{ + struct isp1704_charger *isp = + container_of(nb, struct isp1704_charger, nb); + + isp->event = event; + + if (power) + isp->max_power = *((unsigned *)power); + + schedule_work(&isp->work); + + return NOTIFY_OK; +} + +static int isp1704_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct isp1704_charger *isp = + container_of(psy, struct isp1704_charger, psy); + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + val->intval = isp->present; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = isp->online; + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + val->intval = isp->current_max; + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = isp->model; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = "NXP"; + break; + default: + return -EINVAL; + } + return 0; +} + +static enum power_supply_property power_props[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static inline int isp1704_test_ulpi(struct isp1704_charger *isp) +{ + int vendor; + int product; + int i; + int ret = -ENODEV; + + /* Test ULPI interface */ + ret = otg_io_write(isp->otg, ULPI_SCRATCH, 0xaa); + if (ret < 0) + return ret; + + ret = otg_io_read(isp->otg, ULPI_SCRATCH); + if (ret < 0) + return ret; + + if (ret != 0xaa) + return -ENODEV; + + /* Verify the product and vendor id matches */ + vendor = otg_io_read(isp->otg, ULPI_VENDOR_ID_LOW); + vendor |= otg_io_read(isp->otg, ULPI_VENDOR_ID_HIGH) << 8; + if (vendor != NXP_VENDOR_ID) + return -ENODEV; + + product = otg_io_read(isp->otg, ULPI_PRODUCT_ID_LOW); + product |= otg_io_read(isp->otg, ULPI_PRODUCT_ID_HIGH) << 8; + + for (i = 0; i < ARRAY_SIZE(isp170x_id); i++) { + if (product == isp170x_id[i]) { + sprintf(isp->model, "isp%x", product); + return product; + } + } + + dev_err(isp->dev, "product id %x not matching known ids", product); + + return -ENODEV; +} + +static int __devinit isp1704_charger_probe(struct platform_device *pdev) +{ + struct isp1704_charger *isp; + int ret = -ENODEV; + + isp = kzalloc(sizeof *isp, GFP_KERNEL); + if (!isp) + return -ENOMEM; + + isp->otg = otg_get_transceiver(); + if (!isp->otg) + goto fail0; + + isp->dev = &pdev->dev; + platform_set_drvdata(pdev, isp); + + isp1704_charger_set_power(isp, 1); + + ret = isp1704_test_ulpi(isp); + if (ret < 0) + goto fail1; + + isp->psy.name = "isp1704"; + isp->psy.type = POWER_SUPPLY_TYPE_USB; + isp->psy.properties = power_props; + isp->psy.num_properties = ARRAY_SIZE(power_props); + isp->psy.get_property = isp1704_charger_get_property; + + ret = power_supply_register(isp->dev, &isp->psy); + if (ret) + goto fail1; + + /* + * REVISIT: using work in order to allow the otg notifications to be + * made atomically in the future. + */ + INIT_WORK(&isp->work, isp1704_charger_work); + + isp->nb.notifier_call = isp1704_notifier_call; + + ret = otg_register_notifier(isp->otg, &isp->nb); + if (ret) + goto fail2; + + dev_info(isp->dev, "registered with product id %s\n", isp->model); + + /* + * Taking over the D+ pullup. + * + * FIXME: The device will be disconnected if it was already + * enumerated. The charger driver should be always loaded before any + * gadget is loaded. + */ + if (isp->otg->gadget) + usb_gadget_disconnect(isp->otg->gadget); + + /* Detect charger if VBUS is valid (the cable was already plugged). */ + ret = otg_io_read(isp->otg, ULPI_USB_INT_STS); + isp1704_charger_set_power(isp, 0); + if ((ret & ULPI_INT_VBUS_VALID) && !isp->otg->default_a) { + isp->event = USB_EVENT_VBUS; + schedule_work(&isp->work); + } + + return 0; +fail2: + power_supply_unregister(&isp->psy); +fail1: + otg_put_transceiver(isp->otg); +fail0: + kfree(isp); + + dev_err(&pdev->dev, "failed to register isp1704 with error %d\n", ret); + + return ret; +} + +static int __devexit isp1704_charger_remove(struct platform_device *pdev) +{ + struct isp1704_charger *isp = platform_get_drvdata(pdev); + + otg_unregister_notifier(isp->otg, &isp->nb); + power_supply_unregister(&isp->psy); + otg_put_transceiver(isp->otg); + isp1704_charger_set_power(isp, 0); + kfree(isp); + + return 0; +} + +static struct platform_driver isp1704_charger_driver = { + .driver = { + .name = "isp1704_charger", + }, + .probe = isp1704_charger_probe, + .remove = __devexit_p(isp1704_charger_remove), +}; + +static int __init isp1704_charger_init(void) +{ + return platform_driver_register(&isp1704_charger_driver); +} +module_init(isp1704_charger_init); + +static void __exit isp1704_charger_exit(void) +{ + platform_driver_unregister(&isp1704_charger_driver); +} +module_exit(isp1704_charger_exit); + +MODULE_ALIAS("platform:isp1704_charger"); +MODULE_AUTHOR("Nokia Corporation"); +MODULE_DESCRIPTION("ISP170x USB Charger driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/jz4740-battery.c b/drivers/power/jz4740-battery.c new file mode 100644 index 00000000..763f894e --- /dev/null +++ b/drivers/power/jz4740-battery.c @@ -0,0 +1,459 @@ +/* + * Battery measurement code for Ingenic JZ SOC. + * + * Copyright (C) 2009 Jiejing Zhang + * Copyright (C) 2010, Lars-Peter Clausen + * + * based on tosa_battery.c + * + * Copyright (C) 2008 Marek Vasut +* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +struct jz_battery { + struct jz_battery_platform_data *pdata; + struct platform_device *pdev; + + struct resource *mem; + void __iomem *base; + + int irq; + int charge_irq; + + const struct mfd_cell *cell; + + int status; + long voltage; + + struct completion read_completion; + + struct power_supply battery; + struct delayed_work work; + + struct mutex lock; +}; + +static inline struct jz_battery *psy_to_jz_battery(struct power_supply *psy) +{ + return container_of(psy, struct jz_battery, battery); +} + +static irqreturn_t jz_battery_irq_handler(int irq, void *devid) +{ + struct jz_battery *battery = devid; + + complete(&battery->read_completion); + return IRQ_HANDLED; +} + +static long jz_battery_read_voltage(struct jz_battery *battery) +{ + unsigned long t; + unsigned long val; + long voltage; + + mutex_lock(&battery->lock); + + INIT_COMPLETION(battery->read_completion); + + enable_irq(battery->irq); + battery->cell->enable(battery->pdev); + + t = wait_for_completion_interruptible_timeout(&battery->read_completion, + HZ); + + if (t > 0) { + val = readw(battery->base) & 0xfff; + + if (battery->pdata->info.voltage_max_design <= 2500000) + val = (val * 78125UL) >> 7UL; + else + val = ((val * 924375UL) >> 9UL) + 33000; + voltage = (long)val; + } else { + voltage = t ? t : -ETIMEDOUT; + } + + battery->cell->disable(battery->pdev); + disable_irq(battery->irq); + + mutex_unlock(&battery->lock); + + return voltage; +} + +static int jz_battery_get_capacity(struct power_supply *psy) +{ + struct jz_battery *jz_battery = psy_to_jz_battery(psy); + struct power_supply_info *info = &jz_battery->pdata->info; + long voltage; + int ret; + int voltage_span; + + voltage = jz_battery_read_voltage(jz_battery); + + if (voltage < 0) + return voltage; + + voltage_span = info->voltage_max_design - info->voltage_min_design; + ret = ((voltage - info->voltage_min_design) * 100) / voltage_span; + + if (ret > 100) + ret = 100; + else if (ret < 0) + ret = 0; + + return ret; +} + +static int jz_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, union power_supply_propval *val) +{ + struct jz_battery *jz_battery = psy_to_jz_battery(psy); + struct power_supply_info *info = &jz_battery->pdata->info; + long voltage; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = jz_battery->status; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = jz_battery->pdata->info.technology; + break; + case POWER_SUPPLY_PROP_HEALTH: + voltage = jz_battery_read_voltage(jz_battery); + if (voltage < info->voltage_min_design) + val->intval = POWER_SUPPLY_HEALTH_DEAD; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = jz_battery_get_capacity(psy); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = jz_battery_read_voltage(jz_battery); + if (val->intval < 0) + return val->intval; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = info->voltage_max_design; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = info->voltage_min_design; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 1; + break; + default: + return -EINVAL; + } + return 0; +} + +static void jz_battery_external_power_changed(struct power_supply *psy) +{ + struct jz_battery *jz_battery = psy_to_jz_battery(psy); + + cancel_delayed_work(&jz_battery->work); + schedule_delayed_work(&jz_battery->work, 0); +} + +static irqreturn_t jz_battery_charge_irq(int irq, void *data) +{ + struct jz_battery *jz_battery = data; + + cancel_delayed_work(&jz_battery->work); + schedule_delayed_work(&jz_battery->work, 0); + + return IRQ_HANDLED; +} + +static void jz_battery_update(struct jz_battery *jz_battery) +{ + int status; + long voltage; + bool has_changed = false; + int is_charging; + + if (gpio_is_valid(jz_battery->pdata->gpio_charge)) { + is_charging = gpio_get_value(jz_battery->pdata->gpio_charge); + is_charging ^= jz_battery->pdata->gpio_charge_active_low; + if (is_charging) + status = POWER_SUPPLY_STATUS_CHARGING; + else + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + + if (status != jz_battery->status) { + jz_battery->status = status; + has_changed = true; + } + } + + voltage = jz_battery_read_voltage(jz_battery); + if (abs(voltage - jz_battery->voltage) < 50000) { + jz_battery->voltage = voltage; + has_changed = true; + } + + if (has_changed) + power_supply_changed(&jz_battery->battery); +} + +static enum power_supply_property jz_battery_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_PRESENT, +}; + +static void jz_battery_work(struct work_struct *work) +{ + /* Too small interval will increase system workload */ + const int interval = HZ * 30; + struct jz_battery *jz_battery = container_of(work, struct jz_battery, + work.work); + + jz_battery_update(jz_battery); + schedule_delayed_work(&jz_battery->work, interval); +} + +static int __devinit jz_battery_probe(struct platform_device *pdev) +{ + int ret = 0; + struct jz_battery_platform_data *pdata = pdev->dev.parent->platform_data; + struct jz_battery *jz_battery; + struct power_supply *battery; + + if (!pdata) { + dev_err(&pdev->dev, "No platform_data supplied\n"); + return -ENXIO; + } + + jz_battery = kzalloc(sizeof(*jz_battery), GFP_KERNEL); + if (!jz_battery) { + dev_err(&pdev->dev, "Failed to allocate driver structure\n"); + return -ENOMEM; + } + + jz_battery->cell = mfd_get_cell(pdev); + + jz_battery->irq = platform_get_irq(pdev, 0); + if (jz_battery->irq < 0) { + ret = jz_battery->irq; + dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret); + goto err_free; + } + + jz_battery->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!jz_battery->mem) { + ret = -ENOENT; + dev_err(&pdev->dev, "Failed to get platform mmio resource\n"); + goto err_free; + } + + jz_battery->mem = request_mem_region(jz_battery->mem->start, + resource_size(jz_battery->mem), pdev->name); + if (!jz_battery->mem) { + ret = -EBUSY; + dev_err(&pdev->dev, "Failed to request mmio memory region\n"); + goto err_free; + } + + jz_battery->base = ioremap_nocache(jz_battery->mem->start, + resource_size(jz_battery->mem)); + if (!jz_battery->base) { + ret = -EBUSY; + dev_err(&pdev->dev, "Failed to ioremap mmio memory\n"); + goto err_release_mem_region; + } + + battery = &jz_battery->battery; + battery->name = pdata->info.name; + battery->type = POWER_SUPPLY_TYPE_BATTERY; + battery->properties = jz_battery_properties; + battery->num_properties = ARRAY_SIZE(jz_battery_properties); + battery->get_property = jz_battery_get_property; + battery->external_power_changed = jz_battery_external_power_changed; + battery->use_for_apm = 1; + + jz_battery->pdata = pdata; + jz_battery->pdev = pdev; + + init_completion(&jz_battery->read_completion); + mutex_init(&jz_battery->lock); + + INIT_DELAYED_WORK(&jz_battery->work, jz_battery_work); + + ret = request_irq(jz_battery->irq, jz_battery_irq_handler, 0, pdev->name, + jz_battery); + if (ret) { + dev_err(&pdev->dev, "Failed to request irq %d\n", ret); + goto err_iounmap; + } + disable_irq(jz_battery->irq); + + if (gpio_is_valid(pdata->gpio_charge)) { + ret = gpio_request(pdata->gpio_charge, dev_name(&pdev->dev)); + if (ret) { + dev_err(&pdev->dev, "charger state gpio request failed.\n"); + goto err_free_irq; + } + ret = gpio_direction_input(pdata->gpio_charge); + if (ret) { + dev_err(&pdev->dev, "charger state gpio set direction failed.\n"); + goto err_free_gpio; + } + + jz_battery->charge_irq = gpio_to_irq(pdata->gpio_charge); + + if (jz_battery->charge_irq >= 0) { + ret = request_irq(jz_battery->charge_irq, + jz_battery_charge_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + dev_name(&pdev->dev), jz_battery); + if (ret) { + dev_err(&pdev->dev, "Failed to request charge irq: %d\n", ret); + goto err_free_gpio; + } + } + } else { + jz_battery->charge_irq = -1; + } + + if (jz_battery->pdata->info.voltage_max_design <= 2500000) + jz4740_adc_set_config(pdev->dev.parent, JZ_ADC_CONFIG_BAT_MB, + JZ_ADC_CONFIG_BAT_MB); + else + jz4740_adc_set_config(pdev->dev.parent, JZ_ADC_CONFIG_BAT_MB, 0); + + ret = power_supply_register(&pdev->dev, &jz_battery->battery); + if (ret) { + dev_err(&pdev->dev, "power supply battery register failed.\n"); + goto err_free_charge_irq; + } + + platform_set_drvdata(pdev, jz_battery); + schedule_delayed_work(&jz_battery->work, 0); + + return 0; + +err_free_charge_irq: + if (jz_battery->charge_irq >= 0) + free_irq(jz_battery->charge_irq, jz_battery); +err_free_gpio: + if (gpio_is_valid(pdata->gpio_charge)) + gpio_free(jz_battery->pdata->gpio_charge); +err_free_irq: + free_irq(jz_battery->irq, jz_battery); +err_iounmap: + platform_set_drvdata(pdev, NULL); + iounmap(jz_battery->base); +err_release_mem_region: + release_mem_region(jz_battery->mem->start, resource_size(jz_battery->mem)); +err_free: + kfree(jz_battery); + return ret; +} + +static int __devexit jz_battery_remove(struct platform_device *pdev) +{ + struct jz_battery *jz_battery = platform_get_drvdata(pdev); + + cancel_delayed_work_sync(&jz_battery->work); + + if (gpio_is_valid(jz_battery->pdata->gpio_charge)) { + if (jz_battery->charge_irq >= 0) + free_irq(jz_battery->charge_irq, jz_battery); + gpio_free(jz_battery->pdata->gpio_charge); + } + + power_supply_unregister(&jz_battery->battery); + + free_irq(jz_battery->irq, jz_battery); + + iounmap(jz_battery->base); + release_mem_region(jz_battery->mem->start, resource_size(jz_battery->mem)); + kfree(jz_battery); + + return 0; +} + +#ifdef CONFIG_PM +static int jz_battery_suspend(struct device *dev) +{ + struct jz_battery *jz_battery = dev_get_drvdata(dev); + + cancel_delayed_work_sync(&jz_battery->work); + jz_battery->status = POWER_SUPPLY_STATUS_UNKNOWN; + + return 0; +} + +static int jz_battery_resume(struct device *dev) +{ + struct jz_battery *jz_battery = dev_get_drvdata(dev); + + schedule_delayed_work(&jz_battery->work, 0); + + return 0; +} + +static const struct dev_pm_ops jz_battery_pm_ops = { + .suspend = jz_battery_suspend, + .resume = jz_battery_resume, +}; + +#define JZ_BATTERY_PM_OPS (&jz_battery_pm_ops) +#else +#define JZ_BATTERY_PM_OPS NULL +#endif + +static struct platform_driver jz_battery_driver = { + .probe = jz_battery_probe, + .remove = __devexit_p(jz_battery_remove), + .driver = { + .name = "jz4740-battery", + .owner = THIS_MODULE, + .pm = JZ_BATTERY_PM_OPS, + }, +}; + +static int __init jz_battery_init(void) +{ + return platform_driver_register(&jz_battery_driver); +} +module_init(jz_battery_init); + +static void __exit jz_battery_exit(void) +{ + platform_driver_unregister(&jz_battery_driver); +} +module_exit(jz_battery_exit); + +MODULE_ALIAS("platform:jz4740-battery"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("JZ4740 SoC battery driver"); diff --git a/drivers/power/max17040_battery.c b/drivers/power/max17040_battery.c new file mode 100644 index 00000000..2f2f9a6f --- /dev/null +++ b/drivers/power/max17040_battery.c @@ -0,0 +1,308 @@ +/* + * max17040_battery.c + * fuel-gauge systems for lithium-ion (Li+) batteries + * + * Copyright (C) 2009 Samsung Electronics + * Minkyu Kang + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX17040_VCELL_MSB 0x02 +#define MAX17040_VCELL_LSB 0x03 +#define MAX17040_SOC_MSB 0x04 +#define MAX17040_SOC_LSB 0x05 +#define MAX17040_MODE_MSB 0x06 +#define MAX17040_MODE_LSB 0x07 +#define MAX17040_VER_MSB 0x08 +#define MAX17040_VER_LSB 0x09 +#define MAX17040_RCOMP_MSB 0x0C +#define MAX17040_RCOMP_LSB 0x0D +#define MAX17040_CMD_MSB 0xFE +#define MAX17040_CMD_LSB 0xFF + +#define MAX17040_DELAY 1000 +#define MAX17040_BATTERY_FULL 95 + +struct max17040_chip { + struct i2c_client *client; + struct delayed_work work; + struct power_supply battery; + struct max17040_platform_data *pdata; + + /* State Of Connect */ + int online; + /* battery voltage */ + int vcell; + /* battery capacity */ + int soc; + /* State Of Charge */ + int status; +}; + +static int max17040_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max17040_chip *chip = container_of(psy, + struct max17040_chip, battery); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = chip->status; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = chip->online; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = chip->vcell; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = chip->soc; + break; + default: + return -EINVAL; + } + return 0; +} + +static int max17040_write_reg(struct i2c_client *client, int reg, u8 value) +{ + int ret; + + ret = i2c_smbus_write_byte_data(client, reg, value); + + if (ret < 0) + dev_err(&client->dev, "%s: err %d\n", __func__, ret); + + return ret; +} + +static int max17040_read_reg(struct i2c_client *client, int reg) +{ + int ret; + + ret = i2c_smbus_read_byte_data(client, reg); + + if (ret < 0) + dev_err(&client->dev, "%s: err %d\n", __func__, ret); + + return ret; +} + +static void max17040_reset(struct i2c_client *client) +{ + max17040_write_reg(client, MAX17040_CMD_MSB, 0x54); + max17040_write_reg(client, MAX17040_CMD_LSB, 0x00); +} + +static void max17040_get_vcell(struct i2c_client *client) +{ + struct max17040_chip *chip = i2c_get_clientdata(client); + u8 msb; + u8 lsb; + + msb = max17040_read_reg(client, MAX17040_VCELL_MSB); + lsb = max17040_read_reg(client, MAX17040_VCELL_LSB); + + chip->vcell = (msb << 4) + (lsb >> 4); +} + +static void max17040_get_soc(struct i2c_client *client) +{ + struct max17040_chip *chip = i2c_get_clientdata(client); + u8 msb; + u8 lsb; + + msb = max17040_read_reg(client, MAX17040_SOC_MSB); + lsb = max17040_read_reg(client, MAX17040_SOC_LSB); + + chip->soc = msb; +} + +static void max17040_get_version(struct i2c_client *client) +{ + u8 msb; + u8 lsb; + + msb = max17040_read_reg(client, MAX17040_VER_MSB); + lsb = max17040_read_reg(client, MAX17040_VER_LSB); + + dev_info(&client->dev, "MAX17040 Fuel-Gauge Ver %d%d\n", msb, lsb); +} + +static void max17040_get_online(struct i2c_client *client) +{ + struct max17040_chip *chip = i2c_get_clientdata(client); + + if (chip->pdata->battery_online) + chip->online = chip->pdata->battery_online(); + else + chip->online = 1; +} + +static void max17040_get_status(struct i2c_client *client) +{ + struct max17040_chip *chip = i2c_get_clientdata(client); + + if (!chip->pdata->charger_online || !chip->pdata->charger_enable) { + chip->status = POWER_SUPPLY_STATUS_UNKNOWN; + return; + } + + if (chip->pdata->charger_online()) { + if (chip->pdata->charger_enable()) + chip->status = POWER_SUPPLY_STATUS_CHARGING; + else + chip->status = POWER_SUPPLY_STATUS_NOT_CHARGING; + } else { + chip->status = POWER_SUPPLY_STATUS_DISCHARGING; + } + + if (chip->soc > MAX17040_BATTERY_FULL) + chip->status = POWER_SUPPLY_STATUS_FULL; +} + +static void max17040_work(struct work_struct *work) +{ + struct max17040_chip *chip; + + chip = container_of(work, struct max17040_chip, work.work); + + max17040_get_vcell(chip->client); + max17040_get_soc(chip->client); + max17040_get_online(chip->client); + max17040_get_status(chip->client); + + schedule_delayed_work(&chip->work, MAX17040_DELAY); +} + +static enum power_supply_property max17040_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, +}; + +static int __devinit max17040_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct max17040_chip *chip; + int ret; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + return -EIO; + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->client = client; + chip->pdata = client->dev.platform_data; + + i2c_set_clientdata(client, chip); + + chip->battery.name = "battery"; + chip->battery.type = POWER_SUPPLY_TYPE_BATTERY; + chip->battery.get_property = max17040_get_property; + chip->battery.properties = max17040_battery_props; + chip->battery.num_properties = ARRAY_SIZE(max17040_battery_props); + + ret = power_supply_register(&client->dev, &chip->battery); + if (ret) { + dev_err(&client->dev, "failed: power supply register\n"); + kfree(chip); + return ret; + } + + max17040_reset(client); + max17040_get_version(client); + + INIT_DELAYED_WORK_DEFERRABLE(&chip->work, max17040_work); + schedule_delayed_work(&chip->work, MAX17040_DELAY); + + return 0; +} + +static int __devexit max17040_remove(struct i2c_client *client) +{ + struct max17040_chip *chip = i2c_get_clientdata(client); + + power_supply_unregister(&chip->battery); + cancel_delayed_work(&chip->work); + kfree(chip); + return 0; +} + +#ifdef CONFIG_PM + +static int max17040_suspend(struct i2c_client *client, + pm_message_t state) +{ + struct max17040_chip *chip = i2c_get_clientdata(client); + + cancel_delayed_work(&chip->work); + return 0; +} + +static int max17040_resume(struct i2c_client *client) +{ + struct max17040_chip *chip = i2c_get_clientdata(client); + + schedule_delayed_work(&chip->work, MAX17040_DELAY); + return 0; +} + +#else + +#define max17040_suspend NULL +#define max17040_resume NULL + +#endif /* CONFIG_PM */ + +static const struct i2c_device_id max17040_id[] = { + { "max17040", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max17040_id); + +static struct i2c_driver max17040_i2c_driver = { + .driver = { + .name = "max17040", + }, + .probe = max17040_probe, + .remove = __devexit_p(max17040_remove), + .suspend = max17040_suspend, + .resume = max17040_resume, + .id_table = max17040_id, +}; + +static int __init max17040_init(void) +{ + return i2c_add_driver(&max17040_i2c_driver); +} +module_init(max17040_init); + +static void __exit max17040_exit(void) +{ + i2c_del_driver(&max17040_i2c_driver); +} +module_exit(max17040_exit); + +MODULE_AUTHOR("Minkyu Kang "); +MODULE_DESCRIPTION("MAX17040 Fuel Gauge"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/max17042_battery.c b/drivers/power/max17042_battery.c new file mode 100644 index 00000000..c5c88051 --- /dev/null +++ b/drivers/power/max17042_battery.c @@ -0,0 +1,239 @@ +/* + * Fuel gauge driver for Maxim 17042 / 8966 / 8997 + * Note that Maxim 8966 and 8997 are mfd and this is its subdevice. + * + * Copyright (C) 2011 Samsung Electronics + * MyungJoo Ham + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * This driver is based on max17040_battery.c + */ + +#include +#include +#include +#include +#include +#include + +enum max17042_register { + MAX17042_STATUS = 0x00, + MAX17042_VALRT_Th = 0x01, + MAX17042_TALRT_Th = 0x02, + MAX17042_SALRT_Th = 0x03, + MAX17042_AtRate = 0x04, + MAX17042_RepCap = 0x05, + MAX17042_RepSOC = 0x06, + MAX17042_Age = 0x07, + MAX17042_TEMP = 0x08, + MAX17042_VCELL = 0x09, + MAX17042_Current = 0x0A, + MAX17042_AvgCurrent = 0x0B, + MAX17042_Qresidual = 0x0C, + MAX17042_SOC = 0x0D, + MAX17042_AvSOC = 0x0E, + MAX17042_RemCap = 0x0F, + MAX17402_FullCAP = 0x10, + MAX17042_TTE = 0x11, + MAX17042_V_empty = 0x12, + + MAX17042_RSLOW = 0x14, + + MAX17042_AvgTA = 0x16, + MAX17042_Cycles = 0x17, + MAX17042_DesignCap = 0x18, + MAX17042_AvgVCELL = 0x19, + MAX17042_MinMaxTemp = 0x1A, + MAX17042_MinMaxVolt = 0x1B, + MAX17042_MinMaxCurr = 0x1C, + MAX17042_CONFIG = 0x1D, + MAX17042_ICHGTerm = 0x1E, + MAX17042_AvCap = 0x1F, + MAX17042_ManName = 0x20, + MAX17042_DevName = 0x21, + MAX17042_DevChem = 0x22, + + MAX17042_TempNom = 0x24, + MAX17042_TempCold = 0x25, + MAX17042_TempHot = 0x26, + MAX17042_AIN = 0x27, + MAX17042_LearnCFG = 0x28, + MAX17042_SHFTCFG = 0x29, + MAX17042_RelaxCFG = 0x2A, + MAX17042_MiscCFG = 0x2B, + MAX17042_TGAIN = 0x2C, + MAx17042_TOFF = 0x2D, + MAX17042_CGAIN = 0x2E, + MAX17042_COFF = 0x2F, + + MAX17042_Q_empty = 0x33, + MAX17042_T_empty = 0x34, + + MAX17042_RCOMP0 = 0x38, + MAX17042_TempCo = 0x39, + MAX17042_Rx = 0x3A, + MAX17042_T_empty0 = 0x3B, + MAX17042_TaskPeriod = 0x3C, + MAX17042_FSTAT = 0x3D, + + MAX17042_SHDNTIMER = 0x3F, + + MAX17042_VFRemCap = 0x4A, + + MAX17042_QH = 0x4D, + MAX17042_QL = 0x4E, +}; + +struct max17042_chip { + struct i2c_client *client; + struct power_supply battery; + struct max17042_platform_data *pdata; +}; + +static int max17042_write_reg(struct i2c_client *client, u8 reg, u16 value) +{ + int ret = i2c_smbus_write_word_data(client, reg, value); + + if (ret < 0) + dev_err(&client->dev, "%s: err %d\n", __func__, ret); + + return ret; +} + +static int max17042_read_reg(struct i2c_client *client, u8 reg) +{ + int ret = i2c_smbus_read_word_data(client, reg); + + if (ret < 0) + dev_err(&client->dev, "%s: err %d\n", __func__, ret); + + return ret; +} + +static enum power_supply_property max17042_battery_props[] = { + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_CAPACITY, +}; + +static int max17042_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max17042_chip *chip = container_of(psy, + struct max17042_chip, battery); + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = max17042_read_reg(chip->client, + MAX17042_VCELL) * 83; /* 1000 / 12 = 83 */ + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + val->intval = max17042_read_reg(chip->client, + MAX17042_AvgVCELL) * 83; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = max17042_read_reg(chip->client, + MAX17042_SOC) / 256; + break; + default: + return -EINVAL; + } + return 0; +} + +static int __devinit max17042_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct max17042_chip *chip; + int ret; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) + return -EIO; + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->client = client; + chip->pdata = client->dev.platform_data; + + i2c_set_clientdata(client, chip); + + chip->battery.name = "max17042_battery"; + chip->battery.type = POWER_SUPPLY_TYPE_BATTERY; + chip->battery.get_property = max17042_get_property; + chip->battery.properties = max17042_battery_props; + chip->battery.num_properties = ARRAY_SIZE(max17042_battery_props); + + ret = power_supply_register(&client->dev, &chip->battery); + if (ret) { + dev_err(&client->dev, "failed: power supply register\n"); + i2c_set_clientdata(client, NULL); + kfree(chip); + return ret; + } + + if (!chip->pdata->enable_current_sense) { + max17042_write_reg(client, MAX17042_CGAIN, 0x0000); + max17042_write_reg(client, MAX17042_MiscCFG, 0x0003); + max17042_write_reg(client, MAX17042_LearnCFG, 0x0007); + } + + return 0; +} + +static int __devexit max17042_remove(struct i2c_client *client) +{ + struct max17042_chip *chip = i2c_get_clientdata(client); + + power_supply_unregister(&chip->battery); + i2c_set_clientdata(client, NULL); + kfree(chip); + return 0; +} + +static const struct i2c_device_id max17042_id[] = { + { "max17042", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max17042_id); + +static struct i2c_driver max17042_i2c_driver = { + .driver = { + .name = "max17042", + }, + .probe = max17042_probe, + .remove = __devexit_p(max17042_remove), + .id_table = max17042_id, +}; + +static int __init max17042_init(void) +{ + return i2c_add_driver(&max17042_i2c_driver); +} +module_init(max17042_init); + +static void __exit max17042_exit(void) +{ + i2c_del_driver(&max17042_i2c_driver); +} +module_exit(max17042_exit); + +MODULE_AUTHOR("MyungJoo Ham "); +MODULE_DESCRIPTION("MAX17042 Fuel Gauge"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/max8903_battery.c b/drivers/power/max8903_battery.c new file mode 100755 index 00000000..84b5373c --- /dev/null +++ b/drivers/power/max8903_battery.c @@ -0,0 +1,748 @@ +/* + * max8903_battery.c - Maxim 8903 USB/Adapter Charger Driver + * + * Copyright (C) 2011 Samsung Electronics + * Copyright (C) 2011-2012 Freescale Semiconductor, Inc. + * Based on max8903_charger.c + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include + + +#define BATTERY_UPDATE_INTERVAL 5 /*seconds*/ +#define LOW_VOLT_THRESHOLD 2800000 +#define HIGH_VOLT_THRESHOLD 4200000 +#define ADC_SAMPLE_COUNT 4 + +struct max8903_data { + struct max8903_pdata *pdata; + struct device *dev; + struct power_supply psy; + bool fault; + bool usb_in; + bool ta_in; + bool chg_state; + struct delayed_work work; + unsigned int interval; + unsigned short thermal_raw; + int voltage_uV; + int current_uA; + int battery_status; + int charger_online; + int charger_voltage_uV; + int real_capacity; + int percent; + int old_percent; + struct power_supply bat; + struct mutex work_lock; +}; + +typedef struct { + u32 voltage; + u32 percent; +} battery_capacity , *pbattery_capacity; + +static bool capacity_changed_flag; + +static battery_capacity chargingTable[] = { + {4148, 100}, + {4126, 99}, + {4123, 98}, + {4120, 97}, + {4105, 96}, + {4090, 96}, + {4075, 95}, + {4060, 94}, + {4045, 93}, + {4030, 92}, + {4015, 91}, + {4000, 90}, + {3900, 85}, + {3790, 80}, + {3760, 75}, + {3730, 70}, + {3700, 65}, + {3680, 60}, + {3660, 55}, + {3640, 50}, + {3600, 45}, + {3550, 40}, + {3510, 35}, + {3450, 30}, + {3310, 25}, + {3240, 20}, + {3180, 15}, + {3030, 10}, + {2820, 5}, + {2800, 0}, + {0, 0} +}; +static battery_capacity dischargingTable[] = { + {4100, 100}, + {4090, 99}, + {4080, 98}, + {4060, 97}, + {4040, 96}, + {3920, 96}, + {3900, 95}, + {3970, 94}, + {3940, 93}, + {3910, 92}, + {3890, 91}, + {3860, 90}, + {3790, 85}, + {3690, 80}, + {3660, 75}, + {3630, 70}, + {3600, 65}, + {3580, 60}, + {3560, 55}, + {3540, 50}, + {3500, 45}, + {3450, 40}, + {3410, 35}, + {3350, 30}, + {3310, 25}, + {3240, 20}, + {3180, 15}, + {3030, 10}, + {2820, 5}, + {2800, 0}, + {0, 0} +}; + +u32 calibrate_battery_capability_percent(struct max8903_data *data) +{ + u8 i; + pbattery_capacity pTable; + u32 tableSize; + if (data->battery_status == POWER_SUPPLY_STATUS_DISCHARGING) { + pTable = dischargingTable; + tableSize = sizeof(dischargingTable)/sizeof(dischargingTable[0]); + } else { + pTable = chargingTable; + tableSize = sizeof(chargingTable)/sizeof(chargingTable[0]); + } + for (i = 0; i < tableSize; i++) { + if (data->voltage_uV >= pTable[i].voltage) + return pTable[i].percent; + } + + return 0; +} + +static enum power_supply_property max8903_charger_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_HEALTH, +}; + +static enum power_supply_property max8903_battery_props[] = { + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, +}; + +extern u32 max11801_read_adc(void); + +static void max8903_charger_update_status(struct max8903_data *data) +{ + if (data->usb_in || data->ta_in) { + data->charger_online = 1; + } else { + data->charger_online = 0; + } + if (data->charger_online == 0) { + data->battery_status = POWER_SUPPLY_STATUS_DISCHARGING; + } else { + if (data->pdata->chg) { + if (gpio_get_value(data->pdata->chg) == 0) { + data->battery_status = POWER_SUPPLY_STATUS_CHARGING; + } else if ((data->usb_in || data->ta_in) && + gpio_get_value(data->pdata->chg) == 1) { + data->battery_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + } + } + } + pr_debug("chg: %d \n", gpio_get_value(data->pdata->chg)); +} +static int cmp_func(const void *_a, const void *_b) +{ + const int *a = _a, *b = _b; + + if (*a > *b) + return 1; + if (*a < *b) + return -1; + return 0; +} +u32 calibration_voltage(struct max8903_data *data) +{ + int volt[ADC_SAMPLE_COUNT]; + u32 voltage_data; + int i; + for (i = 0; i < ADC_SAMPLE_COUNT; i++) { + if (data->charger_online == 0) { + /* ADC offset when battery is discharger*/ + volt[i] = max11801_read_adc()-1880; + } else { + volt[i] = max11801_read_adc()-1960; + } + } + sort(volt, i, 4, cmp_func, NULL); + for (i = 0; i < ADC_SAMPLE_COUNT; i++) + pr_debug("volt_sorted[%2d]: %d\n", i, volt[i]); + /* get the average of second max/min of remained. */ + voltage_data = (volt[1] + volt[i - 2]) / 2; + return voltage_data; +} + +static void max8903_battery_update_status(struct max8903_data *data) +{ + static int counter; +#if 0 + data->voltage_uV = max11801_read_adc(); +#endif + mutex_lock(&data->work_lock); + data->voltage_uV = calibration_voltage(data); + data->percent = calibrate_battery_capability_percent(data); + if (data->percent != data->old_percent) { + data->old_percent = data->percent; + capacity_changed_flag = true; + } + if ((capacity_changed_flag == true) && (data->charger_online)) { + counter++; + if (counter > 2) { + counter = 0; + capacity_changed_flag = false; + power_supply_changed(&data->bat); + } + } + mutex_unlock(&data->work_lock); +} + +static int max8903_battery_get_property(struct power_supply *bat, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max8903_data *di = container_of(bat, + struct max8903_data, bat); + static unsigned long last; + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + if (di->pdata->chg) { + if (gpio_get_value(di->pdata->chg) == 0) { + val->intval = POWER_SUPPLY_STATUS_CHARGING; + } else if ((di->usb_in || di->ta_in) && gpio_get_value(di->pdata->chg) == 1) { + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + } else { + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + } + } + di->battery_status = val->intval; + return 0; + default: + break; + } + if (!last || time_after(jiffies, last + HZ / 2)) { + last = jiffies; + max8903_charger_update_status(di); + max8903_battery_update_status(di); + } + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = di->voltage_uV; + break; +#if 0 + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = di->current_uA; + break; +#endif + case POWER_SUPPLY_PROP_CHARGE_NOW: + val->intval = 0; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = HIGH_VOLT_THRESHOLD; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = LOW_VOLT_THRESHOLD; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 1; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = di->percent < 0 ? 0 : + (di->percent > 100 ? 100 : di->percent); + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = 0; + if (di->usb_in || di->ta_in) + val->intval = 1; + di->charger_online = val->intval; + break; + default: + return -EINVAL; + } + + return 0; +} +static int max8903_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max8903_data *data = container_of(psy, + struct max8903_data, psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + if (data->pdata->chg) { + if (gpio_get_value(data->pdata->chg) == 0) { + val->intval = POWER_SUPPLY_STATUS_CHARGING; + } + else if ((data->usb_in || data->ta_in) && + gpio_get_value(data->pdata->chg) == 1) { + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + } else { + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + } + } + data->battery_status = val->intval; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = 0; + if (data->usb_in || data->ta_in) + val->intval = 1; + data->charger_online = val->intval; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = POWER_SUPPLY_HEALTH_GOOD; + if (data->fault) + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + break; + default: + return -EINVAL; + } + return 0; +} + +static irqreturn_t max8903_dcin(int irq, void *_data) +{ + struct max8903_data *data = _data; + struct max8903_pdata *pdata = data->pdata; + bool ta_in; + enum power_supply_type old_type; + + ta_in = gpio_get_value(pdata->dok) ? false : true; + + if (ta_in == data->ta_in) + return IRQ_HANDLED; + + data->ta_in = ta_in; +#if 0 + /* Set Current-Limit-Mode 1:DC 0:USB */ + if (pdata->dcm) + gpio_set_value(pdata->dcm, ta_in ? 1 : 0); + /* Charger Enable / Disable (cen is negated) */ + if (pdata->cen) + gpio_set_value(pdata->cen, ta_in ? 0 : + (data->usb_in ? 0 : 1)); +#endif + pr_info("TA(DC-IN) Charger %s.\n", ta_in ? + "Connected" : "Disconnected"); + + old_type = data->psy.type; + + if (data->ta_in) + data->psy.type = POWER_SUPPLY_TYPE_MAINS; + else if (data->usb_in) + data->psy.type = POWER_SUPPLY_TYPE_USB; + else + data->psy.type = POWER_SUPPLY_TYPE_BATTERY; + + if (old_type != data->psy.type) { + power_supply_changed(&data->psy); + power_supply_changed(&data->bat); + } + return IRQ_HANDLED; +} + +static irqreturn_t max8903_usbin(int irq, void *_data) +{ + struct max8903_data *data = _data; + struct max8903_pdata *pdata = data->pdata; + bool usb_in; + enum power_supply_type old_type; + + usb_in = gpio_get_value(pdata->uok) ? false : true; + + if (usb_in == data->usb_in) + return IRQ_HANDLED; + + data->usb_in = usb_in; + +#if 0 + /* Do not touch Current-Limit-Mode */ + + /* Charger Enable / Disable (cen is negated) */ + if (pdata->cen) + gpio_set_value(pdata->cen, usb_in ? 0 : + (data->ta_in ? 0 : 1)); +#endif + pr_info("USB Charger %s.\n", usb_in ? + "Connected" : "Disconnected"); + + old_type = data->psy.type; + + if (data->ta_in) + data->psy.type = POWER_SUPPLY_TYPE_MAINS; + else if (data->usb_in) + data->psy.type = POWER_SUPPLY_TYPE_USB; + else + data->psy.type = POWER_SUPPLY_TYPE_BATTERY; + + if (old_type != data->psy.type) { + power_supply_changed(&data->psy); + power_supply_changed(&data->bat); + } + return IRQ_HANDLED; +} + +static irqreturn_t max8903_fault(int irq, void *_data) +{ + struct max8903_data *data = _data; + struct max8903_pdata *pdata = data->pdata; + bool fault; + + fault = gpio_get_value(pdata->flt) ? false : true; + + if (fault == data->fault) + return IRQ_HANDLED; + + data->fault = fault; + + if (fault) + dev_err(data->dev, "Charger suffers a fault and stops.\n"); + else + dev_err(data->dev, "Charger recovered from a fault.\n"); + return IRQ_HANDLED; +} + +static irqreturn_t max8903_chg(int irq, void *_data) +{ + struct max8903_data *data = _data; + struct max8903_pdata *pdata = data->pdata; + int chg_state; + + chg_state = gpio_get_value(pdata->chg) ? false : true; + + if (chg_state == data->chg_state) + return IRQ_HANDLED; + + data->chg_state = chg_state; +#if 0 + if (chg_state) + pr_info("Charger stop.\n "); + else + pr_info("Charger start.\n "); +#endif + return IRQ_HANDLED; +} + +static void max8903_battery_work(struct work_struct *work) +{ + struct max8903_data *data; + data = container_of(work, struct max8903_data, work.work); + data->interval = HZ * BATTERY_UPDATE_INTERVAL; + + max8903_charger_update_status(data); + max8903_battery_update_status(data); + + pr_debug("battery voltage: %4d mV\n" , data->voltage_uV); + pr_debug("charger online status: %d\n" , data->charger_online); + pr_debug("battery status : %d\n" , data->battery_status); + pr_debug("battery capacity percent: %3d\n" , data->percent); + pr_debug("data->usb_in: %x , data->ta_in: %x \n" , data->usb_in, data->ta_in); + /* reschedule for the next time */ + schedule_delayed_work(&data->work, data->interval); +} +static __devinit int max8903_probe(struct platform_device *pdev) +{ + struct max8903_data *data; + struct device *dev = &pdev->dev; + struct max8903_pdata *pdata = pdev->dev.platform_data; + int ret = 0; + int gpio = 0; + int ta_in = 0; + int usb_in = 0; + int retval; + + data = kzalloc(sizeof(struct max8903_data), GFP_KERNEL); + if (data == NULL) { + dev_err(dev, "Cannot allocate memory.\n"); + return -ENOMEM; + } + data->pdata = pdata; + data->dev = dev; + + platform_set_drvdata(pdev, data); + capacity_changed_flag = false; + data->usb_in = 0; + data->ta_in = 0; + + if (pdata->dc_valid == false && pdata->usb_valid == false) { + dev_err(dev, "No valid power sources.\n"); + printk(KERN_INFO "No valid power sources.\n"); + ret = -EINVAL; + goto err; + } + if (pdata->dc_valid) { +#if 0 + if (pdata->dok && gpio_is_valid(pdata->dok) && + pdata->dcm && gpio_is_valid(pdata->dcm)) { +#endif + if (pdata->dok && gpio_is_valid(pdata->dok)) { + gpio = pdata->dok; /* PULL_UPed Interrupt */ + ta_in = gpio_get_value(gpio) ? 0 : 1; +#if 0 + gpio = pdata->dcm; /* Output */ + gpio_set_value(gpio, ta_in); +#endif + } else if (pdata->dok && gpio_is_valid(pdata->dok) && pdata->dcm_always_high) { + ta_in = pdata->dok; /* PULL_UPed Interrupt */ + ta_in = gpio_get_value(gpio) ? 0 : 1; + } else { + dev_err(dev, "When DC is wired, DOK and DCM should" + " be wired as well." + " or set dcm always high\n"); + ret = -EINVAL; + goto err; + } + } + if (pdata->usb_valid) { + if (pdata->uok && gpio_is_valid(pdata->uok)) { + gpio = pdata->uok; + usb_in = gpio_get_value(gpio) ? 0 : 1; + } else { + dev_err(dev, "When USB is wired, UOK should be wired." + "as well.\n"); + ret = -EINVAL; + goto err; + } + } + if (pdata->chg) { + if (!gpio_is_valid(pdata->chg)) { + dev_err(dev, "Invalid pin: chg.\n"); + ret = -EINVAL; + goto err; + } + } + + if (pdata->flt) { + if (!gpio_is_valid(pdata->flt)) { + dev_err(dev, "Invalid pin: flt.\n"); + ret = -EINVAL; + goto err; + } + } + + if (pdata->usus) { + if (!gpio_is_valid(pdata->usus)) { + dev_err(dev, "Invalid pin: usus.\n"); + ret = -EINVAL; + goto err; + } + } + mutex_init(&data->work_lock); + data->fault = false; + data->ta_in = ta_in; + data->usb_in = usb_in; + data->psy.name = "max8903-ac"; + data->psy.type = (ta_in) ? POWER_SUPPLY_TYPE_MAINS : + ((usb_in) ? POWER_SUPPLY_TYPE_USB : + POWER_SUPPLY_TYPE_BATTERY); + data->psy.get_property = max8903_get_property; + data->psy.properties = max8903_charger_props; + data->psy.num_properties = ARRAY_SIZE(max8903_charger_props); + ret = power_supply_register(dev, &data->psy); + if (ret) { + dev_err(dev, "failed: power supply register.\n"); + goto err_psy; + } + data->bat.name = "max8903-charger"; + data->bat.type = POWER_SUPPLY_TYPE_BATTERY; + data->bat.properties = max8903_battery_props; + data->bat.num_properties = ARRAY_SIZE(max8903_battery_props); + data->bat.get_property = max8903_battery_get_property; + data->bat.use_for_apm = 1; + retval = power_supply_register(&pdev->dev, &data->bat); + if (retval) { + dev_err(data->dev, "failed to register battery\n"); + goto battery_failed; + } + INIT_DELAYED_WORK(&data->work, max8903_battery_work); + schedule_delayed_work(&data->work, data->interval); + if (pdata->dc_valid) { + ret = request_threaded_irq(gpio_to_irq(pdata->dok), + NULL, max8903_dcin, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, + "MAX8903 DC IN", data); + if (ret) { + dev_err(dev, "Cannot request irq %d for DC (%d)\n", + gpio_to_irq(pdata->dok), ret); + goto err_usb_irq; + } + } + + if (pdata->usb_valid) { + ret = request_threaded_irq(gpio_to_irq(pdata->uok), + NULL, max8903_usbin, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, + "MAX8903 USB IN", data); + if (ret) { + dev_err(dev, "Cannot request irq %d for USB (%d)\n", + gpio_to_irq(pdata->uok), ret); + goto err_dc_irq; + } + } + + if (pdata->flt) { + ret = request_threaded_irq(gpio_to_irq(pdata->flt), + NULL, max8903_fault, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, + "MAX8903 Fault", data); + if (ret) { + dev_err(dev, "Cannot request irq %d for Fault (%d)\n", + gpio_to_irq(pdata->flt), ret); + goto err_flt_irq; + } + } + + if (pdata->chg) { + ret = request_threaded_irq(gpio_to_irq(pdata->chg), + NULL, max8903_chg, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, + "MAX8903 Fault", data); + if (ret) { + dev_err(dev, "Cannot request irq %d for Fault (%d)\n", + gpio_to_irq(pdata->flt), ret); + goto err_chg_irq; + } + } + return 0; +err_psy: + power_supply_unregister(&data->psy); +battery_failed: + power_supply_unregister(&data->bat); +err_usb_irq: + if (pdata->usb_valid) + free_irq(gpio_to_irq(pdata->uok), data); + cancel_delayed_work(&data->work); +err_dc_irq: + if (pdata->dc_valid) + free_irq(gpio_to_irq(pdata->dok), data); + cancel_delayed_work(&data->work); +err_flt_irq: + if (pdata->usb_valid) + free_irq(gpio_to_irq(pdata->uok), data); + cancel_delayed_work(&data->work); +err_chg_irq: + if (pdata->dc_valid) + free_irq(gpio_to_irq(pdata->dok), data); + cancel_delayed_work(&data->work); +err: + kfree(data); + return ret; +} + +static __devexit int max8903_remove(struct platform_device *pdev) +{ + struct max8903_data *data = platform_get_drvdata(pdev); + if (data) { + struct max8903_pdata *pdata = data->pdata; + if (pdata->flt) + free_irq(gpio_to_irq(pdata->flt), data); + if (pdata->usb_valid) + free_irq(gpio_to_irq(pdata->uok), data); + if (pdata->dc_valid) + free_irq(gpio_to_irq(pdata->dok), data); + if (pdata->dc_valid) + free_irq(gpio_to_irq(pdata->chg), data); + cancel_delayed_work_sync(&data->work); + power_supply_unregister(&data->psy); + power_supply_unregister(&data->bat); + kfree(data); + } + return 0; +} + +static int max8903_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct max8903_data *data = platform_get_drvdata(pdev); + + cancel_delayed_work(&data->work); + return 0; +} + +static int max8903_resume(struct platform_device *pdev) +{ + struct max8903_data *data = platform_get_drvdata(pdev); + + schedule_delayed_work(&data->work, BATTERY_UPDATE_INTERVAL); + return 0; + +} + +static struct platform_driver max8903_driver = { + .probe = max8903_probe, + .remove = __devexit_p(max8903_remove), + .suspend = max8903_suspend, + .resume = max8903_resume, + .driver = { + .name = "max8903-charger", + .owner = THIS_MODULE, + }, +}; + +static int __init max8903_init(void) +{ + return platform_driver_register(&max8903_driver); +} +module_init(max8903_init); + +static void __exit max8903_exit(void) +{ + platform_driver_unregister(&max8903_driver); +} +module_exit(max8903_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MAX8903 Battery Driver"); +MODULE_ALIAS("max8903_battery"); diff --git a/drivers/power/max8903_charger.c b/drivers/power/max8903_charger.c new file mode 100644 index 00000000..fee81917 --- /dev/null +++ b/drivers/power/max8903_charger.c @@ -0,0 +1,506 @@ +/* + * max8903_charger.c - Maxim 8903 USB/Adapter Charger Driver + * + * Copyright (C) 2011 Samsung Electronics + * MyungJoo Ham + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include + +#define MAX8903_DELAY (50 * HZ) +struct max8903_data { + struct max8903_pdata *pdata; + struct device *dev; + struct power_supply psy; + struct power_supply acpsy; + bool fault; + bool usb_in; + bool ta_in; + int cap; + struct delayed_work work; +}; + +static enum power_supply_property max8903_charger_props[] = { + POWER_SUPPLY_PROP_STATUS, /* Charger status output */ + POWER_SUPPLY_PROP_HEALTH, /* Fault or OK */ + POWER_SUPPLY_PROP_CAPACITY, +}; + +static enum power_supply_property max8903_ac_props[] = { + POWER_SUPPLY_PROP_ONLINE, /* External power source */ +}; + +/* fake capacity */ +static inline void get_cap(struct max8903_data *data) +{ + data->cap = 90; + power_supply_changed(&data->psy); +} + +static int max8903_get_ac_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max8903_data *data = container_of(psy, + struct max8903_data, acpsy); + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = 0; + if (data->usb_in || data->ta_in) + val->intval = 1; + break; + default: + return -EINVAL; + } + return 0; +} + +static int max8903_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max8903_data *data = container_of(psy, + struct max8903_data, psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + if (data->pdata->chg) { + if (gpio_get_value(data->pdata->chg) == 0) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else if (data->usb_in || data->ta_in) { + if (data->cap == 100) + val->intval = POWER_SUPPLY_STATUS_FULL; + else + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + } else + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + } + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = POWER_SUPPLY_HEALTH_GOOD; + if (data->fault) + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = data->cap; /* fake capacity */ + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = 0; + if (data->usb_in || data->ta_in) + val->intval = 1; + break; + default: + return -EINVAL; + } + return 0; +} + +static void max8903_work(struct work_struct *work) +{ + struct max8903_data *data = container_of(work, + struct max8903_data, work.work); + get_cap(data); + schedule_delayed_work(&data->work, MAX8903_DELAY); +} + +static irqreturn_t max8903_dcin(int irq, void *_data) +{ + struct max8903_data *data = _data; + struct max8903_pdata *pdata = data->pdata; + bool ta_in; + + ta_in = gpio_get_value(pdata->dok) ? false : true; + + if (ta_in == data->ta_in) + return IRQ_HANDLED; + + data->ta_in = ta_in; + + /* Set Current-Limit-Mode 1:DC 0:USB */ + if (pdata->dcm) + gpio_set_value(pdata->dcm, ta_in ? 1 : 0); + + /* Charger Enable / Disable (cen is negated) */ + if (pdata->cen) + gpio_set_value(pdata->cen, ta_in ? 0 : + (data->usb_in ? 0 : 1)); + + dev_dbg(data->dev, "TA(DC-IN) Charger %s.\n", ta_in ? + "Connected" : "Disconnected"); + + power_supply_changed(&data->psy); + power_supply_changed(&data->acpsy); + + return IRQ_HANDLED; +} + +static irqreturn_t max8903_usbin(int irq, void *_data) +{ + struct max8903_data *data = _data; + struct max8903_pdata *pdata = data->pdata; + bool usb_in; + + usb_in = gpio_get_value(pdata->uok) ? false : true; + + if (usb_in == data->usb_in) + return IRQ_HANDLED; + + data->usb_in = usb_in; + + /* Do not touch Current-Limit-Mode */ + + /* Charger Enable / Disable (cen is negated) */ + if (pdata->cen) + gpio_set_value(pdata->cen, usb_in ? 0 : + (data->ta_in ? 0 : 1)); + + dev_dbg(data->dev, "USB Charger %s.\n", usb_in ? + "Connected" : "Disconnected"); + + power_supply_changed(&data->psy); + power_supply_changed(&data->acpsy); + + return IRQ_HANDLED; +} + +static irqreturn_t max8903_fault(int irq, void *_data) +{ + struct max8903_data *data = _data; + struct max8903_pdata *pdata = data->pdata; + bool fault; + + fault = gpio_get_value(pdata->flt) ? false : true; + + if (fault == data->fault) + return IRQ_HANDLED; + + data->fault = fault; + + if (fault) + dev_err(data->dev, "Charger suffers a fault and stops.\n"); + else + dev_err(data->dev, "Charger recovered from a fault.\n"); + + return IRQ_HANDLED; +} + +static __devinit int max8903_probe(struct platform_device *pdev) +{ + struct max8903_data *data; + struct max8903_data *ac_data; + struct device *dev = &pdev->dev; + struct max8903_pdata *pdata = pdev->dev.platform_data; + int ret = 0; + int gpio = 0; + int ta_in = 0; + int usb_in = 0; + int error; + + data = kzalloc(sizeof(struct max8903_data), GFP_KERNEL); + if (data == NULL) { + dev_err(dev, "Cannot allocate memory.\n"); + return -ENOMEM; + } + data->pdata = pdata; + data->dev = dev; + platform_set_drvdata(pdev, data); + + if (pdata->dc_valid == false && pdata->usb_valid == false) { + dev_err(dev, "No valid power sources.\n"); + ret = -EINVAL; + goto err; + } + + if (pdata->dc_valid) { + if (pdata->dok && gpio_is_valid(pdata->dok) && + pdata->dcm && gpio_is_valid(pdata->dcm)) { + gpio = pdata->dok; /* PULL_UPed Interrupt */ + ta_in = gpio_get_value(gpio) ? 0 : 1; + + gpio = pdata->dcm; /* Output */ + gpio_set_value(gpio, ta_in); + } else if (pdata->dok && gpio_is_valid(pdata->dok) && + pdata->dcm_always_high) { + gpio = pdata->dok; /* PULL_UPed Interrupt */ + + error = gpio_request(gpio, "chg_dc"); + if (error < 0) { + dev_err(dev, "failed to configure" + " request/direction for GPIO %d, error %d\n", + gpio, error); + goto err; + } + gpio_direction_input(gpio); + + ta_in = gpio_get_value(gpio) ? 0 : 1; + + if (ta_in) + data->ta_in = true; + else + data->ta_in = false; + } else { + dev_err(dev, "When DC is wired, DOK and DCM should" + " be wired as well." + " or set dcm always high\n"); + ret = -EINVAL; + goto err; + } + } else { + if (pdata->dcm) { + if (gpio_is_valid(pdata->dcm)) + gpio_set_value(pdata->dcm, 0); + else { + dev_err(dev, "Invalid pin: dcm.\n"); + ret = -EINVAL; + goto err; + } + } + } + + if (pdata->usb_valid) { + if (pdata->uok && gpio_is_valid(pdata->uok)) { + gpio = pdata->uok; + error = gpio_request(gpio, "chg_usb"); + if (error < 0) { + dev_err(dev, "failed to configure" + " request/direction for GPIO %d, error %d\n", + gpio, error); + goto err; + } + + gpio_direction_input(gpio); + usb_in = gpio_get_value(gpio) ? 0 : 1; + if (usb_in) + data->usb_in = true; + else + data->usb_in = false; + } else { + dev_err(dev, "When USB is wired, UOK should be wired." + "as well.\n"); + ret = -EINVAL; + goto err; + } + } + + if (pdata->cen) { + if (gpio_is_valid(pdata->cen)) { + gpio_set_value(pdata->cen, (ta_in || usb_in) ? 0 : 1); + } else { + dev_err(dev, "Invalid pin: cen.\n"); + ret = -EINVAL; + goto err; + } + } + + if (pdata->chg) { + if (!gpio_is_valid(pdata->chg)) { + dev_err(dev, "Invalid pin: chg.\n"); + ret = -EINVAL; + goto err; + } + error = gpio_request(pdata->chg, "chg_status"); + if (error < 0) { + dev_err(dev, "failed to configure" + " request/direction for GPIO %d, error %d\n", + pdata->chg, error); + goto err; + } + error = gpio_direction_input(pdata->chg); + } + + if (pdata->flt) { + if (!gpio_is_valid(pdata->flt)) { + dev_err(dev, "Invalid pin: flt.\n"); + ret = -EINVAL; + goto err; + } + error = gpio_request(pdata->flt, "chg_fault"); + if (error < 0) { + dev_err(dev, "failed to configure" + " request/direction for GPIO %d, error %d\n", + pdata->flt, error); + goto err; + } + error = gpio_direction_input(pdata->flt); + } + + if (pdata->usus) { + if (!gpio_is_valid(pdata->usus)) { + dev_err(dev, "Invalid pin: usus.\n"); + ret = -EINVAL; + goto err; + } + } + + data->fault = false; + data->ta_in = ta_in; + data->usb_in = usb_in; + + data->acpsy.name = "max8903-ac"; + data->acpsy.type = POWER_SUPPLY_TYPE_MAINS; + data->acpsy.get_property = max8903_get_ac_property; + data->acpsy.properties = max8903_ac_props; + data->acpsy.num_properties = ARRAY_SIZE(max8903_ac_props); + + ret = power_supply_register(dev, &data->acpsy); + if (ret) { + dev_err(dev, "failed: power supply register.\n"); + goto err; + } + + data->psy.name = "max8903-charger"; + data->psy.type = POWER_SUPPLY_TYPE_BATTERY; + data->psy.get_property = max8903_get_property; + data->psy.properties = max8903_charger_props; + data->psy.num_properties = ARRAY_SIZE(max8903_charger_props); + + ret = power_supply_register(dev, &data->psy); + if (ret) { + dev_err(dev, "failed: power supply register.\n"); + goto err; + } + + if (pdata->dc_valid) { + ret = request_threaded_irq(gpio_to_irq(pdata->dok), + NULL, max8903_dcin, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, + "MAX8903 DC IN", data); + if (ret) { + dev_err(dev, "Cannot request irq %d for DC (%d)\n", + gpio_to_irq(pdata->dok), ret); + goto err_psy; + } + } + + if (pdata->usb_valid) { + ret = request_threaded_irq(gpio_to_irq(pdata->uok), + NULL, max8903_usbin, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, + "MAX8903 USB IN", data); + if (ret) { + dev_err(dev, "Cannot request irq %d for USB (%d)\n", + gpio_to_irq(pdata->uok), ret); + goto err_dc_irq; + } + } + + if (pdata->flt) { + ret = request_threaded_irq(gpio_to_irq(pdata->flt), + NULL, max8903_fault, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, + "MAX8903 Fault", data); + if (ret) { + dev_err(dev, "Cannot request irq %d for Fault (%d)\n", + gpio_to_irq(pdata->flt), ret); + goto err_usb_irq; + } + } + /* should remove this if capacity is supported */ + data->cap = 90; + + INIT_DELAYED_WORK_DEFERRABLE(&data->work, max8903_work); + + power_supply_changed(&data->psy); + power_supply_changed(&data->acpsy); + schedule_delayed_work(&data->work, MAX8903_DELAY); + + return 0; + +err_usb_irq: + if (pdata->usb_valid) + free_irq(gpio_to_irq(pdata->uok), data); +err_dc_irq: + if (pdata->dc_valid) + free_irq(gpio_to_irq(pdata->dok), data); +err_psy: + power_supply_unregister(&data->psy); + power_supply_unregister(&data->acpsy); +err: + if (pdata->uok) + gpio_free(pdata->uok); + if (pdata->dok) + gpio_free(pdata->dok); + if (pdata->flt) + gpio_free(pdata->flt); + if (pdata->chg) + gpio_free(pdata->chg); + kfree(data); + return ret; +} + +static __devexit int max8903_remove(struct platform_device *pdev) +{ + struct max8903_data *data = platform_get_drvdata(pdev); + + if (data) { + struct max8903_pdata *pdata = data->pdata; + + if (pdata->flt) { + free_irq(gpio_to_irq(pdata->flt), data); + gpio_free(pdata->flt); + } + if (pdata->usb_valid) { + free_irq(gpio_to_irq(pdata->uok), data); + gpio_free(pdata->uok); + } + if (pdata->dc_valid) { + free_irq(gpio_to_irq(pdata->dok), data); + gpio_free(pdata->dok); + } + power_supply_unregister(&data->psy); + power_supply_unregister(&data->acpsy); + if (pdata->chg) + gpio_free(pdata->chg); + kfree(data); + } + + return 0; +} + +static struct platform_driver max8903_driver = { + .probe = max8903_probe, + .remove = __devexit_p(max8903_remove), + .driver = { + .name = "max8903-charger", + .owner = THIS_MODULE, + }, +}; + +static int __init max8903_init(void) +{ + return platform_driver_register(&max8903_driver); +} +module_init(max8903_init); + +static void __exit max8903_exit(void) +{ + platform_driver_unregister(&max8903_driver); +} +module_exit(max8903_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("MAX8903 Charger Driver"); +MODULE_AUTHOR("MyungJoo Ham "); +MODULE_ALIAS("max8903-charger"); diff --git a/drivers/power/max8925_power.c b/drivers/power/max8925_power.c new file mode 100644 index 00000000..a70e16d3 --- /dev/null +++ b/drivers/power/max8925_power.c @@ -0,0 +1,529 @@ +/* + * Battery driver for Maxim MAX8925 + * + * Copyright (c) 2009-2010 Marvell International Ltd. + * Haojian Zhuang + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* registers in GPM */ +#define MAX8925_OUT5VEN 0x54 +#define MAX8925_OUT3VEN 0x58 +#define MAX8925_CHG_CNTL1 0x7c + +/* bits definition */ +#define MAX8925_CHG_STAT_VSYSLOW (1 << 0) +#define MAX8925_CHG_STAT_MODE_MASK (3 << 2) +#define MAX8925_CHG_STAT_EN_MASK (1 << 4) +#define MAX8925_CHG_MBDET (1 << 1) +#define MAX8925_CHG_AC_RANGE_MASK (3 << 6) + +/* registers in ADC */ +#define MAX8925_ADC_RES_CNFG1 0x06 +#define MAX8925_ADC_AVG_CNFG1 0x07 +#define MAX8925_ADC_ACQ_CNFG1 0x08 +#define MAX8925_ADC_ACQ_CNFG2 0x09 +/* 2 bytes registers in below. MSB is 1st, LSB is 2nd. */ +#define MAX8925_ADC_AUX2 0x62 +#define MAX8925_ADC_VCHG 0x64 +#define MAX8925_ADC_VBBATT 0x66 +#define MAX8925_ADC_VMBATT 0x68 +#define MAX8925_ADC_ISNS 0x6a +#define MAX8925_ADC_THM 0x6c +#define MAX8925_ADC_TDIE 0x6e +#define MAX8925_CMD_AUX2 0xc8 +#define MAX8925_CMD_VCHG 0xd0 +#define MAX8925_CMD_VBBATT 0xd8 +#define MAX8925_CMD_VMBATT 0xe0 +#define MAX8925_CMD_ISNS 0xe8 +#define MAX8925_CMD_THM 0xf0 +#define MAX8925_CMD_TDIE 0xf8 + +enum { + MEASURE_AUX2, + MEASURE_VCHG, + MEASURE_VBBATT, + MEASURE_VMBATT, + MEASURE_ISNS, + MEASURE_THM, + MEASURE_TDIE, + MEASURE_MAX, +}; + +struct max8925_power_info { + struct max8925_chip *chip; + struct i2c_client *gpm; + struct i2c_client *adc; + + struct power_supply ac; + struct power_supply usb; + struct power_supply battery; + int irq_base; + unsigned ac_online:1; + unsigned usb_online:1; + unsigned bat_online:1; + unsigned chg_mode:2; + unsigned batt_detect:1; /* detecing MB by ID pin */ + unsigned topoff_threshold:2; + unsigned fast_charge:3; + + int (*set_charger) (int); +}; + +static int __set_charger(struct max8925_power_info *info, int enable) +{ + struct max8925_chip *chip = info->chip; + if (enable) { + /* enable charger in platform */ + if (info->set_charger) + info->set_charger(1); + /* enable charger */ + max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 1 << 7, 0); + } else { + /* disable charge */ + max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 1 << 7, 1 << 7); + if (info->set_charger) + info->set_charger(0); + } + dev_dbg(chip->dev, "%s\n", (enable) ? "Enable charger" + : "Disable charger"); + return 0; +} + +static irqreturn_t max8925_charger_handler(int irq, void *data) +{ + struct max8925_power_info *info = (struct max8925_power_info *)data; + struct max8925_chip *chip = info->chip; + + switch (irq - chip->irq_base) { + case MAX8925_IRQ_VCHG_DC_R: + info->ac_online = 1; + __set_charger(info, 1); + dev_dbg(chip->dev, "Adapter inserted\n"); + break; + case MAX8925_IRQ_VCHG_DC_F: + info->ac_online = 0; + __set_charger(info, 0); + dev_dbg(chip->dev, "Adapter is removal\n"); + break; + case MAX8925_IRQ_VCHG_USB_R: + info->usb_online = 1; + __set_charger(info, 1); + dev_dbg(chip->dev, "USB inserted\n"); + break; + case MAX8925_IRQ_VCHG_USB_F: + info->usb_online = 0; + __set_charger(info, 0); + dev_dbg(chip->dev, "USB is removal\n"); + break; + case MAX8925_IRQ_VCHG_THM_OK_F: + /* Battery is not ready yet */ + dev_dbg(chip->dev, "Battery temperature is out of range\n"); + case MAX8925_IRQ_VCHG_DC_OVP: + dev_dbg(chip->dev, "Error detection\n"); + __set_charger(info, 0); + break; + case MAX8925_IRQ_VCHG_THM_OK_R: + /* Battery is ready now */ + dev_dbg(chip->dev, "Battery temperature is in range\n"); + break; + case MAX8925_IRQ_VCHG_SYSLOW_R: + /* VSYS is low */ + dev_info(chip->dev, "Sys power is too low\n"); + break; + case MAX8925_IRQ_VCHG_SYSLOW_F: + dev_dbg(chip->dev, "Sys power is above low threshold\n"); + break; + case MAX8925_IRQ_VCHG_DONE: + __set_charger(info, 0); + dev_dbg(chip->dev, "Charging is done\n"); + break; + case MAX8925_IRQ_VCHG_TOPOFF: + dev_dbg(chip->dev, "Charging in top-off mode\n"); + break; + case MAX8925_IRQ_VCHG_TMR_FAULT: + __set_charger(info, 0); + dev_dbg(chip->dev, "Safe timer is expired\n"); + break; + case MAX8925_IRQ_VCHG_RST: + __set_charger(info, 0); + dev_dbg(chip->dev, "Charger is reset\n"); + break; + } + return IRQ_HANDLED; +} + +static int start_measure(struct max8925_power_info *info, int type) +{ + unsigned char buf[2] = {0, 0}; + int meas_reg = 0, ret; + + switch (type) { + case MEASURE_VCHG: + meas_reg = MAX8925_ADC_VCHG; + break; + case MEASURE_VBBATT: + meas_reg = MAX8925_ADC_VBBATT; + break; + case MEASURE_VMBATT: + meas_reg = MAX8925_ADC_VMBATT; + break; + case MEASURE_ISNS: + meas_reg = MAX8925_ADC_ISNS; + break; + default: + return -EINVAL; + } + + max8925_bulk_read(info->adc, meas_reg, 2, buf); + ret = (buf[0] << 4) | (buf[1] >> 4); + + return ret; +} + +static int max8925_ac_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max8925_power_info *info = dev_get_drvdata(psy->dev->parent); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = info->ac_online; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + if (info->ac_online) { + ret = start_measure(info, MEASURE_VCHG); + if (ret >= 0) { + val->intval = ret << 1; /* unit is mV */ + goto out; + } + } + ret = -ENODATA; + break; + default: + ret = -ENODEV; + break; + } +out: + return ret; +} + +static enum power_supply_property max8925_ac_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + +static int max8925_usb_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max8925_power_info *info = dev_get_drvdata(psy->dev->parent); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = info->usb_online; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + if (info->usb_online) { + ret = start_measure(info, MEASURE_VCHG); + if (ret >= 0) { + val->intval = ret << 1; /* unit is mV */ + goto out; + } + } + ret = -ENODATA; + break; + default: + ret = -ENODEV; + break; + } +out: + return ret; +} + +static enum power_supply_property max8925_usb_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + +static int max8925_bat_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max8925_power_info *info = dev_get_drvdata(psy->dev->parent); + long long int tmp = 0; + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = info->bat_online; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + if (info->bat_online) { + ret = start_measure(info, MEASURE_VMBATT); + if (ret >= 0) { + val->intval = ret << 1; /* unit is mV */ + ret = 0; + break; + } + } + ret = -ENODATA; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + if (info->bat_online) { + ret = start_measure(info, MEASURE_ISNS); + if (ret >= 0) { + tmp = (long long int)ret * 6250 / 4096 - 3125; + ret = (int)tmp; + val->intval = 0; + if (ret > 0) + val->intval = ret; /* unit is mA */ + ret = 0; + break; + } + } + ret = -ENODATA; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + if (!info->bat_online) { + ret = -ENODATA; + break; + } + ret = max8925_reg_read(info->gpm, MAX8925_CHG_STATUS); + ret = (ret & MAX8925_CHG_STAT_MODE_MASK) >> 2; + switch (ret) { + case 1: + val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; + break; + case 0: + case 2: + val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + break; + case 3: + val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; + break; + } + ret = 0; + break; + case POWER_SUPPLY_PROP_STATUS: + if (!info->bat_online) { + ret = -ENODATA; + break; + } + ret = max8925_reg_read(info->gpm, MAX8925_CHG_STATUS); + if (info->usb_online || info->ac_online) { + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + if (ret & MAX8925_CHG_STAT_EN_MASK) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + } else + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + ret = 0; + break; + default: + ret = -ENODEV; + break; + } + return ret; +} + +static enum power_supply_property max8925_battery_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_STATUS, +}; + +#define REQUEST_IRQ(_irq, _name) \ +do { \ + ret = request_threaded_irq(chip->irq_base + _irq, NULL, \ + max8925_charger_handler, \ + IRQF_ONESHOT, _name, info); \ + if (ret) \ + dev_err(chip->dev, "Failed to request IRQ #%d: %d\n", \ + _irq, ret); \ +} while (0) + +static __devinit int max8925_init_charger(struct max8925_chip *chip, + struct max8925_power_info *info) +{ + int ret; + + REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_OVP, "ac-ovp"); + REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_F, "ac-remove"); + REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_R, "ac-insert"); + REQUEST_IRQ(MAX8925_IRQ_VCHG_USB_OVP, "usb-ovp"); + REQUEST_IRQ(MAX8925_IRQ_VCHG_USB_F, "usb-remove"); + REQUEST_IRQ(MAX8925_IRQ_VCHG_USB_R, "usb-insert"); + REQUEST_IRQ(MAX8925_IRQ_VCHG_THM_OK_R, "batt-temp-in-range"); + REQUEST_IRQ(MAX8925_IRQ_VCHG_THM_OK_F, "batt-temp-out-range"); + REQUEST_IRQ(MAX8925_IRQ_VCHG_SYSLOW_F, "vsys-high"); + REQUEST_IRQ(MAX8925_IRQ_VCHG_SYSLOW_R, "vsys-low"); + REQUEST_IRQ(MAX8925_IRQ_VCHG_RST, "charger-reset"); + REQUEST_IRQ(MAX8925_IRQ_VCHG_DONE, "charger-done"); + REQUEST_IRQ(MAX8925_IRQ_VCHG_TOPOFF, "charger-topoff"); + REQUEST_IRQ(MAX8925_IRQ_VCHG_TMR_FAULT, "charger-timer-expire"); + + info->ac_online = 0; + info->usb_online = 0; + info->bat_online = 0; + ret = max8925_reg_read(info->gpm, MAX8925_CHG_STATUS); + if (ret >= 0) { + /* + * If battery detection is enabled, ID pin of battery is + * connected to MBDET pin of MAX8925. It could be used to + * detect battery presence. + * Otherwise, we have to assume that battery is always on. + */ + if (info->batt_detect) + info->bat_online = (ret & MAX8925_CHG_MBDET) ? 0 : 1; + else + info->bat_online = 1; + if (ret & MAX8925_CHG_AC_RANGE_MASK) + info->ac_online = 1; + else + info->ac_online = 0; + } + /* disable charge */ + max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 1 << 7, 1 << 7); + /* set charging current in charge topoff mode */ + max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 3 << 5, + info->topoff_threshold << 5); + /* set charing current in fast charge mode */ + max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 7, info->fast_charge); + + return 0; +} + +static __devexit int max8925_deinit_charger(struct max8925_power_info *info) +{ + struct max8925_chip *chip = info->chip; + int irq; + + irq = chip->irq_base + MAX8925_IRQ_VCHG_DC_OVP; + for (; irq <= chip->irq_base + MAX8925_IRQ_VCHG_TMR_FAULT; irq++) + free_irq(irq, info); + + return 0; +} + +static __devinit int max8925_power_probe(struct platform_device *pdev) +{ + struct max8925_chip *chip = dev_get_drvdata(pdev->dev.parent); + struct max8925_power_pdata *pdata = NULL; + struct max8925_power_info *info; + int ret; + + pdata = pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, "platform data isn't assigned to " + "power supply\n"); + return -EINVAL; + } + + info = kzalloc(sizeof(struct max8925_power_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + info->chip = chip; + info->gpm = chip->i2c; + info->adc = chip->adc; + platform_set_drvdata(pdev, info); + + info->ac.name = "max8925-ac"; + info->ac.type = POWER_SUPPLY_TYPE_MAINS; + info->ac.properties = max8925_ac_props; + info->ac.num_properties = ARRAY_SIZE(max8925_ac_props); + info->ac.get_property = max8925_ac_get_prop; + ret = power_supply_register(&pdev->dev, &info->ac); + if (ret) + goto out; + info->ac.dev->parent = &pdev->dev; + + info->usb.name = "max8925-usb"; + info->usb.type = POWER_SUPPLY_TYPE_USB; + info->usb.properties = max8925_usb_props; + info->usb.num_properties = ARRAY_SIZE(max8925_usb_props); + info->usb.get_property = max8925_usb_get_prop; + ret = power_supply_register(&pdev->dev, &info->usb); + if (ret) + goto out_usb; + info->usb.dev->parent = &pdev->dev; + + info->battery.name = "max8925-battery"; + info->battery.type = POWER_SUPPLY_TYPE_BATTERY; + info->battery.properties = max8925_battery_props; + info->battery.num_properties = ARRAY_SIZE(max8925_battery_props); + info->battery.get_property = max8925_bat_get_prop; + ret = power_supply_register(&pdev->dev, &info->battery); + if (ret) + goto out_battery; + info->battery.dev->parent = &pdev->dev; + + info->batt_detect = pdata->batt_detect; + info->topoff_threshold = pdata->topoff_threshold; + info->fast_charge = pdata->fast_charge; + info->set_charger = pdata->set_charger; + + max8925_init_charger(chip, info); + return 0; +out_battery: + power_supply_unregister(&info->battery); +out_usb: + power_supply_unregister(&info->ac); +out: + kfree(info); + return ret; +} + +static __devexit int max8925_power_remove(struct platform_device *pdev) +{ + struct max8925_power_info *info = platform_get_drvdata(pdev); + + if (info) { + power_supply_unregister(&info->ac); + power_supply_unregister(&info->usb); + power_supply_unregister(&info->battery); + max8925_deinit_charger(info); + kfree(info); + } + return 0; +} + +static struct platform_driver max8925_power_driver = { + .probe = max8925_power_probe, + .remove = __devexit_p(max8925_power_remove), + .driver = { + .name = "max8925-power", + }, +}; + +static int __init max8925_power_init(void) +{ + return platform_driver_register(&max8925_power_driver); +} +module_init(max8925_power_init); + +static void __exit max8925_power_exit(void) +{ + platform_driver_unregister(&max8925_power_driver); +} +module_exit(max8925_power_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Power supply driver for MAX8925"); +MODULE_ALIAS("platform:max8925-power"); diff --git a/drivers/power/olpc_battery.c b/drivers/power/olpc_battery.c new file mode 100644 index 00000000..0b0ff3a9 --- /dev/null +++ b/drivers/power/olpc_battery.c @@ -0,0 +1,618 @@ +/* + * Battery driver for One Laptop Per Child board. + * + * Copyright © 2006-2010 David Woodhouse + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define EC_BAT_VOLTAGE 0x10 /* uint16_t, *9.76/32, mV */ +#define EC_BAT_CURRENT 0x11 /* int16_t, *15.625/120, mA */ +#define EC_BAT_ACR 0x12 /* int16_t, *6250/15, µAh */ +#define EC_BAT_TEMP 0x13 /* uint16_t, *100/256, °C */ +#define EC_AMB_TEMP 0x14 /* uint16_t, *100/256, °C */ +#define EC_BAT_STATUS 0x15 /* uint8_t, bitmask */ +#define EC_BAT_SOC 0x16 /* uint8_t, percentage */ +#define EC_BAT_SERIAL 0x17 /* uint8_t[6] */ +#define EC_BAT_EEPROM 0x18 /* uint8_t adr as input, uint8_t output */ +#define EC_BAT_ERRCODE 0x1f /* uint8_t, bitmask */ + +#define BAT_STAT_PRESENT 0x01 +#define BAT_STAT_FULL 0x02 +#define BAT_STAT_LOW 0x04 +#define BAT_STAT_DESTROY 0x08 +#define BAT_STAT_AC 0x10 +#define BAT_STAT_CHARGING 0x20 +#define BAT_STAT_DISCHARGING 0x40 +#define BAT_STAT_TRICKLE 0x80 + +#define BAT_ERR_INFOFAIL 0x02 +#define BAT_ERR_OVERVOLTAGE 0x04 +#define BAT_ERR_OVERTEMP 0x05 +#define BAT_ERR_GAUGESTOP 0x06 +#define BAT_ERR_OUT_OF_CONTROL 0x07 +#define BAT_ERR_ID_FAIL 0x09 +#define BAT_ERR_ACR_FAIL 0x10 + +#define BAT_ADDR_MFR_TYPE 0x5F + +/********************************************************************* + * Power + *********************************************************************/ + +static int olpc_ac_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret = 0; + uint8_t status; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + ret = olpc_ec_cmd(EC_BAT_STATUS, NULL, 0, &status, 1); + if (ret) + return ret; + + val->intval = !!(status & BAT_STAT_AC); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static enum power_supply_property olpc_ac_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static struct power_supply olpc_ac = { + .name = "olpc-ac", + .type = POWER_SUPPLY_TYPE_MAINS, + .properties = olpc_ac_props, + .num_properties = ARRAY_SIZE(olpc_ac_props), + .get_property = olpc_ac_get_prop, +}; + +static char bat_serial[17]; /* Ick */ + +static int olpc_bat_get_status(union power_supply_propval *val, uint8_t ec_byte) +{ + if (olpc_platform_info.ecver > 0x44) { + if (ec_byte & (BAT_STAT_CHARGING | BAT_STAT_TRICKLE)) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else if (ec_byte & BAT_STAT_DISCHARGING) + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + else if (ec_byte & BAT_STAT_FULL) + val->intval = POWER_SUPPLY_STATUS_FULL; + else /* er,... */ + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + } else { + /* Older EC didn't report charge/discharge bits */ + if (!(ec_byte & BAT_STAT_AC)) /* No AC means discharging */ + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + else if (ec_byte & BAT_STAT_FULL) + val->intval = POWER_SUPPLY_STATUS_FULL; + else /* Not _necessarily_ true but EC doesn't tell all yet */ + val->intval = POWER_SUPPLY_STATUS_CHARGING; + } + + return 0; +} + +static int olpc_bat_get_health(union power_supply_propval *val) +{ + uint8_t ec_byte; + int ret; + + ret = olpc_ec_cmd(EC_BAT_ERRCODE, NULL, 0, &ec_byte, 1); + if (ret) + return ret; + + switch (ec_byte) { + case 0: + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + + case BAT_ERR_OVERTEMP: + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + break; + + case BAT_ERR_OVERVOLTAGE: + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + break; + + case BAT_ERR_INFOFAIL: + case BAT_ERR_OUT_OF_CONTROL: + case BAT_ERR_ID_FAIL: + case BAT_ERR_ACR_FAIL: + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + break; + + default: + /* Eep. We don't know this failure code */ + ret = -EIO; + } + + return ret; +} + +static int olpc_bat_get_mfr(union power_supply_propval *val) +{ + uint8_t ec_byte; + int ret; + + ec_byte = BAT_ADDR_MFR_TYPE; + ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &ec_byte, 1); + if (ret) + return ret; + + switch (ec_byte >> 4) { + case 1: + val->strval = "Gold Peak"; + break; + case 2: + val->strval = "BYD"; + break; + default: + val->strval = "Unknown"; + break; + } + + return ret; +} + +static int olpc_bat_get_tech(union power_supply_propval *val) +{ + uint8_t ec_byte; + int ret; + + ec_byte = BAT_ADDR_MFR_TYPE; + ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &ec_byte, 1); + if (ret) + return ret; + + switch (ec_byte & 0xf) { + case 1: + val->intval = POWER_SUPPLY_TECHNOLOGY_NiMH; + break; + case 2: + val->intval = POWER_SUPPLY_TECHNOLOGY_LiFe; + break; + default: + val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; + break; + } + + return ret; +} + +static int olpc_bat_get_charge_full_design(union power_supply_propval *val) +{ + uint8_t ec_byte; + union power_supply_propval tech; + int ret, mfr; + + ret = olpc_bat_get_tech(&tech); + if (ret) + return ret; + + ec_byte = BAT_ADDR_MFR_TYPE; + ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &ec_byte, 1); + if (ret) + return ret; + + mfr = ec_byte >> 4; + + switch (tech.intval) { + case POWER_SUPPLY_TECHNOLOGY_NiMH: + switch (mfr) { + case 1: /* Gold Peak */ + val->intval = 3000000*.8; + break; + default: + return -EIO; + } + break; + + case POWER_SUPPLY_TECHNOLOGY_LiFe: + switch (mfr) { + case 1: /* Gold Peak */ + val->intval = 2800000; + break; + case 2: /* BYD */ + val->intval = 3100000; + break; + default: + return -EIO; + } + break; + + default: + return -EIO; + } + + return ret; +} + +static int olpc_bat_get_charge_now(union power_supply_propval *val) +{ + uint8_t soc; + union power_supply_propval full; + int ret; + + ret = olpc_ec_cmd(EC_BAT_SOC, NULL, 0, &soc, 1); + if (ret) + return ret; + + ret = olpc_bat_get_charge_full_design(&full); + if (ret) + return ret; + + val->intval = soc * (full.intval / 100); + return 0; +} + +/********************************************************************* + * Battery properties + *********************************************************************/ +static int olpc_bat_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret = 0; + __be16 ec_word; + uint8_t ec_byte; + __be64 ser_buf; + + ret = olpc_ec_cmd(EC_BAT_STATUS, NULL, 0, &ec_byte, 1); + if (ret) + return ret; + + /* Theoretically there's a race here -- the battery could be + removed immediately after we check whether it's present, and + then we query for some other property of the now-absent battery. + It doesn't matter though -- the EC will return the last-known + information, and it's as if we just ran that _little_ bit faster + and managed to read it out before the battery went away. */ + if (!(ec_byte & (BAT_STAT_PRESENT | BAT_STAT_TRICKLE)) && + psp != POWER_SUPPLY_PROP_PRESENT) + return -ENODEV; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + ret = olpc_bat_get_status(val, ec_byte); + if (ret) + return ret; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + if (ec_byte & BAT_STAT_TRICKLE) + val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + else if (ec_byte & BAT_STAT_CHARGING) + val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; + else + val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = !!(ec_byte & (BAT_STAT_PRESENT | + BAT_STAT_TRICKLE)); + break; + + case POWER_SUPPLY_PROP_HEALTH: + if (ec_byte & BAT_STAT_DESTROY) + val->intval = POWER_SUPPLY_HEALTH_DEAD; + else { + ret = olpc_bat_get_health(val); + if (ret) + return ret; + } + break; + + case POWER_SUPPLY_PROP_MANUFACTURER: + ret = olpc_bat_get_mfr(val); + if (ret) + return ret; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + ret = olpc_bat_get_tech(val); + if (ret) + return ret; + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = olpc_ec_cmd(EC_BAT_VOLTAGE, NULL, 0, (void *)&ec_word, 2); + if (ret) + return ret; + + val->intval = (s16)be16_to_cpu(ec_word) * 9760L / 32; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = olpc_ec_cmd(EC_BAT_CURRENT, NULL, 0, (void *)&ec_word, 2); + if (ret) + return ret; + + val->intval = (s16)be16_to_cpu(ec_word) * 15625L / 120; + break; + case POWER_SUPPLY_PROP_CAPACITY: + ret = olpc_ec_cmd(EC_BAT_SOC, NULL, 0, &ec_byte, 1); + if (ret) + return ret; + val->intval = ec_byte; + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + if (ec_byte & BAT_STAT_FULL) + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_FULL; + else if (ec_byte & BAT_STAT_LOW) + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_LOW; + else + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + ret = olpc_bat_get_charge_full_design(val); + if (ret) + return ret; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + ret = olpc_bat_get_charge_now(val); + if (ret) + return ret; + break; + case POWER_SUPPLY_PROP_TEMP: + ret = olpc_ec_cmd(EC_BAT_TEMP, NULL, 0, (void *)&ec_word, 2); + if (ret) + return ret; + + val->intval = (s16)be16_to_cpu(ec_word) * 100 / 256; + break; + case POWER_SUPPLY_PROP_TEMP_AMBIENT: + ret = olpc_ec_cmd(EC_AMB_TEMP, NULL, 0, (void *)&ec_word, 2); + if (ret) + return ret; + + val->intval = (int)be16_to_cpu(ec_word) * 100 / 256; + break; + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + ret = olpc_ec_cmd(EC_BAT_ACR, NULL, 0, (void *)&ec_word, 2); + if (ret) + return ret; + + val->intval = (s16)be16_to_cpu(ec_word) * 6250 / 15; + break; + case POWER_SUPPLY_PROP_SERIAL_NUMBER: + ret = olpc_ec_cmd(EC_BAT_SERIAL, NULL, 0, (void *)&ser_buf, 8); + if (ret) + return ret; + + sprintf(bat_serial, "%016llx", (long long)be64_to_cpu(ser_buf)); + val->strval = bat_serial; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static enum power_supply_property olpc_xo1_bat_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TEMP_AMBIENT, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_SERIAL_NUMBER, + POWER_SUPPLY_PROP_CHARGE_COUNTER, +}; + +/* XO-1.5 does not have ambient temperature property */ +static enum power_supply_property olpc_xo15_bat_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_SERIAL_NUMBER, + POWER_SUPPLY_PROP_CHARGE_COUNTER, +}; + +/* EEPROM reading goes completely around the power_supply API, sadly */ + +#define EEPROM_START 0x20 +#define EEPROM_END 0x80 +#define EEPROM_SIZE (EEPROM_END - EEPROM_START) + +static ssize_t olpc_bat_eeprom_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, loff_t off, size_t count) +{ + uint8_t ec_byte; + int ret; + int i; + + if (off >= EEPROM_SIZE) + return 0; + if (off + count > EEPROM_SIZE) + count = EEPROM_SIZE - off; + + for (i = 0; i < count; i++) { + ec_byte = EEPROM_START + off + i; + ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &buf[i], 1); + if (ret) { + pr_err("olpc-battery: " + "EC_BAT_EEPROM cmd @ 0x%x failed - %d!\n", + ec_byte, ret); + return -EIO; + } + } + + return count; +} + +static struct bin_attribute olpc_bat_eeprom = { + .attr = { + .name = "eeprom", + .mode = S_IRUGO, + }, + .size = 0, + .read = olpc_bat_eeprom_read, +}; + +/* Allow userspace to see the specific error value pulled from the EC */ + +static ssize_t olpc_bat_error_read(struct device *dev, + struct device_attribute *attr, char *buf) +{ + uint8_t ec_byte; + ssize_t ret; + + ret = olpc_ec_cmd(EC_BAT_ERRCODE, NULL, 0, &ec_byte, 1); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", ec_byte); +} + +static struct device_attribute olpc_bat_error = { + .attr = { + .name = "error", + .mode = S_IRUGO, + }, + .show = olpc_bat_error_read, +}; + +/********************************************************************* + * Initialisation + *********************************************************************/ + +static struct platform_device *bat_pdev; + +static struct power_supply olpc_bat = { + .get_property = olpc_bat_get_property, + .use_for_apm = 1, +}; + +void olpc_battery_trigger_uevent(unsigned long cause) +{ + if (cause & EC_SCI_SRC_ACPWR) + kobject_uevent(&olpc_ac.dev->kobj, KOBJ_CHANGE); + if (cause & (EC_SCI_SRC_BATERR|EC_SCI_SRC_BATSOC|EC_SCI_SRC_BATTERY)) + kobject_uevent(&olpc_bat.dev->kobj, KOBJ_CHANGE); +} + +static int __init olpc_bat_init(void) +{ + int ret = 0; + uint8_t status; + + if (!olpc_platform_info.ecver) + return -ENXIO; + + /* + * We've seen a number of EC protocol changes; this driver requires + * the latest EC protocol, supported by 0x44 and above. + */ + if (olpc_platform_info.ecver < 0x44) { + printk(KERN_NOTICE "OLPC EC version 0x%02x too old for " + "battery driver.\n", olpc_platform_info.ecver); + return -ENXIO; + } + + ret = olpc_ec_cmd(EC_BAT_STATUS, NULL, 0, &status, 1); + if (ret) + return ret; + + /* Ignore the status. It doesn't actually matter */ + + bat_pdev = platform_device_register_simple("olpc-battery", 0, NULL, 0); + if (IS_ERR(bat_pdev)) + return PTR_ERR(bat_pdev); + + ret = power_supply_register(&bat_pdev->dev, &olpc_ac); + if (ret) + goto ac_failed; + + olpc_bat.name = bat_pdev->name; + if (olpc_board_at_least(olpc_board_pre(0xd0))) { /* XO-1.5 */ + olpc_bat.properties = olpc_xo15_bat_props; + olpc_bat.num_properties = ARRAY_SIZE(olpc_xo15_bat_props); + } else { /* XO-1 */ + olpc_bat.properties = olpc_xo1_bat_props; + olpc_bat.num_properties = ARRAY_SIZE(olpc_xo1_bat_props); + } + + ret = power_supply_register(&bat_pdev->dev, &olpc_bat); + if (ret) + goto battery_failed; + + ret = device_create_bin_file(olpc_bat.dev, &olpc_bat_eeprom); + if (ret) + goto eeprom_failed; + + ret = device_create_file(olpc_bat.dev, &olpc_bat_error); + if (ret) + goto error_failed; + + goto success; + +error_failed: + device_remove_bin_file(olpc_bat.dev, &olpc_bat_eeprom); +eeprom_failed: + power_supply_unregister(&olpc_bat); +battery_failed: + power_supply_unregister(&olpc_ac); +ac_failed: + platform_device_unregister(bat_pdev); +success: + return ret; +} + +static void __exit olpc_bat_exit(void) +{ + device_remove_file(olpc_bat.dev, &olpc_bat_error); + device_remove_bin_file(olpc_bat.dev, &olpc_bat_eeprom); + power_supply_unregister(&olpc_bat); + power_supply_unregister(&olpc_ac); + platform_device_unregister(bat_pdev); +} + +module_init(olpc_bat_init); +module_exit(olpc_bat_exit); + +MODULE_AUTHOR("David Woodhouse "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Battery driver for One Laptop Per Child 'XO' machine"); diff --git a/drivers/power/pcf50633-charger.c b/drivers/power/pcf50633-charger.c new file mode 100644 index 00000000..4fa52e17 --- /dev/null +++ b/drivers/power/pcf50633-charger.c @@ -0,0 +1,492 @@ +/* NXP PCF50633 Main Battery Charger Driver + * + * (C) 2006-2008 by Openmoko, Inc. + * Author: Balaji Rao + * All rights reserved. + * + * Broken down from monstrous PCF50633 driver mainly by + * Harald Welte, Andy Green and Werner Almesberger + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +struct pcf50633_mbc { + struct pcf50633 *pcf; + + int adapter_online; + int usb_online; + + struct power_supply usb; + struct power_supply adapter; + struct power_supply ac; +}; + +int pcf50633_mbc_usb_curlim_set(struct pcf50633 *pcf, int ma) +{ + struct pcf50633_mbc *mbc = platform_get_drvdata(pcf->mbc_pdev); + int ret = 0; + u8 bits; + int charging_start = 1; + u8 mbcs2, chgmod; + unsigned int mbcc5; + + if (ma >= 1000) { + bits = PCF50633_MBCC7_USB_1000mA; + ma = 1000; + } else if (ma >= 500) { + bits = PCF50633_MBCC7_USB_500mA; + ma = 500; + } else if (ma >= 100) { + bits = PCF50633_MBCC7_USB_100mA; + ma = 100; + } else { + bits = PCF50633_MBCC7_USB_SUSPEND; + charging_start = 0; + ma = 0; + } + + ret = pcf50633_reg_set_bit_mask(pcf, PCF50633_REG_MBCC7, + PCF50633_MBCC7_USB_MASK, bits); + if (ret) + dev_err(pcf->dev, "error setting usb curlim to %d mA\n", ma); + else + dev_info(pcf->dev, "usb curlim to %d mA\n", ma); + + /* + * We limit the charging current to be the USB current limit. + * The reason is that on pcf50633, when it enters PMU Standby mode, + * which it does when the device goes "off", the USB current limit + * reverts to the variant default. In at least one common case, that + * default is 500mA. By setting the charging current to be the same + * as the USB limit we set here before PMU standby, we enforce it only + * using the correct amount of current even when the USB current limit + * gets reset to the wrong thing + */ + + if (mbc->pcf->pdata->charger_reference_current_ma) { + mbcc5 = (ma << 8) / mbc->pcf->pdata->charger_reference_current_ma; + if (mbcc5 > 255) + mbcc5 = 255; + pcf50633_reg_write(mbc->pcf, PCF50633_REG_MBCC5, mbcc5); + } + + mbcs2 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS2); + chgmod = (mbcs2 & PCF50633_MBCS2_MBC_MASK); + + /* If chgmod == BATFULL, setting chgena has no effect. + * Datasheet says we need to set resume instead but when autoresume is + * used resume doesn't work. Clear and set chgena instead. + */ + if (chgmod != PCF50633_MBCS2_MBC_BAT_FULL) + pcf50633_reg_set_bit_mask(pcf, PCF50633_REG_MBCC1, + PCF50633_MBCC1_CHGENA, PCF50633_MBCC1_CHGENA); + else { + pcf50633_reg_clear_bits(pcf, PCF50633_REG_MBCC1, + PCF50633_MBCC1_CHGENA); + pcf50633_reg_set_bit_mask(pcf, PCF50633_REG_MBCC1, + PCF50633_MBCC1_CHGENA, PCF50633_MBCC1_CHGENA); + } + + power_supply_changed(&mbc->usb); + + return ret; +} +EXPORT_SYMBOL_GPL(pcf50633_mbc_usb_curlim_set); + +int pcf50633_mbc_get_status(struct pcf50633 *pcf) +{ + struct pcf50633_mbc *mbc = platform_get_drvdata(pcf->mbc_pdev); + int status = 0; + u8 chgmod; + + if (!mbc) + return 0; + + chgmod = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS2) + & PCF50633_MBCS2_MBC_MASK; + + if (mbc->usb_online) + status |= PCF50633_MBC_USB_ONLINE; + if (chgmod == PCF50633_MBCS2_MBC_USB_PRE || + chgmod == PCF50633_MBCS2_MBC_USB_PRE_WAIT || + chgmod == PCF50633_MBCS2_MBC_USB_FAST || + chgmod == PCF50633_MBCS2_MBC_USB_FAST_WAIT) + status |= PCF50633_MBC_USB_ACTIVE; + if (mbc->adapter_online) + status |= PCF50633_MBC_ADAPTER_ONLINE; + if (chgmod == PCF50633_MBCS2_MBC_ADP_PRE || + chgmod == PCF50633_MBCS2_MBC_ADP_PRE_WAIT || + chgmod == PCF50633_MBCS2_MBC_ADP_FAST || + chgmod == PCF50633_MBCS2_MBC_ADP_FAST_WAIT) + status |= PCF50633_MBC_ADAPTER_ACTIVE; + + return status; +} +EXPORT_SYMBOL_GPL(pcf50633_mbc_get_status); + +int pcf50633_mbc_get_usb_online_status(struct pcf50633 *pcf) +{ + struct pcf50633_mbc *mbc = platform_get_drvdata(pcf->mbc_pdev); + + if (!mbc) + return 0; + + return mbc->usb_online; +} +EXPORT_SYMBOL_GPL(pcf50633_mbc_get_usb_online_status); + +static ssize_t +show_chgmode(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct pcf50633_mbc *mbc = dev_get_drvdata(dev); + + u8 mbcs2 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS2); + u8 chgmod = (mbcs2 & PCF50633_MBCS2_MBC_MASK); + + return sprintf(buf, "%d\n", chgmod); +} +static DEVICE_ATTR(chgmode, S_IRUGO, show_chgmode, NULL); + +static ssize_t +show_usblim(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct pcf50633_mbc *mbc = dev_get_drvdata(dev); + u8 usblim = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCC7) & + PCF50633_MBCC7_USB_MASK; + unsigned int ma; + + if (usblim == PCF50633_MBCC7_USB_1000mA) + ma = 1000; + else if (usblim == PCF50633_MBCC7_USB_500mA) + ma = 500; + else if (usblim == PCF50633_MBCC7_USB_100mA) + ma = 100; + else + ma = 0; + + return sprintf(buf, "%u\n", ma); +} + +static ssize_t set_usblim(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct pcf50633_mbc *mbc = dev_get_drvdata(dev); + unsigned long ma; + int ret; + + ret = strict_strtoul(buf, 10, &ma); + if (ret) + return -EINVAL; + + pcf50633_mbc_usb_curlim_set(mbc->pcf, ma); + + return count; +} + +static DEVICE_ATTR(usb_curlim, S_IRUGO | S_IWUSR, show_usblim, set_usblim); + +static ssize_t +show_chglim(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct pcf50633_mbc *mbc = dev_get_drvdata(dev); + u8 mbcc5 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCC5); + unsigned int ma; + + if (!mbc->pcf->pdata->charger_reference_current_ma) + return -ENODEV; + + ma = (mbc->pcf->pdata->charger_reference_current_ma * mbcc5) >> 8; + + return sprintf(buf, "%u\n", ma); +} + +static ssize_t set_chglim(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct pcf50633_mbc *mbc = dev_get_drvdata(dev); + unsigned long ma; + unsigned int mbcc5; + int ret; + + if (!mbc->pcf->pdata->charger_reference_current_ma) + return -ENODEV; + + ret = strict_strtoul(buf, 10, &ma); + if (ret) + return -EINVAL; + + mbcc5 = (ma << 8) / mbc->pcf->pdata->charger_reference_current_ma; + if (mbcc5 > 255) + mbcc5 = 255; + pcf50633_reg_write(mbc->pcf, PCF50633_REG_MBCC5, mbcc5); + + return count; +} + +/* + * This attribute allows to change MBC charging limit on the fly + * independently of usb current limit. It also gets set automatically every + * time usb current limit is changed. + */ +static DEVICE_ATTR(chg_curlim, S_IRUGO | S_IWUSR, show_chglim, set_chglim); + +static struct attribute *pcf50633_mbc_sysfs_entries[] = { + &dev_attr_chgmode.attr, + &dev_attr_usb_curlim.attr, + &dev_attr_chg_curlim.attr, + NULL, +}; + +static struct attribute_group mbc_attr_group = { + .name = NULL, /* put in device directory */ + .attrs = pcf50633_mbc_sysfs_entries, +}; + +static void +pcf50633_mbc_irq_handler(int irq, void *data) +{ + struct pcf50633_mbc *mbc = data; + + /* USB */ + if (irq == PCF50633_IRQ_USBINS) { + mbc->usb_online = 1; + } else if (irq == PCF50633_IRQ_USBREM) { + mbc->usb_online = 0; + pcf50633_mbc_usb_curlim_set(mbc->pcf, 0); + } + + /* Adapter */ + if (irq == PCF50633_IRQ_ADPINS) + mbc->adapter_online = 1; + else if (irq == PCF50633_IRQ_ADPREM) + mbc->adapter_online = 0; + + power_supply_changed(&mbc->ac); + power_supply_changed(&mbc->usb); + power_supply_changed(&mbc->adapter); + + if (mbc->pcf->pdata->mbc_event_callback) + mbc->pcf->pdata->mbc_event_callback(mbc->pcf, irq); +} + +static int adapter_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct pcf50633_mbc *mbc = container_of(psy, + struct pcf50633_mbc, adapter); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = mbc->adapter_online; + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int usb_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct pcf50633_mbc *mbc = container_of(psy, struct pcf50633_mbc, usb); + int ret = 0; + u8 usblim = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCC7) & + PCF50633_MBCC7_USB_MASK; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = mbc->usb_online && + (usblim <= PCF50633_MBCC7_USB_500mA); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int ac_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct pcf50633_mbc *mbc = container_of(psy, struct pcf50633_mbc, ac); + int ret = 0; + u8 usblim = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCC7) & + PCF50633_MBCC7_USB_MASK; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = mbc->usb_online && + (usblim == PCF50633_MBCC7_USB_1000mA); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static enum power_supply_property power_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static const u8 mbc_irq_handlers[] = { + PCF50633_IRQ_ADPINS, + PCF50633_IRQ_ADPREM, + PCF50633_IRQ_USBINS, + PCF50633_IRQ_USBREM, + PCF50633_IRQ_BATFULL, + PCF50633_IRQ_CHGHALT, + PCF50633_IRQ_THLIMON, + PCF50633_IRQ_THLIMOFF, + PCF50633_IRQ_USBLIMON, + PCF50633_IRQ_USBLIMOFF, + PCF50633_IRQ_LOWSYS, + PCF50633_IRQ_LOWBAT, +}; + +static int __devinit pcf50633_mbc_probe(struct platform_device *pdev) +{ + struct pcf50633_mbc *mbc; + int ret; + int i; + u8 mbcs1; + + mbc = kzalloc(sizeof(*mbc), GFP_KERNEL); + if (!mbc) + return -ENOMEM; + + platform_set_drvdata(pdev, mbc); + mbc->pcf = dev_to_pcf50633(pdev->dev.parent); + + /* Set up IRQ handlers */ + for (i = 0; i < ARRAY_SIZE(mbc_irq_handlers); i++) + pcf50633_register_irq(mbc->pcf, mbc_irq_handlers[i], + pcf50633_mbc_irq_handler, mbc); + + /* Create power supplies */ + mbc->adapter.name = "adapter"; + mbc->adapter.type = POWER_SUPPLY_TYPE_MAINS; + mbc->adapter.properties = power_props; + mbc->adapter.num_properties = ARRAY_SIZE(power_props); + mbc->adapter.get_property = &adapter_get_property; + mbc->adapter.supplied_to = mbc->pcf->pdata->batteries; + mbc->adapter.num_supplicants = mbc->pcf->pdata->num_batteries; + + mbc->usb.name = "usb"; + mbc->usb.type = POWER_SUPPLY_TYPE_USB; + mbc->usb.properties = power_props; + mbc->usb.num_properties = ARRAY_SIZE(power_props); + mbc->usb.get_property = usb_get_property; + mbc->usb.supplied_to = mbc->pcf->pdata->batteries; + mbc->usb.num_supplicants = mbc->pcf->pdata->num_batteries; + + mbc->ac.name = "ac"; + mbc->ac.type = POWER_SUPPLY_TYPE_MAINS; + mbc->ac.properties = power_props; + mbc->ac.num_properties = ARRAY_SIZE(power_props); + mbc->ac.get_property = ac_get_property; + mbc->ac.supplied_to = mbc->pcf->pdata->batteries; + mbc->ac.num_supplicants = mbc->pcf->pdata->num_batteries; + + ret = power_supply_register(&pdev->dev, &mbc->adapter); + if (ret) { + dev_err(mbc->pcf->dev, "failed to register adapter\n"); + kfree(mbc); + return ret; + } + + ret = power_supply_register(&pdev->dev, &mbc->usb); + if (ret) { + dev_err(mbc->pcf->dev, "failed to register usb\n"); + power_supply_unregister(&mbc->adapter); + kfree(mbc); + return ret; + } + + ret = power_supply_register(&pdev->dev, &mbc->ac); + if (ret) { + dev_err(mbc->pcf->dev, "failed to register ac\n"); + power_supply_unregister(&mbc->adapter); + power_supply_unregister(&mbc->usb); + kfree(mbc); + return ret; + } + + ret = sysfs_create_group(&pdev->dev.kobj, &mbc_attr_group); + if (ret) + dev_err(mbc->pcf->dev, "failed to create sysfs entries\n"); + + mbcs1 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS1); + if (mbcs1 & PCF50633_MBCS1_USBPRES) + pcf50633_mbc_irq_handler(PCF50633_IRQ_USBINS, mbc); + if (mbcs1 & PCF50633_MBCS1_ADAPTPRES) + pcf50633_mbc_irq_handler(PCF50633_IRQ_ADPINS, mbc); + + return 0; +} + +static int __devexit pcf50633_mbc_remove(struct platform_device *pdev) +{ + struct pcf50633_mbc *mbc = platform_get_drvdata(pdev); + int i; + + /* Remove IRQ handlers */ + for (i = 0; i < ARRAY_SIZE(mbc_irq_handlers); i++) + pcf50633_free_irq(mbc->pcf, mbc_irq_handlers[i]); + + sysfs_remove_group(&pdev->dev.kobj, &mbc_attr_group); + power_supply_unregister(&mbc->usb); + power_supply_unregister(&mbc->adapter); + power_supply_unregister(&mbc->ac); + + kfree(mbc); + + return 0; +} + +static struct platform_driver pcf50633_mbc_driver = { + .driver = { + .name = "pcf50633-mbc", + }, + .probe = pcf50633_mbc_probe, + .remove = __devexit_p(pcf50633_mbc_remove), +}; + +static int __init pcf50633_mbc_init(void) +{ + return platform_driver_register(&pcf50633_mbc_driver); +} +module_init(pcf50633_mbc_init); + +static void __exit pcf50633_mbc_exit(void) +{ + platform_driver_unregister(&pcf50633_mbc_driver); +} +module_exit(pcf50633_mbc_exit); + +MODULE_AUTHOR("Balaji Rao "); +MODULE_DESCRIPTION("PCF50633 mbc driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:pcf50633-mbc"); diff --git a/drivers/power/pda_power.c b/drivers/power/pda_power.c new file mode 100644 index 00000000..81b72010 --- /dev/null +++ b/drivers/power/pda_power.c @@ -0,0 +1,515 @@ +/* + * Common power driver for PDAs and phones with one or two external + * power supplies (AC/USB) connected to main and backup batteries, + * and optional builtin charger. + * + * Copyright © 2007 Anton Vorontsov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static inline unsigned int get_irq_flags(struct resource *res) +{ + unsigned int flags = IRQF_SAMPLE_RANDOM | IRQF_SHARED; + + flags |= res->flags & IRQF_TRIGGER_MASK; + + return flags; +} + +static struct device *dev; +static struct pda_power_pdata *pdata; +static struct resource *ac_irq, *usb_irq; +static struct timer_list charger_timer; +static struct timer_list supply_timer; +static struct timer_list polling_timer; +static int polling; + +static struct otg_transceiver *transceiver; +static struct notifier_block otg_nb; +static struct regulator *ac_draw; + +enum { + PDA_PSY_OFFLINE = 0, + PDA_PSY_ONLINE = 1, + PDA_PSY_TO_CHANGE, +}; +static int new_ac_status = -1; +static int new_usb_status = -1; +static int ac_status = -1; +static int usb_status = -1; + +static int pda_power_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + if (psy->type == POWER_SUPPLY_TYPE_MAINS) + val->intval = pdata->is_ac_online ? + pdata->is_ac_online() : 0; + else + val->intval = pdata->is_usb_online ? + pdata->is_usb_online() : 0; + break; + default: + return -EINVAL; + } + return 0; +} + +static enum power_supply_property pda_power_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static char *pda_power_supplied_to[] = { + "main-battery", + "backup-battery", +}; + +static struct power_supply pda_psy_ac = { + .name = "ac", + .type = POWER_SUPPLY_TYPE_MAINS, + .supplied_to = pda_power_supplied_to, + .num_supplicants = ARRAY_SIZE(pda_power_supplied_to), + .properties = pda_power_props, + .num_properties = ARRAY_SIZE(pda_power_props), + .get_property = pda_power_get_property, +}; + +static struct power_supply pda_psy_usb = { + .name = "usb", + .type = POWER_SUPPLY_TYPE_USB, + .supplied_to = pda_power_supplied_to, + .num_supplicants = ARRAY_SIZE(pda_power_supplied_to), + .properties = pda_power_props, + .num_properties = ARRAY_SIZE(pda_power_props), + .get_property = pda_power_get_property, +}; + +static void update_status(void) +{ + if (pdata->is_ac_online) + new_ac_status = !!pdata->is_ac_online(); + + if (pdata->is_usb_online) + new_usb_status = !!pdata->is_usb_online(); +} + +static void update_charger(void) +{ + static int regulator_enabled; + int max_uA = pdata->ac_max_uA; + + if (pdata->set_charge) { + if (new_ac_status > 0) { + dev_dbg(dev, "charger on (AC)\n"); + pdata->set_charge(PDA_POWER_CHARGE_AC); + } else if (new_usb_status > 0) { + dev_dbg(dev, "charger on (USB)\n"); + pdata->set_charge(PDA_POWER_CHARGE_USB); + } else { + dev_dbg(dev, "charger off\n"); + pdata->set_charge(0); + } + } else if (ac_draw) { + if (new_ac_status > 0) { + regulator_set_current_limit(ac_draw, max_uA, max_uA); + if (!regulator_enabled) { + dev_dbg(dev, "charger on (AC)\n"); + regulator_enable(ac_draw); + regulator_enabled = 1; + } + } else { + if (regulator_enabled) { + dev_dbg(dev, "charger off\n"); + regulator_disable(ac_draw); + regulator_enabled = 0; + } + } + } +} + +static void supply_timer_func(unsigned long unused) +{ + if (ac_status == PDA_PSY_TO_CHANGE) { + ac_status = new_ac_status; + power_supply_changed(&pda_psy_ac); + } + + if (usb_status == PDA_PSY_TO_CHANGE) { + usb_status = new_usb_status; + power_supply_changed(&pda_psy_usb); + } +} + +static void psy_changed(void) +{ + update_charger(); + + /* + * Okay, charger set. Now wait a bit before notifying supplicants, + * charge power should stabilize. + */ + mod_timer(&supply_timer, + jiffies + msecs_to_jiffies(pdata->wait_for_charger)); +} + +static void charger_timer_func(unsigned long unused) +{ + update_status(); + psy_changed(); +} + +static irqreturn_t power_changed_isr(int irq, void *power_supply) +{ + if (power_supply == &pda_psy_ac) + ac_status = PDA_PSY_TO_CHANGE; + else if (power_supply == &pda_psy_usb) + usb_status = PDA_PSY_TO_CHANGE; + else + return IRQ_NONE; + + /* + * Wait a bit before reading ac/usb line status and setting charger, + * because ac/usb status readings may lag from irq. + */ + mod_timer(&charger_timer, + jiffies + msecs_to_jiffies(pdata->wait_for_status)); + + return IRQ_HANDLED; +} + +static void polling_timer_func(unsigned long unused) +{ + int changed = 0; + + dev_dbg(dev, "polling...\n"); + + update_status(); + + if (!ac_irq && new_ac_status != ac_status) { + ac_status = PDA_PSY_TO_CHANGE; + changed = 1; + } + + if (!usb_irq && new_usb_status != usb_status) { + usb_status = PDA_PSY_TO_CHANGE; + changed = 1; + } + + if (changed) + psy_changed(); + + mod_timer(&polling_timer, + jiffies + msecs_to_jiffies(pdata->polling_interval)); +} + +#ifdef CONFIG_USB_OTG_UTILS +static int otg_is_usb_online(void) +{ + return (transceiver->last_event == USB_EVENT_VBUS || + transceiver->last_event == USB_EVENT_ENUMERATED); +} + +static int otg_is_ac_online(void) +{ + return (transceiver->last_event == USB_EVENT_CHARGER); +} + +static int otg_handle_notification(struct notifier_block *nb, + unsigned long event, void *unused) +{ + switch (event) { + case USB_EVENT_CHARGER: + ac_status = PDA_PSY_TO_CHANGE; + break; + case USB_EVENT_VBUS: + case USB_EVENT_ENUMERATED: + usb_status = PDA_PSY_TO_CHANGE; + break; + case USB_EVENT_NONE: + ac_status = PDA_PSY_TO_CHANGE; + usb_status = PDA_PSY_TO_CHANGE; + break; + default: + return NOTIFY_OK; + } + + /* + * Wait a bit before reading ac/usb line status and setting charger, + * because ac/usb status readings may lag from irq. + */ + mod_timer(&charger_timer, + jiffies + msecs_to_jiffies(pdata->wait_for_status)); + + return NOTIFY_OK; +} +#endif + +static int pda_power_probe(struct platform_device *pdev) +{ + int ret = 0; + + dev = &pdev->dev; + + if (pdev->id != -1) { + dev_err(dev, "it's meaningless to register several " + "pda_powers; use id = -1\n"); + ret = -EINVAL; + goto wrongid; + } + + pdata = pdev->dev.platform_data; + + if (pdata->init) { + ret = pdata->init(dev); + if (ret < 0) + goto init_failed; + } + + update_status(); + update_charger(); + + if (!pdata->wait_for_status) + pdata->wait_for_status = 500; + + if (!pdata->wait_for_charger) + pdata->wait_for_charger = 500; + + if (!pdata->polling_interval) + pdata->polling_interval = 2000; + + if (!pdata->ac_max_uA) + pdata->ac_max_uA = 500000; + + setup_timer(&charger_timer, charger_timer_func, 0); + setup_timer(&supply_timer, supply_timer_func, 0); + + ac_irq = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "ac"); + usb_irq = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "usb"); + + if (pdata->supplied_to) { + pda_psy_ac.supplied_to = pdata->supplied_to; + pda_psy_ac.num_supplicants = pdata->num_supplicants; + pda_psy_usb.supplied_to = pdata->supplied_to; + pda_psy_usb.num_supplicants = pdata->num_supplicants; + } + + ac_draw = regulator_get(dev, "ac_draw"); + if (IS_ERR(ac_draw)) { + dev_dbg(dev, "couldn't get ac_draw regulator\n"); + ac_draw = NULL; + ret = PTR_ERR(ac_draw); + } + + transceiver = otg_get_transceiver(); + if (transceiver && !pdata->is_usb_online) { + pdata->is_usb_online = otg_is_usb_online; + } + if (transceiver && !pdata->is_ac_online) { + pdata->is_ac_online = otg_is_ac_online; + } + + if (pdata->is_ac_online) { + ret = power_supply_register(&pdev->dev, &pda_psy_ac); + if (ret) { + dev_err(dev, "failed to register %s power supply\n", + pda_psy_ac.name); + goto ac_supply_failed; + } + + if (ac_irq) { + ret = request_irq(ac_irq->start, power_changed_isr, + get_irq_flags(ac_irq), ac_irq->name, + &pda_psy_ac); + if (ret) { + dev_err(dev, "request ac irq failed\n"); + goto ac_irq_failed; + } + } else { + polling = 1; + } + } + + if (pdata->is_usb_online) { + ret = power_supply_register(&pdev->dev, &pda_psy_usb); + if (ret) { + dev_err(dev, "failed to register %s power supply\n", + pda_psy_usb.name); + goto usb_supply_failed; + } + + if (usb_irq) { + ret = request_irq(usb_irq->start, power_changed_isr, + get_irq_flags(usb_irq), + usb_irq->name, &pda_psy_usb); + if (ret) { + dev_err(dev, "request usb irq failed\n"); + goto usb_irq_failed; + } + } else { + polling = 1; + } + } + + if (transceiver && pdata->use_otg_notifier) { + otg_nb.notifier_call = otg_handle_notification; + ret = otg_register_notifier(transceiver, &otg_nb); + if (ret) { + dev_err(dev, "failure to register otg notifier\n"); + goto otg_reg_notifier_failed; + } + polling = 0; + } + + if (polling) { + dev_dbg(dev, "will poll for status\n"); + setup_timer(&polling_timer, polling_timer_func, 0); + mod_timer(&polling_timer, + jiffies + msecs_to_jiffies(pdata->polling_interval)); + } + + if (ac_irq || usb_irq) + device_init_wakeup(&pdev->dev, 1); + + return 0; + +otg_reg_notifier_failed: + if (pdata->is_usb_online && usb_irq) + free_irq(usb_irq->start, &pda_psy_usb); +usb_irq_failed: + if (pdata->is_usb_online) + power_supply_unregister(&pda_psy_usb); +usb_supply_failed: + if (pdata->is_ac_online && ac_irq) + free_irq(ac_irq->start, &pda_psy_ac); + if (transceiver) + otg_put_transceiver(transceiver); +ac_irq_failed: + if (pdata->is_ac_online) + power_supply_unregister(&pda_psy_ac); +ac_supply_failed: + if (ac_draw) { + regulator_put(ac_draw); + ac_draw = NULL; + } + if (pdata->exit) + pdata->exit(dev); +init_failed: +wrongid: + return ret; +} + +static int pda_power_remove(struct platform_device *pdev) +{ + if (pdata->is_usb_online && usb_irq) + free_irq(usb_irq->start, &pda_psy_usb); + if (pdata->is_ac_online && ac_irq) + free_irq(ac_irq->start, &pda_psy_ac); + + if (polling) + del_timer_sync(&polling_timer); + del_timer_sync(&charger_timer); + del_timer_sync(&supply_timer); + + if (pdata->is_usb_online) + power_supply_unregister(&pda_psy_usb); + if (pdata->is_ac_online) + power_supply_unregister(&pda_psy_ac); +#ifdef CONFIG_USB_OTG_UTILS + if (transceiver) + otg_put_transceiver(transceiver); +#endif + if (ac_draw) { + regulator_put(ac_draw); + ac_draw = NULL; + } + if (pdata->exit) + pdata->exit(dev); + + return 0; +} + +#ifdef CONFIG_PM +static int ac_wakeup_enabled; +static int usb_wakeup_enabled; + +static int pda_power_suspend(struct platform_device *pdev, pm_message_t state) +{ + if (pdata->suspend) { + int ret = pdata->suspend(state); + + if (ret) + return ret; + } + + if (device_may_wakeup(&pdev->dev)) { + if (ac_irq) + ac_wakeup_enabled = !enable_irq_wake(ac_irq->start); + if (usb_irq) + usb_wakeup_enabled = !enable_irq_wake(usb_irq->start); + } + + return 0; +} + +static int pda_power_resume(struct platform_device *pdev) +{ + if (device_may_wakeup(&pdev->dev)) { + if (usb_irq && usb_wakeup_enabled) + disable_irq_wake(usb_irq->start); + if (ac_irq && ac_wakeup_enabled) + disable_irq_wake(ac_irq->start); + } + + if (pdata->resume) + return pdata->resume(); + + return 0; +} +#else +#define pda_power_suspend NULL +#define pda_power_resume NULL +#endif /* CONFIG_PM */ + +MODULE_ALIAS("platform:pda-power"); + +static struct platform_driver pda_power_pdrv = { + .driver = { + .name = "pda-power", + }, + .probe = pda_power_probe, + .remove = pda_power_remove, + .suspend = pda_power_suspend, + .resume = pda_power_resume, +}; + +static int __init pda_power_init(void) +{ + return platform_driver_register(&pda_power_pdrv); +} + +static void __exit pda_power_exit(void) +{ + platform_driver_unregister(&pda_power_pdrv); +} + +module_init(pda_power_init); +module_exit(pda_power_exit); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Anton Vorontsov "); diff --git a/drivers/power/pmu_battery.c b/drivers/power/pmu_battery.c new file mode 100644 index 00000000..023d2499 --- /dev/null +++ b/drivers/power/pmu_battery.c @@ -0,0 +1,216 @@ +/* + * Battery class driver for Apple PMU + * + * Copyright © 2006 David Woodhouse + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include + +static struct pmu_battery_dev { + struct power_supply bat; + struct pmu_battery_info *pbi; + char name[16]; + int propval; +} *pbats[PMU_MAX_BATTERIES]; + +#define to_pmu_battery_dev(x) container_of(x, struct pmu_battery_dev, bat) + +/********************************************************************* + * Power + *********************************************************************/ + +static int pmu_get_ac_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = (!!(pmu_power_flags & PMU_PWR_AC_PRESENT)) || + (pmu_battery_count == 0); + break; + default: + return -EINVAL; + } + + return 0; +} + +static enum power_supply_property pmu_ac_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static struct power_supply pmu_ac = { + .name = "pmu-ac", + .type = POWER_SUPPLY_TYPE_MAINS, + .properties = pmu_ac_props, + .num_properties = ARRAY_SIZE(pmu_ac_props), + .get_property = pmu_get_ac_prop, +}; + +/********************************************************************* + * Battery properties + *********************************************************************/ + +static char *pmu_batt_types[] = { + "Smart", "Comet", "Hooper", "Unknown" +}; + +static char *pmu_bat_get_model_name(struct pmu_battery_info *pbi) +{ + switch (pbi->flags & PMU_BATT_TYPE_MASK) { + case PMU_BATT_TYPE_SMART: + return pmu_batt_types[0]; + case PMU_BATT_TYPE_COMET: + return pmu_batt_types[1]; + case PMU_BATT_TYPE_HOOPER: + return pmu_batt_types[2]; + default: break; + } + return pmu_batt_types[3]; +} + +static int pmu_bat_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct pmu_battery_dev *pbat = to_pmu_battery_dev(psy); + struct pmu_battery_info *pbi = pbat->pbi; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (pbi->flags & PMU_BATT_CHARGING) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else if (pmu_power_flags & PMU_PWR_AC_PRESENT) + val->intval = POWER_SUPPLY_STATUS_FULL; + else + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = !!(pbi->flags & PMU_BATT_PRESENT); + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = pmu_bat_get_model_name(pbi); + break; + case POWER_SUPPLY_PROP_ENERGY_AVG: + val->intval = pbi->charge * 1000; /* mWh -> µWh */ + break; + case POWER_SUPPLY_PROP_ENERGY_FULL: + val->intval = pbi->max_charge * 1000; /* mWh -> µWh */ + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + val->intval = pbi->amperage * 1000; /* mA -> µA */ + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + val->intval = pbi->voltage * 1000; /* mV -> µV */ + break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: + val->intval = pbi->time_remaining; + break; + default: + return -EINVAL; + } + + return 0; +} + +static enum power_supply_property pmu_bat_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_ENERGY_AVG, + POWER_SUPPLY_PROP_ENERGY_FULL, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, +}; + +/********************************************************************* + * Initialisation + *********************************************************************/ + +static struct platform_device *bat_pdev; + +static int __init pmu_bat_init(void) +{ + int ret; + int i; + + bat_pdev = platform_device_register_simple("pmu-battery", + 0, NULL, 0); + if (IS_ERR(bat_pdev)) { + ret = PTR_ERR(bat_pdev); + goto pdev_register_failed; + } + + ret = power_supply_register(&bat_pdev->dev, &pmu_ac); + if (ret) + goto ac_register_failed; + + for (i = 0; i < pmu_battery_count; i++) { + struct pmu_battery_dev *pbat = kzalloc(sizeof(*pbat), + GFP_KERNEL); + if (!pbat) + break; + + sprintf(pbat->name, "PMU_battery_%d", i); + pbat->bat.name = pbat->name; + pbat->bat.properties = pmu_bat_props; + pbat->bat.num_properties = ARRAY_SIZE(pmu_bat_props); + pbat->bat.get_property = pmu_bat_get_property; + pbat->pbi = &pmu_batteries[i]; + + ret = power_supply_register(&bat_pdev->dev, &pbat->bat); + if (ret) { + kfree(pbat); + goto battery_register_failed; + } + pbats[i] = pbat; + } + + goto success; + +battery_register_failed: + while (i--) { + if (!pbats[i]) + continue; + power_supply_unregister(&pbats[i]->bat); + kfree(pbats[i]); + } + power_supply_unregister(&pmu_ac); +ac_register_failed: + platform_device_unregister(bat_pdev); +pdev_register_failed: +success: + return ret; +} + +static void __exit pmu_bat_exit(void) +{ + int i; + + for (i = 0; i < PMU_MAX_BATTERIES; i++) { + if (!pbats[i]) + continue; + power_supply_unregister(&pbats[i]->bat); + kfree(pbats[i]); + } + power_supply_unregister(&pmu_ac); + platform_device_unregister(bat_pdev); +} + +module_init(pmu_bat_init); +module_exit(pmu_bat_exit); + +MODULE_AUTHOR("David Woodhouse "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("PMU battery driver"); diff --git a/drivers/power/power_supply.h b/drivers/power/power_supply.h new file mode 100644 index 00000000..018de2b2 --- /dev/null +++ b/drivers/power/power_supply.h @@ -0,0 +1,38 @@ +/* + * Functions private to power supply class + * + * Copyright © 2007 Anton Vorontsov + * Copyright © 2004 Szabolcs Gyurko + * Copyright © 2003 Ian Molton + * + * Modified: 2004, Oct Szabolcs Gyurko + * + * You may use this code as per GPL version 2 + */ + +#ifdef CONFIG_SYSFS + +extern void power_supply_init_attrs(struct device_type *dev_type); +extern int power_supply_uevent(struct device *dev, struct kobj_uevent_env *env); + +#else + +static inline void power_supply_init_attrs(struct device_type *dev_type) {} +#define power_supply_uevent NULL + +#endif /* CONFIG_SYSFS */ + +#ifdef CONFIG_LEDS_TRIGGERS + +extern void power_supply_update_leds(struct power_supply *psy); +extern int power_supply_create_triggers(struct power_supply *psy); +extern void power_supply_remove_triggers(struct power_supply *psy); + +#else + +static inline void power_supply_update_leds(struct power_supply *psy) {} +static inline int power_supply_create_triggers(struct power_supply *psy) +{ return 0; } +static inline void power_supply_remove_triggers(struct power_supply *psy) {} + +#endif /* CONFIG_LEDS_TRIGGERS */ diff --git a/drivers/power/power_supply_core.c b/drivers/power/power_supply_core.c new file mode 100644 index 00000000..6e80146a --- /dev/null +++ b/drivers/power/power_supply_core.c @@ -0,0 +1,288 @@ +/* + * Universal power supply monitor class + * + * Copyright © 2007 Anton Vorontsov + * Copyright © 2004 Szabolcs Gyurko + * Copyright © 2003 Ian Molton + * + * Modified: 2004, Oct Szabolcs Gyurko + * + * You may use this code as per GPL version 2 + */ + +#include +#include +#include +#include +#include +#include +#include +#include "power_supply.h" + +/* exported for the APM Power driver, APM emulation */ +struct class *power_supply_class; +EXPORT_SYMBOL_GPL(power_supply_class); + +static struct device_type power_supply_dev_type; + +static int __power_supply_changed_work(struct device *dev, void *data) +{ + struct power_supply *psy = (struct power_supply *)data; + struct power_supply *pst = dev_get_drvdata(dev); + int i; + + for (i = 0; i < psy->num_supplicants; i++) + if (!strcmp(psy->supplied_to[i], pst->name)) { + if (pst->external_power_changed) + pst->external_power_changed(pst); + } + return 0; +} + +static void power_supply_changed_work(struct work_struct *work) +{ + unsigned long flags; + struct power_supply *psy = container_of(work, struct power_supply, + changed_work); + + dev_dbg(psy->dev, "%s\n", __func__); + + spin_lock_irqsave(&psy->changed_lock, flags); + if (psy->changed) { + psy->changed = false; + spin_unlock_irqrestore(&psy->changed_lock, flags); + + class_for_each_device(power_supply_class, NULL, psy, + __power_supply_changed_work); + + power_supply_update_leds(psy); + + kobject_uevent(&psy->dev->kobj, KOBJ_CHANGE); + spin_lock_irqsave(&psy->changed_lock, flags); + } + if (!psy->changed) + wake_unlock(&psy->work_wake_lock); + spin_unlock_irqrestore(&psy->changed_lock, flags); +} + +void power_supply_changed(struct power_supply *psy) +{ + unsigned long flags; + + dev_dbg(psy->dev, "%s\n", __func__); + + spin_lock_irqsave(&psy->changed_lock, flags); + psy->changed = true; + wake_lock(&psy->work_wake_lock); + spin_unlock_irqrestore(&psy->changed_lock, flags); + schedule_work(&psy->changed_work); +} +EXPORT_SYMBOL_GPL(power_supply_changed); + +static int __power_supply_am_i_supplied(struct device *dev, void *data) +{ + union power_supply_propval ret = {0,}; + struct power_supply *psy = (struct power_supply *)data; + struct power_supply *epsy = dev_get_drvdata(dev); + int i; + + for (i = 0; i < epsy->num_supplicants; i++) { + if (!strcmp(epsy->supplied_to[i], psy->name)) { + if (epsy->get_property(epsy, + POWER_SUPPLY_PROP_ONLINE, &ret)) + continue; + if (ret.intval) + return ret.intval; + } + } + return 0; +} + +int power_supply_am_i_supplied(struct power_supply *psy) +{ + int error; + + error = class_for_each_device(power_supply_class, NULL, psy, + __power_supply_am_i_supplied); + + dev_dbg(psy->dev, "%s %d\n", __func__, error); + + return error; +} +EXPORT_SYMBOL_GPL(power_supply_am_i_supplied); + +static int power_supply_find_supplier(struct device *dev, void *data) +{ + struct power_supply *psy = (struct power_supply *)data; + struct power_supply *epsy = dev_get_drvdata(dev); + int i; + + for (i = 0; i < epsy->num_supplicants; i++) + if (!strcmp(epsy->supplied_to[i], psy->name)) + return 1; + return 0; +} + +int power_supply_get_supplier_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct power_supply *epsy; + struct device *dev; + + dev = class_find_device(power_supply_class, NULL, psy, + power_supply_find_supplier); + if (!dev) + return 1; + + epsy = dev_get_drvdata(dev); + put_device(dev); + + return epsy->get_property(epsy, psp, val); +} +EXPORT_SYMBOL_GPL(power_supply_get_supplier_property); + +static int __power_supply_is_system_supplied(struct device *dev, void *data) +{ + union power_supply_propval ret = {0,}; + struct power_supply *psy = dev_get_drvdata(dev); + + if (psy->type != POWER_SUPPLY_TYPE_BATTERY) { + if (psy->get_property(psy, POWER_SUPPLY_PROP_ONLINE, &ret)) + return 0; + if (ret.intval) + return ret.intval; + } + return 0; +} + +int power_supply_is_system_supplied(void) +{ + int error; + + error = class_for_each_device(power_supply_class, NULL, NULL, + __power_supply_is_system_supplied); + + return error; +} +EXPORT_SYMBOL_GPL(power_supply_is_system_supplied); + +int power_supply_set_battery_charged(struct power_supply *psy) +{ + if (psy->type == POWER_SUPPLY_TYPE_BATTERY && psy->set_charged) { + psy->set_charged(psy); + return 0; + } + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(power_supply_set_battery_charged); + +static int power_supply_match_device_by_name(struct device *dev, void *data) +{ + const char *name = data; + struct power_supply *psy = dev_get_drvdata(dev); + + return strcmp(psy->name, name) == 0; +} + +struct power_supply *power_supply_get_by_name(char *name) +{ + struct device *dev = class_find_device(power_supply_class, NULL, name, + power_supply_match_device_by_name); + + return dev ? dev_get_drvdata(dev) : NULL; +} +EXPORT_SYMBOL_GPL(power_supply_get_by_name); + +static void power_supply_dev_release(struct device *dev) +{ + pr_debug("device: '%s': %s\n", dev_name(dev), __func__); + kfree(dev); +} + +int power_supply_register(struct device *parent, struct power_supply *psy) +{ + struct device *dev; + int rc; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + device_initialize(dev); + + dev->class = power_supply_class; + dev->type = &power_supply_dev_type; + dev->parent = parent; + dev->release = power_supply_dev_release; + dev_set_drvdata(dev, psy); + psy->dev = dev; + + INIT_WORK(&psy->changed_work, power_supply_changed_work); + + rc = kobject_set_name(&dev->kobj, "%s", psy->name); + if (rc) + goto kobject_set_name_failed; + + spin_lock_init(&psy->changed_lock); + wake_lock_init(&psy->work_wake_lock, WAKE_LOCK_SUSPEND, "power-supply"); + + rc = device_add(dev); + if (rc) + goto device_add_failed; + + rc = power_supply_create_triggers(psy); + if (rc) + goto create_triggers_failed; + + power_supply_changed(psy); + + goto success; + +create_triggers_failed: + wake_lock_destroy(&psy->work_wake_lock); + device_del(dev); +kobject_set_name_failed: +device_add_failed: + put_device(dev); +success: + return rc; +} +EXPORT_SYMBOL_GPL(power_supply_register); + +void power_supply_unregister(struct power_supply *psy) +{ + cancel_work_sync(&psy->changed_work); + power_supply_remove_triggers(psy); + wake_lock_destroy(&psy->work_wake_lock); + device_unregister(psy->dev); +} +EXPORT_SYMBOL_GPL(power_supply_unregister); + +static int __init power_supply_class_init(void) +{ + power_supply_class = class_create(THIS_MODULE, "power_supply"); + + if (IS_ERR(power_supply_class)) + return PTR_ERR(power_supply_class); + + power_supply_class->dev_uevent = power_supply_uevent; + power_supply_init_attrs(&power_supply_dev_type); + + return 0; +} + +static void __exit power_supply_class_exit(void) +{ + class_destroy(power_supply_class); +} + +subsys_initcall(power_supply_class_init); +module_exit(power_supply_class_exit); + +MODULE_DESCRIPTION("Universal power supply monitor class"); +MODULE_AUTHOR("Ian Molton , " + "Szabolcs Gyurko, " + "Anton Vorontsov "); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/power_supply_leds.c b/drivers/power/power_supply_leds.c new file mode 100644 index 00000000..da25eb94 --- /dev/null +++ b/drivers/power/power_supply_leds.c @@ -0,0 +1,179 @@ +/* + * LEDs triggers for power supply class + * + * Copyright © 2007 Anton Vorontsov + * Copyright © 2004 Szabolcs Gyurko + * Copyright © 2003 Ian Molton + * + * Modified: 2004, Oct Szabolcs Gyurko + * + * You may use this code as per GPL version 2 + */ + +#include +#include +#include + +#include "power_supply.h" + +/* Battery specific LEDs triggers. */ + +static void power_supply_update_bat_leds(struct power_supply *psy) +{ + union power_supply_propval status; + unsigned long delay_on = 0; + unsigned long delay_off = 0; + + if (psy->get_property(psy, POWER_SUPPLY_PROP_STATUS, &status)) + return; + + dev_dbg(psy->dev, "%s %d\n", __func__, status.intval); + + switch (status.intval) { + case POWER_SUPPLY_STATUS_FULL: + led_trigger_event(psy->charging_full_trig, LED_FULL); + led_trigger_event(psy->charging_trig, LED_OFF); + led_trigger_event(psy->full_trig, LED_FULL); + led_trigger_event(psy->charging_blink_full_solid_trig, + LED_FULL); + break; + case POWER_SUPPLY_STATUS_CHARGING: + led_trigger_event(psy->charging_full_trig, LED_FULL); + led_trigger_event(psy->charging_trig, LED_FULL); + led_trigger_event(psy->full_trig, LED_OFF); + led_trigger_blink(psy->charging_blink_full_solid_trig, + &delay_on, &delay_off); + break; + default: + led_trigger_event(psy->charging_full_trig, LED_OFF); + led_trigger_event(psy->charging_trig, LED_OFF); + led_trigger_event(psy->full_trig, LED_OFF); + led_trigger_event(psy->charging_blink_full_solid_trig, + LED_OFF); + break; + } +} + +static int power_supply_create_bat_triggers(struct power_supply *psy) +{ + int rc = 0; + + psy->charging_full_trig_name = kasprintf(GFP_KERNEL, + "%s-charging-or-full", psy->name); + if (!psy->charging_full_trig_name) + goto charging_full_failed; + + psy->charging_trig_name = kasprintf(GFP_KERNEL, + "%s-charging", psy->name); + if (!psy->charging_trig_name) + goto charging_failed; + + psy->full_trig_name = kasprintf(GFP_KERNEL, "%s-full", psy->name); + if (!psy->full_trig_name) + goto full_failed; + + psy->charging_blink_full_solid_trig_name = kasprintf(GFP_KERNEL, + "%s-charging-blink-full-solid", psy->name); + if (!psy->charging_blink_full_solid_trig_name) + goto charging_blink_full_solid_failed; + + led_trigger_register_simple(psy->charging_full_trig_name, + &psy->charging_full_trig); + led_trigger_register_simple(psy->charging_trig_name, + &psy->charging_trig); + led_trigger_register_simple(psy->full_trig_name, + &psy->full_trig); + led_trigger_register_simple(psy->charging_blink_full_solid_trig_name, + &psy->charging_blink_full_solid_trig); + + goto success; + +charging_blink_full_solid_failed: + kfree(psy->full_trig_name); +full_failed: + kfree(psy->charging_trig_name); +charging_failed: + kfree(psy->charging_full_trig_name); +charging_full_failed: + rc = -ENOMEM; +success: + return rc; +} + +static void power_supply_remove_bat_triggers(struct power_supply *psy) +{ + led_trigger_unregister_simple(psy->charging_full_trig); + led_trigger_unregister_simple(psy->charging_trig); + led_trigger_unregister_simple(psy->full_trig); + led_trigger_unregister_simple(psy->charging_blink_full_solid_trig); + kfree(psy->charging_blink_full_solid_trig_name); + kfree(psy->full_trig_name); + kfree(psy->charging_trig_name); + kfree(psy->charging_full_trig_name); +} + +/* Generated power specific LEDs triggers. */ + +static void power_supply_update_gen_leds(struct power_supply *psy) +{ + union power_supply_propval online; + + if (psy->get_property(psy, POWER_SUPPLY_PROP_ONLINE, &online)) + return; + + dev_dbg(psy->dev, "%s %d\n", __func__, online.intval); + + if (online.intval) + led_trigger_event(psy->online_trig, LED_FULL); + else + led_trigger_event(psy->online_trig, LED_OFF); +} + +static int power_supply_create_gen_triggers(struct power_supply *psy) +{ + int rc = 0; + + psy->online_trig_name = kasprintf(GFP_KERNEL, "%s-online", psy->name); + if (!psy->online_trig_name) + goto online_failed; + + led_trigger_register_simple(psy->online_trig_name, &psy->online_trig); + + goto success; + +online_failed: + rc = -ENOMEM; +success: + return rc; +} + +static void power_supply_remove_gen_triggers(struct power_supply *psy) +{ + led_trigger_unregister_simple(psy->online_trig); + kfree(psy->online_trig_name); +} + +/* Choice what triggers to create&update. */ + +void power_supply_update_leds(struct power_supply *psy) +{ + if (psy->type == POWER_SUPPLY_TYPE_BATTERY) + power_supply_update_bat_leds(psy); + else + power_supply_update_gen_leds(psy); +} + +int power_supply_create_triggers(struct power_supply *psy) +{ + if (psy->type == POWER_SUPPLY_TYPE_BATTERY) + return power_supply_create_bat_triggers(psy); + return power_supply_create_gen_triggers(psy); +} + +void power_supply_remove_triggers(struct power_supply *psy) +{ + if (psy->type == POWER_SUPPLY_TYPE_BATTERY) + power_supply_remove_bat_triggers(psy); + else + power_supply_remove_gen_triggers(psy); +} diff --git a/drivers/power/power_supply_sysfs.c b/drivers/power/power_supply_sysfs.c new file mode 100644 index 00000000..605514af --- /dev/null +++ b/drivers/power/power_supply_sysfs.c @@ -0,0 +1,305 @@ +/* + * Sysfs interface for the universal power supply monitor class + * + * Copyright © 2007 David Woodhouse + * Copyright © 2007 Anton Vorontsov + * Copyright © 2004 Szabolcs Gyurko + * Copyright © 2003 Ian Molton + * + * Modified: 2004, Oct Szabolcs Gyurko + * + * You may use this code as per GPL version 2 + */ + +#include +#include +#include + +#include "power_supply.h" + +/* + * This is because the name "current" breaks the device attr macro. + * The "current" word resolves to "(get_current())" so instead of + * "current" "(get_current())" appears in the sysfs. + * + * The source of this definition is the device.h which calls __ATTR + * macro in sysfs.h which calls the __stringify macro. + * + * Only modification that the name is not tried to be resolved + * (as a macro let's say). + */ + +#define POWER_SUPPLY_ATTR(_name) \ +{ \ + .attr = { .name = #_name }, \ + .show = power_supply_show_property, \ + .store = power_supply_store_property, \ +} + +static struct device_attribute power_supply_attrs[]; + +static ssize_t power_supply_show_property(struct device *dev, + struct device_attribute *attr, + char *buf) { + static char *type_text[] = { + "Battery", "UPS", "Mains", "USB", + "USB_DCP", "USB_CDP", "USB_ACA" + }; + static char *status_text[] = { + "Unknown", "Charging", "Discharging", "Not charging", "Full" + }; + static char *charge_type[] = { + "Unknown", "N/A", "Trickle", "Fast" + }; + static char *health_text[] = { + "Unknown", "Good", "Overheat", "Dead", "Over voltage", + "Unspecified failure", "Cold", + }; + static char *technology_text[] = { + "Unknown", "NiMH", "Li-ion", "Li-poly", "LiFe", "NiCd", + "LiMn" + }; + static char *capacity_level_text[] = { + "Unknown", "Critical", "Low", "Normal", "High", "Full" + }; + ssize_t ret = 0; + struct power_supply *psy = dev_get_drvdata(dev); + const ptrdiff_t off = attr - power_supply_attrs; + union power_supply_propval value; + + if (off == POWER_SUPPLY_PROP_TYPE) + value.intval = psy->type; + else + ret = psy->get_property(psy, off, &value); + + if (ret < 0) { + if (ret == -ENODATA) + dev_dbg(dev, "driver has no data for `%s' property\n", + attr->attr.name); + else if (ret != -ENODEV) + dev_err(dev, "driver failed to report `%s' property\n", + attr->attr.name); + return ret; + } + + if (off == POWER_SUPPLY_PROP_STATUS) + return sprintf(buf, "%s\n", status_text[value.intval]); + else if (off == POWER_SUPPLY_PROP_CHARGE_TYPE) + return sprintf(buf, "%s\n", charge_type[value.intval]); + else if (off == POWER_SUPPLY_PROP_HEALTH) + return sprintf(buf, "%s\n", health_text[value.intval]); + else if (off == POWER_SUPPLY_PROP_TECHNOLOGY) + return sprintf(buf, "%s\n", technology_text[value.intval]); + else if (off == POWER_SUPPLY_PROP_CAPACITY_LEVEL) + return sprintf(buf, "%s\n", capacity_level_text[value.intval]); + else if (off == POWER_SUPPLY_PROP_TYPE) + return sprintf(buf, "%s\n", type_text[value.intval]); + else if (off >= POWER_SUPPLY_PROP_MODEL_NAME) + return sprintf(buf, "%s\n", value.strval); + + return sprintf(buf, "%d\n", value.intval); +} + +static ssize_t power_supply_store_property(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) { + ssize_t ret; + struct power_supply *psy = dev_get_drvdata(dev); + const ptrdiff_t off = attr - power_supply_attrs; + union power_supply_propval value; + long long_val; + + /* TODO: support other types than int */ + ret = strict_strtol(buf, 10, &long_val); + if (ret < 0) + return ret; + + value.intval = long_val; + + ret = psy->set_property(psy, off, &value); + if (ret < 0) + return ret; + + return count; +} + +/* Must be in the same order as POWER_SUPPLY_PROP_* */ +static struct device_attribute power_supply_attrs[] = { + /* Properties of type `int' */ + POWER_SUPPLY_ATTR(status), + POWER_SUPPLY_ATTR(charge_type), + POWER_SUPPLY_ATTR(health), + POWER_SUPPLY_ATTR(present), + POWER_SUPPLY_ATTR(online), + POWER_SUPPLY_ATTR(technology), + POWER_SUPPLY_ATTR(cycle_count), + POWER_SUPPLY_ATTR(voltage_max), + POWER_SUPPLY_ATTR(voltage_min), + POWER_SUPPLY_ATTR(voltage_max_design), + POWER_SUPPLY_ATTR(voltage_min_design), + POWER_SUPPLY_ATTR(voltage_now), + POWER_SUPPLY_ATTR(voltage_avg), + POWER_SUPPLY_ATTR(current_max), + POWER_SUPPLY_ATTR(current_now), + POWER_SUPPLY_ATTR(current_avg), + POWER_SUPPLY_ATTR(power_now), + POWER_SUPPLY_ATTR(power_avg), + POWER_SUPPLY_ATTR(charge_full_design), + POWER_SUPPLY_ATTR(charge_empty_design), + POWER_SUPPLY_ATTR(charge_full), + POWER_SUPPLY_ATTR(charge_empty), + POWER_SUPPLY_ATTR(charge_now), + POWER_SUPPLY_ATTR(charge_avg), + POWER_SUPPLY_ATTR(charge_counter), + POWER_SUPPLY_ATTR(energy_full_design), + POWER_SUPPLY_ATTR(energy_empty_design), + POWER_SUPPLY_ATTR(energy_full), + POWER_SUPPLY_ATTR(energy_empty), + POWER_SUPPLY_ATTR(energy_now), + POWER_SUPPLY_ATTR(energy_avg), + POWER_SUPPLY_ATTR(capacity), + POWER_SUPPLY_ATTR(capacity_level), + POWER_SUPPLY_ATTR(temp), + POWER_SUPPLY_ATTR(temp_ambient), + POWER_SUPPLY_ATTR(time_to_empty_now), + POWER_SUPPLY_ATTR(time_to_empty_avg), + POWER_SUPPLY_ATTR(time_to_full_now), + POWER_SUPPLY_ATTR(time_to_full_avg), + POWER_SUPPLY_ATTR(type), + /* Properties of type `const char *' */ + POWER_SUPPLY_ATTR(model_name), + POWER_SUPPLY_ATTR(manufacturer), + POWER_SUPPLY_ATTR(serial_number), +}; + +static struct attribute * +__power_supply_attrs[ARRAY_SIZE(power_supply_attrs) + 1]; + +static mode_t power_supply_attr_is_visible(struct kobject *kobj, + struct attribute *attr, + int attrno) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct power_supply *psy = dev_get_drvdata(dev); + mode_t mode = S_IRUSR | S_IRGRP | S_IROTH; + int i; + + if (attrno == POWER_SUPPLY_PROP_TYPE) + return mode; + + for (i = 0; i < psy->num_properties; i++) { + int property = psy->properties[i]; + + if (property == attrno) { + if (psy->property_is_writeable && + psy->property_is_writeable(psy, property) > 0) + mode |= S_IWUSR; + + return mode; + } + } + + return 0; +} + +static struct attribute_group power_supply_attr_group = { + .attrs = __power_supply_attrs, + .is_visible = power_supply_attr_is_visible, +}; + +static const struct attribute_group *power_supply_attr_groups[] = { + &power_supply_attr_group, + NULL, +}; + +void power_supply_init_attrs(struct device_type *dev_type) +{ + int i; + + dev_type->groups = power_supply_attr_groups; + + for (i = 0; i < ARRAY_SIZE(power_supply_attrs); i++) + __power_supply_attrs[i] = &power_supply_attrs[i].attr; +} + +static char *kstruprdup(const char *str, gfp_t gfp) +{ + char *ret, *ustr; + + ustr = ret = kmalloc(strlen(str) + 1, gfp); + + if (!ret) + return NULL; + + while (*str) + *ustr++ = toupper(*str++); + + *ustr = 0; + + return ret; +} + +int power_supply_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct power_supply *psy = dev_get_drvdata(dev); + int ret = 0, j; + char *prop_buf; + char *attrname; + + dev_dbg(dev, "uevent\n"); + + if (!psy || !psy->dev) { + dev_dbg(dev, "No power supply yet\n"); + return ret; + } + + dev_dbg(dev, "POWER_SUPPLY_NAME=%s\n", psy->name); + + ret = add_uevent_var(env, "POWER_SUPPLY_NAME=%s", psy->name); + if (ret) + return ret; + + prop_buf = (char *)get_zeroed_page(GFP_KERNEL); + if (!prop_buf) + return -ENOMEM; + + for (j = 0; j < psy->num_properties; j++) { + struct device_attribute *attr; + char *line; + + attr = &power_supply_attrs[psy->properties[j]]; + + ret = power_supply_show_property(dev, attr, prop_buf); + if (ret == -ENODEV || ret == -ENODATA) { + /* When a battery is absent, we expect -ENODEV. Don't abort; + send the uevent with at least the the PRESENT=0 property */ + ret = 0; + continue; + } + + if (ret < 0) + goto out; + + line = strchr(prop_buf, '\n'); + if (line) + *line = 0; + + attrname = kstruprdup(attr->attr.name, GFP_KERNEL); + if (!attrname) { + ret = -ENOMEM; + goto out; + } + + dev_dbg(dev, "prop %s=%s\n", attrname, prop_buf); + + ret = add_uevent_var(env, "POWER_SUPPLY_%s=%s", attrname, prop_buf); + kfree(attrname); + if (ret) + goto out; + } + +out: + free_page((unsigned long)prop_buf); + + return ret; +} diff --git a/drivers/power/ricoh619-battery.c b/drivers/power/ricoh619-battery.c new file mode 100755 index 00000000..0185771e --- /dev/null +++ b/drivers/power/ricoh619-battery.c @@ -0,0 +1,6361 @@ +/* + * drivers/power/ricoh619-battery.c + * + * Charger driver for RICOH R5T619 power management chip. + * + * Copyright (C) 2012-2014 RICOH COMPANY,LTD + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#define RICOH61x_BATTERY_VERSION "RICOH61x_BATTERY_VERSION: 2014.02.21 V3.1.0.0-Solution1 2015/02/09" + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../../../arch/arm/mach-mx6/ntx_hwconfig.h" +extern volatile NTX_HWCONFIG *gptHWCFG; + +/* define for function */ +#define ENABLE_FUEL_GAUGE_FUNCTION +#define ENABLE_LOW_BATTERY_DETECTION +#define ENABLE_FACTORY_MODE +#define DISABLE_CHARGER_TIMER +/* #define ENABLE_FG_KEEP_ON_MODE */ +#define ENABLE_OCV_TABLE_CALIB +/* #define ENABLE_MASKING_INTERRUPT_IN_SLEEP */ + +#define ENABLE_BATTERY_TEMP_DETECTION +#define LOW_BATTERY_TEMP_VOL 1824 // 0 degree 269.96K, 2.5V * 270K / (270K+100K) +#define HIGH_BATTERY_TEMP_VOL 577 // 60 degree 30.546K, 2.5V * 30K / (30K+100K) + +#define _RICOH619_DEBUG_ +#define LTS_DEBUG +//#define STANDBY_MODE_DEBUG +//#define CHANGE_FL_MODE_DEBUG + +/* FG setting */ +#define RICOH61x_REL1_SEL_VALUE 64 +#define RICOH61x_REL2_SEL_VALUE 0 + +enum int_type { + SYS_INT = 0x01, + DCDC_INT = 0x02, + ADC_INT = 0x08, + GPIO_INT = 0x10, + CHG_INT = 0x40, +}; + +#ifdef ENABLE_FUEL_GAUGE_FUNCTION +/* define for FG delayed time */ +#define RICOH61x_MONITOR_START_TIME 15 +#define RICOH61x_FG_RESET_TIME 6 +#define RICOH61x_MAIN_START_TIME 2 +#define RICOH61x_FG_STABLE_TIME 120 +#define RICOH61x_DISP_CHG_UPDATE_TIME 10 +#define RICOH61x_DISPLAY_UPDATE_TIME 29 +#define RICOH61x_LOW_VOL_DOWN_TIME 60 //10 +#define RICOH61x_CHARGE_MONITOR_TIME 19 +#define RICOH61x_CHARGE_RESUME_TIME 1 +#define RICOH61x_CHARGE_CALC_TIME 1 +#define RICOH61x_JEITA_UPDATE_TIME 60 +#define RICOH61x_DELAY_TIME 40 /* 120 */ +/* define for FG parameter */ +#define RICOH61x_MAX_RESET_SOC_DIFF 5 +#define RICOH61x_GET_CHARGE_NUM 10 +#define RICOH61x_UPDATE_COUNT_DISP 4 +#define RICOH61x_UPDATE_COUNT_FULL 4 +#define RICOH61x_UPDATE_COUNT_FULL_RESET 4 +#define RICOH61x_CHARGE_UPDATE_TIME 3 +#define RE_CAP_GO_DOWN 10 /* 40 */ +#define RICOH61x_ENTER_LOW_VOL 70 +#define RICOH61x_TAH_SEL2 5 +#define RICOH61x_TAL_SEL2 6 + +#define RICOH61x_OCV_OFFSET_BOUND 3 +#define RICOH61x_OCV_OFFSET_RATIO 2 + +#define RICOH61x_ENTER_FULL_STATE_OCV 9 +#define RICOH61x_ENTER_FULL_STATE_DSOC 85 /* 90 */ + +#define RICOH61x_FL_LEVEL_DEF 70 // 70% +#define RICOH61x_FL_CURRENT_DEF 29593 // 29.593mA(70%) +#define RICOH61x_IDLE_CURRENT_DEF 20000 // 20mA +#define RICOH61x_SUS_CURRENT_DEF 3000 // 3mA +#define RICOH61x_SUS_CURRENT_THRESH 20000 // 20mA +#define RICOH61x_HIBER_CURRENT_DEF 800 // 0.8mA +#define RICOH61x_FL_CURRENT_LIMIT 150000 // 150mA +#define RICOH61x_SLEEP_CURRENT_LIMIT 50000 // 50mA + +#define ORIGINAL 0 +#define USING 1 + + +/* define for FG status */ +enum { + RICOH61x_SOCA_START, + RICOH61x_SOCA_UNSTABLE, + RICOH61x_SOCA_FG_RESET, + RICOH61x_SOCA_DISP, + RICOH61x_SOCA_STABLE, + RICOH61x_SOCA_ZERO, + RICOH61x_SOCA_FULL, + RICOH61x_SOCA_LOW_VOL, +}; + +/* table of dividing charge current */ +#define RICOH61x_IBAT_TABLE_NUM 16 +static int ibat_table[RICOH61x_IBAT_TABLE_NUM] /* 85% - 100% */ + = {370, 348, 326, 304, 282, 260, 238, 216, 194, 172, 150, 128, 107, 87, 68, 50}; + +#endif + +#ifdef ENABLE_LOW_BATTERY_DETECTION +#define LOW_BATTERY_DETECTION_TIME 0 //10 +#endif + +struct ricoh61x_soca_info { + int Rbat; + int n_cap; + int ocv_table_def[11]; + int ocv_table[11]; + int ocv_table_low[11]; + uint8_t battery_init_para_original[32]; + int soc; /* Latest FG SOC value */ + int displayed_soc; + int suspend_soc; + int suspend_cc; + int suspend_rsoc; + bool suspend_full_flg; + int status; /* SOCA status 0: Not initial; 5: Finished */ + int stable_count; + int chg_status; /* chg_status */ + int soc_delta; /* soc delta for status3(DISP) */ + int cc_delta; + int cc_cap_offset; + long sus_cc_cap_offset; + int last_soc; + int last_displayed_soc; + int last_cc_rrf0; + int last_cc_delta_cap; + long last_cc_delta_cap_mas; + long temp_cc_delta_cap_mas; + int temp_cc_delta_cap; + int ready_fg; + int reset_count; + int reset_soc[3]; + int dischg_state; + int Vbat[RICOH61x_GET_CHARGE_NUM]; + int Vsys[RICOH61x_GET_CHARGE_NUM]; + int Ibat[RICOH61x_GET_CHARGE_NUM]; + int Vbat_ave; + int Vbat_old; + int Vsys_ave; + int Ibat_ave; + int chg_count; + int full_reset_count; + int soc_full; + int fc_cap; + /* for LOW VOL state */ + int hurry_up_flg; + int zero_flg; + int re_cap_old; + + int cutoff_ocv; + int Rsys; + int target_vsys; + int target_ibat; + int jt_limit; + int OCV100_min; + int OCV100_max; + int R_low; + int rsoc_ready_flag; + int init_pswr; + int last_soc_full; + int rsoc_limit; + int critical_low_flag; + + int store_fl_current; + int store_slp_state; + int store_sus_current; + int store_hiber_current; +}; + +static int critical_low_flag = 0; + +struct ricoh61x_battery_info { + struct device *dev; + struct power_supply battery; + struct delayed_work monitor_work; + struct delayed_work displayed_work; + struct delayed_work charge_stable_work; + struct delayed_work changed_work; +#ifdef ENABLE_LOW_BATTERY_DETECTION + struct delayed_work low_battery_work; +#endif +#ifdef ENABLE_BATTERY_TEMP_DETECTION + struct delayed_work battery_temp_work; +#endif + struct delayed_work charge_monitor_work; + struct delayed_work get_charge_work; + struct delayed_work jeita_work; + + struct work_struct irq_work; /* for Charging & VADP/VUSB */ + + struct workqueue_struct *monitor_wqueue; + struct workqueue_struct *workqueue; /* for Charging & VUSB/VADP */ + +#ifdef ENABLE_FACTORY_MODE + struct delayed_work factory_mode_work; + struct workqueue_struct *factory_mode_wqueue; +#endif + + struct mutex lock; + unsigned long monitor_time; + int adc_vdd_mv; + int multiple; + int alarm_vol_mv; + int status; + int min_voltage; + int max_voltage; + int cur_voltage; + int capacity; + int battery_temp; + int time_to_empty; + int time_to_full; + int chg_ctr; + int chg_stat1; + unsigned present:1; + u16 delay; + struct ricoh61x_soca_info *soca; + int first_pwon; + bool entry_factory_mode; + int ch_vfchg; + int ch_vrchg; + int ch_vbatovset; + int ch_ichg; + int ch_ilim_adp; + int ch_ilim_usb; + int ch_icchg; + int fg_target_vsys; + int fg_target_ibat; + int fg_poff_vbat; + int jt_en; + int jt_hw_sw; + int jt_temp_h; + int jt_temp_l; + int jt_vfchg_h; + int jt_vfchg_l; + int jt_ichg_h; + int jt_ichg_l; + bool suspend_state; + bool stop_disp; + unsigned long sleepEntryTime; + unsigned long sleepExitTime; + + int num; + }; + +int g_full_flag; +int charger_irq; +int g_soc; +int g_fg_on_mode; + +#ifdef STANDBY_MODE_DEBUG +int multiple_sleep_mode; +#endif + +/*This is for full state*/ +static int BatteryTableFlageDef=0; +static int BatteryTypeDef=0; +static int Battery_Type(void) +{ + return BatteryTypeDef; +} + +static int Battery_Table(void) +{ + return BatteryTableFlageDef; +} + +static void ricoh61x_battery_work(struct work_struct *work) +{ + struct ricoh61x_battery_info *info = container_of(work, + struct ricoh61x_battery_info, monitor_work.work); + +// printk(KERN_INFO "PMU: %s\n", __func__); + power_supply_changed(&info->battery); + queue_delayed_work(info->monitor_wqueue, &info->monitor_work, + info->monitor_time); +} + +#define RTC_SEC_REG 0xA0 +static void get_current_time(struct ricoh61x_battery_info *info, + unsigned long *seconds) +{ + struct rtc_time tm; + u8 buff[7]; + int err; + int cent_flag; + + /* get_rtc_time(&tm); */ + err = ricoh61x_bulk_reads(info->dev->parent, RTC_SEC_REG, sizeof(buff), buff); + if (err < 0) { + dev_err(info->dev, "PMU: %s *** failed to read time *****\n", __func__); + return; + } + if (buff[5] & 0x80) + cent_flag = 1; + else + cent_flag = 0; + + tm.tm_sec = bcd2bin(buff[0] & 0x7f); + tm.tm_min = bcd2bin(buff[1] & 0x7f); + tm.tm_hour = bcd2bin(buff[2] & 0x3f); /* 24h */ + tm.tm_wday = bcd2bin(buff[3] & 0x07); + tm.tm_mday = bcd2bin(buff[4] & 0x3f); + tm.tm_mon = bcd2bin(buff[5] & 0x1f) - 1;/* back to system 0-11 */ + tm.tm_year = bcd2bin(buff[6]) + 100 * cent_flag; + + dev_dbg(info->dev, "rtc-time : Mon/ Day/ Year H:M:S\n"); + dev_dbg(info->dev, " : %d/%d/%d %d:%d:%d\n", + (tm.tm_mon+1), tm.tm_mday, (tm.tm_year + 1900), + tm.tm_hour, tm.tm_min, tm.tm_sec); + + rtc_tm_to_time(&tm, seconds); +} + +/** +* Enable test register of Bank1 +* +* info : battery info +* +* return value : +* true : Removing Protect correctly +* false : not Removing protect +*/ +static bool Enable_Test_Register(struct ricoh61x_battery_info *info){ + int ret; + uint8_t val = 0x01; + uint8_t val_backUp; + uint8_t val2; + + //Remove protect of test register + ret = ricoh61x_write_bank1(info->dev->parent, BAT_TEST_EN_REG, 0xaa); + ret += ricoh61x_write_bank1(info->dev->parent, BAT_TEST_EN_REG, 0x55); + ret += ricoh61x_write_bank1(info->dev->parent, BAT_TEST_EN_REG, 0xaa); + ret += ricoh61x_write_bank1(info->dev->parent, BAT_TEST_EN_REG, 0x55); + if (ret < 0) { + dev_err(info->dev, "Error in writing BAT_TEST_EN_REG\n"); + return false; + } + + //Check protect is removed or not + ret = ricoh61x_read_bank1(info->dev->parent, BAT_ADD1B2_REG, &val_backUp); + ret += ricoh61x_write_bank1(info->dev->parent, BAT_ADD1B2_REG, val); + ret += ricoh61x_read_bank1(info->dev->parent, BAT_ADD1B2_REG, &val2); + ret += ricoh61x_write_bank1(info->dev->parent, BAT_ADD1B2_REG, val_backUp); + if (ret < 0) { + dev_err(info->dev, "Error in writing BAT_ADD1B2_REG\n"); + return false; + } + + if(val == val2){ + return true; + } else { + return false; + } + + return false; +} + +/** +* check can write correctly or not +* +* regAddr : register address +* targetValue : target value for write +* bank_num : bank number +* +* return : ture or false +*/ +static bool write_and_check_read_back(struct ricoh61x_battery_info *info, u8 regAddr, uint8_t targetValue, int bank_num) +{ + int ret; + uint8_t val; + + //Check protect is removed or not + if(bank_num == 0){ + ret = ricoh61x_write(info->dev->parent, regAddr, targetValue); + ret += ricoh61x_read(info->dev->parent, regAddr, &val); + if (ret < 0) { + dev_err(info->dev, "Error in writing in 0x%d\n",regAddr); + return false; + } + } else { + ret = ricoh61x_write_bank1(info->dev->parent, regAddr, targetValue); + ret += ricoh61x_read_bank1(info->dev->parent, regAddr, &val); + if (ret < 0) { + dev_err(info->dev, "Error in writing in 0x%d\n",regAddr); + return false; + } + } + + if(targetValue == val){ + return true; + } else { + return false; + } +} +/** +* get stored time from register +* 0xB2 : bit 0 ~ 7 +* 0xB3 : bit 8 ~ 15 +* 0xDD : bit 16 ~ 23 +* +* info : battery info +* +* return sored time unit is hour +*/ +static unsigned long get_storedTime_from_register(struct ricoh61x_battery_info *info) +{ + unsigned long hour = 0; + uint8_t val; + int ret; + + ret = ricoh61x_read_bank1(info->dev->parent, BAT_ADD1B2_REG, &val); + hour += val; + ret = ricoh61x_read_bank1(info->dev->parent, BAT_ADD1B3_REG, &val); + hour += val << 8; + ret = ricoh61x_read_bank1(info->dev->parent, BAT_ADD1DD_REG, &val); + hour += val << 16; + + + return hour; +} + + +/** +* Set current RTC time to Register. unit is hour +* 0xB2 : bit 0 ~ 7 +* 0xB3 : bit 8 ~ 15 +* 0xDD : bit 16 ~ 23 +* +* info : battery info +* +* return +*/ +static void set_current_time2register(struct ricoh61x_battery_info *info) +{ + unsigned long hour; + unsigned long seconds; + int loop_counter = 0; + bool canWriteFlag = true; + uint8_t val; + + // + get_current_time(info, &seconds); + hour = seconds / 3600; + printk("PMU : %s : second is %lu, hour is %lu\n",__func__, seconds, hour); + + do{ + val = hour & 0xff; + canWriteFlag &= write_and_check_read_back(info, BAT_ADD1B2_REG, val, 1); + val = (hour >> 8) & 0xff; + canWriteFlag &= write_and_check_read_back(info, BAT_ADD1B3_REG, val, 1); + val = (hour >> 16) & 0xff; + canWriteFlag &= write_and_check_read_back(info, BAT_ADD1DD_REG, val, 1); + + if(canWriteFlag != true){ + Enable_Test_Register(info); + loop_counter++; + } + + //read back + if(loop_counter > 5){ + canWriteFlag = true; + } + }while(canWriteFlag == false); + + return; +} + +#ifdef ENABLE_FUEL_GAUGE_FUNCTION +static int measure_vbatt_FG(struct ricoh61x_battery_info *info, int *data); +static int measure_Ibatt_FG(struct ricoh61x_battery_info *info, int *data); +static int calc_capacity(struct ricoh61x_battery_info *info); +static int calc_capacity_2(struct ricoh61x_battery_info *info); +static int get_OCV_init_Data(struct ricoh61x_battery_info *info, int index, int table_num); +static int get_OCV_voltage(struct ricoh61x_battery_info *info, int index, int table_num); +static int get_check_fuel_gauge_reg(struct ricoh61x_battery_info *info, + int Reg_h, int Reg_l, int enable_bit); +static int calc_capacity_in_period(struct ricoh61x_battery_info *info, + int *cc_cap, long *cc_cap_mas, bool *is_charging, int cc_rst); +static int get_charge_priority(struct ricoh61x_battery_info *info, bool *data); +static int set_charge_priority(struct ricoh61x_battery_info *info, bool *data); +static int get_power_supply_status(struct ricoh61x_battery_info *info); +static int get_power_supply_Android_status(struct ricoh61x_battery_info *info); +static int measure_vsys_ADC(struct ricoh61x_battery_info *info, int *data); +static int Calc_Linear_Interpolation(int x0, int y0, int x1, int y1, int y); +static int get_battery_temp(struct ricoh61x_battery_info *info); +static int get_battery_temp_2(struct ricoh61x_battery_info *info); +static int check_jeita_status(struct ricoh61x_battery_info *info, bool *is_jeita_updated); +static void ricoh61x_scaling_OCV_table(struct ricoh61x_battery_info *info, int cutoff_vol, int full_vol, int *start_per, int *end_per); +static int ricoh61x_Check_OCV_Offset(struct ricoh61x_battery_info *info); +static void mainFlowOfLowVoltage(struct ricoh61x_battery_info *info); +static void initSettingOfLowVoltage(struct ricoh61x_battery_info *info); +static int getCapFromOriTable(struct ricoh61x_battery_info *info, int voltage, int currentvalue, int resvalue); +static int getCapFromOriTable_U10per(struct ricoh61x_battery_info *info, int voltage, int currentvalue, int resvalue); + +static int calc_ocv(struct ricoh61x_battery_info *info) +{ + int Vbat = 0; + int Ibat = 0; + int ret; + int ocv; + + ret = measure_vbatt_FG(info, &Vbat); + ret = measure_Ibatt_FG(info, &Ibat); + + ocv = Vbat - Ibat * info->soca->Rbat; + + return ocv; +} + +static int calc_soc_on_ocv(struct ricoh61x_battery_info *info, int Ocv) +{ + int i; + int capacity=0; + + /* capacity is 0.01% unit */ + if (info->soca->ocv_table[0] >= Ocv) { + capacity = 1 * 100; + } else if (info->soca->ocv_table[10] <= Ocv) { + capacity = 100 * 100; + } else { + for (i = 1; i < 11; i++) { + if (info->soca->ocv_table[i] >= Ocv) { + /* unit is 0.01% */ + capacity = Calc_Linear_Interpolation( + (i-1)*10 * 100, info->soca->ocv_table[i-1], i*10 * 100, + info->soca->ocv_table[i], Ocv); + if(capacity < 100){ + capacity = 100; + } + break; + } + } + } + + printk(KERN_INFO "PMU: %s capacity(%d)\n", + __func__, capacity); + + return capacity; +} + +static int set_Rlow(struct ricoh61x_battery_info *info) +{ + int err; + int Rbat_low_max; + uint8_t val; + int Vocv; + int temp; + + if (info->soca->Rbat == 0) + info->soca->Rbat = get_OCV_init_Data(info, 12, USING) * 1000 / 512 + * 5000 / 4095; + + Vocv = calc_ocv(info); + Rbat_low_max = info->soca->Rbat * 1.5; + + if (Vocv < get_OCV_voltage(info,3, USING)) + { + info->soca->R_low = Calc_Linear_Interpolation(info->soca->Rbat,get_OCV_voltage(info,3,USING), + Rbat_low_max, get_OCV_voltage(info,0,USING), Vocv); +#ifdef _RICOH619_DEBUG_ + printk(KERN_DEBUG"PMU: Modify RBAT from %d to %d ", info->soca->Rbat, info->soca->R_low); +#endif + temp = info->soca->R_low *4095/5000*512/1000; + + val = temp >> 8; + err = ricoh61x_write_bank1(info->dev->parent, 0xD4, val); + if (err < 0) { + dev_err(info->dev, "batterry initialize error\n"); + return err; + } + + val = info->soca->R_low & 0xff; + err = ricoh61x_write_bank1(info->dev->parent, 0xD5, val); + if (err < 0) { + dev_err(info->dev, "batterry initialize error\n"); + return err; + } + } + else info->soca->R_low = 0; + + + return err; +} + +static int Set_back_ocv_table(struct ricoh61x_battery_info *info) +{ + int err; + uint8_t val; + int temp; + int i; + uint8_t debug_disp[22]; + + /* Modify back ocv table */ + + if (0 != info->soca->ocv_table_low[0]) + { + for (i = 0 ; i < 11; i++){ + battery_init_para[info->num][i*2 + 1] = info->soca->ocv_table_low[i]; + battery_init_para[info->num][i*2] = info->soca->ocv_table_low[i] >> 8; + } + err = ricoh61x_clr_bits(info->dev->parent, FG_CTRL_REG, 0x01); + + err = ricoh61x_bulk_writes_bank1(info->dev->parent, + BAT_INIT_TOP_REG, 22, battery_init_para[info->num]); + + err = ricoh61x_set_bits(info->dev->parent, FG_CTRL_REG, 0x01); + + /* debug comment start*/ + err = ricoh61x_bulk_reads_bank1(info->dev->parent, + BAT_INIT_TOP_REG, 22, debug_disp); + for (i = 0; i < 11; i++){ + printk("PMU : %s : after OCV table %d 0x%x\n",__func__, i * 10, (debug_disp[i*2] << 8 | debug_disp[i*2+1])); + } + /* end */ + /* clear table*/ + for(i = 0; i < 11; i++) + { + info->soca->ocv_table_low[i] = 0; + } + } + + /* Modify back Rbat */ + if (0!=info->soca->R_low) + { + printk("PMU: Modify back RBAT from %d to %d ", info->soca->R_low,info->soca->Rbat); + temp = info->soca->Rbat*4095/5000*512/1000; + + val = temp >> 8; + err = ricoh61x_write_bank1(info->dev->parent, 0xD4, val); + if (err < 0) { + dev_err(info->dev, "batterry initialize error\n"); + return err; + } + + val = info->soca->R_low & 0xff; + err = ricoh61x_write_bank1(info->dev->parent, 0xD5, val); + if (err < 0) { + dev_err(info->dev, "batterry initialize error\n"); + return err; + } + + info->soca->R_low = 0; + } + return 0; +} + +/** +**/ +static int ricoh61x_Check_OCV_Offset(struct ricoh61x_battery_info *info) +{ + int ocv_table[11]; // HEX value + int i; + int temp; + int ret; + uint8_t debug_disp[22]; + uint8_t val = 0; + + printk("PMU : %s : calc ocv %d get OCV %d\n",__func__,calc_ocv(info),get_OCV_voltage(info, RICOH61x_OCV_OFFSET_BOUND,USING)); + + /* check adp/usb status */ + ret = ricoh61x_read(info->dev->parent, CHGSTATE_REG, &val); + if (ret < 0) { + dev_err(info->dev, "Error in reading the control register\n"); + return 0; + } + + val = (val & 0xC0) >> 6; + + if (val != 0){ /* connect adp or usb */ + if (calc_ocv(info) < get_OCV_voltage(info, RICOH61x_OCV_OFFSET_BOUND,USING) ) + { + if(0 == info->soca->ocv_table_low[0]){ + for (i = 0 ; i < 11; i++){ + ocv_table[i] = (battery_init_para[info->num][i*2]<<8) | (battery_init_para[info->num][i*2+1]); + printk("PMU : %s : OCV table %d 0x%x\n",__func__,i * 10, ocv_table[i]); + info->soca->ocv_table_low[i] = ocv_table[i]; + } + + for (i = 0 ; i < 11; i++){ + temp = ocv_table[i] * (100 + RICOH61x_OCV_OFFSET_RATIO) / 100; + + battery_init_para[info->num][i*2 + 1] = temp; + battery_init_para[info->num][i*2] = temp >> 8; + } + ret = ricoh61x_clr_bits(info->dev->parent, FG_CTRL_REG, 0x01); + + ret = ricoh61x_bulk_writes_bank1(info->dev->parent, + BAT_INIT_TOP_REG, 22, battery_init_para[info->num]); + + ret = ricoh61x_set_bits(info->dev->parent, FG_CTRL_REG, 0x01); + + /* debug comment start*/ + ret = ricoh61x_bulk_reads_bank1(info->dev->parent, + BAT_INIT_TOP_REG, 22, debug_disp); + for (i = 0; i < 11; i++){ + printk("PMU : %s : after OCV table %d 0x%x\n",__func__, i * 10, (debug_disp[i*2] << 8 | debug_disp[i*2+1])); + } + /* end */ + } + } + } + + return 0; +} + +static int reset_FG_process(struct ricoh61x_battery_info *info) +{ + int err; + + //err = set_Rlow(info); + //err = ricoh61x_Check_OCV_Offset(info); + err = ricoh61x_write(info->dev->parent, + FG_CTRL_REG, 0x51); + info->soca->ready_fg = 0; + info->soca->rsoc_ready_flag = 1; + + return err; +} + + +static int check_charge_status_2(struct ricoh61x_battery_info *info, int displayed_soc_temp) +{ + if (displayed_soc_temp < 0) + displayed_soc_temp = 0; + + get_power_supply_status(info); + info->soca->soc = calc_capacity(info) * 100; + + if (POWER_SUPPLY_STATUS_FULL == info->soca->chg_status) { + if ((info->first_pwon == 1) + && (RICOH61x_SOCA_START == info->soca->status)) { + g_full_flag = 1; + info->soca->soc_full = info->soca->soc; + info->soca->displayed_soc = 100*100; + info->soca->full_reset_count = 0; + } else { + if (calc_ocv(info) > get_OCV_voltage(info, 9,USING)){ + g_full_flag = 1; + info->soca->soc_full = info->soca->soc; + info->soca->displayed_soc = 100*100; + info->soca->full_reset_count = 0; + } else { + g_full_flag = 0; + info->soca->displayed_soc = displayed_soc_temp; + printk(KERN_INFO "PMU: %s Charge Complete but OCV is low\n", __func__); + } + + } + } + if (info->soca->Ibat_ave >= 0) { + if (g_full_flag == 1) { + info->soca->displayed_soc = 100*100; + } else { + info->soca->displayed_soc = min(9949, displayed_soc_temp); + } + } + if (info->soca->Ibat_ave < 0) { + if (g_full_flag == 1) { + if (calc_ocv(info) < get_OCV_voltage(info, 9, USING) + (get_OCV_voltage(info, 10, USING) - get_OCV_voltage(info, 9, USING))*7/10) { + g_full_flag = 0; + //info->soca->displayed_soc = 100*100; + info->soca->displayed_soc = displayed_soc_temp; + printk(KERN_INFO "PMU: %s g_full_flag=1 but OCV is low\n", __func__); + } else { + info->soca->displayed_soc = 100*100; + } + } else { + info->soca->displayed_soc = min(9949, displayed_soc_temp); + g_full_flag = 0; + } + } + if (RICOH61x_SOCA_START == info->soca->status) { + if ((g_full_flag == 1) && (calc_ocv(info) > get_OCV_voltage(info, 9,USING))){ + info->soca->soc_full = info->soca->soc; + info->soca->displayed_soc = 100*100; + info->soca->full_reset_count = 0; + printk(KERN_INFO "PMU:%s Charge Complete in PowerOff\n", __func__); + } else if ((info->first_pwon == 0) + && !g_fg_on_mode) { + printk(KERN_INFO "PMU:%s 2nd P-On init_pswr(%d), cc(%d)\n", + __func__, info->soca->init_pswr, info->soca->cc_delta); + if ((info->soca->init_pswr == 100) + && (info->soca->cc_delta > -100)) { + printk(KERN_INFO "PMU:%s Set 100%%\n", __func__); + g_full_flag = 1; + info->soca->soc_full = info->soca->soc; + info->soca->displayed_soc = 100*100; + info->soca->full_reset_count = 0; + } + } + } else { + printk(KERN_INFO "PMU:%s Resume Sus_soc(%d), cc(%d)\n", + __func__, info->soca->suspend_soc, info->soca->cc_delta); + if ((info->soca->suspend_soc == 10000) + && (info->soca->cc_delta > -100)) { + printk(KERN_INFO "PMU:%s Set 100%%\n", __func__); + info->soca->displayed_soc = 100*100; + } + } + + return info->soca->displayed_soc; +} + +/** +* Calculate Capacity in a period +* - read CC_SUM & FA_CAP from Coulom Counter +* - and calculate Capacity. +* @cc_cap: capacity in a period, unit 0.01% +* @cc_cap_mas : capacity in a period, unit 1mAs +* @is_charging: Flag of charging current direction +* TRUE : charging (plus) +* FALSE: discharging (minus) +* @cc_rst: reset CC_SUM or not +* 0 : not reset +* 1 : reset +* 2 : half reset (Leave under 1% of FACAP) +**/ +static int calc_capacity_in_period(struct ricoh61x_battery_info *info, + int *cc_cap, long *cc_cap_mas, bool *is_charging, int cc_rst) +{ + int err; + uint8_t cc_sum_reg[4]; + uint8_t cc_clr[4] = {0, 0, 0, 0}; + uint8_t fa_cap_reg[2]; + uint16_t fa_cap; + uint32_t cc_sum; + int cc_stop_flag; + uint8_t status; + uint8_t charge_state; + int Ocv; + uint32_t cc_cap_temp; + uint32_t cc_cap_min; + int cc_cap_res; + int fa_cap_int; + long cc_sum_int; + long cc_sum_dec; + + *is_charging = true; /* currrent state initialize -> charging */ + + if (info->entry_factory_mode) + return 0; + + /* Read FA_CAP */ + err = ricoh61x_bulk_reads(info->dev->parent, + FA_CAP_H_REG, 2, fa_cap_reg); + if (err < 0) + goto out; + + /* fa_cap = *(uint16_t*)fa_cap_reg & 0x7fff; */ + fa_cap = (fa_cap_reg[0] << 8 | fa_cap_reg[1]) & 0x7fff; + + + /* get power supply status */ + err = ricoh61x_read(info->dev->parent, CHGSTATE_REG, &status); + if (err < 0) + goto out; + charge_state = (status & 0x1F); + Ocv = calc_ocv(info); + if (charge_state == CHG_STATE_CHG_COMPLETE) { + /* Check CHG status is complete or not */ + cc_stop_flag = 0; +// } else if (calc_capacity(info) == 100) { +// /* Check HW soc is 100 or not */ +// cc_stop_flag = 0; + } else if (Ocv < get_OCV_voltage(info, 9, USING)) { + /* Check VBAT is high level or not */ + cc_stop_flag = 0; + } else { + cc_stop_flag = 1; + } + + if (cc_stop_flag == 1) + { + /* Disable Charging/Completion Interrupt */ + err = ricoh61x_set_bits(info->dev->parent, + RICOH61x_INT_MSK_CHGSTS1, 0x01); + if (err < 0) + goto out; + + /* disable charging */ + err = ricoh61x_clr_bits(info->dev->parent, RICOH61x_CHG_CTL1, 0x03); + if (err < 0) + goto out; + } + + /* Read CC_SUM */ + err = ricoh61x_bulk_reads(info->dev->parent, + CC_SUMREG3_REG, 4, cc_sum_reg); + if (err < 0) + goto out; + + /* cc_sum = *(uint32_t*)cc_sum_reg; */ + cc_sum = cc_sum_reg[0] << 24 | cc_sum_reg[1] << 16 | + cc_sum_reg[2] << 8 | cc_sum_reg[3]; + + /* calculation two's complement of CC_SUM */ + if (cc_sum & 0x80000000) { + cc_sum = (cc_sum^0xffffffff)+0x01; + *is_charging = false; /* discharge */ + } + + if (cc_rst == 1) { + /* CC_pause enter */ + err = ricoh61x_write(info->dev->parent, CC_CTRL_REG, 0x01); + if (err < 0) + goto out; + + /* CC_SUM <- 0 */ + err = ricoh61x_bulk_writes(info->dev->parent, + CC_SUMREG3_REG, 4, cc_clr); + if (err < 0) + goto out; + } else if (cc_rst == 2) { + /* Check 1%[mAs] of FA_CAP (FA_CAP * 3600 /100) */ + fa_cap_int = fa_cap * 36; + cc_sum_int = cc_sum / fa_cap_int; + cc_sum_dec = cc_sum % fa_cap_int; + + if (*is_charging == false) { + cc_sum_dec = (cc_sum_dec^0xffffffff) + 1; + } + printk(KERN_INFO "PMU %s 1%%FACAP(%d)[mAs], cc_sum(%d)[mAs], cc_sum_dec(%d)\n", + __func__, fa_cap_int, cc_sum, cc_sum_dec); + + if (cc_sum_int != 0) { + cc_clr[0] = (uint8_t)(cc_sum_dec >> 24) & 0xff; + cc_clr[1] = (uint8_t)(cc_sum_dec >> 16) & 0xff; + cc_clr[2] = (uint8_t)(cc_sum_dec >> 8) & 0xff; + cc_clr[3] = (uint8_t)cc_sum_dec & 0xff; + + /* CC_pause enter */ + err = ricoh61x_write(info->dev->parent, CC_CTRL_REG, 0x01); + if (err < 0) + goto out; + + /* CC_SUM <- 0 */ + err = ricoh61x_bulk_writes(info->dev->parent, + CC_SUMREG3_REG, 4, cc_clr); + if (err < 0) + goto out; + printk(KERN_INFO "PMU %s Half-Clear CC, cc_sum is over 1%%\n", + __func__); + } + } + + /* CC_pause exist */ + err = ricoh61x_write(info->dev->parent, CC_CTRL_REG, 0); + if (err < 0) + goto out; + if (cc_stop_flag == 1) + { + + /* Enable charging */ + err = ricoh61x_set_bits(info->dev->parent, RICOH61x_CHG_CTL1, 0x03); + if (err < 0) + goto out; + + udelay(1000); + + /* Clear Charging Interrupt status */ + err = ricoh61x_clr_bits(info->dev->parent, + RICOH61x_INT_IR_CHGSTS1, 0x01); + if (err < 0) + goto out; + + /* ricoh61x_read(info->dev->parent, RICOH61x_INT_IR_CHGSTS1, &val); +// printk("INT_IR_CHGSTS1 = 0x%x\n",val); */ + + /* Enable Charging Interrupt */ + err = ricoh61x_clr_bits(info->dev->parent, + RICOH61x_INT_MSK_CHGSTS1, 0x01); + if (err < 0) + goto out; + } + + /* (CC_SUM x 10000)/3600/FA_CAP */ + + if(fa_cap == 0) + goto out; + else + *cc_cap = cc_sum*25/9/fa_cap; /* unit is 0.01% */ + + *cc_cap_mas = cc_sum; + + //printk("PMU: cc_sum = %d: cc_cap= %d: cc_cap_mas = %d\n", cc_sum, *cc_cap, *cc_cap_mas); + + if (cc_rst == 1) { + cc_cap_min = fa_cap*3600/100/100/100; /* Unit is 0.0001% */ + + if(cc_cap_min == 0) + goto out; + else + cc_cap_temp = cc_sum / cc_cap_min; + + cc_cap_res = cc_cap_temp % 100; + +#ifdef _RICOH619_DEBUG_ + printk(KERN_DEBUG"PMU: cc_sum = %d: cc_cap_res= %d: cc_cap_mas = %d\n", cc_sum, cc_cap_res, cc_cap_mas); +#endif + + if(*is_charging) { + info->soca->cc_cap_offset += cc_cap_res; + if (info->soca->cc_cap_offset >= 100) { + *cc_cap += 1; + info->soca->cc_cap_offset %= 100; + } + } else { + info->soca->cc_cap_offset -= cc_cap_res; + if (info->soca->cc_cap_offset <= -100) { + *cc_cap += 1; + info->soca->cc_cap_offset %= 100; + } + } +#ifdef _RICOH619_DEBUG_ + printk(KERN_DEBUG"PMU: cc_cap_offset= %d: \n", info->soca->cc_cap_offset); +#endif + } else { + info->soca->cc_cap_offset = 0; + } + + ////////////////////////////////////////////////////////////////// + return 0; +out: + /* CC_pause exist */ + err = ricoh61x_write(info->dev->parent, CC_CTRL_REG, 0); + + dev_err(info->dev, "Error !!-----\n"); + return err; +} + +/** +* Initial setting of Low voltage. +**/ +static void initSettingOfLowVoltage(struct ricoh61x_battery_info *info) +{ + int err; + int cc_cap; + long cc_cap_mas; + bool is_charging = true; + + + if(info->soca->rsoc_ready_flag ==1) { + err = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas, &is_charging, 1); + info->soca->last_cc_delta_cap = 0; + } else { + err = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas, &is_charging, 0); + info->soca->last_cc_delta_cap = (is_charging == true) ? cc_cap : -cc_cap; + } + if (err < 0) + dev_err(info->dev, "Read cc_sum Error !!-----\n"); + + return; +} + +/** +* Low voltage main flow. +**/ +static void mainFlowOfLowVoltage(struct ricoh61x_battery_info *info) +{ + int ret = 0; + int cc_cap = 0; + long cc_cap_mas = 0; + bool is_charging = true; + int cc_delta_cap; + int cc_delta_cap_temp; + int cc_delta_cap_mas_temp; + int cc_delta_cap_now; + int cc_delta_cap_debug; //for debug value + int capacity_now; //Unit is 0.01 % + int capacity_zero; //Unit is 0.01 % + int capacity_remain; //Unit is 0.01 % + int low_rate; //Unit is 0.01 times + int target_equal_soc; //unit is 0.01 % + int temp_cc_delta_cap; //unit is 0.01 % + int fa_cap; //unit is mAh + + if(info->soca->rsoc_ready_flag ==1) { + ret = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas, &is_charging, 1); + } else { + ret = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas, &is_charging, 0); + } + if (ret < 0) + dev_err(info->dev, "Read cc_sum Error !!-----\n"); + + if(is_charging == true) { + cc_delta_cap_now = cc_cap; + //cc_cap_mas; + } else { + cc_delta_cap_now = -cc_cap; + cc_cap_mas = -cc_cap_mas; + } + + fa_cap = (battery_init_para[info->num][22]<<8) + | (battery_init_para[info->num][23]); + + if(fa_cap != 0) { + //( cc(mas) * 10000 ) / 3600 / fa_cap + temp_cc_delta_cap = info->soca->temp_cc_delta_cap_mas * 25 / 9 / fa_cap; + } else { + temp_cc_delta_cap = 0; + } + + cc_delta_cap = (cc_delta_cap_now - info->soca->last_cc_delta_cap) + temp_cc_delta_cap; + +// info->soca->temp_cc_delta_cap_mas = info->soca->temp_cc_delta_cap_mas - ( (fa_cap * 3600) * (temp_cc_delta_cap / 10000)) ; + info->soca->temp_cc_delta_cap_mas = info->soca->temp_cc_delta_cap_mas - ( ((fa_cap * 9) / 25) * temp_cc_delta_cap); + + printk(KERN_DEBUG "PMU: %s : Noxx : cc_delta_cap is %d, cc_delta_cap_now is %d, last_cc_delta_cap is %d\n" + , __func__, cc_delta_cap, cc_delta_cap_now, info->soca->last_cc_delta_cap); + printk(KERN_DEBUG "PMU: %s : Noxx : temp_cc_delta_cap is %d, after temp_cc_delta_cap_mas is %ld, cc_cap_mas %ld\n" + , __func__, temp_cc_delta_cap ,info->soca->temp_cc_delta_cap_mas, cc_cap_mas); + + if(info->soca->rsoc_ready_flag ==1) { + info->soca->last_cc_delta_cap = 0; + info->soca->last_cc_delta_cap_mas = 0; + } else { + info->soca->last_cc_delta_cap = cc_delta_cap_now; + info->soca->last_cc_delta_cap_mas = cc_cap_mas; + } + + cc_delta_cap_debug = cc_delta_cap; + + // check charging or not, if charging -> move to Disp state + if ((cc_delta_cap > 0) || + (info->soca->Ibat_ave >= 0)){//chekc discharging or not + info->soca->soc = calc_capacity(info) * 100; + info->soca->status = RICOH61x_SOCA_DISP; + info->soca->last_soc = info->soca->soc; + info->soca->soc_delta = 0; + info->soca->hurry_up_flg = 0; + info->soca->temp_cc_delta_cap_mas = 0; + return; + } + + //check Vbat and POff_vbat + if(info->soca->Vbat_ave <= (info->fg_poff_vbat * 1000)) { + info->soca->displayed_soc = info->soca->displayed_soc - 100; + info->soca->displayed_soc = max(0, info->soca->displayed_soc); + info->soca->hurry_up_flg = 1; + return; + } + + //calc current recap value + capacity_now = getCapFromOriTable(info,info->soca->Vbat_ave,info->soca->Ibat_ave,info->soca->Rbat); + + //calc recap value when soc is 0% + if(info->fg_poff_vbat != 0){ + //enable poff vbat + capacity_zero = getCapFromOriTable(info,(info->fg_poff_vbat * 1000),info->soca->Ibat_ave,info->soca->Rbat); + } else if(info->fg_target_vsys != 0){ + //enable target vsys + capacity_zero = getCapFromOriTable(info,(info->fg_target_vsys * 1000),info->soca->Ibat_ave,info->soca->Rsys); + } else { + //disable poff vbat and target vsys + capacity_zero = 0; + } + + capacity_remain = (capacity_now - capacity_zero) + 50; + + if (capacity_remain <= 50){ + printk(KERN_INFO "PMU: %s : No6 :Hurry up!!! \n", __func__); + info->soca->displayed_soc = info->soca->displayed_soc - 100; + info->soca->displayed_soc = max(0, info->soca->displayed_soc); + info->soca->hurry_up_flg = 1; + return; + } + else { + info->soca->hurry_up_flg = 0; + + if (info->soca->displayed_soc < 1000) { //low DSOC case + if(capacity_remain > info->soca->displayed_soc){ + target_equal_soc = info->soca->displayed_soc * 95 / 100; + } else { + target_equal_soc = 50; + } + } else {// normal case + if(capacity_remain > info->soca->displayed_soc){ + target_equal_soc = info->soca->displayed_soc - 1000; + } else { + target_equal_soc = capacity_remain - 1000; + } + } + + target_equal_soc = max(50, target_equal_soc); + + low_rate = (info->soca->displayed_soc - target_equal_soc) * 100 / (capacity_remain - target_equal_soc); + + low_rate = max(1, low_rate); + low_rate = min(300, low_rate); + + cc_delta_cap_temp = cc_delta_cap * 100 * low_rate / 100; //unit is 0.0001% + + if(cc_delta_cap_temp < 0){ + //Unit 0.0001 -> 0.01 + cc_delta_cap = cc_delta_cap_temp / 100; + + cc_delta_cap_temp = cc_delta_cap_temp - cc_delta_cap * 100; + //transform 0.0001% -> mAs + //mAs = 0.0001 % * (fa_cap(mAh)*60*60) + //mAs = cc_delta_cap_temp * (fa_cap * 60 * 60) / (100 * 100 * 100) + cc_delta_cap_mas_temp = cc_delta_cap_temp * fa_cap * 9 / 2500; + info->soca->temp_cc_delta_cap_mas += cc_delta_cap_mas_temp; + }else{ + cc_delta_cap = 0; + } + + info->soca->displayed_soc = info->soca->displayed_soc + cc_delta_cap; + info->soca->displayed_soc = max(100, info->soca->displayed_soc); //Set Under limit DSOC is 1% + printk(KERN_DEBUG "PMU: %s : No9 :Cap is %d , low_rate is %d, dsoc is %d, capnow is %d, capzero is %d, delta cc is %d, delta cc ori is %d\n" + , __func__, capacity_remain, low_rate, info->soca->displayed_soc, capacity_now, capacity_zero, cc_delta_cap, cc_delta_cap_debug); + printk(KERN_DEBUG "PMU: %s : No10 :temp_mas is %d, offset_mas is %d, value is %d, final value is %d\n" + , __func__, info->soca->temp_cc_delta_cap_mas, cc_delta_cap_mas_temp,(cc_delta_cap_temp + cc_delta_cap * 100), cc_delta_cap); + } + return; +} + +/** +* get capacity from Original OCV Table. this value is calculted by ocv +* info : battery info +* voltage : unit is 1mV +* current : unit is 1mA +* resvalue: unit is 1mohm +* +* return value : capcaity, unit is 0.01% +*/ +static int getCapFromOriTable(struct ricoh61x_battery_info *info, int voltage, int currentvalue, int resvalue) +{ + int ocv = 0; + int i =0; + int capacity=0; + + int ocv_table[11]; + + ocv = voltage - (currentvalue * resvalue); + + //get ocv table from header file + for(i = 0; i < 11; i++){ + ocv_table[i] = get_OCV_voltage(info, i, ORIGINAL); + } + + /* capacity is 0.01% unit */ + if (ocv_table[10] <= ocv) { + capacity = 100 * 100; + } else { + for (i = 1; i < 11; i++) { + if (ocv_table[i] >= ocv) { + if(i == 1){//Under 10 % + capacity = getCapFromOriTable_U10per(info, voltage, currentvalue, resvalue); + }else{ + /* unit is 0.01% */ + capacity = Calc_Linear_Interpolation( + (i-1)*10 * 100, ocv_table[i-1], i*10 * 100, + ocv_table[i], ocv); + if(capacity < 100){ + capacity = 100; + } + } + break; + } + } + } + return capacity; +} + +/** +* get capacity from special OCV Table(10%-0%). this value is calculted by ocv +* info : battery info +* voltage : unit is 1mV +* current : unit is 1mA +* resvalue: unit is 1mohm +* +* return value : capcaity, unit is 0.01% +*/ +static int getCapFromOriTable_U10per(struct ricoh61x_battery_info *info, int voltage, int currentvalue, int resvalue) +{ + int ocv = 0; + int i =0; + int capacity=0; + + int ocv_table[11] = { 3468207, + 3554926, + 3605932, + 3627745, + 3639093, + 3646930, + 3655757, + 3665738, + 3672731, + 3680469, + 3687400}; + + ocv = voltage - (currentvalue * resvalue); + + /* capacity is 0.01% unit */ + if (ocv_table[0] >= ocv) { + capacity = 0; + } else if (ocv_table[10] <= ocv) { + capacity = 10 * 100; + } else { + for (i = 1; i < 11; i++) { + if (ocv_table[i] >= ocv) { + /* unit is 0.01% */ + capacity = Calc_Linear_Interpolation( + (i-1) * 100, ocv_table[i-1], i * 100, + ocv_table[i], ocv); + if(capacity < 0){ + capacity = 0; + } + break; + } + } + } + return capacity; + +} + +/** +* ReWrite extra CC Value to CC_SUM(register) +* info : battery info +* extraValue : Under 1% value. unit is 0.01% +* +* return value : delta soc, unit is "minus" 0.01% +*/ + +static void write_extra_value_to_ccsum(struct ricoh61x_battery_info *info, int extraValue) +{ + int err; + uint8_t cc_clr[4] = {0, 0, 0, 0}; //temporary box + uint8_t fa_cap_reg[2]; //reg value + int fa_cap; //Unit is mAh + int cc_sum_dec; //unit is mAs + bool is_charging = 0; + + //check dicharging or not + if(extraValue < 0){ + extraValue = extraValue * -1; + is_charging = false; + } else { + is_charging = true; + } + + /* Read FA_CAP */ + err = ricoh61x_bulk_reads(info->dev->parent, + FA_CAP_H_REG, 2, fa_cap_reg); + if (err < 0) + dev_err(info->dev, "Read fa_cap Error !!-----\n"); + + /* fa_cap = *(uint16_t*)fa_cap_reg & 0x7fff; */ + fa_cap = (fa_cap_reg[0] << 8 | fa_cap_reg[1]) & 0x7fff; + + //convertion extraValue(0.01%) -> mAs + //cc_sum_dec = (extraValue * fa_cap * 3600) / (100 * 100) + cc_sum_dec = (extraValue * fa_cap * 9) / 25; + + // Add 0.005% + if (extraValue < 100) { + cc_sum_dec += (1 * fa_cap * 9) / 25; + } + + if (is_charging == false) { + cc_sum_dec = (cc_sum_dec^0xffffffff) + 1; + } + + cc_clr[0] = (uint8_t)(cc_sum_dec >> 24) & 0xff; + cc_clr[1] = (uint8_t)(cc_sum_dec >> 16) & 0xff; + cc_clr[2] = (uint8_t)(cc_sum_dec >> 8) & 0xff; + cc_clr[3] = (uint8_t)cc_sum_dec & 0xff; + + /* CC_pause enter */ + err = ricoh61x_write(info->dev->parent, CC_CTRL_REG, 0x01); + if (err < 0) + dev_err(info->dev, "Write cc_CTRL Error !!-----\n"); + + /* CC_SUM <- 0 */ + err = ricoh61x_bulk_writes(info->dev->parent, + CC_SUMREG3_REG, 4, cc_clr); + if (err < 0) + dev_err(info->dev, "Write cc_Sum Error !!-----\n"); + + /* CC_pause exit */ + err = ricoh61x_write(info->dev->parent, CC_CTRL_REG, 0); + if (err < 0) + dev_err(info->dev, "Write cc_CTRL Error !!-----\n"); + + return; +} + + +#ifdef ENABLE_OCV_TABLE_CALIB +/** +* Calibration OCV Table +* - Update the value of VBAT on 100% in OCV table +* if battery is Full charged. +* - int vbat_ocv <- unit is uV +**/ +static int calib_ocvTable(struct ricoh61x_battery_info *info, int vbat_ocv) +{ + int ret; + int cutoff_ocv; + int i; + int ocv100_new; + int start_per = 0; + int end_per = 0; + + if (info->soca->Ibat_ave > RICOH61x_REL1_SEL_VALUE) { + printk("PMU: %s IBAT > 64mA -- Not Calibration --\n", __func__); + return 0; + } + + if (vbat_ocv < info->soca->OCV100_max) { + if (vbat_ocv < info->soca->OCV100_min) + ocv100_new = info->soca->OCV100_min; + else + ocv100_new = vbat_ocv; + } else { + ocv100_new = info->soca->OCV100_max; + } + printk("PMU : %s :max %d min %d current %d\n",__func__,info->soca->OCV100_max,info->soca->OCV100_min,vbat_ocv); + printk("PMU : %s : New OCV 100 = 0x%x\n",__func__,ocv100_new); + + /* FG_En Off */ + ret = ricoh61x_clr_bits(info->dev->parent, FG_CTRL_REG, 0x01); + if (ret < 0) { + dev_err(info->dev,"Error in FG_En OFF\n"); + goto err; + } + + + //cutoff_ocv = (battery_init_para[info->num][0]<<8) | (battery_init_para[info->num][1]); + cutoff_ocv = get_OCV_voltage(info, 0, USING); + + info->soca->ocv_table_def[10] = info->soca->OCV100_max; + + ricoh61x_scaling_OCV_table(info, cutoff_ocv/1000, ocv100_new/1000, &start_per, &end_per); + + ret = ricoh61x_bulk_writes_bank1(info->dev->parent, + BAT_INIT_TOP_REG, 22, battery_init_para[info->num]); + if (ret < 0) { + dev_err(info->dev, "batterry initialize error\n"); + goto err; + } + + for (i = 0; i <= 10; i = i+1) { + info->soca->ocv_table[i] = get_OCV_voltage(info, i, USING); + printk("PMU: %s : * %d0%% voltage = %d uV\n", + __func__, i, info->soca->ocv_table[i]); + } + + /* FG_En on & Reset*/ + ret = reset_FG_process(info); + if (ret < 0) { + dev_err(info->dev, "Error in FG_En On & Reset %d\n", ret); + goto err; + } + + printk("PMU: %s Exit \n", __func__); + return 0; +err: + return ret; + +} + +#endif + +/** +* get SOC value during period of Suspend/Hibernate with voltage method +* info : battery info +* +* return value : soc, unit is 0.01% +*/ + +static int calc_soc_by_voltageMethod(struct ricoh61x_battery_info *info) +{ + int soc; + int ret; + + ret = measure_vbatt_FG(info, &info->soca->Vbat_ave); + + if(info->soca->Vbat_ave > 4100000) { + soc = 10000; + } else if(info->soca->Vbat_ave < 3500000) { + soc = 0; + } else { + soc = 10000 - ((4100000 - info->soca->Vbat_ave) / 60); + } + + get_power_supply_status(info); + if (POWER_SUPPLY_STATUS_FULL == info->soca->chg_status) { + soc = 10000; + } else { + soc = min(soc, 9900); + } + + // Cutoff under 1% on Voltage Method + soc = (soc / 100) * 100; + + printk("PMU : %s : VBAT is %d [uV], soc is %d [0.01%%] ----------\n" + ,__func__, info->soca->Vbat_ave, soc); + + // soc range is 0~10000 + return soc; +} + +/** +* update RSOC and related parameters after using voltage method +* info : battery info +* soc_voltage : soc by using voltage method +* +*/ + +static void update_rsoc_on_voltageMethod(struct ricoh61x_battery_info *info, int soc_voltage) +{ + info->soca->init_pswr = soc_voltage / 100; + write_extra_value_to_ccsum(info, (soc_voltage % 100)); + info->soca->status = RICOH61x_SOCA_STABLE; + info->soca->last_soc = soc_voltage; + info->soca->rsoc_ready_flag = 0; + + printk(KERN_INFO "PMU: %s : Voltage Method. state(%d), dsoc(%d), rsoc(%d), init_pswr(%d), cc_delta(%d) ----------\n", + __func__, info->soca->status, soc_voltage, soc_voltage, info->soca->init_pswr, soc_voltage%100); + + return; +} + +/** +* update RSOC and related parameters after using current method +* Only resume function can call this one. +* info : battery info +* soc_current : soc by using current method +* +*/ + +static void update_rsoc_on_currentMethod(struct ricoh61x_battery_info *info, int soc_current) +{ + int resume_rsoc; + + if (RICOH61x_SOCA_START == info->soca->status + || RICOH61x_SOCA_UNSTABLE == info->soca->status + || RICOH61x_SOCA_STABLE == info->soca->status) { + resume_rsoc = soc_current; + } else { + resume_rsoc = info->soca->suspend_rsoc + info->soca->cc_delta; + } + resume_rsoc = max(0, min(10000, resume_rsoc)); // Apply upper&lower limit + info->soca->init_pswr = resume_rsoc / 100; + write_extra_value_to_ccsum(info, (resume_rsoc % 100)); + info->soca->rsoc_ready_flag = 0; + printk(KERN_INFO "PMU: %s : Current Method. state(%d), dsoc(%d), rsoc(%d), init_pswr(%d), cc_delta(%d) ----------\n", + __func__, info->soca->status, soc_current, resume_rsoc, info->soca->init_pswr, resume_rsoc%100); + + return; +} + + +static void ricoh61x_displayed_work(struct work_struct *work) +{ + int err; + uint8_t val; + uint8_t val_pswr; + uint8_t val2; + int soc_round; + int last_soc_round; + int last_disp_round; + int displayed_soc_temp; + int disp_dec; + int cc_cap = 0; + long cc_cap_mas = 0; + bool is_charging = true; + int re_cap,fa_cap,use_cap; + bool is_jeita_updated; + uint8_t reg_val; + int delay_flag = 0; + int Vbat = 0; + int Ibat = 0; + int Vsys = 0; + int temp_ocv; + int current_soc_full; + int fc_delta = 0; + int temp_soc; + int current_cc_sum; + int calculated_ocv; + long full_rate = 0; + long full_rate_org; + long full_rate_max; + long full_rate_min; + int temp_cc_delta_cap; + int ibat_soc = 0; + int ibat_soc_base; + int dsoc_var; + int dsoc_var_org; + int cc_delta; + int i; + int last_dsoc; + + struct ricoh61x_battery_info *info = container_of(work, + struct ricoh61x_battery_info, displayed_work.work); + + if (info->entry_factory_mode) { + info->soca->status = RICOH61x_SOCA_STABLE; + info->soca->displayed_soc = -EINVAL; + info->soca->ready_fg = 0; + return; + } + + if (info->stop_disp) { + printk(KERN_INFO "PMU: Finish displayed_work func\n", + __func__); + return; + } + + mutex_lock(&info->lock); + + is_jeita_updated = false; + + if ((RICOH61x_SOCA_START == info->soca->status) + || (RICOH61x_SOCA_STABLE == info->soca->status) + || (RICOH61x_SOCA_FULL == info->soca->status)) + { + info->soca->ready_fg = 1; + } + //if (RICOH61x_SOCA_FG_RESET != info->soca->status) + // Set_back_ocv_table(info); + + if (bat_alert_req_flg == 1) { + // Use Voltage method if difference is large + info->soca->displayed_soc = calc_soc_by_voltageMethod(info); + update_rsoc_on_voltageMethod(info, info->soca->displayed_soc); + bat_alert_req_flg = 0; + + goto end_flow; + } + + /* judge Full state or Moni Vsys state */ + calculated_ocv = calc_ocv(info); + if ((RICOH61x_SOCA_DISP == info->soca->status) + || (RICOH61x_SOCA_STABLE == info->soca->status)) { + /* caluc 95% ocv */ + temp_ocv = get_OCV_voltage(info, 10, USING) - + (get_OCV_voltage(info, 10, USING) - get_OCV_voltage(info, 9, USING))/2; + + if(g_full_flag == 1){ /* for issue 1 solution start*/ + info->soca->status = RICOH61x_SOCA_FULL; + info->soca->last_soc_full = 0; + } else if ((POWER_SUPPLY_STATUS_FULL == info->soca->chg_status) + && (calculated_ocv > temp_ocv)) { + info->soca->status = RICOH61x_SOCA_FULL; + g_full_flag = 0; + info->soca->last_soc_full = 0; + } else if (info->soca->Ibat_ave >= -12) { + /* for issue1 solution end */ + /* check Full state or not*/ + if ((calculated_ocv > get_OCV_voltage(info, RICOH61x_ENTER_FULL_STATE_OCV, USING)) + || (POWER_SUPPLY_STATUS_FULL == info->soca->chg_status) + || (info->soca->displayed_soc > RICOH61x_ENTER_FULL_STATE_DSOC * 100)) { + info->soca->status = RICOH61x_SOCA_FULL; + g_full_flag = 0; + info->soca->last_soc_full = 0; + } else if ((calculated_ocv > get_OCV_voltage(info, 9, USING)) + && (info->soca->Ibat_ave < 300)) { + info->soca->status = RICOH61x_SOCA_FULL; + g_full_flag = 0; + info->soca->last_soc_full = 0; + } + } else { /* dis-charging */ +// if (info->soca->displayed_soc/100 < RICOH61x_ENTER_LOW_VOL) { + initSettingOfLowVoltage(info); + info->soca->status = RICOH61x_SOCA_LOW_VOL; +// } + } + } + + if (RICOH61x_SOCA_STABLE == info->soca->status) { + info->soca->soc = calc_capacity_2(info); + info->soca->soc_delta = info->soca->soc - info->soca->last_soc; + + if (info->soca->soc_delta >= -100 && info->soca->soc_delta <= 100) { + info->soca->displayed_soc = info->soca->soc; + } else { + info->soca->status = RICOH61x_SOCA_DISP; + } + info->soca->last_soc = info->soca->soc; + info->soca->soc_delta = 0; + } else if (RICOH61x_SOCA_FULL == info->soca->status) { + err = check_jeita_status(info, &is_jeita_updated); + if (err < 0) { + dev_err(info->dev, "Error in updating JEITA %d\n", err); + goto end_flow; + } + info->soca->soc = calc_capacity(info) * 100; + info->soca->last_soc = calc_capacity_2(info); /* for DISP */ + last_dsoc = info->soca->displayed_soc; + + if (info->soca->Ibat_ave >= -12) { /* charging */ + if (0 == info->soca->jt_limit) { + if (g_full_flag == 1) { + + if (POWER_SUPPLY_STATUS_FULL == info->soca->chg_status) { + if(info->soca->full_reset_count < RICOH61x_UPDATE_COUNT_FULL_RESET) { + info->soca->full_reset_count++; + } else if (info->soca->full_reset_count < (RICOH61x_UPDATE_COUNT_FULL_RESET + 1)) { + err = reset_FG_process(info); + if (err < 0) + dev_err(info->dev, "Error in writing the control register\n"); + info->soca->full_reset_count++; + info->soca->rsoc_ready_flag =1; + goto end_flow; + } else if(info->soca->full_reset_count < (RICOH61x_UPDATE_COUNT_FULL_RESET + 2)) { + info->soca->full_reset_count++; + info->soca->fc_cap = 0; + info->soca->soc_full = info->soca->soc; + } + } else { + if(info->soca->fc_cap < -1 * 200) { + g_full_flag = 0; + info->soca->displayed_soc = 99 * 100; + } + info->soca->full_reset_count = 0; + } + + + if(info->soca->rsoc_ready_flag ==1) { + err = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas, &is_charging, 1); + if (err < 0) + dev_err(info->dev, "Read cc_sum Error !!-----\n"); + + fc_delta = (is_charging == true) ? cc_cap : -cc_cap; + + info->soca->fc_cap = info->soca->fc_cap + fc_delta; + } + + if (g_full_flag == 1){ + info->soca->displayed_soc = 100*100; + } + } else { + if ((calculated_ocv < get_OCV_voltage(info, (RICOH61x_ENTER_FULL_STATE_OCV - 1), USING)) + && (info->soca->displayed_soc < (RICOH61x_ENTER_FULL_STATE_DSOC - 10) * 100)) { /* fail safe*/ + g_full_flag = 0; + info->soca->status = RICOH61x_SOCA_DISP; + info->soca->soc_delta = 0; + info->soca->full_reset_count = 0; + info->soca->last_soc = info->soca->soc; + info->soca->temp_cc_delta_cap = 0; + } else if ((POWER_SUPPLY_STATUS_FULL == info->soca->chg_status) + && (info->soca->displayed_soc >= 9890)){ + info->soca->displayed_soc = 100*100; + g_full_flag = 1; + info->soca->full_reset_count = 0; + info->soca->soc_full = info->soca->soc; + info->soca->fc_cap = 0; + info->soca->last_soc_full = 0; +#ifdef ENABLE_OCV_TABLE_CALIB + err = calib_ocvTable(info,calculated_ocv); + if (err < 0) + dev_err(info->dev, "Calibration OCV Error !!\n"); +#endif + } else { + fa_cap = get_check_fuel_gauge_reg(info, FA_CAP_H_REG, FA_CAP_L_REG, + 0x7fff); + + if (info->soca->displayed_soc >= 9950) { + if((info->soca->soc_full - info->soca->soc) < 200) { + goto end_flow; + } + } + + /* Calculate CC Delta */ + if(info->soca->rsoc_ready_flag ==1) { + err = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas, &is_charging, 1); + if (err < 0) + dev_err(info->dev, "Read cc_sum Error !!-----\n"); + info->soca->cc_delta + = (is_charging == true) ? cc_cap : -cc_cap; + } else { + err = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas, &is_charging, 0); + if (err < 0) + dev_err(info->dev, "Read cc_sum Error !!-----\n"); + cc_delta = (is_charging == true) ? cc_cap : -cc_cap; + current_soc_full = info->soca->init_pswr * 100 + cc_delta; + + if (info->soca->last_soc_full == 0) { /* initial setting of last cc sum */ + info->soca->cc_delta = 0; + info->soca->rsoc_limit = 0; + printk(KERN_INFO "PMU: %s 1st last_soc_full(%d), cc_delta=0\n", + __func__, info->soca->last_soc_full); + } else if (info->soca->rsoc_limit == 1) { + info->soca->cc_delta = 100 + current_soc_full - info->soca->last_soc_full; + } else { + info->soca->cc_delta = current_soc_full - info->soca->last_soc_full; + } + info->soca->last_soc_full = current_soc_full; + + if ((info->soca->init_pswr == 100) && (cc_delta >= 100)) { + info->soca->rsoc_limit = 1; + } else { + info->soca->rsoc_limit = 0; + } + } + + printk(KERN_INFO "PMU: %s rrf= %d: cc_delta= %d: current_soc= %d: rsoc_limit= %d: cc_delta_temp = %d:\n", + __func__, info->soca->rsoc_ready_flag, info->soca->cc_delta, current_soc_full, info->soca->rsoc_limit,info->soca->temp_cc_delta_cap); + + info->soca->temp_cc_delta_cap = min(800, info->soca->temp_cc_delta_cap); + + info->soca->cc_delta += info->soca->temp_cc_delta_cap; + info->soca->temp_cc_delta_cap = 0; + + +#ifdef LTS_DEBUG + printk(KERN_INFO "PMU: %s rrf= %d: cc_delta= %d: current_soc= %d: rsoc_limit= %d:\n", + __func__, info->soca->rsoc_ready_flag, info->soca->cc_delta, current_soc_full, info->soca->rsoc_limit); +#endif + + if(POWER_SUPPLY_STATUS_FULL == info->soca->chg_status) + { + info->soca->displayed_soc += 13 * 3000 / fa_cap; + } else { + ibat_soc_base = 10000 - (RICOH61x_IBAT_TABLE_NUM - 1) * 100 - 50; + if (ibat_table[0] < info->soca->Ibat_ave) { + if (ibat_soc_base < info->soca->displayed_soc){ + ibat_soc = ibat_soc_base; + } else { + ibat_soc = info->soca->displayed_soc; + } + printk(KERN_INFO "PMU: %s IBAT= %d: ibat_table[%d%%]= %d: ibat_soc= %d ************\n", + __func__, info->soca->Ibat_ave, (100 - RICOH61x_IBAT_TABLE_NUM + 1), ibat_table[0], ibat_soc); + } else if (ibat_table[RICOH61x_IBAT_TABLE_NUM-1] >= info->soca->Ibat_ave) { + ibat_soc = 9950; + printk(KERN_INFO "PMU: %s IBAT= %d: ibat_table[100%%]= %d: ibat_soc= %d ************\n", + __func__, info->soca->Ibat_ave, ibat_table[RICOH61x_IBAT_TABLE_NUM-1], ibat_soc); + } else { + for (i = 1; i <= (RICOH61x_IBAT_TABLE_NUM-1); i++) { + if(ibat_table[i] <= info->soca->Ibat_ave) { + ibat_soc = Calc_Linear_Interpolation( + (i-1) * 100, ibat_table[i-1], i * 100, + ibat_table[i], info->soca->Ibat_ave); + ibat_soc += ibat_soc_base; + +#ifdef LTS_DEBUG + printk(KERN_INFO "PMU: %s IBAT= %d: ibat_table[%d%%]= %d, ibat_table[%d%%]= %d: ibat_soc= %d: ************\n", + __func__, info->soca->Ibat_ave, (100 - RICOH61x_IBAT_TABLE_NUM + i), ibat_table[i-1], + (100 - RICOH61x_IBAT_TABLE_NUM + 1 + i), ibat_table[i], ibat_soc); +#endif + break; + } + } + } + + // full_rate = 100 * (100 - DSOC) * (100 - DSOC) / ((100 - IBAT_SOC) * (100 - IBAT_SOC)) + full_rate = (long)(100 * (10000 - info->soca->displayed_soc)) / (10000 - ibat_soc); + full_rate = full_rate * (10000 - info->soca->displayed_soc) / (10000 - ibat_soc); + + /* Adjust parameters */ + full_rate_org = full_rate; + full_rate_max = 140; + full_rate_min = 30; + + if (ibat_soc >= 9450) { + full_rate_max = 140 + (ibat_soc - 9450) / 2; + } + + full_rate = min(full_rate_max, max(full_rate_min, full_rate)); + + dsoc_var = info->soca->cc_delta * (int)full_rate / 100; + dsoc_var_org = dsoc_var; + if (info->soca->cc_delta <= 0) { + dsoc_var = 0; + } else { + dsoc_var = max(3, dsoc_var); + } + +#ifdef LTS_DEBUG + printk(KERN_INFO "PMU: cc_delta= %d: ibat_soc= %d: full_rate= %ld: %ld: dsoc_var= %d: %d: IBAT= %d: DSOC= %d: RSOC= %d:\n", + info->soca->cc_delta, ibat_soc, full_rate_org, full_rate, dsoc_var_org, dsoc_var, + info->soca->Ibat_ave, (info->soca->displayed_soc + dsoc_var), info->soca->last_soc); +#endif + + info->soca->displayed_soc + = info->soca->displayed_soc + dsoc_var; + } + info->soca->displayed_soc + = min(10000, info->soca->displayed_soc); + info->soca->displayed_soc = max(0, info->soca->displayed_soc); + + if (info->soca->displayed_soc >= 9890) { + info->soca->displayed_soc = 99 * 100; + } + } + } + } else { + info->soca->full_reset_count = 0; + } + } else { /* discharging */ + if (info->soca->displayed_soc >= 9950) { + if (info->soca->Ibat_ave <= -1 * RICOH61x_REL1_SEL_VALUE) { + if ((calculated_ocv < (get_OCV_voltage(info, 9, USING) + (get_OCV_voltage(info, 10, USING) - get_OCV_voltage(info, 9, USING))*3/10)) + || ((info->soca->soc_full - info->soca->soc) > 200)) { + + g_full_flag = 0; + info->soca->full_reset_count = 0; + info->soca->displayed_soc = 100 * 100; + info->soca->status = RICOH61x_SOCA_DISP; + info->soca->last_soc = info->soca->soc; + info->soca->soc_delta = 0; + info->soca->temp_cc_delta_cap = 0; + } else { + info->soca->displayed_soc = 100 * 100; + } + } else { /* into relaxation state */ + ricoh61x_read(info->dev->parent, CHGSTATE_REG, ®_val); + if (reg_val & 0xc0) { + info->soca->displayed_soc = 100 * 100; + } else { + g_full_flag = 0; + info->soca->full_reset_count = 0; + info->soca->displayed_soc = 100 * 100; + info->soca->status = RICOH61x_SOCA_DISP; + info->soca->last_soc = info->soca->soc; + info->soca->soc_delta = 0; + info->soca->temp_cc_delta_cap = 0; + } + } + } else { + g_full_flag = 0; + info->soca->status = RICOH61x_SOCA_DISP; + info->soca->soc_delta = 0; + info->soca->full_reset_count = 0; + info->soca->last_soc = info->soca->soc; + info->soca->temp_cc_delta_cap = 0; + } + } + } else if (RICOH61x_SOCA_LOW_VOL == info->soca->status) { + + mainFlowOfLowVoltage(info); + } + + if (RICOH61x_SOCA_DISP == info->soca->status) { + + info->soca->soc = calc_capacity_2(info); + + soc_round = (info->soca->soc + 50) / 100; + last_soc_round = (info->soca->last_soc + 50) / 100; + last_disp_round = (info->soca->displayed_soc + 50) / 100; + + info->soca->soc_delta = + info->soca->soc_delta + (info->soca->soc - info->soca->last_soc); + + info->soca->last_soc = info->soca->soc; + /* six case */ + if (last_disp_round == soc_round) { + /* if SOC == DISPLAY move to stable */ + info->soca->displayed_soc = info->soca->soc ; + info->soca->status = RICOH61x_SOCA_STABLE; + delay_flag = 1; + } else if (info->soca->Ibat_ave > 0) { + if ((0 == info->soca->jt_limit) || + (POWER_SUPPLY_STATUS_FULL != info->soca->chg_status)) { + /* Charge */ + if (last_disp_round < soc_round) { + /* Case 1 : Charge, Display < SOC */ + if (info->soca->soc_delta >= 100) { + info->soca->displayed_soc + = last_disp_round * 100 + 50; + info->soca->soc_delta -= 100; + if (info->soca->soc_delta >= 100) + delay_flag = 1; + } else { + info->soca->displayed_soc += 25; + disp_dec = info->soca->displayed_soc % 100; + if ((50 <= disp_dec) && (disp_dec <= 74)) + info->soca->soc_delta = 0; + } + if ((info->soca->displayed_soc + 50)/100 + >= soc_round) { + info->soca->displayed_soc + = info->soca->soc ; + info->soca->status + = RICOH61x_SOCA_STABLE; + delay_flag = 1; + } + } else if (last_disp_round > soc_round) { + /* Case 2 : Charge, Display > SOC */ + if (info->soca->soc_delta >= 300) { + info->soca->displayed_soc += 100; + info->soca->soc_delta -= 300; + } + if ((info->soca->displayed_soc + 50)/100 + <= soc_round) { + info->soca->displayed_soc + = info->soca->soc ; + info->soca->status + = RICOH61x_SOCA_STABLE; + delay_flag = 1; + } + } + } else { + info->soca->soc_delta = 0; + } + } else { + /* Dis-Charge */ + if (last_disp_round > soc_round) { + /* Case 3 : Dis-Charge, Display > SOC */ + if (info->soca->soc_delta <= -100) { + info->soca->displayed_soc + = last_disp_round * 100 - 75; + info->soca->soc_delta += 100; + if (info->soca->soc_delta <= -100) + delay_flag = 1; + } else { + info->soca->displayed_soc -= 25; + disp_dec = info->soca->displayed_soc % 100; + if ((25 <= disp_dec) && (disp_dec <= 49)) + info->soca->soc_delta = 0; + } + if ((info->soca->displayed_soc + 50)/100 + <= soc_round) { + info->soca->displayed_soc + = info->soca->soc ; + info->soca->status + = RICOH61x_SOCA_STABLE; + delay_flag = 1; + } + } else if (last_disp_round < soc_round) { + /* Case 4 : Dis-Charge, Display < SOC */ + if (info->soca->soc_delta <= -300) { + info->soca->displayed_soc -= 100; + info->soca->soc_delta += 300; + } + if ((info->soca->displayed_soc + 50)/100 + >= soc_round) { + info->soca->displayed_soc + = info->soca->soc ; + info->soca->status + = RICOH61x_SOCA_STABLE; + delay_flag = 1; + } + } + } + } else if (RICOH61x_SOCA_UNSTABLE == info->soca->status) { + /* caluc 95% ocv */ + temp_ocv = get_OCV_voltage(info, 10, USING) - + (get_OCV_voltage(info, 10, USING) - get_OCV_voltage(info, 9, USING))/2; + + if(g_full_flag == 1){ /* for issue 1 solution start*/ + info->soca->status = RICOH61x_SOCA_FULL; + info->soca->last_soc_full = 0; + err = reset_FG_process(info); + if (err < 0) + dev_err(info->dev, "Error in writing the control register\n"); + + goto end_flow; + }else if ((POWER_SUPPLY_STATUS_FULL == info->soca->chg_status) + && (calculated_ocv > temp_ocv)) { + info->soca->status = RICOH61x_SOCA_FULL; + g_full_flag = 0; + info->soca->last_soc_full = 0; + err = reset_FG_process(info); + if (err < 0) + dev_err(info->dev, "Error in writing the control register\n"); + goto end_flow; + } else if (info->soca->Ibat_ave >= -12) { + /* for issue1 solution end */ + /* check Full state or not*/ + if ((calculated_ocv > (get_OCV_voltage(info, 9, USING) + (get_OCV_voltage(info, 10, USING) - get_OCV_voltage(info, 9, USING))*7/10)) + || (POWER_SUPPLY_STATUS_FULL == info->soca->chg_status) + || (info->soca->displayed_soc > 9850)) + { + info->soca->status = RICOH61x_SOCA_FULL; + g_full_flag = 0; + info->soca->last_soc_full = 0; + err = reset_FG_process(info); + if (err < 0) + dev_err(info->dev, "Error in writing the control register\n"); + goto end_flow; + } else if ((calculated_ocv > (get_OCV_voltage(info, 9, USING))) + && (info->soca->Ibat_ave < 300)) + { + info->soca->status = RICOH61x_SOCA_FULL; + g_full_flag = 0; + info->soca->last_soc_full = 0; + err = reset_FG_process(info); + if (err < 0) + dev_err(info->dev, "Error in writing the control register\n"); + goto end_flow; + } + } + + info->soca->soc = info->soca->init_pswr * 100; + + err = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas, + &is_charging, 0); + if (err < 0) + dev_err(info->dev, "Read cc_sum Error !!-----\n"); + + info->soca->cc_delta + = (is_charging == true) ? cc_cap : -cc_cap; + + displayed_soc_temp + = info->soca->soc + info->soca->cc_delta; + if (displayed_soc_temp < 0) + displayed_soc_temp = 0; + displayed_soc_temp + = min(9850, displayed_soc_temp); + displayed_soc_temp = max(0, displayed_soc_temp); + + info->soca->displayed_soc = displayed_soc_temp; + + } else if (RICOH61x_SOCA_FG_RESET == info->soca->status) { + /* No update */ + } else if (RICOH61x_SOCA_START == info->soca->status) { + + err = measure_Ibatt_FG(info, &Ibat); + err = measure_vbatt_FG(info, &Vbat); + err = measure_vsys_ADC(info, &Vsys); + + info->soca->Ibat_ave = Ibat; + info->soca->Vbat_ave = Vbat; + info->soca->Vsys_ave = Vsys; + + err = check_jeita_status(info, &is_jeita_updated); + is_jeita_updated = false; + if (err < 0) { + dev_err(info->dev, "Error in updating JEITA %d\n", err); + } + err = ricoh61x_read(info->dev->parent, PSWR_REG, &val_pswr); + val_pswr &= 0x7f; + + if (info->first_pwon) { + displayed_soc_temp = val_pswr * 100; + + info->soca->soc = calc_capacity(info) * 100; + + err = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas, + &is_charging, 1); + if (err < 0) + dev_err(info->dev, "Read cc_sum Error !!-----\n"); + + info->soca->cc_delta + = (is_charging == true) ? cc_cap : -cc_cap; + + //get DSOC temp value + if (displayed_soc_temp == 0) { //initial power on or some error case + + displayed_soc_temp = info->soca->soc; + printk(KERN_INFO "PMU: %s : initial power on\n",__func__); + + } else if ((Ibat > 0) + && (displayed_soc_temp < info->soca->soc)){ //charge and poff_DSOC < RSOC + displayed_soc_temp = info->soca->soc; + printk(KERN_INFO "PMU: %s : normal case Ibat is %dmA, poffDSOC is %d, RSOC is %dn\n" + ,__func__, Ibat, val_pswr*100, info->soca->soc); + } else if ((info->soca->cc_delta <= 0) + && (displayed_soc_temp > info->soca->soc)){ //discharge and poff_DSOC > RSOC + + displayed_soc_temp = info->soca->soc; + printk(KERN_INFO "PMU: %s : normal case cc delta is %dmA, poffDSOC is %d, RSOC is %dn\n" + ,__func__, info->soca->cc_delta, val_pswr*100, info->soca->soc); + + } else if ((info->soca->cc_delta > 0) + && (displayed_soc_temp < info->soca->soc)){ //charge and poff_DSOC < RSOC + displayed_soc_temp = info->soca->soc; + printk(KERN_INFO "PMU: %s : normal case cc delta is %dmA, poffDSOC is %d, RSOC is %dn\n" + ,__func__, info->soca->cc_delta, val_pswr*100, info->soca->soc); + } else { + //displayed_soc_temp = displayed_soc_temp; + printk(KERN_INFO "PMU: %s : error case cc delta is %dmA, poffDSOC is %d, RSOC is %dn\n" + ,__func__, info->soca->cc_delta, val_pswr*100, info->soca->soc); + } + + //val = (info->soca->soc + 50)/100; + val = (displayed_soc_temp + 50)/100; + val &= 0x7f; + err = ricoh61x_write(info->dev->parent, PSWR_REG, val); + if (err < 0) + dev_err(info->dev, "Error in writing PSWR_REG\n"); + info->soca->init_pswr = val; + g_soc = val; + set_current_time2register(info); + + if (0 == info->soca->jt_limit) { + check_charge_status_2(info, displayed_soc_temp); + } else { + info->soca->displayed_soc = displayed_soc_temp; + } + if (Ibat < 0) { + initSettingOfLowVoltage(info); + info->soca->status = RICOH61x_SOCA_LOW_VOL; + } else { + info->soca->status = RICOH61x_SOCA_DISP; + info->soca->soc_delta = 0; + info->soca->last_soc = displayed_soc_temp; + } + + } else if (g_fg_on_mode && (val_pswr == 0x7f)) { + info->soca->soc = calc_capacity(info) * 100; + if (0 == info->soca->jt_limit) { + check_charge_status_2(info, info->soca->soc); + } else { + info->soca->displayed_soc = info->soca->soc; + } + info->soca->last_soc = info->soca->soc; + info->soca->status = RICOH61x_SOCA_STABLE; + } else { + info->soca->soc = val_pswr * 100; + if (err < 0) { + dev_err(info->dev, + "Error in reading PSWR_REG %d\n", err); + info->soca->soc + = calc_capacity(info) * 100; + } + + err = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas, + &is_charging, 2); + if (err < 0) + dev_err(info->dev, "Read cc_sum Error !!-----\n"); + + info->soca->cc_delta + = (is_charging == true) ? cc_cap : -cc_cap; + displayed_soc_temp + = info->soca->soc + (info->soca->cc_delta / 100) * 100; + + displayed_soc_temp + = min(10000, displayed_soc_temp); + if (displayed_soc_temp <= 100) { + displayed_soc_temp = 100; + err = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas, + &is_charging, 1); + } + + printk(KERN_INFO "PMU: %s : dsoc_temp(%d), soc(%d), cc_delta(%d)\n", + __func__, displayed_soc_temp, info->soca->soc, info->soca->cc_delta); + printk(KERN_INFO "PMU: %s : status(%d), rsoc_ready_flag(%d)\n", + __func__, info->soca->status, info->soca->rsoc_ready_flag); + + if (0 == info->soca->jt_limit) { + check_charge_status_2(info, displayed_soc_temp); + } else { + info->soca->displayed_soc = displayed_soc_temp; + } + + val = (displayed_soc_temp + 50)/100; + val &= 0x7f; + err = ricoh61x_write(info->dev->parent, PSWR_REG, val); + if (err < 0) + dev_err(info->dev, "Error in writing PSWR_REG\n"); + info->soca->init_pswr = val; + g_soc = val; + set_current_time2register(info); + + info->soca->last_soc = calc_capacity_2(info); + + if(info->soca->rsoc_ready_flag == 0) { + + info->soca->status = RICOH61x_SOCA_DISP; + info->soca->soc_delta = 0; +#ifdef _RICOH619_DEBUG_ + printk(KERN_DEBUG"PMU FG_RESET : %s : initial dsoc is %d\n",__func__,info->soca->displayed_soc); +#endif + } else if (Ibat < 0) { + initSettingOfLowVoltage(info); + info->soca->status = RICOH61x_SOCA_LOW_VOL; + } else { + info->soca->status = RICOH61x_SOCA_DISP; + info->soca->soc_delta = 0; + } + } + } +end_flow: + /* keep DSOC = 1 when Vbat is over 3.4V*/ + if( info->fg_poff_vbat != 0) { + if (info->soca->zero_flg == 1) { + if ((info->soca->Ibat_ave >= 0) + || (info->soca->Vbat_ave >= (info->fg_poff_vbat+100)*1000)) { + info->soca->zero_flg = 0; + } else { + info->soca->displayed_soc = 0; + } + } else if (info->soca->displayed_soc < 50) { + if (info->soca->Vbat_ave < 2000*1000) { /* error value */ + info->soca->displayed_soc = 100; + } else if (info->soca->Vbat_ave < info->fg_poff_vbat*1000) { + info->soca->displayed_soc = 0; + info->soca->zero_flg = 1; + } else { + info->soca->displayed_soc = 100; + } + } + } + + if (g_fg_on_mode + && (info->soca->status == RICOH61x_SOCA_STABLE)) { + err = ricoh61x_write(info->dev->parent, PSWR_REG, 0x7f); + if (err < 0) + dev_err(info->dev, "Error in writing PSWR_REG\n"); + g_soc = 0x7F; + set_current_time2register(info); + err = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas, + &is_charging, 1); + if (err < 0) + dev_err(info->dev, "Read cc_sum Error !!-----\n"); + + } else if (((RICOH61x_SOCA_UNSTABLE != info->soca->status) + && (info->soca->rsoc_ready_flag != 0)) + || (RICOH61x_SOCA_LOW_VOL == info->soca->status)){ + if ((info->soca->displayed_soc + 50)/100 <= 1) { + val = 1; + } else { + val = (info->soca->displayed_soc + 50)/100; + val &= 0x7f; + } + err = ricoh61x_write(info->dev->parent, PSWR_REG, val); + if (err < 0) + dev_err(info->dev, "Error in writing PSWR_REG\n"); + + g_soc = val; + set_current_time2register(info); + + info->soca->init_pswr = val; + + if(RICOH61x_SOCA_LOW_VOL != info->soca->status) + { + err = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas, + &is_charging, 1); + if (err < 0) + dev_err(info->dev, "Read cc_sum Error !!-----\n"); + printk(KERN_INFO "PMU: %s Full-Clear CC, PSWR(%d)\n", + __func__, val); + } + } else { /* Case of UNSTABLE STATE */ + if ((info->soca->displayed_soc + 50)/100 <= 1) { + val = 1; + } else { + val = (info->soca->displayed_soc + 50)/100; + val &= 0x7f; + } + err = ricoh61x_write(info->dev->parent, PSWR_REG, val); + if (err < 0) + dev_err(info->dev, "Error in writing PSWR_REG\n"); + + g_soc = val; + set_current_time2register(info); + + err = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas, + &is_charging, 2); + if (err < 0) + dev_err(info->dev, "Read cc_sum Error !!-----\n"); + info->soca->cc_delta + = (is_charging == true) ? cc_cap : -cc_cap; + + val = info->soca->init_pswr + (info->soca->cc_delta/100); + val = min(100, val); + val = max(1, val); + + info->soca->init_pswr = val; + + info->soca->last_cc_rrf0 = info->soca->cc_delta%100; + + printk(KERN_INFO "PMU: %s Half-Clear CC, init_pswr(%d), cc_delta(%d)\n", + __func__, info->soca->init_pswr, info->soca->cc_delta); + + } + +#ifdef _RICOH619_DEBUG_ + printk(KERN_DEBUG"PMU:STATUS= %d: IBAT= %d: VSYS= %d: VBAT= %d: DSOC= %d: RSOC= %d: cc_delta=%d: rrf= %d\n", + info->soca->status, info->soca->Ibat_ave, info->soca->Vsys_ave, info->soca->Vbat_ave, + info->soca->displayed_soc, info->soca->soc, info->soca->cc_delta, info->soca->rsoc_ready_flag); +#endif + +// printk("PMU AGE*STATUS * %d*IBAT*%d*VSYS*%d*VBAT*%d*DSOC*%d*RSOC*%d*-------\n", +// info->soca->status, info->soca->Ibat_ave, info->soca->Vsys_ave, info->soca->Vbat_ave, +// info->soca->displayed_soc, info->soca->soc); + +#ifdef DISABLE_CHARGER_TIMER + /* clear charger timer */ + if ( info->soca->chg_status == POWER_SUPPLY_STATUS_CHARGING ) { + err = ricoh61x_read(info->dev->parent, TIMSET_REG, &val); + if (err < 0) + dev_err(info->dev, + "Error in read TIMSET_REG%d\n", err); + /* to check bit 0-1 */ + val2 = val & 0x03; + + if (val2 == 0x02){ + /* set rapid timer 240 -> 300 */ + err = ricoh61x_set_bits(info->dev->parent, TIMSET_REG, 0x03); + if (err < 0) { + dev_err(info->dev, "Error in writing the control register\n"); + } + } else { + /* set rapid timer 300 -> 240 */ + err = ricoh61x_clr_bits(info->dev->parent, TIMSET_REG, 0x01); + err = ricoh61x_set_bits(info->dev->parent, TIMSET_REG, 0x02); + if (err < 0) { + dev_err(info->dev, "Error in writing the control register\n"); + } + } + } +#endif + + if (0 == info->soca->ready_fg) + queue_delayed_work(info->monitor_wqueue, &info->displayed_work, + RICOH61x_FG_RESET_TIME * HZ); + else if (delay_flag == 1) + queue_delayed_work(info->monitor_wqueue, &info->displayed_work, + RICOH61x_DELAY_TIME * HZ); + else if ((RICOH61x_SOCA_DISP == info->soca->status) + && (info->soca->Ibat_ave > 0)) + queue_delayed_work(info->monitor_wqueue, &info->displayed_work, + RICOH61x_DISP_CHG_UPDATE_TIME * HZ); + else if ((info->soca->hurry_up_flg == 1) && (RICOH61x_SOCA_LOW_VOL == info->soca->status)) + queue_delayed_work(info->monitor_wqueue, &info->displayed_work, + RICOH61x_LOW_VOL_DOWN_TIME * HZ); + else + queue_delayed_work(info->monitor_wqueue, &info->displayed_work, + RICOH61x_DISPLAY_UPDATE_TIME * HZ); + + mutex_unlock(&info->lock); + + if((true == is_jeita_updated) + || (info->soca->last_displayed_soc/100 != (info->soca->displayed_soc+50)/100)) + power_supply_changed(&info->battery); + + info->soca->last_displayed_soc = info->soca->displayed_soc+50; + + return; +} + +static void ricoh61x_stable_charge_countdown_work(struct work_struct *work) +{ + int ret; + int max = 0; + int min = 100; + int i; + struct ricoh61x_battery_info *info = container_of(work, + struct ricoh61x_battery_info, charge_stable_work.work); + + if (info->entry_factory_mode) + return; + + mutex_lock(&info->lock); + if (RICOH61x_SOCA_FG_RESET == info->soca->status) + info->soca->ready_fg = 1; + + if (2 <= info->soca->stable_count) { + if (3 == info->soca->stable_count + && RICOH61x_SOCA_FG_RESET == info->soca->status) { + ret = reset_FG_process(info); + if (ret < 0) + dev_err(info->dev, "Error in writing the control register\n"); + } + info->soca->stable_count = info->soca->stable_count - 1; + queue_delayed_work(info->monitor_wqueue, + &info->charge_stable_work, + RICOH61x_FG_STABLE_TIME * HZ / 10); + } else if (0 >= info->soca->stable_count) { + /* Finished queue, ignore */ + } else if (1 == info->soca->stable_count) { + if (RICOH61x_SOCA_UNSTABLE == info->soca->status) { + /* Judge if FG need reset or Not */ + info->soca->soc = calc_capacity(info) * 100; + if (info->chg_ctr != 0) { + queue_delayed_work(info->monitor_wqueue, + &info->charge_stable_work, + RICOH61x_FG_STABLE_TIME * HZ / 10); + mutex_unlock(&info->lock); + return; + } + /* Do reset setting */ + ret = reset_FG_process(info); + if (ret < 0) + dev_err(info->dev, "Error in writing the control register\n"); + + info->soca->status = RICOH61x_SOCA_FG_RESET; + + /* Delay for addition Reset Time (6s) */ + queue_delayed_work(info->monitor_wqueue, + &info->charge_stable_work, + RICOH61x_FG_RESET_TIME*HZ); + } else if (RICOH61x_SOCA_FG_RESET == info->soca->status) { + info->soca->reset_soc[2] = info->soca->reset_soc[1]; + info->soca->reset_soc[1] = info->soca->reset_soc[0]; + info->soca->reset_soc[0] = calc_capacity(info) * 100; + info->soca->reset_count++; + + if (info->soca->reset_count > 10) { + /* Reset finished; */ + info->soca->soc = info->soca->reset_soc[0]; + info->soca->stable_count = 0; + goto adjust; + } + + for (i = 0; i < 3; i++) { + if (max < info->soca->reset_soc[i]/100) + max = info->soca->reset_soc[i]/100; + if (min > info->soca->reset_soc[i]/100) + min = info->soca->reset_soc[i]/100; + } + + if ((info->soca->reset_count > 3) && ((max - min) + < RICOH61x_MAX_RESET_SOC_DIFF)) { + /* Reset finished; */ + info->soca->soc = info->soca->reset_soc[0]; + info->soca->stable_count = 0; + goto adjust; + } else { + /* Do reset setting */ + ret = reset_FG_process(info); + if (ret < 0) + dev_err(info->dev, "Error in writing the control register\n"); + + /* Delay for addition Reset Time (6s) */ + queue_delayed_work(info->monitor_wqueue, + &info->charge_stable_work, + RICOH61x_FG_RESET_TIME*HZ); + } + /* Finished queue From now, select FG as result; */ + } else if (RICOH61x_SOCA_START == info->soca->status) { + /* Normal condition */ + } else { /* other state ZERO/DISP/STABLE */ + info->soca->stable_count = 0; + } + + mutex_unlock(&info->lock); + return; + +adjust: + info->soca->last_soc = info->soca->soc; + info->soca->status = RICOH61x_SOCA_DISP; + info->soca->soc_delta = 0; + + } + mutex_unlock(&info->lock); + return; +} + +static void ricoh61x_charge_monitor_work(struct work_struct *work) +{ + struct ricoh61x_battery_info *info = container_of(work, + struct ricoh61x_battery_info, charge_monitor_work.work); + + get_power_supply_status(info); + + if (POWER_SUPPLY_STATUS_DISCHARGING == info->soca->chg_status + || POWER_SUPPLY_STATUS_NOT_CHARGING == info->soca->chg_status) { + switch (info->soca->dischg_state) { + case 0: + info->soca->dischg_state = 1; + break; + case 1: + info->soca->dischg_state = 2; + break; + + case 2: + default: + break; + } + } else { + info->soca->dischg_state = 0; + } + + queue_delayed_work(info->monitor_wqueue, &info->charge_monitor_work, + RICOH61x_CHARGE_MONITOR_TIME * HZ); + + return; +} + +static void ricoh61x_get_charge_work(struct work_struct *work) +{ + struct ricoh61x_battery_info *info = container_of(work, + struct ricoh61x_battery_info, get_charge_work.work); + + int Vbat_temp, Vsys_temp, Ibat_temp; + int Vbat_sort[RICOH61x_GET_CHARGE_NUM]; + int Vsys_sort[RICOH61x_GET_CHARGE_NUM]; + int Ibat_sort[RICOH61x_GET_CHARGE_NUM]; + int i, j; + int ret; + + mutex_lock(&info->lock); + + for (i = RICOH61x_GET_CHARGE_NUM-1; i > 0; i--) { + if (0 == info->soca->chg_count) { + info->soca->Vbat[i] = 0; + info->soca->Vsys[i] = 0; + info->soca->Ibat[i] = 0; + } else { + info->soca->Vbat[i] = info->soca->Vbat[i-1]; + info->soca->Vsys[i] = info->soca->Vsys[i-1]; + info->soca->Ibat[i] = info->soca->Ibat[i-1]; + } + } + + ret = measure_vbatt_FG(info, &info->soca->Vbat[0]); + ret = measure_vsys_ADC(info, &info->soca->Vsys[0]); + ret = measure_Ibatt_FG(info, &info->soca->Ibat[0]); + + info->soca->chg_count++; + + if (RICOH61x_GET_CHARGE_NUM != info->soca->chg_count) { + queue_delayed_work(info->monitor_wqueue, &info->get_charge_work, + RICOH61x_CHARGE_CALC_TIME * HZ); + mutex_unlock(&info->lock); + return ; + } + + for (i = 0; i < RICOH61x_GET_CHARGE_NUM; i++) { + Vbat_sort[i] = info->soca->Vbat[i]; + Vsys_sort[i] = info->soca->Vsys[i]; + Ibat_sort[i] = info->soca->Ibat[i]; + } + + Vbat_temp = 0; + Vsys_temp = 0; + Ibat_temp = 0; + for (i = 0; i < RICOH61x_GET_CHARGE_NUM - 1; i++) { + for (j = RICOH61x_GET_CHARGE_NUM - 1; j > i; j--) { + if (Vbat_sort[j - 1] > Vbat_sort[j]) { + Vbat_temp = Vbat_sort[j]; + Vbat_sort[j] = Vbat_sort[j - 1]; + Vbat_sort[j - 1] = Vbat_temp; + } + if (Vsys_sort[j - 1] > Vsys_sort[j]) { + Vsys_temp = Vsys_sort[j]; + Vsys_sort[j] = Vsys_sort[j - 1]; + Vsys_sort[j - 1] = Vsys_temp; + } + if (Ibat_sort[j - 1] > Ibat_sort[j]) { + Ibat_temp = Ibat_sort[j]; + Ibat_sort[j] = Ibat_sort[j - 1]; + Ibat_sort[j - 1] = Ibat_temp; + } + } + } + + Vbat_temp = 0; + Vsys_temp = 0; + Ibat_temp = 0; + for (i = 3; i < RICOH61x_GET_CHARGE_NUM-3; i++) { + Vbat_temp = Vbat_temp + Vbat_sort[i]; + Vsys_temp = Vsys_temp + Vsys_sort[i]; + Ibat_temp = Ibat_temp + Ibat_sort[i]; + } + Vbat_temp = Vbat_temp / (RICOH61x_GET_CHARGE_NUM - 6); + Vsys_temp = Vsys_temp / (RICOH61x_GET_CHARGE_NUM - 6); + Ibat_temp = Ibat_temp / (RICOH61x_GET_CHARGE_NUM - 6); + + if (0 == info->soca->chg_count) { + queue_delayed_work(info->monitor_wqueue, &info->get_charge_work, + RICOH61x_CHARGE_UPDATE_TIME * HZ); + mutex_unlock(&info->lock); + return; + } else { + info->soca->Vbat_ave = Vbat_temp; + info->soca->Vsys_ave = Vsys_temp; + info->soca->Ibat_ave = Ibat_temp; + } + + info->soca->chg_count = 0; + queue_delayed_work(info->monitor_wqueue, &info->get_charge_work, + RICOH61x_CHARGE_UPDATE_TIME * HZ); + mutex_unlock(&info->lock); + return; +} + +/* Initial setting of FuelGauge SOCA function */ +static int ricoh61x_init_fgsoca(struct ricoh61x_battery_info *info) +{ + int i; + int err; + uint8_t val; + + for (i = 0; i <= 10; i = i+1) { + info->soca->ocv_table[i] = get_OCV_voltage(info, i, USING); + printk(KERN_INFO "PMU: %s : * %d0%% voltage = %d uV\n", + __func__, i, info->soca->ocv_table[i]); + } + + for (i = 0; i < 3; i = i+1) + info->soca->reset_soc[i] = 0; + info->soca->reset_count = 0; + + if (info->first_pwon) { + + err = ricoh61x_read(info->dev->parent, CHGISET_REG, &val); + if (err < 0) + dev_err(info->dev, + "Error in read CHGISET_REG%d\n", err); + + err = ricoh61x_write(info->dev->parent, CHGISET_REG, 0); + if (err < 0) + dev_err(info->dev, + "Error in writing CHGISET_REG%d\n", err); + /* msleep(1000); */ + + if (!info->entry_factory_mode) { + err = ricoh61x_write(info->dev->parent, + FG_CTRL_REG, 0x51); + if (err < 0) + dev_err(info->dev, "Error in writing the control register\n"); + } + + info->soca->rsoc_ready_flag = 1; + + /* msleep(6000); */ + + err = ricoh61x_write(info->dev->parent, CHGISET_REG, val); + if (err < 0) + dev_err(info->dev, + "Error in writing CHGISET_REG%d\n", err); + } + + /* Rbat : Transfer */ + info->soca->Rbat = get_OCV_init_Data(info, 12, USING) * 1000 / 512 + * 5000 / 4095; + info->soca->n_cap = get_OCV_init_Data(info, 11, USING); + + + info->soca->displayed_soc = 0; + info->soca->last_displayed_soc = 0; + info->soca->suspend_soc = 0; + info->soca->suspend_full_flg = false; + info->soca->ready_fg = 0; + info->soca->soc_delta = 0; + info->soca->full_reset_count = 0; + info->soca->soc_full = 0; + info->soca->fc_cap = 0; + info->soca->status = RICOH61x_SOCA_START; + /* stable count down 11->2, 1: reset; 0: Finished; */ + info->soca->stable_count = 11; + info->soca->dischg_state = 0; + info->soca->Vbat_ave = 0; + info->soca->Vbat_old = 0; + info->soca->Vsys_ave = 0; + info->soca->Ibat_ave = 0; + info->soca->chg_count = 0; + info->soca->hurry_up_flg = 0; + info->soca->re_cap_old = 0; + info->soca->jt_limit = 0; + info->soca->zero_flg = 0; + info->soca->cc_cap_offset = 0; + info->soca->sus_cc_cap_offset = 0; + info->soca->last_soc_full = 0; + info->soca->rsoc_limit = 0; + info->soca->last_cc_rrf0 = 0; + info->soca->last_cc_delta_cap = 0; + info->soca->last_cc_delta_cap_mas = 0; + info->soca->temp_cc_delta_cap_mas = 0; + info->soca->temp_cc_delta_cap = 0; + + info->soca->store_fl_current = RICOH61x_FL_CURRENT_DEF; + info->soca->store_slp_state = 0; + info->soca->store_sus_current = RICOH61x_SUS_CURRENT_DEF; + info->soca->store_hiber_current = RICOH61x_HIBER_CURRENT_DEF; + + for (i = 0; i < 11; i++) { + info->soca->ocv_table_low[i] = 0; + } + + for (i = 0; i < RICOH61x_GET_CHARGE_NUM; i++) { + info->soca->Vbat[i] = 0; + info->soca->Vsys[i] = 0; + info->soca->Ibat[i] = 0; + } + + /*********************************/ + //fl_level = RICOH61x_FL_LEVEL_DEF; + //fl_current = RICOH61x_FL_CURRENT_DEF; + //slp_state = 0; + //idle_current = RICOH61x_IDLE_CURRENT_DEF; + //sus_current = RICOH61x_SUS_CURRENT_DEF; + //hiber_current = RICOH61x_HIBER_CURRENT_DEF; + //bat_alert_req_flg = 0; +#ifdef STANDBY_MODE_DEBUG + multiple_sleep_mode = 0; +#endif + /*********************************/ + +#ifdef ENABLE_FG_KEEP_ON_MODE + g_fg_on_mode = 1; + info->soca->rsoc_ready_flag = 1; +#else + g_fg_on_mode = 0; +#endif + + + /* Start first Display job */ + if(info->first_pwon) { + queue_delayed_work(info->monitor_wqueue, &info->displayed_work, + RICOH61x_FG_RESET_TIME*HZ); + }else { + queue_delayed_work(info->monitor_wqueue, &info->displayed_work, + RICOH61x_MAIN_START_TIME*HZ); + } + + /* Start first Waiting stable job */ + queue_delayed_work(info->monitor_wqueue, &info->charge_stable_work, + RICOH61x_FG_STABLE_TIME*HZ/10); + + queue_delayed_work(info->monitor_wqueue, &info->charge_monitor_work, + RICOH61x_CHARGE_MONITOR_TIME * HZ); + + queue_delayed_work(info->monitor_wqueue, &info->get_charge_work, + RICOH61x_CHARGE_MONITOR_TIME * HZ); + if (info->jt_en) { + if (info->jt_hw_sw) { + /* Enable JEITA function supported by H/W */ + err = ricoh61x_set_bits(info->dev->parent, CHGCTL1_REG, 0x04); + if (err < 0) + dev_err(info->dev, "Error in writing the control register\n"); + } else { + /* Disable JEITA function supported by H/W */ + err = ricoh61x_clr_bits(info->dev->parent, CHGCTL1_REG, 0x04); + if (err < 0) + dev_err(info->dev, "Error in writing the control register\n"); + queue_delayed_work(info->monitor_wqueue, &info->jeita_work, + RICOH61x_FG_RESET_TIME * HZ); + } + } else { + /* Disable JEITA function supported by H/W */ + err = ricoh61x_clr_bits(info->dev->parent, CHGCTL1_REG, 0x04); + if (err < 0) + dev_err(info->dev, "Error in writing the control register\n"); + if (0xff != info->ch_ilim_adp && (info->ch_ilim_adp <= 0x1D)) { + /* REGISET1:(0xB6) setting */ + err = ricoh61x_write(info->dev->parent, REGISET1_REG, info->ch_ilim_adp); + if (err < 0) + dev_err(info->dev, "Error in writing REGISET1_REG %d\n",err); + if (0xff != info->jt_ichg_h && (info->jt_ichg_h <= 0x1D)) { + /* CHGISET:(0xB8) setting */ + err = ricoh61x_write(info->dev->parent, CHGISET_REG, info->jt_ichg_h); + if (err < 0) + dev_err(info->dev, "Error in writing CHGISET_REG %d\n",err); + } + } + } + + printk(KERN_INFO "PMU: %s : * Rbat = %d mOhm n_cap = %d mAH\n", + __func__, info->soca->Rbat, info->soca->n_cap); + return 1; +} +#endif + +static void ricoh61x_changed_work(struct work_struct *work) +{ + struct ricoh61x_battery_info *info = container_of(work, + struct ricoh61x_battery_info, changed_work.work); + + printk(KERN_INFO "PMU: %s\n", __func__); + power_supply_changed(&info->battery); + + return; +} + +static int check_jeita_status(struct ricoh61x_battery_info *info, bool *is_jeita_updated) +/* JEITA Parameter settings +* +* VCHG +* | +* jt_vfchg_h~+~~~~~~~~~~~~~~~~~~~+ +* | | +* jt_vfchg_l-| - - - - - - - - - +~~~~~~~~~~+ +* | Charge area + | +* -------0--+-------------------+----------+--- Temp +* ! + +* ICHG +* | + +* jt_ichg_h-+ - -+~~~~~~~~~~~~~~+~~~~~~~~~~+ +* + | + | +* jt_ichg_l-+~~~~+ Charge area | +* | + + | +* 0--+----+--------------+----------+--- Temp +* 0 jt_temp_l jt_temp_h 55 +*/ +{ + int temp; + int err = 0; + int vfchg; + uint8_t chgiset_org; + uint8_t batset2_org; + uint8_t set_vchg_h, set_vchg_l; + uint8_t set_ichg_h, set_ichg_l; + + *is_jeita_updated = false; + /* No execute if JEITA disabled */ + if (!info->jt_en || info->jt_hw_sw) + return 0; + + /* Check FG Reset */ + if (info->soca->ready_fg) { + temp = get_battery_temp_2(info) / 10; + } else { + printk(KERN_INFO "JEITA: %s *** cannot update by resetting FG ******\n", __func__); + goto out; + } + + /* Read BATSET2 */ + err = ricoh61x_read(info->dev->parent, BATSET2_REG, &batset2_org); + if (err < 0) { + dev_err(info->dev, "Error in readng the battery setting register\n"); + goto out; + } + vfchg = (batset2_org & 0x70) >> 4; + batset2_org &= 0x8F; + + /* Read CHGISET */ + err = ricoh61x_read(info->dev->parent, CHGISET_REG, &chgiset_org); + if (err < 0) { + dev_err(info->dev, "Error in readng the chrage setting register\n"); + goto out; + } + chgiset_org &= 0xC0; + + set_ichg_h = (uint8_t)(chgiset_org | info->jt_ichg_h); + set_ichg_l = (uint8_t)(chgiset_org | info->jt_ichg_l); + + set_vchg_h = (uint8_t)((info->jt_vfchg_h << 4) | batset2_org); + set_vchg_l = (uint8_t)((info->jt_vfchg_l << 4) | batset2_org); + + printk(KERN_INFO "PMU: %s *** Temperature: %d, vfchg: %d, SW status: %d, chg_status: %d ******\n", + __func__, temp, vfchg, info->soca->status, info->soca->chg_status); + + if (temp <= 0 || 55 <= temp) { + /* 1st and 5th temperature ranges (~0, 55~) */ + printk(KERN_INFO "PMU: %s *** Temp(%d) is out of 0-55 ******\n", __func__, temp); + err = ricoh61x_clr_bits(info->dev->parent, CHGCTL1_REG, 0x03); + if (err < 0) { + dev_err(info->dev, "Error in writing the control register\n"); + goto out; + } + info->soca->jt_limit = 0; + *is_jeita_updated = true; + } else if (temp < info->jt_temp_l) { + /* 2nd temperature range (0~12) */ + if (vfchg != info->jt_vfchg_h) { + printk(KERN_INFO "PMU: %s *** 0jt_vfchg_h); + err = ricoh61x_clr_bits(info->dev->parent, CHGCTL1_REG, 0x03); + if (err < 0) { + dev_err(info->dev, "Error in writing the control register\n"); + goto out; + } + + /* set VFCHG/VRCHG */ + err = ricoh61x_write(info->dev->parent, + BATSET2_REG, set_vchg_h); + if (err < 0) { + dev_err(info->dev, "Error in writing the battery setting register\n"); + goto out; + } + info->soca->jt_limit = 0; + *is_jeita_updated = true; + } else + printk(KERN_INFO "PMU: %s *** 0jt_vfchg_h); + + /* set ICHG */ + err = ricoh61x_write(info->dev->parent, CHGISET_REG, set_ichg_l); + if (err < 0) { + dev_err(info->dev, "Error in writing the battery setting register\n"); + goto out; + } + err = ricoh61x_set_bits(info->dev->parent, CHGCTL1_REG, 0x03); + if (err < 0) { + dev_err(info->dev, "Error in writing the control register\n"); + goto out; + } + } else if (temp < info->jt_temp_h) { + /* 3rd temperature range (12~50) */ + if (vfchg != info->jt_vfchg_h) { + printk(KERN_INFO "PMU: %s *** 12jt_vfchg_h); + + err = ricoh61x_clr_bits(info->dev->parent, CHGCTL1_REG, 0x03); + if (err < 0) { + dev_err(info->dev, "Error in writing the control register\n"); + goto out; + } + /* set VFCHG/VRCHG */ + err = ricoh61x_write(info->dev->parent, + BATSET2_REG, set_vchg_h); + if (err < 0) { + dev_err(info->dev, "Error in writing the battery setting register\n"); + goto out; + } + info->soca->jt_limit = 0; + *is_jeita_updated = true; + } else + printk(KERN_INFO "PMU: %s *** 12jt_vfchg_h); + + /* set ICHG */ + err = ricoh61x_write(info->dev->parent, CHGISET_REG, set_ichg_h); + if (err < 0) { + dev_err(info->dev, "Error in writing the battery setting register\n"); + goto out; + } + err = ricoh61x_set_bits(info->dev->parent, CHGCTL1_REG, 0x03); + if (err < 0) { + dev_err(info->dev, "Error in writing the control register\n"); + goto out; + } + } else if (temp < 55) { + /* 4th temperature range (50~55) */ + if (vfchg != info->jt_vfchg_l) { + printk(KERN_INFO "PMU: %s *** 50jt_vfchg_l); + + err = ricoh61x_clr_bits(info->dev->parent, CHGCTL1_REG, 0x03); + if (err < 0) { + dev_err(info->dev, "Error in writing the control register\n"); + goto out; + } + /* set VFCHG/VRCHG */ + err = ricoh61x_write(info->dev->parent, + BATSET2_REG, set_vchg_l); + if (err < 0) { + dev_err(info->dev, "Error in writing the battery setting register\n"); + goto out; + } + info->soca->jt_limit = 1; + *is_jeita_updated = true; + } else + printk(KERN_INFO "JEITA: %s *** 50jt_vfchg_l); + + /* set ICHG */ + err = ricoh61x_write(info->dev->parent, CHGISET_REG, set_ichg_h); + if (err < 0) { + dev_err(info->dev, "Error in writing the battery setting register\n"); + goto out; + } + err = ricoh61x_set_bits(info->dev->parent, CHGCTL1_REG, 0x03); + if (err < 0) { + dev_err(info->dev, "Error in writing the control register\n"); + goto out; + } + } + + get_power_supply_status(info); + printk(KERN_INFO "PMU: %s *** Hope updating value in this timing after checking jeita, chg_status: %d, is_jeita_updated: %d ******\n", + __func__, info->soca->chg_status, *is_jeita_updated); + + return 0; + +out: + printk(KERN_INFO "PMU: %s ERROR ******\n", __func__); + return err; +} + +static void ricoh61x_jeita_work(struct work_struct *work) +{ + int ret; + bool is_jeita_updated = false; + struct ricoh61x_battery_info *info = container_of(work, + struct ricoh61x_battery_info, jeita_work.work); + + mutex_lock(&info->lock); + + ret = check_jeita_status(info, &is_jeita_updated); + if (0 == ret) { + queue_delayed_work(info->monitor_wqueue, &info->jeita_work, + RICOH61x_JEITA_UPDATE_TIME * HZ); + } else { + printk(KERN_INFO "PMU: %s *** Call check_jeita_status() in jeita_work, err:%d ******\n", + __func__, ret); + queue_delayed_work(info->monitor_wqueue, &info->jeita_work, + RICOH61x_FG_RESET_TIME * HZ); + } + + mutex_unlock(&info->lock); + + if(true == is_jeita_updated) + power_supply_changed(&info->battery); + + return; +} + +#ifdef ENABLE_FACTORY_MODE +/*------------------------------------------------------*/ +/* Factory Mode */ +/* Check Battery exist or not */ +/* If not, disabled Rapid to Complete State change */ +/*------------------------------------------------------*/ +static int ricoh61x_factory_mode(struct ricoh61x_battery_info *info) +{ + int ret = 0; + uint8_t val = 0; + + ret = ricoh61x_read(info->dev->parent, RICOH61x_INT_MON_CHGCTR, &val); + if (ret < 0) { + dev_err(info->dev, "Error in reading the control register\n"); + return ret; + } + if (!(val & 0x01)) /* No Adapter connected */ + return ret; + + /* Rapid to Complete State change disable */ + ret = ricoh61x_set_bits(info->dev->parent, CHGCTL1_REG, 0x40); + + if (ret < 0) { + dev_err(info->dev, "Error in writing the control register\n"); + return ret; + } + + /* Wait 1s for checking Charging State */ + queue_delayed_work(info->factory_mode_wqueue, &info->factory_mode_work, + 1*HZ); + + return ret; +} + +static void check_charging_state_work(struct work_struct *work) +{ + struct ricoh61x_battery_info *info = container_of(work, + struct ricoh61x_battery_info, factory_mode_work.work); + + int ret = 0; + uint8_t val = 0; + int chargeCurrent = 0; + + ret = ricoh61x_read(info->dev->parent, CHGSTATE_REG, &val); + if (ret < 0) { + dev_err(info->dev, "Error in reading the control register\n"); + return; + } + + + chargeCurrent = get_check_fuel_gauge_reg(info, CC_AVERAGE1_REG, + CC_AVERAGE0_REG, 0x3fff); + if (chargeCurrent < 0) { + dev_err(info->dev, "Error in reading the FG register\n"); + return; + } + + /* Repid State && Charge Current about 0mA */ + if (((chargeCurrent >= 0x3ffc && chargeCurrent <= 0x3fff) + || chargeCurrent < 0x05) && val == 0x43) { + printk(KERN_INFO "PMU:%s --- No battery !! Enter Factory mode ---\n" + , __func__); + info->entry_factory_mode = true; + /* clear FG_ACC bit */ + ret = ricoh61x_clr_bits(info->dev->parent, RICOH61x_FG_CTRL, 0x10); + if (ret < 0) + dev_err(info->dev, "Error in writing FG_CTRL\n"); + + return; /* Factory Mode */ + } + + /* Return Normal Mode --> Rapid to Complete State change enable */ + /* disable the status change from Rapid Charge to Charge Complete */ + + ret = ricoh61x_clr_bits(info->dev->parent, CHGCTL1_REG, 0x40); + if (ret < 0) { + dev_err(info->dev, "Error in writing the control register\n"); + return; + } + printk(KERN_INFO "PMU:%s --- Battery exist !! Return Normal mode ---0x%2x\n" + , __func__, val); + + return; +} +#endif /* ENABLE_FACTORY_MODE */ + +static int Calc_Linear_Interpolation(int x0, int y0, int x1, int y1, int y) +{ + int alpha; + int x; + + alpha = (y - y0)*100 / (y1 - y0); + + x = ((100 - alpha) * x0 + alpha * x1) / 100; + + return x; +} + +static void ricoh61x_scaling_OCV_table(struct ricoh61x_battery_info *info, int cutoff_vol, int full_vol, int *start_per, int *end_per) +{ + int i, j; + int temp; + int percent_step; + int OCV_percent_new[11]; + + /* get ocv table. this table is calculated by Apprication */ + //printk("PMU : %s : original table\n",__func__); + //for (i = 0; i <= 10; i = i+1) { + // printk(KERN_INFO "PMU: %s : %d0%% voltage = %d uV\n", + // __func__, i, info->soca->ocv_table_def[i]); + //} + //printk("PMU: %s : cutoff_vol %d full_vol %d\n", + // __func__, cutoff_vol,full_vol); + + /* Check Start % */ + if (info->soca->ocv_table_def[0] > cutoff_vol * 1000) { + *start_per = 0; + printk("PMU : %s : setting value of cuttoff_vol(%d) is out of range(%d) \n",__func__, cutoff_vol, info->soca->ocv_table_def[0]); + } else { + for (i = 1; i < 11; i++) { + if (info->soca->ocv_table_def[i] >= cutoff_vol * 1000) { + /* unit is 0.001% */ + *start_per = Calc_Linear_Interpolation( + (i-1)*1000, info->soca->ocv_table_def[i-1], i*1000, + info->soca->ocv_table_def[i], (cutoff_vol * 1000)); + break; + } + } + } + + /* Check End % */ + for (i = 1; i < 11; i++) { + if (info->soca->ocv_table_def[i] >= full_vol * 1000) { + /* unit is 0.001% */ + *end_per = Calc_Linear_Interpolation( + (i-1)*1000, info->soca->ocv_table_def[i-1], i*1000, + info->soca->ocv_table_def[i], (full_vol * 1000)); + break; + } + } + + /* calc new ocv percent */ + percent_step = ( *end_per - *start_per) / 10; + //printk("PMU : %s : percent_step is %d end per is %d start per is %d\n",__func__, percent_step, *end_per, *start_per); + + for (i = 0; i < 11; i++) { + OCV_percent_new[i] + = *start_per + percent_step*(i - 0); + } + + /* calc new ocv voltage */ + for (i = 0; i < 11; i++) { + for (j = 1; j < 11; j++) { + if (1000*j >= OCV_percent_new[i]) { + temp = Calc_Linear_Interpolation( + info->soca->ocv_table_def[j-1], (j-1)*1000, + info->soca->ocv_table_def[j] , j*1000, + OCV_percent_new[i]); + + temp = ( (temp/1000) * 4095 ) / 5000; + + battery_init_para[info->num][i*2 + 1] = temp; + battery_init_para[info->num][i*2] = temp >> 8; + + break; + } + } + } + printk("PMU : %s : new table\n",__func__); + for (i = 0; i <= 10; i = i+1) { + temp = (battery_init_para[info->num][i*2]<<8) + | (battery_init_para[info->num][i*2+1]); + /* conversion unit 1 Unit is 1.22mv (5000/4095 mv) */ + temp = ((temp * 50000 * 10 / 4095) + 5) / 10; + printk("PMU : %s : ocv_table %d is %d v\n",__func__, i, temp); + } + +} + +static int ricoh61x_set_OCV_table(struct ricoh61x_battery_info *info) +{ + int ret = 0; + int i; + int full_ocv; + int available_cap; + int available_cap_ori; + int temp; + int temp1; + int start_per = 0; + int end_per = 0; + int Rbat; + int Ibat_min; + uint8_t val; + uint8_t val2; + uint8_t val_temp; + + + //get ocv table + for (i = 0; i <= 10; i = i+1) { + info->soca->ocv_table_def[i] = get_OCV_voltage(info, i, USING); + printk(KERN_INFO "PMU: %s : %d0%% voltage = %d uV\n", + __func__, i, info->soca->ocv_table_def[i]); + } + + //save original header file data + for (i = 0; i < 32; i++){ + info->soca->battery_init_para_original[i] = battery_init_para[info->num][i]; + } + + temp = (battery_init_para[info->num][24]<<8) | (battery_init_para[info->num][25]); + Rbat = temp * 1000 / 512 * 5000 / 4095; + info->soca->Rsys = Rbat + 55; + + if ((info->fg_target_ibat == 0) || (info->fg_target_vsys == 0)) { /* normal version */ + + temp = (battery_init_para[info->num][22]<<8) | (battery_init_para[info->num][23]); + //fa_cap = get_check_fuel_gauge_reg(info, FA_CAP_H_REG, FA_CAP_L_REG, + // 0x7fff); + + info->soca->target_ibat = temp*2/10; /* calc 0.2C*/ + temp1 = (battery_init_para[info->num][0]<<8) | (battery_init_para[info->num][1]); +// temp = get_OCV_voltage(info, 0) / 1000; /* unit is 1mv*/ +// info->soca->cutoff_ocv = info->soca->target_vsys - Ibat_min * info->soca->Rsys / 1000; + + info->soca->target_vsys = temp1 + ( info->soca->target_ibat * info->soca->Rsys ) / 1000; + + + } else { + info->soca->target_ibat = info->fg_target_ibat; + /* calc min vsys value */ + temp1 = (battery_init_para[info->num][0]<<8) | (battery_init_para[info->num][1]); + temp = temp1 + ( info->soca->target_ibat * info->soca->Rsys ) / 1000; + if( temp < info->fg_target_vsys) { + info->soca->target_vsys = info->fg_target_vsys; + } else { + info->soca->target_vsys = temp; + printk("PMU : %s : setting value of target vsys(%d) is out of range(%d)\n",__func__, info->fg_target_vsys, temp); + } + } + + //for debug + printk("PMU : %s : target_vsys is %d target_ibat is %d\n",__func__,info->soca->target_vsys,info->soca->target_ibat); + + if ((info->soca->target_ibat == 0) || (info->soca->target_vsys == 0)) { /* normal version */ + } else { /*Slice cutoff voltage version. */ + + Ibat_min = -1 * info->soca->target_ibat; + info->soca->cutoff_ocv = info->soca->target_vsys - Ibat_min * info->soca->Rsys / 1000; + + full_ocv = (battery_init_para[info->num][20]<<8) | (battery_init_para[info->num][21]); + full_ocv = full_ocv * 5000 / 4095; + + ricoh61x_scaling_OCV_table(info, info->soca->cutoff_ocv, full_ocv, &start_per, &end_per); + + /* calc available capacity */ + /* get avilable capacity */ + /* battery_init_para23-24 is designe capacity */ + available_cap = (battery_init_para[info->num][22]<<8) + | (battery_init_para[info->num][23]); + + available_cap = available_cap + * ((10000 - start_per) / 100) / 100 ; + + + battery_init_para[info->num][23] = available_cap; + battery_init_para[info->num][22] = available_cap >> 8; + + } + ret = ricoh61x_clr_bits(info->dev->parent, FG_CTRL_REG, 0x01); + if (ret < 0) { + dev_err(info->dev, "error in FG_En off\n"); + goto err; + } + ///////////////////////////////// + ret = ricoh61x_read_bank1(info->dev->parent, 0xDC, &val); + if (ret < 0) { + dev_err(info->dev, "batterry initialize error\n"); + goto err; + } + + val_temp = val; + val &= 0x0F; //clear bit 4-7 + val |= 0x10; //set bit 4 + + ret = ricoh61x_write_bank1(info->dev->parent, 0xDC, val); + if (ret < 0) { + dev_err(info->dev, "batterry initialize error\n"); + goto err; + } + + ret = ricoh61x_read_bank1(info->dev->parent, 0xDC, &val2); + if (ret < 0) { + dev_err(info->dev, "batterry initialize error\n"); + goto err; + } + + ret = ricoh61x_write_bank1(info->dev->parent, 0xDC, val_temp); + if (ret < 0) { + dev_err(info->dev, "batterry initialize error\n"); + goto err; + } + + //printk("PMU : %s : original 0x%x, before 0x%x, after 0x%x\n",__func__, val_temp, val, val2); + + if (val != val2) { + ret = ricoh61x_bulk_writes_bank1(info->dev->parent, + BAT_INIT_TOP_REG, 30, battery_init_para[info->num]); + if (ret < 0) { + dev_err(info->dev, "batterry initialize error\n"); + goto err; + } + } else { + ret = ricoh61x_read_bank1(info->dev->parent, 0xD2, &val); + if (ret < 0) { + dev_err(info->dev, "batterry initialize error\n"); + goto err; + } + + ret = ricoh61x_read_bank1(info->dev->parent, 0xD3, &val2); + if (ret < 0) { + dev_err(info->dev, "batterry initialize error\n"); + goto err; + } + + available_cap_ori = val2 + (val << 8); + available_cap = battery_init_para[info->num][23] + + (battery_init_para[info->num][22] << 8); + + if (available_cap_ori == available_cap) { + ret = ricoh61x_bulk_writes_bank1(info->dev->parent, + BAT_INIT_TOP_REG, 22, battery_init_para[info->num]); + if (ret < 0) { + dev_err(info->dev, "batterry initialize error\n"); + return ret; + } + + for (i = 0; i < 6; i++) { + ret = ricoh61x_write_bank1(info->dev->parent, 0xD4+i, battery_init_para[info->num][24+i]); + if (ret < 0) { + dev_err(info->dev, "batterry initialize error\n"); + return ret; + } + } + } else { + ret = ricoh61x_bulk_writes_bank1(info->dev->parent, + BAT_INIT_TOP_REG, 30, battery_init_para[info->num]); + if (ret < 0) { + dev_err(info->dev, "batterry initialize error\n"); + goto err; + } + } + } + + //////////////////////////////// + + return 0; +err: + return ret; +} + +/* Initial setting of battery */ +static int ricoh61x_init_battery(struct ricoh61x_battery_info *info) +{ + int ret = 0; + uint8_t val; + uint8_t val2; + unsigned long hour_power_off; + unsigned long hour_power_on; + long power_off_period; + unsigned long seconds; + int cc_cap = 0; + long cc_cap_mas = 0; + bool is_charging = true; + + /* Need to implement initial setting of batery and error */ + /* -------------------------- */ +#ifdef ENABLE_FUEL_GAUGE_FUNCTION + + /* set relaxation state */ + if (RICOH61x_REL1_SEL_VALUE > 240) + val = 0x0F; + else + val = RICOH61x_REL1_SEL_VALUE / 16 ; + + /* set relaxation state */ + if (RICOH61x_REL2_SEL_VALUE > 120) + val2 = 0x0F; + else + val2 = RICOH61x_REL2_SEL_VALUE / 8 ; + + val = val + (val2 << 4); + + //ret = ricoh61x_write_bank1(info->dev->parent, BAT_REL_SEL_REG, val); + ret = ricoh61x_write_bank1(info->dev->parent, BAT_REL_SEL_REG, 0); + if (ret < 0) { + dev_err(info->dev, "Error in writing BAT_REL_SEL_REG\n"); + return ret; + } + + ret = ricoh61x_read_bank1(info->dev->parent, BAT_REL_SEL_REG, &val); + //printk("PMU: ------- BAT_REL_SEL= %xh: =======\n", + // val); + + ret = ricoh61x_write_bank1(info->dev->parent, BAT_TA_SEL_REG, 0x00); + if (ret < 0) { + dev_err(info->dev, "Error in writing BAT_TA_SEL_REG\n"); + return ret; + } + + //check first power on condition + //initial value + info->first_pwon = 0; + ret = ricoh61x_read(info->dev->parent, PSWR_REG, &val); + if (ret < 0) { + dev_err(info->dev,"Error in reading PSWR_REG %d\n", ret); + return ret; + } + + g_soc = val & 0x7f; + info->soca->init_pswr = val & 0x7f; + printk("PMU FG_RESET : %s : initial pswr = %d\n",__func__,info->soca->init_pswr); + + if(val == 0){ + printk("PMU : %s : first attached battery\n", __func__); + ret = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas, &is_charging, 1); + info->first_pwon = 1; + } + + //info->first_pwon = (val == 0) ? 1 : 0; + + ret = ricoh61x_read(info->dev->parent, RICOH61x_PWR_OFF_HIS, &val); + if (ret < 0) { + dev_err(info->dev,"Error in reading PWER OFF HIS %d\n", ret); + return ret; + } + printk("PMU : %s : POWER off history 0x%02x is 0x%02x \n", __func__,RICOH61x_PWR_OFF_HIS ,val); + //check bit 0 status + if(val & 0x01){ + printk("PMU : %s : Long power on key press\n", __func__); + info->first_pwon = 1; + } + + ret = ricoh61x_read(info->dev->parent, RICOH61x_PWR_FUNC, &val); + if (ret < 0) { + dev_err(info->dev,"Error in reading PWER FUNC %d\n", ret); + return ret; + } + printk("PMU : %s : POWER control function 0x%02x is 0x%02x \n", __func__,RICOH61x_PWR_FUNC ,val); +#if 0 + //check all bit is clear or not + if((val & 0xFF) == 0){ + printk("PMU : %s : cold boot\n", __func__); + info->first_pwon = 1; + } +#endif + //end first power on condition + + if(info->first_pwon == 0){ + //check Power off period + //if upper 1day, this power on sequence become first power on + hour_power_off = get_storedTime_from_register(info); + get_current_time(info, &seconds); + hour_power_on = seconds / 3600; + + hour_power_on &= 0xFFFFFF; + + power_off_period = hour_power_on - hour_power_off; + if(power_off_period >= 24) { + bat_alert_req_flg = 1; + } else if(power_off_period < 0){ + //error case + bat_alert_req_flg = 1; + } else { + bat_alert_req_flg = 0; + } + printk("PMU : %s : off is %lu, on is %lu, period is %lu, fpon_flag is %d\n", __func__, hour_power_off, hour_power_on, power_off_period, info->first_pwon); + } + + if(info->first_pwon) { + info->soca->rsoc_ready_flag = 1; + }else { + info->soca->rsoc_ready_flag = 0; + } + + ret = ricoh61x_set_OCV_table(info); + if (ret < 0) { + dev_err(info->dev, "Error in writing the OCV Tabler\n"); + return ret; + } + + ret = ricoh61x_write(info->dev->parent, FG_CTRL_REG, 0x11); + if (ret < 0) { + dev_err(info->dev, "Error in writing the control register\n"); + return ret; + } + + + Enable_Test_Register(info); + +#endif + +#if 0 + ret = ricoh61x_write(info->dev->parent, VINDAC_REG, 0x01); + if (ret < 0) { + dev_err(info->dev, "Error in writing the control register\n"); + return ret; + } +#endif + +#if 0 + if (info->alarm_vol_mv < 2700 || info->alarm_vol_mv > 3400) { + dev_err(info->dev, "alarm_vol_mv is out of range!\n"); + return -1; + } +#endif + + return ret; +} + +/* Initial setting of charger */ +static int ricoh61x_init_charger(struct ricoh61x_battery_info *info) +{ + int err; + uint8_t val; + uint8_t val2; + uint8_t val3; + int charge_status; + int vfchg_val; + int icchg_val; + int rbat; + int temp; + + info->chg_ctr = 0; + info->chg_stat1 = 0; + + err = ricoh61x_set_bits(info->dev->parent, RICOH61x_PWR_FUNC, 0x20); + if (err < 0) { + dev_err(info->dev, "Error in writing the PWR FUNC register\n"); + goto free_device; + } + + charge_status = get_power_supply_status(info); + + if (charge_status != POWER_SUPPLY_STATUS_FULL) + { + /* Disable charging */ + err = ricoh61x_clr_bits(info->dev->parent,CHGCTL1_REG, 0x03); + if (err < 0) { + dev_err(info->dev, "Error in writing the control register\n"); + goto free_device; + } + } + + + err = ricoh61x_read(info->dev->parent, 0xDA, &val); + printk("PMU : %s : GCHGDET (0xDA) is 0x%x\n",__func__,val); + if (val & 0x30) { + /* REGISET1:(0xB6) setting */ + if ((info->ch_ilim_adp != 0xFF) || (info->ch_ilim_adp <= 0x1D)) { + val = info->ch_ilim_adp; + + } + else + val = 0x0D; + err = ricoh61x_write(info->dev->parent, REGISET1_REG,val); + if (err < 0) { + dev_err(info->dev, "Error in writing REGISET1_REG %d\n", + err); + goto free_device; + } + } + + /* REGISET2:(0xB7) setting */ + err = ricoh61x_read(info->dev->parent, REGISET2_REG, &val); + if (err < 0) { + dev_err(info->dev, + "Error in read REGISET2_REG %d\n", err); + goto free_device; + } + + if ((info->ch_ilim_usb != 0xFF) || (info->ch_ilim_usb <= 0x1D)) { + val2 = info->ch_ilim_usb; + } else {/* Keep OTP value */ + val2 = (val & 0x1F); + } + + /* keep bit 5-7 */ + val &= 0xE0; + + val = val + val2; + + val |= 0xA0; // Set SDPOVRLIM to allow charge limit 500mA + + err = ricoh61x_write(info->dev->parent, REGISET2_REG,val); + if (err < 0) { + dev_err(info->dev, "Error in writing REGISET2_REG %d\n", + err); + goto free_device; + } + + err = ricoh61x_read(info->dev->parent, CHGISET_REG, &val); + if (err < 0) { + dev_err(info->dev, + "Error in read CHGISET_REG %d\n", err); + goto free_device; + } + + /* Define Current settings value for charging (bit 4~0)*/ + if ((info->ch_ichg != 0xFF) || (info->ch_ichg <= 0x1D)) { + val2 = info->ch_ichg; + } else { /* Keep OTP value */ + val2 = (val & 0x1F); + } + + /* Define Current settings at the charge completion (bit 7~6)*/ + if ((info->ch_icchg != 0xFF) || (info->ch_icchg <= 0x03)) { + val3 = info->ch_icchg << 6; + } else { /* Keep OTP value */ + val3 = (val & 0xC0); + } + + val = val2 + val3; + + err = ricoh61x_write(info->dev->parent, CHGISET_REG, val); + if (err < 0) { + dev_err(info->dev, "Error in writing CHGISET_REG %d\n", + err); + goto free_device; + } + + //debug messeage + err = ricoh61x_read(info->dev->parent, CHGISET_REG,&val); + printk("PMU : %s : after CHGISET_REG (0x%x) is 0x%x info->ch_ichg is 0x%x info->ch_icchg is 0x%x\n",__func__,CHGISET_REG,val,info->ch_ichg,info->ch_icchg); + + //debug messeage + err = ricoh61x_read(info->dev->parent, BATSET1_REG,&val); + printk("PMU : %s : before BATSET1_REG (0x%x) is 0x%x info->ch_vbatovset is 0x%x\n",__func__,BATSET1_REG,val,info->ch_vbatovset); + + /* BATSET1_REG(0xBA) setting */ + err = ricoh61x_read(info->dev->parent, BATSET1_REG, &val); + if (err < 0) { + dev_err(info->dev, + "Error in read BATSET1 register %d\n", err); + goto free_device; + } + + /* Define Battery overvoltage (bit 4)*/ + if ((info->ch_vbatovset != 0xFF) || (info->ch_vbatovset <= 0x1)) { + val2 = info->ch_vbatovset; + val2 = val2 << 4; + } else { /* Keep OTP value */ + val2 = (val & 0x10); + } + + /* keep bit 0-3 and bit 5-7 */ + val = (val & 0xEF); + + val = val + val2; + + val |= 0x08; // set vweak to 3.3 + + err = ricoh61x_write(info->dev->parent, BATSET1_REG, val); + if (err < 0) { + dev_err(info->dev, "Error in writing BAT1_REG %d\n", + err); + goto free_device; + } + //debug messeage + err = ricoh61x_read(info->dev->parent, BATSET1_REG,&val); + printk("PMU : %s : after BATSET1_REG (0x%x) is 0x%x info->ch_vbatovset is 0x%x\n",__func__,BATSET1_REG,val,info->ch_vbatovset); + + //debug messeage + err = ricoh61x_read(info->dev->parent, BATSET2_REG,&val); + printk("PMU : %s : before BATSET2_REG (0x%x) is 0x%x info->ch_vrchg is 0x%x info->ch_vfchg is 0x%x \n",__func__,BATSET2_REG,val,info->ch_vrchg,info->ch_vfchg); + + + /* BATSET2_REG(0xBB) setting */ + err = ricoh61x_read(info->dev->parent, BATSET2_REG, &val); + if (err < 0) { + dev_err(info->dev, + "Error in read BATSET2 register %d\n", err); + goto free_device; + } + + /* Define Re-charging voltage (bit 2~0)*/ + if ((info->ch_vrchg != 0xFF) || (info->ch_vrchg <= 0x04)) { + val2 = info->ch_vrchg; + } else { /* Keep OTP value */ + val2 = (val & 0x07); + } + + /* Define FULL charging voltage (bit 6~4)*/ + if ((info->ch_vfchg != 0xFF) || (info->ch_vfchg <= 0x04)) { + val3 = info->ch_vfchg; + val3 = val3 << 4; + } else { /* Keep OTP value */ + val3 = (val & 0x70); + } + + /* keep bit 3 and bit 7 */ + val = (val & 0x88); + + val = val + val2 + val3; + + err = ricoh61x_write(info->dev->parent, BATSET2_REG, val); + if (err < 0) { + dev_err(info->dev, "Error in writing RICOH61x_RE_CHARGE_VOLTAGE %d\n", + err); + goto free_device; + } + + /* Set rising edge setting ([1:0]=01b)for INT in charging */ + /* and rising edge setting ([3:2]=01b)for charge completion */ + err = ricoh61x_read(info->dev->parent, RICOH61x_CHG_STAT_DETMOD1, &val); + if (err < 0) { + dev_err(info->dev, "Error in reading CHG_STAT_DETMOD1 %d\n", + err); + goto free_device; + } + val &= 0xf0; + val |= 0x05; + err = ricoh61x_write(info->dev->parent, RICOH61x_CHG_STAT_DETMOD1, val); + if (err < 0) { + dev_err(info->dev, "Error in writing CHG_STAT_DETMOD1 %d\n", + err); + goto free_device; + } + + /* Unmask In charging/charge completion */ + err = ricoh61x_write(info->dev->parent, RICOH61x_INT_MSK_CHGSTS1, 0xfc); + if (err < 0) { + dev_err(info->dev, "Error in writing INT_MSK_CHGSTS1 %d\n", + err); + goto free_device; + } + + /* Set both edge for VUSB([3:2]=11b)/VADP([1:0]=11b) detect */ + err = ricoh61x_read(info->dev->parent, RICOH61x_CHG_CTRL_DETMOD1, &val); + if (err < 0) { + dev_err(info->dev, "Error in reading CHG_CTRL_DETMOD1 %d\n", + err); + goto free_device; + } + val &= 0xf0; + val |= 0x0f; + err = ricoh61x_write(info->dev->parent, RICOH61x_CHG_CTRL_DETMOD1, val); + if (err < 0) { + dev_err(info->dev, "Error in writing CHG_CTRL_DETMOD1 %d\n", + err); + goto free_device; + } + + /* Unmask In VUSB/VADP completion */ + err = ricoh61x_write(info->dev->parent, RICOH61x_INT_MSK_CHGCTR, 0xfc); + if (err < 0) { + dev_err(info->dev, "Error in writing INT_MSK_CHGSTS1 %d\n", + err); + goto free_device; + } + + if (charge_status != POWER_SUPPLY_STATUS_FULL) + { + /* Enable charging */ + err = ricoh61x_set_bits(info->dev->parent,CHGCTL1_REG, 0x03); + if (err < 0) { + dev_err(info->dev, "Error in writing the control register\n"); + goto free_device; + } + } + /* get OCV100_min, OCV100_min*/ + temp = (battery_init_para[info->num][24]<<8) | (battery_init_para[info->num][25]); + rbat = temp * 1000 / 512 * 5000 / 4095; + + /* get vfchg value */ + err = ricoh61x_read(info->dev->parent, BATSET2_REG, &val); + if (err < 0) { + dev_err(info->dev, "Error in reading the batset2reg\n"); + goto free_device; + } + val &= 0x70; + val2 = val >> 4; + if (val2 <= 3) { + vfchg_val = 4050 + val2 * 50; + } else { + vfchg_val = 4350; + } + printk("PMU : %s : test test val %d, val2 %d vfchg %d\n", __func__, val, val2, vfchg_val); + + /* get value */ + err = ricoh61x_read(info->dev->parent, CHGISET_REG, &val); + if (err < 0) { + dev_err(info->dev, "Error in reading the chgisetreg\n"); + goto free_device; + } + val &= 0xC0; + val2 = val >> 6; + icchg_val = 50 + val2 * 50; + printk("PMU : %s : test test val %d, val2 %d icchg %d\n", __func__, val, val2, icchg_val); + + info->soca->OCV100_min = ( vfchg_val * 99 / 100 - (icchg_val * (rbat +20))/1000 - 20 ) * 1000; + info->soca->OCV100_max = ( vfchg_val * 101 / 100 - (icchg_val * (rbat +20))/1000 + 20 ) * 1000; + + printk("PMU : %s : 100 min %d, 100 max %d vfchg %d icchg %d rbat %d\n",__func__, + info->soca->OCV100_min,info->soca->OCV100_max,vfchg_val,icchg_val,rbat); + +#ifdef ENABLE_LOW_BATTERY_DETECTION + /* Set ADRQ=00 to stop ADC */ + ricoh61x_write(info->dev->parent, RICOH61x_ADC_CNT3, 0x0); +#if 0 + /* Enable VSYS threshold Low interrupt */ + ricoh61x_write(info->dev->parent, RICOH61x_INT_EN_ADC1, 0x10); + /* Set ADC auto conversion interval 250ms */ + ricoh61x_write(info->dev->parent, RICOH61x_ADC_CNT2, 0x0); + /* Enable VSYS pin conversion in auto-ADC */ + ricoh61x_write(info->dev->parent, RICOH61x_ADC_CNT1, 0x10); + /* Set VSYS threshold low voltage value = (voltage(V)*255)/(3*2.5) */ + val = info->alarm_vol_mv * 255 / 7500; + ricoh61x_write(info->dev->parent, RICOH61x_ADC_VSYS_THL, val); +#else + /* Enable VBAT threshold Low interrupt */ + ricoh61x_write(info->dev->parent, RICOH61x_INT_EN_ADC1, 0x02); + /* Set ADC auto conversion interval 250ms */ + ricoh61x_write(info->dev->parent, RICOH61x_ADC_CNT2, 0x0); + /* Enable VBAT pin conversion in auto-ADC */ + ricoh61x_write(info->dev->parent, RICOH61x_ADC_CNT1, 0x12); + /* Set VBAT threshold low voltage value = (voltage(V)*255)/(2*2.5) */ + val = (info->alarm_vol_mv - 20) * 255 / 5000; + ricoh61x_write(info->dev->parent, RICOH61x_ADC_VBAT_THL, val); +#endif +#ifdef ENABLE_BATTERY_TEMP_DETECTION + /* Enable VTHM threshold Low interrupt */ + ricoh61x_set_bits(info->dev->parent, RICOH61x_ADC_CNT1, 0x20); + /* Enable VBAT threshold Low interrupt */ + ricoh61x_set_bits(info->dev->parent, RICOH61x_INT_EN_ADC1, 0x20); + /* Set VTHM threshold low voltage value = (voltage(V)*255)/(2.5) */ + val = HIGH_BATTERY_TEMP_VOL * 255 / 2500; + ricoh61x_write(info->dev->parent, RICOH61x_ADC_VTHM_THL, val); + /* Enable VTHM threshold high interrupt */ + ricoh61x_set_bits(info->dev->parent, RICOH61x_INT_EN_ADC2, 0x20); + /* Set VTHM threshold high voltage value = (voltage(V)*255)/(2.5) */ + val = LOW_BATTERY_TEMP_VOL * 255 / 2500; + ricoh61x_write(info->dev->parent, RICOH61x_ADC_VTHM_THH, val); +#endif + + /* Start auto-mode & average 4-time conversion mode for ADC */ + ricoh61x_write(info->dev->parent, RICOH61x_ADC_CNT3, 0x28); + +#endif + +free_device: + return err; +} + + +static int get_power_supply_status(struct ricoh61x_battery_info *info) +{ + uint8_t status; + uint8_t supply_state; + uint8_t charge_state; + int ret = 0; + + /* get power supply status */ + ret = ricoh61x_read(info->dev->parent, CHGSTATE_REG, &status); + if (ret < 0) { + dev_err(info->dev, "Error in reading the control register\n"); + return ret; + } + + charge_state = (status & 0x1F); + supply_state = ((status & 0xC0) >> 6); + + if (info->entry_factory_mode) + return POWER_SUPPLY_STATUS_NOT_CHARGING; + + if (supply_state == SUPPLY_STATE_BAT) { + info->soca->chg_status = POWER_SUPPLY_STATUS_DISCHARGING; + } else { + switch (charge_state) { + case CHG_STATE_CHG_OFF: + info->soca->chg_status + = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case CHG_STATE_CHG_READY_VADP: + info->soca->chg_status + = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case CHG_STATE_CHG_TRICKLE: + info->soca->chg_status + = POWER_SUPPLY_STATUS_CHARGING; + break; + case CHG_STATE_CHG_RAPID: + info->soca->chg_status + = POWER_SUPPLY_STATUS_CHARGING; + break; + case CHG_STATE_CHG_COMPLETE: + info->soca->chg_status + = POWER_SUPPLY_STATUS_FULL; + break; + case CHG_STATE_SUSPEND: + info->soca->chg_status + = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case CHG_STATE_VCHG_OVER_VOL: + info->soca->chg_status + = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case CHG_STATE_BAT_ERROR: + info->soca->chg_status + = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case CHG_STATE_NO_BAT: + info->soca->chg_status + = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case CHG_STATE_BAT_OVER_VOL: + info->soca->chg_status + = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case CHG_STATE_BAT_TEMP_ERR: + info->soca->chg_status + = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case CHG_STATE_DIE_ERR: + info->soca->chg_status + = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case CHG_STATE_DIE_SHUTDOWN: + info->soca->chg_status + = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case CHG_STATE_NO_BAT2: + info->soca->chg_status + = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case CHG_STATE_CHG_READY_VUSB: + info->soca->chg_status + = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + default: + info->soca->chg_status + = POWER_SUPPLY_STATUS_UNKNOWN; + break; + } + } + + return info->soca->chg_status; +} + +static int get_power_supply_Android_status(struct ricoh61x_battery_info *info) +{ + + get_power_supply_status(info); + + /* get power supply status */ + if (info->entry_factory_mode) + return POWER_SUPPLY_STATUS_NOT_CHARGING; + + switch (info->soca->chg_status) { + case POWER_SUPPLY_STATUS_UNKNOWN: + return POWER_SUPPLY_STATUS_UNKNOWN; + break; + + case POWER_SUPPLY_STATUS_NOT_CHARGING: + return POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + + case POWER_SUPPLY_STATUS_DISCHARGING: + return POWER_SUPPLY_STATUS_DISCHARGING; + break; + + case POWER_SUPPLY_STATUS_CHARGING: + return POWER_SUPPLY_STATUS_CHARGING; + break; + + case POWER_SUPPLY_STATUS_FULL: + if(info->soca->displayed_soc == 100 * 100) { + return POWER_SUPPLY_STATUS_FULL; + } else { + return POWER_SUPPLY_STATUS_CHARGING; + } + break; + default: + return POWER_SUPPLY_STATUS_UNKNOWN; + break; + } + + return POWER_SUPPLY_STATUS_UNKNOWN; +} + +extern int ricoh619_charger_detect(void); +typedef void (*usb_insert_handler) (char inserted); +extern usb_insert_handler mxc_misc_report_usb; + +static int giRICOH619_DCIN; +int ricoh619_dcin_status(void) +{ + return giRICOH619_DCIN; +} + +static void charger_irq_work(struct work_struct *work) +{ + struct ricoh61x_battery_info *info + = container_of(work, struct ricoh61x_battery_info, irq_work); + //uint8_t status; + int ret = 0; + uint8_t val = 0, ilim_adp = 0, ichg = 0; + //uint8_t adp_current_val = 0x0E; + //uint8_t usb_current_val = 0x04; + extern void led_red(int isOn); + + printk(KERN_INFO "PMU:%s In\n", __func__); + + power_supply_changed(&info->battery); + +#if defined (STANDBY_MODE_DEBUG) + ret = ricoh61x_read(info->dev->parent, CHGSTATE_REG, &val); + if (val & 0xc0) { + if(multiple_sleep_mode == 0) { + multiple_sleep_mode = 1; + printk("PMU: %s sleep time ratio = x60 *****************\n", __func__); + } else if(multiple_sleep_mode == 1) { + multiple_sleep_mode = 2; + printk("PMU: %s sleep time ratio = x3600 *****************\n", __func__); + } else if(multiple_sleep_mode == 2) { + multiple_sleep_mode = 0; + printk("PMU: %s sleep time ratio = x1 *****************\n", __func__); + } + } +#elif defined(CHANGE_FL_MODE_DEBUG) + ret = ricoh61x_read(info->dev->parent, CHGSTATE_REG, &val); + if (val & 0xc0) { + if (fl_current < 10000) { + fl_current = 10000; // If FL<10mA, Set FL=10mA + } else if (fl_current < 20000) { + fl_current = 20000; // If FL<20mA, Set FL=20mA + } else if (fl_current < 30000) { + fl_current = 30000; // If FL<30mA, Set FL=30mA + } else if (fl_current < 40000) { + fl_current = 40000; // If FL<40mA, Set FL=40mA + } else { + fl_current = 5000; // If FL>40mA, Set FL=5mA + } + printk("PMU: %s FL(%d) mA *****************\n", __func__, fl_current/1000); + } +#endif +// mutex_lock(&info->lock); + +#if 0 + if(info->chg_ctr & 0x02) { + uint8_t sts; + ret = ricoh61x_read(info->dev->parent, RICOH61x_INT_MON_CHGCTR, &sts); + if (ret < 0) + dev_err(info->dev, "Error in reading the control register\n"); + + sts &= 0x02; + + /* If "sts" is true, USB is plugged. If not, unplugged. */ + } +#endif + info->chg_ctr = 0; + info->chg_stat1 = 0; + + /* Enable Interrupt for VADP/VUSB */ + ret = ricoh61x_write(info->dev->parent, RICOH61x_INT_MSK_CHGCTR, 0xfc); + if (ret < 0) + dev_err(info->dev, + "%s(): Error in enable charger mask INT %d\n", + __func__, ret); + + /* Enable Interrupt for Charging & complete */ + ret = ricoh61x_write(info->dev->parent, RICOH61x_INT_MSK_CHGSTS1, 0xfc); + if (ret < 0) + dev_err(info->dev, + "%s(): Error in enable charger mask INT %d\n", + __func__, ret); + + /* set USB/ADP ILIM */ + ret = ricoh61x_read(info->dev->parent, CHGSTATE_REG, &val); + if (ret < 0) { + dev_err(info->dev, "Error in reading the control register\n"); + return; + } + + val = (val & 0xC0) >> 6; + switch (val) { + case 0: // plug out USB/ADP + printk("%s : val = %d plug out\n",__func__, val); + break; + case 1: // plug in ADP + printk("%s : val = %d plug in ADPt\n",__func__, val); + //Add the code of AC adapter Charge and Limit current settings + //ret = ricoh61x_write(info->dev->parent, REGISET1_REG, adp_current_val); + break; + case 2:// plug in USB + printk("%s : val = %d plug in USB\n",__func__, val); + //Add the code of USB Charge and Limit current settings + //ret = ricoh61x_write(info->dev->parent, REGISET2_REG, usb_current_val) + break; + case 3:// plug in USB/ADP + printk("%s : val = %d plug in ADP USB\n",__func__, val); + break; + default: + printk("%s : val = %d unknown\n",__func__, val); + break; + } + + + giRICOH619_DCIN = ricoh619_charger_detect(); + if(giRICOH619_DCIN) { + led_red(1); + } + else { + led_red(0); + } + + //ricoh61x_read(info->dev->parent, 0xDA, &status); + ricoh61x_read(info->dev->parent, CHGISET_REG, &val); + val &= 0xe0; + //if (status&0x30) + if(giRICOH619_DCIN==CDP_CHARGER||giRICOH619_DCIN==DCP_CHARGER) + { // set 1000mA if DCP(10) or CDP(01) . + switch (gptHWCFG->m_val.bPCB) { + case 49: //E60QDX + ilim_adp = 0x09; //1000mA + ichg = 0x07; //800mA + break; + default: + ilim_adp = 0x0D; //1400mA + ichg = 0x09; //1000mA + break; + } + ricoh61x_write(info->dev->parent, REGISET1_REG, ilim_adp); + ricoh61x_write(info->dev->parent, CHGISET_REG, val|ichg); + } + else + { + ricoh61x_write(info->dev->parent, REGISET1_REG, 0x04); + ricoh61x_write(info->dev->parent, CHGISET_REG, val|0x04); + } + if(mxc_misc_report_usb) { + mxc_misc_report_usb(giRICOH619_DCIN?1:0); + } + +// mutex_unlock(&info->lock); + printk(KERN_INFO "PMU:%s Out\n", __func__); +} + +#ifdef ENABLE_LOW_BATTERY_DETECTION +static void low_battery_irq_work(struct work_struct *work) +{ + struct ricoh61x_battery_info *info = container_of(work, + struct ricoh61x_battery_info, low_battery_work.work); + + int ret = 0; + + printk(KERN_INFO "PMU:%s In\n", __func__); + + critical_low_flag = 1; + power_supply_changed(&info->battery); + info->suspend_state = false; + printk(KERN_INFO "PMU:%s Set ciritical_low_flag = 1 **********\n", __func__); + +#if 0 + /* Enable VSYS threshold Low interrupt */ + ricoh61x_write(info->dev->parent, RICOH61x_INT_EN_ADC1, 0x10); + if (ret < 0) + dev_err(info->dev, + "%s(): Error in enable adc mask INT %d\n", + __func__, ret); +#endif +} +#endif + +#ifdef ENABLE_BATTERY_TEMP_DETECTION +static void battery_temp_irq_work(struct work_struct *work) +{ + struct ricoh61x_battery_info *info = container_of(work, + struct ricoh61x_battery_info, battery_temp_work.work); + + int ret = 0; + uint8_t val; + uint8_t high_temp_vol = HIGH_BATTERY_TEMP_VOL*255/2500; + uint8_t low_temp_vol = LOW_BATTERY_TEMP_VOL*255/2500; + + printk(KERN_INFO "PMU:%s In\n", __func__); + + power_supply_changed(&info->battery); + + ricoh61x_read(info->dev->parent, RICOH61x_ADC_VTHMDATAH, &val); + printk(KERN_INFO "PMU:%s Battery temperature triggered (VTHMDATA 0x%02X)**********\n", __func__, val); + + if (val < high_temp_vol) { + /* Set VTHM threshold high voltage value = (voltage(V)*255)/(2.5) */ + ricoh61x_write(info->dev->parent, RICOH61x_ADC_VTHM_THH, high_temp_vol); + printk(KERN_INFO "PMU:%s set VTHM_THH to %02X\n", __func__, high_temp_vol); + /* Enable VTHM threshold high interrupt */ + ricoh61x_set_bits(info->dev->parent, RICOH61x_INT_EN_ADC2, 0x20); + } + else if (val > low_temp_vol) { + /* Set VTHM threshold low voltage value = (voltage(V)*255)/(2.5) */ + printk(KERN_INFO "PMU:%s set VTHM_THL to %02X\n", __func__, low_temp_vol); + ricoh61x_write(info->dev->parent, RICOH61x_ADC_VTHM_THL, low_temp_vol); + /* Enable VBAT threshold Low interrupt */ + ricoh61x_set_bits(info->dev->parent, RICOH61x_INT_EN_ADC1, 0x20); + } + else { + /* Set VTHM threshold low voltage value = (voltage(V)*255)/(2.5) */ + val = HIGH_BATTERY_TEMP_VOL * 255 / 2500; + ricoh61x_write(info->dev->parent, RICOH61x_ADC_VTHM_THL, val); + printk(KERN_INFO "PMU:%s set VTHM_THL to %02X\n", __func__, val); + /* Enable VBAT threshold Low interrupt */ + ricoh61x_set_bits(info->dev->parent, RICOH61x_INT_EN_ADC1, 0x20); + /* Set VTHM threshold high voltage value = (voltage(V)*255)/(2.5) */ + val = LOW_BATTERY_TEMP_VOL * 255 / 2500; + ricoh61x_write(info->dev->parent, RICOH61x_ADC_VTHM_THH, val); + printk(KERN_INFO "PMU:%s set VTHM_THH to %02X\n", __func__, val); + /* Enable VTHM threshold high interrupt */ + ricoh61x_set_bits(info->dev->parent, RICOH61x_INT_EN_ADC2, 0x20); + } +} +#endif + +static irqreturn_t charger_in_isr(int irq, void *battery_info) +{ + struct ricoh61x_battery_info *info = battery_info; + printk(KERN_INFO "PMU:%s\n", __func__); + + info->chg_stat1 |= 0x01; + queue_work(info->workqueue, &info->irq_work); + return IRQ_HANDLED; +} + +static irqreturn_t charger_complete_isr(int irq, void *battery_info) +{ + struct ricoh61x_battery_info *info = battery_info; + printk(KERN_INFO "PMU:%s\n", __func__); + + info->chg_stat1 |= 0x02; + queue_work(info->workqueue, &info->irq_work); + + return IRQ_HANDLED; +} + +static irqreturn_t charger_usb_isr(int irq, void *battery_info) +{ + struct ricoh61x_battery_info *info = battery_info; + printk(KERN_INFO "PMU:%s\n", __func__); + + info->chg_ctr |= 0x02; + + queue_work(info->workqueue, &info->irq_work); + + info->soca->dischg_state = 0; + info->soca->chg_count = 0; + if (RICOH61x_SOCA_UNSTABLE == info->soca->status + || RICOH61x_SOCA_FG_RESET == info->soca->status) + info->soca->stable_count = 11; + + return IRQ_HANDLED; +} + +static irqreturn_t charger_adp_isr(int irq, void *battery_info) +{ + struct ricoh61x_battery_info *info = battery_info; + printk(KERN_INFO "PMU:%s\n", __func__); + + info->chg_ctr |= 0x01; + queue_work(info->workqueue, &info->irq_work); + + info->soca->dischg_state = 0; + info->soca->chg_count = 0; + if (RICOH61x_SOCA_UNSTABLE == info->soca->status + || RICOH61x_SOCA_FG_RESET == info->soca->status) + info->soca->stable_count = 11; + + return IRQ_HANDLED; +} + + +#ifdef ENABLE_LOW_BATTERY_DETECTION +/*************************************************************/ +/* for Detecting Low Battery */ +/*************************************************************/ + +static irqreturn_t adc_vsysl_isr(int irq, void *battery_info) +{ + + struct ricoh61x_battery_info *info = battery_info; + + printk(KERN_INFO "PMU:%s\n", __func__); + printk(KERN_INFO "PMU:%s Detect Low Battery Interrupt **********\n", __func__); + // critical_low_flag = 1; + queue_delayed_work(info->monitor_wqueue, &info->low_battery_work, + LOW_BATTERY_DETECTION_TIME*HZ); + + return IRQ_HANDLED; +} +#endif + +#ifdef ENABLE_BATTERY_TEMP_DETECTION +/*************************************************************/ +/* for Detecting Battery Temperature */ +/*************************************************************/ + +static irqreturn_t adc_vtherm_isr(int irq, void *battery_info) +{ + + struct ricoh61x_battery_info *info = battery_info; + + printk(KERN_INFO "PMU:%s\n", __func__); + printk(KERN_INFO "PMU:%s Detect Battery Temperature Interrupt **********\n", __func__); + queue_delayed_work(info->monitor_wqueue, &info->battery_temp_work, 0); + + return IRQ_HANDLED; +} +#endif + +/* + * Get Charger Priority + * - get higher-priority between VADP and VUSB + * @ data: higher-priority is stored + * true : VUSB + * false: VADP + */ +static int get_charge_priority(struct ricoh61x_battery_info *info, bool *data) +{ + int ret = 0; + uint8_t val = 0; + + ret = ricoh61x_read(info->dev->parent, CHGCTL1_REG, &val); + val = val >> 7; + *data = (bool)val; + + return ret; +} + +/* + * Set Charger Priority + * - set higher-priority between VADP and VUSB + * - data: higher-priority is stored + * true : VUSB + * false: VADP + */ +static int set_charge_priority(struct ricoh61x_battery_info *info, bool *data) +{ + int ret = 0; + uint8_t val = 0x80; + + if (*data == 1) + ret = ricoh61x_set_bits(info->dev->parent, CHGCTL1_REG, val); + else + ret = ricoh61x_clr_bits(info->dev->parent, CHGCTL1_REG, val); + + return ret; +} + +#ifdef ENABLE_FUEL_GAUGE_FUNCTION +static int get_check_fuel_gauge_reg(struct ricoh61x_battery_info *info, + int Reg_h, int Reg_l, int enable_bit) +{ + uint8_t get_data_h, get_data_l; + int old_data, current_data; + int i; + int ret = 0; + + old_data = 0; + + for (i = 0; i < 5 ; i++) { + ret = ricoh61x_read(info->dev->parent, Reg_h, &get_data_h); + if (ret < 0) { + dev_err(info->dev, "Error in reading the control register\n"); + return ret; + } + + ret = ricoh61x_read(info->dev->parent, Reg_l, &get_data_l); + if (ret < 0) { + dev_err(info->dev, "Error in reading the control register\n"); + return ret; + } + + current_data = ((get_data_h & 0xff) << 8) | (get_data_l & 0xff); + current_data = (current_data & enable_bit); + + if (current_data == old_data) + return current_data; + else + old_data = current_data; + } + + return current_data; +} + +static int calc_capacity(struct ricoh61x_battery_info *info) +{ + uint8_t capacity; + long capacity_l; + int temp; + int ret = 0; + int nt; + int temperature; + int cc_cap = 0; + long cc_cap_mas =0; + int cc_delta; + bool is_charging = true; + + if (info->soca->rsoc_ready_flag != 0) { + /* get remaining battery capacity from fuel gauge */ + ret = ricoh61x_read(info->dev->parent, SOC_REG, &capacity); + if (ret < 0) { + dev_err(info->dev, "Error in reading the control register\n"); + return ret; + } + capacity_l = (long)capacity; + } else { + ret = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas, &is_charging, 0); + cc_delta = (is_charging == true) ? cc_cap : -cc_cap; + capacity_l = (info->soca->init_pswr * 100 + cc_delta) / 100; +#ifdef _RICOH619_DEBUG_ + printk(KERN_DEBUG"PMU FG_RESET : %s : capacity %d init_pswr %d cc_delta %d\n",__func__, capacity_l, info->soca->init_pswr, cc_delta); +#endif + } + + temperature = get_battery_temp_2(info) / 10; /* unit 0.1 degree -> 1 degree */ + + if (temperature >= 25) { + nt = 0; + } else if (temperature >= 5) { + nt = (25 - temperature) * RICOH61x_TAH_SEL2 * 625 / 100; + } else { + nt = (625 + (5 - temperature) * RICOH61x_TAL_SEL2 * 625 / 100); + } + + temp = capacity_l * 100 * 100 / (10000 - nt); + + temp = min(100, temp); + temp = max(0, temp); + + return temp; /* Unit is 1% */ +} + +static int calc_capacity_2(struct ricoh61x_battery_info *info) +{ + uint8_t val; + long capacity; + int re_cap, fa_cap; + int temp; + int ret = 0; + int nt; + int temperature; + int cc_cap = 0; + long cc_cap_mas =0; + int cc_delta; + bool is_charging = true; + + + if (info->soca->rsoc_ready_flag != 0) { + re_cap = get_check_fuel_gauge_reg(info, RE_CAP_H_REG, RE_CAP_L_REG, + 0x7fff); + fa_cap = get_check_fuel_gauge_reg(info, FA_CAP_H_REG, FA_CAP_L_REG, + 0x7fff); + + if (fa_cap != 0) { + capacity = ((long)re_cap * 100 * 100 / fa_cap); + capacity = (long)(min(10000, (int)capacity)); + capacity = (long)(max(0, (int)capacity)); + } else { + ret = ricoh61x_read(info->dev->parent, SOC_REG, &val); + if (ret < 0) { + dev_err(info->dev, "Error in reading the control register\n"); + return ret; + } + capacity = (long)val * 100; + } + } else { + ret = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas, &is_charging, 0); + cc_delta = (is_charging == true) ? cc_cap : -cc_cap; + capacity = info->soca->init_pswr * 100 + cc_delta; +#ifdef _RICOH619_DEBUG_ + printk(KERN_DEBUG"PMU FG_RESET : %s : capacity %d init_pswr %d cc_delta %d\n",__func__, (int)capacity, info->soca->init_pswr, cc_delta); +#endif + } + + temperature = get_battery_temp_2(info) / 10; /* unit 0.1 degree -> 1 degree */ + + if (temperature >= 25) { + nt = 0; + } else if (temperature >= 5) { + nt = (25 - temperature) * RICOH61x_TAH_SEL2 * 625 / 100; + } else { + nt = (625 + (5 - temperature) * RICOH61x_TAL_SEL2 * 625 / 100); + } + + temp = (int)(capacity * 100 * 100 / (10000 - nt)); + + temp = min(10000, temp); + temp = max(0, temp); + + return temp; /* Unit is 0.01% */ +} + +static int get_battery_temp(struct ricoh61x_battery_info *info) +{ + int ret = 0; + int sign_bit; + + ret = get_check_fuel_gauge_reg(info, TEMP_1_REG, TEMP_2_REG, 0x0fff); + if (ret < 0) { + dev_err(info->dev, "Error in reading the fuel gauge control register\n"); + return ret; + } + + /* bit3 of 0xED(TEMP_1) is sign_bit */ + sign_bit = ((ret & 0x0800) >> 11); + + ret = (ret & 0x07ff); + + if (sign_bit == 0) /* positive value part */ + /* conversion unit */ + /* 1 unit is 0.0625 degree and retun unit + * should be 0.1 degree, + */ + ret = ret * 625 / 1000; + else { /*negative value part */ + ret = (~ret + 1) & 0x7ff; + ret = -1 * ret * 625 / 1000; + } + + return ret; +} + +static int get_battery_temp_2(struct ricoh61x_battery_info *info) +{ + uint8_t reg_buff[2]; + long temp, temp_off, temp_gain; + bool temp_sign, temp_off_sign, temp_gain_sign; + int Vsns = 0; + int Iout = 0; + int Vthm, Rthm; + int reg_val = 0; + int new_temp; + long R_ln1, R_ln2; + int ret = 0; + + /* Calculate TEMP */ + ret = get_check_fuel_gauge_reg(info, TEMP_1_REG, TEMP_2_REG, 0x0fff); + if (ret < 0) { + dev_err(info->dev, "Error in reading the fuel gauge register\n"); + goto out; + } + + reg_val = ret; + temp_sign = (reg_val & 0x0800) >> 11; + reg_val = (reg_val & 0x07ff); + + if (temp_sign == 0) /* positive value part */ + /* the unit is 0.0001 degree */ + temp = (long)reg_val * 625; + else { /*negative value part */ + reg_val = (~reg_val + 1) & 0x7ff; + temp = -1 * (long)reg_val * 625; + } + + /* Calculate TEMP_OFF */ + ret = ricoh61x_bulk_reads_bank1(info->dev->parent, + TEMP_OFF_H_REG, 2, reg_buff); + if (ret < 0) { + dev_err(info->dev, "Error in reading the fuel gauge register\n"); + goto out; + } + + reg_val = reg_buff[0] << 8 | reg_buff[1]; + temp_off_sign = (reg_val & 0x0800) >> 11; + reg_val = (reg_val & 0x07ff); + + if (temp_off_sign == 0) /* positive value part */ + /* the unit is 0.0001 degree */ + temp_off = (long)reg_val * 625; + else { /*negative value part */ + reg_val = (~reg_val + 1) & 0x7ff; + temp_off = -1 * (long)reg_val * 625; + } + + /* Calculate TEMP_GAIN */ + ret = ricoh61x_bulk_reads_bank1(info->dev->parent, + TEMP_GAIN_H_REG, 2, reg_buff); + if (ret < 0) { + dev_err(info->dev, "Error in reading the fuel gauge register\n"); + goto out; + } + + reg_val = reg_buff[0] << 8 | reg_buff[1]; + temp_gain_sign = (reg_val & 0x0800) >> 11; + reg_val = (reg_val & 0x07ff); + + if (temp_gain_sign == 0) /* positive value part */ + /* 1 unit is 0.000488281. the result is 0.01 */ + temp_gain = (long)reg_val * 488281 / 100000; + else { /*negative value part */ + reg_val = (~reg_val + 1) & 0x7ff; + temp_gain = -1 * (long)reg_val * 488281 / 100000; + } + + /* Calculate VTHM */ + if (0 != temp_gain) + Vthm = (int)((temp - temp_off) / 4095 * 2500 / temp_gain); + else { +#ifdef _RICOH619_DEBUG_ + printk(KERN_DEBUG"PMU %s Skip to compensate temperature\n", __func__); +#endif + goto out; + } + + ret = measure_Ibatt_FG(info, &Iout); + Vsns = Iout * 2 / 100; + + if (temp < -120000) { + /* Low Temperature */ + if (0 != (2500 - Vthm)) { + Rthm = 10 * 10 * (Vthm - Vsns) / (2500 - Vthm); + } else { +#ifdef _RICOH619_DEBUG_ + printk(KERN_DEBUG"PMU %s Skip to compensate temperature\n", __func__); +#endif + goto out; + } + + R_ln1 = Rthm / 10; + R_ln2 = (R_ln1 * R_ln1 * R_ln1 * R_ln1 * R_ln1 / 100000 + - R_ln1 * R_ln1 * R_ln1 * R_ln1 * 2 / 100 + + R_ln1 * R_ln1 * R_ln1 * 11 + - R_ln1 * R_ln1 * 2980 + + R_ln1 * 449800 + - 784000) / 10000; + + /* the unit of new_temp is 0.1 degree */ + new_temp = (int)((100 * 1000 * B_VALUE / (R_ln2 + B_VALUE * 100 * 1000 / 29815) - 27315) / 10); +#ifdef _RICOH619_DEBUG_ + printk(KERN_DEBUG"PMU %s low temperature %d\n", __func__, new_temp/10); +#endif + } else if (temp > 520000) { + /* High Temperature */ + if (0 != (2500 - Vthm)) { + Rthm = 100 * 10 * (Vthm - Vsns) / (2500 - Vthm); + } else { +#ifdef _RICOH619_DEBUG_ + printk(KERN_DEBUG"PMU %s Skip to compensate temperature\n", __func__); +#endif + goto out; + } +#ifdef _RICOH619_DEBUG_ + printk(KERN_DEBUG"PMU %s [Rthm] Rthm %d[ohm]\n", __func__, Rthm); +#endif + + R_ln1 = Rthm / 10; + R_ln2 = (R_ln1 * R_ln1 * R_ln1 * R_ln1 * R_ln1 / 100000 * 15652 / 100 + - R_ln1 * R_ln1 * R_ln1 * R_ln1 / 1000 * 23103 / 100 + + R_ln1 * R_ln1 * R_ln1 * 1298 / 100 + - R_ln1 * R_ln1 * 35089 / 100 + + R_ln1 * 50334 / 10 + - 48569) / 100; + /* the unit of new_temp is 0.1 degree */ + new_temp = (int)((100 * 100 * B_VALUE / (R_ln2 + B_VALUE * 100 * 100 / 29815) - 27315) / 10); +#ifdef _RICOH619_DEBUG_ + printk(KERN_DEBUG"PMU %s high temperature %d\n", __func__, new_temp/10); +#endif + } else { + /* the unit of new_temp is 0.1 degree */ + new_temp = temp / 1000; + } + + return new_temp; + +out: + new_temp = get_battery_temp(info); + return new_temp; +} + +static int get_time_to_empty(struct ricoh61x_battery_info *info) +{ + int ret = 0; + + ret = get_check_fuel_gauge_reg(info, TT_EMPTY_H_REG, TT_EMPTY_L_REG, + 0xffff); + if (ret < 0) { + dev_err(info->dev, "Error in reading the fuel gauge control register\n"); + return ret; + } + + /* conversion unit */ + /* 1unit is 1miniute and return nnit should be 1 second */ + ret = ret * 60; + + return ret; +} + +static int get_time_to_full(struct ricoh61x_battery_info *info) +{ + int ret = 0; + + ret = get_check_fuel_gauge_reg(info, TT_FULL_H_REG, TT_FULL_L_REG, + 0xffff); + if (ret < 0) { + dev_err(info->dev, "Error in reading the fuel gauge control register\n"); + return ret; + } + + ret = ret * 60; + + return ret; +} + +/* battery voltage is get from Fuel gauge */ +static int measure_vbatt_FG(struct ricoh61x_battery_info *info, int *data) +{ + int ret = 0; + + if(info->soca->ready_fg == 1) { + ret = get_check_fuel_gauge_reg(info, VOLTAGE_1_REG, VOLTAGE_2_REG, + 0x0fff); + if (ret < 0) { + dev_err(info->dev, "Error in reading the fuel gauge control register\n"); + return ret; + } + + *data = ret; + /* conversion unit 1 Unit is 1.22mv (5000/4095 mv) */ + *data = *data * 50000 / 4095; + /* return unit should be 1uV */ + *data = *data * 100; + info->soca->Vbat_old = *data; + } else { + *data = info->soca->Vbat_old; + } + + return ret; +} + +static int measure_Ibatt_FG(struct ricoh61x_battery_info *info, int *data) +{ + int ret = 0; + + ret = get_check_fuel_gauge_reg(info, CC_AVERAGE1_REG, + CC_AVERAGE0_REG, 0x3fff); + if (ret < 0) { + dev_err(info->dev, "Error in reading the fuel gauge control register\n"); + return ret; + } + + *data = (ret > 0x1fff) ? (ret - 0x4000) : ret; + return ret; +} + +/** +* index : index No.(2 -> 20%) +* table_num : ocv table selection +* 0 : Original Table(defined by header file) +* 1 : Using Table +*/ +static int get_OCV_init_Data(struct ricoh61x_battery_info *info, int index, int table_num) +{ + int ret = 0; + if (table_num == USING) { + ret = (battery_init_para[info->num][index*2]<<8) | (battery_init_para[info->num][index*2+1]); + } else if (table_num == ORIGINAL) { + ret = (info->soca->battery_init_para_original[index*2]<<8) + | (info->soca->battery_init_para_original[index*2+1]); + } + return ret; +} + +/** +* index : index No.(2 -> 20%) +* table_num : ocv table selection +* 0 : Original Table +* 1 : Using Table +*/ +static int get_OCV_voltage(struct ricoh61x_battery_info *info, int index, int table_num) +{ + int ret = 0; + ret = get_OCV_init_Data(info, index, table_num); + /* conversion unit 1 Unit is 1.22mv (5000/4095 mv) */ + ret = ret * 50000 / 4095; + /* return unit should be 1uV */ + ret = ret * 100; + return ret; +} + +#else +/* battery voltage is get from ADC */ +static int measure_vbatt_ADC(struct ricoh61x_battery_info *info, int *data) +{ + int i; + uint8_t data_l = 0, data_h = 0; + int ret; + + /* ADC interrupt enable */ + ret = ricoh61x_set_bits(info->dev->parent, INTEN_REG, 0x08); + if (ret < 0) { + dev_err(info->dev, "Error in setting the control register bit\n"); + goto err; + } + + /* enable interrupt request of single mode */ + ret = ricoh61x_set_bits(info->dev->parent, EN_ADCIR3_REG, 0x01); + if (ret < 0) { + dev_err(info->dev, "Error in setting the control register bit\n"); + goto err; + } + + /* single request */ + ret = ricoh61x_write(info->dev->parent, ADCCNT3_REG, 0x10); + if (ret < 0) { + dev_err(info->dev, "Error in writing the control register\n"); + goto err; + } + + for (i = 0; i < 5; i++) { + usleep(1000); + dev_info(info->dev, "ADC conversion times: %d\n", i); + /* read completed flag of ADC */ + ret = ricoh61x_read(info->dev->parent, EN_ADCIR3_REG, &data_h); + if (ret < 0) { + dev_err(info->dev, "Error in reading the control register\n"); + goto err; + } + + if (data_h & 0x01) + goto done; + } + + dev_err(info->dev, "ADC conversion too long!\n"); + goto err; + +done: + ret = ricoh61x_read(info->dev->parent, VBATDATAH_REG, &data_h); + if (ret < 0) { + dev_err(info->dev, "Error in reading the control register\n"); + goto err; + } + + ret = ricoh61x_read(info->dev->parent, VBATDATAL_REG, &data_l); + if (ret < 0) { + dev_err(info->dev, "Error in reading the control register\n"); + goto err; + } + + *data = ((data_h & 0xff) << 4) | (data_l & 0x0f); + /* conversion unit 1 Unit is 1.22mv (5000/4095 mv) */ + *data = *data * 5000 / 4095; + /* return unit should be 1uV */ + *data = *data * 1000; + + return 0; + +err: + return -1; +} +#endif + +static int measure_vsys_ADC(struct ricoh61x_battery_info *info, int *data) +{ + uint8_t data_l = 0, data_h = 0; + int ret; + + ret = ricoh61x_read(info->dev->parent, VSYSDATAH_REG, &data_h); + if (ret < 0) { + dev_err(info->dev, "Error in reading the control register\n"); + } + + ret = ricoh61x_read(info->dev->parent, VSYSDATAL_REG, &data_l); + if (ret < 0) { + dev_err(info->dev, "Error in reading the control register\n"); + } + + *data = ((data_h & 0xff) << 4) | (data_l & 0x0f); + *data = *data * 1000 * 3 * 5 / 2 / 4095; + /* return unit should be 1uV */ + *data = *data * 1000; + + return 0; +} + +static void ricoh61x_external_power_changed(struct power_supply *psy) +{ + struct ricoh61x_battery_info *info; + + info = container_of(psy, struct ricoh61x_battery_info, battery); + queue_delayed_work(info->monitor_wqueue, + &info->changed_work, HZ / 2); + return; +} + +static int gRicoh619_cur_voltage; + +int ricoh619_battery_2_msp430_adc(void) +{ + int i, battValue, result; + const unsigned short battGasgauge[] = { + // 3.0V, 3.1V, 3.2V, 3.3V, 3.4V, 3.5V, 3.6V, 3.7V, 3.8V, 3.9V, 4.0V, 4.1V, 4.2V, + 767, 791, 812, 833, 852, 877, 903, 928, 950, 979, 993, 1019, 1023, + }; + if (critical_low_flag) return 0; + + if ((!gRicoh619_cur_voltage) || (3000 > gRicoh619_cur_voltage) || (4200 < gRicoh619_cur_voltage)) + return 1023; + + i = (gRicoh619_cur_voltage - 3000)/100; + if (gRicoh619_cur_voltage % 100) { + result = (gRicoh619_cur_voltage % 100)/ (100 / (battGasgauge[i+1]-battGasgauge[i])); + result += battGasgauge[i]; + } + else + result = battGasgauge[i]; + return result; +} + +static int ricoh61x_batt_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ricoh61x_battery_info *info = dev_get_drvdata(psy->dev->parent); + int data = 0; + int ret = 0; + uint8_t status; + + mutex_lock(&info->lock); + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + ret = ricoh61x_read(info->dev->parent, CHGSTATE_REG, &status); + if (ret < 0) { + dev_err(info->dev, "Error in reading the control register\n"); + mutex_unlock(&info->lock); + return ret; + } + //printk("status : 0x%02x \n", status) + if (psy->type == POWER_SUPPLY_TYPE_MAINS) + val->intval = (status & 0x80 ? 3 : 0); + else if (psy->type == POWER_SUPPLY_TYPE_USB) + val->intval = (status & 0x40 ? 3 : 0); + else + val->intval = (status & 0xC0 ? 3 : 0); + //yian, check charge full + if (val->intval == 3) { + uint8_t rd_status = (status & 0x1F); + //printk("rd_status : 0x%02x \n", rd_status); + if (rd_status == 0x04) //00100 Charge Complete + val->intval = 1; + } + break; + /* this setting is same as battery driver of 584 */ + case POWER_SUPPLY_PROP_STATUS: + ret = get_power_supply_Android_status(info); + if (POWER_SUPPLY_STATUS_FULL == ret) + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + else + val->intval = ret; + info->status = ret; + dev_dbg(info->dev, "Power Supply Status is %d\n", + info->status); + break; + + /* this setting is same as battery driver of 584 */ + case POWER_SUPPLY_PROP_PRESENT: + val->intval = info->present; + break; + + /* current voltage is got from fuel gauge */ + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + /* return real vbatt Voltage */ +#ifdef ENABLE_FUEL_GAUGE_FUNCTION + if (info->soca->ready_fg) + ret = measure_vbatt_FG(info, &data); + else { + //val->intval = -EINVAL; + data = info->cur_voltage * 1000; + } +#else + ret = measure_vbatt_ADC(info, &data); +#endif + val->intval = data; + /* convert unit uV -> mV */ + info->cur_voltage = data / 1000; + + gRicoh619_cur_voltage = info->cur_voltage; +#ifdef _RICOH619_DEBUG_ + dev_dbg(info->dev, "battery voltage is %d mV\n", + info->cur_voltage); +#endif + break; + +#ifdef ENABLE_FUEL_GAUGE_FUNCTION + /* current battery capacity is get from fuel gauge */ + case POWER_SUPPLY_PROP_CAPACITY: + if (info->entry_factory_mode){ + val->intval = 100; + info->capacity = 100; + } else if (info->soca->displayed_soc <= 0) { + val->intval = 0; + info->capacity = 0; + } else { + val->intval = (info->soca->displayed_soc + 50)/100; + info->capacity = (info->soca->displayed_soc + 50)/100; + } + + if (critical_low_flag) { + uint8_t chg_sts = 0; + ret = ricoh61x_read(info->dev->parent, CHGSTATE_REG, &chg_sts); + if (ret < 0) { + dev_err(info->dev, "Error in reading the status register\n"); + chg_sts = 0xC0; + } + if (chg_sts & 0xC0) { + critical_low_flag = 0; + } else { + val->intval = 0; + info->capacity = 0; + } + } + +#ifdef _RICOH619_DEBUG_ + dev_dbg(info->dev, "battery capacity is %d%%\n", + info->capacity); +#endif + break; + + /* current temperature of battery */ + case POWER_SUPPLY_PROP_TEMP: + if (info->soca->ready_fg) { + ret = 0; + val->intval = get_battery_temp_2(info); + info->battery_temp = val->intval/10; +#ifdef _RICOH619_DEBUG_ + dev_dbg(info->dev, + "battery temperature is %d degree\n", + info->battery_temp); +#endif + } else { + val->intval = info->battery_temp * 10; +#ifdef _RICOH619_DEBUG_ + dev_dbg(info->dev, "battery temperature is %d degree\n", info->battery_temp); +#endif + } + break; + + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: + if (info->soca->ready_fg) { + ret = get_time_to_empty(info); + val->intval = ret; + info->time_to_empty = ret/60; +#ifdef _RICOH619_DEBUG_ + dev_dbg(info->dev, + "time of empty battery is %d minutes\n", + info->time_to_empty); +#endif + } else { + //val->intval = -EINVAL; + val->intval = info->time_to_empty * 60; +#ifdef _RICOH619_DEBUG_ + dev_dbg(info->dev, "time of empty battery is %d minutes\n", info->time_to_empty); +#endif + } + break; + + case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW: + if (info->soca->ready_fg) { + ret = get_time_to_full(info); + val->intval = ret; + info->time_to_full = ret/60; +#ifdef _RICOH619_DEBUG_ + dev_dbg(info->dev, + "time of full battery is %d minutes\n", + info->time_to_full); +#endif + } else { + //val->intval = -EINVAL; + val->intval = info->time_to_full * 60; +#ifdef _RICOH619_DEBUG_ + dev_dbg(info->dev, "time of full battery is %d minutes\n", info->time_to_full); +#endif + } + break; +#endif + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + ret = 0; + break; + + case POWER_SUPPLY_PROP_HEALTH: + val->intval = POWER_SUPPLY_HEALTH_GOOD; + ret = 0; + break; + + default: + mutex_unlock(&info->lock); + return -ENODEV; + } + + mutex_unlock(&info->lock); + + return ret; +} + +static enum power_supply_property ricoh61x_batt_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + +#ifdef ENABLE_FUEL_GAUGE_FUNCTION + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, + POWER_SUPPLY_PROP_TIME_TO_FULL_NOW, +#endif + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_HEALTH, +}; + +static enum power_supply_property ricoh61x_power_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +struct power_supply power_ntx = { + .name = "mc13892_charger", + .type = POWER_SUPPLY_TYPE_MAINS|POWER_SUPPLY_TYPE_USB, + .properties = ricoh61x_power_props, + .num_properties = ARRAY_SIZE(ricoh61x_power_props), + .get_property = ricoh61x_batt_get_prop, +}; + +struct power_supply powerac = { + .name = "acpwr", + .type = POWER_SUPPLY_TYPE_MAINS, + .properties = ricoh61x_power_props, + .num_properties = ARRAY_SIZE(ricoh61x_power_props), + .get_property = ricoh61x_batt_get_prop, +}; + +struct power_supply powerusb = { + .name = "usbpwr", + .type = POWER_SUPPLY_TYPE_USB, + .properties = ricoh61x_power_props, + .num_properties = ARRAY_SIZE(ricoh61x_power_props), + .get_property = ricoh61x_batt_get_prop, +}; + +static __devinit int ricoh61x_battery_probe(struct platform_device *pdev) +{ + struct ricoh61x_battery_info *info; + struct ricoh619_battery_platform_data *pdata; + int type_n; + int ret, temp; + + printk(KERN_EMERG "PMU: %s : version is %s\n", __func__,RICOH61x_BATTERY_VERSION); + + info = kzalloc(sizeof(struct ricoh61x_battery_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + info->soca = kzalloc(sizeof(struct ricoh61x_soca_info), GFP_KERNEL); + if (!info->soca) + return -ENOMEM; + + info->dev = &pdev->dev; + info->status = POWER_SUPPLY_STATUS_CHARGING; + pdata = pdev->dev.platform_data; + info->monitor_time = pdata->monitor_time * HZ; + info->alarm_vol_mv = pdata->alarm_vol_mv; + + /* check rage of b,.attery type */ + type_n = Battery_Type(); + temp = sizeof(pdata->type)/(sizeof(struct ricoh619_battery_type_data)); + if(type_n >= temp) + { + printk("%s : Battery type num is out of range\n", __func__); + type_n = 0; + } + printk("%s type_n=%d,temp is %d\n", __func__, type_n,temp); + + /* check rage of battery num */ + info->num = Battery_Table(); + temp = sizeof(battery_init_para)/(sizeof(uint8_t)*32); + if(info->num >= (sizeof(battery_init_para)/(sizeof(uint8_t)*32))) + { + printk("%s : Battery num is out of range\n", __func__); + info->num = 0; + } + printk("%s info->num=%d,temp is %d\n", __func__, info->num,temp); + + /* these valuse are set in platform */ + info->ch_vfchg = pdata->type[type_n].ch_vfchg; + info->ch_vrchg = pdata->type[type_n].ch_vrchg; + info->ch_vbatovset = pdata->type[type_n].ch_vbatovset; + info->ch_ichg = pdata->type[type_n].ch_ichg; + info->ch_ilim_adp = pdata->type[type_n].ch_ilim_adp; + info->ch_ilim_usb = pdata->type[type_n].ch_ilim_usb; + info->ch_icchg = pdata->type[type_n].ch_icchg; + info->fg_target_vsys = pdata->type[type_n].fg_target_vsys; + info->fg_target_ibat = pdata->type[type_n].fg_target_ibat; + info->fg_poff_vbat = pdata->type[type_n].fg_poff_vbat; + info->jt_en = pdata->type[type_n].jt_en; + info->jt_hw_sw = pdata->type[type_n].jt_hw_sw; + info->jt_temp_h = pdata->type[type_n].jt_temp_h; + info->jt_temp_l = pdata->type[type_n].jt_temp_l; + info->jt_vfchg_h = pdata->type[type_n].jt_vfchg_h; + info->jt_vfchg_l = pdata->type[type_n].jt_vfchg_l; + info->jt_ichg_h = pdata->type[type_n].jt_ichg_h; + info->jt_ichg_l = pdata->type[type_n].jt_ichg_l; + + /* + printk("%s setting value\n", __func__); + printk("%s info->ch_vfchg = 0x%x\n", __func__, info->ch_vfchg); + printk("%s info->ch_vrchg = 0x%x\n", __func__, info->ch_vrchg); + printk("%s info->ch_vbatovset =0x%x\n", __func__, info->ch_vbatovset); + printk("%s info->ch_ichg = 0x%x\n", __func__, info->ch_ichg); + printk("%s info->ch_ilim_adp =0x%x \n", __func__, info->ch_ilim_adp); + printk("%s info->ch_ilim_usb = 0x%x\n", __func__, info->ch_ilim_usb); + printk("%s info->ch_icchg = 0x%x\n", __func__, info->ch_icchg); + printk("%s info->fg_target_vsys = 0x%x\n", __func__, info->fg_target_vsys); + printk("%s info->fg_target_ibat = 0x%x\n", __func__, info->fg_target_ibat); + printk("%s info->jt_en = 0x%x\n", __func__, info->jt_en); + printk("%s info->jt_hw_sw = 0x%x\n", __func__, info->jt_hw_sw); + printk("%s info->jt_temp_h = 0x%x\n", __func__, info->jt_temp_h); + printk("%s info->jt_temp_l = 0x%x\n", __func__, info->jt_temp_l); + printk("%s info->jt_vfchg_h = 0x%x\n", __func__, info->jt_vfchg_h); + printk("%s info->jt_vfchg_l = 0x%x\n", __func__, info->jt_vfchg_l); + printk("%s info->jt_ichg_h = 0x%x\n", __func__, info->jt_ichg_h); + printk("%s info->jt_ichg_l = 0x%x\n", __func__, info->jt_ichg_l); + */ + + info->adc_vdd_mv = ADC_VDD_MV; /* 2800; */ + info->min_voltage = MIN_VOLTAGE; /* 3100; */ + info->max_voltage = MAX_VOLTAGE; /* 4200; */ + info->delay = 500; + info->entry_factory_mode = false; + info->suspend_state = false; + info->stop_disp = false; + + mutex_init(&info->lock); + platform_set_drvdata(pdev, info); + +// info->battery.name = "battery"; + info->battery.name = "mc13892_bat"; + info->battery.type = POWER_SUPPLY_TYPE_BATTERY; + info->battery.properties = ricoh61x_batt_props; + info->battery.num_properties = ARRAY_SIZE(ricoh61x_batt_props); + info->battery.get_property = ricoh61x_batt_get_prop; + info->battery.set_property = NULL; + info->battery.external_power_changed + = ricoh61x_external_power_changed; + + /* Disable Charger/ADC interrupt */ + ret = ricoh61x_clr_bits(info->dev->parent, RICOH61x_INTC_INTEN, + CHG_INT | ADC_INT); + if (ret) + goto out; + + ret = ricoh61x_init_battery(info); + if (ret) + goto out; + +#ifdef ENABLE_FACTORY_MODE + info->factory_mode_wqueue + = create_singlethread_workqueue("ricoh61x_factory_mode"); + INIT_DELAYED_WORK_DEFERRABLE(&info->factory_mode_work, + check_charging_state_work); + + ret = ricoh61x_factory_mode(info); + if (ret) + goto out; + +#endif + + ret = power_supply_register(&pdev->dev, &info->battery); + + if (ret) + info->battery.dev->parent = &pdev->dev; + + ret = power_supply_register(&pdev->dev, &powerac); + ret = power_supply_register(&pdev->dev, &powerusb); + ret = power_supply_register(&pdev->dev, &power_ntx); + + info->monitor_wqueue + = create_singlethread_workqueue("ricoh61x_battery_monitor"); + info->workqueue = create_singlethread_workqueue("r5t61x_charger_in"); + INIT_WORK(&info->irq_work, charger_irq_work); + INIT_DELAYED_WORK_DEFERRABLE(&info->monitor_work, + ricoh61x_battery_work); + INIT_DELAYED_WORK_DEFERRABLE(&info->displayed_work, + ricoh61x_displayed_work); + INIT_DELAYED_WORK_DEFERRABLE(&info->charge_stable_work, + ricoh61x_stable_charge_countdown_work); + INIT_DELAYED_WORK_DEFERRABLE(&info->charge_monitor_work, + ricoh61x_charge_monitor_work); + INIT_DELAYED_WORK_DEFERRABLE(&info->get_charge_work, + ricoh61x_get_charge_work); + INIT_DELAYED_WORK_DEFERRABLE(&info->jeita_work, ricoh61x_jeita_work); + INIT_DELAYED_WORK(&info->changed_work, ricoh61x_changed_work); + + /* Charger IRQ workqueue settings */ + charger_irq = pdata->irq; + + + ret = request_threaded_irq(charger_irq + RICOH61x_IRQ_FONCHGINT, + NULL, charger_in_isr, IRQF_ONESHOT, + "r5t61x_charger_in", info); + if (ret < 0) { + dev_err(&pdev->dev, "Can't get CHG_INT IRQ for chrager: %d\n", + ret); + goto out; + } + + ret = request_threaded_irq(charger_irq + RICOH61x_IRQ_FCHGCMPINT, + NULL, charger_complete_isr, + IRQF_ONESHOT, "r5t61x_charger_comp", + info); + if (ret < 0) { + dev_err(&pdev->dev, "Can't get CHG_COMP IRQ for chrager: %d\n", + ret); + goto out; + } + + ret = request_threaded_irq(charger_irq + RICOH61x_IRQ_FVUSBDETSINT, + NULL, charger_usb_isr, IRQF_ONESHOT, + "r5t61x_usb_det", info); + if (ret < 0) { + dev_err(&pdev->dev, "Can't get USB_DET IRQ for chrager: %d\n", + ret); + goto out; + } + + ret = request_threaded_irq(charger_irq + RICOH61x_IRQ_FVADPDETSINT, + NULL, charger_adp_isr, IRQF_ONESHOT, + "r5t61x_adp_det", info); + if (ret < 0) { + dev_err(&pdev->dev, + "Can't get ADP_DET IRQ for chrager: %d\n", ret); + goto out; + } + + +#ifdef ENABLE_LOW_BATTERY_DETECTION +// ret = request_threaded_irq(charger_irq + RICOH61x_IRQ_VSYSLIR, +// NULL, adc_vsysl_isr, IRQF_ONESHOT, +// "r5t61x_adc_vsysl", info); + ret = request_threaded_irq(charger_irq + RICOH61x_IRQ_VBATLIR, + NULL, adc_vsysl_isr, IRQF_ONESHOT, + "r5t61x_adc_vsysl", info); + if (ret < 0) { + dev_err(&pdev->dev, + "Can't get ADC_VSYSL IRQ for chrager: %d\n", ret); + goto out; + } + INIT_DELAYED_WORK_DEFERRABLE(&info->low_battery_work, + low_battery_irq_work); +#endif + +#ifdef ENABLE_BATTERY_TEMP_DETECTION + ret = request_threaded_irq(charger_irq + RICOH61x_IRQ_VTHMLIR, + NULL, adc_vtherm_isr, IRQF_ONESHOT, + "r5t61x_adc_vtherm", info); + if (ret < 0) { + dev_err(&pdev->dev, + "Can't get ADC_VTHML IRQ for chrager: %d\n", ret); + goto out; + } + + ret = request_threaded_irq(charger_irq + RICOH61x_IRQ_VTHMHIR, + NULL, adc_vtherm_isr, IRQF_ONESHOT, + "r5t61x_adc_vtherm", info); + if (ret < 0) { + dev_err(&pdev->dev, + "Can't get ADC_VTHMH IRQ for chrager: %d\n", ret); + goto out; + } + + INIT_DELAYED_WORK_DEFERRABLE(&info->battery_temp_work, + battery_temp_irq_work); +#endif + + /* Charger init and IRQ setting */ + ret = ricoh61x_init_charger(info); + if (ret) + goto out; + +#ifdef ENABLE_FUEL_GAUGE_FUNCTION + ret = ricoh61x_init_fgsoca(info); +#endif + queue_delayed_work(info->monitor_wqueue, &info->monitor_work, + RICOH61x_MONITOR_START_TIME*HZ); + + + /* Enable Charger/ADC interrupt */ + ricoh61x_set_bits(info->dev->parent, RICOH61x_INTC_INTEN, CHG_INT | ADC_INT); + +// if(sysfs_create_link(&info->battery.dev->kobj, &info->battery.dev->kobj, "mc13892_bat")) { +// printk("[%s-%d] create mc13892_bat link fail !\n", __func__, __LINE__); +// } + if(sysfs_create_link(&info->dev->parent->parent->parent->parent->kobj, &info->dev->kobj, "pmic_battery.1")) { + printk("[%s-%d] create pmic_battery.1 link fail !\n", __func__, __LINE__); + } + + return 0; + +out: + kfree(info); + return ret; +} + +static int __devexit ricoh61x_battery_remove(struct platform_device *pdev) +{ + struct ricoh61x_battery_info *info = platform_get_drvdata(pdev); + uint8_t val; + int ret; + int err; + int cc_cap = 0; + long cc_cap_mas = 0; + bool is_charging = true; + + if (g_fg_on_mode + && (info->soca->status == RICOH61x_SOCA_STABLE)) { + err = ricoh61x_write(info->dev->parent, PSWR_REG, 0x7f); + if (err < 0) + dev_err(info->dev, "Error in writing PSWR_REG\n"); + g_soc = 0x7f; + set_current_time2register(info); + + } else if (info->soca->status != RICOH61x_SOCA_START + && info->soca->status != RICOH61x_SOCA_UNSTABLE + && info->soca->rsoc_ready_flag != 0) { + if (info->soca->displayed_soc < 50) { + val = 1; + } else { + val = (info->soca->displayed_soc + 50)/100; + val &= 0x7f; + } + ret = ricoh61x_write(info->dev->parent, PSWR_REG, val); + if (ret < 0) + dev_err(info->dev, "Error in writing PSWR_REG\n"); + + g_soc = val; + set_current_time2register(info); + + ret = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas, + &is_charging, 1); + if (ret < 0) + dev_err(info->dev, "Read cc_sum Error !!-----\n"); + } + + if (g_fg_on_mode == 0) { + ret = ricoh61x_clr_bits(info->dev->parent, + FG_CTRL_REG, 0x01); + if (ret < 0) + dev_err(info->dev, "Error in clr FG EN\n"); + } + + /* set rapid timer 300 min */ + err = ricoh61x_set_bits(info->dev->parent, TIMSET_REG, 0x03); + if (err < 0) { + dev_err(info->dev, "Error in writing the control register\n"); + } + + free_irq(charger_irq + RICOH61x_IRQ_FONCHGINT, &info); + free_irq(charger_irq + RICOH61x_IRQ_FCHGCMPINT, &info); + free_irq(charger_irq + RICOH61x_IRQ_FVUSBDETSINT, &info); + free_irq(charger_irq + RICOH61x_IRQ_FVADPDETSINT, &info); +#ifdef ENABLE_LOW_BATTERY_DETECTION +// free_irq(charger_irq + RICOH61x_IRQ_VSYSLIR, &info); + free_irq(charger_irq + RICOH61x_IRQ_VADPLIR, &info); +#endif +#ifdef ENABLE_BATTERY_TEMP_DETECTION + free_irq(charger_irq + RICOH61x_IRQ_VTHMLIR, &info); + free_irq(charger_irq + RICOH61x_IRQ_VTHMHIR, &info); +#endif + + + cancel_delayed_work(&info->monitor_work); + cancel_delayed_work(&info->charge_stable_work); + cancel_delayed_work(&info->charge_monitor_work); + cancel_delayed_work(&info->get_charge_work); + cancel_delayed_work(&info->displayed_work); + cancel_delayed_work(&info->changed_work); +#ifdef ENABLE_LOW_BATTERY_DETECTION + cancel_delayed_work(&info->low_battery_work); +#endif +#ifdef ENABLE_BATTERY_TEMP_DETECTION + cancel_delayed_work(&info->battery_temp_work); +#endif +#ifdef ENABLE_FACTORY_MODE + cancel_delayed_work(&info->factory_mode_work); +#endif + cancel_delayed_work(&info->jeita_work); + cancel_work_sync(&info->irq_work); + + flush_workqueue(info->monitor_wqueue); + flush_workqueue(info->workqueue); +#ifdef ENABLE_FACTORY_MODE + flush_workqueue(info->factory_mode_wqueue); +#endif + + destroy_workqueue(info->monitor_wqueue); + destroy_workqueue(info->workqueue); +#ifdef ENABLE_FACTORY_MODE + destroy_workqueue(info->factory_mode_wqueue); +#endif + + power_supply_unregister(&info->battery); + kfree(info); + platform_set_drvdata(pdev, NULL); + return 0; +} + +#ifdef CONFIG_PM +extern void ricoh_suspend_state_sync(void); +static int ricoh61x_battery_suspend(struct device *dev) +{ + struct ricoh61x_battery_info *info = dev_get_drvdata(dev); + uint8_t val; + uint8_t val2; + int ret; + int err; + int cc_display2suspend = 0; + int cc_cap = 0; + long cc_cap_mas =0; + bool is_charging = true; + int displayed_soc_temp; + + printk("PMU: %s START ================================================================================\n", __func__); + ricoh_suspend_state_sync(); + + get_current_time(info, &info->sleepEntryTime); + dev_info(info->dev, "sleep entry time : %lu secs\n", + info->sleepEntryTime); + +#ifdef ENABLE_MASKING_INTERRUPT_IN_SLEEP + ricoh61x_clr_bits(dev->parent, RICOH61x_INTC_INTEN, CHG_INT); +#endif + info->stop_disp = true; + info->soca->suspend_full_flg = false; + + if (g_fg_on_mode + && (info->soca->status == RICOH61x_SOCA_STABLE)) { + err = ricoh61x_write(info->dev->parent, PSWR_REG, 0x7f); + if (err < 0) + dev_err(info->dev, "Error in writing PSWR_REG\n"); + g_soc = 0x7F; + set_current_time2register(info); + + info->soca->suspend_soc = info->soca->displayed_soc; + ret = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas, + &is_charging, 1); + if (ret < 0) + dev_err(info->dev, "Read cc_sum Error !!-----\n"); + + info->soca->suspend_cc = 0; + + } else { + if(info->soca->status == RICOH61x_SOCA_LOW_VOL){ + ret = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas, + &is_charging, 1); + if (ret < 0) + dev_err(info->dev, "Read cc_sum Error !!-----\n"); + + if(is_charging == true){ + info->soca->cc_delta = cc_cap; + //cc_cap_mas; + }else { + info->soca->cc_delta = -cc_cap; + cc_cap_mas = -cc_cap_mas; + } + + info->soca->temp_cc_delta_cap_mas += cc_cap_mas - info->soca->last_cc_delta_cap_mas; + + printk("PMU : %s : Suspend : temp_cc_delta_cap_mas is %ld, cc_delta is %ld, last_cc_delta_cap_mas is %ld\n" + ,__func__, info->soca->temp_cc_delta_cap_mas, cc_cap_mas, info->soca->last_cc_delta_cap_mas); + displayed_soc_temp = info->soca->displayed_soc; + + if ((info->soca->displayed_soc + 50)/100 >= 100) { + displayed_soc_temp = min(10000, displayed_soc_temp); + } else { + displayed_soc_temp = min(9949, displayed_soc_temp); + } + displayed_soc_temp = max(0, displayed_soc_temp); + info->soca->displayed_soc = displayed_soc_temp; + + info->soca->suspend_soc = info->soca->displayed_soc; + info->soca->suspend_cc = 0; + info->soca->suspend_rsoc = calc_capacity_2(info); + + }else if (info->soca->rsoc_ready_flag == 0 + || info->soca->status == RICOH61x_SOCA_START + || info->soca->status == RICOH61x_SOCA_UNSTABLE) { + ret = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas, + &is_charging, 2); + if (ret < 0) + dev_err(info->dev, "Read cc_sum Error !!-----\n"); + + if(is_charging == true){ + info->soca->cc_delta = cc_cap; + //cc_cap_mas; + }else { + info->soca->cc_delta = -cc_cap; + cc_cap_mas = -cc_cap_mas; + } + + //info->soca->temp_cc_delta_cap_mas += cc_cap_mas - info->soca->last_cc_delta_cap_mas; + + //get charge/discharge value from displayed work to suspend start + cc_display2suspend = info->soca->cc_delta - info->soca->last_cc_rrf0; + cc_display2suspend = min(400, cc_display2suspend); //fail-safe + cc_display2suspend = max(-400, cc_display2suspend); + info->soca->last_cc_rrf0 = 0; + + printk("PMU : %s : Suspend : temp_cc_delta_cap_mas is %ld, cc_delta is %ld, last_cc_delta_cap_mas is %ld, cc_display2suspend is %d\n" + ,__func__, info->soca->temp_cc_delta_cap_mas, cc_cap_mas, info->soca->last_cc_delta_cap_mas, cc_display2suspend); + + if (info->soca->status == RICOH61x_SOCA_START + || info->soca->status == RICOH61x_SOCA_UNSTABLE + || info->soca->status == RICOH61x_SOCA_STABLE) { + displayed_soc_temp + = info->soca->init_pswr * 100 + info->soca->cc_delta; + } else { + if(info->soca->status == RICOH61x_SOCA_FULL){ + info->soca->temp_cc_delta_cap += cc_display2suspend; + displayed_soc_temp + = info->soca->displayed_soc;// + (info->soca->cc_delta/100) *100; + } else { + displayed_soc_temp + = info->soca->displayed_soc + cc_display2suspend; + } + } + + if ((info->soca->displayed_soc + 50)/100 >= 100) { + displayed_soc_temp = min(10000, displayed_soc_temp); + } else { + displayed_soc_temp = min(9949, displayed_soc_temp); + } + displayed_soc_temp = max(0, displayed_soc_temp); + info->soca->displayed_soc = displayed_soc_temp; + + info->soca->suspend_soc = info->soca->displayed_soc; + info->soca->suspend_cc = info->soca->cc_delta % 100; + + val = info->soca->init_pswr + (info->soca->cc_delta/100); + val = min(100, val); + val = max(1, val); + + info->soca->init_pswr = val; + info->soca->suspend_rsoc = (info->soca->init_pswr * 100) + (info->soca->cc_delta % 100); + + } else { + ret = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas, + &is_charging, 1); + if (ret < 0) + dev_err(info->dev, "Read cc_sum Error !!-----\n"); + + if(is_charging == true){ + info->soca->cc_delta = cc_cap; + //cc_cap_mas; + }else { + info->soca->cc_delta = -cc_cap; + cc_cap_mas = -cc_cap_mas; + } + + //info->soca->temp_cc_delta_cap_mas += cc_cap_mas - info->soca->last_cc_delta_cap_mas; + printk("PMU : %s : Suspend : temp_cc_delta_cap_mas is %ld, cc_delta is %ld, last_cc_delta_cap_mas is %ld\n" + ,__func__, info->soca->temp_cc_delta_cap_mas, cc_cap_mas, info->soca->last_cc_delta_cap_mas); + + if (info->soca->status == RICOH61x_SOCA_FULL){ + info->soca->temp_cc_delta_cap += info->soca->cc_delta; + displayed_soc_temp = info->soca->displayed_soc; + } else { + displayed_soc_temp + = info->soca->displayed_soc + info->soca->cc_delta; + } + + if ((info->soca->displayed_soc + 50)/100 >= 100) { + displayed_soc_temp = min(10000, displayed_soc_temp); + } else { + displayed_soc_temp = min(9949, displayed_soc_temp); + } + displayed_soc_temp = max(0, displayed_soc_temp); + info->soca->displayed_soc = displayed_soc_temp; + + info->soca->suspend_soc = info->soca->displayed_soc; + info->soca->suspend_cc = 0; + info->soca->suspend_rsoc = calc_capacity_2(info); + + } + + printk(KERN_INFO "PMU: %s status(%d), rrf(%d), suspend_soc(%d), suspend_cc(%d)\n", + __func__, info->soca->status, info->soca->rsoc_ready_flag, info->soca->suspend_soc, info->soca->suspend_cc); + printk(KERN_INFO "PMU: %s DSOC(%d), init_pswr(%d), cc_delta(%d)\n", + __func__, info->soca->displayed_soc, info->soca->init_pswr, info->soca->cc_delta); + + if (info->soca->displayed_soc < 50) { + val = 1; + } else { + val = (info->soca->displayed_soc + 50)/100; + val &= 0x7f; + } + ret = ricoh61x_write(info->dev->parent, PSWR_REG, val); + if (ret < 0) + dev_err(info->dev, "Error in writing PSWR_REG\n"); + + g_soc = val; + set_current_time2register(info); + + } + + ret = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas, + &is_charging, 0); + info->soca->cc_delta + = (is_charging == true) ? cc_cap : -cc_cap; + + printk(KERN_INFO "PMU: %s : STATUS(%d), DSOC(%d), RSOC(%d), init_pswr*100(%d), cc_delta(%d) ====================\n", + __func__, info->soca->status, displayed_soc_temp, + calc_capacity_2(info), info->soca->init_pswr*100, info->soca->cc_delta); + + if (info->soca->status == RICOH61x_SOCA_DISP + || info->soca->status == RICOH61x_SOCA_STABLE + || info->soca->status == RICOH61x_SOCA_FULL) { + info->soca->soc = calc_capacity_2(info); + info->soca->soc_delta = + info->soca->soc_delta + (info->soca->soc - info->soca->last_soc); + + } else { + info->soca->soc_delta = 0; + } + + if (info->soca->status == RICOH61x_SOCA_FULL) + { + info->soca->suspend_full_flg = true; + info->soca->status = RICOH61x_SOCA_DISP; + } + + if (info->soca->status == RICOH61x_SOCA_LOW_VOL) + { + //reset current information + info->soca->hurry_up_flg = 0; + } + + info->soca->store_fl_current = fl_current; + info->soca->store_slp_state = slp_state; + info->soca->store_sus_current = sus_current; + info->soca->store_hiber_current = hiber_current; + + printk(KERN_INFO "PMU: %s : fl_current(%d), slp_state(%d), sus_current(%d), hiber_current(%d)\n", + __func__, info->soca->store_fl_current, info->soca->store_slp_state, + info->soca->store_sus_current, info->soca->store_hiber_current); + + /* set rapid timer 300 min */ + err = ricoh61x_set_bits(info->dev->parent, TIMSET_REG, 0x03); + if (err < 0) { + dev_err(info->dev, "Error in writing the control register\n"); + } + + /* Enable VBAT threshold Low interrupt */ + err = ricoh61x_set_bits(info->dev->parent, RICOH61x_INT_EN_ADC1, 0x02); + if (err < 0) { + dev_err(info->dev, "Error in settind VBAT ThrLow\n"); + } + info->suspend_state = true; + + //Enable relaxtion state + /* set relaxation state */ + if (RICOH61x_REL1_SEL_VALUE > 240) + val = 0x0F; + else + val = RICOH61x_REL1_SEL_VALUE / 16 ; + + /* set relaxation state */ + if (RICOH61x_REL2_SEL_VALUE > 120) + val2 = 0x0F; + else + val2 = RICOH61x_REL2_SEL_VALUE / 8 ; + + val = val + (val2 << 4); + + err = ricoh61x_write_bank1(info->dev->parent, BAT_REL_SEL_REG, val); + if (err < 0) { + dev_err(info->dev, "Error in writing BAT_REL_SEL_REG\n"); + } + + + cancel_delayed_work(&info->monitor_work); + cancel_delayed_work(&info->displayed_work); + cancel_delayed_work(&info->charge_stable_work); + cancel_delayed_work(&info->charge_monitor_work); + cancel_delayed_work(&info->get_charge_work); + cancel_delayed_work(&info->changed_work); +#ifdef ENABLE_LOW_BATTERY_DETECTION + cancel_delayed_work(&info->low_battery_work); +#endif +#ifdef ENABLE_BATTERY_TEMP_DETECTION + cancel_delayed_work(&info->battery_temp_work); +#endif +#ifdef ENABLE_FACTORY_MODE + cancel_delayed_work(&info->factory_mode_work); +#endif + cancel_delayed_work(&info->jeita_work); +/* flush_work(&info->irq_work); */ + + return 0; +} + +/** +* get SOC value during period of Suspend/Hibernation +* this function is only run discharge state +* info : battery info +* Period : sleep period +* sleepCurrent : sleep current +* +* return value : delta soc, unit is "minus" 0.01% +*/ + +static int calc_cc_value_by_sleepPeriod(struct ricoh61x_battery_info *info, unsigned long Period, int sleepCurrent) +{ + int fa_cap; //unit is mAh + unsigned long delta_cap; //unit is uAs + unsigned long delta_soc; //unit is 0.01% + + fa_cap = (battery_init_para[info->num][22]<<8) + | (battery_init_para[info->num][23]); + + if(fa_cap == 0){ + // avoiding 0 divied + return 0; + } else { + // Check Suspend current is normal + + // delta_cap[uAs] = Period[s] * (Suspend current + FL current)[uA] + delta_cap = (Period * sleepCurrent) + info->soca->sus_cc_cap_offset; + //delta_soc[0.01%] = (delta_cap/1000)[mAs] * 10000/ (fa_cap * 60 * 60)[mAs]; + delta_soc = (delta_cap / (fa_cap * 36)) / 10; + + //info->soca->sus_cc_cap_offset[uAs] = delta_cap[uAs] - (delta_soc[0.01%] * (fa_cap[mAs] * 60 * 60/ (100 * 100)))*1000; + info->soca->sus_cc_cap_offset = delta_cap - (delta_soc * (fa_cap * 360)); + //0.01% uAs => fa_cap*360 + info->soca->sus_cc_cap_offset = min((360*fa_cap), info->soca->sus_cc_cap_offset); + info->soca->sus_cc_cap_offset = max(0, info->soca->sus_cc_cap_offset); + + delta_soc = min(10000, delta_soc); + delta_soc = max(0, delta_soc); + + printk("PMU : %s : delta_cap is %ld [uAs], Period is %ld [s], fa_cap is %d [mAh], delta_soc is %ld [0.01%%], offset is %d [uAs]\n" + ,__func__, delta_cap, Period, fa_cap, delta_soc, info->soca->sus_cc_cap_offset); + } + + return (-1 * delta_soc); +} + +/** +* get SOC value during period of Suspend/Hibernate with current method +* info : battery info +* Period : sleep period +* +* return value : soc, unit is 0.01% +*/ + +static int calc_soc_by_currentMethod(struct ricoh61x_battery_info *info, unsigned long Period) +{ + int soc; + int sleepCurrent; // unit is uA + + if(info->soca->store_slp_state == 0) { + // Check Suspend current is normal + if ((info->soca->store_sus_current <= 0) + || (info->soca->store_sus_current > RICOH61x_SLEEP_CURRENT_LIMIT)) { + if ((sus_current <= 0) || (sus_current > RICOH61x_SLEEP_CURRENT_LIMIT)) { + info->soca->store_sus_current = RICOH61x_SUS_CURRENT_DEF; + } else { + info->soca->store_sus_current = sus_current; + } + } + + if ((info->soca->store_fl_current < 0) + || (info->soca->store_fl_current > RICOH61x_FL_CURRENT_LIMIT)) { + if ((fl_current < 0) || (fl_current > RICOH61x_FL_CURRENT_LIMIT)) { + info->soca->store_fl_current = RICOH61x_FL_CURRENT_DEF; + } else { + info->soca->store_fl_current = fl_current; + } + } + + sleepCurrent = info->soca->store_sus_current + info->soca->store_fl_current; + + if (sleepCurrent < RICOH61x_SUS_CURRENT_THRESH) { + // Calculate cc_delta from [Suspend current * Sleep period] + info->soca->cc_delta = calc_cc_value_by_sleepPeriod(info, Period, sleepCurrent); + printk(KERN_INFO "PMU: %s Suspend(S/W) slp_current(%d), sus_current(%d), fl_current(%d), cc_delta(%d) ----------\n", + __func__, sleepCurrent, info->soca->store_sus_current, info->soca->store_fl_current, info->soca->cc_delta); + } else { + // Calculate cc_delta between Sleep-In and Sleep-Out + info->soca->cc_delta -= info->soca->suspend_cc; + printk(KERN_INFO "PMU: %s Suspend(H/W) slp_current(%d), sus_current(%d), fl_current(%d), cc_delta(%d) ----------\n", + __func__, sleepCurrent, info->soca->store_sus_current, info->soca->store_fl_current, info->soca->cc_delta); + } + } else { + // Check Hibernate current is normal + if ((info->soca->store_hiber_current <= 0) + || (info->soca->store_hiber_current > RICOH61x_SLEEP_CURRENT_LIMIT)) { + if ((hiber_current <= 0) || (hiber_current > RICOH61x_SLEEP_CURRENT_LIMIT)) { + sleepCurrent = RICOH61x_HIBER_CURRENT_DEF; + } else { + sleepCurrent = hiber_current; + } + } else { + sleepCurrent = info->soca->store_hiber_current; + } + // Calculate cc_delta from [Hibernate current * Sleep period] + info->soca->cc_delta = calc_cc_value_by_sleepPeriod(info, Period, sleepCurrent); + printk(KERN_INFO "PMU: %s Hibernate(S/W) hiber_current(%d), cc_delta(%d) ----------\n", + __func__, sleepCurrent, info->soca->cc_delta); + } + + soc = info->soca->suspend_soc + info->soca->cc_delta; + + printk("PMU : %s : slp_state is %d, soc is %d [0.01%%] ----------\n" + , __func__, info->soca->store_slp_state, soc); + + // soc range is 0~10000 + return soc; +} + + +static int ricoh61x_battery_resume(struct device *dev) +{ + struct ricoh61x_battery_info *info = dev_get_drvdata(dev); + uint8_t val; + int ret; + int displayed_soc_temp; + int cc_cap = 0; + long cc_cap_mas = 0; + bool is_charging = true; + bool is_jeita_updated; + int i; + unsigned long suspend_period_time; //unit is sec + int soc_voltage, soc_current; + int resume_rsoc; + + printk("PMU: %s START ================================================================================\n", __func__); + + get_current_time(info, &info->sleepExitTime); + dev_info(info->dev, "sleep exit time : %lu secs\n", + info->sleepExitTime); + + suspend_period_time = info->sleepExitTime - info->sleepEntryTime; + +#ifdef STANDBY_MODE_DEBUG + if(multiple_sleep_mode == 0) { +// suspend_period_time *= 1; + } else if(multiple_sleep_mode == 1) { + suspend_period_time *= 60; + } else if(multiple_sleep_mode == 2) { + suspend_period_time *= 3600; + } +#endif + + printk("PMU : %s : suspend_period_time is %lu, sleepExitTime is %lu sleepEntryTime is %lu ==========\n", + __func__, suspend_period_time,info->sleepExitTime,info->sleepEntryTime); + + /* Clear VBAT threshold Low interrupt */ + ret = ricoh61x_clr_bits(info->dev->parent, RICOH61x_INT_EN_ADC1, 0x02); + if (ret < 0) { + dev_err(info->dev, "Error in clearing VBAT ThrLow\n"); + } + + +#ifdef ENABLE_MASKING_INTERRUPT_IN_SLEEP + ricoh61x_set_bits(dev->parent, RICOH61x_INTC_INTEN, CHG_INT); +#endif + ret = check_jeita_status(info, &is_jeita_updated); + if (ret < 0) { + dev_err(info->dev, "Error in updating JEITA %d\n", ret); + } + + if (info->entry_factory_mode) { + info->soca->displayed_soc = -EINVAL; + } else { + info->soca->soc = info->soca->suspend_soc + info->soca->suspend_cc; + + if (RICOH61x_SOCA_START == info->soca->status + || RICOH61x_SOCA_UNSTABLE == info->soca->status + || info->soca->rsoc_ready_flag == 0) { + ret = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas, + &is_charging, 2); + if (ret < 0) + dev_err(info->dev, "Read cc_sum Error !!-----\n"); + info->soca->cc_delta + = (is_charging == true) ? cc_cap : -cc_cap; + + //this is for CC delta issue for Hibernate + if((info->soca->cc_delta - info->soca->suspend_cc) <= 0){ + // Discharge Processing + printk(KERN_INFO "PMU: %s : Discharge Processing (rrf=0)\n", __func__); + + // Calculate SOC by Current Method + soc_current = calc_soc_by_currentMethod(info, suspend_period_time); + + // Calculate SOC by Voltage Method + soc_voltage = calc_soc_by_voltageMethod(info); + + printk(KERN_INFO "PMU: %s : soc_current(%d), soc_voltage(%d), Diff(%d) ==========\n", + __func__, soc_current, soc_voltage, (soc_current - soc_voltage)); + + // If difference is small, use current method. If not, use voltage method. + if ((soc_current - soc_voltage) < 1000) { + // Use Current method if difference is small + displayed_soc_temp = soc_current; + update_rsoc_on_currentMethod(info, soc_current); + } else { + // Use Voltage method if difference is large + displayed_soc_temp = soc_voltage; + update_rsoc_on_voltageMethod(info, soc_voltage); + } + } else { + // Charge Processing + val = info->soca->init_pswr + (info->soca->cc_delta/100); + val = min(100, val); + val = max(1, val); + + info->soca->init_pswr = val; + + if (RICOH61x_SOCA_START == info->soca->status + || RICOH61x_SOCA_UNSTABLE == info->soca->status + || RICOH61x_SOCA_STABLE == info->soca->status) { +// displayed_soc_temp = info->soca->suspend_soc + info->soca->cc_delta; + displayed_soc_temp = (info->soca->init_pswr*100) + info->soca->cc_delta%100; + } else { + info->soca->cc_delta = info->soca->cc_delta - info->soca->suspend_cc; + if ((info->soca->cc_delta < 400) && (info->soca->suspend_full_flg == true)){ + displayed_soc_temp = info->soca->suspend_soc; + info->soca->temp_cc_delta_cap += info->soca->cc_delta; + info->soca->cc_delta = info->soca->suspend_cc; + printk("PMU: %s : under 400 cc_delta is %d, temp_cc_delta is %d\n", + __func__, info->soca->cc_delta, info->soca->temp_cc_delta_cap); + }else { + displayed_soc_temp = info->soca->suspend_soc + info->soca->cc_delta; + info->soca->temp_cc_delta_cap = 0; + } + } + } + + } else { + ret = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas, + &is_charging, 1); + if (ret < 0) + dev_err(info->dev, "Read cc_sum Error !!-----\n"); + info->soca->cc_delta + = (is_charging == true) ? cc_cap : -cc_cap; + + printk("PMU: %s cc_delta(%d), suspend_cc(%d), Diff(%d)\n", + __func__, info->soca->cc_delta, info->soca->suspend_cc, (info->soca->cc_delta - info->soca->suspend_cc)); + //this is for CC delta issue for Hibernate + if((info->soca->cc_delta - info->soca->suspend_cc) <= 0){ + // Discharge Processing + printk(KERN_INFO "PMU: %s : Discharge Processing (rrf=1)\n", __func__); + + // Calculate SOC by Current Method + soc_current = calc_soc_by_currentMethod(info, suspend_period_time); + + // Calculate SOC by Voltage Method + soc_voltage = calc_soc_by_voltageMethod(info); + + printk(KERN_INFO "PMU: %s : soc_current(%d), soc_voltage(%d), Diff(%d)\n", + __func__, soc_current, soc_voltage, (soc_current - soc_voltage)); + + // If difference is small, use current method. If not, use voltage method. + if ((soc_current - soc_voltage) < 1000) { + // Use Current method if difference is small + displayed_soc_temp = soc_current; + update_rsoc_on_currentMethod(info, soc_current); + } else { + // Use Voltage method if difference is large + displayed_soc_temp = soc_voltage; + update_rsoc_on_voltageMethod(info, soc_voltage); + } + } else { + // Charge Processing + info->soca->cc_delta = info->soca->cc_delta - info->soca->suspend_cc; + if ((info->soca->cc_delta < 400) && (info->soca->suspend_full_flg == true)){ + displayed_soc_temp = info->soca->suspend_soc; + info->soca->temp_cc_delta_cap += info->soca->cc_delta; + info->soca->cc_delta = info->soca->suspend_cc; + printk("PMU: %s : under 400 cc_delta is %d, temp_cc_delta is %d\n", + __func__, info->soca->cc_delta, info->soca->temp_cc_delta_cap); + }else { + displayed_soc_temp = info->soca->suspend_soc + info->soca->cc_delta; + info->soca->temp_cc_delta_cap = 0; + } + } + } + + /* Check "zero_flg" in all states */ + if (info->soca->zero_flg == 1) { + if((info->soca->Ibat_ave >= 0) + || (displayed_soc_temp >= 100)){ + info->soca->zero_flg = 0; + } else { + displayed_soc_temp = 0; + } + } else if (displayed_soc_temp < 100) { + /* keep DSOC = 1 when Vbat is over 3.4V*/ + if( info->fg_poff_vbat != 0) { + if (info->soca->Vbat_ave < 2000*1000) { /* error value */ + displayed_soc_temp = 100; + } else if (info->soca->Vbat_ave < info->fg_poff_vbat*1000) { + displayed_soc_temp = 0; + info->soca->zero_flg = 1; + } else { + displayed_soc_temp = 100; + } + } + } + displayed_soc_temp = min(10000, displayed_soc_temp); + displayed_soc_temp = max(0, displayed_soc_temp); + + if (0 == info->soca->jt_limit) { + check_charge_status_2(info, displayed_soc_temp); + } else { + info->soca->displayed_soc = displayed_soc_temp; + } + + val = (info->soca->displayed_soc + 50)/100; + val &= 0x7f; + ret = ricoh61x_write(info->dev->parent, PSWR_REG, val); + if (ret < 0) + dev_err(info->dev, "Error in writing PSWR_REG\n"); + + g_soc = val; + set_current_time2register(info); + + + if ((RICOH61x_SOCA_DISP == info->soca->status) + || (RICOH61x_SOCA_STABLE == info->soca->status)){ + info->soca->last_soc = calc_capacity_2(info); + } + } + + ret = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas, + &is_charging, 0); + if(is_charging == true) { + info->soca->cc_delta = cc_cap; + //cc_cap_mas; + } else { + info->soca->cc_delta = -cc_cap; + cc_cap_mas = -cc_cap_mas; + } + + //if (info->soca->status == RICOH61x_SOCA_LOW_VOL) + //{ + info->soca->last_cc_delta_cap = info->soca->cc_delta; + info->soca->last_cc_delta_cap_mas = cc_cap_mas; + //} + + + + printk(KERN_INFO "PMU: %s : STATUS(%d), DSOC(%d), RSOC(%d), init_pswr*100(%d), cc_delta(%d) ====================\n", + __func__, info->soca->status, displayed_soc_temp, calc_capacity_2(info), info->soca->init_pswr*100, info->soca->cc_delta); + + ret = measure_vbatt_FG(info, &info->soca->Vbat_ave); + ret = measure_vsys_ADC(info, &info->soca->Vsys_ave); + ret = measure_Ibatt_FG(info, &info->soca->Ibat_ave); + + //Disable relaxtion state + ret = ricoh61x_write_bank1(info->dev->parent, BAT_REL_SEL_REG, 0); + if (ret < 0) { + dev_err(info->dev, "Error in writing BAT_REL_SEL_REG\n"); + } + + info->stop_disp = false; + + power_supply_changed(&info->battery); + queue_delayed_work(info->monitor_wqueue, &info->displayed_work, HZ); + + if (RICOH61x_SOCA_UNSTABLE == info->soca->status) { + info->soca->stable_count = 10; + queue_delayed_work(info->monitor_wqueue, + &info->charge_stable_work, + RICOH61x_FG_STABLE_TIME*HZ/10); + } else if (RICOH61x_SOCA_FG_RESET == info->soca->status) { + info->soca->stable_count = 1; + + for (i = 0; i < 3; i = i+1) + info->soca->reset_soc[i] = 0; + info->soca->reset_count = 0; + + queue_delayed_work(info->monitor_wqueue, + &info->charge_stable_work, + RICOH61x_FG_RESET_TIME*HZ); + } + + queue_delayed_work(info->monitor_wqueue, &info->monitor_work, + info->monitor_time); + + queue_delayed_work(info->monitor_wqueue, &info->charge_monitor_work, + RICOH61x_CHARGE_RESUME_TIME * HZ); + + info->soca->chg_count = 0; + queue_delayed_work(info->monitor_wqueue, &info->get_charge_work, + RICOH61x_CHARGE_RESUME_TIME * HZ); + if (info->jt_en) { + if (!info->jt_hw_sw) { + queue_delayed_work(info->monitor_wqueue, &info->jeita_work, + RICOH61x_JEITA_UPDATE_TIME * HZ); + } + } + + return 0; +} + +static const struct dev_pm_ops ricoh61x_battery_pm_ops = { + .suspend = ricoh61x_battery_suspend, + .resume = ricoh61x_battery_resume, +}; +#endif + +static struct platform_driver ricoh61x_battery_driver = { + .driver = { + .name = "ricoh619-battery", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &ricoh61x_battery_pm_ops, +#endif + }, + .probe = ricoh61x_battery_probe, + .remove = __devexit_p(ricoh61x_battery_remove), +}; + +static int __init ricoh61x_battery_init(void) +{ + printk(KERN_INFO "PMU: %s\n", __func__); + return platform_driver_register(&ricoh61x_battery_driver); +} +module_init(ricoh61x_battery_init); + +static void __exit ricoh61x_battery_exit(void) +{ + platform_driver_unregister(&ricoh61x_battery_driver); +} +module_exit(ricoh61x_battery_exit); + +MODULE_DESCRIPTION("RICOH R5T619 Battery driver"); +MODULE_ALIAS("platform:ricoh619-battery"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/s3c_adc_battery.c b/drivers/power/s3c_adc_battery.c new file mode 100644 index 00000000..d36c289a --- /dev/null +++ b/drivers/power/s3c_adc_battery.c @@ -0,0 +1,437 @@ +/* + * iPAQ h1930/h1940/rx1950 battery controller driver + * Copyright (c) Vasily Khoruzhick + * Based on h1940_battery.c by Arnaud Patard + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define BAT_POLL_INTERVAL 10000 /* ms */ +#define JITTER_DELAY 500 /* ms */ + +struct s3c_adc_bat { + struct power_supply psy; + struct s3c_adc_client *client; + struct s3c_adc_bat_pdata *pdata; + int volt_value; + int cur_value; + unsigned int timestamp; + int level; + int status; + int cable_plugged:1; +}; + +static struct delayed_work bat_work; + +static void s3c_adc_bat_ext_power_changed(struct power_supply *psy) +{ + schedule_delayed_work(&bat_work, + msecs_to_jiffies(JITTER_DELAY)); +} + +static enum power_supply_property s3c_adc_backup_bat_props[] = { + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_MIN, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, +}; + +static int s3c_adc_backup_bat_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct s3c_adc_bat *bat = container_of(psy, struct s3c_adc_bat, psy); + + if (!bat) { + dev_err(psy->dev, "%s: no battery infos ?!\n", __func__); + return -EINVAL; + } + + if (bat->volt_value < 0 || + jiffies_to_msecs(jiffies - bat->timestamp) > + BAT_POLL_INTERVAL) { + bat->volt_value = s3c_adc_read(bat->client, + bat->pdata->backup_volt_channel); + bat->volt_value *= bat->pdata->backup_volt_mult; + bat->timestamp = jiffies; + } + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = bat->volt_value; + return 0; + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + val->intval = bat->pdata->backup_volt_min; + return 0; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = bat->pdata->backup_volt_max; + return 0; + default: + return -EINVAL; + } +} + +static struct s3c_adc_bat backup_bat = { + .psy = { + .name = "backup-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = s3c_adc_backup_bat_props, + .num_properties = ARRAY_SIZE(s3c_adc_backup_bat_props), + .get_property = s3c_adc_backup_bat_get_property, + .use_for_apm = 1, + }, +}; + +static enum power_supply_property s3c_adc_main_bat_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, +}; + +static int calc_full_volt(int volt_val, int cur_val, int impedance) +{ + return volt_val + cur_val * impedance / 1000; +} + +static int charge_finished(struct s3c_adc_bat *bat) +{ + return bat->pdata->gpio_inverted ? + !gpio_get_value(bat->pdata->gpio_charge_finished) : + gpio_get_value(bat->pdata->gpio_charge_finished); +} + +static int s3c_adc_bat_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct s3c_adc_bat *bat = container_of(psy, struct s3c_adc_bat, psy); + + int new_level; + int full_volt; + const struct s3c_adc_bat_thresh *lut = bat->pdata->lut_noac; + unsigned int lut_size = bat->pdata->lut_noac_cnt; + + if (!bat) { + dev_err(psy->dev, "no battery infos ?!\n"); + return -EINVAL; + } + + if (bat->volt_value < 0 || bat->cur_value < 0 || + jiffies_to_msecs(jiffies - bat->timestamp) > + BAT_POLL_INTERVAL) { + bat->volt_value = s3c_adc_read(bat->client, + bat->pdata->volt_channel) * bat->pdata->volt_mult; + bat->cur_value = s3c_adc_read(bat->client, + bat->pdata->current_channel) * bat->pdata->current_mult; + bat->timestamp = jiffies; + } + + if (bat->cable_plugged && + ((bat->pdata->gpio_charge_finished < 0) || + !charge_finished(bat))) { + lut = bat->pdata->lut_acin; + lut_size = bat->pdata->lut_acin_cnt; + } + + new_level = 100000; + full_volt = calc_full_volt((bat->volt_value / 1000), + (bat->cur_value / 1000), bat->pdata->internal_impedance); + + if (full_volt < calc_full_volt(lut->volt, lut->cur, + bat->pdata->internal_impedance)) { + lut_size--; + while (lut_size--) { + int lut_volt1; + int lut_volt2; + + lut_volt1 = calc_full_volt(lut[0].volt, lut[0].cur, + bat->pdata->internal_impedance); + lut_volt2 = calc_full_volt(lut[1].volt, lut[1].cur, + bat->pdata->internal_impedance); + if (full_volt < lut_volt1 && full_volt >= lut_volt2) { + new_level = (lut[1].level + + (lut[0].level - lut[1].level) * + (full_volt - lut_volt2) / + (lut_volt1 - lut_volt2)) * 1000; + break; + } + new_level = lut[1].level * 1000; + lut++; + } + } + + bat->level = new_level; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (bat->pdata->gpio_charge_finished < 0) + val->intval = bat->level == 100000 ? + POWER_SUPPLY_STATUS_FULL : bat->status; + else + val->intval = bat->status; + return 0; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval = 100000; + return 0; + case POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN: + val->intval = 0; + return 0; + case POWER_SUPPLY_PROP_CHARGE_NOW: + val->intval = bat->level; + return 0; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = bat->volt_value; + return 0; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = bat->cur_value; + return 0; + default: + return -EINVAL; + } +} + +static struct s3c_adc_bat main_bat = { + .psy = { + .name = "main-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = s3c_adc_main_bat_props, + .num_properties = ARRAY_SIZE(s3c_adc_main_bat_props), + .get_property = s3c_adc_bat_get_property, + .external_power_changed = s3c_adc_bat_ext_power_changed, + .use_for_apm = 1, + }, +}; + +static void s3c_adc_bat_work(struct work_struct *work) +{ + struct s3c_adc_bat *bat = &main_bat; + int is_charged; + int is_plugged; + static int was_plugged; + + is_plugged = power_supply_am_i_supplied(&bat->psy); + bat->cable_plugged = is_plugged; + if (is_plugged != was_plugged) { + was_plugged = is_plugged; + if (is_plugged) { + if (bat->pdata->enable_charger) + bat->pdata->enable_charger(); + bat->status = POWER_SUPPLY_STATUS_CHARGING; + } else { + if (bat->pdata->disable_charger) + bat->pdata->disable_charger(); + bat->status = POWER_SUPPLY_STATUS_DISCHARGING; + } + } else { + if ((bat->pdata->gpio_charge_finished >= 0) && is_plugged) { + is_charged = charge_finished(&main_bat); + if (is_charged) { + if (bat->pdata->disable_charger) + bat->pdata->disable_charger(); + bat->status = POWER_SUPPLY_STATUS_FULL; + } else { + if (bat->pdata->enable_charger) + bat->pdata->enable_charger(); + bat->status = POWER_SUPPLY_STATUS_CHARGING; + } + } + } + + power_supply_changed(&bat->psy); +} + +static irqreturn_t s3c_adc_bat_charged(int irq, void *dev_id) +{ + schedule_delayed_work(&bat_work, + msecs_to_jiffies(JITTER_DELAY)); + return IRQ_HANDLED; +} + +static int __init s3c_adc_bat_probe(struct platform_device *pdev) +{ + struct s3c_adc_client *client; + struct s3c_adc_bat_pdata *pdata = pdev->dev.platform_data; + int ret; + + client = s3c_adc_register(pdev, NULL, NULL, 0); + if (IS_ERR(client)) { + dev_err(&pdev->dev, "cannot register adc\n"); + return PTR_ERR(client); + } + + platform_set_drvdata(pdev, client); + + main_bat.client = client; + main_bat.pdata = pdata; + main_bat.volt_value = -1; + main_bat.cur_value = -1; + main_bat.cable_plugged = 0; + main_bat.status = POWER_SUPPLY_STATUS_DISCHARGING; + + ret = power_supply_register(&pdev->dev, &main_bat.psy); + if (ret) + goto err_reg_main; + if (pdata->backup_volt_mult) { + backup_bat.client = client; + backup_bat.pdata = pdev->dev.platform_data; + backup_bat.volt_value = -1; + ret = power_supply_register(&pdev->dev, &backup_bat.psy); + if (ret) + goto err_reg_backup; + } + + INIT_DELAYED_WORK(&bat_work, s3c_adc_bat_work); + + if (pdata->gpio_charge_finished >= 0) { + ret = gpio_request(pdata->gpio_charge_finished, "charged"); + if (ret) + goto err_gpio; + + ret = request_irq(gpio_to_irq(pdata->gpio_charge_finished), + s3c_adc_bat_charged, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "battery charged", NULL); + if (ret) + goto err_irq; + } + + if (pdata->init) { + ret = pdata->init(); + if (ret) + goto err_platform; + } + + dev_info(&pdev->dev, "successfully loaded\n"); + device_init_wakeup(&pdev->dev, 1); + + /* Schedule timer to check current status */ + schedule_delayed_work(&bat_work, + msecs_to_jiffies(JITTER_DELAY)); + + return 0; + +err_platform: + if (pdata->gpio_charge_finished >= 0) + free_irq(gpio_to_irq(pdata->gpio_charge_finished), NULL); +err_irq: + if (pdata->gpio_charge_finished >= 0) + gpio_free(pdata->gpio_charge_finished); +err_gpio: + if (pdata->backup_volt_mult) + power_supply_unregister(&backup_bat.psy); +err_reg_backup: + power_supply_unregister(&main_bat.psy); +err_reg_main: + return ret; +} + +static int s3c_adc_bat_remove(struct platform_device *pdev) +{ + struct s3c_adc_client *client = platform_get_drvdata(pdev); + struct s3c_adc_bat_pdata *pdata = pdev->dev.platform_data; + + power_supply_unregister(&main_bat.psy); + if (pdata->backup_volt_mult) + power_supply_unregister(&backup_bat.psy); + + s3c_adc_release(client); + + if (pdata->gpio_charge_finished >= 0) { + free_irq(gpio_to_irq(pdata->gpio_charge_finished), NULL); + gpio_free(pdata->gpio_charge_finished); + } + + cancel_delayed_work(&bat_work); + + if (pdata->exit) + pdata->exit(); + + return 0; +} + +#ifdef CONFIG_PM +static int s3c_adc_bat_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct s3c_adc_bat_pdata *pdata = pdev->dev.platform_data; + + if (pdata->gpio_charge_finished >= 0) { + if (device_may_wakeup(&pdev->dev)) + enable_irq_wake( + gpio_to_irq(pdata->gpio_charge_finished)); + else { + disable_irq(gpio_to_irq(pdata->gpio_charge_finished)); + main_bat.pdata->disable_charger(); + } + } + + return 0; +} + +static int s3c_adc_bat_resume(struct platform_device *pdev) +{ + struct s3c_adc_bat_pdata *pdata = pdev->dev.platform_data; + + if (pdata->gpio_charge_finished >= 0) { + if (device_may_wakeup(&pdev->dev)) + disable_irq_wake( + gpio_to_irq(pdata->gpio_charge_finished)); + else + enable_irq(gpio_to_irq(pdata->gpio_charge_finished)); + } + + /* Schedule timer to check current status */ + schedule_delayed_work(&bat_work, + msecs_to_jiffies(JITTER_DELAY)); + + return 0; +} +#else +#define s3c_adc_bat_suspend NULL +#define s3c_adc_bat_resume NULL +#endif + +static struct platform_driver s3c_adc_bat_driver = { + .driver = { + .name = "s3c-adc-battery", + }, + .probe = s3c_adc_bat_probe, + .remove = s3c_adc_bat_remove, + .suspend = s3c_adc_bat_suspend, + .resume = s3c_adc_bat_resume, +}; + +static int __init s3c_adc_bat_init(void) +{ + return platform_driver_register(&s3c_adc_bat_driver); +} +module_init(s3c_adc_bat_init); + +static void __exit s3c_adc_bat_exit(void) +{ + platform_driver_unregister(&s3c_adc_bat_driver); +} +module_exit(s3c_adc_bat_exit); + +MODULE_AUTHOR("Vasily Khoruzhick "); +MODULE_DESCRIPTION("iPAQ H1930/H1940/RX1950 battery controller driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/sabresd_battery.c b/drivers/power/sabresd_battery.c new file mode 100755 index 00000000..64892213 --- /dev/null +++ b/drivers/power/sabresd_battery.c @@ -0,0 +1,985 @@ +/* + * sabresd_battery.c - Maxim 8903 USB/Adapter Charger Driver + * + * Copyright (C) 2011 Samsung Electronics + * Copyright (C) 2011-2012 Freescale Semiconductor, Inc. + * Based on max8903_charger.c + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include + + +#define BATTERY_UPDATE_INTERVAL 5 /*seconds*/ +#define LOW_VOLT_THRESHOLD 2800000 +#define HIGH_VOLT_THRESHOLD 4200000 +#define ADC_SAMPLE_COUNT 6 + +struct max8903_data { + struct max8903_pdata *pdata; + struct device *dev; + struct power_supply psy; + struct power_supply usb; + bool fault; + bool usb_in; + bool ta_in; + bool chg_state; + struct delayed_work work; + unsigned int interval; + unsigned short thermal_raw; + int voltage_uV; + int current_uA; + int battery_status; + int charger_online; + int charger_voltage_uV; + int real_capacity; + int percent; + int old_percent; + int usb_charger_online; + int first_delay_count; + struct power_supply bat; + struct power_supply detect_usb; + struct mutex work_lock; +}; + +typedef struct { + u32 voltage; + u32 percent; +} battery_capacity , *pbattery_capacity; +static int cpu_type_flag; +static int offset_discharger; +static int offset_charger; +static int offset_usb_charger; + +static battery_capacity chargingTable[] = { + {4050, 99}, + {4040, 98}, + {4020, 97}, + {4010, 96}, + {3990, 95}, + {3980, 94}, + {3970, 93}, + {3960, 92}, + {3950, 91}, + {3940, 90}, + {3930, 85}, + {3920, 81}, + {3910, 77}, + {3900, 73}, + {3890, 70}, + {3860, 65}, + {3830, 60}, + {3780, 55}, + {3760, 50}, + {3740, 45}, + {3720, 40}, + {3700, 35}, + {3680, 30}, + {3660, 25}, + {3640, 20}, + {3620, 17}, + {3600, 14}, + {3580, 13}, + {3560, 12}, + {3540, 11}, + {3520, 10}, + {3500, 9}, + {3480, 8}, + {3460, 7}, + {3440, 6}, + {3430, 5}, + {3420, 4}, + {3020, 0}, +}; +static battery_capacity dischargingTable[] = { + {4050, 100}, + {4035, 99}, + {4020, 98}, + {4010, 97}, + {4000, 96}, + {3990, 96}, + {3980, 95}, + {3970, 92}, + {3960, 91}, + {3950, 90}, + {3940, 88}, + {3930, 86}, + {3920, 84}, + {3910, 82}, + {3900, 80}, + {3890, 74}, + {3860, 69}, + {3830, 64}, + {3780, 59}, + {3760, 54}, + {3740, 49}, + {3720, 44}, + {3700, 39}, + {3680, 34}, + {3660, 29}, + {3640, 24}, + {3620, 19}, + {3600, 14}, + {3580, 13}, + {3560, 12}, + {3540, 11}, + {3520, 10}, + {3500, 9}, + {3480, 8}, + {3460, 7}, + {3440, 6}, + {3430, 5}, + {3420, 4}, + {3020, 0}, +}; + +u32 calibrate_battery_capability_percent(struct max8903_data *data) +{ + u8 i; + pbattery_capacity pTable; + u32 tableSize; + if (data->battery_status == POWER_SUPPLY_STATUS_DISCHARGING) { + pTable = dischargingTable; + tableSize = sizeof(dischargingTable)/sizeof(dischargingTable[0]); + } else { + pTable = chargingTable; + tableSize = sizeof(chargingTable)/sizeof(chargingTable[0]); + } + for (i = 0; i < tableSize; i++) { + if (data->voltage_uV >= pTable[i].voltage) + return pTable[i].percent; + } + + return 0; +} + +static enum power_supply_property max8903_charger_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static enum power_supply_property max8903_battery_props[] = { + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, +}; +#ifdef CONFIG_TOUCHSCREEN_MAX11801 +extern u32 max11801_read_adc(void); +#endif +static void max8903_charger_update_status(struct max8903_data *data) +{ + if (data->usb_in || data->ta_in) { + if (data->ta_in) + data->charger_online = 1; + + if (data->usb_in) + data->usb_charger_online = 1; + } else { + data->charger_online = 0; + data->usb_charger_online = 0; + } + if (data->charger_online == 0 && data->usb_charger_online == 0) { + data->battery_status = POWER_SUPPLY_STATUS_DISCHARGING; + } else { + if (gpio_get_value(data->pdata->chg) == 0) { + data->battery_status = POWER_SUPPLY_STATUS_CHARGING; + } else if (data->ta_in && gpio_get_value(data->pdata->chg) == 1) { + if (!data->pdata->feature_flag) { + if (data->percent >= 99) + data->battery_status = POWER_SUPPLY_STATUS_FULL; + else + data->battery_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + } else { + data->battery_status = POWER_SUPPLY_STATUS_FULL; + } + } else if (data->usb_in && gpio_get_value(data->pdata->chg) == 1) { + if (!data->pdata->feature_flag) { + if (data->percent >= 99) + data->battery_status = POWER_SUPPLY_STATUS_FULL; + else + data->battery_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + } else { + data->battery_status = POWER_SUPPLY_STATUS_FULL; + } + } + } + pr_debug("chg: %d\n", gpio_get_value(data->pdata->chg)); + pr_debug("dc: %d\n", gpio_get_value(data->pdata->dok)); + pr_debug("flt: %d\n", gpio_get_value(data->pdata->flt)); +} + +#ifdef CONFIG_TOUCHSCREEN_MAX11801 +static int cmp_func(const void *_a, const void *_b) +{ + const int *a = _a, *b = _b; + + if (*a > *b) + return 1; + if (*a < *b) + return -1; + return 0; +} + +u32 calibration_voltage(struct max8903_data *data) +{ + int volt[ADC_SAMPLE_COUNT]; + u32 voltage_data; + int i; + for (i = 0; i < ADC_SAMPLE_COUNT; i++) { + if (cpu_type_flag == 1) { + if (data->charger_online == 0 && data->usb_charger_online == 0) { + /* ADC offset when battery is discharger*/ + volt[i] = max11801_read_adc()-offset_discharger; + } else { + if (data->charger_online == 1) + volt[i] = max11801_read_adc()-offset_charger; + else if (data->usb_charger_online == 1) + volt[i] = max11801_read_adc()-offset_usb_charger; + else if (data->charger_online == 1 && data->usb_charger_online == 1) + volt[i] = max11801_read_adc()-offset_charger; + } + } + if (cpu_type_flag == 0) { + if (data->charger_online == 0 && data->usb_charger_online == 0) { + /* ADC offset when battery is discharger*/ + volt[i] = max11801_read_adc()-offset_discharger; + } else { + if (data->charger_online == 1) + volt[i] = max11801_read_adc()-offset_charger; + else if (data->usb_charger_online == 1) + volt[i] = max11801_read_adc()-offset_usb_charger; + else if (data->charger_online == 1 && data->usb_charger_online == 1) + volt[i] = max11801_read_adc()-offset_charger; + } + } + } + sort(volt, i, 4, cmp_func, NULL); + for (i = 0; i < ADC_SAMPLE_COUNT; i++) + pr_debug("volt_sorted[%2d]: %d\n", i, volt[i]); + /* get the average of second max/min of remained. */ + voltage_data = (volt[2] + volt[ADC_SAMPLE_COUNT - 3]) / 2; + return voltage_data; +} +#endif +static void max8903_battery_update_status(struct max8903_data *data) +{ + int temp = 0; + static int temp_last; + bool changed_flag; + changed_flag = false; + mutex_lock(&data->work_lock); + if (!data->pdata->feature_flag) { +#ifdef CONFIG_TOUCHSCREEN_MAX11801 + temp = calibration_voltage(data); +#endif + if (temp_last == 0) { + data->voltage_uV = temp; + temp_last = temp; + } + if (data->charger_online == 0 && temp_last != 0) { + if (temp < temp_last) { + temp_last = temp; + data->voltage_uV = temp; + } else { + data->voltage_uV = temp_last; + } + } + if (data->charger_online == 1 || data->usb_charger_online == 1) { + data->voltage_uV = temp; + temp_last = temp; + } + data->percent = calibrate_battery_capability_percent(data); + if (data->percent != data->old_percent) { + data->old_percent = data->percent; + changed_flag = true; + } + if (changed_flag) { + changed_flag = false; + power_supply_changed(&data->bat); + } + /* + *because boot time gap between led framwork and charger + *framwork,when system boots with charger attatched, + *charger led framwork loses the first charger online event, + *add once extra power_supply_changed can fix this issure + */ + if (data->first_delay_count < 200) { + data->first_delay_count = data->first_delay_count + 1 ; + power_supply_changed(&data->bat); + } + } + mutex_unlock(&data->work_lock); +} + +static int max8903_battery_get_property(struct power_supply *bat, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max8903_data *di = container_of(bat, + struct max8903_data, bat); + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + if (gpio_get_value(di->pdata->chg) == 0) { + di->battery_status = POWER_SUPPLY_STATUS_CHARGING; + } else if (di->ta_in && gpio_get_value(di->pdata->chg) == 1) { + if (!di->pdata->feature_flag) { + if (di->percent >= 99) + di->battery_status = POWER_SUPPLY_STATUS_FULL; + else + di->battery_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + } else { + di->battery_status = POWER_SUPPLY_STATUS_FULL; + } + } else if (di->usb_in && gpio_get_value(di->pdata->chg) == 1) { + if (!di->pdata->feature_flag) { + if (di->percent >= 99) + di->battery_status = POWER_SUPPLY_STATUS_FULL; + else + di->battery_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + } else { + di->battery_status = POWER_SUPPLY_STATUS_FULL; + } + } + val->intval = di->battery_status; + return 0; + default: + break; + } + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = di->voltage_uV; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + val->intval = 0; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = HIGH_VOLT_THRESHOLD; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = LOW_VOLT_THRESHOLD; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 1; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = di->percent < 0 ? 0 : + (di->percent > 100 ? 100 : di->percent); + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = POWER_SUPPLY_HEALTH_GOOD; + if (di->fault) + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + if (di->battery_status == POWER_SUPPLY_STATUS_FULL) + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_FULL; + else if (di->percent <= 15) + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_LOW; + else + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; + break; + default: + return -EINVAL; + } + + return 0; +} +static int max8903_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max8903_data *data = container_of(psy, + struct max8903_data, psy); + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = 0; + if (data->ta_in) + val->intval = 1; + data->charger_online = val->intval; + break; + default: + return -EINVAL; + } + return 0; +} +static int max8903_get_usb_property(struct power_supply *usb, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max8903_data *data = container_of(usb, + struct max8903_data, usb); + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = 0; + if (data->usb_in) + val->intval = 1; + data->usb_charger_online = val->intval; + break; + default: + return -EINVAL; + } + return 0; +} +static irqreturn_t max8903_dcin(int irq, void *_data) +{ + struct max8903_data *data = _data; + struct max8903_pdata *pdata = data->pdata; + bool ta_in; + + ta_in = gpio_get_value(pdata->dok) ? false : true; + + if (ta_in == data->ta_in) + return IRQ_HANDLED; + + data->ta_in = ta_in; + pr_info("TA(DC-IN) Charger %s.\n", ta_in ? + "Connected" : "Disconnected"); + max8903_charger_update_status(data); + max8903_battery_update_status(data); + power_supply_changed(&data->psy); + power_supply_changed(&data->bat); + return IRQ_HANDLED; +} +static irqreturn_t max8903_usbin(int irq, void *_data) +{ + struct max8903_data *data = _data; + struct max8903_pdata *pdata = data->pdata; + bool usb_in; + usb_in = gpio_get_value(pdata->uok) ? false : true; + if (usb_in == data->usb_in) + return IRQ_HANDLED; + + data->usb_in = usb_in; + max8903_charger_update_status(data); + max8903_battery_update_status(data); + pr_info("USB Charger %s.\n", usb_in ? + "Connected" : "Disconnected"); + power_supply_changed(&data->bat); + power_supply_changed(&data->usb); + return IRQ_HANDLED; +} + +static irqreturn_t max8903_fault(int irq, void *_data) +{ + struct max8903_data *data = _data; + struct max8903_pdata *pdata = data->pdata; + bool fault; + + fault = gpio_get_value(pdata->flt) ? false : true; + + if (fault == data->fault) + return IRQ_HANDLED; + + data->fault = fault; + + if (fault) + dev_err(data->dev, "Charger suffers a fault and stops.\n"); + else + dev_err(data->dev, "Charger recovered from a fault.\n"); + max8903_charger_update_status(data); + max8903_battery_update_status(data); + power_supply_changed(&data->psy); + power_supply_changed(&data->bat); + power_supply_changed(&data->usb); + return IRQ_HANDLED; +} + +static irqreturn_t max8903_chg(int irq, void *_data) +{ + struct max8903_data *data = _data; + struct max8903_pdata *pdata = data->pdata; + int chg_state; + + chg_state = gpio_get_value(pdata->chg) ? false : true; + + if (chg_state == data->chg_state) + return IRQ_HANDLED; + + data->chg_state = chg_state; + max8903_charger_update_status(data); + max8903_battery_update_status(data); + power_supply_changed(&data->psy); + power_supply_changed(&data->bat); + power_supply_changed(&data->usb); + return IRQ_HANDLED; +} + +static void max8903_battery_work(struct work_struct *work) +{ + struct max8903_data *data; + data = container_of(work, struct max8903_data, work.work); + data->interval = HZ * BATTERY_UPDATE_INTERVAL; + + max8903_charger_update_status(data); + max8903_battery_update_status(data); + pr_debug("battery voltage: %4d mV\n" , data->voltage_uV); + pr_debug("charger online status: %d\n" , data->charger_online); + pr_debug("battery status : %d\n" , data->battery_status); + pr_debug("battery capacity percent: %3d\n" , data->percent); + pr_debug("data->usb_in: %x , data->ta_in: %x \n" , data->usb_in, data->ta_in); + /* reschedule for the next time */ + schedule_delayed_work(&data->work, data->interval); +} + +static ssize_t max8903_voltage_offset_discharger_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "read offset_discharger:%04d\n", + offset_discharger); +} + +static ssize_t max8903_voltage_offset_discharger_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + offset_discharger = simple_strtoul(buf, NULL, 10); + pr_info("read offset_discharger:%04d\n", offset_discharger); + return count; +} + +static ssize_t max8903_voltage_offset_charger_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "read offset_charger:%04d\n", + offset_charger); +} + +static ssize_t max8903_voltage_offset_charger_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + offset_charger = simple_strtoul(buf, NULL, 10); + pr_info("read offset_charger:%04d\n", offset_charger); + return count; +} + +static ssize_t max8903_voltage_offset_usb_charger_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "read offset_usb_charger:%04d\n", + offset_usb_charger); +} + +static ssize_t max8903_voltage_offset_usb_charger_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + offset_usb_charger = simple_strtoul(buf, NULL, 10); + pr_info("read offset_charger:%04d\n", offset_usb_charger); + return count; +} + +static struct device_attribute max8903_discharger_dev_attr = { + .attr = { + .name = "max8903_ctl_offset_discharger", + .mode = S_IRUSR | S_IWUSR, + }, + .show = max8903_voltage_offset_discharger_show, + .store = max8903_voltage_offset_discharger_store, +}; + +static struct device_attribute max8903_charger_dev_attr = { + .attr = { + .name = "max8903_ctl_offset_charger", + .mode = S_IRUSR | S_IWUSR, + }, + .show = max8903_voltage_offset_charger_show, + .store = max8903_voltage_offset_charger_store, +}; + +static struct device_attribute max8903_usb_charger_dev_attr = { + .attr = { + .name = "max8903_ctl_offset_usb_charger", + .mode = S_IRUSR | S_IWUSR, + }, + .show = max8903_voltage_offset_usb_charger_show, + .store = max8903_voltage_offset_usb_charger_store, +}; + +static __devinit int max8903_probe(struct platform_device *pdev) +{ + struct max8903_data *data; + struct device *dev = &pdev->dev; + struct max8903_pdata *pdata = pdev->dev.platform_data; + int ret = 0; + int gpio = 0; + int ta_in = 0; + int usb_in = 0; + int retval; + cpu_type_flag = 0; + if (cpu_is_mx6q()) + cpu_type_flag = 1; + if (cpu_is_mx6dl()) + cpu_type_flag = 0; + data = kzalloc(sizeof(struct max8903_data), GFP_KERNEL); + if (data == NULL) { + dev_err(dev, "Cannot allocate memory.\n"); + return -ENOMEM; + } + data->first_delay_count = 0; + data->pdata = pdata; + data->dev = dev; + platform_set_drvdata(pdev, data); + data->usb_in = 0; + data->ta_in = 0; + if (pdata->dc_valid == false && pdata->usb_valid == false) { + dev_err(dev, "No valid power sources.\n"); + printk(KERN_INFO "No valid power sources.\n"); + ret = -EINVAL; + goto err; + } + if (pdata->dc_valid) { + if (pdata->dok && gpio_is_valid(pdata->dok)) { + gpio = pdata->dok; /* PULL_UPed Interrupt */ + /* set DOK gpio input */ + ret = gpio_request(gpio, "max8903-DOK"); + if (ret) { + printk(KERN_ERR"request max8903-DOK error!!\n"); + goto err; + } else { + gpio_direction_input(gpio); + } + ta_in = gpio_get_value(gpio) ? 0 : 1; + } else if (pdata->dok && gpio_is_valid(pdata->dok) && pdata->dcm_always_high) { + ta_in = pdata->dok; /* PULL_UPed Interrupt */ + ta_in = gpio_get_value(gpio) ? 0 : 1; + } else { + dev_err(dev, "When DC is wired, DOK and DCM should" + " be wired as well." + " or set dcm always high\n"); + ret = -EINVAL; + goto err; + } + } + if (pdata->usb_valid) { + if (pdata->uok && gpio_is_valid(pdata->uok)) { + gpio = pdata->uok; + /* set UOK gpio input */ + ret = gpio_request(gpio, "max8903-UOK"); + if (ret) { + printk(KERN_ERR"request max8903-UOK error!!\n"); + goto err; + } else { + gpio_direction_input(gpio); + } + usb_in = gpio_get_value(gpio) ? 0 : 1; + } else { + dev_err(dev, "When USB is wired, UOK should be wired." + "as well.\n"); + ret = -EINVAL; + goto err; + } + } + if (pdata->chg) { + if (!gpio_is_valid(pdata->chg)) { + dev_err(dev, "Invalid pin: chg.\n"); + ret = -EINVAL; + goto err; + } + /* set CHG gpio input */ + ret = gpio_request(pdata->chg, "max8903-CHG"); + if (ret) { + printk(KERN_ERR"request max8903-CHG error!!\n"); + goto err; + } else { + gpio_direction_input(pdata->chg); + } + } + if (pdata->flt) { + if (!gpio_is_valid(pdata->flt)) { + dev_err(dev, "Invalid pin: flt.\n"); + ret = -EINVAL; + goto err; + } + /* set FLT gpio input */ + ret = gpio_request(pdata->flt, "max8903-FLT"); + if (ret) { + printk(KERN_ERR"request max8903-FLT error!!\n"); + goto err; + } else { + gpio_direction_input(pdata->flt); + } + } + if (pdata->usus) { + if (!gpio_is_valid(pdata->usus)) { + dev_err(dev, "Invalid pin: usus.\n"); + ret = -EINVAL; + goto err; + } + } + mutex_init(&data->work_lock); + data->fault = false; + data->ta_in = ta_in; + data->usb_in = usb_in; + data->psy.name = "max8903-ac"; + data->psy.type = POWER_SUPPLY_TYPE_MAINS; + data->psy.get_property = max8903_get_property; + data->psy.properties = max8903_charger_props; + data->psy.num_properties = ARRAY_SIZE(max8903_charger_props); + ret = power_supply_register(dev, &data->psy); + if (ret) { + dev_err(dev, "failed: power supply register.\n"); + goto err_psy; + } + data->usb.name = "max8903-usb"; + data->usb.type = POWER_SUPPLY_TYPE_USB; + data->usb.get_property = max8903_get_usb_property; + data->usb.properties = max8903_charger_props; + data->usb.num_properties = ARRAY_SIZE(max8903_charger_props); + ret = power_supply_register(dev, &data->usb); + if (ret) { + dev_err(dev, "failed: power supply register.\n"); + goto err_psy; + } + data->bat.name = "max8903-charger"; + data->bat.type = POWER_SUPPLY_TYPE_BATTERY; + data->bat.properties = max8903_battery_props; + data->bat.num_properties = ARRAY_SIZE(max8903_battery_props); + data->bat.get_property = max8903_battery_get_property; + data->bat.use_for_apm = 1; + retval = power_supply_register(&pdev->dev, &data->bat); + if (retval) { + dev_err(data->dev, "failed to register battery\n"); + goto battery_failed; + } + INIT_DELAYED_WORK(&data->work, max8903_battery_work); + schedule_delayed_work(&data->work, data->interval); + if (pdata->dc_valid) { + ret = request_threaded_irq(gpio_to_irq(pdata->dok), + NULL, max8903_dcin, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, + "MAX8903 DC IN", data); + if (ret) { + dev_err(dev, "Cannot request irq %d for DC (%d)\n", + gpio_to_irq(pdata->dok), ret); + goto err_usb_irq; + } + } + + if (pdata->usb_valid) { + ret = request_threaded_irq(gpio_to_irq(pdata->uok), + NULL, max8903_usbin, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, + "MAX8903 USB IN", data); + if (ret) { + dev_err(dev, "Cannot request irq %d for USB (%d)\n", + gpio_to_irq(pdata->uok), ret); + goto err_dc_irq; + } + } + + if (pdata->flt) { + ret = request_threaded_irq(gpio_to_irq(pdata->flt), + NULL, max8903_fault, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, + "MAX8903 Fault", data); + if (ret) { + dev_err(dev, "Cannot request irq %d for Fault (%d)\n", + gpio_to_irq(pdata->flt), ret); + goto err_flt_irq; + } + } + + if (pdata->chg) { + ret = request_threaded_irq(gpio_to_irq(pdata->chg), + NULL, max8903_chg, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, + "MAX8903 Status", data); + if (ret) { + dev_err(dev, "Cannot request irq %d for Status (%d)\n", + gpio_to_irq(pdata->flt), ret); + goto err_chg_irq; + } + } + ret = device_create_file(&pdev->dev, &max8903_discharger_dev_attr); + if (ret) + dev_err(&pdev->dev, "create device file failed!\n"); + ret = device_create_file(&pdev->dev, &max8903_charger_dev_attr); + if (ret) + dev_err(&pdev->dev, "create device file failed!\n"); + ret = device_create_file(&pdev->dev, &max8903_usb_charger_dev_attr); + if (ret) + dev_err(&pdev->dev, "create device file failed!\n"); + if (cpu_type_flag == 1) { + offset_discharger = 1694; + offset_charger = 1900; + offset_usb_charger = 1685; + } + if (cpu_type_flag == 0) { + offset_discharger = 1464; + offset_charger = 1485; + offset_usb_charger = 1285; + } + max8903_charger_update_status(data); + max8903_battery_update_status(data); + return 0; +err_psy: + power_supply_unregister(&data->psy); +battery_failed: + power_supply_unregister(&data->bat); +err_usb_irq: + if (pdata->usb_valid) + free_irq(gpio_to_irq(pdata->uok), data); + cancel_delayed_work(&data->work); +err_dc_irq: + if (pdata->dc_valid) + free_irq(gpio_to_irq(pdata->dok), data); + cancel_delayed_work(&data->work); +err_flt_irq: + if (pdata->usb_valid) + free_irq(gpio_to_irq(pdata->uok), data); + cancel_delayed_work(&data->work); +err_chg_irq: + if (pdata->dc_valid) + free_irq(gpio_to_irq(pdata->dok), data); + cancel_delayed_work(&data->work); +err: + if (pdata->uok) + gpio_free(pdata->uok); + if (pdata->dok) + gpio_free(pdata->dok); + if (pdata->flt) + gpio_free(pdata->flt); + if (pdata->chg) + gpio_free(pdata->chg); + kfree(data); + return ret; +} + +static __devexit int max8903_remove(struct platform_device *pdev) +{ + struct max8903_data *data = platform_get_drvdata(pdev); + if (data) { + struct max8903_pdata *pdata = data->pdata; + if (pdata->flt) + free_irq(gpio_to_irq(pdata->flt), data); + if (pdata->usb_valid) + free_irq(gpio_to_irq(pdata->uok), data); + if (pdata->dc_valid) + free_irq(gpio_to_irq(pdata->dok), data); + if (pdata->dc_valid) + free_irq(gpio_to_irq(pdata->chg), data); + cancel_delayed_work_sync(&data->work); + power_supply_unregister(&data->psy); + power_supply_unregister(&data->bat); + kfree(data); + } + return 0; +} + +static int max8903_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct max8903_data *data = platform_get_drvdata(pdev); + int irq; + if (data) { + struct max8903_pdata *pdata = data->pdata; + if (pdata) { + if (pdata->dc_valid) { + irq = gpio_to_irq(pdata->dok); + enable_irq_wake(irq); + } + if (pdata->usb_valid) { + irq = gpio_to_irq(pdata->uok); + enable_irq_wake(irq); + } + cancel_delayed_work(&data->work); + } + } + return 0; +} + +static int max8903_resume(struct platform_device *pdev) +{ + struct max8903_data *data = platform_get_drvdata(pdev); + bool ta_in; + bool usb_in; + int irq; + if (data) { + struct max8903_pdata *pdata = data->pdata; + if (pdata) { + ta_in = gpio_get_value(pdata->dok) ? false : true; + usb_in = gpio_get_value(pdata->uok) ? false : true; + if (ta_in != data->ta_in) { + data->ta_in = ta_in; + pr_info("TA(DC-IN) Charger %s.\n", ta_in ? + "Connected" : "Disconnected"); + max8903_charger_update_status(data); + power_supply_changed(&data->psy); + } + if (usb_in != data->usb_in) { + data->usb_in = usb_in; + pr_info("USB Charger %s.\n", usb_in ? + "Connected" : "Disconnected"); + max8903_charger_update_status(data); + power_supply_changed(&data->usb); + } + if (pdata->dc_valid) { + irq = gpio_to_irq(pdata->dok); + disable_irq_wake(irq); + } + if (pdata->usb_valid) { + irq = gpio_to_irq(pdata->uok); + disable_irq_wake(irq); + } + schedule_delayed_work(&data->work, BATTERY_UPDATE_INTERVAL); + } + } + return 0; + +} + +static struct platform_driver max8903_driver = { + .probe = max8903_probe, + .remove = __devexit_p(max8903_remove), + .suspend = max8903_suspend, + .resume = max8903_resume, + .driver = { + .name = "max8903-charger", + .owner = THIS_MODULE, + }, +}; + +static int __init max8903_init(void) +{ + return platform_driver_register(&max8903_driver); +} +module_init(max8903_init); + +static void __exit max8903_exit(void) +{ + platform_driver_unregister(&max8903_driver); +} +module_exit(max8903_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Sabresd Battery Driver"); +MODULE_ALIAS("sabresd_battery"); diff --git a/drivers/power/test_power.c b/drivers/power/test_power.c new file mode 100644 index 00000000..b527c93b --- /dev/null +++ b/drivers/power/test_power.c @@ -0,0 +1,419 @@ +/* + * Power supply driver for testing. + * + * Copyright 2010 Anton Vorontsov + * + * Dynamic module parameter code from the Virtual Battery Driver + * Copyright (C) 2008 Pylone, Inc. + * By: Masashi YOKOTA + * Originally found here: + * http://downloads.pylone.jp/src/virtual_battery/virtual_battery-0.0.1.tar.bz2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include + +static int ac_online = 1; +static int battery_status = POWER_SUPPLY_STATUS_DISCHARGING; +static int battery_health = POWER_SUPPLY_HEALTH_GOOD; +static int battery_present = 1; /* true */ +static int battery_technology = POWER_SUPPLY_TECHNOLOGY_LION; +static int battery_capacity = 50; + +static int test_power_get_ac_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = ac_online; + break; + default: + return -EINVAL; + } + return 0; +} + +static int test_power_get_battery_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + switch (psp) { + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = "Test battery"; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = "Linux"; + break; + case POWER_SUPPLY_PROP_SERIAL_NUMBER: + val->strval = UTS_RELEASE; + break; + case POWER_SUPPLY_PROP_STATUS: + val->intval = battery_status; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = battery_health; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = battery_present; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = battery_technology; + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; + break; + case POWER_SUPPLY_PROP_CAPACITY: + case POWER_SUPPLY_PROP_CHARGE_NOW: + val->intval = battery_capacity; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + case POWER_SUPPLY_PROP_CHARGE_FULL: + val->intval = 100; + break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: + case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW: + val->intval = 3600; + break; + default: + pr_info("%s: some properties deliberately report errors.\n", + __func__); + return -EINVAL; + } + return 0; +} + +static enum power_supply_property test_power_ac_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static enum power_supply_property test_power_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, + POWER_SUPPLY_PROP_TIME_TO_FULL_NOW, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_SERIAL_NUMBER, +}; + +static char *test_power_ac_supplied_to[] = { + "test_battery", +}; + +static struct power_supply test_power_supplies[] = { + { + .name = "test_ac", + .type = POWER_SUPPLY_TYPE_MAINS, + .supplied_to = test_power_ac_supplied_to, + .num_supplicants = ARRAY_SIZE(test_power_ac_supplied_to), + .properties = test_power_ac_props, + .num_properties = ARRAY_SIZE(test_power_ac_props), + .get_property = test_power_get_ac_property, + }, { + .name = "test_battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = test_power_battery_props, + .num_properties = ARRAY_SIZE(test_power_battery_props), + .get_property = test_power_get_battery_property, + }, +}; + + +static int __init test_power_init(void) +{ + int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(test_power_supplies); i++) { + ret = power_supply_register(NULL, &test_power_supplies[i]); + if (ret) { + pr_err("%s: failed to register %s\n", __func__, + test_power_supplies[i].name); + goto failed; + } + } + + return 0; +failed: + while (--i >= 0) + power_supply_unregister(&test_power_supplies[i]); + return ret; +} +module_init(test_power_init); + +static void __exit test_power_exit(void) +{ + int i; + + /* Let's see how we handle changes... */ + ac_online = 0; + battery_status = POWER_SUPPLY_STATUS_DISCHARGING; + for (i = 0; i < ARRAY_SIZE(test_power_supplies); i++) + power_supply_changed(&test_power_supplies[i]); + pr_info("%s: 'changed' event sent, sleeping for 10 seconds...\n", + __func__); + ssleep(10); + + for (i = 0; i < ARRAY_SIZE(test_power_supplies); i++) + power_supply_unregister(&test_power_supplies[i]); +} +module_exit(test_power_exit); + + + +#define MAX_KEYLENGTH 256 +struct battery_property_map { + int value; + char const *key; +}; + +static struct battery_property_map map_ac_online[] = { + { 0, "on" }, + { 1, "off" }, + { -1, NULL }, +}; + +static struct battery_property_map map_status[] = { + { POWER_SUPPLY_STATUS_CHARGING, "charging" }, + { POWER_SUPPLY_STATUS_DISCHARGING, "discharging" }, + { POWER_SUPPLY_STATUS_NOT_CHARGING, "not-charging" }, + { POWER_SUPPLY_STATUS_FULL, "full" }, + { -1, NULL }, +}; + +static struct battery_property_map map_health[] = { + { POWER_SUPPLY_HEALTH_GOOD, "good" }, + { POWER_SUPPLY_HEALTH_OVERHEAT, "overheat" }, + { POWER_SUPPLY_HEALTH_DEAD, "dead" }, + { POWER_SUPPLY_HEALTH_OVERVOLTAGE, "overvoltage" }, + { POWER_SUPPLY_HEALTH_UNSPEC_FAILURE, "failure" }, + { -1, NULL }, +}; + +static struct battery_property_map map_present[] = { + { 0, "false" }, + { 1, "true" }, + { -1, NULL }, +}; + +static struct battery_property_map map_technology[] = { + { POWER_SUPPLY_TECHNOLOGY_NiMH, "NiMH" }, + { POWER_SUPPLY_TECHNOLOGY_LION, "LION" }, + { POWER_SUPPLY_TECHNOLOGY_LIPO, "LIPO" }, + { POWER_SUPPLY_TECHNOLOGY_LiFe, "LiFe" }, + { POWER_SUPPLY_TECHNOLOGY_NiCd, "NiCd" }, + { POWER_SUPPLY_TECHNOLOGY_LiMn, "LiMn" }, + { -1, NULL }, +}; + + +static int map_get_value(struct battery_property_map *map, const char *key, + int def_val) +{ + char buf[MAX_KEYLENGTH]; + int cr; + + strncpy(buf, key, MAX_KEYLENGTH); + buf[MAX_KEYLENGTH-1] = '\0'; + + cr = strnlen(buf, MAX_KEYLENGTH) - 1; + if (buf[cr] == '\n') + buf[cr] = '\0'; + + while (map->key) { + if (strncasecmp(map->key, buf, MAX_KEYLENGTH) == 0) + return map->value; + map++; + } + + return def_val; +} + + +static const char *map_get_key(struct battery_property_map *map, int value, + const char *def_key) +{ + while (map->key) { + if (map->value == value) + return map->key; + map++; + } + + return def_key; +} + +static int param_set_ac_online(const char *key, const struct kernel_param *kp) +{ + ac_online = map_get_value(map_ac_online, key, ac_online); + power_supply_changed(&test_power_supplies[0]); + return 0; +} + +static int param_get_ac_online(char *buffer, const struct kernel_param *kp) +{ + strcpy(buffer, map_get_key(map_ac_online, ac_online, "unknown")); + return strlen(buffer); +} + +static int param_set_battery_status(const char *key, + const struct kernel_param *kp) +{ + battery_status = map_get_value(map_status, key, battery_status); + power_supply_changed(&test_power_supplies[1]); + return 0; +} + +static int param_get_battery_status(char *buffer, const struct kernel_param *kp) +{ + strcpy(buffer, map_get_key(map_status, battery_status, "unknown")); + return strlen(buffer); +} + +static int param_set_battery_health(const char *key, + const struct kernel_param *kp) +{ + battery_health = map_get_value(map_health, key, battery_health); + power_supply_changed(&test_power_supplies[1]); + return 0; +} + +static int param_get_battery_health(char *buffer, const struct kernel_param *kp) +{ + strcpy(buffer, map_get_key(map_health, battery_health, "unknown")); + return strlen(buffer); +} + +static int param_set_battery_present(const char *key, + const struct kernel_param *kp) +{ + battery_present = map_get_value(map_present, key, battery_present); + power_supply_changed(&test_power_supplies[0]); + return 0; +} + +static int param_get_battery_present(char *buffer, + const struct kernel_param *kp) +{ + strcpy(buffer, map_get_key(map_present, battery_present, "unknown")); + return strlen(buffer); +} + +static int param_set_battery_technology(const char *key, + const struct kernel_param *kp) +{ + battery_technology = map_get_value(map_technology, key, + battery_technology); + power_supply_changed(&test_power_supplies[1]); + return 0; +} + +static int param_get_battery_technology(char *buffer, + const struct kernel_param *kp) +{ + strcpy(buffer, + map_get_key(map_technology, battery_technology, "unknown")); + return strlen(buffer); +} + +static int param_set_battery_capacity(const char *key, + const struct kernel_param *kp) +{ + int tmp; + + if (1 != sscanf(key, "%d", &tmp)) + return -EINVAL; + + battery_capacity = tmp; + power_supply_changed(&test_power_supplies[1]); + return 0; +} + +#define param_get_battery_capacity param_get_int + + + +static struct kernel_param_ops param_ops_ac_online = { + .set = param_set_ac_online, + .get = param_get_ac_online, +}; + +static struct kernel_param_ops param_ops_battery_status = { + .set = param_set_battery_status, + .get = param_get_battery_status, +}; + +static struct kernel_param_ops param_ops_battery_present = { + .set = param_set_battery_present, + .get = param_get_battery_present, +}; + +static struct kernel_param_ops param_ops_battery_technology = { + .set = param_set_battery_technology, + .get = param_get_battery_technology, +}; + +static struct kernel_param_ops param_ops_battery_health = { + .set = param_set_battery_health, + .get = param_get_battery_health, +}; + +static struct kernel_param_ops param_ops_battery_capacity = { + .set = param_set_battery_capacity, + .get = param_get_battery_capacity, +}; + + +#define param_check_ac_online(name, p) __param_check(name, p, void); +#define param_check_battery_status(name, p) __param_check(name, p, void); +#define param_check_battery_present(name, p) __param_check(name, p, void); +#define param_check_battery_technology(name, p) __param_check(name, p, void); +#define param_check_battery_health(name, p) __param_check(name, p, void); +#define param_check_battery_capacity(name, p) __param_check(name, p, void); + + +module_param(ac_online, ac_online, 0644); +MODULE_PARM_DESC(ac_online, "AC charging state "); + +module_param(battery_status, battery_status, 0644); +MODULE_PARM_DESC(battery_status, + "battery status "); + +module_param(battery_present, battery_present, 0644); +MODULE_PARM_DESC(battery_present, + "battery presence state "); + +module_param(battery_technology, battery_technology, 0644); +MODULE_PARM_DESC(battery_technology, + "battery technology "); + +module_param(battery_health, battery_health, 0644); +MODULE_PARM_DESC(battery_health, + "battery health state "); + +module_param(battery_capacity, battery_capacity, 0644); +MODULE_PARM_DESC(battery_capacity, "battery capacity (percentage)"); + + +MODULE_DESCRIPTION("Power supply driver for testing"); +MODULE_AUTHOR("Anton Vorontsov "); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/tosa_battery.c b/drivers/power/tosa_battery.c new file mode 100644 index 00000000..53f0d352 --- /dev/null +++ b/drivers/power/tosa_battery.c @@ -0,0 +1,485 @@ +/* + * Battery and Power Management code for the Sharp SL-6000x + * + * Copyright (c) 2005 Dirk Opfer + * Copyright (c) 2008 Dmitry Baryshkov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +static DEFINE_MUTEX(bat_lock); /* protects gpio pins */ +static struct work_struct bat_work; + +struct tosa_bat { + int status; + struct power_supply psy; + int full_chrg; + + struct mutex work_lock; /* protects data */ + + bool (*is_present)(struct tosa_bat *bat); + int gpio_full; + int gpio_charge_off; + + int technology; + + int gpio_bat; + int adc_bat; + int adc_bat_divider; + int bat_max; + int bat_min; + + int gpio_temp; + int adc_temp; + int adc_temp_divider; +}; + +static struct tosa_bat tosa_bat_main; +static struct tosa_bat tosa_bat_jacket; + +static unsigned long tosa_read_bat(struct tosa_bat *bat) +{ + unsigned long value = 0; + + if (bat->gpio_bat < 0 || bat->adc_bat < 0) + return 0; + + mutex_lock(&bat_lock); + gpio_set_value(bat->gpio_bat, 1); + msleep(5); + value = wm97xx_read_aux_adc(dev_get_drvdata(bat->psy.dev->parent), + bat->adc_bat); + gpio_set_value(bat->gpio_bat, 0); + mutex_unlock(&bat_lock); + + value = value * 1000000 / bat->adc_bat_divider; + + return value; +} + +static unsigned long tosa_read_temp(struct tosa_bat *bat) +{ + unsigned long value = 0; + + if (bat->gpio_temp < 0 || bat->adc_temp < 0) + return 0; + + mutex_lock(&bat_lock); + gpio_set_value(bat->gpio_temp, 1); + msleep(5); + value = wm97xx_read_aux_adc(dev_get_drvdata(bat->psy.dev->parent), + bat->adc_temp); + gpio_set_value(bat->gpio_temp, 0); + mutex_unlock(&bat_lock); + + value = value * 10000 / bat->adc_temp_divider; + + return value; +} + +static int tosa_bat_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret = 0; + struct tosa_bat *bat = container_of(psy, struct tosa_bat, psy); + + if (bat->is_present && !bat->is_present(bat) + && psp != POWER_SUPPLY_PROP_PRESENT) { + return -ENODEV; + } + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = bat->status; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = bat->technology; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = tosa_read_bat(bat); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + if (bat->full_chrg == -1) + val->intval = bat->bat_max; + else + val->intval = bat->full_chrg; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = bat->bat_max; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = bat->bat_min; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = tosa_read_temp(bat); + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = bat->is_present ? bat->is_present(bat) : 1; + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static bool tosa_jacket_bat_is_present(struct tosa_bat *bat) +{ + return gpio_get_value(TOSA_GPIO_JACKET_DETECT) == 0; +} + +static void tosa_bat_external_power_changed(struct power_supply *psy) +{ + schedule_work(&bat_work); +} + +static irqreturn_t tosa_bat_gpio_isr(int irq, void *data) +{ + pr_info("tosa_bat_gpio irq: %d\n", gpio_get_value(irq_to_gpio(irq))); + schedule_work(&bat_work); + return IRQ_HANDLED; +} + +static void tosa_bat_update(struct tosa_bat *bat) +{ + int old; + struct power_supply *psy = &bat->psy; + + mutex_lock(&bat->work_lock); + + old = bat->status; + + if (bat->is_present && !bat->is_present(bat)) { + printk(KERN_NOTICE "%s not present\n", psy->name); + bat->status = POWER_SUPPLY_STATUS_UNKNOWN; + bat->full_chrg = -1; + } else if (power_supply_am_i_supplied(psy)) { + if (bat->status == POWER_SUPPLY_STATUS_DISCHARGING) { + gpio_set_value(bat->gpio_charge_off, 0); + mdelay(15); + } + + if (gpio_get_value(bat->gpio_full)) { + if (old == POWER_SUPPLY_STATUS_CHARGING || + bat->full_chrg == -1) + bat->full_chrg = tosa_read_bat(bat); + + gpio_set_value(bat->gpio_charge_off, 1); + bat->status = POWER_SUPPLY_STATUS_FULL; + } else { + gpio_set_value(bat->gpio_charge_off, 0); + bat->status = POWER_SUPPLY_STATUS_CHARGING; + } + } else { + gpio_set_value(bat->gpio_charge_off, 1); + bat->status = POWER_SUPPLY_STATUS_DISCHARGING; + } + + if (old != bat->status) + power_supply_changed(psy); + + mutex_unlock(&bat->work_lock); +} + +static void tosa_bat_work(struct work_struct *work) +{ + tosa_bat_update(&tosa_bat_main); + tosa_bat_update(&tosa_bat_jacket); +} + + +static enum power_supply_property tosa_bat_main_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_PRESENT, +}; + +static enum power_supply_property tosa_bat_bu_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_PRESENT, +}; + +static struct tosa_bat tosa_bat_main = { + .status = POWER_SUPPLY_STATUS_DISCHARGING, + .full_chrg = -1, + .psy = { + .name = "main-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = tosa_bat_main_props, + .num_properties = ARRAY_SIZE(tosa_bat_main_props), + .get_property = tosa_bat_get_property, + .external_power_changed = tosa_bat_external_power_changed, + .use_for_apm = 1, + }, + + .gpio_full = TOSA_GPIO_BAT0_CRG, + .gpio_charge_off = TOSA_GPIO_CHARGE_OFF, + + .technology = POWER_SUPPLY_TECHNOLOGY_LIPO, + + .gpio_bat = TOSA_GPIO_BAT0_V_ON, + .adc_bat = WM97XX_AUX_ID3, + .adc_bat_divider = 414, + .bat_max = 4310000, + .bat_min = 1551 * 1000000 / 414, + + .gpio_temp = TOSA_GPIO_BAT1_TH_ON, + .adc_temp = WM97XX_AUX_ID2, + .adc_temp_divider = 10000, +}; + +static struct tosa_bat tosa_bat_jacket = { + .status = POWER_SUPPLY_STATUS_DISCHARGING, + .full_chrg = -1, + .psy = { + .name = "jacket-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = tosa_bat_main_props, + .num_properties = ARRAY_SIZE(tosa_bat_main_props), + .get_property = tosa_bat_get_property, + .external_power_changed = tosa_bat_external_power_changed, + }, + + .is_present = tosa_jacket_bat_is_present, + .gpio_full = TOSA_GPIO_BAT1_CRG, + .gpio_charge_off = TOSA_GPIO_CHARGE_OFF_JC, + + .technology = POWER_SUPPLY_TECHNOLOGY_LIPO, + + .gpio_bat = TOSA_GPIO_BAT1_V_ON, + .adc_bat = WM97XX_AUX_ID3, + .adc_bat_divider = 414, + .bat_max = 4310000, + .bat_min = 1551 * 1000000 / 414, + + .gpio_temp = TOSA_GPIO_BAT0_TH_ON, + .adc_temp = WM97XX_AUX_ID2, + .adc_temp_divider = 10000, +}; + +static struct tosa_bat tosa_bat_bu = { + .status = POWER_SUPPLY_STATUS_UNKNOWN, + .full_chrg = -1, + + .psy = { + .name = "backup-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = tosa_bat_bu_props, + .num_properties = ARRAY_SIZE(tosa_bat_bu_props), + .get_property = tosa_bat_get_property, + .external_power_changed = tosa_bat_external_power_changed, + }, + + .gpio_full = -1, + .gpio_charge_off = -1, + + .technology = POWER_SUPPLY_TECHNOLOGY_LiMn, + + .gpio_bat = TOSA_GPIO_BU_CHRG_ON, + .adc_bat = WM97XX_AUX_ID4, + .adc_bat_divider = 1266, + + .gpio_temp = -1, + .adc_temp = -1, + .adc_temp_divider = -1, +}; + +static struct { + int gpio; + char *name; + bool output; + int value; +} gpios[] = { + { TOSA_GPIO_CHARGE_OFF, "main charge off", 1, 1 }, + { TOSA_GPIO_CHARGE_OFF_JC, "jacket charge off", 1, 1 }, + { TOSA_GPIO_BAT_SW_ON, "battery switch", 1, 0 }, + { TOSA_GPIO_BAT0_V_ON, "main battery", 1, 0 }, + { TOSA_GPIO_BAT1_V_ON, "jacket battery", 1, 0 }, + { TOSA_GPIO_BAT1_TH_ON, "main battery temp", 1, 0 }, + { TOSA_GPIO_BAT0_TH_ON, "jacket battery temp", 1, 0 }, + { TOSA_GPIO_BU_CHRG_ON, "backup battery", 1, 0 }, + { TOSA_GPIO_BAT0_CRG, "main battery full", 0, 0 }, + { TOSA_GPIO_BAT1_CRG, "jacket battery full", 0, 0 }, + { TOSA_GPIO_BAT0_LOW, "main battery low", 0, 0 }, + { TOSA_GPIO_BAT1_LOW, "jacket battery low", 0, 0 }, + { TOSA_GPIO_JACKET_DETECT, "jacket detect", 0, 0 }, +}; + +#ifdef CONFIG_PM +static int tosa_bat_suspend(struct platform_device *dev, pm_message_t state) +{ + /* flush all pending status updates */ + flush_work_sync(&bat_work); + return 0; +} + +static int tosa_bat_resume(struct platform_device *dev) +{ + /* things may have changed while we were away */ + schedule_work(&bat_work); + return 0; +} +#else +#define tosa_bat_suspend NULL +#define tosa_bat_resume NULL +#endif + +static int __devinit tosa_bat_probe(struct platform_device *dev) +{ + int ret; + int i; + + if (!machine_is_tosa()) + return -ENODEV; + + for (i = 0; i < ARRAY_SIZE(gpios); i++) { + ret = gpio_request(gpios[i].gpio, gpios[i].name); + if (ret) { + i--; + goto err_gpio; + } + + if (gpios[i].output) + ret = gpio_direction_output(gpios[i].gpio, + gpios[i].value); + else + ret = gpio_direction_input(gpios[i].gpio); + + if (ret) + goto err_gpio; + } + + mutex_init(&tosa_bat_main.work_lock); + mutex_init(&tosa_bat_jacket.work_lock); + + INIT_WORK(&bat_work, tosa_bat_work); + + ret = power_supply_register(&dev->dev, &tosa_bat_main.psy); + if (ret) + goto err_psy_reg_main; + ret = power_supply_register(&dev->dev, &tosa_bat_jacket.psy); + if (ret) + goto err_psy_reg_jacket; + ret = power_supply_register(&dev->dev, &tosa_bat_bu.psy); + if (ret) + goto err_psy_reg_bu; + + ret = request_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG), + tosa_bat_gpio_isr, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "main full", &tosa_bat_main); + if (ret) + goto err_req_main; + + ret = request_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG), + tosa_bat_gpio_isr, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "jacket full", &tosa_bat_jacket); + if (ret) + goto err_req_jacket; + + ret = request_irq(gpio_to_irq(TOSA_GPIO_JACKET_DETECT), + tosa_bat_gpio_isr, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "jacket detect", &tosa_bat_jacket); + if (!ret) { + schedule_work(&bat_work); + return 0; + } + + free_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG), &tosa_bat_jacket); +err_req_jacket: + free_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG), &tosa_bat_main); +err_req_main: + power_supply_unregister(&tosa_bat_bu.psy); +err_psy_reg_bu: + power_supply_unregister(&tosa_bat_jacket.psy); +err_psy_reg_jacket: + power_supply_unregister(&tosa_bat_main.psy); +err_psy_reg_main: + + /* see comment in tosa_bat_remove */ + cancel_work_sync(&bat_work); + + i--; +err_gpio: + for (; i >= 0; i--) + gpio_free(gpios[i].gpio); + + return ret; +} + +static int __devexit tosa_bat_remove(struct platform_device *dev) +{ + int i; + + free_irq(gpio_to_irq(TOSA_GPIO_JACKET_DETECT), &tosa_bat_jacket); + free_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG), &tosa_bat_jacket); + free_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG), &tosa_bat_main); + + power_supply_unregister(&tosa_bat_bu.psy); + power_supply_unregister(&tosa_bat_jacket.psy); + power_supply_unregister(&tosa_bat_main.psy); + + /* + * Now cancel the bat_work. We won't get any more schedules, + * since all sources (isr and external_power_changed) are + * unregistered now. + */ + cancel_work_sync(&bat_work); + + for (i = ARRAY_SIZE(gpios) - 1; i >= 0; i--) + gpio_free(gpios[i].gpio); + + return 0; +} + +static struct platform_driver tosa_bat_driver = { + .driver.name = "wm97xx-battery", + .driver.owner = THIS_MODULE, + .probe = tosa_bat_probe, + .remove = __devexit_p(tosa_bat_remove), + .suspend = tosa_bat_suspend, + .resume = tosa_bat_resume, +}; + +static int __init tosa_bat_init(void) +{ + return platform_driver_register(&tosa_bat_driver); +} + +static void __exit tosa_bat_exit(void) +{ + platform_driver_unregister(&tosa_bat_driver); +} + +module_init(tosa_bat_init); +module_exit(tosa_bat_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Dmitry Baryshkov"); +MODULE_DESCRIPTION("Tosa battery driver"); +MODULE_ALIAS("platform:wm97xx-battery"); diff --git a/drivers/power/twl4030_charger.c b/drivers/power/twl4030_charger.c new file mode 100644 index 00000000..92c16e16 --- /dev/null +++ b/drivers/power/twl4030_charger.c @@ -0,0 +1,578 @@ +/* + * TWL4030/TPS65950 BCI (Battery Charger Interface) driver + * + * Copyright (C) 2010 Gražvydas Ignotas + * + * based on twl4030_bci_battery.c by TI + * Copyright (C) 2008 Texas Instruments, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TWL4030_BCIMSTATEC 0x02 +#define TWL4030_BCIICHG 0x08 +#define TWL4030_BCIVAC 0x0a +#define TWL4030_BCIVBUS 0x0c +#define TWL4030_BCIMFSTS4 0x10 +#define TWL4030_BCICTL1 0x23 + +#define TWL4030_BCIAUTOWEN BIT(5) +#define TWL4030_CONFIG_DONE BIT(4) +#define TWL4030_BCIAUTOUSB BIT(1) +#define TWL4030_BCIAUTOAC BIT(0) +#define TWL4030_CGAIN BIT(5) +#define TWL4030_USBFASTMCHG BIT(2) +#define TWL4030_STS_VBUS BIT(7) +#define TWL4030_STS_USB_ID BIT(2) + +/* BCI interrupts */ +#define TWL4030_WOVF BIT(0) /* Watchdog overflow */ +#define TWL4030_TMOVF BIT(1) /* Timer overflow */ +#define TWL4030_ICHGHIGH BIT(2) /* Battery charge current high */ +#define TWL4030_ICHGLOW BIT(3) /* Battery cc. low / FSM state change */ +#define TWL4030_ICHGEOC BIT(4) /* Battery current end-of-charge */ +#define TWL4030_TBATOR2 BIT(5) /* Battery temperature out of range 2 */ +#define TWL4030_TBATOR1 BIT(6) /* Battery temperature out of range 1 */ +#define TWL4030_BATSTS BIT(7) /* Battery status */ + +#define TWL4030_VBATLVL BIT(0) /* VBAT level */ +#define TWL4030_VBATOV BIT(1) /* VBAT overvoltage */ +#define TWL4030_VBUSOV BIT(2) /* VBUS overvoltage */ +#define TWL4030_ACCHGOV BIT(3) /* Ac charger overvoltage */ + +#define TWL4030_MSTATEC_USB BIT(4) +#define TWL4030_MSTATEC_AC BIT(5) +#define TWL4030_MSTATEC_MASK 0x0f +#define TWL4030_MSTATEC_QUICK1 0x02 +#define TWL4030_MSTATEC_QUICK7 0x07 +#define TWL4030_MSTATEC_COMPLETE1 0x0b +#define TWL4030_MSTATEC_COMPLETE4 0x0e + +static bool allow_usb; +module_param(allow_usb, bool, 1); +MODULE_PARM_DESC(allow_usb, "Allow USB charge drawing default current"); + +struct twl4030_bci { + struct device *dev; + struct power_supply ac; + struct power_supply usb; + struct otg_transceiver *transceiver; + struct notifier_block otg_nb; + struct work_struct work; + int irq_chg; + int irq_bci; + + unsigned long event; +}; + +/* + * clear and set bits on an given register on a given module + */ +static int twl4030_clear_set(u8 mod_no, u8 clear, u8 set, u8 reg) +{ + u8 val = 0; + int ret; + + ret = twl_i2c_read_u8(mod_no, &val, reg); + if (ret) + return ret; + + val &= ~clear; + val |= set; + + return twl_i2c_write_u8(mod_no, val, reg); +} + +static int twl4030_bci_read(u8 reg, u8 *val) +{ + return twl_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, val, reg); +} + +static int twl4030_clear_set_boot_bci(u8 clear, u8 set) +{ + return twl4030_clear_set(TWL4030_MODULE_PM_MASTER, 0, + TWL4030_CONFIG_DONE | TWL4030_BCIAUTOWEN | set, + TWL4030_PM_MASTER_BOOT_BCI); +} + +static int twl4030bci_read_adc_val(u8 reg) +{ + int ret, temp; + u8 val; + + /* read MSB */ + ret = twl4030_bci_read(reg + 1, &val); + if (ret) + return ret; + + temp = (int)(val & 0x03) << 8; + + /* read LSB */ + ret = twl4030_bci_read(reg, &val); + if (ret) + return ret; + + return temp | val; +} + +/* + * Check if VBUS power is present + */ +static int twl4030_bci_have_vbus(struct twl4030_bci *bci) +{ + int ret; + u8 hwsts; + + ret = twl_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &hwsts, + TWL4030_PM_MASTER_STS_HW_CONDITIONS); + if (ret < 0) + return 0; + + dev_dbg(bci->dev, "check_vbus: HW_CONDITIONS %02x\n", hwsts); + + /* in case we also have STS_USB_ID, VBUS is driven by TWL itself */ + if ((hwsts & TWL4030_STS_VBUS) && !(hwsts & TWL4030_STS_USB_ID)) + return 1; + + return 0; +} + +/* + * Enable/Disable USB Charge funtionality. + */ +static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable) +{ + int ret; + + if (enable) { + /* Check for USB charger conneted */ + if (!twl4030_bci_have_vbus(bci)) + return -ENODEV; + + /* + * Until we can find out what current the device can provide, + * require a module param to enable USB charging. + */ + if (!allow_usb) { + dev_warn(bci->dev, "USB charging is disabled.\n"); + return -EACCES; + } + + /* forcing the field BCIAUTOUSB (BOOT_BCI[1]) to 1 */ + ret = twl4030_clear_set_boot_bci(0, TWL4030_BCIAUTOUSB); + if (ret < 0) + return ret; + + /* forcing USBFASTMCHG(BCIMFSTS4[2]) to 1 */ + ret = twl4030_clear_set(TWL4030_MODULE_MAIN_CHARGE, 0, + TWL4030_USBFASTMCHG, TWL4030_BCIMFSTS4); + } else { + ret = twl4030_clear_set_boot_bci(TWL4030_BCIAUTOUSB, 0); + } + + return ret; +} + +/* + * Enable/Disable AC Charge funtionality. + */ +static int twl4030_charger_enable_ac(bool enable) +{ + int ret; + + if (enable) + ret = twl4030_clear_set_boot_bci(0, TWL4030_BCIAUTOAC); + else + ret = twl4030_clear_set_boot_bci(TWL4030_BCIAUTOAC, 0); + + return ret; +} + +/* + * TWL4030 CHG_PRES (AC charger presence) events + */ +static irqreturn_t twl4030_charger_interrupt(int irq, void *arg) +{ + struct twl4030_bci *bci = arg; + + dev_dbg(bci->dev, "CHG_PRES irq\n"); + power_supply_changed(&bci->ac); + power_supply_changed(&bci->usb); + + return IRQ_HANDLED; +} + +/* + * TWL4030 BCI monitoring events + */ +static irqreturn_t twl4030_bci_interrupt(int irq, void *arg) +{ + struct twl4030_bci *bci = arg; + u8 irqs1, irqs2; + int ret; + + ret = twl_i2c_read_u8(TWL4030_MODULE_INTERRUPTS, &irqs1, + TWL4030_INTERRUPTS_BCIISR1A); + if (ret < 0) + return IRQ_HANDLED; + + ret = twl_i2c_read_u8(TWL4030_MODULE_INTERRUPTS, &irqs2, + TWL4030_INTERRUPTS_BCIISR2A); + if (ret < 0) + return IRQ_HANDLED; + + dev_dbg(bci->dev, "BCI irq %02x %02x\n", irqs2, irqs1); + + if (irqs1 & (TWL4030_ICHGLOW | TWL4030_ICHGEOC)) { + /* charger state change, inform the core */ + power_supply_changed(&bci->ac); + power_supply_changed(&bci->usb); + } + + /* various monitoring events, for now we just log them here */ + if (irqs1 & (TWL4030_TBATOR2 | TWL4030_TBATOR1)) + dev_warn(bci->dev, "battery temperature out of range\n"); + + if (irqs1 & TWL4030_BATSTS) + dev_crit(bci->dev, "battery disconnected\n"); + + if (irqs2 & TWL4030_VBATOV) + dev_crit(bci->dev, "VBAT overvoltage\n"); + + if (irqs2 & TWL4030_VBUSOV) + dev_crit(bci->dev, "VBUS overvoltage\n"); + + if (irqs2 & TWL4030_ACCHGOV) + dev_crit(bci->dev, "Ac charger overvoltage\n"); + + return IRQ_HANDLED; +} + +static void twl4030_bci_usb_work(struct work_struct *data) +{ + struct twl4030_bci *bci = container_of(data, struct twl4030_bci, work); + + switch (bci->event) { + case USB_EVENT_VBUS: + case USB_EVENT_CHARGER: + twl4030_charger_enable_usb(bci, true); + break; + case USB_EVENT_NONE: + twl4030_charger_enable_usb(bci, false); + break; + } +} + +static int twl4030_bci_usb_ncb(struct notifier_block *nb, unsigned long val, + void *priv) +{ + struct twl4030_bci *bci = container_of(nb, struct twl4030_bci, otg_nb); + + dev_dbg(bci->dev, "OTG notify %lu\n", val); + + bci->event = val; + schedule_work(&bci->work); + + return NOTIFY_OK; +} + +/* + * TI provided formulas: + * CGAIN == 0: ICHG = (BCIICHG * 1.7) / (2^10 - 1) - 0.85 + * CGAIN == 1: ICHG = (BCIICHG * 3.4) / (2^10 - 1) - 1.7 + * Here we use integer approximation of: + * CGAIN == 0: val * 1.6618 - 0.85 + * CGAIN == 1: (val * 1.6618 - 0.85) * 2 + */ +static int twl4030_charger_get_current(void) +{ + int curr; + int ret; + u8 bcictl1; + + curr = twl4030bci_read_adc_val(TWL4030_BCIICHG); + if (curr < 0) + return curr; + + ret = twl4030_bci_read(TWL4030_BCICTL1, &bcictl1); + if (ret) + return ret; + + ret = (curr * 16618 - 850 * 10000) / 10; + if (bcictl1 & TWL4030_CGAIN) + ret *= 2; + + return ret; +} + +/* + * Returns the main charge FSM state + * Or < 0 on failure. + */ +static int twl4030bci_state(struct twl4030_bci *bci) +{ + int ret; + u8 state; + + ret = twl4030_bci_read(TWL4030_BCIMSTATEC, &state); + if (ret) { + pr_err("twl4030_bci: error reading BCIMSTATEC\n"); + return ret; + } + + dev_dbg(bci->dev, "state: %02x\n", state); + + return state; +} + +static int twl4030_bci_state_to_status(int state) +{ + state &= TWL4030_MSTATEC_MASK; + if (TWL4030_MSTATEC_QUICK1 <= state && state <= TWL4030_MSTATEC_QUICK7) + return POWER_SUPPLY_STATUS_CHARGING; + else if (TWL4030_MSTATEC_COMPLETE1 <= state && + state <= TWL4030_MSTATEC_COMPLETE4) + return POWER_SUPPLY_STATUS_FULL; + else + return POWER_SUPPLY_STATUS_NOT_CHARGING; +} + +static int twl4030_bci_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct twl4030_bci *bci = dev_get_drvdata(psy->dev->parent); + int is_charging; + int state; + int ret; + + state = twl4030bci_state(bci); + if (state < 0) + return state; + + if (psy->type == POWER_SUPPLY_TYPE_USB) + is_charging = state & TWL4030_MSTATEC_USB; + else + is_charging = state & TWL4030_MSTATEC_AC; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (is_charging) + val->intval = twl4030_bci_state_to_status(state); + else + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + /* charging must be active for meaningful result */ + if (!is_charging) + return -ENODATA; + if (psy->type == POWER_SUPPLY_TYPE_USB) { + ret = twl4030bci_read_adc_val(TWL4030_BCIVBUS); + if (ret < 0) + return ret; + /* BCIVBUS uses ADCIN8, 7/1023 V/step */ + val->intval = ret * 6843; + } else { + ret = twl4030bci_read_adc_val(TWL4030_BCIVAC); + if (ret < 0) + return ret; + /* BCIVAC uses ADCIN11, 10/1023 V/step */ + val->intval = ret * 9775; + } + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + if (!is_charging) + return -ENODATA; + /* current measurement is shared between AC and USB */ + ret = twl4030_charger_get_current(); + if (ret < 0) + return ret; + val->intval = ret; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = is_charging && + twl4030_bci_state_to_status(state) != + POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + default: + return -EINVAL; + } + + return 0; +} + +static enum power_supply_property twl4030_charger_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, +}; + +static int __init twl4030_bci_probe(struct platform_device *pdev) +{ + struct twl4030_bci *bci; + int ret; + int reg; + + bci = kzalloc(sizeof(*bci), GFP_KERNEL); + if (bci == NULL) + return -ENOMEM; + + bci->dev = &pdev->dev; + bci->irq_chg = platform_get_irq(pdev, 0); + bci->irq_bci = platform_get_irq(pdev, 1); + + platform_set_drvdata(pdev, bci); + + bci->ac.name = "twl4030_ac"; + bci->ac.type = POWER_SUPPLY_TYPE_MAINS; + bci->ac.properties = twl4030_charger_props; + bci->ac.num_properties = ARRAY_SIZE(twl4030_charger_props); + bci->ac.get_property = twl4030_bci_get_property; + + ret = power_supply_register(&pdev->dev, &bci->ac); + if (ret) { + dev_err(&pdev->dev, "failed to register ac: %d\n", ret); + goto fail_register_ac; + } + + bci->usb.name = "twl4030_usb"; + bci->usb.type = POWER_SUPPLY_TYPE_USB; + bci->usb.properties = twl4030_charger_props; + bci->usb.num_properties = ARRAY_SIZE(twl4030_charger_props); + bci->usb.get_property = twl4030_bci_get_property; + + ret = power_supply_register(&pdev->dev, &bci->usb); + if (ret) { + dev_err(&pdev->dev, "failed to register usb: %d\n", ret); + goto fail_register_usb; + } + + ret = request_threaded_irq(bci->irq_chg, NULL, + twl4030_charger_interrupt, 0, pdev->name, bci); + if (ret < 0) { + dev_err(&pdev->dev, "could not request irq %d, status %d\n", + bci->irq_chg, ret); + goto fail_chg_irq; + } + + ret = request_threaded_irq(bci->irq_bci, NULL, + twl4030_bci_interrupt, 0, pdev->name, bci); + if (ret < 0) { + dev_err(&pdev->dev, "could not request irq %d, status %d\n", + bci->irq_bci, ret); + goto fail_bci_irq; + } + + INIT_WORK(&bci->work, twl4030_bci_usb_work); + + bci->transceiver = otg_get_transceiver(); + if (bci->transceiver != NULL) { + bci->otg_nb.notifier_call = twl4030_bci_usb_ncb; + otg_register_notifier(bci->transceiver, &bci->otg_nb); + } + + /* Enable interrupts now. */ + reg = ~(TWL4030_ICHGLOW | TWL4030_ICHGEOC | TWL4030_TBATOR2 | + TWL4030_TBATOR1 | TWL4030_BATSTS); + ret = twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, reg, + TWL4030_INTERRUPTS_BCIIMR1A); + if (ret < 0) { + dev_err(&pdev->dev, "failed to unmask interrupts: %d\n", ret); + goto fail_unmask_interrupts; + } + + reg = ~(TWL4030_VBATOV | TWL4030_VBUSOV | TWL4030_ACCHGOV); + ret = twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, reg, + TWL4030_INTERRUPTS_BCIIMR2A); + if (ret < 0) + dev_warn(&pdev->dev, "failed to unmask interrupts: %d\n", ret); + + twl4030_charger_enable_ac(true); + twl4030_charger_enable_usb(bci, true); + + return 0; + +fail_unmask_interrupts: + if (bci->transceiver != NULL) { + otg_unregister_notifier(bci->transceiver, &bci->otg_nb); + otg_put_transceiver(bci->transceiver); + } + free_irq(bci->irq_bci, bci); +fail_bci_irq: + free_irq(bci->irq_chg, bci); +fail_chg_irq: + power_supply_unregister(&bci->usb); +fail_register_usb: + power_supply_unregister(&bci->ac); +fail_register_ac: + platform_set_drvdata(pdev, NULL); + kfree(bci); + + return ret; +} + +static int __exit twl4030_bci_remove(struct platform_device *pdev) +{ + struct twl4030_bci *bci = platform_get_drvdata(pdev); + + twl4030_charger_enable_ac(false); + twl4030_charger_enable_usb(bci, false); + + /* mask interrupts */ + twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, 0xff, + TWL4030_INTERRUPTS_BCIIMR1A); + twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, 0xff, + TWL4030_INTERRUPTS_BCIIMR2A); + + if (bci->transceiver != NULL) { + otg_unregister_notifier(bci->transceiver, &bci->otg_nb); + otg_put_transceiver(bci->transceiver); + } + free_irq(bci->irq_bci, bci); + free_irq(bci->irq_chg, bci); + power_supply_unregister(&bci->usb); + power_supply_unregister(&bci->ac); + platform_set_drvdata(pdev, NULL); + kfree(bci); + + return 0; +} + +static struct platform_driver twl4030_bci_driver = { + .driver = { + .name = "twl4030_bci", + .owner = THIS_MODULE, + }, + .remove = __exit_p(twl4030_bci_remove), +}; + +static int __init twl4030_bci_init(void) +{ + return platform_driver_probe(&twl4030_bci_driver, twl4030_bci_probe); +} +module_init(twl4030_bci_init); + +static void __exit twl4030_bci_exit(void) +{ + platform_driver_unregister(&twl4030_bci_driver); +} +module_exit(twl4030_bci_exit); + +MODULE_AUTHOR("Gražydas Ignotas"); +MODULE_DESCRIPTION("TWL4030 Battery Charger Interface driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:twl4030_bci"); diff --git a/drivers/power/wm831x_backup.c b/drivers/power/wm831x_backup.c new file mode 100644 index 00000000..0fd130d8 --- /dev/null +++ b/drivers/power/wm831x_backup.c @@ -0,0 +1,234 @@ +/* + * Backup battery driver for Wolfson Microelectronics wm831x PMICs + * + * Copyright 2009 Wolfson Microelectronics PLC. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +struct wm831x_backup { + struct wm831x *wm831x; + struct power_supply backup; +}; + +static int wm831x_backup_read_voltage(struct wm831x *wm831x, + enum wm831x_auxadc src, + union power_supply_propval *val) +{ + int ret; + + ret = wm831x_auxadc_read_uv(wm831x, src); + if (ret >= 0) + val->intval = ret; + + return ret; +} + +/********************************************************************* + * Backup supply properties + *********************************************************************/ + +static void wm831x_config_backup(struct wm831x *wm831x) +{ + struct wm831x_pdata *wm831x_pdata = wm831x->dev->platform_data; + struct wm831x_backup_pdata *pdata; + int ret, reg; + + if (!wm831x_pdata || !wm831x_pdata->backup) { + dev_warn(wm831x->dev, + "No backup battery charger configuration\n"); + return; + } + + pdata = wm831x_pdata->backup; + + reg = 0; + + if (pdata->charger_enable) + reg |= WM831X_BKUP_CHG_ENA | WM831X_BKUP_BATT_DET_ENA; + if (pdata->no_constant_voltage) + reg |= WM831X_BKUP_CHG_MODE; + + switch (pdata->vlim) { + case 2500: + break; + case 3100: + reg |= WM831X_BKUP_CHG_VLIM; + break; + default: + dev_err(wm831x->dev, "Invalid backup voltage limit %dmV\n", + pdata->vlim); + } + + switch (pdata->ilim) { + case 100: + break; + case 200: + reg |= 1; + break; + case 300: + reg |= 2; + break; + case 400: + reg |= 3; + break; + default: + dev_err(wm831x->dev, "Invalid backup current limit %duA\n", + pdata->ilim); + } + + ret = wm831x_reg_unlock(wm831x); + if (ret != 0) { + dev_err(wm831x->dev, "Failed to unlock registers: %d\n", ret); + return; + } + + ret = wm831x_set_bits(wm831x, WM831X_BACKUP_CHARGER_CONTROL, + WM831X_BKUP_CHG_ENA_MASK | + WM831X_BKUP_CHG_MODE_MASK | + WM831X_BKUP_BATT_DET_ENA_MASK | + WM831X_BKUP_CHG_VLIM_MASK | + WM831X_BKUP_CHG_ILIM_MASK, + reg); + if (ret != 0) + dev_err(wm831x->dev, + "Failed to set backup charger config: %d\n", ret); + + wm831x_reg_lock(wm831x); +} + +static int wm831x_backup_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct wm831x_backup *devdata = dev_get_drvdata(psy->dev->parent); + struct wm831x *wm831x = devdata->wm831x; + int ret = 0; + + ret = wm831x_reg_read(wm831x, WM831X_BACKUP_CHARGER_CONTROL); + if (ret < 0) + return ret; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (ret & WM831X_BKUP_CHG_STS) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = wm831x_backup_read_voltage(wm831x, WM831X_AUX_BKUP_BATT, + val); + break; + + case POWER_SUPPLY_PROP_PRESENT: + if (ret & WM831X_BKUP_CHG_STS) + val->intval = 1; + else + val->intval = 0; + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static enum power_supply_property wm831x_backup_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_PRESENT, +}; + +/********************************************************************* + * Initialisation + *********************************************************************/ + +static __devinit int wm831x_backup_probe(struct platform_device *pdev) +{ + struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); + struct wm831x_backup *devdata; + struct power_supply *backup; + int ret; + + devdata = kzalloc(sizeof(struct wm831x_backup), GFP_KERNEL); + if (devdata == NULL) + return -ENOMEM; + + devdata->wm831x = wm831x; + platform_set_drvdata(pdev, devdata); + + backup = &devdata->backup; + + /* We ignore configuration failures since we can still read + * back the status without enabling the charger (which may + * already be enabled anyway). + */ + wm831x_config_backup(wm831x); + + backup->name = "wm831x-backup"; + backup->type = POWER_SUPPLY_TYPE_BATTERY; + backup->properties = wm831x_backup_props; + backup->num_properties = ARRAY_SIZE(wm831x_backup_props); + backup->get_property = wm831x_backup_get_prop; + ret = power_supply_register(&pdev->dev, backup); + if (ret) + goto err_kmalloc; + + return ret; + +err_kmalloc: + kfree(devdata); + return ret; +} + +static __devexit int wm831x_backup_remove(struct platform_device *pdev) +{ + struct wm831x_backup *devdata = platform_get_drvdata(pdev); + + power_supply_unregister(&devdata->backup); + kfree(devdata); + + return 0; +} + +static struct platform_driver wm831x_backup_driver = { + .probe = wm831x_backup_probe, + .remove = __devexit_p(wm831x_backup_remove), + .driver = { + .name = "wm831x-backup", + }, +}; + +static int __init wm831x_backup_init(void) +{ + return platform_driver_register(&wm831x_backup_driver); +} +module_init(wm831x_backup_init); + +static void __exit wm831x_backup_exit(void) +{ + platform_driver_unregister(&wm831x_backup_driver); +} +module_exit(wm831x_backup_exit); + +MODULE_DESCRIPTION("Backup battery charger driver for WM831x PMICs"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm831x-backup"); diff --git a/drivers/power/wm831x_power.c b/drivers/power/wm831x_power.c new file mode 100644 index 00000000..ddf8cf5f --- /dev/null +++ b/drivers/power/wm831x_power.c @@ -0,0 +1,641 @@ +/* + * PMU driver for Wolfson Microelectronics wm831x PMICs + * + * Copyright 2009 Wolfson Microelectronics PLC. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +struct wm831x_power { + struct wm831x *wm831x; + struct power_supply wall; + struct power_supply usb; + struct power_supply battery; +}; + +static int wm831x_power_check_online(struct wm831x *wm831x, int supply, + union power_supply_propval *val) +{ + int ret; + + ret = wm831x_reg_read(wm831x, WM831X_SYSTEM_STATUS); + if (ret < 0) + return ret; + + if (ret & supply) + val->intval = 1; + else + val->intval = 0; + + return 0; +} + +static int wm831x_power_read_voltage(struct wm831x *wm831x, + enum wm831x_auxadc src, + union power_supply_propval *val) +{ + int ret; + + ret = wm831x_auxadc_read_uv(wm831x, src); + if (ret >= 0) + val->intval = ret; + + return ret; +} + +/********************************************************************* + * WALL Power + *********************************************************************/ +static int wm831x_wall_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct wm831x_power *wm831x_power = dev_get_drvdata(psy->dev->parent); + struct wm831x *wm831x = wm831x_power->wm831x; + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + ret = wm831x_power_check_online(wm831x, WM831X_PWR_WALL, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = wm831x_power_read_voltage(wm831x, WM831X_AUX_WALL, val); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static enum power_supply_property wm831x_wall_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + +/********************************************************************* + * USB Power + *********************************************************************/ +static int wm831x_usb_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct wm831x_power *wm831x_power = dev_get_drvdata(psy->dev->parent); + struct wm831x *wm831x = wm831x_power->wm831x; + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + ret = wm831x_power_check_online(wm831x, WM831X_PWR_USB, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = wm831x_power_read_voltage(wm831x, WM831X_AUX_USB, val); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static enum power_supply_property wm831x_usb_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + +/********************************************************************* + * Battery properties + *********************************************************************/ + +struct chg_map { + int val; + int reg_val; +}; + +static struct chg_map trickle_ilims[] = { + { 50, 0 << WM831X_CHG_TRKL_ILIM_SHIFT }, + { 100, 1 << WM831X_CHG_TRKL_ILIM_SHIFT }, + { 150, 2 << WM831X_CHG_TRKL_ILIM_SHIFT }, + { 200, 3 << WM831X_CHG_TRKL_ILIM_SHIFT }, +}; + +static struct chg_map vsels[] = { + { 4050, 0 << WM831X_CHG_VSEL_SHIFT }, + { 4100, 1 << WM831X_CHG_VSEL_SHIFT }, + { 4150, 2 << WM831X_CHG_VSEL_SHIFT }, + { 4200, 3 << WM831X_CHG_VSEL_SHIFT }, +}; + +static struct chg_map fast_ilims[] = { + { 0, 0 << WM831X_CHG_FAST_ILIM_SHIFT }, + { 50, 1 << WM831X_CHG_FAST_ILIM_SHIFT }, + { 100, 2 << WM831X_CHG_FAST_ILIM_SHIFT }, + { 150, 3 << WM831X_CHG_FAST_ILIM_SHIFT }, + { 200, 4 << WM831X_CHG_FAST_ILIM_SHIFT }, + { 250, 5 << WM831X_CHG_FAST_ILIM_SHIFT }, + { 300, 6 << WM831X_CHG_FAST_ILIM_SHIFT }, + { 350, 7 << WM831X_CHG_FAST_ILIM_SHIFT }, + { 400, 8 << WM831X_CHG_FAST_ILIM_SHIFT }, + { 450, 9 << WM831X_CHG_FAST_ILIM_SHIFT }, + { 500, 10 << WM831X_CHG_FAST_ILIM_SHIFT }, + { 600, 11 << WM831X_CHG_FAST_ILIM_SHIFT }, + { 700, 12 << WM831X_CHG_FAST_ILIM_SHIFT }, + { 800, 13 << WM831X_CHG_FAST_ILIM_SHIFT }, + { 900, 14 << WM831X_CHG_FAST_ILIM_SHIFT }, + { 1000, 15 << WM831X_CHG_FAST_ILIM_SHIFT }, +}; + +static struct chg_map eoc_iterms[] = { + { 20, 0 << WM831X_CHG_ITERM_SHIFT }, + { 30, 1 << WM831X_CHG_ITERM_SHIFT }, + { 40, 2 << WM831X_CHG_ITERM_SHIFT }, + { 50, 3 << WM831X_CHG_ITERM_SHIFT }, + { 60, 4 << WM831X_CHG_ITERM_SHIFT }, + { 70, 5 << WM831X_CHG_ITERM_SHIFT }, + { 80, 6 << WM831X_CHG_ITERM_SHIFT }, + { 90, 7 << WM831X_CHG_ITERM_SHIFT }, +}; + +static struct chg_map chg_times[] = { + { 60, 0 << WM831X_CHG_TIME_SHIFT }, + { 90, 1 << WM831X_CHG_TIME_SHIFT }, + { 120, 2 << WM831X_CHG_TIME_SHIFT }, + { 150, 3 << WM831X_CHG_TIME_SHIFT }, + { 180, 4 << WM831X_CHG_TIME_SHIFT }, + { 210, 5 << WM831X_CHG_TIME_SHIFT }, + { 240, 6 << WM831X_CHG_TIME_SHIFT }, + { 270, 7 << WM831X_CHG_TIME_SHIFT }, + { 300, 8 << WM831X_CHG_TIME_SHIFT }, + { 330, 9 << WM831X_CHG_TIME_SHIFT }, + { 360, 10 << WM831X_CHG_TIME_SHIFT }, + { 390, 11 << WM831X_CHG_TIME_SHIFT }, + { 420, 12 << WM831X_CHG_TIME_SHIFT }, + { 450, 13 << WM831X_CHG_TIME_SHIFT }, + { 480, 14 << WM831X_CHG_TIME_SHIFT }, + { 510, 15 << WM831X_CHG_TIME_SHIFT }, +}; + +static void wm831x_battey_apply_config(struct wm831x *wm831x, + struct chg_map *map, int count, int val, + int *reg, const char *name, + const char *units) +{ + int i; + + for (i = 0; i < count; i++) + if (val == map[i].val) + break; + if (i == count) { + dev_err(wm831x->dev, "Invalid %s %d%s\n", + name, val, units); + } else { + *reg |= map[i].reg_val; + dev_dbg(wm831x->dev, "Set %s of %d%s\n", name, val, units); + } +} + +static void wm831x_config_battery(struct wm831x *wm831x) +{ + struct wm831x_pdata *wm831x_pdata = wm831x->dev->platform_data; + struct wm831x_battery_pdata *pdata; + int ret, reg1, reg2; + + if (!wm831x_pdata || !wm831x_pdata->battery) { + dev_warn(wm831x->dev, + "No battery charger configuration\n"); + return; + } + + pdata = wm831x_pdata->battery; + + reg1 = 0; + reg2 = 0; + + if (!pdata->enable) { + dev_info(wm831x->dev, "Battery charger disabled\n"); + return; + } + + reg1 |= WM831X_CHG_ENA; + if (pdata->off_mask) + reg2 |= WM831X_CHG_OFF_MSK; + if (pdata->fast_enable) + reg1 |= WM831X_CHG_FAST; + + wm831x_battey_apply_config(wm831x, trickle_ilims, + ARRAY_SIZE(trickle_ilims), + pdata->trickle_ilim, ®2, + "trickle charge current limit", "mA"); + + wm831x_battey_apply_config(wm831x, vsels, ARRAY_SIZE(vsels), + pdata->vsel, ®2, + "target voltage", "mV"); + + wm831x_battey_apply_config(wm831x, fast_ilims, ARRAY_SIZE(fast_ilims), + pdata->fast_ilim, ®2, + "fast charge current limit", "mA"); + + wm831x_battey_apply_config(wm831x, eoc_iterms, ARRAY_SIZE(eoc_iterms), + pdata->eoc_iterm, ®1, + "end of charge current threshold", "mA"); + + wm831x_battey_apply_config(wm831x, chg_times, ARRAY_SIZE(chg_times), + pdata->timeout, ®2, + "charger timeout", "min"); + + ret = wm831x_reg_unlock(wm831x); + if (ret != 0) { + dev_err(wm831x->dev, "Failed to unlock registers: %d\n", ret); + return; + } + + ret = wm831x_set_bits(wm831x, WM831X_CHARGER_CONTROL_1, + WM831X_CHG_ENA_MASK | + WM831X_CHG_FAST_MASK | + WM831X_CHG_ITERM_MASK, + reg1); + if (ret != 0) + dev_err(wm831x->dev, "Failed to set charger control 1: %d\n", + ret); + + ret = wm831x_set_bits(wm831x, WM831X_CHARGER_CONTROL_2, + WM831X_CHG_OFF_MSK | + WM831X_CHG_TIME_MASK | + WM831X_CHG_FAST_ILIM_MASK | + WM831X_CHG_TRKL_ILIM_MASK | + WM831X_CHG_VSEL_MASK, + reg2); + if (ret != 0) + dev_err(wm831x->dev, "Failed to set charger control 2: %d\n", + ret); + + wm831x_reg_lock(wm831x); +} + +static int wm831x_bat_check_status(struct wm831x *wm831x, int *status) +{ + int ret; + + ret = wm831x_reg_read(wm831x, WM831X_SYSTEM_STATUS); + if (ret < 0) + return ret; + + if (ret & WM831X_PWR_SRC_BATT) { + *status = POWER_SUPPLY_STATUS_DISCHARGING; + return 0; + } + + ret = wm831x_reg_read(wm831x, WM831X_CHARGER_STATUS); + if (ret < 0) + return ret; + + switch (ret & WM831X_CHG_STATE_MASK) { + case WM831X_CHG_STATE_OFF: + *status = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case WM831X_CHG_STATE_TRICKLE: + case WM831X_CHG_STATE_FAST: + *status = POWER_SUPPLY_STATUS_CHARGING; + break; + + default: + *status = POWER_SUPPLY_STATUS_UNKNOWN; + break; + } + + return 0; +} + +static int wm831x_bat_check_type(struct wm831x *wm831x, int *type) +{ + int ret; + + ret = wm831x_reg_read(wm831x, WM831X_CHARGER_STATUS); + if (ret < 0) + return ret; + + switch (ret & WM831X_CHG_STATE_MASK) { + case WM831X_CHG_STATE_TRICKLE: + case WM831X_CHG_STATE_TRICKLE_OT: + *type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + break; + case WM831X_CHG_STATE_FAST: + case WM831X_CHG_STATE_FAST_OT: + *type = POWER_SUPPLY_CHARGE_TYPE_FAST; + break; + default: + *type = POWER_SUPPLY_CHARGE_TYPE_NONE; + break; + } + + return 0; +} + +static int wm831x_bat_check_health(struct wm831x *wm831x, int *health) +{ + int ret; + + ret = wm831x_reg_read(wm831x, WM831X_CHARGER_STATUS); + if (ret < 0) + return ret; + + if (ret & WM831X_BATT_HOT_STS) { + *health = POWER_SUPPLY_HEALTH_OVERHEAT; + return 0; + } + + if (ret & WM831X_BATT_COLD_STS) { + *health = POWER_SUPPLY_HEALTH_COLD; + return 0; + } + + if (ret & WM831X_BATT_OV_STS) { + *health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + return 0; + } + + switch (ret & WM831X_CHG_STATE_MASK) { + case WM831X_CHG_STATE_TRICKLE_OT: + case WM831X_CHG_STATE_FAST_OT: + *health = POWER_SUPPLY_HEALTH_OVERHEAT; + break; + case WM831X_CHG_STATE_DEFECTIVE: + *health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + break; + default: + *health = POWER_SUPPLY_HEALTH_GOOD; + break; + } + + return 0; +} + +static int wm831x_bat_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct wm831x_power *wm831x_power = dev_get_drvdata(psy->dev->parent); + struct wm831x *wm831x = wm831x_power->wm831x; + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + ret = wm831x_bat_check_status(wm831x, &val->intval); + break; + case POWER_SUPPLY_PROP_ONLINE: + ret = wm831x_power_check_online(wm831x, WM831X_PWR_SRC_BATT, + val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = wm831x_power_read_voltage(wm831x, WM831X_AUX_BATT, val); + break; + case POWER_SUPPLY_PROP_HEALTH: + ret = wm831x_bat_check_health(wm831x, &val->intval); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + ret = wm831x_bat_check_type(wm831x, &val->intval); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static enum power_supply_property wm831x_bat_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_CHARGE_TYPE, +}; + +static const char *wm831x_bat_irqs[] = { + "BATT HOT", + "BATT COLD", + "BATT FAIL", + "OV", + "END", + "TO", + "MODE", + "START", +}; + +static irqreturn_t wm831x_bat_irq(int irq, void *data) +{ + struct wm831x_power *wm831x_power = data; + struct wm831x *wm831x = wm831x_power->wm831x; + + dev_dbg(wm831x->dev, "Battery status changed: %d\n", irq); + + /* The battery charger is autonomous so we don't need to do + * anything except kick user space */ + power_supply_changed(&wm831x_power->battery); + + return IRQ_HANDLED; +} + + +/********************************************************************* + * Initialisation + *********************************************************************/ + +static irqreturn_t wm831x_syslo_irq(int irq, void *data) +{ + struct wm831x_power *wm831x_power = data; + struct wm831x *wm831x = wm831x_power->wm831x; + + /* Not much we can actually *do* but tell people for + * posterity, we're probably about to run out of power. */ + dev_crit(wm831x->dev, "SYSVDD under voltage\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t wm831x_pwr_src_irq(int irq, void *data) +{ + struct wm831x_power *wm831x_power = data; + struct wm831x *wm831x = wm831x_power->wm831x; + + dev_dbg(wm831x->dev, "Power source changed\n"); + + /* Just notify for everything - little harm in overnotifying. */ + power_supply_changed(&wm831x_power->battery); + power_supply_changed(&wm831x_power->usb); + power_supply_changed(&wm831x_power->wall); + + return IRQ_HANDLED; +} + +static __devinit int wm831x_power_probe(struct platform_device *pdev) +{ + struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); + struct wm831x_power *power; + struct power_supply *usb; + struct power_supply *battery; + struct power_supply *wall; + int ret, irq, i; + + power = kzalloc(sizeof(struct wm831x_power), GFP_KERNEL); + if (power == NULL) + return -ENOMEM; + + power->wm831x = wm831x; + platform_set_drvdata(pdev, power); + + usb = &power->usb; + battery = &power->battery; + wall = &power->wall; + + /* We ignore configuration failures since we can still read back + * the status without enabling the charger. + */ + wm831x_config_battery(wm831x); + + wall->name = "wm831x-wall"; + wall->type = POWER_SUPPLY_TYPE_MAINS; + wall->properties = wm831x_wall_props; + wall->num_properties = ARRAY_SIZE(wm831x_wall_props); + wall->get_property = wm831x_wall_get_prop; + ret = power_supply_register(&pdev->dev, wall); + if (ret) + goto err_kmalloc; + + battery->name = "wm831x-battery"; + battery->properties = wm831x_bat_props; + battery->num_properties = ARRAY_SIZE(wm831x_bat_props); + battery->get_property = wm831x_bat_get_prop; + battery->use_for_apm = 1; + ret = power_supply_register(&pdev->dev, battery); + if (ret) + goto err_wall; + + usb->name = "wm831x-usb", + usb->type = POWER_SUPPLY_TYPE_USB; + usb->properties = wm831x_usb_props; + usb->num_properties = ARRAY_SIZE(wm831x_usb_props); + usb->get_property = wm831x_usb_get_prop; + ret = power_supply_register(&pdev->dev, usb); + if (ret) + goto err_battery; + + irq = platform_get_irq_byname(pdev, "SYSLO"); + ret = request_threaded_irq(irq, NULL, wm831x_syslo_irq, + IRQF_TRIGGER_RISING, "System power low", + power); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to request SYSLO IRQ %d: %d\n", + irq, ret); + goto err_usb; + } + + irq = platform_get_irq_byname(pdev, "PWR SRC"); + ret = request_threaded_irq(irq, NULL, wm831x_pwr_src_irq, + IRQF_TRIGGER_RISING, "Power source", + power); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to request PWR SRC IRQ %d: %d\n", + irq, ret); + goto err_syslo; + } + + for (i = 0; i < ARRAY_SIZE(wm831x_bat_irqs); i++) { + irq = platform_get_irq_byname(pdev, wm831x_bat_irqs[i]); + ret = request_threaded_irq(irq, NULL, wm831x_bat_irq, + IRQF_TRIGGER_RISING, + wm831x_bat_irqs[i], + power); + if (ret != 0) { + dev_err(&pdev->dev, + "Failed to request %s IRQ %d: %d\n", + wm831x_bat_irqs[i], irq, ret); + goto err_bat_irq; + } + } + + return ret; + +err_bat_irq: + for (; i >= 0; i--) { + irq = platform_get_irq_byname(pdev, wm831x_bat_irqs[i]); + free_irq(irq, power); + } + irq = platform_get_irq_byname(pdev, "PWR SRC"); + free_irq(irq, power); +err_syslo: + irq = platform_get_irq_byname(pdev, "SYSLO"); + free_irq(irq, power); +err_usb: + power_supply_unregister(usb); +err_battery: + power_supply_unregister(battery); +err_wall: + power_supply_unregister(wall); +err_kmalloc: + kfree(power); + return ret; +} + +static __devexit int wm831x_power_remove(struct platform_device *pdev) +{ + struct wm831x_power *wm831x_power = platform_get_drvdata(pdev); + int irq, i; + + for (i = 0; i < ARRAY_SIZE(wm831x_bat_irqs); i++) { + irq = platform_get_irq_byname(pdev, wm831x_bat_irqs[i]); + free_irq(irq, wm831x_power); + } + + irq = platform_get_irq_byname(pdev, "PWR SRC"); + free_irq(irq, wm831x_power); + + irq = platform_get_irq_byname(pdev, "SYSLO"); + free_irq(irq, wm831x_power); + + power_supply_unregister(&wm831x_power->battery); + power_supply_unregister(&wm831x_power->wall); + power_supply_unregister(&wm831x_power->usb); + kfree(wm831x_power); + return 0; +} + +static struct platform_driver wm831x_power_driver = { + .probe = wm831x_power_probe, + .remove = __devexit_p(wm831x_power_remove), + .driver = { + .name = "wm831x-power", + }, +}; + +static int __init wm831x_power_init(void) +{ + return platform_driver_register(&wm831x_power_driver); +} +module_init(wm831x_power_init); + +static void __exit wm831x_power_exit(void) +{ + platform_driver_unregister(&wm831x_power_driver); +} +module_exit(wm831x_power_exit); + +MODULE_DESCRIPTION("Power supply driver for WM831x PMICs"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm831x-power"); diff --git a/drivers/power/wm8350_power.c b/drivers/power/wm8350_power.c new file mode 100644 index 00000000..0693902d --- /dev/null +++ b/drivers/power/wm8350_power.c @@ -0,0 +1,539 @@ +/* + * Battery driver for wm8350 PMIC + * + * Copyright 2007, 2008 Wolfson Microelectronics PLC. + * + * Based on OLPC Battery Driver + * + * Copyright 2006 David Woodhouse + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include + +static int wm8350_read_battery_uvolts(struct wm8350 *wm8350) +{ + return wm8350_read_auxadc(wm8350, WM8350_AUXADC_BATT, 0, 0) + * WM8350_AUX_COEFF; +} + +static int wm8350_read_line_uvolts(struct wm8350 *wm8350) +{ + return wm8350_read_auxadc(wm8350, WM8350_AUXADC_LINE, 0, 0) + * WM8350_AUX_COEFF; +} + +static int wm8350_read_usb_uvolts(struct wm8350 *wm8350) +{ + return wm8350_read_auxadc(wm8350, WM8350_AUXADC_USB, 0, 0) + * WM8350_AUX_COEFF; +} + +#define WM8350_BATT_SUPPLY 1 +#define WM8350_USB_SUPPLY 2 +#define WM8350_LINE_SUPPLY 4 + +static inline int wm8350_charge_time_min(struct wm8350 *wm8350, int min) +{ + if (!wm8350->power.rev_g_coeff) + return (((min - 30) / 15) & 0xf) << 8; + else + return (((min - 30) / 30) & 0xf) << 8; +} + +static int wm8350_get_supplies(struct wm8350 *wm8350) +{ + u16 sm, ov, co, chrg; + int supplies = 0; + + sm = wm8350_reg_read(wm8350, WM8350_STATE_MACHINE_STATUS); + ov = wm8350_reg_read(wm8350, WM8350_MISC_OVERRIDES); + co = wm8350_reg_read(wm8350, WM8350_COMPARATOR_OVERRIDES); + chrg = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2); + + /* USB_SM */ + sm = (sm & WM8350_USB_SM_MASK) >> WM8350_USB_SM_SHIFT; + + /* CHG_ISEL */ + chrg &= WM8350_CHG_ISEL_MASK; + + /* If the USB state machine is active then we're using that with or + * without battery, otherwise check for wall supply */ + if (((sm == WM8350_USB_SM_100_SLV) || + (sm == WM8350_USB_SM_500_SLV) || + (sm == WM8350_USB_SM_STDBY_SLV)) + && !(ov & WM8350_USB_LIMIT_OVRDE)) + supplies = WM8350_USB_SUPPLY; + else if (((sm == WM8350_USB_SM_100_SLV) || + (sm == WM8350_USB_SM_500_SLV) || + (sm == WM8350_USB_SM_STDBY_SLV)) + && (ov & WM8350_USB_LIMIT_OVRDE) && (chrg == 0)) + supplies = WM8350_USB_SUPPLY | WM8350_BATT_SUPPLY; + else if (co & WM8350_WALL_FB_OVRDE) + supplies = WM8350_LINE_SUPPLY; + else + supplies = WM8350_BATT_SUPPLY; + + return supplies; +} + +static int wm8350_charger_config(struct wm8350 *wm8350, + struct wm8350_charger_policy *policy) +{ + u16 reg, eoc_mA, fast_limit_mA; + + if (!policy) { + dev_warn(wm8350->dev, + "No charger policy, charger not configured.\n"); + return -EINVAL; + } + + /* make sure USB fast charge current is not > 500mA */ + if (policy->fast_limit_USB_mA > 500) { + dev_err(wm8350->dev, "USB fast charge > 500mA\n"); + return -EINVAL; + } + + eoc_mA = WM8350_CHG_EOC_mA(policy->eoc_mA); + + wm8350_reg_unlock(wm8350); + + reg = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_1) + & WM8350_CHG_ENA_R168; + wm8350_reg_write(wm8350, WM8350_BATTERY_CHARGER_CONTROL_1, + reg | eoc_mA | policy->trickle_start_mV | + WM8350_CHG_TRICKLE_TEMP_CHOKE | + WM8350_CHG_TRICKLE_USB_CHOKE | + WM8350_CHG_FAST_USB_THROTTLE); + + if (wm8350_get_supplies(wm8350) & WM8350_USB_SUPPLY) { + fast_limit_mA = + WM8350_CHG_FAST_LIMIT_mA(policy->fast_limit_USB_mA); + wm8350_reg_write(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2, + policy->charge_mV | policy->trickle_charge_USB_mA | + fast_limit_mA | wm8350_charge_time_min(wm8350, + policy->charge_timeout)); + + } else { + fast_limit_mA = + WM8350_CHG_FAST_LIMIT_mA(policy->fast_limit_mA); + wm8350_reg_write(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2, + policy->charge_mV | policy->trickle_charge_mA | + fast_limit_mA | wm8350_charge_time_min(wm8350, + policy->charge_timeout)); + } + + wm8350_reg_lock(wm8350); + return 0; +} + +static int wm8350_batt_status(struct wm8350 *wm8350) +{ + u16 state; + + state = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2); + state &= WM8350_CHG_STS_MASK; + + switch (state) { + case WM8350_CHG_STS_OFF: + return POWER_SUPPLY_STATUS_DISCHARGING; + + case WM8350_CHG_STS_TRICKLE: + case WM8350_CHG_STS_FAST: + return POWER_SUPPLY_STATUS_CHARGING; + + default: + return POWER_SUPPLY_STATUS_UNKNOWN; + } +} + +static ssize_t charger_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct wm8350 *wm8350 = dev_get_drvdata(dev); + char *charge; + int state; + + state = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2) & + WM8350_CHG_STS_MASK; + switch (state) { + case WM8350_CHG_STS_OFF: + charge = "Charger Off"; + break; + case WM8350_CHG_STS_TRICKLE: + charge = "Trickle Charging"; + break; + case WM8350_CHG_STS_FAST: + charge = "Fast Charging"; + break; + default: + return 0; + } + + return sprintf(buf, "%s\n", charge); +} + +static DEVICE_ATTR(charger_state, 0444, charger_state_show, NULL); + +static irqreturn_t wm8350_charger_handler(int irq, void *data) +{ + struct wm8350 *wm8350 = data; + struct wm8350_power *power = &wm8350->power; + struct wm8350_charger_policy *policy = power->policy; + + switch (irq - wm8350->irq_base) { + case WM8350_IRQ_CHG_BAT_FAIL: + dev_err(wm8350->dev, "battery failed\n"); + break; + case WM8350_IRQ_CHG_TO: + dev_err(wm8350->dev, "charger timeout\n"); + power_supply_changed(&power->battery); + break; + + case WM8350_IRQ_CHG_BAT_HOT: + case WM8350_IRQ_CHG_BAT_COLD: + case WM8350_IRQ_CHG_START: + case WM8350_IRQ_CHG_END: + power_supply_changed(&power->battery); + break; + + case WM8350_IRQ_CHG_FAST_RDY: + dev_dbg(wm8350->dev, "fast charger ready\n"); + wm8350_charger_config(wm8350, policy); + wm8350_reg_unlock(wm8350); + wm8350_set_bits(wm8350, WM8350_BATTERY_CHARGER_CONTROL_1, + WM8350_CHG_FAST); + wm8350_reg_lock(wm8350); + break; + + case WM8350_IRQ_CHG_VBATT_LT_3P9: + dev_warn(wm8350->dev, "battery < 3.9V\n"); + break; + case WM8350_IRQ_CHG_VBATT_LT_3P1: + dev_warn(wm8350->dev, "battery < 3.1V\n"); + break; + case WM8350_IRQ_CHG_VBATT_LT_2P85: + dev_warn(wm8350->dev, "battery < 2.85V\n"); + break; + + /* Supply change. We will overnotify but it should do + * no harm. */ + case WM8350_IRQ_EXT_USB_FB: + case WM8350_IRQ_EXT_WALL_FB: + wm8350_charger_config(wm8350, policy); + case WM8350_IRQ_EXT_BAT_FB: /* Fall through */ + power_supply_changed(&power->battery); + power_supply_changed(&power->usb); + power_supply_changed(&power->ac); + break; + + default: + dev_err(wm8350->dev, "Unknown interrupt %d\n", irq); + } + + return IRQ_HANDLED; +} + +/********************************************************************* + * AC Power + *********************************************************************/ +static int wm8350_ac_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct wm8350 *wm8350 = dev_get_drvdata(psy->dev->parent); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = !!(wm8350_get_supplies(wm8350) & + WM8350_LINE_SUPPLY); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = wm8350_read_line_uvolts(wm8350); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static enum power_supply_property wm8350_ac_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + +/********************************************************************* + * USB Power + *********************************************************************/ +static int wm8350_usb_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct wm8350 *wm8350 = dev_get_drvdata(psy->dev->parent); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = !!(wm8350_get_supplies(wm8350) & + WM8350_USB_SUPPLY); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = wm8350_read_usb_uvolts(wm8350); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static enum power_supply_property wm8350_usb_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + +/********************************************************************* + * Battery properties + *********************************************************************/ + +static int wm8350_bat_check_health(struct wm8350 *wm8350) +{ + u16 reg; + + if (wm8350_read_battery_uvolts(wm8350) < 2850000) + return POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + + reg = wm8350_reg_read(wm8350, WM8350_CHARGER_OVERRIDES); + if (reg & WM8350_CHG_BATT_HOT_OVRDE) + return POWER_SUPPLY_HEALTH_OVERHEAT; + + if (reg & WM8350_CHG_BATT_COLD_OVRDE) + return POWER_SUPPLY_HEALTH_COLD; + + return POWER_SUPPLY_HEALTH_GOOD; +} + +static int wm8350_bat_get_charge_type(struct wm8350 *wm8350) +{ + int state; + + state = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2) & + WM8350_CHG_STS_MASK; + switch (state) { + case WM8350_CHG_STS_OFF: + return POWER_SUPPLY_CHARGE_TYPE_NONE; + case WM8350_CHG_STS_TRICKLE: + return POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + case WM8350_CHG_STS_FAST: + return POWER_SUPPLY_CHARGE_TYPE_FAST; + default: + return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; + } +} + +static int wm8350_bat_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct wm8350 *wm8350 = dev_get_drvdata(psy->dev->parent); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = wm8350_batt_status(wm8350); + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = !!(wm8350_get_supplies(wm8350) & + WM8350_BATT_SUPPLY); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = wm8350_read_battery_uvolts(wm8350); + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = wm8350_bat_check_health(wm8350); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = wm8350_bat_get_charge_type(wm8350); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static enum power_supply_property wm8350_bat_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_CHARGE_TYPE, +}; + +/********************************************************************* + * Initialisation + *********************************************************************/ + +static void wm8350_init_charger(struct wm8350 *wm8350) +{ + /* register our interest in charger events */ + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_HOT, + wm8350_charger_handler, 0, "Battery hot", wm8350); + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_COLD, + wm8350_charger_handler, 0, "Battery cold", wm8350); + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_FAIL, + wm8350_charger_handler, 0, "Battery fail", wm8350); + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_TO, + wm8350_charger_handler, 0, + "Charger timeout", wm8350); + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_END, + wm8350_charger_handler, 0, + "Charge end", wm8350); + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_START, + wm8350_charger_handler, 0, + "Charge start", wm8350); + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_FAST_RDY, + wm8350_charger_handler, 0, + "Fast charge ready", wm8350); + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9, + wm8350_charger_handler, 0, + "Battery <3.9V", wm8350); + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1, + wm8350_charger_handler, 0, + "Battery <3.1V", wm8350); + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85, + wm8350_charger_handler, 0, + "Battery <2.85V", wm8350); + + /* and supply change events */ + wm8350_register_irq(wm8350, WM8350_IRQ_EXT_USB_FB, + wm8350_charger_handler, 0, "USB", wm8350); + wm8350_register_irq(wm8350, WM8350_IRQ_EXT_WALL_FB, + wm8350_charger_handler, 0, "Wall", wm8350); + wm8350_register_irq(wm8350, WM8350_IRQ_EXT_BAT_FB, + wm8350_charger_handler, 0, "Battery", wm8350); +} + +static void free_charger_irq(struct wm8350 *wm8350) +{ + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_HOT, wm8350); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_COLD, wm8350); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_FAIL, wm8350); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_TO, wm8350); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_END, wm8350); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_START, wm8350); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9, wm8350); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1, wm8350); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85, wm8350); + wm8350_free_irq(wm8350, WM8350_IRQ_EXT_USB_FB, wm8350); + wm8350_free_irq(wm8350, WM8350_IRQ_EXT_WALL_FB, wm8350); + wm8350_free_irq(wm8350, WM8350_IRQ_EXT_BAT_FB, wm8350); +} + +static __devinit int wm8350_power_probe(struct platform_device *pdev) +{ + struct wm8350 *wm8350 = platform_get_drvdata(pdev); + struct wm8350_power *power = &wm8350->power; + struct wm8350_charger_policy *policy = power->policy; + struct power_supply *usb = &power->usb; + struct power_supply *battery = &power->battery; + struct power_supply *ac = &power->ac; + int ret; + + ac->name = "wm8350-ac"; + ac->type = POWER_SUPPLY_TYPE_MAINS; + ac->properties = wm8350_ac_props; + ac->num_properties = ARRAY_SIZE(wm8350_ac_props); + ac->get_property = wm8350_ac_get_prop; + ret = power_supply_register(&pdev->dev, ac); + if (ret) + return ret; + + battery->name = "wm8350-battery"; + battery->properties = wm8350_bat_props; + battery->num_properties = ARRAY_SIZE(wm8350_bat_props); + battery->get_property = wm8350_bat_get_property; + battery->use_for_apm = 1; + ret = power_supply_register(&pdev->dev, battery); + if (ret) + goto battery_failed; + + usb->name = "wm8350-usb", + usb->type = POWER_SUPPLY_TYPE_USB; + usb->properties = wm8350_usb_props; + usb->num_properties = ARRAY_SIZE(wm8350_usb_props); + usb->get_property = wm8350_usb_get_prop; + ret = power_supply_register(&pdev->dev, usb); + if (ret) + goto usb_failed; + + ret = device_create_file(&pdev->dev, &dev_attr_charger_state); + if (ret < 0) + dev_warn(wm8350->dev, "failed to add charge sysfs: %d\n", ret); + ret = 0; + + wm8350_init_charger(wm8350); + if (wm8350_charger_config(wm8350, policy) == 0) { + wm8350_reg_unlock(wm8350); + wm8350_set_bits(wm8350, WM8350_POWER_MGMT_5, WM8350_CHG_ENA); + wm8350_reg_lock(wm8350); + } + + return ret; + +usb_failed: + power_supply_unregister(battery); +battery_failed: + power_supply_unregister(ac); + + return ret; +} + +static __devexit int wm8350_power_remove(struct platform_device *pdev) +{ + struct wm8350 *wm8350 = platform_get_drvdata(pdev); + struct wm8350_power *power = &wm8350->power; + + free_charger_irq(wm8350); + device_remove_file(&pdev->dev, &dev_attr_charger_state); + power_supply_unregister(&power->battery); + power_supply_unregister(&power->ac); + power_supply_unregister(&power->usb); + return 0; +} + +static struct platform_driver wm8350_power_driver = { + .probe = wm8350_power_probe, + .remove = __devexit_p(wm8350_power_remove), + .driver = { + .name = "wm8350-power", + }, +}; + +static int __init wm8350_power_init(void) +{ + return platform_driver_register(&wm8350_power_driver); +} +module_init(wm8350_power_init); + +static void __exit wm8350_power_exit(void) +{ + platform_driver_unregister(&wm8350_power_driver); +} +module_exit(wm8350_power_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Power supply driver for WM8350"); +MODULE_ALIAS("platform:wm8350-power"); diff --git a/drivers/power/wm97xx_battery.c b/drivers/power/wm97xx_battery.c new file mode 100644 index 00000000..156559e5 --- /dev/null +++ b/drivers/power/wm97xx_battery.c @@ -0,0 +1,309 @@ +/* + * linux/drivers/power/wm97xx_battery.c + * + * Battery measurement code for WM97xx + * + * based on tosa_battery.c + * + * Copyright (C) 2008 Marek Vasut + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static DEFINE_MUTEX(bat_lock); +static struct work_struct bat_work; +static struct mutex work_lock; +static int bat_status = POWER_SUPPLY_STATUS_UNKNOWN; +static enum power_supply_property *prop; + +static unsigned long wm97xx_read_bat(struct power_supply *bat_ps) +{ + struct wm97xx_pdata *wmdata = bat_ps->dev->parent->platform_data; + struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata; + + return wm97xx_read_aux_adc(dev_get_drvdata(bat_ps->dev->parent), + pdata->batt_aux) * pdata->batt_mult / + pdata->batt_div; +} + +static unsigned long wm97xx_read_temp(struct power_supply *bat_ps) +{ + struct wm97xx_pdata *wmdata = bat_ps->dev->parent->platform_data; + struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata; + + return wm97xx_read_aux_adc(dev_get_drvdata(bat_ps->dev->parent), + pdata->temp_aux) * pdata->temp_mult / + pdata->temp_div; +} + +static int wm97xx_bat_get_property(struct power_supply *bat_ps, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct wm97xx_pdata *wmdata = bat_ps->dev->parent->platform_data; + struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = bat_status; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = pdata->batt_tech; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + if (pdata->batt_aux >= 0) + val->intval = wm97xx_read_bat(bat_ps); + else + return -EINVAL; + break; + case POWER_SUPPLY_PROP_TEMP: + if (pdata->temp_aux >= 0) + val->intval = wm97xx_read_temp(bat_ps); + else + return -EINVAL; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + if (pdata->max_voltage >= 0) + val->intval = pdata->max_voltage; + else + return -EINVAL; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + if (pdata->min_voltage >= 0) + val->intval = pdata->min_voltage; + else + return -EINVAL; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 1; + break; + default: + return -EINVAL; + } + return 0; +} + +static void wm97xx_bat_external_power_changed(struct power_supply *bat_ps) +{ + schedule_work(&bat_work); +} + +static void wm97xx_bat_update(struct power_supply *bat_ps) +{ + int old_status = bat_status; + struct wm97xx_pdata *wmdata = bat_ps->dev->parent->platform_data; + struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata; + + mutex_lock(&work_lock); + + bat_status = (pdata->charge_gpio >= 0) ? + (gpio_get_value(pdata->charge_gpio) ? + POWER_SUPPLY_STATUS_DISCHARGING : + POWER_SUPPLY_STATUS_CHARGING) : + POWER_SUPPLY_STATUS_UNKNOWN; + + if (old_status != bat_status) { + pr_debug("%s: %i -> %i\n", bat_ps->name, old_status, + bat_status); + power_supply_changed(bat_ps); + } + + mutex_unlock(&work_lock); +} + +static struct power_supply bat_ps = { + .type = POWER_SUPPLY_TYPE_BATTERY, + .get_property = wm97xx_bat_get_property, + .external_power_changed = wm97xx_bat_external_power_changed, + .use_for_apm = 1, +}; + +static void wm97xx_bat_work(struct work_struct *work) +{ + wm97xx_bat_update(&bat_ps); +} + +static irqreturn_t wm97xx_chrg_irq(int irq, void *data) +{ + schedule_work(&bat_work); + return IRQ_HANDLED; +} + +#ifdef CONFIG_PM +static int wm97xx_bat_suspend(struct device *dev) +{ + flush_work_sync(&bat_work); + return 0; +} + +static int wm97xx_bat_resume(struct device *dev) +{ + schedule_work(&bat_work); + return 0; +} + +static const struct dev_pm_ops wm97xx_bat_pm_ops = { + .suspend = wm97xx_bat_suspend, + .resume = wm97xx_bat_resume, +}; +#endif + +static int __devinit wm97xx_bat_probe(struct platform_device *dev) +{ + int ret = 0; + int props = 1; /* POWER_SUPPLY_PROP_PRESENT */ + int i = 0; + struct wm97xx_pdata *wmdata = dev->dev.platform_data; + struct wm97xx_batt_pdata *pdata; + + if (!wmdata) { + dev_err(&dev->dev, "No platform data supplied\n"); + return -EINVAL; + } + + pdata = wmdata->batt_pdata; + + if (dev->id != -1) + return -EINVAL; + + mutex_init(&work_lock); + + if (!pdata) { + dev_err(&dev->dev, "No platform_data supplied\n"); + return -EINVAL; + } + + if (gpio_is_valid(pdata->charge_gpio)) { + ret = gpio_request(pdata->charge_gpio, "BATT CHRG"); + if (ret) + goto err; + ret = gpio_direction_input(pdata->charge_gpio); + if (ret) + goto err2; + ret = request_irq(gpio_to_irq(pdata->charge_gpio), + wm97xx_chrg_irq, IRQF_DISABLED, + "AC Detect", dev); + if (ret) + goto err2; + props++; /* POWER_SUPPLY_PROP_STATUS */ + } + + if (pdata->batt_tech >= 0) + props++; /* POWER_SUPPLY_PROP_TECHNOLOGY */ + if (pdata->temp_aux >= 0) + props++; /* POWER_SUPPLY_PROP_TEMP */ + if (pdata->batt_aux >= 0) + props++; /* POWER_SUPPLY_PROP_VOLTAGE_NOW */ + if (pdata->max_voltage >= 0) + props++; /* POWER_SUPPLY_PROP_VOLTAGE_MAX */ + if (pdata->min_voltage >= 0) + props++; /* POWER_SUPPLY_PROP_VOLTAGE_MIN */ + + prop = kzalloc(props * sizeof(*prop), GFP_KERNEL); + if (!prop) + goto err3; + + prop[i++] = POWER_SUPPLY_PROP_PRESENT; + if (pdata->charge_gpio >= 0) + prop[i++] = POWER_SUPPLY_PROP_STATUS; + if (pdata->batt_tech >= 0) + prop[i++] = POWER_SUPPLY_PROP_TECHNOLOGY; + if (pdata->temp_aux >= 0) + prop[i++] = POWER_SUPPLY_PROP_TEMP; + if (pdata->batt_aux >= 0) + prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_NOW; + if (pdata->max_voltage >= 0) + prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MAX; + if (pdata->min_voltage >= 0) + prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MIN; + + INIT_WORK(&bat_work, wm97xx_bat_work); + + if (!pdata->batt_name) { + dev_info(&dev->dev, "Please consider setting proper battery " + "name in platform definition file, falling " + "back to name \"wm97xx-batt\"\n"); + bat_ps.name = "wm97xx-batt"; + } else + bat_ps.name = pdata->batt_name; + + bat_ps.properties = prop; + bat_ps.num_properties = props; + + ret = power_supply_register(&dev->dev, &bat_ps); + if (!ret) + schedule_work(&bat_work); + else + goto err4; + + return 0; +err4: + kfree(prop); +err3: + if (gpio_is_valid(pdata->charge_gpio)) + free_irq(gpio_to_irq(pdata->charge_gpio), dev); +err2: + if (gpio_is_valid(pdata->charge_gpio)) + gpio_free(pdata->charge_gpio); +err: + return ret; +} + +static int __devexit wm97xx_bat_remove(struct platform_device *dev) +{ + struct wm97xx_pdata *wmdata = dev->dev.platform_data; + struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata; + + if (pdata && gpio_is_valid(pdata->charge_gpio)) { + free_irq(gpio_to_irq(pdata->charge_gpio), dev); + gpio_free(pdata->charge_gpio); + } + cancel_work_sync(&bat_work); + power_supply_unregister(&bat_ps); + kfree(prop); + return 0; +} + +static struct platform_driver wm97xx_bat_driver = { + .driver = { + .name = "wm97xx-battery", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &wm97xx_bat_pm_ops, +#endif + }, + .probe = wm97xx_bat_probe, + .remove = __devexit_p(wm97xx_bat_remove), +}; + +static int __init wm97xx_bat_init(void) +{ + return platform_driver_register(&wm97xx_bat_driver); +} + +static void __exit wm97xx_bat_exit(void) +{ + platform_driver_unregister(&wm97xx_bat_driver); +} + +module_init(wm97xx_bat_init); +module_exit(wm97xx_bat_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Marek Vasut "); +MODULE_DESCRIPTION("WM97xx battery driver"); diff --git a/drivers/power/z2_battery.c b/drivers/power/z2_battery.c new file mode 100644 index 00000000..d119c38b --- /dev/null +++ b/drivers/power/z2_battery.c @@ -0,0 +1,335 @@ +/* + * Battery measurement code for Zipit Z2 + * + * Copyright (C) 2009 Peter Edwards + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define Z2_DEFAULT_NAME "Z2" + +struct z2_charger { + struct z2_battery_info *info; + int bat_status; + struct i2c_client *client; + struct power_supply batt_ps; + struct mutex work_lock; + struct work_struct bat_work; +}; + +static unsigned long z2_read_bat(struct z2_charger *charger) +{ + int data; + data = i2c_smbus_read_byte_data(charger->client, + charger->info->batt_I2C_reg); + if (data < 0) + return 0; + + return data * charger->info->batt_mult / charger->info->batt_div; +} + +static int z2_batt_get_property(struct power_supply *batt_ps, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct z2_charger *charger = container_of(batt_ps, struct z2_charger, + batt_ps); + struct z2_battery_info *info = charger->info; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = charger->bat_status; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = info->batt_tech; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + if (info->batt_I2C_reg >= 0) + val->intval = z2_read_bat(charger); + else + return -EINVAL; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + if (info->max_voltage >= 0) + val->intval = info->max_voltage; + else + return -EINVAL; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + if (info->min_voltage >= 0) + val->intval = info->min_voltage; + else + return -EINVAL; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 1; + break; + default: + return -EINVAL; + } + + return 0; +} + +static void z2_batt_ext_power_changed(struct power_supply *batt_ps) +{ + struct z2_charger *charger = container_of(batt_ps, struct z2_charger, + batt_ps); + schedule_work(&charger->bat_work); +} + +static void z2_batt_update(struct z2_charger *charger) +{ + int old_status = charger->bat_status; + struct z2_battery_info *info; + + info = charger->info; + + mutex_lock(&charger->work_lock); + + charger->bat_status = (info->charge_gpio >= 0) ? + (gpio_get_value(info->charge_gpio) ? + POWER_SUPPLY_STATUS_CHARGING : + POWER_SUPPLY_STATUS_DISCHARGING) : + POWER_SUPPLY_STATUS_UNKNOWN; + + if (old_status != charger->bat_status) { + pr_debug("%s: %i -> %i\n", charger->batt_ps.name, old_status, + charger->bat_status); + power_supply_changed(&charger->batt_ps); + } + + mutex_unlock(&charger->work_lock); +} + +static void z2_batt_work(struct work_struct *work) +{ + struct z2_charger *charger; + charger = container_of(work, struct z2_charger, bat_work); + z2_batt_update(charger); +} + +static irqreturn_t z2_charge_switch_irq(int irq, void *devid) +{ + struct z2_charger *charger = devid; + schedule_work(&charger->bat_work); + return IRQ_HANDLED; +} + +static int z2_batt_ps_init(struct z2_charger *charger, int props) +{ + int i = 0; + enum power_supply_property *prop; + struct z2_battery_info *info = charger->info; + + if (info->charge_gpio >= 0) + props++; /* POWER_SUPPLY_PROP_STATUS */ + if (info->batt_tech >= 0) + props++; /* POWER_SUPPLY_PROP_TECHNOLOGY */ + if (info->batt_I2C_reg >= 0) + props++; /* POWER_SUPPLY_PROP_VOLTAGE_NOW */ + if (info->max_voltage >= 0) + props++; /* POWER_SUPPLY_PROP_VOLTAGE_MAX */ + if (info->min_voltage >= 0) + props++; /* POWER_SUPPLY_PROP_VOLTAGE_MIN */ + + prop = kzalloc(props * sizeof(*prop), GFP_KERNEL); + if (!prop) + return -ENOMEM; + + prop[i++] = POWER_SUPPLY_PROP_PRESENT; + if (info->charge_gpio >= 0) + prop[i++] = POWER_SUPPLY_PROP_STATUS; + if (info->batt_tech >= 0) + prop[i++] = POWER_SUPPLY_PROP_TECHNOLOGY; + if (info->batt_I2C_reg >= 0) + prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_NOW; + if (info->max_voltage >= 0) + prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MAX; + if (info->min_voltage >= 0) + prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MIN; + + if (!info->batt_name) { + dev_info(&charger->client->dev, + "Please consider setting proper battery " + "name in platform definition file, falling " + "back to name \" Z2_DEFAULT_NAME \"\n"); + charger->batt_ps.name = Z2_DEFAULT_NAME; + } else + charger->batt_ps.name = info->batt_name; + + charger->batt_ps.properties = prop; + charger->batt_ps.num_properties = props; + charger->batt_ps.type = POWER_SUPPLY_TYPE_BATTERY; + charger->batt_ps.get_property = z2_batt_get_property; + charger->batt_ps.external_power_changed = z2_batt_ext_power_changed; + charger->batt_ps.use_for_apm = 1; + + return 0; +} + +static int __devinit z2_batt_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret = 0; + int props = 1; /* POWER_SUPPLY_PROP_PRESENT */ + struct z2_charger *charger; + struct z2_battery_info *info = client->dev.platform_data; + + if (info == NULL) { + dev_err(&client->dev, + "Please set platform device platform_data" + " to a valid z2_battery_info pointer!\n"); + return -EINVAL; + } + + charger = kzalloc(sizeof(*charger), GFP_KERNEL); + if (charger == NULL) + return -ENOMEM; + + charger->bat_status = POWER_SUPPLY_STATUS_UNKNOWN; + charger->info = info; + charger->client = client; + i2c_set_clientdata(client, charger); + + mutex_init(&charger->work_lock); + + if (info->charge_gpio >= 0 && gpio_is_valid(info->charge_gpio)) { + ret = gpio_request(info->charge_gpio, "BATT CHRG"); + if (ret) + goto err; + + ret = gpio_direction_input(info->charge_gpio); + if (ret) + goto err2; + + irq_set_irq_type(gpio_to_irq(info->charge_gpio), + IRQ_TYPE_EDGE_BOTH); + ret = request_irq(gpio_to_irq(info->charge_gpio), + z2_charge_switch_irq, IRQF_DISABLED, + "AC Detect", charger); + if (ret) + goto err3; + } + + ret = z2_batt_ps_init(charger, props); + if (ret) + goto err3; + + INIT_WORK(&charger->bat_work, z2_batt_work); + + ret = power_supply_register(&client->dev, &charger->batt_ps); + if (ret) + goto err4; + + schedule_work(&charger->bat_work); + + return 0; + +err4: + kfree(charger->batt_ps.properties); +err3: + if (info->charge_gpio >= 0 && gpio_is_valid(info->charge_gpio)) + free_irq(gpio_to_irq(info->charge_gpio), charger); +err2: + if (info->charge_gpio >= 0 && gpio_is_valid(info->charge_gpio)) + gpio_free(info->charge_gpio); +err: + kfree(charger); + return ret; +} + +static int __devexit z2_batt_remove(struct i2c_client *client) +{ + struct z2_charger *charger = i2c_get_clientdata(client); + struct z2_battery_info *info = charger->info; + + cancel_work_sync(&charger->bat_work); + power_supply_unregister(&charger->batt_ps); + + kfree(charger->batt_ps.properties); + if (info->charge_gpio >= 0 && gpio_is_valid(info->charge_gpio)) { + free_irq(gpio_to_irq(info->charge_gpio), charger); + gpio_free(info->charge_gpio); + } + + kfree(charger); + + return 0; +} + +#ifdef CONFIG_PM +static int z2_batt_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct z2_charger *charger = i2c_get_clientdata(client); + + flush_work_sync(&charger->bat_work); + return 0; +} + +static int z2_batt_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct z2_charger *charger = i2c_get_clientdata(client); + + schedule_work(&charger->bat_work); + return 0; +} + +static const struct dev_pm_ops z2_battery_pm_ops = { + .suspend = z2_batt_suspend, + .resume = z2_batt_resume, +}; + +#define Z2_BATTERY_PM_OPS (&z2_battery_pm_ops) + +#else +#define Z2_BATTERY_PM_OPS (NULL) +#endif + +static const struct i2c_device_id z2_batt_id[] = { + { "aer915", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, z2_batt_id); + +static struct i2c_driver z2_batt_driver = { + .driver = { + .name = "z2-battery", + .owner = THIS_MODULE, + .pm = Z2_BATTERY_PM_OPS + }, + .probe = z2_batt_probe, + .remove = z2_batt_remove, + .id_table = z2_batt_id, +}; + +static int __init z2_batt_init(void) +{ + return i2c_add_driver(&z2_batt_driver); +} + +static void __exit z2_batt_exit(void) +{ + i2c_del_driver(&z2_batt_driver); +} + +module_init(z2_batt_init); +module_exit(z2_batt_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Peter Edwards "); +MODULE_DESCRIPTION("Zipit Z2 battery driver"); -- cgit v1.2.3