diff options
author | Srikant Patnaik | 2015-01-11 12:28:04 +0530 |
---|---|---|
committer | Srikant Patnaik | 2015-01-11 12:28:04 +0530 |
commit | 871480933a1c28f8a9fed4c4d34d06c439a7a422 (patch) | |
tree | 8718f573808810c2a1e8cb8fb6ac469093ca2784 /drivers/power | |
parent | 9d40ac5867b9aefe0722bc1f110b965ff294d30d (diff) | |
download | FOSSEE-netbook-kernel-source-871480933a1c28f8a9fed4c4d34d06c439a7a422.tar.gz FOSSEE-netbook-kernel-source-871480933a1c28f8a9fed4c4d34d06c439a7a422.tar.bz2 FOSSEE-netbook-kernel-source-871480933a1c28f8a9fed4c4d34d06c439a7a422.zip |
Moved, renamed, and deleted files
The original directory structure was scattered and unorganized.
Changes are basically to make it look like kernel structure.
Diffstat (limited to 'drivers/power')
96 files changed, 46073 insertions, 0 deletions
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig new file mode 100644 index 00000000..da8bac56 --- /dev/null +++ b/drivers/power/Kconfig @@ -0,0 +1,325 @@ +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" + depends on HAS_IOMEM + select W1 + select W1_SLAVE_DS2780 + help + Say Y here to enable support for batteries with ds2780 chip. + +config BATTERY_DS2781 + tristate "2781 battery driver" + depends on HAS_IOMEM + select W1 + select W1_SLAVE_DS2781 + help + If you enable this you will have the DS2781 battery driver support. + + The battery monitor chip is used in many batteries/devices + as the one who is responsible for charging/discharging/monitoring + Li+ batteries. + + If you are unsure, say N. + +config BATTERY_WMT + tristate "wmt battery driver (wonder media & others)" + help + Say Y here to enable support for batteries with wmt 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_SBS + tristate "SBS Compliant gas gauge" + depends on I2C + help + Say Y to include support for SBS battery driver for SBS-compliant + gas gauges. + +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_DA9052 + tristate "Dialog DA9052 Battery" + depends on PMIC_DA9052 + depends on BROKEN + help + Say Y here to enable support for batteries charger integrated into + DA9052 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_ANDROID + tristate "Battery driver for Android" + help + Say Y to enable generic support for battery charging according + to common Android policies. + This driver adds periodic battery level and health monitoring, + kernel log reporting and other debugging features, common board + battery file glue logic for battery/case temperature sensors, + etc. + +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 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_LP8727 + tristate "TI/National Semiconductor LP8727 charger driver" + depends on I2C + help + Say Y here to enable support for LP8727 Charger Driver. + +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 CHARGER_MANAGER + bool "Battery charger manager for multiple chargers" + depends on REGULATOR && RTC_CLASS + help + Say Y to enable charger-manager support, which allows multiple + chargers attached to a battery and multiple batteries attached to a + system. The charger-manager also can monitor charging status in + runtime and in suspend-to-RAM by waking up the system periodically + with help of suspend_again support. + +config CHARGER_MAX8997 + tristate "Maxim MAX8997/MAX8966 PMIC battery charger driver" + depends on MFD_MAX8997 && REGULATOR_MAX8997 + help + Say Y to enable support for the battery charger control sysfs and + platform data of MAX8997/LP3974 PMICs. + +config CHARGER_MAX8998 + tristate "Maxim MAX8998/LP3974 PMIC battery charger driver" + depends on MFD_MAX8998 && REGULATOR_MAX8998 + help + Say Y to enable support for the battery charger control sysfs and + platform data of MAX8998/LP3974 PMICs. + +config CHARGER_SMB347 + tristate "Summit Microelectronics SMB347 Battery Charger" + depends on I2C + help + Say Y to include support for Summit Microelectronics SMB347 + Battery Charger. + +config AB8500_BM + bool "AB8500 Battery Management Driver" + depends on AB8500_CORE && AB8500_GPADC + help + Say Y to include support for AB5500 battery management. + +config AB8500_BATTERY_THERM_ON_BATCTRL + bool "Thermistor connected on BATCTRL ADC" + depends on AB8500_BM + help + Say Y to enable battery temperature measurements using + thermistor connected on BATCTRL ADC. +endif # POWER_SUPPLY diff --git a/drivers/power/Makefile b/drivers/power/Makefile new file mode 100644 index 00000000..0e4417c7 --- /dev/null +++ b/drivers/power/Makefile @@ -0,0 +1,48 @@ +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_WMT) += wmt_battery/ + +obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o +obj-$(CONFIG_BATTERY_DS2780) += ds2780_battery.o +obj-$(CONFIG_BATTERY_DS2781) += ds2781_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_SBS) += sbs-battery.o +obj-$(CONFIG_BATTERY_BQ27x00) += bq27x00_battery.o +obj-$(CONFIG_BATTERY_DA9030) += da9030_battery.o +obj-$(CONFIG_BATTERY_DA9052) += da9052-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_AB8500_BM) += ab8500_charger.o ab8500_btemp.o ab8500_fg.o abx500_chargalg.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_LP8727) += lp8727_charger.o +obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o +obj-$(CONFIG_CHARGER_MANAGER) += charger-manager.o +obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o +obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o +obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o +obj-$(CONFIG_BATTERY_ANDROID) += android_battery.o diff --git a/drivers/power/ab8500_btemp.c b/drivers/power/ab8500_btemp.c new file mode 100644 index 00000000..d8bb9939 --- /dev/null +++ b/drivers/power/ab8500_btemp.c @@ -0,0 +1,1124 @@ +/* + * Copyright (C) ST-Ericsson SA 2012 + * + * Battery temperature driver for AB8500 + * + * License Terms: GNU General Public License v2 + * Author: + * Johan Palsson <johan.palsson@stericsson.com> + * Karl Komierowski <karl.komierowski@stericsson.com> + * Arun R Murthy <arun.murthy@stericsson.com> + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/completion.h> +#include <linux/workqueue.h> +#include <linux/mfd/abx500/ab8500.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab8500-bm.h> +#include <linux/mfd/abx500/ab8500-gpadc.h> +#include <linux/jiffies.h> + +#define VTVOUT_V 1800 + +#define BTEMP_THERMAL_LOW_LIMIT -10 +#define BTEMP_THERMAL_MED_LIMIT 0 +#define BTEMP_THERMAL_HIGH_LIMIT_52 52 +#define BTEMP_THERMAL_HIGH_LIMIT_57 57 +#define BTEMP_THERMAL_HIGH_LIMIT_62 62 + +#define BTEMP_BATCTRL_CURR_SRC_7UA 7 +#define BTEMP_BATCTRL_CURR_SRC_20UA 20 + +#define to_ab8500_btemp_device_info(x) container_of((x), \ + struct ab8500_btemp, btemp_psy); + +/** + * struct ab8500_btemp_interrupts - ab8500 interrupts + * @name: name of the interrupt + * @isr function pointer to the isr + */ +struct ab8500_btemp_interrupts { + char *name; + irqreturn_t (*isr)(int irq, void *data); +}; + +struct ab8500_btemp_events { + bool batt_rem; + bool btemp_high; + bool btemp_medhigh; + bool btemp_lowmed; + bool btemp_low; + bool ac_conn; + bool usb_conn; +}; + +struct ab8500_btemp_ranges { + int btemp_high_limit; + int btemp_med_limit; + int btemp_low_limit; +}; + +/** + * struct ab8500_btemp - ab8500 BTEMP device information + * @dev: Pointer to the structure device + * @node: List of AB8500 BTEMPs, hence prepared for reentrance + * @curr_source: What current source we use, in uA + * @bat_temp: Battery temperature in degree Celcius + * @prev_bat_temp Last dispatched battery temperature + * @parent: Pointer to the struct ab8500 + * @gpadc: Pointer to the struct gpadc + * @fg: Pointer to the struct fg + * @pdata: Pointer to the abx500_btemp platform data + * @bat: Pointer to the abx500_bm platform data + * @btemp_psy: Structure for BTEMP specific battery properties + * @events: Structure for information about events triggered + * @btemp_ranges: Battery temperature range structure + * @btemp_wq: Work queue for measuring the temperature periodically + * @btemp_periodic_work: Work for measuring the temperature periodically + */ +struct ab8500_btemp { + struct device *dev; + struct list_head node; + int curr_source; + int bat_temp; + int prev_bat_temp; + struct ab8500 *parent; + struct ab8500_gpadc *gpadc; + struct ab8500_fg *fg; + struct abx500_btemp_platform_data *pdata; + struct abx500_bm_data *bat; + struct power_supply btemp_psy; + struct ab8500_btemp_events events; + struct ab8500_btemp_ranges btemp_ranges; + struct workqueue_struct *btemp_wq; + struct delayed_work btemp_periodic_work; +}; + +/* BTEMP power supply properties */ +static enum power_supply_property ab8500_btemp_props[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_TEMP, +}; + +static LIST_HEAD(ab8500_btemp_list); + +/** + * ab8500_btemp_get() - returns a reference to the primary AB8500 BTEMP + * (i.e. the first BTEMP in the instance list) + */ +struct ab8500_btemp *ab8500_btemp_get(void) +{ + struct ab8500_btemp *btemp; + btemp = list_first_entry(&ab8500_btemp_list, struct ab8500_btemp, node); + + return btemp; +} + +/** + * ab8500_btemp_batctrl_volt_to_res() - convert batctrl voltage to resistance + * @di: pointer to the ab8500_btemp structure + * @v_batctrl: measured batctrl voltage + * @inst_curr: measured instant current + * + * This function returns the battery resistance that is + * derived from the BATCTRL voltage. + * Returns value in Ohms. + */ +static int ab8500_btemp_batctrl_volt_to_res(struct ab8500_btemp *di, + int v_batctrl, int inst_curr) +{ + int rbs; + + if (is_ab8500_1p1_or_earlier(di->parent)) { + /* + * For ABB cut1.0 and 1.1 BAT_CTRL is internally + * connected to 1.8V through a 450k resistor + */ + return (450000 * (v_batctrl)) / (1800 - v_batctrl); + } + + if (di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL) { + /* + * If the battery has internal NTC, we use the current + * source to calculate the resistance, 7uA or 20uA + */ + rbs = (v_batctrl * 1000 + - di->bat->gnd_lift_resistance * inst_curr) + / di->curr_source; + } else { + /* + * BAT_CTRL is internally + * connected to 1.8V through a 80k resistor + */ + rbs = (80000 * (v_batctrl)) / (1800 - v_batctrl); + } + + return rbs; +} + +/** + * ab8500_btemp_read_batctrl_voltage() - measure batctrl voltage + * @di: pointer to the ab8500_btemp structure + * + * This function returns the voltage on BATCTRL. Returns value in mV. + */ +static int ab8500_btemp_read_batctrl_voltage(struct ab8500_btemp *di) +{ + int vbtemp; + static int prev; + + vbtemp = ab8500_gpadc_convert(di->gpadc, BAT_CTRL); + if (vbtemp < 0) { + dev_err(di->dev, + "%s gpadc conversion failed, using previous value", + __func__); + return prev; + } + prev = vbtemp; + return vbtemp; +} + +/** + * ab8500_btemp_curr_source_enable() - enable/disable batctrl current source + * @di: pointer to the ab8500_btemp structure + * @enable: enable or disable the current source + * + * Enable or disable the current sources for the BatCtrl AD channel + */ +static int ab8500_btemp_curr_source_enable(struct ab8500_btemp *di, + bool enable) +{ + int curr; + int ret = 0; + + /* + * BATCTRL current sources are included on AB8500 cut2.0 + * and future versions + */ + if (is_ab8500_1p1_or_earlier(di->parent)) + return 0; + + /* Only do this for batteries with internal NTC */ + if (di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL && enable) { + if (di->curr_source == BTEMP_BATCTRL_CURR_SRC_7UA) + curr = BAT_CTRL_7U_ENA; + else + curr = BAT_CTRL_20U_ENA; + + dev_dbg(di->dev, "Set BATCTRL %duA\n", di->curr_source); + + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + FORCE_BAT_CTRL_CMP_HIGH, FORCE_BAT_CTRL_CMP_HIGH); + if (ret) { + dev_err(di->dev, "%s failed setting cmp_force\n", + __func__); + return ret; + } + + /* + * We have to wait one 32kHz cycle before enabling + * the current source, since ForceBatCtrlCmpHigh needs + * to be written in a separate cycle + */ + udelay(32); + + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + FORCE_BAT_CTRL_CMP_HIGH | curr); + if (ret) { + dev_err(di->dev, "%s failed enabling current source\n", + __func__); + goto disable_curr_source; + } + } else if (di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL && !enable) { + dev_dbg(di->dev, "Disable BATCTRL curr source\n"); + + /* Write 0 to the curr bits */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA, + ~(BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA)); + if (ret) { + dev_err(di->dev, "%s failed disabling current source\n", + __func__); + goto disable_curr_source; + } + + /* Enable Pull-Up and comparator */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA, + BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA); + if (ret) { + dev_err(di->dev, "%s failed enabling PU and comp\n", + __func__); + goto enable_pu_comp; + } + + /* + * We have to wait one 32kHz cycle before disabling + * ForceBatCtrlCmpHigh since this needs to be written + * in a separate cycle + */ + udelay(32); + + /* Disable 'force comparator' */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + FORCE_BAT_CTRL_CMP_HIGH, ~FORCE_BAT_CTRL_CMP_HIGH); + if (ret) { + dev_err(di->dev, "%s failed disabling force comp\n", + __func__); + goto disable_force_comp; + } + } + return ret; + + /* + * We have to try unsetting FORCE_BAT_CTRL_CMP_HIGH one more time + * if we got an error above + */ +disable_curr_source: + /* Write 0 to the curr bits */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA, + ~(BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA)); + if (ret) { + dev_err(di->dev, "%s failed disabling current source\n", + __func__); + return ret; + } +enable_pu_comp: + /* Enable Pull-Up and comparator */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA, + BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA); + if (ret) { + dev_err(di->dev, "%s failed enabling PU and comp\n", + __func__); + return ret; + } + +disable_force_comp: + /* + * We have to wait one 32kHz cycle before disabling + * ForceBatCtrlCmpHigh since this needs to be written + * in a separate cycle + */ + udelay(32); + + /* Disable 'force comparator' */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + FORCE_BAT_CTRL_CMP_HIGH, ~FORCE_BAT_CTRL_CMP_HIGH); + if (ret) { + dev_err(di->dev, "%s failed disabling force comp\n", + __func__); + return ret; + } + + return ret; +} + +/** + * ab8500_btemp_get_batctrl_res() - get battery resistance + * @di: pointer to the ab8500_btemp structure + * + * This function returns the battery pack identification resistance. + * Returns value in Ohms. + */ +static int ab8500_btemp_get_batctrl_res(struct ab8500_btemp *di) +{ + int ret; + int batctrl = 0; + int res; + int inst_curr; + int i; + + /* + * BATCTRL current sources are included on AB8500 cut2.0 + * and future versions + */ + ret = ab8500_btemp_curr_source_enable(di, true); + if (ret) { + dev_err(di->dev, "%s curr source enabled failed\n", __func__); + return ret; + } + + if (!di->fg) + di->fg = ab8500_fg_get(); + if (!di->fg) { + dev_err(di->dev, "No fg found\n"); + return -EINVAL; + } + + ret = ab8500_fg_inst_curr_start(di->fg); + + if (ret) { + dev_err(di->dev, "Failed to start current measurement\n"); + return ret; + } + + /* + * Since there is no interrupt when current measurement is done, + * loop for over 250ms (250ms is one sample conversion time + * with 32.768 Khz RTC clock). Note that a stop time must be set + * since the ab8500_btemp_read_batctrl_voltage call can block and + * take an unknown amount of time to complete. + */ + i = 0; + + do { + batctrl += ab8500_btemp_read_batctrl_voltage(di); + i++; + msleep(20); + } while (!ab8500_fg_inst_curr_done(di->fg)); + batctrl /= i; + + ret = ab8500_fg_inst_curr_finalize(di->fg, &inst_curr); + if (ret) { + dev_err(di->dev, "Failed to finalize current measurement\n"); + return ret; + } + + res = ab8500_btemp_batctrl_volt_to_res(di, batctrl, inst_curr); + + ret = ab8500_btemp_curr_source_enable(di, false); + if (ret) { + dev_err(di->dev, "%s curr source disable failed\n", __func__); + return ret; + } + + dev_dbg(di->dev, "%s batctrl: %d res: %d inst_curr: %d samples: %d\n", + __func__, batctrl, res, inst_curr, i); + + return res; +} + +/** + * ab8500_btemp_res_to_temp() - resistance to temperature + * @di: pointer to the ab8500_btemp structure + * @tbl: pointer to the resiatance to temperature table + * @tbl_size: size of the resistance to temperature table + * @res: resistance to calculate the temperature from + * + * This function returns the battery temperature in degrees Celcius + * based on the NTC resistance. + */ +static int ab8500_btemp_res_to_temp(struct ab8500_btemp *di, + const struct abx500_res_to_temp *tbl, int tbl_size, int res) +{ + int i, temp; + /* + * Calculate the formula for the straight line + * Simple interpolation if we are within + * the resistance table limits, extrapolate + * if resistance is outside the limits. + */ + if (res > tbl[0].resist) + i = 0; + else if (res <= tbl[tbl_size - 1].resist) + i = tbl_size - 2; + else { + i = 0; + while (!(res <= tbl[i].resist && + res > tbl[i + 1].resist)) + i++; + } + + temp = tbl[i].temp + ((tbl[i + 1].temp - tbl[i].temp) * + (res - tbl[i].resist)) / (tbl[i + 1].resist - tbl[i].resist); + return temp; +} + +/** + * ab8500_btemp_measure_temp() - measure battery temperature + * @di: pointer to the ab8500_btemp structure + * + * Returns battery temperature (on success) else the previous temperature + */ +static int ab8500_btemp_measure_temp(struct ab8500_btemp *di) +{ + int temp; + static int prev; + int rbat, rntc, vntc; + u8 id; + + id = di->bat->batt_id; + + if (di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL && + id != BATTERY_UNKNOWN) { + + rbat = ab8500_btemp_get_batctrl_res(di); + if (rbat < 0) { + dev_err(di->dev, "%s get batctrl res failed\n", + __func__); + /* + * Return out-of-range temperature so that + * charging is stopped + */ + return BTEMP_THERMAL_LOW_LIMIT; + } + + temp = ab8500_btemp_res_to_temp(di, + di->bat->bat_type[id].r_to_t_tbl, + di->bat->bat_type[id].n_temp_tbl_elements, rbat); + } else { + vntc = ab8500_gpadc_convert(di->gpadc, BTEMP_BALL); + if (vntc < 0) { + dev_err(di->dev, + "%s gpadc conversion failed," + " using previous value\n", __func__); + return prev; + } + /* + * The PCB NTC is sourced from VTVOUT via a 230kOhm + * resistor. + */ + rntc = 230000 * vntc / (VTVOUT_V - vntc); + + temp = ab8500_btemp_res_to_temp(di, + di->bat->bat_type[id].r_to_t_tbl, + di->bat->bat_type[id].n_temp_tbl_elements, rntc); + prev = temp; + } + dev_dbg(di->dev, "Battery temperature is %d\n", temp); + return temp; +} + +/** + * ab8500_btemp_id() - Identify the connected battery + * @di: pointer to the ab8500_btemp structure + * + * This function will try to identify the battery by reading the ID + * resistor. Some brands use a combined ID resistor with a NTC resistor to + * both be able to identify and to read the temperature of it. + */ +static int ab8500_btemp_id(struct ab8500_btemp *di) +{ + int res; + u8 i; + + di->curr_source = BTEMP_BATCTRL_CURR_SRC_7UA; + di->bat->batt_id = BATTERY_UNKNOWN; + + res = ab8500_btemp_get_batctrl_res(di); + if (res < 0) { + dev_err(di->dev, "%s get batctrl res failed\n", __func__); + return -ENXIO; + } + + /* BATTERY_UNKNOWN is defined on position 0, skip it! */ + for (i = BATTERY_UNKNOWN + 1; i < di->bat->n_btypes; i++) { + if ((res <= di->bat->bat_type[i].resis_high) && + (res >= di->bat->bat_type[i].resis_low)) { + dev_dbg(di->dev, "Battery detected on %s" + " low %d < res %d < high: %d" + " index: %d\n", + di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL ? + "BATCTRL" : "BATTEMP", + di->bat->bat_type[i].resis_low, res, + di->bat->bat_type[i].resis_high, i); + + di->bat->batt_id = i; + break; + } + } + + if (di->bat->batt_id == BATTERY_UNKNOWN) { + dev_warn(di->dev, "Battery identified as unknown" + ", resistance %d Ohm\n", res); + return -ENXIO; + } + + /* + * We only have to change current source if the + * detected type is Type 1, else we use the 7uA source + */ + if (di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL && + di->bat->batt_id == 1) { + dev_dbg(di->dev, "Set BATCTRL current source to 20uA\n"); + di->curr_source = BTEMP_BATCTRL_CURR_SRC_20UA; + } + + return di->bat->batt_id; +} + +/** + * ab8500_btemp_periodic_work() - Measuring the temperature periodically + * @work: pointer to the work_struct structure + * + * Work function for measuring the temperature periodically + */ +static void ab8500_btemp_periodic_work(struct work_struct *work) +{ + int interval; + struct ab8500_btemp *di = container_of(work, + struct ab8500_btemp, btemp_periodic_work.work); + + di->bat_temp = ab8500_btemp_measure_temp(di); + + if (di->bat_temp != di->prev_bat_temp) { + di->prev_bat_temp = di->bat_temp; + power_supply_changed(&di->btemp_psy); + } + + if (di->events.ac_conn || di->events.usb_conn) + interval = di->bat->temp_interval_chg; + else + interval = di->bat->temp_interval_nochg; + + /* Schedule a new measurement */ + queue_delayed_work(di->btemp_wq, + &di->btemp_periodic_work, + round_jiffies(interval * HZ)); +} + +/** + * ab8500_btemp_batctrlindb_handler() - battery removal detected + * @irq: interrupt number + * @_di: void pointer that has to address of ab8500_btemp + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_btemp_batctrlindb_handler(int irq, void *_di) +{ + struct ab8500_btemp *di = _di; + dev_err(di->dev, "Battery removal detected!\n"); + + di->events.batt_rem = true; + power_supply_changed(&di->btemp_psy); + + return IRQ_HANDLED; +} + +/** + * ab8500_btemp_templow_handler() - battery temp lower than 10 degrees + * @irq: interrupt number + * @_di: void pointer that has to address of ab8500_btemp + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_btemp_templow_handler(int irq, void *_di) +{ + struct ab8500_btemp *di = _di; + + if (is_ab8500_2p0_or_earlier(di->parent)) { + dev_dbg(di->dev, "Ignore false btemp low irq" + " for ABB cut 1.0, 1.1 and 2.0\n"); + } else { + dev_crit(di->dev, "Battery temperature lower than -10deg c\n"); + + di->events.btemp_low = true; + di->events.btemp_high = false; + di->events.btemp_medhigh = false; + di->events.btemp_lowmed = false; + power_supply_changed(&di->btemp_psy); + } + + return IRQ_HANDLED; +} + +/** + * ab8500_btemp_temphigh_handler() - battery temp higher than max temp + * @irq: interrupt number + * @_di: void pointer that has to address of ab8500_btemp + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_btemp_temphigh_handler(int irq, void *_di) +{ + struct ab8500_btemp *di = _di; + + dev_crit(di->dev, "Battery temperature is higher than MAX temp\n"); + + di->events.btemp_high = true; + di->events.btemp_medhigh = false; + di->events.btemp_lowmed = false; + di->events.btemp_low = false; + power_supply_changed(&di->btemp_psy); + + return IRQ_HANDLED; +} + +/** + * ab8500_btemp_lowmed_handler() - battery temp between low and medium + * @irq: interrupt number + * @_di: void pointer that has to address of ab8500_btemp + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_btemp_lowmed_handler(int irq, void *_di) +{ + struct ab8500_btemp *di = _di; + + dev_dbg(di->dev, "Battery temperature is between low and medium\n"); + + di->events.btemp_lowmed = true; + di->events.btemp_medhigh = false; + di->events.btemp_high = false; + di->events.btemp_low = false; + power_supply_changed(&di->btemp_psy); + + return IRQ_HANDLED; +} + +/** + * ab8500_btemp_medhigh_handler() - battery temp between medium and high + * @irq: interrupt number + * @_di: void pointer that has to address of ab8500_btemp + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_btemp_medhigh_handler(int irq, void *_di) +{ + struct ab8500_btemp *di = _di; + + dev_dbg(di->dev, "Battery temperature is between medium and high\n"); + + di->events.btemp_medhigh = true; + di->events.btemp_lowmed = false; + di->events.btemp_high = false; + di->events.btemp_low = false; + power_supply_changed(&di->btemp_psy); + + return IRQ_HANDLED; +} + +/** + * ab8500_btemp_periodic() - Periodic temperature measurements + * @di: pointer to the ab8500_btemp structure + * @enable: enable or disable periodic temperature measurements + * + * Starts of stops periodic temperature measurements. Periodic measurements + * should only be done when a charger is connected. + */ +static void ab8500_btemp_periodic(struct ab8500_btemp *di, + bool enable) +{ + dev_dbg(di->dev, "Enable periodic temperature measurements: %d\n", + enable); + /* + * Make sure a new measurement is done directly by cancelling + * any pending work + */ + cancel_delayed_work_sync(&di->btemp_periodic_work); + + if (enable) + queue_delayed_work(di->btemp_wq, &di->btemp_periodic_work, 0); +} + +/** + * ab8500_btemp_get_temp() - get battery temperature + * @di: pointer to the ab8500_btemp structure + * + * Returns battery temperature + */ +static int ab8500_btemp_get_temp(struct ab8500_btemp *di) +{ + int temp = 0; + + /* + * The BTEMP events are not reliabe on AB8500 cut2.0 + * and prior versions + */ + if (is_ab8500_2p0_or_earlier(di->parent)) { + temp = di->bat_temp * 10; + } else { + if (di->events.btemp_low) { + if (temp > di->btemp_ranges.btemp_low_limit) + temp = di->btemp_ranges.btemp_low_limit; + else + temp = di->bat_temp * 10; + } else if (di->events.btemp_high) { + if (temp < di->btemp_ranges.btemp_high_limit) + temp = di->btemp_ranges.btemp_high_limit; + else + temp = di->bat_temp * 10; + } else if (di->events.btemp_lowmed) { + if (temp > di->btemp_ranges.btemp_med_limit) + temp = di->btemp_ranges.btemp_med_limit; + else + temp = di->bat_temp * 10; + } else if (di->events.btemp_medhigh) { + if (temp < di->btemp_ranges.btemp_med_limit) + temp = di->btemp_ranges.btemp_med_limit; + else + temp = di->bat_temp * 10; + } else + temp = di->bat_temp * 10; + } + return temp; +} + +/** + * ab8500_btemp_get_batctrl_temp() - get the temperature + * @btemp: pointer to the btemp structure + * + * Returns the batctrl temperature in millidegrees + */ +int ab8500_btemp_get_batctrl_temp(struct ab8500_btemp *btemp) +{ + return btemp->bat_temp * 1000; +} + +/** + * ab8500_btemp_get_property() - get the btemp properties + * @psy: pointer to the power_supply structure + * @psp: pointer to the power_supply_property structure + * @val: pointer to the power_supply_propval union + * + * This function gets called when an application tries to get the btemp + * properties by reading the sysfs files. + * online: presence of the battery + * present: presence of the battery + * technology: battery technology + * temp: battery temperature + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_btemp_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ab8500_btemp *di; + + di = to_ab8500_btemp_device_info(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + case POWER_SUPPLY_PROP_ONLINE: + if (di->events.batt_rem) + val->intval = 0; + else + val->intval = 1; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = di->bat->bat_type[di->bat->batt_id].name; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = ab8500_btemp_get_temp(di); + break; + default: + return -EINVAL; + } + return 0; +} + +static int ab8500_btemp_get_ext_psy_data(struct device *dev, void *data) +{ + struct power_supply *psy; + struct power_supply *ext; + struct ab8500_btemp *di; + union power_supply_propval ret; + int i, j; + bool psy_found = false; + + psy = (struct power_supply *)data; + ext = dev_get_drvdata(dev); + di = to_ab8500_btemp_device_info(psy); + + /* + * For all psy where the name of your driver + * appears in any supplied_to + */ + for (i = 0; i < ext->num_supplicants; i++) { + if (!strcmp(ext->supplied_to[i], psy->name)) + psy_found = true; + } + + if (!psy_found) + return 0; + + /* Go through all properties for the psy */ + for (j = 0; j < ext->num_properties; j++) { + enum power_supply_property prop; + prop = ext->properties[j]; + + if (ext->get_property(ext, prop, &ret)) + continue; + + switch (prop) { + case POWER_SUPPLY_PROP_PRESENT: + switch (ext->type) { + case POWER_SUPPLY_TYPE_MAINS: + /* AC disconnected */ + if (!ret.intval && di->events.ac_conn) { + di->events.ac_conn = false; + } + /* AC connected */ + else if (ret.intval && !di->events.ac_conn) { + di->events.ac_conn = true; + if (!di->events.usb_conn) + ab8500_btemp_periodic(di, true); + } + break; + case POWER_SUPPLY_TYPE_USB: + /* USB disconnected */ + if (!ret.intval && di->events.usb_conn) { + di->events.usb_conn = false; + } + /* USB connected */ + else if (ret.intval && !di->events.usb_conn) { + di->events.usb_conn = true; + if (!di->events.ac_conn) + ab8500_btemp_periodic(di, true); + } + break; + default: + break; + } + break; + default: + break; + } + } + return 0; +} + +/** + * ab8500_btemp_external_power_changed() - callback for power supply changes + * @psy: pointer to the structure power_supply + * + * This function is pointing to the function pointer external_power_changed + * of the structure power_supply. + * This function gets executed when there is a change in the external power + * supply to the btemp. + */ +static void ab8500_btemp_external_power_changed(struct power_supply *psy) +{ + struct ab8500_btemp *di = to_ab8500_btemp_device_info(psy); + + class_for_each_device(power_supply_class, NULL, + &di->btemp_psy, ab8500_btemp_get_ext_psy_data); +} + +/* ab8500 btemp driver interrupts and their respective isr */ +static struct ab8500_btemp_interrupts ab8500_btemp_irq[] = { + {"BAT_CTRL_INDB", ab8500_btemp_batctrlindb_handler}, + {"BTEMP_LOW", ab8500_btemp_templow_handler}, + {"BTEMP_HIGH", ab8500_btemp_temphigh_handler}, + {"BTEMP_LOW_MEDIUM", ab8500_btemp_lowmed_handler}, + {"BTEMP_MEDIUM_HIGH", ab8500_btemp_medhigh_handler}, +}; + +#if defined(CONFIG_PM) +static int ab8500_btemp_resume(struct platform_device *pdev) +{ + struct ab8500_btemp *di = platform_get_drvdata(pdev); + + ab8500_btemp_periodic(di, true); + + return 0; +} + +static int ab8500_btemp_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct ab8500_btemp *di = platform_get_drvdata(pdev); + + ab8500_btemp_periodic(di, false); + + return 0; +} +#else +#define ab8500_btemp_suspend NULL +#define ab8500_btemp_resume NULL +#endif + +static int __devexit ab8500_btemp_remove(struct platform_device *pdev) +{ + struct ab8500_btemp *di = platform_get_drvdata(pdev); + int i, irq; + + /* Disable interrupts */ + for (i = 0; i < ARRAY_SIZE(ab8500_btemp_irq); i++) { + irq = platform_get_irq_byname(pdev, ab8500_btemp_irq[i].name); + free_irq(irq, di); + } + + /* Delete the work queue */ + destroy_workqueue(di->btemp_wq); + + flush_scheduled_work(); + power_supply_unregister(&di->btemp_psy); + platform_set_drvdata(pdev, NULL); + kfree(di); + + return 0; +} + +static int __devinit ab8500_btemp_probe(struct platform_device *pdev) +{ + int irq, i, ret = 0; + u8 val; + struct abx500_bm_plat_data *plat_data; + + struct ab8500_btemp *di = + kzalloc(sizeof(struct ab8500_btemp), GFP_KERNEL); + if (!di) + return -ENOMEM; + + /* get parent data */ + di->dev = &pdev->dev; + di->parent = dev_get_drvdata(pdev->dev.parent); + di->gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + + /* get btemp specific platform data */ + plat_data = pdev->dev.platform_data; + di->pdata = plat_data->btemp; + if (!di->pdata) { + dev_err(di->dev, "no btemp platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + + /* get battery specific platform data */ + di->bat = plat_data->battery; + if (!di->bat) { + dev_err(di->dev, "no battery platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + + /* BTEMP supply */ + di->btemp_psy.name = "ab8500_btemp"; + di->btemp_psy.type = POWER_SUPPLY_TYPE_BATTERY; + di->btemp_psy.properties = ab8500_btemp_props; + di->btemp_psy.num_properties = ARRAY_SIZE(ab8500_btemp_props); + di->btemp_psy.get_property = ab8500_btemp_get_property; + di->btemp_psy.supplied_to = di->pdata->supplied_to; + di->btemp_psy.num_supplicants = di->pdata->num_supplicants; + di->btemp_psy.external_power_changed = + ab8500_btemp_external_power_changed; + + + /* Create a work queue for the btemp */ + di->btemp_wq = + create_singlethread_workqueue("ab8500_btemp_wq"); + if (di->btemp_wq == NULL) { + dev_err(di->dev, "failed to create work queue\n"); + goto free_device_info; + } + + /* Init work for measuring temperature periodically */ + INIT_DELAYED_WORK_DEFERRABLE(&di->btemp_periodic_work, + ab8500_btemp_periodic_work); + + /* Identify the battery */ + if (ab8500_btemp_id(di) < 0) + dev_warn(di->dev, "failed to identify the battery\n"); + + /* Set BTEMP thermal limits. Low and Med are fixed */ + di->btemp_ranges.btemp_low_limit = BTEMP_THERMAL_LOW_LIMIT; + di->btemp_ranges.btemp_med_limit = BTEMP_THERMAL_MED_LIMIT; + + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_BTEMP_HIGH_TH, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + goto free_btemp_wq; + } + switch (val) { + case BTEMP_HIGH_TH_57_0: + case BTEMP_HIGH_TH_57_1: + di->btemp_ranges.btemp_high_limit = + BTEMP_THERMAL_HIGH_LIMIT_57; + break; + case BTEMP_HIGH_TH_52: + di->btemp_ranges.btemp_high_limit = + BTEMP_THERMAL_HIGH_LIMIT_52; + break; + case BTEMP_HIGH_TH_62: + di->btemp_ranges.btemp_high_limit = + BTEMP_THERMAL_HIGH_LIMIT_62; + break; + } + + /* Register BTEMP power supply class */ + ret = power_supply_register(di->dev, &di->btemp_psy); + if (ret) { + dev_err(di->dev, "failed to register BTEMP psy\n"); + goto free_btemp_wq; + } + + /* Register interrupts */ + for (i = 0; i < ARRAY_SIZE(ab8500_btemp_irq); i++) { + irq = platform_get_irq_byname(pdev, ab8500_btemp_irq[i].name); + ret = request_threaded_irq(irq, NULL, ab8500_btemp_irq[i].isr, + IRQF_SHARED | IRQF_NO_SUSPEND, + ab8500_btemp_irq[i].name, di); + + if (ret) { + dev_err(di->dev, "failed to request %s IRQ %d: %d\n" + , ab8500_btemp_irq[i].name, irq, ret); + goto free_irq; + } + dev_dbg(di->dev, "Requested %s IRQ %d: %d\n", + ab8500_btemp_irq[i].name, irq, ret); + } + + platform_set_drvdata(pdev, di); + + /* Kick off periodic temperature measurements */ + ab8500_btemp_periodic(di, true); + list_add_tail(&di->node, &ab8500_btemp_list); + + return ret; + +free_irq: + power_supply_unregister(&di->btemp_psy); + + /* We also have to free all successfully registered irqs */ + for (i = i - 1; i >= 0; i--) { + irq = platform_get_irq_byname(pdev, ab8500_btemp_irq[i].name); + free_irq(irq, di); + } +free_btemp_wq: + destroy_workqueue(di->btemp_wq); +free_device_info: + kfree(di); + + return ret; +} + +static struct platform_driver ab8500_btemp_driver = { + .probe = ab8500_btemp_probe, + .remove = __devexit_p(ab8500_btemp_remove), + .suspend = ab8500_btemp_suspend, + .resume = ab8500_btemp_resume, + .driver = { + .name = "ab8500-btemp", + .owner = THIS_MODULE, + }, +}; + +static int __init ab8500_btemp_init(void) +{ + return platform_driver_register(&ab8500_btemp_driver); +} + +static void __exit ab8500_btemp_exit(void) +{ + platform_driver_unregister(&ab8500_btemp_driver); +} + +subsys_initcall_sync(ab8500_btemp_init); +module_exit(ab8500_btemp_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Johan Palsson, Karl Komierowski, Arun R Murthy"); +MODULE_ALIAS("platform:ab8500-btemp"); +MODULE_DESCRIPTION("AB8500 battery temperature driver"); diff --git a/drivers/power/ab8500_charger.c b/drivers/power/ab8500_charger.c new file mode 100644 index 00000000..e2b4accb --- /dev/null +++ b/drivers/power/ab8500_charger.c @@ -0,0 +1,2789 @@ +/* + * Copyright (C) ST-Ericsson SA 2012 + * + * Charger driver for AB8500 + * + * License Terms: GNU General Public License v2 + * Author: + * Johan Palsson <johan.palsson@stericsson.com> + * Karl Komierowski <karl.komierowski@stericsson.com> + * Arun R Murthy <arun.murthy@stericsson.com> + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/completion.h> +#include <linux/regulator/consumer.h> +#include <linux/err.h> +#include <linux/workqueue.h> +#include <linux/kobject.h> +#include <linux/mfd/abx500/ab8500.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab8500-bm.h> +#include <linux/mfd/abx500/ab8500-gpadc.h> +#include <linux/mfd/abx500/ux500_chargalg.h> +#include <linux/usb/otg.h> + +/* Charger constants */ +#define NO_PW_CONN 0 +#define AC_PW_CONN 1 +#define USB_PW_CONN 2 + +#define MAIN_WDOG_ENA 0x01 +#define MAIN_WDOG_KICK 0x02 +#define MAIN_WDOG_DIS 0x00 +#define CHARG_WD_KICK 0x01 +#define MAIN_CH_ENA 0x01 +#define MAIN_CH_NO_OVERSHOOT_ENA_N 0x02 +#define USB_CH_ENA 0x01 +#define USB_CHG_NO_OVERSHOOT_ENA_N 0x02 +#define MAIN_CH_DET 0x01 +#define MAIN_CH_CV_ON 0x04 +#define USB_CH_CV_ON 0x08 +#define VBUS_DET_DBNC100 0x02 +#define VBUS_DET_DBNC1 0x01 +#define OTP_ENABLE_WD 0x01 + +#define MAIN_CH_INPUT_CURR_SHIFT 4 +#define VBUS_IN_CURR_LIM_SHIFT 4 + +#define LED_INDICATOR_PWM_ENA 0x01 +#define LED_INDICATOR_PWM_DIS 0x00 +#define LED_IND_CUR_5MA 0x04 +#define LED_INDICATOR_PWM_DUTY_252_256 0xBF + +/* HW failure constants */ +#define MAIN_CH_TH_PROT 0x02 +#define VBUS_CH_NOK 0x08 +#define USB_CH_TH_PROT 0x02 +#define VBUS_OVV_TH 0x01 +#define MAIN_CH_NOK 0x01 +#define VBUS_DET 0x80 + +/* UsbLineStatus register bit masks */ +#define AB8500_USB_LINK_STATUS 0x78 +#define AB8500_STD_HOST_SUSP 0x18 + +/* Watchdog timeout constant */ +#define WD_TIMER 0x30 /* 4min */ +#define WD_KICK_INTERVAL (60 * HZ) + +/* Lowest charger voltage is 3.39V -> 0x4E */ +#define LOW_VOLT_REG 0x4E + +/* UsbLineStatus register - usb types */ +enum ab8500_charger_link_status { + USB_STAT_NOT_CONFIGURED, + USB_STAT_STD_HOST_NC, + USB_STAT_STD_HOST_C_NS, + USB_STAT_STD_HOST_C_S, + USB_STAT_HOST_CHG_NM, + USB_STAT_HOST_CHG_HS, + USB_STAT_HOST_CHG_HS_CHIRP, + USB_STAT_DEDICATED_CHG, + USB_STAT_ACA_RID_A, + USB_STAT_ACA_RID_B, + USB_STAT_ACA_RID_C_NM, + USB_STAT_ACA_RID_C_HS, + USB_STAT_ACA_RID_C_HS_CHIRP, + USB_STAT_HM_IDGND, + USB_STAT_RESERVED, + USB_STAT_NOT_VALID_LINK, +}; + +enum ab8500_usb_state { + AB8500_BM_USB_STATE_RESET_HS, /* HighSpeed Reset */ + AB8500_BM_USB_STATE_RESET_FS, /* FullSpeed/LowSpeed Reset */ + AB8500_BM_USB_STATE_CONFIGURED, + AB8500_BM_USB_STATE_SUSPEND, + AB8500_BM_USB_STATE_RESUME, + AB8500_BM_USB_STATE_MAX, +}; + +/* VBUS input current limits supported in AB8500 in mA */ +#define USB_CH_IP_CUR_LVL_0P05 50 +#define USB_CH_IP_CUR_LVL_0P09 98 +#define USB_CH_IP_CUR_LVL_0P19 193 +#define USB_CH_IP_CUR_LVL_0P29 290 +#define USB_CH_IP_CUR_LVL_0P38 380 +#define USB_CH_IP_CUR_LVL_0P45 450 +#define USB_CH_IP_CUR_LVL_0P5 500 +#define USB_CH_IP_CUR_LVL_0P6 600 +#define USB_CH_IP_CUR_LVL_0P7 700 +#define USB_CH_IP_CUR_LVL_0P8 800 +#define USB_CH_IP_CUR_LVL_0P9 900 +#define USB_CH_IP_CUR_LVL_1P0 1000 +#define USB_CH_IP_CUR_LVL_1P1 1100 +#define USB_CH_IP_CUR_LVL_1P3 1300 +#define USB_CH_IP_CUR_LVL_1P4 1400 +#define USB_CH_IP_CUR_LVL_1P5 1500 + +#define VBAT_TRESH_IP_CUR_RED 3800 + +#define to_ab8500_charger_usb_device_info(x) container_of((x), \ + struct ab8500_charger, usb_chg) +#define to_ab8500_charger_ac_device_info(x) container_of((x), \ + struct ab8500_charger, ac_chg) + +/** + * struct ab8500_charger_interrupts - ab8500 interupts + * @name: name of the interrupt + * @isr function pointer to the isr + */ +struct ab8500_charger_interrupts { + char *name; + irqreturn_t (*isr)(int irq, void *data); +}; + +struct ab8500_charger_info { + int charger_connected; + int charger_online; + int charger_voltage; + int cv_active; + bool wd_expired; +}; + +struct ab8500_charger_event_flags { + bool mainextchnotok; + bool main_thermal_prot; + bool usb_thermal_prot; + bool vbus_ovv; + bool usbchargernotok; + bool chgwdexp; + bool vbus_collapse; +}; + +struct ab8500_charger_usb_state { + bool usb_changed; + int usb_current; + enum ab8500_usb_state state; + spinlock_t usb_lock; +}; + +/** + * struct ab8500_charger - ab8500 Charger device information + * @dev: Pointer to the structure device + * @max_usb_in_curr: Max USB charger input current + * @vbus_detected: VBUS detected + * @vbus_detected_start: + * VBUS detected during startup + * @ac_conn: This will be true when the AC charger has been plugged + * @vddadc_en_ac: Indicate if VDD ADC supply is enabled because AC + * charger is enabled + * @vddadc_en_usb: Indicate if VDD ADC supply is enabled because USB + * charger is enabled + * @vbat Battery voltage + * @old_vbat Previously measured battery voltage + * @autopower Indicate if we should have automatic pwron after pwrloss + * @parent: Pointer to the struct ab8500 + * @gpadc: Pointer to the struct gpadc + * @pdata: Pointer to the abx500_charger platform data + * @bat: Pointer to the abx500_bm platform data + * @flags: Structure for information about events triggered + * @usb_state: Structure for usb stack information + * @ac_chg: AC charger power supply + * @usb_chg: USB charger power supply + * @ac: Structure that holds the AC charger properties + * @usb: Structure that holds the USB charger properties + * @regu: Pointer to the struct regulator + * @charger_wq: Work queue for the IRQs and checking HW state + * @check_vbat_work Work for checking vbat threshold to adjust vbus current + * @check_hw_failure_work: Work for checking HW state + * @check_usbchgnotok_work: Work for checking USB charger not ok status + * @kick_wd_work: Work for kicking the charger watchdog in case + * of ABB rev 1.* due to the watchog logic bug + * @ac_work: Work for checking AC charger connection + * @detect_usb_type_work: Work for detecting the USB type connected + * @usb_link_status_work: Work for checking the new USB link status + * @usb_state_changed_work: Work for checking USB state + * @check_main_thermal_prot_work: + * Work for checking Main thermal status + * @check_usb_thermal_prot_work: + * Work for checking USB thermal status + */ +struct ab8500_charger { + struct device *dev; + int max_usb_in_curr; + bool vbus_detected; + bool vbus_detected_start; + bool ac_conn; + bool vddadc_en_ac; + bool vddadc_en_usb; + int vbat; + int old_vbat; + bool autopower; + struct ab8500 *parent; + struct ab8500_gpadc *gpadc; + struct abx500_charger_platform_data *pdata; + struct abx500_bm_data *bat; + struct ab8500_charger_event_flags flags; + struct ab8500_charger_usb_state usb_state; + struct ux500_charger ac_chg; + struct ux500_charger usb_chg; + struct ab8500_charger_info ac; + struct ab8500_charger_info usb; + struct regulator *regu; + struct workqueue_struct *charger_wq; + struct delayed_work check_vbat_work; + struct delayed_work check_hw_failure_work; + struct delayed_work check_usbchgnotok_work; + struct delayed_work kick_wd_work; + struct work_struct ac_work; + struct work_struct detect_usb_type_work; + struct work_struct usb_link_status_work; + struct work_struct usb_state_changed_work; + struct work_struct check_main_thermal_prot_work; + struct work_struct check_usb_thermal_prot_work; + struct usb_phy *usb_phy; + struct notifier_block nb; +}; + +/* AC properties */ +static enum power_supply_property ab8500_charger_ac_props[] = { + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_CURRENT_NOW, +}; + +/* USB properties */ +static enum power_supply_property ab8500_charger_usb_props[] = { + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_CURRENT_NOW, +}; + +/** + * ab8500_power_loss_handling - set how we handle powerloss. + * @di: pointer to the ab8500_charger structure + * + * Magic nummbers are from STE HW department. + */ +static void ab8500_power_loss_handling(struct ab8500_charger *di) +{ + u8 reg; + int ret; + + dev_dbg(di->dev, "Autopower : %d\n", di->autopower); + + /* read the autopower register */ + ret = abx500_get_register_interruptible(di->dev, 0x15, 0x00, ®); + if (ret) { + dev_err(di->dev, "%d write failed\n", __LINE__); + return; + } + + /* enable the OPT emulation registers */ + ret = abx500_set_register_interruptible(di->dev, 0x11, 0x00, 0x2); + if (ret) { + dev_err(di->dev, "%d write failed\n", __LINE__); + return; + } + + if (di->autopower) + reg |= 0x8; + else + reg &= ~0x8; + + /* write back the changed value to autopower reg */ + ret = abx500_set_register_interruptible(di->dev, 0x15, 0x00, reg); + if (ret) { + dev_err(di->dev, "%d write failed\n", __LINE__); + return; + } + + /* disable the set OTP registers again */ + ret = abx500_set_register_interruptible(di->dev, 0x11, 0x00, 0x0); + if (ret) { + dev_err(di->dev, "%d write failed\n", __LINE__); + return; + } +} + +/** + * ab8500_power_supply_changed - a wrapper with local extentions for + * power_supply_changed + * @di: pointer to the ab8500_charger structure + * @psy: pointer to power_supply_that have changed. + * + */ +static void ab8500_power_supply_changed(struct ab8500_charger *di, + struct power_supply *psy) +{ + if (di->pdata->autopower_cfg) { + if (!di->usb.charger_connected && + !di->ac.charger_connected && + di->autopower) { + di->autopower = false; + ab8500_power_loss_handling(di); + } else if (!di->autopower && + (di->ac.charger_connected || + di->usb.charger_connected)) { + di->autopower = true; + ab8500_power_loss_handling(di); + } + } + power_supply_changed(psy); +} + +static void ab8500_charger_set_usb_connected(struct ab8500_charger *di, + bool connected) +{ + if (connected != di->usb.charger_connected) { + dev_dbg(di->dev, "USB connected:%i\n", connected); + di->usb.charger_connected = connected; + sysfs_notify(&di->usb_chg.psy.dev->kobj, NULL, "present"); + } +} + +/** + * ab8500_charger_get_ac_voltage() - get ac charger voltage + * @di: pointer to the ab8500_charger structure + * + * Returns ac charger voltage (on success) + */ +static int ab8500_charger_get_ac_voltage(struct ab8500_charger *di) +{ + int vch; + + /* Only measure voltage if the charger is connected */ + if (di->ac.charger_connected) { + vch = ab8500_gpadc_convert(di->gpadc, MAIN_CHARGER_V); + if (vch < 0) + dev_err(di->dev, "%s gpadc conv failed,\n", __func__); + } else { + vch = 0; + } + return vch; +} + +/** + * ab8500_charger_ac_cv() - check if the main charger is in CV mode + * @di: pointer to the ab8500_charger structure + * + * Returns ac charger CV mode (on success) else error code + */ +static int ab8500_charger_ac_cv(struct ab8500_charger *di) +{ + u8 val; + int ret = 0; + + /* Only check CV mode if the charger is online */ + if (di->ac.charger_online) { + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_STATUS1_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return 0; + } + + if (val & MAIN_CH_CV_ON) + ret = 1; + else + ret = 0; + } + + return ret; +} + +/** + * ab8500_charger_get_vbus_voltage() - get vbus voltage + * @di: pointer to the ab8500_charger structure + * + * This function returns the vbus voltage. + * Returns vbus voltage (on success) + */ +static int ab8500_charger_get_vbus_voltage(struct ab8500_charger *di) +{ + int vch; + + /* Only measure voltage if the charger is connected */ + if (di->usb.charger_connected) { + vch = ab8500_gpadc_convert(di->gpadc, VBUS_V); + if (vch < 0) + dev_err(di->dev, "%s gpadc conv failed\n", __func__); + } else { + vch = 0; + } + return vch; +} + +/** + * ab8500_charger_get_usb_current() - get usb charger current + * @di: pointer to the ab8500_charger structure + * + * This function returns the usb charger current. + * Returns usb current (on success) and error code on failure + */ +static int ab8500_charger_get_usb_current(struct ab8500_charger *di) +{ + int ich; + + /* Only measure current if the charger is online */ + if (di->usb.charger_online) { + ich = ab8500_gpadc_convert(di->gpadc, USB_CHARGER_C); + if (ich < 0) + dev_err(di->dev, "%s gpadc conv failed\n", __func__); + } else { + ich = 0; + } + return ich; +} + +/** + * ab8500_charger_get_ac_current() - get ac charger current + * @di: pointer to the ab8500_charger structure + * + * This function returns the ac charger current. + * Returns ac current (on success) and error code on failure. + */ +static int ab8500_charger_get_ac_current(struct ab8500_charger *di) +{ + int ich; + + /* Only measure current if the charger is online */ + if (di->ac.charger_online) { + ich = ab8500_gpadc_convert(di->gpadc, MAIN_CHARGER_C); + if (ich < 0) + dev_err(di->dev, "%s gpadc conv failed\n", __func__); + } else { + ich = 0; + } + return ich; +} + +/** + * ab8500_charger_usb_cv() - check if the usb charger is in CV mode + * @di: pointer to the ab8500_charger structure + * + * Returns ac charger CV mode (on success) else error code + */ +static int ab8500_charger_usb_cv(struct ab8500_charger *di) +{ + int ret; + u8 val; + + /* Only check CV mode if the charger is online */ + if (di->usb.charger_online) { + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_USBCH_STAT1_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return 0; + } + + if (val & USB_CH_CV_ON) + ret = 1; + else + ret = 0; + } else { + ret = 0; + } + + return ret; +} + +/** + * ab8500_charger_detect_chargers() - Detect the connected chargers + * @di: pointer to the ab8500_charger structure + * + * Returns the type of charger connected. + * For USB it will not mean we can actually charge from it + * but that there is a USB cable connected that we have to + * identify. This is used during startup when we don't get + * interrupts of the charger detection + * + * Returns an integer value, that means, + * NO_PW_CONN no power supply is connected + * AC_PW_CONN if the AC power supply is connected + * USB_PW_CONN if the USB power supply is connected + * AC_PW_CONN + USB_PW_CONN if USB and AC power supplies are both connected + */ +static int ab8500_charger_detect_chargers(struct ab8500_charger *di) +{ + int result = NO_PW_CONN; + int ret; + u8 val; + + /* Check for AC charger */ + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_STATUS1_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return ret; + } + + if (val & MAIN_CH_DET) + result = AC_PW_CONN; + + /* Check for USB charger */ + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_USBCH_STAT1_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return ret; + } + + if ((val & VBUS_DET_DBNC1) && (val & VBUS_DET_DBNC100)) + result |= USB_PW_CONN; + + return result; +} + +/** + * ab8500_charger_max_usb_curr() - get the max curr for the USB type + * @di: pointer to the ab8500_charger structure + * @link_status: the identified USB type + * + * Get the maximum current that is allowed to be drawn from the host + * based on the USB type. + * Returns error code in case of failure else 0 on success + */ +static int ab8500_charger_max_usb_curr(struct ab8500_charger *di, + enum ab8500_charger_link_status link_status) +{ + int ret = 0; + + switch (link_status) { + case USB_STAT_STD_HOST_NC: + case USB_STAT_STD_HOST_C_NS: + case USB_STAT_STD_HOST_C_S: + dev_dbg(di->dev, "USB Type - Standard host is " + "detected through USB driver\n"); + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P09; + break; + case USB_STAT_HOST_CHG_HS_CHIRP: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P5; + break; + case USB_STAT_HOST_CHG_HS: + case USB_STAT_ACA_RID_C_HS: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P9; + break; + case USB_STAT_ACA_RID_A: + /* + * Dedicated charger level minus maximum current accessory + * can consume (300mA). Closest level is 1100mA + */ + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P1; + break; + case USB_STAT_ACA_RID_B: + /* + * Dedicated charger level minus 120mA (20mA for ACA and + * 100mA for potential accessory). Closest level is 1300mA + */ + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P3; + break; + case USB_STAT_DEDICATED_CHG: + case USB_STAT_HOST_CHG_NM: + case USB_STAT_ACA_RID_C_HS_CHIRP: + case USB_STAT_ACA_RID_C_NM: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P5; + break; + case USB_STAT_RESERVED: + /* + * This state is used to indicate that VBUS has dropped below + * the detection level 4 times in a row. This is due to the + * charger output current is set to high making the charger + * voltage collapse. This have to be propagated through to + * chargalg. This is done using the property + * POWER_SUPPLY_PROP_CURRENT_AVG = 1 + */ + di->flags.vbus_collapse = true; + dev_dbg(di->dev, "USB Type - USB_STAT_RESERVED " + "VBUS has collapsed\n"); + ret = -1; + break; + case USB_STAT_HM_IDGND: + case USB_STAT_NOT_CONFIGURED: + case USB_STAT_NOT_VALID_LINK: + dev_err(di->dev, "USB Type - Charging not allowed\n"); + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05; + ret = -ENXIO; + break; + default: + dev_err(di->dev, "USB Type - Unknown\n"); + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05; + ret = -ENXIO; + break; + }; + + dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", + link_status, di->max_usb_in_curr); + + return ret; +} + +/** + * ab8500_charger_read_usb_type() - read the type of usb connected + * @di: pointer to the ab8500_charger structure + * + * Detect the type of the plugged USB + * Returns error code in case of failure else 0 on success + */ +static int ab8500_charger_read_usb_type(struct ab8500_charger *di) +{ + int ret; + u8 val; + + ret = abx500_get_register_interruptible(di->dev, + AB8500_INTERRUPT, AB8500_IT_SOURCE21_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return ret; + } + ret = abx500_get_register_interruptible(di->dev, AB8500_USB, + AB8500_USB_LINE_STAT_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return ret; + } + + /* get the USB type */ + val = (val & AB8500_USB_LINK_STATUS) >> 3; + ret = ab8500_charger_max_usb_curr(di, + (enum ab8500_charger_link_status) val); + + return ret; +} + +/** + * ab8500_charger_detect_usb_type() - get the type of usb connected + * @di: pointer to the ab8500_charger structure + * + * Detect the type of the plugged USB + * Returns error code in case of failure else 0 on success + */ +static int ab8500_charger_detect_usb_type(struct ab8500_charger *di) +{ + int i, ret; + u8 val; + + /* + * On getting the VBUS rising edge detect interrupt there + * is a 250ms delay after which the register UsbLineStatus + * is filled with valid data. + */ + for (i = 0; i < 10; i++) { + msleep(250); + ret = abx500_get_register_interruptible(di->dev, + AB8500_INTERRUPT, AB8500_IT_SOURCE21_REG, + &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return ret; + } + ret = abx500_get_register_interruptible(di->dev, AB8500_USB, + AB8500_USB_LINE_STAT_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return ret; + } + /* + * Until the IT source register is read the UsbLineStatus + * register is not updated, hence doing the same + * Revisit this: + */ + + /* get the USB type */ + val = (val & AB8500_USB_LINK_STATUS) >> 3; + if (val) + break; + } + ret = ab8500_charger_max_usb_curr(di, + (enum ab8500_charger_link_status) val); + + return ret; +} + +/* + * This array maps the raw hex value to charger voltage used by the AB8500 + * Values taken from the UM0836 + */ +static int ab8500_charger_voltage_map[] = { + 3500 , + 3525 , + 3550 , + 3575 , + 3600 , + 3625 , + 3650 , + 3675 , + 3700 , + 3725 , + 3750 , + 3775 , + 3800 , + 3825 , + 3850 , + 3875 , + 3900 , + 3925 , + 3950 , + 3975 , + 4000 , + 4025 , + 4050 , + 4060 , + 4070 , + 4080 , + 4090 , + 4100 , + 4110 , + 4120 , + 4130 , + 4140 , + 4150 , + 4160 , + 4170 , + 4180 , + 4190 , + 4200 , + 4210 , + 4220 , + 4230 , + 4240 , + 4250 , + 4260 , + 4270 , + 4280 , + 4290 , + 4300 , + 4310 , + 4320 , + 4330 , + 4340 , + 4350 , + 4360 , + 4370 , + 4380 , + 4390 , + 4400 , + 4410 , + 4420 , + 4430 , + 4440 , + 4450 , + 4460 , + 4470 , + 4480 , + 4490 , + 4500 , + 4510 , + 4520 , + 4530 , + 4540 , + 4550 , + 4560 , + 4570 , + 4580 , + 4590 , + 4600 , +}; + +/* + * This array maps the raw hex value to charger current used by the AB8500 + * Values taken from the UM0836 + */ +static int ab8500_charger_current_map[] = { + 100 , + 200 , + 300 , + 400 , + 500 , + 600 , + 700 , + 800 , + 900 , + 1000 , + 1100 , + 1200 , + 1300 , + 1400 , + 1500 , +}; + +/* + * This array maps the raw hex value to VBUS input current used by the AB8500 + * Values taken from the UM0836 + */ +static int ab8500_charger_vbus_in_curr_map[] = { + USB_CH_IP_CUR_LVL_0P05, + USB_CH_IP_CUR_LVL_0P09, + USB_CH_IP_CUR_LVL_0P19, + USB_CH_IP_CUR_LVL_0P29, + USB_CH_IP_CUR_LVL_0P38, + USB_CH_IP_CUR_LVL_0P45, + USB_CH_IP_CUR_LVL_0P5, + USB_CH_IP_CUR_LVL_0P6, + USB_CH_IP_CUR_LVL_0P7, + USB_CH_IP_CUR_LVL_0P8, + USB_CH_IP_CUR_LVL_0P9, + USB_CH_IP_CUR_LVL_1P0, + USB_CH_IP_CUR_LVL_1P1, + USB_CH_IP_CUR_LVL_1P3, + USB_CH_IP_CUR_LVL_1P4, + USB_CH_IP_CUR_LVL_1P5, +}; + +static int ab8500_voltage_to_regval(int voltage) +{ + int i; + + /* Special case for voltage below 3.5V */ + if (voltage < ab8500_charger_voltage_map[0]) + return LOW_VOLT_REG; + + for (i = 1; i < ARRAY_SIZE(ab8500_charger_voltage_map); i++) { + if (voltage < ab8500_charger_voltage_map[i]) + return i - 1; + } + + /* If not last element, return error */ + i = ARRAY_SIZE(ab8500_charger_voltage_map) - 1; + if (voltage == ab8500_charger_voltage_map[i]) + return i; + else + return -1; +} + +static int ab8500_current_to_regval(int curr) +{ + int i; + + if (curr < ab8500_charger_current_map[0]) + return 0; + + for (i = 0; i < ARRAY_SIZE(ab8500_charger_current_map); i++) { + if (curr < ab8500_charger_current_map[i]) + return i - 1; + } + + /* If not last element, return error */ + i = ARRAY_SIZE(ab8500_charger_current_map) - 1; + if (curr == ab8500_charger_current_map[i]) + return i; + else + return -1; +} + +static int ab8500_vbus_in_curr_to_regval(int curr) +{ + int i; + + if (curr < ab8500_charger_vbus_in_curr_map[0]) + return 0; + + for (i = 0; i < ARRAY_SIZE(ab8500_charger_vbus_in_curr_map); i++) { + if (curr < ab8500_charger_vbus_in_curr_map[i]) + return i - 1; + } + + /* If not last element, return error */ + i = ARRAY_SIZE(ab8500_charger_vbus_in_curr_map) - 1; + if (curr == ab8500_charger_vbus_in_curr_map[i]) + return i; + else + return -1; +} + +/** + * ab8500_charger_get_usb_cur() - get usb current + * @di: pointer to the ab8500_charger structre + * + * The usb stack provides the maximum current that can be drawn from + * the standard usb host. This will be in mA. + * This function converts current in mA to a value that can be written + * to the register. Returns -1 if charging is not allowed + */ +static int ab8500_charger_get_usb_cur(struct ab8500_charger *di) +{ + switch (di->usb_state.usb_current) { + case 100: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P09; + break; + case 200: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P19; + break; + case 300: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P29; + break; + case 400: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P38; + break; + case 500: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P5; + break; + default: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05; + return -1; + break; + }; + return 0; +} + +/** + * ab8500_charger_set_vbus_in_curr() - set VBUS input current limit + * @di: pointer to the ab8500_charger structure + * @ich_in: charger input current limit + * + * Sets the current that can be drawn from the USB host + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_set_vbus_in_curr(struct ab8500_charger *di, + int ich_in) +{ + int ret; + int input_curr_index; + int min_value; + + /* We should always use to lowest current limit */ + min_value = min(di->bat->chg_params->usb_curr_max, ich_in); + + switch (min_value) { + case 100: + if (di->vbat < VBAT_TRESH_IP_CUR_RED) + min_value = USB_CH_IP_CUR_LVL_0P05; + break; + case 500: + if (di->vbat < VBAT_TRESH_IP_CUR_RED) + min_value = USB_CH_IP_CUR_LVL_0P45; + break; + default: + break; + } + + input_curr_index = ab8500_vbus_in_curr_to_regval(min_value); + if (input_curr_index < 0) { + dev_err(di->dev, "VBUS input current limit too high\n"); + return -ENXIO; + } + + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_USBCH_IPT_CRNTLVL_REG, + input_curr_index << VBUS_IN_CURR_LIM_SHIFT); + if (ret) + dev_err(di->dev, "%s write failed\n", __func__); + + return ret; +} + +/** + * ab8500_charger_led_en() - turn on/off chargign led + * @di: pointer to the ab8500_charger structure + * @on: flag to turn on/off the chargign led + * + * Power ON/OFF charging LED indication + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_led_en(struct ab8500_charger *di, int on) +{ + int ret; + + if (on) { + /* Power ON charging LED indicator, set LED current to 5mA */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_LED_INDICATOR_PWM_CTRL, + (LED_IND_CUR_5MA | LED_INDICATOR_PWM_ENA)); + if (ret) { + dev_err(di->dev, "Power ON LED failed\n"); + return ret; + } + /* LED indicator PWM duty cycle 252/256 */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_LED_INDICATOR_PWM_DUTY, + LED_INDICATOR_PWM_DUTY_252_256); + if (ret) { + dev_err(di->dev, "Set LED PWM duty cycle failed\n"); + return ret; + } + } else { + /* Power off charging LED indicator */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_LED_INDICATOR_PWM_CTRL, + LED_INDICATOR_PWM_DIS); + if (ret) { + dev_err(di->dev, "Power-off LED failed\n"); + return ret; + } + } + + return ret; +} + +/** + * ab8500_charger_ac_en() - enable or disable ac charging + * @di: pointer to the ab8500_charger structure + * @enable: enable/disable flag + * @vset: charging voltage + * @iset: charging current + * + * Enable/Disable AC/Mains charging and turns on/off the charging led + * respectively. + **/ +static int ab8500_charger_ac_en(struct ux500_charger *charger, + int enable, int vset, int iset) +{ + int ret; + int volt_index; + int curr_index; + int input_curr_index; + u8 overshoot = 0; + + struct ab8500_charger *di = to_ab8500_charger_ac_device_info(charger); + + if (enable) { + /* Check if AC is connected */ + if (!di->ac.charger_connected) { + dev_err(di->dev, "AC charger not connected\n"); + return -ENXIO; + } + + /* Enable AC charging */ + dev_dbg(di->dev, "Enable AC: %dmV %dmA\n", vset, iset); + + /* + * Due to a bug in AB8500, BTEMP_HIGH/LOW interrupts + * will be triggered everytime we enable the VDD ADC supply. + * This will turn off charging for a short while. + * It can be avoided by having the supply on when + * there is a charger enabled. Normally the VDD ADC supply + * is enabled everytime a GPADC conversion is triggered. We will + * force it to be enabled from this driver to have + * the GPADC module independant of the AB8500 chargers + */ + if (!di->vddadc_en_ac) { + regulator_enable(di->regu); + di->vddadc_en_ac = true; + } + + /* Check if the requested voltage or current is valid */ + volt_index = ab8500_voltage_to_regval(vset); + curr_index = ab8500_current_to_regval(iset); + input_curr_index = ab8500_current_to_regval( + di->bat->chg_params->ac_curr_max); + if (volt_index < 0 || curr_index < 0 || input_curr_index < 0) { + dev_err(di->dev, + "Charger voltage or current too high, " + "charging not started\n"); + return -ENXIO; + } + + /* ChVoltLevel: maximum battery charging voltage */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_VOLT_LVL_REG, (u8) volt_index); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + /* MainChInputCurr: current that can be drawn from the charger*/ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_MCH_IPT_CURLVL_REG, + input_curr_index << MAIN_CH_INPUT_CURR_SHIFT); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + /* ChOutputCurentLevel: protected output current */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_OPT_CRNTLVL_REG, (u8) curr_index); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + + /* Check if VBAT overshoot control should be enabled */ + if (!di->bat->enable_overshoot) + overshoot = MAIN_CH_NO_OVERSHOOT_ENA_N; + + /* Enable Main Charger */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_MCH_CTRL1, MAIN_CH_ENA | overshoot); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + + /* Power on charging LED indication */ + ret = ab8500_charger_led_en(di, true); + if (ret < 0) + dev_err(di->dev, "failed to enable LED\n"); + + di->ac.charger_online = 1; + } else { + /* Disable AC charging */ + if (is_ab8500_1p1_or_earlier(di->parent)) { + /* + * For ABB revision 1.0 and 1.1 there is a bug in the + * watchdog logic. That means we have to continously + * kick the charger watchdog even when no charger is + * connected. This is only valid once the AC charger + * has been enabled. This is a bug that is not handled + * by the algorithm and the watchdog have to be kicked + * by the charger driver when the AC charger + * is disabled + */ + if (di->ac_conn) { + queue_delayed_work(di->charger_wq, + &di->kick_wd_work, + round_jiffies(WD_KICK_INTERVAL)); + } + + /* + * We can't turn off charging completely + * due to a bug in AB8500 cut1. + * If we do, charging will not start again. + * That is why we set the lowest voltage + * and current possible + */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_CH_VOLT_LVL_REG, CH_VOL_LVL_3P5); + if (ret) { + dev_err(di->dev, + "%s write failed\n", __func__); + return ret; + } + + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_CH_OPT_CRNTLVL_REG, CH_OP_CUR_LVL_0P1); + if (ret) { + dev_err(di->dev, + "%s write failed\n", __func__); + return ret; + } + } else { + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_MCH_CTRL1, 0); + if (ret) { + dev_err(di->dev, + "%s write failed\n", __func__); + return ret; + } + } + + ret = ab8500_charger_led_en(di, false); + if (ret < 0) + dev_err(di->dev, "failed to disable LED\n"); + + di->ac.charger_online = 0; + di->ac.wd_expired = false; + + /* Disable regulator if enabled */ + if (di->vddadc_en_ac) { + regulator_disable(di->regu); + di->vddadc_en_ac = false; + } + + dev_dbg(di->dev, "%s Disabled AC charging\n", __func__); + } + ab8500_power_supply_changed(di, &di->ac_chg.psy); + + return ret; +} + +/** + * ab8500_charger_usb_en() - enable usb charging + * @di: pointer to the ab8500_charger structure + * @enable: enable/disable flag + * @vset: charging voltage + * @ich_out: charger output current + * + * Enable/Disable USB charging and turns on/off the charging led respectively. + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_usb_en(struct ux500_charger *charger, + int enable, int vset, int ich_out) +{ + int ret; + int volt_index; + int curr_index; + u8 overshoot = 0; + + struct ab8500_charger *di = to_ab8500_charger_usb_device_info(charger); + + if (enable) { + /* Check if USB is connected */ + if (!di->usb.charger_connected) { + dev_err(di->dev, "USB charger not connected\n"); + return -ENXIO; + } + + /* + * Due to a bug in AB8500, BTEMP_HIGH/LOW interrupts + * will be triggered everytime we enable the VDD ADC supply. + * This will turn off charging for a short while. + * It can be avoided by having the supply on when + * there is a charger enabled. Normally the VDD ADC supply + * is enabled everytime a GPADC conversion is triggered. We will + * force it to be enabled from this driver to have + * the GPADC module independant of the AB8500 chargers + */ + if (!di->vddadc_en_usb) { + regulator_enable(di->regu); + di->vddadc_en_usb = true; + } + + /* Enable USB charging */ + dev_dbg(di->dev, "Enable USB: %dmV %dmA\n", vset, ich_out); + + /* Check if the requested voltage or current is valid */ + volt_index = ab8500_voltage_to_regval(vset); + curr_index = ab8500_current_to_regval(ich_out); + if (volt_index < 0 || curr_index < 0) { + dev_err(di->dev, + "Charger voltage or current too high, " + "charging not started\n"); + return -ENXIO; + } + + /* ChVoltLevel: max voltage upto which battery can be charged */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_VOLT_LVL_REG, (u8) volt_index); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + /* USBChInputCurr: current that can be drawn from the usb */ + ret = ab8500_charger_set_vbus_in_curr(di, di->max_usb_in_curr); + if (ret) { + dev_err(di->dev, "setting USBChInputCurr failed\n"); + return ret; + } + /* ChOutputCurentLevel: protected output current */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_OPT_CRNTLVL_REG, (u8) curr_index); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + /* Check if VBAT overshoot control should be enabled */ + if (!di->bat->enable_overshoot) + overshoot = USB_CHG_NO_OVERSHOOT_ENA_N; + + /* Enable USB Charger */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_USBCH_CTRL1_REG, USB_CH_ENA | overshoot); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + + /* If success power on charging LED indication */ + ret = ab8500_charger_led_en(di, true); + if (ret < 0) + dev_err(di->dev, "failed to enable LED\n"); + + queue_delayed_work(di->charger_wq, &di->check_vbat_work, HZ); + + di->usb.charger_online = 1; + } else { + /* Disable USB charging */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_USBCH_CTRL1_REG, 0); + if (ret) { + dev_err(di->dev, + "%s write failed\n", __func__); + return ret; + } + + ret = ab8500_charger_led_en(di, false); + if (ret < 0) + dev_err(di->dev, "failed to disable LED\n"); + + di->usb.charger_online = 0; + di->usb.wd_expired = false; + + /* Disable regulator if enabled */ + if (di->vddadc_en_usb) { + regulator_disable(di->regu); + di->vddadc_en_usb = false; + } + + dev_dbg(di->dev, "%s Disabled USB charging\n", __func__); + + /* Cancel any pending Vbat check work */ + if (delayed_work_pending(&di->check_vbat_work)) + cancel_delayed_work(&di->check_vbat_work); + + } + ab8500_power_supply_changed(di, &di->usb_chg.psy); + + return ret; +} + +/** + * ab8500_charger_watchdog_kick() - kick charger watchdog + * @di: pointer to the ab8500_charger structure + * + * Kick charger watchdog + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_watchdog_kick(struct ux500_charger *charger) +{ + int ret; + struct ab8500_charger *di; + + if (charger->psy.type == POWER_SUPPLY_TYPE_MAINS) + di = to_ab8500_charger_ac_device_info(charger); + else if (charger->psy.type == POWER_SUPPLY_TYPE_USB) + di = to_ab8500_charger_usb_device_info(charger); + else + return -ENXIO; + + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CHARG_WD_CTRL, CHARG_WD_KICK); + if (ret) + dev_err(di->dev, "Failed to kick WD!\n"); + + return ret; +} + +/** + * ab8500_charger_update_charger_current() - update charger current + * @di: pointer to the ab8500_charger structure + * + * Update the charger output current for the specified charger + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_update_charger_current(struct ux500_charger *charger, + int ich_out) +{ + int ret; + int curr_index; + struct ab8500_charger *di; + + if (charger->psy.type == POWER_SUPPLY_TYPE_MAINS) + di = to_ab8500_charger_ac_device_info(charger); + else if (charger->psy.type == POWER_SUPPLY_TYPE_USB) + di = to_ab8500_charger_usb_device_info(charger); + else + return -ENXIO; + + curr_index = ab8500_current_to_regval(ich_out); + if (curr_index < 0) { + dev_err(di->dev, + "Charger current too high, " + "charging not started\n"); + return -ENXIO; + } + + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_OPT_CRNTLVL_REG, (u8) curr_index); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + + /* Reset the main and usb drop input current measurement counter */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CHARGER_CTRL, + 0x1); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + + return ret; +} + +static int ab8500_charger_get_ext_psy_data(struct device *dev, void *data) +{ + struct power_supply *psy; + struct power_supply *ext; + struct ab8500_charger *di; + union power_supply_propval ret; + int i, j; + bool psy_found = false; + struct ux500_charger *usb_chg; + + usb_chg = (struct ux500_charger *)data; + psy = &usb_chg->psy; + + di = to_ab8500_charger_usb_device_info(usb_chg); + + ext = dev_get_drvdata(dev); + + /* For all psy where the driver name appears in any supplied_to */ + for (i = 0; i < ext->num_supplicants; i++) { + if (!strcmp(ext->supplied_to[i], psy->name)) + psy_found = true; + } + + if (!psy_found) + return 0; + + /* Go through all properties for the psy */ + for (j = 0; j < ext->num_properties; j++) { + enum power_supply_property prop; + prop = ext->properties[j]; + + if (ext->get_property(ext, prop, &ret)) + continue; + + switch (prop) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + di->vbat = ret.intval / 1000; + break; + default: + break; + } + break; + default: + break; + } + } + return 0; +} + +/** + * ab8500_charger_check_vbat_work() - keep vbus current within spec + * @work pointer to the work_struct structure + * + * Due to a asic bug it is necessary to lower the input current to the vbus + * charger when charging with at some specific levels. This issue is only valid + * for below a certain battery voltage. This function makes sure that the + * the allowed current limit isn't exceeded. + */ +static void ab8500_charger_check_vbat_work(struct work_struct *work) +{ + int t = 10; + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, check_vbat_work.work); + + class_for_each_device(power_supply_class, NULL, + &di->usb_chg.psy, ab8500_charger_get_ext_psy_data); + + /* First run old_vbat is 0. */ + if (di->old_vbat == 0) + di->old_vbat = di->vbat; + + if (!((di->old_vbat <= VBAT_TRESH_IP_CUR_RED && + di->vbat <= VBAT_TRESH_IP_CUR_RED) || + (di->old_vbat > VBAT_TRESH_IP_CUR_RED && + di->vbat > VBAT_TRESH_IP_CUR_RED))) { + + dev_dbg(di->dev, "Vbat did cross threshold, curr: %d, new: %d," + " old: %d\n", di->max_usb_in_curr, di->vbat, + di->old_vbat); + ab8500_charger_set_vbus_in_curr(di, di->max_usb_in_curr); + power_supply_changed(&di->usb_chg.psy); + } + + di->old_vbat = di->vbat; + + /* + * No need to check the battery voltage every second when not close to + * the threshold. + */ + if (di->vbat < (VBAT_TRESH_IP_CUR_RED + 100) && + (di->vbat > (VBAT_TRESH_IP_CUR_RED - 100))) + t = 1; + + queue_delayed_work(di->charger_wq, &di->check_vbat_work, t * HZ); +} + +/** + * ab8500_charger_check_hw_failure_work() - check main charger failure + * @work: pointer to the work_struct structure + * + * Work queue function for checking the main charger status + */ +static void ab8500_charger_check_hw_failure_work(struct work_struct *work) +{ + int ret; + u8 reg_value; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, check_hw_failure_work.work); + + /* Check if the status bits for HW failure is still active */ + if (di->flags.mainextchnotok) { + ret = abx500_get_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_CH_STATUS2_REG, ®_value); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return; + } + if (!(reg_value & MAIN_CH_NOK)) { + di->flags.mainextchnotok = false; + ab8500_power_supply_changed(di, &di->ac_chg.psy); + } + } + if (di->flags.vbus_ovv) { + ret = abx500_get_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_CH_USBCH_STAT2_REG, + ®_value); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return; + } + if (!(reg_value & VBUS_OVV_TH)) { + di->flags.vbus_ovv = false; + ab8500_power_supply_changed(di, &di->usb_chg.psy); + } + } + /* If we still have a failure, schedule a new check */ + if (di->flags.mainextchnotok || di->flags.vbus_ovv) { + queue_delayed_work(di->charger_wq, + &di->check_hw_failure_work, round_jiffies(HZ)); + } +} + +/** + * ab8500_charger_kick_watchdog_work() - kick the watchdog + * @work: pointer to the work_struct structure + * + * Work queue function for kicking the charger watchdog. + * + * For ABB revision 1.0 and 1.1 there is a bug in the watchdog + * logic. That means we have to continously kick the charger + * watchdog even when no charger is connected. This is only + * valid once the AC charger has been enabled. This is + * a bug that is not handled by the algorithm and the + * watchdog have to be kicked by the charger driver + * when the AC charger is disabled + */ +static void ab8500_charger_kick_watchdog_work(struct work_struct *work) +{ + int ret; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, kick_wd_work.work); + + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CHARG_WD_CTRL, CHARG_WD_KICK); + if (ret) + dev_err(di->dev, "Failed to kick WD!\n"); + + /* Schedule a new watchdog kick */ + queue_delayed_work(di->charger_wq, + &di->kick_wd_work, round_jiffies(WD_KICK_INTERVAL)); +} + +/** + * ab8500_charger_ac_work() - work to get and set main charger status + * @work: pointer to the work_struct structure + * + * Work queue function for checking the main charger status + */ +static void ab8500_charger_ac_work(struct work_struct *work) +{ + int ret; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, ac_work); + + /* + * Since we can't be sure that the events are received + * synchronously, we have the check if the main charger is + * connected by reading the status register + */ + ret = ab8500_charger_detect_chargers(di); + if (ret < 0) + return; + + if (ret & AC_PW_CONN) { + di->ac.charger_connected = 1; + di->ac_conn = true; + } else { + di->ac.charger_connected = 0; + } + + ab8500_power_supply_changed(di, &di->ac_chg.psy); + sysfs_notify(&di->ac_chg.psy.dev->kobj, NULL, "present"); +} + +/** + * ab8500_charger_detect_usb_type_work() - work to detect USB type + * @work: Pointer to the work_struct structure + * + * Detect the type of USB plugged + */ +static void ab8500_charger_detect_usb_type_work(struct work_struct *work) +{ + int ret; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, detect_usb_type_work); + + /* + * Since we can't be sure that the events are received + * synchronously, we have the check if is + * connected by reading the status register + */ + ret = ab8500_charger_detect_chargers(di); + if (ret < 0) + return; + + if (!(ret & USB_PW_CONN)) { + di->vbus_detected = 0; + ab8500_charger_set_usb_connected(di, false); + ab8500_power_supply_changed(di, &di->usb_chg.psy); + } else { + di->vbus_detected = 1; + + if (is_ab8500_1p1_or_earlier(di->parent)) { + ret = ab8500_charger_detect_usb_type(di); + if (!ret) { + ab8500_charger_set_usb_connected(di, true); + ab8500_power_supply_changed(di, + &di->usb_chg.psy); + } + } else { + /* For ABB cut2.0 and onwards we have an IRQ, + * USB_LINK_STATUS that will be triggered when the USB + * link status changes. The exception is USB connected + * during startup. Then we don't get a + * USB_LINK_STATUS IRQ + */ + if (di->vbus_detected_start) { + di->vbus_detected_start = false; + ret = ab8500_charger_detect_usb_type(di); + if (!ret) { + ab8500_charger_set_usb_connected(di, + true); + ab8500_power_supply_changed(di, + &di->usb_chg.psy); + } + } + } + } +} + +/** + * ab8500_charger_usb_link_status_work() - work to detect USB type + * @work: pointer to the work_struct structure + * + * Detect the type of USB plugged + */ +static void ab8500_charger_usb_link_status_work(struct work_struct *work) +{ + int ret; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, usb_link_status_work); + + /* + * Since we can't be sure that the events are received + * synchronously, we have the check if is + * connected by reading the status register + */ + ret = ab8500_charger_detect_chargers(di); + if (ret < 0) + return; + + if (!(ret & USB_PW_CONN)) { + di->vbus_detected = 0; + ab8500_charger_set_usb_connected(di, false); + ab8500_power_supply_changed(di, &di->usb_chg.psy); + } else { + di->vbus_detected = 1; + ret = ab8500_charger_read_usb_type(di); + if (!ret) { + /* Update maximum input current */ + ret = ab8500_charger_set_vbus_in_curr(di, + di->max_usb_in_curr); + if (ret) + return; + + ab8500_charger_set_usb_connected(di, true); + ab8500_power_supply_changed(di, &di->usb_chg.psy); + } else if (ret == -ENXIO) { + /* No valid charger type detected */ + ab8500_charger_set_usb_connected(di, false); + ab8500_power_supply_changed(di, &di->usb_chg.psy); + } + } +} + +static void ab8500_charger_usb_state_changed_work(struct work_struct *work) +{ + int ret; + unsigned long flags; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, usb_state_changed_work); + + if (!di->vbus_detected) + return; + + spin_lock_irqsave(&di->usb_state.usb_lock, flags); + di->usb_state.usb_changed = false; + spin_unlock_irqrestore(&di->usb_state.usb_lock, flags); + + /* + * wait for some time until you get updates from the usb stack + * and negotiations are completed + */ + msleep(250); + + if (di->usb_state.usb_changed) + return; + + dev_dbg(di->dev, "%s USB state: 0x%02x mA: %d\n", + __func__, di->usb_state.state, di->usb_state.usb_current); + + switch (di->usb_state.state) { + case AB8500_BM_USB_STATE_RESET_HS: + case AB8500_BM_USB_STATE_RESET_FS: + case AB8500_BM_USB_STATE_SUSPEND: + case AB8500_BM_USB_STATE_MAX: + ab8500_charger_set_usb_connected(di, false); + ab8500_power_supply_changed(di, &di->usb_chg.psy); + break; + + case AB8500_BM_USB_STATE_RESUME: + /* + * when suspend->resume there should be delay + * of 1sec for enabling charging + */ + msleep(1000); + /* Intentional fall through */ + case AB8500_BM_USB_STATE_CONFIGURED: + /* + * USB is configured, enable charging with the charging + * input current obtained from USB driver + */ + if (!ab8500_charger_get_usb_cur(di)) { + /* Update maximum input current */ + ret = ab8500_charger_set_vbus_in_curr(di, + di->max_usb_in_curr); + if (ret) + return; + + ab8500_charger_set_usb_connected(di, true); + ab8500_power_supply_changed(di, &di->usb_chg.psy); + } + break; + + default: + break; + }; +} + +/** + * ab8500_charger_check_usbchargernotok_work() - check USB chg not ok status + * @work: pointer to the work_struct structure + * + * Work queue function for checking the USB charger Not OK status + */ +static void ab8500_charger_check_usbchargernotok_work(struct work_struct *work) +{ + int ret; + u8 reg_value; + bool prev_status; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, check_usbchgnotok_work.work); + + /* Check if the status bit for usbchargernotok is still active */ + ret = abx500_get_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_CH_USBCH_STAT2_REG, ®_value); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return; + } + prev_status = di->flags.usbchargernotok; + + if (reg_value & VBUS_CH_NOK) { + di->flags.usbchargernotok = true; + /* Check again in 1sec */ + queue_delayed_work(di->charger_wq, + &di->check_usbchgnotok_work, HZ); + } else { + di->flags.usbchargernotok = false; + di->flags.vbus_collapse = false; + } + + if (prev_status != di->flags.usbchargernotok) + ab8500_power_supply_changed(di, &di->usb_chg.psy); +} + +/** + * ab8500_charger_check_main_thermal_prot_work() - check main thermal status + * @work: pointer to the work_struct structure + * + * Work queue function for checking the Main thermal prot status + */ +static void ab8500_charger_check_main_thermal_prot_work( + struct work_struct *work) +{ + int ret; + u8 reg_value; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, check_main_thermal_prot_work); + + /* Check if the status bit for main_thermal_prot is still active */ + ret = abx500_get_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_CH_STATUS2_REG, ®_value); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return; + } + if (reg_value & MAIN_CH_TH_PROT) + di->flags.main_thermal_prot = true; + else + di->flags.main_thermal_prot = false; + + ab8500_power_supply_changed(di, &di->ac_chg.psy); +} + +/** + * ab8500_charger_check_usb_thermal_prot_work() - check usb thermal status + * @work: pointer to the work_struct structure + * + * Work queue function for checking the USB thermal prot status + */ +static void ab8500_charger_check_usb_thermal_prot_work( + struct work_struct *work) +{ + int ret; + u8 reg_value; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, check_usb_thermal_prot_work); + + /* Check if the status bit for usb_thermal_prot is still active */ + ret = abx500_get_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_CH_USBCH_STAT2_REG, ®_value); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return; + } + if (reg_value & USB_CH_TH_PROT) + di->flags.usb_thermal_prot = true; + else + di->flags.usb_thermal_prot = false; + + ab8500_power_supply_changed(di, &di->usb_chg.psy); +} + +/** + * ab8500_charger_mainchunplugdet_handler() - main charger unplugged + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_mainchunplugdet_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "Main charger unplugged\n"); + queue_work(di->charger_wq, &di->ac_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_mainchplugdet_handler() - main charger plugged + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_mainchplugdet_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "Main charger plugged\n"); + queue_work(di->charger_wq, &di->ac_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_mainextchnotok_handler() - main charger not ok + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_mainextchnotok_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "Main charger not ok\n"); + di->flags.mainextchnotok = true; + ab8500_power_supply_changed(di, &di->ac_chg.psy); + + /* Schedule a new HW failure check */ + queue_delayed_work(di->charger_wq, &di->check_hw_failure_work, 0); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_mainchthprotr_handler() - Die temp is above main charger + * thermal protection threshold + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_mainchthprotr_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, + "Die temp above Main charger thermal protection threshold\n"); + queue_work(di->charger_wq, &di->check_main_thermal_prot_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_mainchthprotf_handler() - Die temp is below main charger + * thermal protection threshold + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_mainchthprotf_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, + "Die temp ok for Main charger thermal protection threshold\n"); + queue_work(di->charger_wq, &di->check_main_thermal_prot_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_vbusdetf_handler() - VBUS falling detected + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_vbusdetf_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "VBUS falling detected\n"); + queue_work(di->charger_wq, &di->detect_usb_type_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_vbusdetr_handler() - VBUS rising detected + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_vbusdetr_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + di->vbus_detected = true; + dev_dbg(di->dev, "VBUS rising detected\n"); + queue_work(di->charger_wq, &di->detect_usb_type_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_usblinkstatus_handler() - USB link status has changed + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_usblinkstatus_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "USB link status changed\n"); + + queue_work(di->charger_wq, &di->usb_link_status_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_usbchthprotr_handler() - Die temp is above usb charger + * thermal protection threshold + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_usbchthprotr_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, + "Die temp above USB charger thermal protection threshold\n"); + queue_work(di->charger_wq, &di->check_usb_thermal_prot_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_usbchthprotf_handler() - Die temp is below usb charger + * thermal protection threshold + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_usbchthprotf_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, + "Die temp ok for USB charger thermal protection threshold\n"); + queue_work(di->charger_wq, &di->check_usb_thermal_prot_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_usbchargernotokr_handler() - USB charger not ok detected + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_usbchargernotokr_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "Not allowed USB charger detected\n"); + queue_delayed_work(di->charger_wq, &di->check_usbchgnotok_work, 0); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_chwdexp_handler() - Charger watchdog expired + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_chwdexp_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "Charger watchdog expired\n"); + + /* + * The charger that was online when the watchdog expired + * needs to be restarted for charging to start again + */ + if (di->ac.charger_online) { + di->ac.wd_expired = true; + ab8500_power_supply_changed(di, &di->ac_chg.psy); + } + if (di->usb.charger_online) { + di->usb.wd_expired = true; + ab8500_power_supply_changed(di, &di->usb_chg.psy); + } + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_vbusovv_handler() - VBUS overvoltage detected + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_vbusovv_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "VBUS overvoltage detected\n"); + di->flags.vbus_ovv = true; + ab8500_power_supply_changed(di, &di->usb_chg.psy); + + /* Schedule a new HW failure check */ + queue_delayed_work(di->charger_wq, &di->check_hw_failure_work, 0); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_ac_get_property() - get the ac/mains properties + * @psy: pointer to the power_supply structure + * @psp: pointer to the power_supply_property structure + * @val: pointer to the power_supply_propval union + * + * This function gets called when an application tries to get the ac/mains + * properties by reading the sysfs files. + * AC/Mains properties are online, present and voltage. + * online: ac/mains charging is in progress or not + * present: presence of the ac/mains + * voltage: AC/Mains voltage + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_ac_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ab8500_charger *di; + + di = to_ab8500_charger_ac_device_info(psy_to_ux500_charger(psy)); + + switch (psp) { + case POWER_SUPPLY_PROP_HEALTH: + if (di->flags.mainextchnotok) + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + else if (di->ac.wd_expired || di->usb.wd_expired) + val->intval = POWER_SUPPLY_HEALTH_DEAD; + else if (di->flags.main_thermal_prot) + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = di->ac.charger_online; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = di->ac.charger_connected; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + di->ac.charger_voltage = ab8500_charger_get_ac_voltage(di); + val->intval = di->ac.charger_voltage * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + /* + * This property is used to indicate when CV mode is entered + * for the AC charger + */ + di->ac.cv_active = ab8500_charger_ac_cv(di); + val->intval = di->ac.cv_active; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = ab8500_charger_get_ac_current(di) * 1000; + break; + default: + return -EINVAL; + } + return 0; +} + +/** + * ab8500_charger_usb_get_property() - get the usb properties + * @psy: pointer to the power_supply structure + * @psp: pointer to the power_supply_property structure + * @val: pointer to the power_supply_propval union + * + * This function gets called when an application tries to get the usb + * properties by reading the sysfs files. + * USB properties are online, present and voltage. + * online: usb charging is in progress or not + * present: presence of the usb + * voltage: vbus voltage + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_usb_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ab8500_charger *di; + + di = to_ab8500_charger_usb_device_info(psy_to_ux500_charger(psy)); + + switch (psp) { + case POWER_SUPPLY_PROP_HEALTH: + if (di->flags.usbchargernotok) + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + else if (di->ac.wd_expired || di->usb.wd_expired) + val->intval = POWER_SUPPLY_HEALTH_DEAD; + else if (di->flags.usb_thermal_prot) + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (di->flags.vbus_ovv) + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = di->usb.charger_online; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = di->usb.charger_connected; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + di->usb.charger_voltage = ab8500_charger_get_vbus_voltage(di); + val->intval = di->usb.charger_voltage * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + /* + * This property is used to indicate when CV mode is entered + * for the USB charger + */ + di->usb.cv_active = ab8500_charger_usb_cv(di); + val->intval = di->usb.cv_active; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = ab8500_charger_get_usb_current(di) * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + /* + * This property is used to indicate when VBUS has collapsed + * due to too high output current from the USB charger + */ + if (di->flags.vbus_collapse) + val->intval = 1; + else + val->intval = 0; + break; + default: + return -EINVAL; + } + return 0; +} + +/** + * ab8500_charger_init_hw_registers() - Set up charger related registers + * @di: pointer to the ab8500_charger structure + * + * Set up charger OVV, watchdog and maximum voltage registers as well as + * charging of the backup battery + */ +static int ab8500_charger_init_hw_registers(struct ab8500_charger *di) +{ + int ret = 0; + + /* Setup maximum charger current and voltage for ABB cut2.0 */ + if (!is_ab8500_1p1_or_earlier(di->parent)) { + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_CH_VOLT_LVL_MAX_REG, CH_VOL_LVL_4P6); + if (ret) { + dev_err(di->dev, + "failed to set CH_VOLT_LVL_MAX_REG\n"); + goto out; + } + + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_CH_OPT_CRNTLVL_MAX_REG, CH_OP_CUR_LVL_1P6); + if (ret) { + dev_err(di->dev, + "failed to set CH_OPT_CRNTLVL_MAX_REG\n"); + goto out; + } + } + + /* VBUS OVV set to 6.3V and enable automatic current limitiation */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_USBCH_CTRL2_REG, + VBUS_OVV_SELECT_6P3V | VBUS_AUTO_IN_CURR_LIM_ENA); + if (ret) { + dev_err(di->dev, "failed to set VBUS OVV\n"); + goto out; + } + + /* Enable main watchdog in OTP */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_OTP_EMUL, AB8500_OTP_CONF_15, OTP_ENABLE_WD); + if (ret) { + dev_err(di->dev, "failed to enable main WD in OTP\n"); + goto out; + } + + /* Enable main watchdog */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_SYS_CTRL2_BLOCK, + AB8500_MAIN_WDOG_CTRL_REG, MAIN_WDOG_ENA); + if (ret) { + dev_err(di->dev, "faile to enable main watchdog\n"); + goto out; + } + + /* + * Due to internal synchronisation, Enable and Kick watchdog bits + * cannot be enabled in a single write. + * A minimum delay of 2*32 kHz period (62.5µs) must be inserted + * between writing Enable then Kick bits. + */ + udelay(63); + + /* Kick main watchdog */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_SYS_CTRL2_BLOCK, + AB8500_MAIN_WDOG_CTRL_REG, + (MAIN_WDOG_ENA | MAIN_WDOG_KICK)); + if (ret) { + dev_err(di->dev, "failed to kick main watchdog\n"); + goto out; + } + + /* Disable main watchdog */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_SYS_CTRL2_BLOCK, + AB8500_MAIN_WDOG_CTRL_REG, MAIN_WDOG_DIS); + if (ret) { + dev_err(di->dev, "failed to disable main watchdog\n"); + goto out; + } + + /* Set watchdog timeout */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_WD_TIMER_REG, WD_TIMER); + if (ret) { + dev_err(di->dev, "failed to set charger watchdog timeout\n"); + goto out; + } + + /* Backup battery voltage and current */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_RTC, + AB8500_RTC_BACKUP_CHG_REG, + di->bat->bkup_bat_v | + di->bat->bkup_bat_i); + if (ret) { + dev_err(di->dev, "failed to setup backup battery charging\n"); + goto out; + } + + /* Enable backup battery charging */ + abx500_mask_and_set_register_interruptible(di->dev, + AB8500_RTC, AB8500_RTC_CTRL_REG, + RTC_BUP_CH_ENA, RTC_BUP_CH_ENA); + if (ret < 0) + dev_err(di->dev, "%s mask and set failed\n", __func__); + +out: + return ret; +} + +/* + * ab8500 charger driver interrupts and their respective isr + */ +static struct ab8500_charger_interrupts ab8500_charger_irq[] = { + {"MAIN_CH_UNPLUG_DET", ab8500_charger_mainchunplugdet_handler}, + {"MAIN_CHARGE_PLUG_DET", ab8500_charger_mainchplugdet_handler}, + {"MAIN_EXT_CH_NOT_OK", ab8500_charger_mainextchnotok_handler}, + {"MAIN_CH_TH_PROT_R", ab8500_charger_mainchthprotr_handler}, + {"MAIN_CH_TH_PROT_F", ab8500_charger_mainchthprotf_handler}, + {"VBUS_DET_F", ab8500_charger_vbusdetf_handler}, + {"VBUS_DET_R", ab8500_charger_vbusdetr_handler}, + {"USB_LINK_STATUS", ab8500_charger_usblinkstatus_handler}, + {"USB_CH_TH_PROT_R", ab8500_charger_usbchthprotr_handler}, + {"USB_CH_TH_PROT_F", ab8500_charger_usbchthprotf_handler}, + {"USB_CHARGER_NOT_OKR", ab8500_charger_usbchargernotokr_handler}, + {"VBUS_OVV", ab8500_charger_vbusovv_handler}, + {"CH_WD_EXP", ab8500_charger_chwdexp_handler}, +}; + +static int ab8500_charger_usb_notifier_call(struct notifier_block *nb, + unsigned long event, void *power) +{ + struct ab8500_charger *di = + container_of(nb, struct ab8500_charger, nb); + enum ab8500_usb_state bm_usb_state; + unsigned mA = *((unsigned *)power); + + if (event != USB_EVENT_VBUS) { + dev_dbg(di->dev, "not a standard host, returning\n"); + return NOTIFY_DONE; + } + + /* TODO: State is fabricate here. See if charger really needs USB + * state or if mA is enough + */ + if ((di->usb_state.usb_current == 2) && (mA > 2)) + bm_usb_state = AB8500_BM_USB_STATE_RESUME; + else if (mA == 0) + bm_usb_state = AB8500_BM_USB_STATE_RESET_HS; + else if (mA == 2) + bm_usb_state = AB8500_BM_USB_STATE_SUSPEND; + else if (mA >= 8) /* 8, 100, 500 */ + bm_usb_state = AB8500_BM_USB_STATE_CONFIGURED; + else /* Should never occur */ + bm_usb_state = AB8500_BM_USB_STATE_RESET_FS; + + dev_dbg(di->dev, "%s usb_state: 0x%02x mA: %d\n", + __func__, bm_usb_state, mA); + + spin_lock(&di->usb_state.usb_lock); + di->usb_state.usb_changed = true; + spin_unlock(&di->usb_state.usb_lock); + + di->usb_state.state = bm_usb_state; + di->usb_state.usb_current = mA; + + queue_work(di->charger_wq, &di->usb_state_changed_work); + + return NOTIFY_OK; +} + +#if defined(CONFIG_PM) +static int ab8500_charger_resume(struct platform_device *pdev) +{ + int ret; + struct ab8500_charger *di = platform_get_drvdata(pdev); + + /* + * For ABB revision 1.0 and 1.1 there is a bug in the watchdog + * logic. That means we have to continously kick the charger + * watchdog even when no charger is connected. This is only + * valid once the AC charger has been enabled. This is + * a bug that is not handled by the algorithm and the + * watchdog have to be kicked by the charger driver + * when the AC charger is disabled + */ + if (di->ac_conn && is_ab8500_1p1_or_earlier(di->parent)) { + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CHARG_WD_CTRL, CHARG_WD_KICK); + if (ret) + dev_err(di->dev, "Failed to kick WD!\n"); + + /* If not already pending start a new timer */ + if (!delayed_work_pending( + &di->kick_wd_work)) { + queue_delayed_work(di->charger_wq, &di->kick_wd_work, + round_jiffies(WD_KICK_INTERVAL)); + } + } + + /* If we still have a HW failure, schedule a new check */ + if (di->flags.mainextchnotok || di->flags.vbus_ovv) { + queue_delayed_work(di->charger_wq, + &di->check_hw_failure_work, 0); + } + + return 0; +} + +static int ab8500_charger_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct ab8500_charger *di = platform_get_drvdata(pdev); + + /* Cancel any pending HW failure check */ + if (delayed_work_pending(&di->check_hw_failure_work)) + cancel_delayed_work(&di->check_hw_failure_work); + + return 0; +} +#else +#define ab8500_charger_suspend NULL +#define ab8500_charger_resume NULL +#endif + +static int __devexit ab8500_charger_remove(struct platform_device *pdev) +{ + struct ab8500_charger *di = platform_get_drvdata(pdev); + int i, irq, ret; + + /* Disable AC charging */ + ab8500_charger_ac_en(&di->ac_chg, false, 0, 0); + + /* Disable USB charging */ + ab8500_charger_usb_en(&di->usb_chg, false, 0, 0); + + /* Disable interrupts */ + for (i = 0; i < ARRAY_SIZE(ab8500_charger_irq); i++) { + irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name); + free_irq(irq, di); + } + + /* disable the regulator */ + regulator_put(di->regu); + + /* Backup battery voltage and current disable */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_RTC, AB8500_RTC_CTRL_REG, RTC_BUP_CH_ENA, 0); + if (ret < 0) + dev_err(di->dev, "%s mask and set failed\n", __func__); + + usb_unregister_notifier(di->usb_phy, &di->nb); + usb_put_transceiver(di->usb_phy); + + /* Delete the work queue */ + destroy_workqueue(di->charger_wq); + + flush_scheduled_work(); + power_supply_unregister(&di->usb_chg.psy); + power_supply_unregister(&di->ac_chg.psy); + platform_set_drvdata(pdev, NULL); + kfree(di); + + return 0; +} + +static int __devinit ab8500_charger_probe(struct platform_device *pdev) +{ + int irq, i, charger_status, ret = 0; + struct abx500_bm_plat_data *plat_data; + + struct ab8500_charger *di = + kzalloc(sizeof(struct ab8500_charger), GFP_KERNEL); + if (!di) + return -ENOMEM; + + /* get parent data */ + di->dev = &pdev->dev; + di->parent = dev_get_drvdata(pdev->dev.parent); + di->gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + + /* initialize lock */ + spin_lock_init(&di->usb_state.usb_lock); + + /* get charger specific platform data */ + plat_data = pdev->dev.platform_data; + di->pdata = plat_data->charger; + + if (!di->pdata) { + dev_err(di->dev, "no charger platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + + /* get battery specific platform data */ + di->bat = plat_data->battery; + if (!di->bat) { + dev_err(di->dev, "no battery platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + + di->autopower = false; + + /* AC supply */ + /* power_supply base class */ + di->ac_chg.psy.name = "ab8500_ac"; + di->ac_chg.psy.type = POWER_SUPPLY_TYPE_MAINS; + di->ac_chg.psy.properties = ab8500_charger_ac_props; + di->ac_chg.psy.num_properties = ARRAY_SIZE(ab8500_charger_ac_props); + di->ac_chg.psy.get_property = ab8500_charger_ac_get_property; + di->ac_chg.psy.supplied_to = di->pdata->supplied_to; + di->ac_chg.psy.num_supplicants = di->pdata->num_supplicants; + /* ux500_charger sub-class */ + di->ac_chg.ops.enable = &ab8500_charger_ac_en; + di->ac_chg.ops.kick_wd = &ab8500_charger_watchdog_kick; + di->ac_chg.ops.update_curr = &ab8500_charger_update_charger_current; + di->ac_chg.max_out_volt = ab8500_charger_voltage_map[ + ARRAY_SIZE(ab8500_charger_voltage_map) - 1]; + di->ac_chg.max_out_curr = ab8500_charger_current_map[ + ARRAY_SIZE(ab8500_charger_current_map) - 1]; + + /* USB supply */ + /* power_supply base class */ + di->usb_chg.psy.name = "ab8500_usb"; + di->usb_chg.psy.type = POWER_SUPPLY_TYPE_USB; + di->usb_chg.psy.properties = ab8500_charger_usb_props; + di->usb_chg.psy.num_properties = ARRAY_SIZE(ab8500_charger_usb_props); + di->usb_chg.psy.get_property = ab8500_charger_usb_get_property; + di->usb_chg.psy.supplied_to = di->pdata->supplied_to; + di->usb_chg.psy.num_supplicants = di->pdata->num_supplicants; + /* ux500_charger sub-class */ + di->usb_chg.ops.enable = &ab8500_charger_usb_en; + di->usb_chg.ops.kick_wd = &ab8500_charger_watchdog_kick; + di->usb_chg.ops.update_curr = &ab8500_charger_update_charger_current; + di->usb_chg.max_out_volt = ab8500_charger_voltage_map[ + ARRAY_SIZE(ab8500_charger_voltage_map) - 1]; + di->usb_chg.max_out_curr = ab8500_charger_current_map[ + ARRAY_SIZE(ab8500_charger_current_map) - 1]; + + + /* Create a work queue for the charger */ + di->charger_wq = + create_singlethread_workqueue("ab8500_charger_wq"); + if (di->charger_wq == NULL) { + dev_err(di->dev, "failed to create work queue\n"); + goto free_device_info; + } + + /* Init work for HW failure check */ + INIT_DELAYED_WORK_DEFERRABLE(&di->check_hw_failure_work, + ab8500_charger_check_hw_failure_work); + INIT_DELAYED_WORK_DEFERRABLE(&di->check_usbchgnotok_work, + ab8500_charger_check_usbchargernotok_work); + + /* + * For ABB revision 1.0 and 1.1 there is a bug in the watchdog + * logic. That means we have to continously kick the charger + * watchdog even when no charger is connected. This is only + * valid once the AC charger has been enabled. This is + * a bug that is not handled by the algorithm and the + * watchdog have to be kicked by the charger driver + * when the AC charger is disabled + */ + INIT_DELAYED_WORK_DEFERRABLE(&di->kick_wd_work, + ab8500_charger_kick_watchdog_work); + + INIT_DELAYED_WORK_DEFERRABLE(&di->check_vbat_work, + ab8500_charger_check_vbat_work); + + /* Init work for charger detection */ + INIT_WORK(&di->usb_link_status_work, + ab8500_charger_usb_link_status_work); + INIT_WORK(&di->ac_work, ab8500_charger_ac_work); + INIT_WORK(&di->detect_usb_type_work, + ab8500_charger_detect_usb_type_work); + + INIT_WORK(&di->usb_state_changed_work, + ab8500_charger_usb_state_changed_work); + + /* Init work for checking HW status */ + INIT_WORK(&di->check_main_thermal_prot_work, + ab8500_charger_check_main_thermal_prot_work); + INIT_WORK(&di->check_usb_thermal_prot_work, + ab8500_charger_check_usb_thermal_prot_work); + + /* + * VDD ADC supply needs to be enabled from this driver when there + * is a charger connected to avoid erroneous BTEMP_HIGH/LOW + * interrupts during charging + */ + di->regu = regulator_get(di->dev, "vddadc"); + if (IS_ERR(di->regu)) { + ret = PTR_ERR(di->regu); + dev_err(di->dev, "failed to get vddadc regulator\n"); + goto free_charger_wq; + } + + + /* Initialize OVV, and other registers */ + ret = ab8500_charger_init_hw_registers(di); + if (ret) { + dev_err(di->dev, "failed to initialize ABB registers\n"); + goto free_regulator; + } + + /* Register AC charger class */ + ret = power_supply_register(di->dev, &di->ac_chg.psy); + if (ret) { + dev_err(di->dev, "failed to register AC charger\n"); + goto free_regulator; + } + + /* Register USB charger class */ + ret = power_supply_register(di->dev, &di->usb_chg.psy); + if (ret) { + dev_err(di->dev, "failed to register USB charger\n"); + goto free_ac; + } + + di->usb_phy = usb_get_transceiver(); + if (!di->usb_phy) { + dev_err(di->dev, "failed to get usb transceiver\n"); + ret = -EINVAL; + goto free_usb; + } + di->nb.notifier_call = ab8500_charger_usb_notifier_call; + ret = usb_register_notifier(di->usb_phy, &di->nb); + if (ret) { + dev_err(di->dev, "failed to register usb notifier\n"); + goto put_usb_phy; + } + + /* Identify the connected charger types during startup */ + charger_status = ab8500_charger_detect_chargers(di); + if (charger_status & AC_PW_CONN) { + di->ac.charger_connected = 1; + di->ac_conn = true; + ab8500_power_supply_changed(di, &di->ac_chg.psy); + sysfs_notify(&di->ac_chg.psy.dev->kobj, NULL, "present"); + } + + if (charger_status & USB_PW_CONN) { + dev_dbg(di->dev, "VBUS Detect during startup\n"); + di->vbus_detected = true; + di->vbus_detected_start = true; + queue_work(di->charger_wq, + &di->detect_usb_type_work); + } + + /* Register interrupts */ + for (i = 0; i < ARRAY_SIZE(ab8500_charger_irq); i++) { + irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name); + ret = request_threaded_irq(irq, NULL, ab8500_charger_irq[i].isr, + IRQF_SHARED | IRQF_NO_SUSPEND, + ab8500_charger_irq[i].name, di); + + if (ret != 0) { + dev_err(di->dev, "failed to request %s IRQ %d: %d\n" + , ab8500_charger_irq[i].name, irq, ret); + goto free_irq; + } + dev_dbg(di->dev, "Requested %s IRQ %d: %d\n", + ab8500_charger_irq[i].name, irq, ret); + } + + platform_set_drvdata(pdev, di); + + return ret; + +free_irq: + usb_unregister_notifier(di->usb_phy, &di->nb); + + /* We also have to free all successfully registered irqs */ + for (i = i - 1; i >= 0; i--) { + irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name); + free_irq(irq, di); + } +put_usb_phy: + usb_put_transceiver(di->usb_phy); +free_usb: + power_supply_unregister(&di->usb_chg.psy); +free_ac: + power_supply_unregister(&di->ac_chg.psy); +free_regulator: + regulator_put(di->regu); +free_charger_wq: + destroy_workqueue(di->charger_wq); +free_device_info: + kfree(di); + + return ret; +} + +static struct platform_driver ab8500_charger_driver = { + .probe = ab8500_charger_probe, + .remove = __devexit_p(ab8500_charger_remove), + .suspend = ab8500_charger_suspend, + .resume = ab8500_charger_resume, + .driver = { + .name = "ab8500-charger", + .owner = THIS_MODULE, + }, +}; + +static int __init ab8500_charger_init(void) +{ + return platform_driver_register(&ab8500_charger_driver); +} + +static void __exit ab8500_charger_exit(void) +{ + platform_driver_unregister(&ab8500_charger_driver); +} + +subsys_initcall_sync(ab8500_charger_init); +module_exit(ab8500_charger_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Johan Palsson, Karl Komierowski, Arun R Murthy"); +MODULE_ALIAS("platform:ab8500-charger"); +MODULE_DESCRIPTION("AB8500 charger management driver"); diff --git a/drivers/power/ab8500_fg.c b/drivers/power/ab8500_fg.c new file mode 100644 index 00000000..c22f2f05 --- /dev/null +++ b/drivers/power/ab8500_fg.c @@ -0,0 +1,2637 @@ +/* + * Copyright (C) ST-Ericsson AB 2012 + * + * Main and Back-up battery management driver. + * + * Note: Backup battery management is required in case of Li-Ion battery and not + * for capacitive battery. HREF boards have capacitive battery and hence backup + * battery management is not used and the supported code is available in this + * driver. + * + * License Terms: GNU General Public License v2 + * Author: + * Johan Palsson <johan.palsson@stericsson.com> + * Karl Komierowski <karl.komierowski@stericsson.com> + * Arun R Murthy <arun.murthy@stericsson.com> + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/kobject.h> +#include <linux/mfd/abx500/ab8500.h> +#include <linux/mfd/abx500.h> +#include <linux/slab.h> +#include <linux/mfd/abx500/ab8500-bm.h> +#include <linux/delay.h> +#include <linux/mfd/abx500/ab8500-gpadc.h> +#include <linux/mfd/abx500.h> +#include <linux/time.h> +#include <linux/completion.h> + +#define MILLI_TO_MICRO 1000 +#define FG_LSB_IN_MA 1627 +#define QLSB_NANO_AMP_HOURS_X10 1129 +#define INS_CURR_TIMEOUT (3 * HZ) + +#define SEC_TO_SAMPLE(S) (S * 4) + +#define NBR_AVG_SAMPLES 20 + +#define LOW_BAT_CHECK_INTERVAL (2 * HZ) + +#define VALID_CAPACITY_SEC (45 * 60) /* 45 minutes */ +#define BATT_OK_MIN 2360 /* mV */ +#define BATT_OK_INCREMENT 50 /* mV */ +#define BATT_OK_MAX_NR_INCREMENTS 0xE + +/* FG constants */ +#define BATT_OVV 0x01 + +#define interpolate(x, x1, y1, x2, y2) \ + ((y1) + ((((y2) - (y1)) * ((x) - (x1))) / ((x2) - (x1)))); + +#define to_ab8500_fg_device_info(x) container_of((x), \ + struct ab8500_fg, fg_psy); + +/** + * struct ab8500_fg_interrupts - ab8500 fg interupts + * @name: name of the interrupt + * @isr function pointer to the isr + */ +struct ab8500_fg_interrupts { + char *name; + irqreturn_t (*isr)(int irq, void *data); +}; + +enum ab8500_fg_discharge_state { + AB8500_FG_DISCHARGE_INIT, + AB8500_FG_DISCHARGE_INITMEASURING, + AB8500_FG_DISCHARGE_INIT_RECOVERY, + AB8500_FG_DISCHARGE_RECOVERY, + AB8500_FG_DISCHARGE_READOUT_INIT, + AB8500_FG_DISCHARGE_READOUT, + AB8500_FG_DISCHARGE_WAKEUP, +}; + +static char *discharge_state[] = { + "DISCHARGE_INIT", + "DISCHARGE_INITMEASURING", + "DISCHARGE_INIT_RECOVERY", + "DISCHARGE_RECOVERY", + "DISCHARGE_READOUT_INIT", + "DISCHARGE_READOUT", + "DISCHARGE_WAKEUP", +}; + +enum ab8500_fg_charge_state { + AB8500_FG_CHARGE_INIT, + AB8500_FG_CHARGE_READOUT, +}; + +static char *charge_state[] = { + "CHARGE_INIT", + "CHARGE_READOUT", +}; + +enum ab8500_fg_calibration_state { + AB8500_FG_CALIB_INIT, + AB8500_FG_CALIB_WAIT, + AB8500_FG_CALIB_END, +}; + +struct ab8500_fg_avg_cap { + int avg; + int samples[NBR_AVG_SAMPLES]; + __kernel_time_t time_stamps[NBR_AVG_SAMPLES]; + int pos; + int nbr_samples; + int sum; +}; + +struct ab8500_fg_battery_capacity { + int max_mah_design; + int max_mah; + int mah; + int permille; + int level; + int prev_mah; + int prev_percent; + int prev_level; + int user_mah; +}; + +struct ab8500_fg_flags { + bool fg_enabled; + bool conv_done; + bool charging; + bool fully_charged; + bool force_full; + bool low_bat_delay; + bool low_bat; + bool bat_ovv; + bool batt_unknown; + bool calibrate; + bool user_cap; + bool batt_id_received; +}; + +struct inst_curr_result_list { + struct list_head list; + int *result; +}; + +/** + * struct ab8500_fg - ab8500 FG device information + * @dev: Pointer to the structure device + * @node: a list of AB8500 FGs, hence prepared for reentrance + * @irq holds the CCEOC interrupt number + * @vbat: Battery voltage in mV + * @vbat_nom: Nominal battery voltage in mV + * @inst_curr: Instantenous battery current in mA + * @avg_curr: Average battery current in mA + * @bat_temp battery temperature + * @fg_samples: Number of samples used in the FG accumulation + * @accu_charge: Accumulated charge from the last conversion + * @recovery_cnt: Counter for recovery mode + * @high_curr_cnt: Counter for high current mode + * @init_cnt: Counter for init mode + * @recovery_needed: Indicate if recovery is needed + * @high_curr_mode: Indicate if we're in high current mode + * @init_capacity: Indicate if initial capacity measuring should be done + * @turn_off_fg: True if fg was off before current measurement + * @calib_state State during offset calibration + * @discharge_state: Current discharge state + * @charge_state: Current charge state + * @ab8500_fg_complete Completion struct used for the instant current reading + * @flags: Structure for information about events triggered + * @bat_cap: Structure for battery capacity specific parameters + * @avg_cap: Average capacity filter + * @parent: Pointer to the struct ab8500 + * @gpadc: Pointer to the struct gpadc + * @pdata: Pointer to the abx500_fg platform data + * @bat: Pointer to the abx500_bm platform data + * @fg_psy: Structure that holds the FG specific battery properties + * @fg_wq: Work queue for running the FG algorithm + * @fg_periodic_work: Work to run the FG algorithm periodically + * @fg_low_bat_work: Work to check low bat condition + * @fg_reinit_work Work used to reset and reinitialise the FG algorithm + * @fg_work: Work to run the FG algorithm instantly + * @fg_acc_cur_work: Work to read the FG accumulator + * @fg_check_hw_failure_work: Work for checking HW state + * @cc_lock: Mutex for locking the CC + * @fg_kobject: Structure of type kobject + */ +struct ab8500_fg { + struct device *dev; + struct list_head node; + int irq; + int vbat; + int vbat_nom; + int inst_curr; + int avg_curr; + int bat_temp; + int fg_samples; + int accu_charge; + int recovery_cnt; + int high_curr_cnt; + int init_cnt; + bool recovery_needed; + bool high_curr_mode; + bool init_capacity; + bool turn_off_fg; + enum ab8500_fg_calibration_state calib_state; + enum ab8500_fg_discharge_state discharge_state; + enum ab8500_fg_charge_state charge_state; + struct completion ab8500_fg_complete; + struct ab8500_fg_flags flags; + struct ab8500_fg_battery_capacity bat_cap; + struct ab8500_fg_avg_cap avg_cap; + struct ab8500 *parent; + struct ab8500_gpadc *gpadc; + struct abx500_fg_platform_data *pdata; + struct abx500_bm_data *bat; + struct power_supply fg_psy; + struct workqueue_struct *fg_wq; + struct delayed_work fg_periodic_work; + struct delayed_work fg_low_bat_work; + struct delayed_work fg_reinit_work; + struct work_struct fg_work; + struct work_struct fg_acc_cur_work; + struct delayed_work fg_check_hw_failure_work; + struct mutex cc_lock; + struct kobject fg_kobject; +}; +static LIST_HEAD(ab8500_fg_list); + +/** + * ab8500_fg_get() - returns a reference to the primary AB8500 fuel gauge + * (i.e. the first fuel gauge in the instance list) + */ +struct ab8500_fg *ab8500_fg_get(void) +{ + struct ab8500_fg *fg; + + if (list_empty(&ab8500_fg_list)) + return NULL; + + fg = list_first_entry(&ab8500_fg_list, struct ab8500_fg, node); + return fg; +} + +/* Main battery properties */ +static enum power_supply_property ab8500_fg_props[] = { + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, + POWER_SUPPLY_PROP_ENERGY_FULL, + POWER_SUPPLY_PROP_ENERGY_NOW, + 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, +}; + +/* + * This array maps the raw hex value to lowbat voltage used by the AB8500 + * Values taken from the UM0836 + */ +static int ab8500_fg_lowbat_voltage_map[] = { + 2300 , + 2325 , + 2350 , + 2375 , + 2400 , + 2425 , + 2450 , + 2475 , + 2500 , + 2525 , + 2550 , + 2575 , + 2600 , + 2625 , + 2650 , + 2675 , + 2700 , + 2725 , + 2750 , + 2775 , + 2800 , + 2825 , + 2850 , + 2875 , + 2900 , + 2925 , + 2950 , + 2975 , + 3000 , + 3025 , + 3050 , + 3075 , + 3100 , + 3125 , + 3150 , + 3175 , + 3200 , + 3225 , + 3250 , + 3275 , + 3300 , + 3325 , + 3350 , + 3375 , + 3400 , + 3425 , + 3450 , + 3475 , + 3500 , + 3525 , + 3550 , + 3575 , + 3600 , + 3625 , + 3650 , + 3675 , + 3700 , + 3725 , + 3750 , + 3775 , + 3800 , + 3825 , + 3850 , + 3850 , +}; + +static u8 ab8500_volt_to_regval(int voltage) +{ + int i; + + if (voltage < ab8500_fg_lowbat_voltage_map[0]) + return 0; + + for (i = 0; i < ARRAY_SIZE(ab8500_fg_lowbat_voltage_map); i++) { + if (voltage < ab8500_fg_lowbat_voltage_map[i]) + return (u8) i - 1; + } + + /* If not captured above, return index of last element */ + return (u8) ARRAY_SIZE(ab8500_fg_lowbat_voltage_map) - 1; +} + +/** + * ab8500_fg_is_low_curr() - Low or high current mode + * @di: pointer to the ab8500_fg structure + * @curr: the current to base or our decision on + * + * Low current mode if the current consumption is below a certain threshold + */ +static int ab8500_fg_is_low_curr(struct ab8500_fg *di, int curr) +{ + /* + * We want to know if we're in low current mode + */ + if (curr > -di->bat->fg_params->high_curr_threshold) + return true; + else + return false; +} + +/** + * ab8500_fg_add_cap_sample() - Add capacity to average filter + * @di: pointer to the ab8500_fg structure + * @sample: the capacity in mAh to add to the filter + * + * A capacity is added to the filter and a new mean capacity is calculated and + * returned + */ +static int ab8500_fg_add_cap_sample(struct ab8500_fg *di, int sample) +{ + struct timespec ts; + struct ab8500_fg_avg_cap *avg = &di->avg_cap; + + getnstimeofday(&ts); + + do { + avg->sum += sample - avg->samples[avg->pos]; + avg->samples[avg->pos] = sample; + avg->time_stamps[avg->pos] = ts.tv_sec; + avg->pos++; + + if (avg->pos == NBR_AVG_SAMPLES) + avg->pos = 0; + + if (avg->nbr_samples < NBR_AVG_SAMPLES) + avg->nbr_samples++; + + /* + * Check the time stamp for each sample. If too old, + * replace with latest sample + */ + } while (ts.tv_sec - VALID_CAPACITY_SEC > avg->time_stamps[avg->pos]); + + avg->avg = avg->sum / avg->nbr_samples; + + return avg->avg; +} + +/** + * ab8500_fg_clear_cap_samples() - Clear average filter + * @di: pointer to the ab8500_fg structure + * + * The capacity filter is is reset to zero. + */ +static void ab8500_fg_clear_cap_samples(struct ab8500_fg *di) +{ + int i; + struct ab8500_fg_avg_cap *avg = &di->avg_cap; + + avg->pos = 0; + avg->nbr_samples = 0; + avg->sum = 0; + avg->avg = 0; + + for (i = 0; i < NBR_AVG_SAMPLES; i++) { + avg->samples[i] = 0; + avg->time_stamps[i] = 0; + } +} + +/** + * ab8500_fg_fill_cap_sample() - Fill average filter + * @di: pointer to the ab8500_fg structure + * @sample: the capacity in mAh to fill the filter with + * + * The capacity filter is filled with a capacity in mAh + */ +static void ab8500_fg_fill_cap_sample(struct ab8500_fg *di, int sample) +{ + int i; + struct timespec ts; + struct ab8500_fg_avg_cap *avg = &di->avg_cap; + + getnstimeofday(&ts); + + for (i = 0; i < NBR_AVG_SAMPLES; i++) { + avg->samples[i] = sample; + avg->time_stamps[i] = ts.tv_sec; + } + + avg->pos = 0; + avg->nbr_samples = NBR_AVG_SAMPLES; + avg->sum = sample * NBR_AVG_SAMPLES; + avg->avg = sample; +} + +/** + * ab8500_fg_coulomb_counter() - enable coulomb counter + * @di: pointer to the ab8500_fg structure + * @enable: enable/disable + * + * Enable/Disable coulomb counter. + * On failure returns negative value. + */ +static int ab8500_fg_coulomb_counter(struct ab8500_fg *di, bool enable) +{ + int ret = 0; + mutex_lock(&di->cc_lock); + if (enable) { + /* To be able to reprogram the number of samples, we have to + * first stop the CC and then enable it again */ + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8500_RTC_CC_CONF_REG, 0x00); + if (ret) + goto cc_err; + + /* Program the samples */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_GAS_GAUGE, AB8500_GASG_CC_NCOV_ACCU, + di->fg_samples); + if (ret) + goto cc_err; + + /* Start the CC */ + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8500_RTC_CC_CONF_REG, + (CC_DEEP_SLEEP_ENA | CC_PWR_UP_ENA)); + if (ret) + goto cc_err; + + di->flags.fg_enabled = true; + } else { + /* Clear any pending read requests */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, 0); + if (ret) + goto cc_err; + + ret = abx500_set_register_interruptible(di->dev, + AB8500_GAS_GAUGE, AB8500_GASG_CC_NCOV_ACCU_CTRL, 0); + if (ret) + goto cc_err; + + /* Stop the CC */ + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8500_RTC_CC_CONF_REG, 0); + if (ret) + goto cc_err; + + di->flags.fg_enabled = false; + + } + dev_dbg(di->dev, " CC enabled: %d Samples: %d\n", + enable, di->fg_samples); + + mutex_unlock(&di->cc_lock); + + return ret; +cc_err: + dev_err(di->dev, "%s Enabling coulomb counter failed\n", __func__); + mutex_unlock(&di->cc_lock); + return ret; +} + +/** + * ab8500_fg_inst_curr_start() - start battery instantaneous current + * @di: pointer to the ab8500_fg structure + * + * Returns 0 or error code + * Note: This is part "one" and has to be called before + * ab8500_fg_inst_curr_finalize() + */ + int ab8500_fg_inst_curr_start(struct ab8500_fg *di) +{ + u8 reg_val; + int ret; + + mutex_lock(&di->cc_lock); + + ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, + AB8500_RTC_CC_CONF_REG, ®_val); + if (ret < 0) + goto fail; + + if (!(reg_val & CC_PWR_UP_ENA)) { + dev_dbg(di->dev, "%s Enable FG\n", __func__); + di->turn_off_fg = true; + + /* Program the samples */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_GAS_GAUGE, AB8500_GASG_CC_NCOV_ACCU, + SEC_TO_SAMPLE(10)); + if (ret) + goto fail; + + /* Start the CC */ + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8500_RTC_CC_CONF_REG, + (CC_DEEP_SLEEP_ENA | CC_PWR_UP_ENA)); + if (ret) + goto fail; + } else { + di->turn_off_fg = false; + } + + /* Return and WFI */ + INIT_COMPLETION(di->ab8500_fg_complete); + enable_irq(di->irq); + + /* Note: cc_lock is still locked */ + return 0; +fail: + mutex_unlock(&di->cc_lock); + return ret; +} + +/** + * ab8500_fg_inst_curr_done() - check if fg conversion is done + * @di: pointer to the ab8500_fg structure + * + * Returns 1 if conversion done, 0 if still waiting + */ +int ab8500_fg_inst_curr_done(struct ab8500_fg *di) +{ + return completion_done(&di->ab8500_fg_complete); +} + +/** + * ab8500_fg_inst_curr_finalize() - battery instantaneous current + * @di: pointer to the ab8500_fg structure + * @res: battery instantenous current(on success) + * + * Returns 0 or an error code + * Note: This is part "two" and has to be called at earliest 250 ms + * after ab8500_fg_inst_curr_start() + */ +int ab8500_fg_inst_curr_finalize(struct ab8500_fg *di, int *res) +{ + u8 low, high; + int val; + int ret; + int timeout; + + if (!completion_done(&di->ab8500_fg_complete)) { + timeout = wait_for_completion_timeout(&di->ab8500_fg_complete, + INS_CURR_TIMEOUT); + dev_dbg(di->dev, "Finalize time: %d ms\n", + ((INS_CURR_TIMEOUT - timeout) * 1000) / HZ); + if (!timeout) { + ret = -ETIME; + disable_irq(di->irq); + dev_err(di->dev, "completion timed out [%d]\n", + __LINE__); + goto fail; + } + } + + disable_irq(di->irq); + + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, + READ_REQ, READ_REQ); + + /* 100uS between read request and read is needed */ + usleep_range(100, 100); + + /* Read CC Sample conversion value Low and high */ + ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE, + AB8500_GASG_CC_SMPL_CNVL_REG, &low); + if (ret < 0) + goto fail; + + ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE, + AB8500_GASG_CC_SMPL_CNVH_REG, &high); + if (ret < 0) + goto fail; + + /* + * negative value for Discharging + * convert 2's compliment into decimal + */ + if (high & 0x10) + val = (low | (high << 8) | 0xFFFFE000); + else + val = (low | (high << 8)); + + /* + * Convert to unit value in mA + * Full scale input voltage is + * 66.660mV => LSB = 66.660mV/(4096*res) = 1.627mA + * Given a 250ms conversion cycle time the LSB corresponds + * to 112.9 nAh. Convert to current by dividing by the conversion + * time in hours (250ms = 1 / (3600 * 4)h) + * 112.9nAh assumes 10mOhm, but fg_res is in 0.1mOhm + */ + val = (val * QLSB_NANO_AMP_HOURS_X10 * 36 * 4) / + (1000 * di->bat->fg_res); + + if (di->turn_off_fg) { + dev_dbg(di->dev, "%s Disable FG\n", __func__); + + /* Clear any pending read requests */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, 0); + if (ret) + goto fail; + + /* Stop the CC */ + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8500_RTC_CC_CONF_REG, 0); + if (ret) + goto fail; + } + mutex_unlock(&di->cc_lock); + (*res) = val; + + return 0; +fail: + mutex_unlock(&di->cc_lock); + return ret; +} + +/** + * ab8500_fg_inst_curr_blocking() - battery instantaneous current + * @di: pointer to the ab8500_fg structure + * @res: battery instantenous current(on success) + * + * Returns 0 else error code + */ +int ab8500_fg_inst_curr_blocking(struct ab8500_fg *di) +{ + int ret; + int res = 0; + + ret = ab8500_fg_inst_curr_start(di); + if (ret) { + dev_err(di->dev, "Failed to initialize fg_inst\n"); + return 0; + } + + ret = ab8500_fg_inst_curr_finalize(di, &res); + if (ret) { + dev_err(di->dev, "Failed to finalize fg_inst\n"); + return 0; + } + + return res; +} + +/** + * ab8500_fg_acc_cur_work() - average battery current + * @work: pointer to the work_struct structure + * + * Updated the average battery current obtained from the + * coulomb counter. + */ +static void ab8500_fg_acc_cur_work(struct work_struct *work) +{ + int val; + int ret; + u8 low, med, high; + + struct ab8500_fg *di = container_of(work, + struct ab8500_fg, fg_acc_cur_work); + + mutex_lock(&di->cc_lock); + ret = abx500_set_register_interruptible(di->dev, AB8500_GAS_GAUGE, + AB8500_GASG_CC_NCOV_ACCU_CTRL, RD_NCONV_ACCU_REQ); + if (ret) + goto exit; + + ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE, + AB8500_GASG_CC_NCOV_ACCU_LOW, &low); + if (ret < 0) + goto exit; + + ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE, + AB8500_GASG_CC_NCOV_ACCU_MED, &med); + if (ret < 0) + goto exit; + + ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE, + AB8500_GASG_CC_NCOV_ACCU_HIGH, &high); + if (ret < 0) + goto exit; + + /* Check for sign bit in case of negative value, 2's compliment */ + if (high & 0x10) + val = (low | (med << 8) | (high << 16) | 0xFFE00000); + else + val = (low | (med << 8) | (high << 16)); + + /* + * Convert to uAh + * Given a 250ms conversion cycle time the LSB corresponds + * to 112.9 nAh. + * 112.9nAh assumes 10mOhm, but fg_res is in 0.1mOhm + */ + di->accu_charge = (val * QLSB_NANO_AMP_HOURS_X10) / + (100 * di->bat->fg_res); + + /* + * Convert to unit value in mA + * Full scale input voltage is + * 66.660mV => LSB = 66.660mV/(4096*res) = 1.627mA + * Given a 250ms conversion cycle time the LSB corresponds + * to 112.9 nAh. Convert to current by dividing by the conversion + * time in hours (= samples / (3600 * 4)h) + * 112.9nAh assumes 10mOhm, but fg_res is in 0.1mOhm + */ + di->avg_curr = (val * QLSB_NANO_AMP_HOURS_X10 * 36) / + (1000 * di->bat->fg_res * (di->fg_samples / 4)); + + di->flags.conv_done = true; + + mutex_unlock(&di->cc_lock); + + queue_work(di->fg_wq, &di->fg_work); + + return; +exit: + dev_err(di->dev, + "Failed to read or write gas gauge registers\n"); + mutex_unlock(&di->cc_lock); + queue_work(di->fg_wq, &di->fg_work); +} + +/** + * ab8500_fg_bat_voltage() - get battery voltage + * @di: pointer to the ab8500_fg structure + * + * Returns battery voltage(on success) else error code + */ +static int ab8500_fg_bat_voltage(struct ab8500_fg *di) +{ + int vbat; + static int prev; + + vbat = ab8500_gpadc_convert(di->gpadc, MAIN_BAT_V); + if (vbat < 0) { + dev_err(di->dev, + "%s gpadc conversion failed, using previous value\n", + __func__); + return prev; + } + + prev = vbat; + return vbat; +} + +/** + * ab8500_fg_volt_to_capacity() - Voltage based capacity + * @di: pointer to the ab8500_fg structure + * @voltage: The voltage to convert to a capacity + * + * Returns battery capacity in per mille based on voltage + */ +static int ab8500_fg_volt_to_capacity(struct ab8500_fg *di, int voltage) +{ + int i, tbl_size; + struct abx500_v_to_cap *tbl; + int cap = 0; + + tbl = di->bat->bat_type[di->bat->batt_id].v_to_cap_tbl, + tbl_size = di->bat->bat_type[di->bat->batt_id].n_v_cap_tbl_elements; + + for (i = 0; i < tbl_size; ++i) { + if (voltage > tbl[i].voltage) + break; + } + + if ((i > 0) && (i < tbl_size)) { + cap = interpolate(voltage, + tbl[i].voltage, + tbl[i].capacity * 10, + tbl[i-1].voltage, + tbl[i-1].capacity * 10); + } else if (i == 0) { + cap = 1000; + } else { + cap = 0; + } + + dev_dbg(di->dev, "%s Vbat: %d, Cap: %d per mille", + __func__, voltage, cap); + + return cap; +} + +/** + * ab8500_fg_uncomp_volt_to_capacity() - Uncompensated voltage based capacity + * @di: pointer to the ab8500_fg structure + * + * Returns battery capacity based on battery voltage that is not compensated + * for the voltage drop due to the load + */ +static int ab8500_fg_uncomp_volt_to_capacity(struct ab8500_fg *di) +{ + di->vbat = ab8500_fg_bat_voltage(di); + return ab8500_fg_volt_to_capacity(di, di->vbat); +} + +/** + * ab8500_fg_battery_resistance() - Returns the battery inner resistance + * @di: pointer to the ab8500_fg structure + * + * Returns battery inner resistance added with the fuel gauge resistor value + * to get the total resistance in the whole link from gnd to bat+ node. + */ +static int ab8500_fg_battery_resistance(struct ab8500_fg *di) +{ + int i, tbl_size; + struct batres_vs_temp *tbl; + int resist = 0; + + tbl = di->bat->bat_type[di->bat->batt_id].batres_tbl; + tbl_size = di->bat->bat_type[di->bat->batt_id].n_batres_tbl_elements; + + for (i = 0; i < tbl_size; ++i) { + if (di->bat_temp / 10 > tbl[i].temp) + break; + } + + if ((i > 0) && (i < tbl_size)) { + resist = interpolate(di->bat_temp / 10, + tbl[i].temp, + tbl[i].resist, + tbl[i-1].temp, + tbl[i-1].resist); + } else if (i == 0) { + resist = tbl[0].resist; + } else { + resist = tbl[tbl_size - 1].resist; + } + + dev_dbg(di->dev, "%s Temp: %d battery internal resistance: %d" + " fg resistance %d, total: %d (mOhm)\n", + __func__, di->bat_temp, resist, di->bat->fg_res / 10, + (di->bat->fg_res / 10) + resist); + + /* fg_res variable is in 0.1mOhm */ + resist += di->bat->fg_res / 10; + + return resist; +} + +/** + * ab8500_fg_load_comp_volt_to_capacity() - Load compensated voltage based capacity + * @di: pointer to the ab8500_fg structure + * + * Returns battery capacity based on battery voltage that is load compensated + * for the voltage drop + */ +static int ab8500_fg_load_comp_volt_to_capacity(struct ab8500_fg *di) +{ + int vbat_comp, res; + int i = 0; + int vbat = 0; + + ab8500_fg_inst_curr_start(di); + + do { + vbat += ab8500_fg_bat_voltage(di); + i++; + msleep(5); + } while (!ab8500_fg_inst_curr_done(di)); + + ab8500_fg_inst_curr_finalize(di, &di->inst_curr); + + di->vbat = vbat / i; + res = ab8500_fg_battery_resistance(di); + + /* Use Ohms law to get the load compensated voltage */ + vbat_comp = di->vbat - (di->inst_curr * res) / 1000; + + dev_dbg(di->dev, "%s Measured Vbat: %dmV,Compensated Vbat %dmV, " + "R: %dmOhm, Current: %dmA Vbat Samples: %d\n", + __func__, di->vbat, vbat_comp, res, di->inst_curr, i); + + return ab8500_fg_volt_to_capacity(di, vbat_comp); +} + +/** + * ab8500_fg_convert_mah_to_permille() - Capacity in mAh to permille + * @di: pointer to the ab8500_fg structure + * @cap_mah: capacity in mAh + * + * Converts capacity in mAh to capacity in permille + */ +static int ab8500_fg_convert_mah_to_permille(struct ab8500_fg *di, int cap_mah) +{ + return (cap_mah * 1000) / di->bat_cap.max_mah_design; +} + +/** + * ab8500_fg_convert_permille_to_mah() - Capacity in permille to mAh + * @di: pointer to the ab8500_fg structure + * @cap_pm: capacity in permille + * + * Converts capacity in permille to capacity in mAh + */ +static int ab8500_fg_convert_permille_to_mah(struct ab8500_fg *di, int cap_pm) +{ + return cap_pm * di->bat_cap.max_mah_design / 1000; +} + +/** + * ab8500_fg_convert_mah_to_uwh() - Capacity in mAh to uWh + * @di: pointer to the ab8500_fg structure + * @cap_mah: capacity in mAh + * + * Converts capacity in mAh to capacity in uWh + */ +static int ab8500_fg_convert_mah_to_uwh(struct ab8500_fg *di, int cap_mah) +{ + u64 div_res; + u32 div_rem; + + div_res = ((u64) cap_mah) * ((u64) di->vbat_nom); + div_rem = do_div(div_res, 1000); + + /* Make sure to round upwards if necessary */ + if (div_rem >= 1000 / 2) + div_res++; + + return (int) div_res; +} + +/** + * ab8500_fg_calc_cap_charging() - Calculate remaining capacity while charging + * @di: pointer to the ab8500_fg structure + * + * Return the capacity in mAh based on previous calculated capcity and the FG + * accumulator register value. The filter is filled with this capacity + */ +static int ab8500_fg_calc_cap_charging(struct ab8500_fg *di) +{ + dev_dbg(di->dev, "%s cap_mah %d accu_charge %d\n", + __func__, + di->bat_cap.mah, + di->accu_charge); + + /* Capacity should not be less than 0 */ + if (di->bat_cap.mah + di->accu_charge > 0) + di->bat_cap.mah += di->accu_charge; + else + di->bat_cap.mah = 0; + /* + * We force capacity to 100% once when the algorithm + * reports that it's full. + */ + if (di->bat_cap.mah >= di->bat_cap.max_mah_design || + di->flags.force_full) { + di->bat_cap.mah = di->bat_cap.max_mah_design; + } + + ab8500_fg_fill_cap_sample(di, di->bat_cap.mah); + di->bat_cap.permille = + ab8500_fg_convert_mah_to_permille(di, di->bat_cap.mah); + + /* We need to update battery voltage and inst current when charging */ + di->vbat = ab8500_fg_bat_voltage(di); + di->inst_curr = ab8500_fg_inst_curr_blocking(di); + + return di->bat_cap.mah; +} + +/** + * ab8500_fg_calc_cap_discharge_voltage() - Capacity in discharge with voltage + * @di: pointer to the ab8500_fg structure + * @comp: if voltage should be load compensated before capacity calc + * + * Return the capacity in mAh based on the battery voltage. The voltage can + * either be load compensated or not. This value is added to the filter and a + * new mean value is calculated and returned. + */ +static int ab8500_fg_calc_cap_discharge_voltage(struct ab8500_fg *di, bool comp) +{ + int permille, mah; + + if (comp) + permille = ab8500_fg_load_comp_volt_to_capacity(di); + else + permille = ab8500_fg_uncomp_volt_to_capacity(di); + + mah = ab8500_fg_convert_permille_to_mah(di, permille); + + di->bat_cap.mah = ab8500_fg_add_cap_sample(di, mah); + di->bat_cap.permille = + ab8500_fg_convert_mah_to_permille(di, di->bat_cap.mah); + + return di->bat_cap.mah; +} + +/** + * ab8500_fg_calc_cap_discharge_fg() - Capacity in discharge with FG + * @di: pointer to the ab8500_fg structure + * + * Return the capacity in mAh based on previous calculated capcity and the FG + * accumulator register value. This value is added to the filter and a + * new mean value is calculated and returned. + */ +static int ab8500_fg_calc_cap_discharge_fg(struct ab8500_fg *di) +{ + int permille_volt, permille; + + dev_dbg(di->dev, "%s cap_mah %d accu_charge %d\n", + __func__, + di->bat_cap.mah, + di->accu_charge); + + /* Capacity should not be less than 0 */ + if (di->bat_cap.mah + di->accu_charge > 0) + di->bat_cap.mah += di->accu_charge; + else + di->bat_cap.mah = 0; + + if (di->bat_cap.mah >= di->bat_cap.max_mah_design) + di->bat_cap.mah = di->bat_cap.max_mah_design; + + /* + * Check against voltage based capacity. It can not be lower + * than what the uncompensated voltage says + */ + permille = ab8500_fg_convert_mah_to_permille(di, di->bat_cap.mah); + permille_volt = ab8500_fg_uncomp_volt_to_capacity(di); + + if (permille < permille_volt) { + di->bat_cap.permille = permille_volt; + di->bat_cap.mah = ab8500_fg_convert_permille_to_mah(di, + di->bat_cap.permille); + + dev_dbg(di->dev, "%s voltage based: perm %d perm_volt %d\n", + __func__, + permille, + permille_volt); + + ab8500_fg_fill_cap_sample(di, di->bat_cap.mah); + } else { + ab8500_fg_fill_cap_sample(di, di->bat_cap.mah); + di->bat_cap.permille = + ab8500_fg_convert_mah_to_permille(di, di->bat_cap.mah); + } + + return di->bat_cap.mah; +} + +/** + * ab8500_fg_capacity_level() - Get the battery capacity level + * @di: pointer to the ab8500_fg structure + * + * Get the battery capacity level based on the capacity in percent + */ +static int ab8500_fg_capacity_level(struct ab8500_fg *di) +{ + int ret, percent; + + percent = di->bat_cap.permille / 10; + + if (percent <= di->bat->cap_levels->critical || + di->flags.low_bat) + ret = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; + else if (percent <= di->bat->cap_levels->low) + ret = POWER_SUPPLY_CAPACITY_LEVEL_LOW; + else if (percent <= di->bat->cap_levels->normal) + ret = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; + else if (percent <= di->bat->cap_levels->high) + ret = POWER_SUPPLY_CAPACITY_LEVEL_HIGH; + else + ret = POWER_SUPPLY_CAPACITY_LEVEL_FULL; + + return ret; +} + +/** + * ab8500_fg_check_capacity_limits() - Check if capacity has changed + * @di: pointer to the ab8500_fg structure + * @init: capacity is allowed to go up in init mode + * + * Check if capacity or capacity limit has changed and notify the system + * about it using the power_supply framework + */ +static void ab8500_fg_check_capacity_limits(struct ab8500_fg *di, bool init) +{ + bool changed = false; + + di->bat_cap.level = ab8500_fg_capacity_level(di); + + if (di->bat_cap.level != di->bat_cap.prev_level) { + /* + * We do not allow reported capacity level to go up + * unless we're charging or if we're in init + */ + if (!(!di->flags.charging && di->bat_cap.level > + di->bat_cap.prev_level) || init) { + dev_dbg(di->dev, "level changed from %d to %d\n", + di->bat_cap.prev_level, + di->bat_cap.level); + di->bat_cap.prev_level = di->bat_cap.level; + changed = true; + } else { + dev_dbg(di->dev, "level not allowed to go up " + "since no charger is connected: %d to %d\n", + di->bat_cap.prev_level, + di->bat_cap.level); + } + } + + /* + * If we have received the LOW_BAT IRQ, set capacity to 0 to initiate + * shutdown + */ + if (di->flags.low_bat) { + dev_dbg(di->dev, "Battery low, set capacity to 0\n"); + di->bat_cap.prev_percent = 0; + di->bat_cap.permille = 0; + di->bat_cap.prev_mah = 0; + di->bat_cap.mah = 0; + changed = true; + } else if (di->flags.fully_charged) { + /* + * We report 100% if algorithm reported fully charged + * unless capacity drops too much + */ + if (di->flags.force_full) { + di->bat_cap.prev_percent = di->bat_cap.permille / 10; + di->bat_cap.prev_mah = di->bat_cap.mah; + } else if (!di->flags.force_full && + di->bat_cap.prev_percent != + (di->bat_cap.permille) / 10 && + (di->bat_cap.permille / 10) < + di->bat->fg_params->maint_thres) { + dev_dbg(di->dev, + "battery reported full " + "but capacity dropping: %d\n", + di->bat_cap.permille / 10); + di->bat_cap.prev_percent = di->bat_cap.permille / 10; + di->bat_cap.prev_mah = di->bat_cap.mah; + + changed = true; + } + } else if (di->bat_cap.prev_percent != di->bat_cap.permille / 10) { + if (di->bat_cap.permille / 10 == 0) { + /* + * We will not report 0% unless we've got + * the LOW_BAT IRQ, no matter what the FG + * algorithm says. + */ + di->bat_cap.prev_percent = 1; + di->bat_cap.permille = 1; + di->bat_cap.prev_mah = 1; + di->bat_cap.mah = 1; + + changed = true; + } else if (!(!di->flags.charging && + (di->bat_cap.permille / 10) > + di->bat_cap.prev_percent) || init) { + /* + * We do not allow reported capacity to go up + * unless we're charging or if we're in init + */ + dev_dbg(di->dev, + "capacity changed from %d to %d (%d)\n", + di->bat_cap.prev_percent, + di->bat_cap.permille / 10, + di->bat_cap.permille); + di->bat_cap.prev_percent = di->bat_cap.permille / 10; + di->bat_cap.prev_mah = di->bat_cap.mah; + + changed = true; + } else { + dev_dbg(di->dev, "capacity not allowed to go up since " + "no charger is connected: %d to %d (%d)\n", + di->bat_cap.prev_percent, + di->bat_cap.permille / 10, + di->bat_cap.permille); + } + } + + if (changed) { + power_supply_changed(&di->fg_psy); + if (di->flags.fully_charged && di->flags.force_full) { + dev_dbg(di->dev, "Battery full, notifying.\n"); + di->flags.force_full = false; + sysfs_notify(&di->fg_kobject, NULL, "charge_full"); + } + sysfs_notify(&di->fg_kobject, NULL, "charge_now"); + } +} + +static void ab8500_fg_charge_state_to(struct ab8500_fg *di, + enum ab8500_fg_charge_state new_state) +{ + dev_dbg(di->dev, "Charge state from %d [%s] to %d [%s]\n", + di->charge_state, + charge_state[di->charge_state], + new_state, + charge_state[new_state]); + + di->charge_state = new_state; +} + +static void ab8500_fg_discharge_state_to(struct ab8500_fg *di, + enum ab8500_fg_discharge_state new_state) +{ + dev_dbg(di->dev, "Disharge state from %d [%s] to %d [%s]\n", + di->discharge_state, + discharge_state[di->discharge_state], + new_state, + discharge_state[new_state]); + + di->discharge_state = new_state; +} + +/** + * ab8500_fg_algorithm_charging() - FG algorithm for when charging + * @di: pointer to the ab8500_fg structure + * + * Battery capacity calculation state machine for when we're charging + */ +static void ab8500_fg_algorithm_charging(struct ab8500_fg *di) +{ + /* + * If we change to discharge mode + * we should start with recovery + */ + if (di->discharge_state != AB8500_FG_DISCHARGE_INIT_RECOVERY) + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_INIT_RECOVERY); + + switch (di->charge_state) { + case AB8500_FG_CHARGE_INIT: + di->fg_samples = SEC_TO_SAMPLE( + di->bat->fg_params->accu_charging); + + ab8500_fg_coulomb_counter(di, true); + ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_READOUT); + + break; + + case AB8500_FG_CHARGE_READOUT: + /* + * Read the FG and calculate the new capacity + */ + mutex_lock(&di->cc_lock); + if (!di->flags.conv_done) { + /* Wasn't the CC IRQ that got us here */ + mutex_unlock(&di->cc_lock); + dev_dbg(di->dev, "%s CC conv not done\n", + __func__); + + break; + } + di->flags.conv_done = false; + mutex_unlock(&di->cc_lock); + + ab8500_fg_calc_cap_charging(di); + + break; + + default: + break; + } + + /* Check capacity limits */ + ab8500_fg_check_capacity_limits(di, false); +} + +static void force_capacity(struct ab8500_fg *di) +{ + int cap; + + ab8500_fg_clear_cap_samples(di); + cap = di->bat_cap.user_mah; + if (cap > di->bat_cap.max_mah_design) { + dev_dbg(di->dev, "Remaining cap %d can't be bigger than total" + " %d\n", cap, di->bat_cap.max_mah_design); + cap = di->bat_cap.max_mah_design; + } + ab8500_fg_fill_cap_sample(di, di->bat_cap.user_mah); + di->bat_cap.permille = ab8500_fg_convert_mah_to_permille(di, cap); + di->bat_cap.mah = cap; + ab8500_fg_check_capacity_limits(di, true); +} + +static bool check_sysfs_capacity(struct ab8500_fg *di) +{ + int cap, lower, upper; + int cap_permille; + + cap = di->bat_cap.user_mah; + + cap_permille = ab8500_fg_convert_mah_to_permille(di, + di->bat_cap.user_mah); + + lower = di->bat_cap.permille - di->bat->fg_params->user_cap_limit * 10; + upper = di->bat_cap.permille + di->bat->fg_params->user_cap_limit * 10; + + if (lower < 0) + lower = 0; + /* 1000 is permille, -> 100 percent */ + if (upper > 1000) + upper = 1000; + + dev_dbg(di->dev, "Capacity limits:" + " (Lower: %d User: %d Upper: %d) [user: %d, was: %d]\n", + lower, cap_permille, upper, cap, di->bat_cap.mah); + + /* If within limits, use the saved capacity and exit estimation...*/ + if (cap_permille > lower && cap_permille < upper) { + dev_dbg(di->dev, "OK! Using users cap %d uAh now\n", cap); + force_capacity(di); + return true; + } + dev_dbg(di->dev, "Capacity from user out of limits, ignoring"); + return false; +} + +/** + * ab8500_fg_algorithm_discharging() - FG algorithm for when discharging + * @di: pointer to the ab8500_fg structure + * + * Battery capacity calculation state machine for when we're discharging + */ +static void ab8500_fg_algorithm_discharging(struct ab8500_fg *di) +{ + int sleep_time; + + /* If we change to charge mode we should start with init */ + if (di->charge_state != AB8500_FG_CHARGE_INIT) + ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_INIT); + + switch (di->discharge_state) { + case AB8500_FG_DISCHARGE_INIT: + /* We use the FG IRQ to work on */ + di->init_cnt = 0; + di->fg_samples = SEC_TO_SAMPLE(di->bat->fg_params->init_timer); + ab8500_fg_coulomb_counter(di, true); + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_INITMEASURING); + + /* Intentional fallthrough */ + case AB8500_FG_DISCHARGE_INITMEASURING: + /* + * Discard a number of samples during startup. + * After that, use compensated voltage for a few + * samples to get an initial capacity. + * Then go to READOUT + */ + sleep_time = di->bat->fg_params->init_timer; + + /* Discard the first [x] seconds */ + if (di->init_cnt > + di->bat->fg_params->init_discard_time) { + ab8500_fg_calc_cap_discharge_voltage(di, true); + + ab8500_fg_check_capacity_limits(di, true); + } + + di->init_cnt += sleep_time; + if (di->init_cnt > di->bat->fg_params->init_total_time) + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_READOUT_INIT); + + break; + + case AB8500_FG_DISCHARGE_INIT_RECOVERY: + di->recovery_cnt = 0; + di->recovery_needed = true; + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_RECOVERY); + + /* Intentional fallthrough */ + + case AB8500_FG_DISCHARGE_RECOVERY: + sleep_time = di->bat->fg_params->recovery_sleep_timer; + + /* + * We should check the power consumption + * If low, go to READOUT (after x min) or + * RECOVERY_SLEEP if time left. + * If high, go to READOUT + */ + di->inst_curr = ab8500_fg_inst_curr_blocking(di); + + if (ab8500_fg_is_low_curr(di, di->inst_curr)) { + if (di->recovery_cnt > + di->bat->fg_params->recovery_total_time) { + di->fg_samples = SEC_TO_SAMPLE( + di->bat->fg_params->accu_high_curr); + ab8500_fg_coulomb_counter(di, true); + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_READOUT); + di->recovery_needed = false; + } else { + queue_delayed_work(di->fg_wq, + &di->fg_periodic_work, + sleep_time * HZ); + } + di->recovery_cnt += sleep_time; + } else { + di->fg_samples = SEC_TO_SAMPLE( + di->bat->fg_params->accu_high_curr); + ab8500_fg_coulomb_counter(di, true); + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_READOUT); + } + break; + + case AB8500_FG_DISCHARGE_READOUT_INIT: + di->fg_samples = SEC_TO_SAMPLE( + di->bat->fg_params->accu_high_curr); + ab8500_fg_coulomb_counter(di, true); + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_READOUT); + break; + + case AB8500_FG_DISCHARGE_READOUT: + di->inst_curr = ab8500_fg_inst_curr_blocking(di); + + if (ab8500_fg_is_low_curr(di, di->inst_curr)) { + /* Detect mode change */ + if (di->high_curr_mode) { + di->high_curr_mode = false; + di->high_curr_cnt = 0; + } + + if (di->recovery_needed) { + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_RECOVERY); + + queue_delayed_work(di->fg_wq, + &di->fg_periodic_work, 0); + + break; + } + + ab8500_fg_calc_cap_discharge_voltage(di, true); + } else { + mutex_lock(&di->cc_lock); + if (!di->flags.conv_done) { + /* Wasn't the CC IRQ that got us here */ + mutex_unlock(&di->cc_lock); + dev_dbg(di->dev, "%s CC conv not done\n", + __func__); + + break; + } + di->flags.conv_done = false; + mutex_unlock(&di->cc_lock); + + /* Detect mode change */ + if (!di->high_curr_mode) { + di->high_curr_mode = true; + di->high_curr_cnt = 0; + } + + di->high_curr_cnt += + di->bat->fg_params->accu_high_curr; + if (di->high_curr_cnt > + di->bat->fg_params->high_curr_time) + di->recovery_needed = true; + + ab8500_fg_calc_cap_discharge_fg(di); + } + + ab8500_fg_check_capacity_limits(di, false); + + break; + + case AB8500_FG_DISCHARGE_WAKEUP: + ab8500_fg_coulomb_counter(di, true); + di->inst_curr = ab8500_fg_inst_curr_blocking(di); + + ab8500_fg_calc_cap_discharge_voltage(di, true); + + di->fg_samples = SEC_TO_SAMPLE( + di->bat->fg_params->accu_high_curr); + ab8500_fg_coulomb_counter(di, true); + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_READOUT); + + ab8500_fg_check_capacity_limits(di, false); + + break; + + default: + break; + } +} + +/** + * ab8500_fg_algorithm_calibrate() - Internal columb counter offset calibration + * @di: pointer to the ab8500_fg structure + * + */ +static void ab8500_fg_algorithm_calibrate(struct ab8500_fg *di) +{ + int ret; + + switch (di->calib_state) { + case AB8500_FG_CALIB_INIT: + dev_dbg(di->dev, "Calibration ongoing...\n"); + + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, + CC_INT_CAL_N_AVG_MASK, CC_INT_CAL_SAMPLES_8); + if (ret < 0) + goto err; + + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, + CC_INTAVGOFFSET_ENA, CC_INTAVGOFFSET_ENA); + if (ret < 0) + goto err; + di->calib_state = AB8500_FG_CALIB_WAIT; + break; + case AB8500_FG_CALIB_END: + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, + CC_MUXOFFSET, CC_MUXOFFSET); + if (ret < 0) + goto err; + di->flags.calibrate = false; + dev_dbg(di->dev, "Calibration done...\n"); + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); + break; + case AB8500_FG_CALIB_WAIT: + dev_dbg(di->dev, "Calibration WFI\n"); + default: + break; + } + return; +err: + /* Something went wrong, don't calibrate then */ + dev_err(di->dev, "failed to calibrate the CC\n"); + di->flags.calibrate = false; + di->calib_state = AB8500_FG_CALIB_INIT; + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); +} + +/** + * ab8500_fg_algorithm() - Entry point for the FG algorithm + * @di: pointer to the ab8500_fg structure + * + * Entry point for the battery capacity calculation state machine + */ +static void ab8500_fg_algorithm(struct ab8500_fg *di) +{ + if (di->flags.calibrate) + ab8500_fg_algorithm_calibrate(di); + else { + if (di->flags.charging) + ab8500_fg_algorithm_charging(di); + else + ab8500_fg_algorithm_discharging(di); + } + + dev_dbg(di->dev, "[FG_DATA] %d %d %d %d %d %d %d %d %d " + "%d %d %d %d %d %d %d\n", + di->bat_cap.max_mah_design, + di->bat_cap.mah, + di->bat_cap.permille, + di->bat_cap.level, + di->bat_cap.prev_mah, + di->bat_cap.prev_percent, + di->bat_cap.prev_level, + di->vbat, + di->inst_curr, + di->avg_curr, + di->accu_charge, + di->flags.charging, + di->charge_state, + di->discharge_state, + di->high_curr_mode, + di->recovery_needed); +} + +/** + * ab8500_fg_periodic_work() - Run the FG state machine periodically + * @work: pointer to the work_struct structure + * + * Work queue function for periodic work + */ +static void ab8500_fg_periodic_work(struct work_struct *work) +{ + struct ab8500_fg *di = container_of(work, struct ab8500_fg, + fg_periodic_work.work); + + if (di->init_capacity) { + /* A dummy read that will return 0 */ + di->inst_curr = ab8500_fg_inst_curr_blocking(di); + /* Get an initial capacity calculation */ + ab8500_fg_calc_cap_discharge_voltage(di, true); + ab8500_fg_check_capacity_limits(di, true); + di->init_capacity = false; + + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); + } else if (di->flags.user_cap) { + if (check_sysfs_capacity(di)) { + ab8500_fg_check_capacity_limits(di, true); + if (di->flags.charging) + ab8500_fg_charge_state_to(di, + AB8500_FG_CHARGE_INIT); + else + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_READOUT_INIT); + } + di->flags.user_cap = false; + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); + } else + ab8500_fg_algorithm(di); + +} + +/** + * ab8500_fg_check_hw_failure_work() - Check OVV_BAT condition + * @work: pointer to the work_struct structure + * + * Work queue function for checking the OVV_BAT condition + */ +static void ab8500_fg_check_hw_failure_work(struct work_struct *work) +{ + int ret; + u8 reg_value; + + struct ab8500_fg *di = container_of(work, struct ab8500_fg, + fg_check_hw_failure_work.work); + + /* + * If we have had a battery over-voltage situation, + * check ovv-bit to see if it should be reset. + */ + if (di->flags.bat_ovv) { + ret = abx500_get_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_CH_STAT_REG, + ®_value); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return; + } + if ((reg_value & BATT_OVV) != BATT_OVV) { + dev_dbg(di->dev, "Battery recovered from OVV\n"); + di->flags.bat_ovv = false; + power_supply_changed(&di->fg_psy); + return; + } + + /* Not yet recovered from ovv, reschedule this test */ + queue_delayed_work(di->fg_wq, &di->fg_check_hw_failure_work, + round_jiffies(HZ)); + } +} + +/** + * ab8500_fg_low_bat_work() - Check LOW_BAT condition + * @work: pointer to the work_struct structure + * + * Work queue function for checking the LOW_BAT condition + */ +static void ab8500_fg_low_bat_work(struct work_struct *work) +{ + int vbat; + + struct ab8500_fg *di = container_of(work, struct ab8500_fg, + fg_low_bat_work.work); + + vbat = ab8500_fg_bat_voltage(di); + + /* Check if LOW_BAT still fulfilled */ + if (vbat < di->bat->fg_params->lowbat_threshold) { + di->flags.low_bat = true; + dev_warn(di->dev, "Battery voltage still LOW\n"); + + /* + * We need to re-schedule this check to be able to detect + * if the voltage increases again during charging + */ + queue_delayed_work(di->fg_wq, &di->fg_low_bat_work, + round_jiffies(LOW_BAT_CHECK_INTERVAL)); + } else { + di->flags.low_bat = false; + dev_warn(di->dev, "Battery voltage OK again\n"); + } + + /* This is needed to dispatch LOW_BAT */ + ab8500_fg_check_capacity_limits(di, false); + + /* Set this flag to check if LOW_BAT IRQ still occurs */ + di->flags.low_bat_delay = false; +} + +/** + * ab8500_fg_battok_calc - calculate the bit pattern corresponding + * to the target voltage. + * @di: pointer to the ab8500_fg structure + * @target target voltage + * + * Returns bit pattern closest to the target voltage + * valid return values are 0-14. (0-BATT_OK_MAX_NR_INCREMENTS) + */ + +static int ab8500_fg_battok_calc(struct ab8500_fg *di, int target) +{ + if (target > BATT_OK_MIN + + (BATT_OK_INCREMENT * BATT_OK_MAX_NR_INCREMENTS)) + return BATT_OK_MAX_NR_INCREMENTS; + if (target < BATT_OK_MIN) + return 0; + return (target - BATT_OK_MIN) / BATT_OK_INCREMENT; +} + +/** + * ab8500_fg_battok_init_hw_register - init battok levels + * @di: pointer to the ab8500_fg structure + * + */ + +static int ab8500_fg_battok_init_hw_register(struct ab8500_fg *di) +{ + int selected; + int sel0; + int sel1; + int cbp_sel0; + int cbp_sel1; + int ret; + int new_val; + + sel0 = di->bat->fg_params->battok_falling_th_sel0; + sel1 = di->bat->fg_params->battok_raising_th_sel1; + + cbp_sel0 = ab8500_fg_battok_calc(di, sel0); + cbp_sel1 = ab8500_fg_battok_calc(di, sel1); + + selected = BATT_OK_MIN + cbp_sel0 * BATT_OK_INCREMENT; + + if (selected != sel0) + dev_warn(di->dev, "Invalid voltage step:%d, using %d %d\n", + sel0, selected, cbp_sel0); + + selected = BATT_OK_MIN + cbp_sel1 * BATT_OK_INCREMENT; + + if (selected != sel1) + dev_warn(di->dev, "Invalid voltage step:%d, using %d %d\n", + sel1, selected, cbp_sel1); + + new_val = cbp_sel0 | (cbp_sel1 << 4); + + dev_dbg(di->dev, "using: %x %d %d\n", new_val, cbp_sel0, cbp_sel1); + ret = abx500_set_register_interruptible(di->dev, AB8500_SYS_CTRL2_BLOCK, + AB8500_BATT_OK_REG, new_val); + return ret; +} + +/** + * ab8500_fg_instant_work() - Run the FG state machine instantly + * @work: pointer to the work_struct structure + * + * Work queue function for instant work + */ +static void ab8500_fg_instant_work(struct work_struct *work) +{ + struct ab8500_fg *di = container_of(work, struct ab8500_fg, fg_work); + + ab8500_fg_algorithm(di); +} + +/** + * ab8500_fg_cc_data_end_handler() - isr to get battery avg current. + * @irq: interrupt number + * @_di: pointer to the ab8500_fg structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_fg_cc_data_end_handler(int irq, void *_di) +{ + struct ab8500_fg *di = _di; + complete(&di->ab8500_fg_complete); + return IRQ_HANDLED; +} + +/** + * ab8500_fg_cc_convend_handler() - isr to get battery avg current. + * @irq: interrupt number + * @_di: pointer to the ab8500_fg structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_fg_cc_int_calib_handler(int irq, void *_di) +{ + struct ab8500_fg *di = _di; + di->calib_state = AB8500_FG_CALIB_END; + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); + return IRQ_HANDLED; +} + +/** + * ab8500_fg_cc_convend_handler() - isr to get battery avg current. + * @irq: interrupt number + * @_di: pointer to the ab8500_fg structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_fg_cc_convend_handler(int irq, void *_di) +{ + struct ab8500_fg *di = _di; + + queue_work(di->fg_wq, &di->fg_acc_cur_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_fg_batt_ovv_handler() - Battery OVV occured + * @irq: interrupt number + * @_di: pointer to the ab8500_fg structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_fg_batt_ovv_handler(int irq, void *_di) +{ + struct ab8500_fg *di = _di; + + dev_dbg(di->dev, "Battery OVV\n"); + di->flags.bat_ovv = true; + power_supply_changed(&di->fg_psy); + + /* Schedule a new HW failure check */ + queue_delayed_work(di->fg_wq, &di->fg_check_hw_failure_work, 0); + + return IRQ_HANDLED; +} + +/** + * ab8500_fg_lowbatf_handler() - Battery voltage is below LOW threshold + * @irq: interrupt number + * @_di: pointer to the ab8500_fg structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_fg_lowbatf_handler(int irq, void *_di) +{ + struct ab8500_fg *di = _di; + + if (!di->flags.low_bat_delay) { + dev_warn(di->dev, "Battery voltage is below LOW threshold\n"); + di->flags.low_bat_delay = true; + /* + * Start a timer to check LOW_BAT again after some time + * This is done to avoid shutdown on single voltage dips + */ + queue_delayed_work(di->fg_wq, &di->fg_low_bat_work, + round_jiffies(LOW_BAT_CHECK_INTERVAL)); + } + return IRQ_HANDLED; +} + +/** + * ab8500_fg_get_property() - get the fg properties + * @psy: pointer to the power_supply structure + * @psp: pointer to the power_supply_property structure + * @val: pointer to the power_supply_propval union + * + * This function gets called when an application tries to get the + * fg properties by reading the sysfs files. + * voltage_now: battery voltage + * current_now: battery instant current + * current_avg: battery average current + * charge_full_design: capacity where battery is considered full + * charge_now: battery capacity in nAh + * capacity: capacity in percent + * capacity_level: capacity level + * + * Returns error code in case of failure else 0 on success + */ +static int ab8500_fg_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); + + /* + * If battery is identified as unknown and charging of unknown + * batteries is disabled, we always report 100% capacity and + * capacity level UNKNOWN, since we can't calculate + * remaining capacity + */ + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + if (di->flags.bat_ovv) + val->intval = BATT_OVV_VALUE * 1000; + else + val->intval = di->vbat * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = di->inst_curr * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + val->intval = di->avg_curr * 1000; + break; + case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: + val->intval = ab8500_fg_convert_mah_to_uwh(di, + di->bat_cap.max_mah_design); + break; + case POWER_SUPPLY_PROP_ENERGY_FULL: + val->intval = ab8500_fg_convert_mah_to_uwh(di, + di->bat_cap.max_mah); + break; + case POWER_SUPPLY_PROP_ENERGY_NOW: + if (di->flags.batt_unknown && !di->bat->chg_unknown_bat && + di->flags.batt_id_received) + val->intval = ab8500_fg_convert_mah_to_uwh(di, + di->bat_cap.max_mah); + else + val->intval = ab8500_fg_convert_mah_to_uwh(di, + di->bat_cap.prev_mah); + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval = di->bat_cap.max_mah_design; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + val->intval = di->bat_cap.max_mah; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + if (di->flags.batt_unknown && !di->bat->chg_unknown_bat && + di->flags.batt_id_received) + val->intval = di->bat_cap.max_mah; + else + val->intval = di->bat_cap.prev_mah; + break; + case POWER_SUPPLY_PROP_CAPACITY: + if (di->flags.batt_unknown && !di->bat->chg_unknown_bat && + di->flags.batt_id_received) + val->intval = 100; + else + val->intval = di->bat_cap.prev_percent; + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + if (di->flags.batt_unknown && !di->bat->chg_unknown_bat && + di->flags.batt_id_received) + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; + else + val->intval = di->bat_cap.prev_level; + break; + default: + return -EINVAL; + } + return 0; +} + +static int ab8500_fg_get_ext_psy_data(struct device *dev, void *data) +{ + struct power_supply *psy; + struct power_supply *ext; + struct ab8500_fg *di; + union power_supply_propval ret; + int i, j; + bool psy_found = false; + + psy = (struct power_supply *)data; + ext = dev_get_drvdata(dev); + di = to_ab8500_fg_device_info(psy); + + /* + * For all psy where the name of your driver + * appears in any supplied_to + */ + for (i = 0; i < ext->num_supplicants; i++) { + if (!strcmp(ext->supplied_to[i], psy->name)) + psy_found = true; + } + + if (!psy_found) + return 0; + + /* Go through all properties for the psy */ + for (j = 0; j < ext->num_properties; j++) { + enum power_supply_property prop; + prop = ext->properties[j]; + + if (ext->get_property(ext, prop, &ret)) + continue; + + switch (prop) { + case POWER_SUPPLY_PROP_STATUS: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + switch (ret.intval) { + case POWER_SUPPLY_STATUS_UNKNOWN: + case POWER_SUPPLY_STATUS_DISCHARGING: + case POWER_SUPPLY_STATUS_NOT_CHARGING: + if (!di->flags.charging) + break; + di->flags.charging = false; + di->flags.fully_charged = false; + queue_work(di->fg_wq, &di->fg_work); + break; + case POWER_SUPPLY_STATUS_FULL: + if (di->flags.fully_charged) + break; + di->flags.fully_charged = true; + di->flags.force_full = true; + /* Save current capacity as maximum */ + di->bat_cap.max_mah = di->bat_cap.mah; + queue_work(di->fg_wq, &di->fg_work); + break; + case POWER_SUPPLY_STATUS_CHARGING: + if (di->flags.charging) + break; + di->flags.charging = true; + di->flags.fully_charged = false; + queue_work(di->fg_wq, &di->fg_work); + break; + }; + default: + break; + }; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + if (!di->flags.batt_id_received) { + const struct abx500_battery_type *b; + + b = &(di->bat->bat_type[di->bat->batt_id]); + + di->flags.batt_id_received = true; + + di->bat_cap.max_mah_design = + MILLI_TO_MICRO * + b->charge_full_design; + + di->bat_cap.max_mah = + di->bat_cap.max_mah_design; + + di->vbat_nom = b->nominal_voltage; + } + + if (ret.intval) + di->flags.batt_unknown = false; + else + di->flags.batt_unknown = true; + break; + default: + break; + } + break; + case POWER_SUPPLY_PROP_TEMP: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + if (di->flags.batt_id_received) + di->bat_temp = ret.intval; + break; + default: + break; + } + break; + default: + break; + } + } + return 0; +} + +/** + * ab8500_fg_init_hw_registers() - Set up FG related registers + * @di: pointer to the ab8500_fg structure + * + * Set up battery OVV, low battery voltage registers + */ +static int ab8500_fg_init_hw_registers(struct ab8500_fg *di) +{ + int ret; + + /* Set VBAT OVV threshold */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_BATT_OVV, + BATT_OVV_TH_4P75, + BATT_OVV_TH_4P75); + if (ret) { + dev_err(di->dev, "failed to set BATT_OVV\n"); + goto out; + } + + /* Enable VBAT OVV detection */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_BATT_OVV, + BATT_OVV_ENA, + BATT_OVV_ENA); + if (ret) { + dev_err(di->dev, "failed to enable BATT_OVV\n"); + goto out; + } + + /* Low Battery Voltage */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_SYS_CTRL2_BLOCK, + AB8500_LOW_BAT_REG, + ab8500_volt_to_regval( + di->bat->fg_params->lowbat_threshold) << 1 | + LOW_BAT_ENABLE); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + goto out; + } + + /* Battery OK threshold */ + ret = ab8500_fg_battok_init_hw_register(di); + if (ret) { + dev_err(di->dev, "BattOk init write failed.\n"); + goto out; + } +out: + return ret; +} + +/** + * ab8500_fg_external_power_changed() - callback for power supply changes + * @psy: pointer to the structure power_supply + * + * This function is the entry point of the pointer external_power_changed + * of the structure power_supply. + * This function gets executed when there is a change in any external power + * supply that this driver needs to be notified of. + */ +static void ab8500_fg_external_power_changed(struct power_supply *psy) +{ + struct ab8500_fg *di = to_ab8500_fg_device_info(psy); + + class_for_each_device(power_supply_class, NULL, + &di->fg_psy, ab8500_fg_get_ext_psy_data); +} + +/** + * abab8500_fg_reinit_work() - work to reset the FG algorithm + * @work: pointer to the work_struct structure + * + * Used to reset the current battery capacity to be able to + * retrigger a new voltage base capacity calculation. For + * test and verification purpose. + */ +static void ab8500_fg_reinit_work(struct work_struct *work) +{ + struct ab8500_fg *di = container_of(work, struct ab8500_fg, + fg_reinit_work.work); + + if (di->flags.calibrate == false) { + dev_dbg(di->dev, "Resetting FG state machine to init.\n"); + ab8500_fg_clear_cap_samples(di); + ab8500_fg_calc_cap_discharge_voltage(di, true); + ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_INIT); + ab8500_fg_discharge_state_to(di, AB8500_FG_DISCHARGE_INIT); + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); + + } else { + dev_err(di->dev, "Residual offset calibration ongoing " + "retrying..\n"); + /* Wait one second until next try*/ + queue_delayed_work(di->fg_wq, &di->fg_reinit_work, + round_jiffies(1)); + } +} + +/** + * ab8500_fg_reinit() - forces FG algorithm to reinitialize with current values + * + * This function can be used to force the FG algorithm to recalculate a new + * voltage based battery capacity. + */ +void ab8500_fg_reinit(void) +{ + struct ab8500_fg *di = ab8500_fg_get(); + /* User won't be notified if a null pointer returned. */ + if (di != NULL) + queue_delayed_work(di->fg_wq, &di->fg_reinit_work, 0); +} + +/* Exposure to the sysfs interface */ + +struct ab8500_fg_sysfs_entry { + struct attribute attr; + ssize_t (*show)(struct ab8500_fg *, char *); + ssize_t (*store)(struct ab8500_fg *, const char *, size_t); +}; + +static ssize_t charge_full_show(struct ab8500_fg *di, char *buf) +{ + return sprintf(buf, "%d\n", di->bat_cap.max_mah); +} + +static ssize_t charge_full_store(struct ab8500_fg *di, const char *buf, + size_t count) +{ + unsigned long charge_full; + ssize_t ret = -EINVAL; + + ret = strict_strtoul(buf, 10, &charge_full); + + dev_dbg(di->dev, "Ret %zd charge_full %lu", ret, charge_full); + + if (!ret) { + di->bat_cap.max_mah = (int) charge_full; + ret = count; + } + return ret; +} + +static ssize_t charge_now_show(struct ab8500_fg *di, char *buf) +{ + return sprintf(buf, "%d\n", di->bat_cap.prev_mah); +} + +static ssize_t charge_now_store(struct ab8500_fg *di, const char *buf, + size_t count) +{ + unsigned long charge_now; + ssize_t ret; + + ret = strict_strtoul(buf, 10, &charge_now); + + dev_dbg(di->dev, "Ret %zd charge_now %lu was %d", + ret, charge_now, di->bat_cap.prev_mah); + + if (!ret) { + di->bat_cap.user_mah = (int) charge_now; + di->flags.user_cap = true; + ret = count; + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); + } + return ret; +} + +static struct ab8500_fg_sysfs_entry charge_full_attr = + __ATTR(charge_full, 0644, charge_full_show, charge_full_store); + +static struct ab8500_fg_sysfs_entry charge_now_attr = + __ATTR(charge_now, 0644, charge_now_show, charge_now_store); + +static ssize_t +ab8500_fg_show(struct kobject *kobj, struct attribute *attr, char *buf) +{ + struct ab8500_fg_sysfs_entry *entry; + struct ab8500_fg *di; + + entry = container_of(attr, struct ab8500_fg_sysfs_entry, attr); + di = container_of(kobj, struct ab8500_fg, fg_kobject); + + if (!entry->show) + return -EIO; + + return entry->show(di, buf); +} +static ssize_t +ab8500_fg_store(struct kobject *kobj, struct attribute *attr, const char *buf, + size_t count) +{ + struct ab8500_fg_sysfs_entry *entry; + struct ab8500_fg *di; + + entry = container_of(attr, struct ab8500_fg_sysfs_entry, attr); + di = container_of(kobj, struct ab8500_fg, fg_kobject); + + if (!entry->store) + return -EIO; + + return entry->store(di, buf, count); +} + +static const struct sysfs_ops ab8500_fg_sysfs_ops = { + .show = ab8500_fg_show, + .store = ab8500_fg_store, +}; + +static struct attribute *ab8500_fg_attrs[] = { + &charge_full_attr.attr, + &charge_now_attr.attr, + NULL, +}; + +static struct kobj_type ab8500_fg_ktype = { + .sysfs_ops = &ab8500_fg_sysfs_ops, + .default_attrs = ab8500_fg_attrs, +}; + +/** + * ab8500_chargalg_sysfs_exit() - de-init of sysfs entry + * @di: pointer to the struct ab8500_chargalg + * + * This function removes the entry in sysfs. + */ +static void ab8500_fg_sysfs_exit(struct ab8500_fg *di) +{ + kobject_del(&di->fg_kobject); +} + +/** + * ab8500_chargalg_sysfs_init() - init of sysfs entry + * @di: pointer to the struct ab8500_chargalg + * + * This function adds an entry in sysfs. + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_fg_sysfs_init(struct ab8500_fg *di) +{ + int ret = 0; + + ret = kobject_init_and_add(&di->fg_kobject, + &ab8500_fg_ktype, + NULL, "battery"); + if (ret < 0) + dev_err(di->dev, "failed to create sysfs entry\n"); + + return ret; +} +/* Exposure to the sysfs interface <<END>> */ + +#if defined(CONFIG_PM) +static int ab8500_fg_resume(struct platform_device *pdev) +{ + struct ab8500_fg *di = platform_get_drvdata(pdev); + + /* + * Change state if we're not charging. If we're charging we will wake + * up on the FG IRQ + */ + if (!di->flags.charging) { + ab8500_fg_discharge_state_to(di, AB8500_FG_DISCHARGE_WAKEUP); + queue_work(di->fg_wq, &di->fg_work); + } + + return 0; +} + +static int ab8500_fg_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct ab8500_fg *di = platform_get_drvdata(pdev); + + flush_delayed_work(&di->fg_periodic_work); + + /* + * If the FG is enabled we will disable it before going to suspend + * only if we're not charging + */ + if (di->flags.fg_enabled && !di->flags.charging) + ab8500_fg_coulomb_counter(di, false); + + return 0; +} +#else +#define ab8500_fg_suspend NULL +#define ab8500_fg_resume NULL +#endif + +static int __devexit ab8500_fg_remove(struct platform_device *pdev) +{ + int ret = 0; + struct ab8500_fg *di = platform_get_drvdata(pdev); + + list_del(&di->node); + + /* Disable coulomb counter */ + ret = ab8500_fg_coulomb_counter(di, false); + if (ret) + dev_err(di->dev, "failed to disable coulomb counter\n"); + + destroy_workqueue(di->fg_wq); + ab8500_fg_sysfs_exit(di); + + flush_scheduled_work(); + power_supply_unregister(&di->fg_psy); + platform_set_drvdata(pdev, NULL); + kfree(di); + return ret; +} + +/* ab8500 fg driver interrupts and their respective isr */ +static struct ab8500_fg_interrupts ab8500_fg_irq[] = { + {"NCONV_ACCU", ab8500_fg_cc_convend_handler}, + {"BATT_OVV", ab8500_fg_batt_ovv_handler}, + {"LOW_BAT_F", ab8500_fg_lowbatf_handler}, + {"CC_INT_CALIB", ab8500_fg_cc_int_calib_handler}, + {"CCEOC", ab8500_fg_cc_data_end_handler}, +}; + +static int __devinit ab8500_fg_probe(struct platform_device *pdev) +{ + int i, irq; + int ret = 0; + struct abx500_bm_plat_data *plat_data; + + struct ab8500_fg *di = + kzalloc(sizeof(struct ab8500_fg), GFP_KERNEL); + if (!di) + return -ENOMEM; + + mutex_init(&di->cc_lock); + + /* get parent data */ + di->dev = &pdev->dev; + di->parent = dev_get_drvdata(pdev->dev.parent); + di->gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + + /* get fg specific platform data */ + plat_data = pdev->dev.platform_data; + di->pdata = plat_data->fg; + if (!di->pdata) { + dev_err(di->dev, "no fg platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + + /* get battery specific platform data */ + di->bat = plat_data->battery; + if (!di->bat) { + dev_err(di->dev, "no battery platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + + di->fg_psy.name = "ab8500_fg"; + di->fg_psy.type = POWER_SUPPLY_TYPE_BATTERY; + di->fg_psy.properties = ab8500_fg_props; + di->fg_psy.num_properties = ARRAY_SIZE(ab8500_fg_props); + di->fg_psy.get_property = ab8500_fg_get_property; + di->fg_psy.supplied_to = di->pdata->supplied_to; + di->fg_psy.num_supplicants = di->pdata->num_supplicants; + di->fg_psy.external_power_changed = ab8500_fg_external_power_changed; + + di->bat_cap.max_mah_design = MILLI_TO_MICRO * + di->bat->bat_type[di->bat->batt_id].charge_full_design; + + di->bat_cap.max_mah = di->bat_cap.max_mah_design; + + di->vbat_nom = di->bat->bat_type[di->bat->batt_id].nominal_voltage; + + di->init_capacity = true; + + ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_INIT); + ab8500_fg_discharge_state_to(di, AB8500_FG_DISCHARGE_INIT); + + /* Create a work queue for running the FG algorithm */ + di->fg_wq = create_singlethread_workqueue("ab8500_fg_wq"); + if (di->fg_wq == NULL) { + dev_err(di->dev, "failed to create work queue\n"); + goto free_device_info; + } + + /* Init work for running the fg algorithm instantly */ + INIT_WORK(&di->fg_work, ab8500_fg_instant_work); + + /* Init work for getting the battery accumulated current */ + INIT_WORK(&di->fg_acc_cur_work, ab8500_fg_acc_cur_work); + + /* Init work for reinitialising the fg algorithm */ + INIT_DELAYED_WORK_DEFERRABLE(&di->fg_reinit_work, + ab8500_fg_reinit_work); + + /* Work delayed Queue to run the state machine */ + INIT_DELAYED_WORK_DEFERRABLE(&di->fg_periodic_work, + ab8500_fg_periodic_work); + + /* Work to check low battery condition */ + INIT_DELAYED_WORK_DEFERRABLE(&di->fg_low_bat_work, + ab8500_fg_low_bat_work); + + /* Init work for HW failure check */ + INIT_DELAYED_WORK_DEFERRABLE(&di->fg_check_hw_failure_work, + ab8500_fg_check_hw_failure_work); + + /* Initialize OVV, and other registers */ + ret = ab8500_fg_init_hw_registers(di); + if (ret) { + dev_err(di->dev, "failed to initialize registers\n"); + goto free_inst_curr_wq; + } + + /* Consider battery unknown until we're informed otherwise */ + di->flags.batt_unknown = true; + di->flags.batt_id_received = false; + + /* Register FG power supply class */ + ret = power_supply_register(di->dev, &di->fg_psy); + if (ret) { + dev_err(di->dev, "failed to register FG psy\n"); + goto free_inst_curr_wq; + } + + di->fg_samples = SEC_TO_SAMPLE(di->bat->fg_params->init_timer); + ab8500_fg_coulomb_counter(di, true); + + /* Initialize completion used to notify completion of inst current */ + init_completion(&di->ab8500_fg_complete); + + /* Register interrupts */ + for (i = 0; i < ARRAY_SIZE(ab8500_fg_irq); i++) { + irq = platform_get_irq_byname(pdev, ab8500_fg_irq[i].name); + ret = request_threaded_irq(irq, NULL, ab8500_fg_irq[i].isr, + IRQF_SHARED | IRQF_NO_SUSPEND, + ab8500_fg_irq[i].name, di); + + if (ret != 0) { + dev_err(di->dev, "failed to request %s IRQ %d: %d\n" + , ab8500_fg_irq[i].name, irq, ret); + goto free_irq; + } + dev_dbg(di->dev, "Requested %s IRQ %d: %d\n", + ab8500_fg_irq[i].name, irq, ret); + } + di->irq = platform_get_irq_byname(pdev, "CCEOC"); + disable_irq(di->irq); + + platform_set_drvdata(pdev, di); + + ret = ab8500_fg_sysfs_init(di); + if (ret) { + dev_err(di->dev, "failed to create sysfs entry\n"); + goto free_irq; + } + + /* Calibrate the fg first time */ + di->flags.calibrate = true; + di->calib_state = AB8500_FG_CALIB_INIT; + + /* Use room temp as default value until we get an update from driver. */ + di->bat_temp = 210; + + /* Run the FG algorithm */ + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); + + list_add_tail(&di->node, &ab8500_fg_list); + + return ret; + +free_irq: + power_supply_unregister(&di->fg_psy); + + /* We also have to free all successfully registered irqs */ + for (i = i - 1; i >= 0; i--) { + irq = platform_get_irq_byname(pdev, ab8500_fg_irq[i].name); + free_irq(irq, di); + } +free_inst_curr_wq: + destroy_workqueue(di->fg_wq); +free_device_info: + kfree(di); + + return ret; +} + +static struct platform_driver ab8500_fg_driver = { + .probe = ab8500_fg_probe, + .remove = __devexit_p(ab8500_fg_remove), + .suspend = ab8500_fg_suspend, + .resume = ab8500_fg_resume, + .driver = { + .name = "ab8500-fg", + .owner = THIS_MODULE, + }, +}; + +static int __init ab8500_fg_init(void) +{ + return platform_driver_register(&ab8500_fg_driver); +} + +static void __exit ab8500_fg_exit(void) +{ + platform_driver_unregister(&ab8500_fg_driver); +} + +subsys_initcall_sync(ab8500_fg_init); +module_exit(ab8500_fg_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Johan Palsson, Karl Komierowski"); +MODULE_ALIAS("platform:ab8500-fg"); +MODULE_DESCRIPTION("AB8500 Fuel Gauge driver"); diff --git a/drivers/power/abx500_chargalg.c b/drivers/power/abx500_chargalg.c new file mode 100644 index 00000000..804b88c7 --- /dev/null +++ b/drivers/power/abx500_chargalg.c @@ -0,0 +1,1921 @@ +/* + * Copyright (C) ST-Ericsson SA 2012 + * + * Charging algorithm driver for abx500 variants + * + * License Terms: GNU General Public License v2 + * Authors: + * Johan Palsson <johan.palsson@stericsson.com> + * Karl Komierowski <karl.komierowski@stericsson.com> + * Arun R Murthy <arun.murthy@stericsson.com> + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/completion.h> +#include <linux/workqueue.h> +#include <linux/kobject.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ux500_chargalg.h> +#include <linux/mfd/abx500/ab8500-bm.h> + +/* Watchdog kick interval */ +#define CHG_WD_INTERVAL (6 * HZ) + +/* End-of-charge criteria counter */ +#define EOC_COND_CNT 10 + +/* Recharge criteria counter */ +#define RCH_COND_CNT 3 + +#define to_abx500_chargalg_device_info(x) container_of((x), \ + struct abx500_chargalg, chargalg_psy); + +enum abx500_chargers { + NO_CHG, + AC_CHG, + USB_CHG, +}; + +struct abx500_chargalg_charger_info { + enum abx500_chargers conn_chg; + enum abx500_chargers prev_conn_chg; + enum abx500_chargers online_chg; + enum abx500_chargers prev_online_chg; + enum abx500_chargers charger_type; + bool usb_chg_ok; + bool ac_chg_ok; + int usb_volt; + int usb_curr; + int ac_volt; + int ac_curr; + int usb_vset; + int usb_iset; + int ac_vset; + int ac_iset; +}; + +struct abx500_chargalg_suspension_status { + bool suspended_change; + bool ac_suspended; + bool usb_suspended; +}; + +struct abx500_chargalg_battery_data { + int temp; + int volt; + int avg_curr; + int inst_curr; + int percent; +}; + +enum abx500_chargalg_states { + STATE_HANDHELD_INIT, + STATE_HANDHELD, + STATE_CHG_NOT_OK_INIT, + STATE_CHG_NOT_OK, + STATE_HW_TEMP_PROTECT_INIT, + STATE_HW_TEMP_PROTECT, + STATE_NORMAL_INIT, + STATE_NORMAL, + STATE_WAIT_FOR_RECHARGE_INIT, + STATE_WAIT_FOR_RECHARGE, + STATE_MAINTENANCE_A_INIT, + STATE_MAINTENANCE_A, + STATE_MAINTENANCE_B_INIT, + STATE_MAINTENANCE_B, + STATE_TEMP_UNDEROVER_INIT, + STATE_TEMP_UNDEROVER, + STATE_TEMP_LOWHIGH_INIT, + STATE_TEMP_LOWHIGH, + STATE_SUSPENDED_INIT, + STATE_SUSPENDED, + STATE_OVV_PROTECT_INIT, + STATE_OVV_PROTECT, + STATE_SAFETY_TIMER_EXPIRED_INIT, + STATE_SAFETY_TIMER_EXPIRED, + STATE_BATT_REMOVED_INIT, + STATE_BATT_REMOVED, + STATE_WD_EXPIRED_INIT, + STATE_WD_EXPIRED, +}; + +static const char *states[] = { + "HANDHELD_INIT", + "HANDHELD", + "CHG_NOT_OK_INIT", + "CHG_NOT_OK", + "HW_TEMP_PROTECT_INIT", + "HW_TEMP_PROTECT", + "NORMAL_INIT", + "NORMAL", + "WAIT_FOR_RECHARGE_INIT", + "WAIT_FOR_RECHARGE", + "MAINTENANCE_A_INIT", + "MAINTENANCE_A", + "MAINTENANCE_B_INIT", + "MAINTENANCE_B", + "TEMP_UNDEROVER_INIT", + "TEMP_UNDEROVER", + "TEMP_LOWHIGH_INIT", + "TEMP_LOWHIGH", + "SUSPENDED_INIT", + "SUSPENDED", + "OVV_PROTECT_INIT", + "OVV_PROTECT", + "SAFETY_TIMER_EXPIRED_INIT", + "SAFETY_TIMER_EXPIRED", + "BATT_REMOVED_INIT", + "BATT_REMOVED", + "WD_EXPIRED_INIT", + "WD_EXPIRED", +}; + +struct abx500_chargalg_events { + bool batt_unknown; + bool mainextchnotok; + bool batt_ovv; + bool batt_rem; + bool btemp_underover; + bool btemp_lowhigh; + bool main_thermal_prot; + bool usb_thermal_prot; + bool main_ovv; + bool vbus_ovv; + bool usbchargernotok; + bool safety_timer_expired; + bool maintenance_timer_expired; + bool ac_wd_expired; + bool usb_wd_expired; + bool ac_cv_active; + bool usb_cv_active; + bool vbus_collapsed; +}; + +/** + * struct abx500_charge_curr_maximization - Charger maximization parameters + * @original_iset: the non optimized/maximised charger current + * @current_iset: the charging current used at this moment + * @test_delta_i: the delta between the current we want to charge and the + current that is really going into the battery + * @condition_cnt: number of iterations needed before a new charger current + is set + * @max_current: maximum charger current + * @wait_cnt: to avoid too fast current step down in case of charger + * voltage collapse, we insert this delay between step + * down + * @level: tells in how many steps the charging current has been + increased + */ +struct abx500_charge_curr_maximization { + int original_iset; + int current_iset; + int test_delta_i; + int condition_cnt; + int max_current; + int wait_cnt; + u8 level; +}; + +enum maxim_ret { + MAXIM_RET_NOACTION, + MAXIM_RET_CHANGE, + MAXIM_RET_IBAT_TOO_HIGH, +}; + +/** + * struct abx500_chargalg - abx500 Charging algorithm device information + * @dev: pointer to the structure device + * @charge_status: battery operating status + * @eoc_cnt: counter used to determine end-of_charge + * @rch_cnt: counter used to determine start of recharge + * @maintenance_chg: indicate if maintenance charge is active + * @t_hyst_norm temperature hysteresis when the temperature has been + * over or under normal limits + * @t_hyst_lowhigh temperature hysteresis when the temperature has been + * over or under the high or low limits + * @charge_state: current state of the charging algorithm + * @ccm charging current maximization parameters + * @chg_info: information about connected charger types + * @batt_data: data of the battery + * @susp_status: current charger suspension status + * @pdata: pointer to the abx500_chargalg platform data + * @bat: pointer to the abx500_bm platform data + * @chargalg_psy: structure that holds the battery properties exposed by + * the charging algorithm + * @events: structure for information about events triggered + * @chargalg_wq: work queue for running the charging algorithm + * @chargalg_periodic_work: work to run the charging algorithm periodically + * @chargalg_wd_work: work to kick the charger watchdog periodically + * @chargalg_work: work to run the charging algorithm instantly + * @safety_timer: charging safety timer + * @maintenance_timer: maintenance charging timer + * @chargalg_kobject: structure of type kobject + */ +struct abx500_chargalg { + struct device *dev; + int charge_status; + int eoc_cnt; + int rch_cnt; + bool maintenance_chg; + int t_hyst_norm; + int t_hyst_lowhigh; + enum abx500_chargalg_states charge_state; + struct abx500_charge_curr_maximization ccm; + struct abx500_chargalg_charger_info chg_info; + struct abx500_chargalg_battery_data batt_data; + struct abx500_chargalg_suspension_status susp_status; + struct abx500_chargalg_platform_data *pdata; + struct abx500_bm_data *bat; + struct power_supply chargalg_psy; + struct ux500_charger *ac_chg; + struct ux500_charger *usb_chg; + struct abx500_chargalg_events events; + struct workqueue_struct *chargalg_wq; + struct delayed_work chargalg_periodic_work; + struct delayed_work chargalg_wd_work; + struct work_struct chargalg_work; + struct timer_list safety_timer; + struct timer_list maintenance_timer; + struct kobject chargalg_kobject; +}; + +/* Main battery properties */ +static enum power_supply_property abx500_chargalg_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, +}; + +/** + * abx500_chargalg_safety_timer_expired() - Expiration of the safety timer + * @data: pointer to the abx500_chargalg structure + * + * This function gets called when the safety timer for the charger + * expires + */ +static void abx500_chargalg_safety_timer_expired(unsigned long data) +{ + struct abx500_chargalg *di = (struct abx500_chargalg *) data; + dev_err(di->dev, "Safety timer expired\n"); + di->events.safety_timer_expired = true; + + /* Trigger execution of the algorithm instantly */ + queue_work(di->chargalg_wq, &di->chargalg_work); +} + +/** + * abx500_chargalg_maintenance_timer_expired() - Expiration of + * the maintenance timer + * @i: pointer to the abx500_chargalg structure + * + * This function gets called when the maintenence timer + * expires + */ +static void abx500_chargalg_maintenance_timer_expired(unsigned long data) +{ + + struct abx500_chargalg *di = (struct abx500_chargalg *) data; + dev_dbg(di->dev, "Maintenance timer expired\n"); + di->events.maintenance_timer_expired = true; + + /* Trigger execution of the algorithm instantly */ + queue_work(di->chargalg_wq, &di->chargalg_work); +} + +/** + * abx500_chargalg_state_to() - Change charge state + * @di: pointer to the abx500_chargalg structure + * + * This function gets called when a charge state change should occur + */ +static void abx500_chargalg_state_to(struct abx500_chargalg *di, + enum abx500_chargalg_states state) +{ + dev_dbg(di->dev, + "State changed: %s (From state: [%d] %s =to=> [%d] %s )\n", + di->charge_state == state ? "NO" : "YES", + di->charge_state, + states[di->charge_state], + state, + states[state]); + + di->charge_state = state; +} + +/** + * abx500_chargalg_check_charger_connection() - Check charger connection change + * @di: pointer to the abx500_chargalg structure + * + * This function will check if there is a change in the charger connection + * and change charge state accordingly. AC has precedence over USB. + */ +static int abx500_chargalg_check_charger_connection(struct abx500_chargalg *di) +{ + if (di->chg_info.conn_chg != di->chg_info.prev_conn_chg || + di->susp_status.suspended_change) { + /* + * Charger state changed or suspension + * has changed since last update + */ + if ((di->chg_info.conn_chg & AC_CHG) && + !di->susp_status.ac_suspended) { + dev_dbg(di->dev, "Charging source is AC\n"); + if (di->chg_info.charger_type != AC_CHG) { + di->chg_info.charger_type = AC_CHG; + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + } + } else if ((di->chg_info.conn_chg & USB_CHG) && + !di->susp_status.usb_suspended) { + dev_dbg(di->dev, "Charging source is USB\n"); + di->chg_info.charger_type = USB_CHG; + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + } else if (di->chg_info.conn_chg && + (di->susp_status.ac_suspended || + di->susp_status.usb_suspended)) { + dev_dbg(di->dev, "Charging is suspended\n"); + di->chg_info.charger_type = NO_CHG; + abx500_chargalg_state_to(di, STATE_SUSPENDED_INIT); + } else { + dev_dbg(di->dev, "Charging source is OFF\n"); + di->chg_info.charger_type = NO_CHG; + abx500_chargalg_state_to(di, STATE_HANDHELD_INIT); + } + di->chg_info.prev_conn_chg = di->chg_info.conn_chg; + di->susp_status.suspended_change = false; + } + return di->chg_info.conn_chg; +} + +/** + * abx500_chargalg_start_safety_timer() - Start charging safety timer + * @di: pointer to the abx500_chargalg structure + * + * The safety timer is used to avoid overcharging of old or bad batteries. + * There are different timers for AC and USB + */ +static void abx500_chargalg_start_safety_timer(struct abx500_chargalg *di) +{ + unsigned long timer_expiration = 0; + + switch (di->chg_info.charger_type) { + case AC_CHG: + timer_expiration = + round_jiffies(jiffies + + (di->bat->main_safety_tmr_h * 3600 * HZ)); + break; + + case USB_CHG: + timer_expiration = + round_jiffies(jiffies + + (di->bat->usb_safety_tmr_h * 3600 * HZ)); + break; + + default: + dev_err(di->dev, "Unknown charger to charge from\n"); + break; + } + + di->events.safety_timer_expired = false; + di->safety_timer.expires = timer_expiration; + if (!timer_pending(&di->safety_timer)) + add_timer(&di->safety_timer); + else + mod_timer(&di->safety_timer, timer_expiration); +} + +/** + * abx500_chargalg_stop_safety_timer() - Stop charging safety timer + * @di: pointer to the abx500_chargalg structure + * + * The safety timer is stopped whenever the NORMAL state is exited + */ +static void abx500_chargalg_stop_safety_timer(struct abx500_chargalg *di) +{ + di->events.safety_timer_expired = false; + del_timer(&di->safety_timer); +} + +/** + * abx500_chargalg_start_maintenance_timer() - Start charging maintenance timer + * @di: pointer to the abx500_chargalg structure + * @duration: duration of ther maintenance timer in hours + * + * The maintenance timer is used to maintain the charge in the battery once + * the battery is considered full. These timers are chosen to match the + * discharge curve of the battery + */ +static void abx500_chargalg_start_maintenance_timer(struct abx500_chargalg *di, + int duration) +{ + unsigned long timer_expiration; + + /* Convert from hours to jiffies */ + timer_expiration = round_jiffies(jiffies + (duration * 3600 * HZ)); + + di->events.maintenance_timer_expired = false; + di->maintenance_timer.expires = timer_expiration; + if (!timer_pending(&di->maintenance_timer)) + add_timer(&di->maintenance_timer); + else + mod_timer(&di->maintenance_timer, timer_expiration); +} + +/** + * abx500_chargalg_stop_maintenance_timer() - Stop maintenance timer + * @di: pointer to the abx500_chargalg structure + * + * The maintenance timer is stopped whenever maintenance ends or when another + * state is entered + */ +static void abx500_chargalg_stop_maintenance_timer(struct abx500_chargalg *di) +{ + di->events.maintenance_timer_expired = false; + del_timer(&di->maintenance_timer); +} + +/** + * abx500_chargalg_kick_watchdog() - Kick charger watchdog + * @di: pointer to the abx500_chargalg structure + * + * The charger watchdog have to be kicked periodically whenever the charger is + * on, else the ABB will reset the system + */ +static int abx500_chargalg_kick_watchdog(struct abx500_chargalg *di) +{ + /* Check if charger exists and kick watchdog if charging */ + if (di->ac_chg && di->ac_chg->ops.kick_wd && + di->chg_info.online_chg & AC_CHG) + return di->ac_chg->ops.kick_wd(di->ac_chg); + else if (di->usb_chg && di->usb_chg->ops.kick_wd && + di->chg_info.online_chg & USB_CHG) + return di->usb_chg->ops.kick_wd(di->usb_chg); + + return -ENXIO; +} + +/** + * abx500_chargalg_ac_en() - Turn on/off the AC charger + * @di: pointer to the abx500_chargalg structure + * @enable: charger on/off + * @vset: requested charger output voltage + * @iset: requested charger output current + * + * The AC charger will be turned on/off with the requested charge voltage and + * current + */ +static int abx500_chargalg_ac_en(struct abx500_chargalg *di, int enable, + int vset, int iset) +{ + if (!di->ac_chg || !di->ac_chg->ops.enable) + return -ENXIO; + + /* Select maximum of what both the charger and the battery supports */ + if (di->ac_chg->max_out_volt) + vset = min(vset, di->ac_chg->max_out_volt); + if (di->ac_chg->max_out_curr) + iset = min(iset, di->ac_chg->max_out_curr); + + di->chg_info.ac_iset = iset; + di->chg_info.ac_vset = vset; + + return di->ac_chg->ops.enable(di->ac_chg, enable, vset, iset); +} + +/** + * abx500_chargalg_usb_en() - Turn on/off the USB charger + * @di: pointer to the abx500_chargalg structure + * @enable: charger on/off + * @vset: requested charger output voltage + * @iset: requested charger output current + * + * The USB charger will be turned on/off with the requested charge voltage and + * current + */ +static int abx500_chargalg_usb_en(struct abx500_chargalg *di, int enable, + int vset, int iset) +{ + if (!di->usb_chg || !di->usb_chg->ops.enable) + return -ENXIO; + + /* Select maximum of what both the charger and the battery supports */ + if (di->usb_chg->max_out_volt) + vset = min(vset, di->usb_chg->max_out_volt); + if (di->usb_chg->max_out_curr) + iset = min(iset, di->usb_chg->max_out_curr); + + di->chg_info.usb_iset = iset; + di->chg_info.usb_vset = vset; + + return di->usb_chg->ops.enable(di->usb_chg, enable, vset, iset); +} + +/** + * abx500_chargalg_update_chg_curr() - Update charger current + * @di: pointer to the abx500_chargalg structure + * @iset: requested charger output current + * + * The charger output current will be updated for the charger + * that is currently in use + */ +static int abx500_chargalg_update_chg_curr(struct abx500_chargalg *di, + int iset) +{ + /* Check if charger exists and update current if charging */ + if (di->ac_chg && di->ac_chg->ops.update_curr && + di->chg_info.charger_type & AC_CHG) { + /* + * Select maximum of what both the charger + * and the battery supports + */ + if (di->ac_chg->max_out_curr) + iset = min(iset, di->ac_chg->max_out_curr); + + di->chg_info.ac_iset = iset; + + return di->ac_chg->ops.update_curr(di->ac_chg, iset); + } else if (di->usb_chg && di->usb_chg->ops.update_curr && + di->chg_info.charger_type & USB_CHG) { + /* + * Select maximum of what both the charger + * and the battery supports + */ + if (di->usb_chg->max_out_curr) + iset = min(iset, di->usb_chg->max_out_curr); + + di->chg_info.usb_iset = iset; + + return di->usb_chg->ops.update_curr(di->usb_chg, iset); + } + + return -ENXIO; +} + +/** + * abx500_chargalg_stop_charging() - Stop charging + * @di: pointer to the abx500_chargalg structure + * + * This function is called from any state where charging should be stopped. + * All charging is disabled and all status parameters and timers are changed + * accordingly + */ +static void abx500_chargalg_stop_charging(struct abx500_chargalg *di) +{ + abx500_chargalg_ac_en(di, false, 0, 0); + abx500_chargalg_usb_en(di, false, 0, 0); + abx500_chargalg_stop_safety_timer(di); + abx500_chargalg_stop_maintenance_timer(di); + di->charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + di->maintenance_chg = false; + cancel_delayed_work(&di->chargalg_wd_work); + power_supply_changed(&di->chargalg_psy); +} + +/** + * abx500_chargalg_hold_charging() - Pauses charging + * @di: pointer to the abx500_chargalg structure + * + * This function is called in the case where maintenance charging has been + * disabled and instead a battery voltage mode is entered to check when the + * battery voltage has reached a certain recharge voltage + */ +static void abx500_chargalg_hold_charging(struct abx500_chargalg *di) +{ + abx500_chargalg_ac_en(di, false, 0, 0); + abx500_chargalg_usb_en(di, false, 0, 0); + abx500_chargalg_stop_safety_timer(di); + abx500_chargalg_stop_maintenance_timer(di); + di->charge_status = POWER_SUPPLY_STATUS_CHARGING; + di->maintenance_chg = false; + cancel_delayed_work(&di->chargalg_wd_work); + power_supply_changed(&di->chargalg_psy); +} + +/** + * abx500_chargalg_start_charging() - Start the charger + * @di: pointer to the abx500_chargalg structure + * @vset: requested charger output voltage + * @iset: requested charger output current + * + * A charger will be enabled depending on the requested charger type that was + * detected previously. + */ +static void abx500_chargalg_start_charging(struct abx500_chargalg *di, + int vset, int iset) +{ + switch (di->chg_info.charger_type) { + case AC_CHG: + dev_dbg(di->dev, + "AC parameters: Vset %d, Ich %d\n", vset, iset); + abx500_chargalg_usb_en(di, false, 0, 0); + abx500_chargalg_ac_en(di, true, vset, iset); + break; + + case USB_CHG: + dev_dbg(di->dev, + "USB parameters: Vset %d, Ich %d\n", vset, iset); + abx500_chargalg_ac_en(di, false, 0, 0); + abx500_chargalg_usb_en(di, true, vset, iset); + break; + + default: + dev_err(di->dev, "Unknown charger to charge from\n"); + break; + } +} + +/** + * abx500_chargalg_check_temp() - Check battery temperature ranges + * @di: pointer to the abx500_chargalg structure + * + * The battery temperature is checked against the predefined limits and the + * charge state is changed accordingly + */ +static void abx500_chargalg_check_temp(struct abx500_chargalg *di) +{ + if (di->batt_data.temp > (di->bat->temp_low + di->t_hyst_norm) && + di->batt_data.temp < (di->bat->temp_high - di->t_hyst_norm)) { + /* Temp OK! */ + di->events.btemp_underover = false; + di->events.btemp_lowhigh = false; + di->t_hyst_norm = 0; + di->t_hyst_lowhigh = 0; + } else { + if (((di->batt_data.temp >= di->bat->temp_high) && + (di->batt_data.temp < + (di->bat->temp_over - di->t_hyst_lowhigh))) || + ((di->batt_data.temp > + (di->bat->temp_under + di->t_hyst_lowhigh)) && + (di->batt_data.temp <= di->bat->temp_low))) { + /* TEMP minor!!!!! */ + di->events.btemp_underover = false; + di->events.btemp_lowhigh = true; + di->t_hyst_norm = di->bat->temp_hysteresis; + di->t_hyst_lowhigh = 0; + } else if (di->batt_data.temp <= di->bat->temp_under || + di->batt_data.temp >= di->bat->temp_over) { + /* TEMP major!!!!! */ + di->events.btemp_underover = true; + di->events.btemp_lowhigh = false; + di->t_hyst_norm = 0; + di->t_hyst_lowhigh = di->bat->temp_hysteresis; + } else { + /* Within hysteresis */ + dev_dbg(di->dev, "Within hysteresis limit temp: %d " + "hyst_lowhigh %d, hyst normal %d\n", + di->batt_data.temp, di->t_hyst_lowhigh, + di->t_hyst_norm); + } + } +} + +/** + * abx500_chargalg_check_charger_voltage() - Check charger voltage + * @di: pointer to the abx500_chargalg structure + * + * Charger voltage is checked against maximum limit + */ +static void abx500_chargalg_check_charger_voltage(struct abx500_chargalg *di) +{ + if (di->chg_info.usb_volt > di->bat->chg_params->usb_volt_max) + di->chg_info.usb_chg_ok = false; + else + di->chg_info.usb_chg_ok = true; + + if (di->chg_info.ac_volt > di->bat->chg_params->ac_volt_max) + di->chg_info.ac_chg_ok = false; + else + di->chg_info.ac_chg_ok = true; + +} + +/** + * abx500_chargalg_end_of_charge() - Check if end-of-charge criteria is fulfilled + * @di: pointer to the abx500_chargalg structure + * + * End-of-charge criteria is fulfilled when the battery voltage is above a + * certain limit and the battery current is below a certain limit for a + * predefined number of consecutive seconds. If true, the battery is full + */ +static void abx500_chargalg_end_of_charge(struct abx500_chargalg *di) +{ + if (di->charge_status == POWER_SUPPLY_STATUS_CHARGING && + di->charge_state == STATE_NORMAL && + !di->maintenance_chg && (di->batt_data.volt >= + di->bat->bat_type[di->bat->batt_id].termination_vol || + di->events.usb_cv_active || di->events.ac_cv_active) && + di->batt_data.avg_curr < + di->bat->bat_type[di->bat->batt_id].termination_curr && + di->batt_data.avg_curr > 0) { + if (++di->eoc_cnt >= EOC_COND_CNT) { + di->eoc_cnt = 0; + di->charge_status = POWER_SUPPLY_STATUS_FULL; + di->maintenance_chg = true; + dev_dbg(di->dev, "EOC reached!\n"); + power_supply_changed(&di->chargalg_psy); + } else { + dev_dbg(di->dev, + " EOC limit reached for the %d" + " time, out of %d before EOC\n", + di->eoc_cnt, + EOC_COND_CNT); + } + } else { + di->eoc_cnt = 0; + } +} + +static void init_maxim_chg_curr(struct abx500_chargalg *di) +{ + di->ccm.original_iset = + di->bat->bat_type[di->bat->batt_id].normal_cur_lvl; + di->ccm.current_iset = + di->bat->bat_type[di->bat->batt_id].normal_cur_lvl; + di->ccm.test_delta_i = di->bat->maxi->charger_curr_step; + di->ccm.max_current = di->bat->maxi->chg_curr; + di->ccm.condition_cnt = di->bat->maxi->wait_cycles; + di->ccm.level = 0; +} + +/** + * abx500_chargalg_chg_curr_maxim - increases the charger current to + * compensate for the system load + * @di pointer to the abx500_chargalg structure + * + * This maximization function is used to raise the charger current to get the + * battery current as close to the optimal value as possible. The battery + * current during charging is affected by the system load + */ +static enum maxim_ret abx500_chargalg_chg_curr_maxim(struct abx500_chargalg *di) +{ + int delta_i; + + if (!di->bat->maxi->ena_maxi) + return MAXIM_RET_NOACTION; + + delta_i = di->ccm.original_iset - di->batt_data.inst_curr; + + if (di->events.vbus_collapsed) { + dev_dbg(di->dev, "Charger voltage has collapsed %d\n", + di->ccm.wait_cnt); + if (di->ccm.wait_cnt == 0) { + dev_dbg(di->dev, "lowering current\n"); + di->ccm.wait_cnt++; + di->ccm.condition_cnt = di->bat->maxi->wait_cycles; + di->ccm.max_current = + di->ccm.current_iset - di->ccm.test_delta_i; + di->ccm.current_iset = di->ccm.max_current; + di->ccm.level--; + return MAXIM_RET_CHANGE; + } else { + dev_dbg(di->dev, "waiting\n"); + /* Let's go in here twice before lowering curr again */ + di->ccm.wait_cnt = (di->ccm.wait_cnt + 1) % 3; + return MAXIM_RET_NOACTION; + } + } + + di->ccm.wait_cnt = 0; + + if ((di->batt_data.inst_curr > di->ccm.original_iset)) { + dev_dbg(di->dev, " Maximization Ibat (%dmA) too high" + " (limit %dmA) (current iset: %dmA)!\n", + di->batt_data.inst_curr, di->ccm.original_iset, + di->ccm.current_iset); + + if (di->ccm.current_iset == di->ccm.original_iset) + return MAXIM_RET_NOACTION; + + di->ccm.condition_cnt = di->bat->maxi->wait_cycles; + di->ccm.current_iset = di->ccm.original_iset; + di->ccm.level = 0; + + return MAXIM_RET_IBAT_TOO_HIGH; + } + + if (delta_i > di->ccm.test_delta_i && + (di->ccm.current_iset + di->ccm.test_delta_i) < + di->ccm.max_current) { + if (di->ccm.condition_cnt-- == 0) { + /* Increse the iset with cco.test_delta_i */ + di->ccm.condition_cnt = di->bat->maxi->wait_cycles; + di->ccm.current_iset += di->ccm.test_delta_i; + di->ccm.level++; + dev_dbg(di->dev, " Maximization needed, increase" + " with %d mA to %dmA (Optimal ibat: %d)" + " Level %d\n", + di->ccm.test_delta_i, + di->ccm.current_iset, + di->ccm.original_iset, + di->ccm.level); + return MAXIM_RET_CHANGE; + } else { + return MAXIM_RET_NOACTION; + } + } else { + di->ccm.condition_cnt = di->bat->maxi->wait_cycles; + return MAXIM_RET_NOACTION; + } +} + +static void handle_maxim_chg_curr(struct abx500_chargalg *di) +{ + enum maxim_ret ret; + int result; + + ret = abx500_chargalg_chg_curr_maxim(di); + switch (ret) { + case MAXIM_RET_CHANGE: + result = abx500_chargalg_update_chg_curr(di, + di->ccm.current_iset); + if (result) + dev_err(di->dev, "failed to set chg curr\n"); + break; + case MAXIM_RET_IBAT_TOO_HIGH: + result = abx500_chargalg_update_chg_curr(di, + di->bat->bat_type[di->bat->batt_id].normal_cur_lvl); + if (result) + dev_err(di->dev, "failed to set chg curr\n"); + break; + + case MAXIM_RET_NOACTION: + default: + /* Do nothing..*/ + break; + } +} + +static int abx500_chargalg_get_ext_psy_data(struct device *dev, void *data) +{ + struct power_supply *psy; + struct power_supply *ext; + struct abx500_chargalg *di; + union power_supply_propval ret; + int i, j; + bool psy_found = false; + + psy = (struct power_supply *)data; + ext = dev_get_drvdata(dev); + di = to_abx500_chargalg_device_info(psy); + /* For all psy where the driver name appears in any supplied_to */ + for (i = 0; i < ext->num_supplicants; i++) { + if (!strcmp(ext->supplied_to[i], psy->name)) + psy_found = true; + } + if (!psy_found) + return 0; + + /* Go through all properties for the psy */ + for (j = 0; j < ext->num_properties; j++) { + enum power_supply_property prop; + prop = ext->properties[j]; + + /* Initialize chargers if not already done */ + if (!di->ac_chg && + ext->type == POWER_SUPPLY_TYPE_MAINS) + di->ac_chg = psy_to_ux500_charger(ext); + else if (!di->usb_chg && + ext->type == POWER_SUPPLY_TYPE_USB) + di->usb_chg = psy_to_ux500_charger(ext); + + if (ext->get_property(ext, prop, &ret)) + continue; + switch (prop) { + case POWER_SUPPLY_PROP_PRESENT: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + /* Battery present */ + if (ret.intval) + di->events.batt_rem = false; + /* Battery removed */ + else + di->events.batt_rem = true; + break; + case POWER_SUPPLY_TYPE_MAINS: + /* AC disconnected */ + if (!ret.intval && + (di->chg_info.conn_chg & AC_CHG)) { + di->chg_info.prev_conn_chg = + di->chg_info.conn_chg; + di->chg_info.conn_chg &= ~AC_CHG; + } + /* AC connected */ + else if (ret.intval && + !(di->chg_info.conn_chg & AC_CHG)) { + di->chg_info.prev_conn_chg = + di->chg_info.conn_chg; + di->chg_info.conn_chg |= AC_CHG; + } + break; + case POWER_SUPPLY_TYPE_USB: + /* USB disconnected */ + if (!ret.intval && + (di->chg_info.conn_chg & USB_CHG)) { + di->chg_info.prev_conn_chg = + di->chg_info.conn_chg; + di->chg_info.conn_chg &= ~USB_CHG; + } + /* USB connected */ + else if (ret.intval && + !(di->chg_info.conn_chg & USB_CHG)) { + di->chg_info.prev_conn_chg = + di->chg_info.conn_chg; + di->chg_info.conn_chg |= USB_CHG; + } + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_ONLINE: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + break; + case POWER_SUPPLY_TYPE_MAINS: + /* AC offline */ + if (!ret.intval && + (di->chg_info.online_chg & AC_CHG)) { + di->chg_info.prev_online_chg = + di->chg_info.online_chg; + di->chg_info.online_chg &= ~AC_CHG; + } + /* AC online */ + else if (ret.intval && + !(di->chg_info.online_chg & AC_CHG)) { + di->chg_info.prev_online_chg = + di->chg_info.online_chg; + di->chg_info.online_chg |= AC_CHG; + queue_delayed_work(di->chargalg_wq, + &di->chargalg_wd_work, 0); + } + break; + case POWER_SUPPLY_TYPE_USB: + /* USB offline */ + if (!ret.intval && + (di->chg_info.online_chg & USB_CHG)) { + di->chg_info.prev_online_chg = + di->chg_info.online_chg; + di->chg_info.online_chg &= ~USB_CHG; + } + /* USB online */ + else if (ret.intval && + !(di->chg_info.online_chg & USB_CHG)) { + di->chg_info.prev_online_chg = + di->chg_info.online_chg; + di->chg_info.online_chg |= USB_CHG; + queue_delayed_work(di->chargalg_wq, + &di->chargalg_wd_work, 0); + } + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_HEALTH: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + break; + case POWER_SUPPLY_TYPE_MAINS: + switch (ret.intval) { + case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE: + di->events.mainextchnotok = true; + di->events.main_thermal_prot = false; + di->events.main_ovv = false; + di->events.ac_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_DEAD: + di->events.ac_wd_expired = true; + di->events.mainextchnotok = false; + di->events.main_ovv = false; + di->events.main_thermal_prot = false; + break; + case POWER_SUPPLY_HEALTH_COLD: + case POWER_SUPPLY_HEALTH_OVERHEAT: + di->events.main_thermal_prot = true; + di->events.mainextchnotok = false; + di->events.main_ovv = false; + di->events.ac_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_OVERVOLTAGE: + di->events.main_ovv = true; + di->events.mainextchnotok = false; + di->events.main_thermal_prot = false; + di->events.ac_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_GOOD: + di->events.main_thermal_prot = false; + di->events.mainextchnotok = false; + di->events.main_ovv = false; + di->events.ac_wd_expired = false; + break; + default: + break; + } + break; + + case POWER_SUPPLY_TYPE_USB: + switch (ret.intval) { + case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE: + di->events.usbchargernotok = true; + di->events.usb_thermal_prot = false; + di->events.vbus_ovv = false; + di->events.usb_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_DEAD: + di->events.usb_wd_expired = true; + di->events.usbchargernotok = false; + di->events.usb_thermal_prot = false; + di->events.vbus_ovv = false; + break; + case POWER_SUPPLY_HEALTH_COLD: + case POWER_SUPPLY_HEALTH_OVERHEAT: + di->events.usb_thermal_prot = true; + di->events.usbchargernotok = false; + di->events.vbus_ovv = false; + di->events.usb_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_OVERVOLTAGE: + di->events.vbus_ovv = true; + di->events.usbchargernotok = false; + di->events.usb_thermal_prot = false; + di->events.usb_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_GOOD: + di->events.usbchargernotok = false; + di->events.usb_thermal_prot = false; + di->events.vbus_ovv = false; + di->events.usb_wd_expired = false; + break; + default: + break; + } + default: + break; + } + break; + + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + di->batt_data.volt = ret.intval / 1000; + break; + case POWER_SUPPLY_TYPE_MAINS: + di->chg_info.ac_volt = ret.intval / 1000; + break; + case POWER_SUPPLY_TYPE_USB: + di->chg_info.usb_volt = ret.intval / 1000; + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + switch (ext->type) { + case POWER_SUPPLY_TYPE_MAINS: + /* AVG is used to indicate when we are + * in CV mode */ + if (ret.intval) + di->events.ac_cv_active = true; + else + di->events.ac_cv_active = false; + + break; + case POWER_SUPPLY_TYPE_USB: + /* AVG is used to indicate when we are + * in CV mode */ + if (ret.intval) + di->events.usb_cv_active = true; + else + di->events.usb_cv_active = false; + + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_TECHNOLOGY: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + if (ret.intval) + di->events.batt_unknown = false; + else + di->events.batt_unknown = true; + + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_TEMP: + di->batt_data.temp = ret.intval / 10; + break; + + case POWER_SUPPLY_PROP_CURRENT_NOW: + switch (ext->type) { + case POWER_SUPPLY_TYPE_MAINS: + di->chg_info.ac_curr = + ret.intval / 1000; + break; + case POWER_SUPPLY_TYPE_USB: + di->chg_info.usb_curr = + ret.intval / 1000; + break; + case POWER_SUPPLY_TYPE_BATTERY: + di->batt_data.inst_curr = ret.intval / 1000; + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_CURRENT_AVG: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + di->batt_data.avg_curr = ret.intval / 1000; + break; + case POWER_SUPPLY_TYPE_USB: + if (ret.intval) + di->events.vbus_collapsed = true; + else + di->events.vbus_collapsed = false; + break; + default: + break; + } + break; + case POWER_SUPPLY_PROP_CAPACITY: + di->batt_data.percent = ret.intval; + break; + default: + break; + } + } + return 0; +} + +/** + * abx500_chargalg_external_power_changed() - callback for power supply changes + * @psy: pointer to the structure power_supply + * + * This function is the entry point of the pointer external_power_changed + * of the structure power_supply. + * This function gets executed when there is a change in any external power + * supply that this driver needs to be notified of. + */ +static void abx500_chargalg_external_power_changed(struct power_supply *psy) +{ + struct abx500_chargalg *di = to_abx500_chargalg_device_info(psy); + + /* + * Trigger execution of the algorithm instantly and read + * all power_supply properties there instead + */ + queue_work(di->chargalg_wq, &di->chargalg_work); +} + +/** + * abx500_chargalg_algorithm() - Main function for the algorithm + * @di: pointer to the abx500_chargalg structure + * + * This is the main control function for the charging algorithm. + * It is called periodically or when something happens that will + * trigger a state change + */ +static void abx500_chargalg_algorithm(struct abx500_chargalg *di) +{ + int charger_status; + + /* Collect data from all power_supply class devices */ + class_for_each_device(power_supply_class, NULL, + &di->chargalg_psy, abx500_chargalg_get_ext_psy_data); + + abx500_chargalg_end_of_charge(di); + abx500_chargalg_check_temp(di); + abx500_chargalg_check_charger_voltage(di); + + charger_status = abx500_chargalg_check_charger_connection(di); + /* + * First check if we have a charger connected. + * Also we don't allow charging of unknown batteries if configured + * this way + */ + if (!charger_status || + (di->events.batt_unknown && !di->bat->chg_unknown_bat)) { + if (di->charge_state != STATE_HANDHELD) { + di->events.safety_timer_expired = false; + abx500_chargalg_state_to(di, STATE_HANDHELD_INIT); + } + } + + /* If suspended, we should not continue checking the flags */ + else if (di->charge_state == STATE_SUSPENDED_INIT || + di->charge_state == STATE_SUSPENDED) { + /* We don't do anything here, just don,t continue */ + } + + /* Safety timer expiration */ + else if (di->events.safety_timer_expired) { + if (di->charge_state != STATE_SAFETY_TIMER_EXPIRED) + abx500_chargalg_state_to(di, + STATE_SAFETY_TIMER_EXPIRED_INIT); + } + /* + * Check if any interrupts has occured + * that will prevent us from charging + */ + + /* Battery removed */ + else if (di->events.batt_rem) { + if (di->charge_state != STATE_BATT_REMOVED) + abx500_chargalg_state_to(di, STATE_BATT_REMOVED_INIT); + } + /* Main or USB charger not ok. */ + else if (di->events.mainextchnotok || di->events.usbchargernotok) { + /* + * If vbus_collapsed is set, we have to lower the charger + * current, which is done in the normal state below + */ + if (di->charge_state != STATE_CHG_NOT_OK && + !di->events.vbus_collapsed) + abx500_chargalg_state_to(di, STATE_CHG_NOT_OK_INIT); + } + /* VBUS, Main or VBAT OVV. */ + else if (di->events.vbus_ovv || + di->events.main_ovv || + di->events.batt_ovv || + !di->chg_info.usb_chg_ok || + !di->chg_info.ac_chg_ok) { + if (di->charge_state != STATE_OVV_PROTECT) + abx500_chargalg_state_to(di, STATE_OVV_PROTECT_INIT); + } + /* USB Thermal, stop charging */ + else if (di->events.main_thermal_prot || + di->events.usb_thermal_prot) { + if (di->charge_state != STATE_HW_TEMP_PROTECT) + abx500_chargalg_state_to(di, + STATE_HW_TEMP_PROTECT_INIT); + } + /* Battery temp over/under */ + else if (di->events.btemp_underover) { + if (di->charge_state != STATE_TEMP_UNDEROVER) + abx500_chargalg_state_to(di, + STATE_TEMP_UNDEROVER_INIT); + } + /* Watchdog expired */ + else if (di->events.ac_wd_expired || + di->events.usb_wd_expired) { + if (di->charge_state != STATE_WD_EXPIRED) + abx500_chargalg_state_to(di, STATE_WD_EXPIRED_INIT); + } + /* Battery temp high/low */ + else if (di->events.btemp_lowhigh) { + if (di->charge_state != STATE_TEMP_LOWHIGH) + abx500_chargalg_state_to(di, STATE_TEMP_LOWHIGH_INIT); + } + + dev_dbg(di->dev, + "[CHARGALG] Vb %d Ib_avg %d Ib_inst %d Tb %d Cap %d Maint %d " + "State %s Active_chg %d Chg_status %d AC %d USB %d " + "AC_online %d USB_online %d AC_CV %d USB_CV %d AC_I %d " + "USB_I %d AC_Vset %d AC_Iset %d USB_Vset %d USB_Iset %d\n", + di->batt_data.volt, + di->batt_data.avg_curr, + di->batt_data.inst_curr, + di->batt_data.temp, + di->batt_data.percent, + di->maintenance_chg, + states[di->charge_state], + di->chg_info.charger_type, + di->charge_status, + di->chg_info.conn_chg & AC_CHG, + di->chg_info.conn_chg & USB_CHG, + di->chg_info.online_chg & AC_CHG, + di->chg_info.online_chg & USB_CHG, + di->events.ac_cv_active, + di->events.usb_cv_active, + di->chg_info.ac_curr, + di->chg_info.usb_curr, + di->chg_info.ac_vset, + di->chg_info.ac_iset, + di->chg_info.usb_vset, + di->chg_info.usb_iset); + + switch (di->charge_state) { + case STATE_HANDHELD_INIT: + abx500_chargalg_stop_charging(di); + di->charge_status = POWER_SUPPLY_STATUS_DISCHARGING; + abx500_chargalg_state_to(di, STATE_HANDHELD); + /* Intentional fallthrough */ + + case STATE_HANDHELD: + break; + + case STATE_SUSPENDED_INIT: + if (di->susp_status.ac_suspended) + abx500_chargalg_ac_en(di, false, 0, 0); + if (di->susp_status.usb_suspended) + abx500_chargalg_usb_en(di, false, 0, 0); + abx500_chargalg_stop_safety_timer(di); + abx500_chargalg_stop_maintenance_timer(di); + di->charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + di->maintenance_chg = false; + abx500_chargalg_state_to(di, STATE_SUSPENDED); + power_supply_changed(&di->chargalg_psy); + /* Intentional fallthrough */ + + case STATE_SUSPENDED: + /* CHARGING is suspended */ + break; + + case STATE_BATT_REMOVED_INIT: + abx500_chargalg_stop_charging(di); + abx500_chargalg_state_to(di, STATE_BATT_REMOVED); + /* Intentional fallthrough */ + + case STATE_BATT_REMOVED: + if (!di->events.batt_rem) + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_HW_TEMP_PROTECT_INIT: + abx500_chargalg_stop_charging(di); + abx500_chargalg_state_to(di, STATE_HW_TEMP_PROTECT); + /* Intentional fallthrough */ + + case STATE_HW_TEMP_PROTECT: + if (!di->events.main_thermal_prot && + !di->events.usb_thermal_prot) + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_OVV_PROTECT_INIT: + abx500_chargalg_stop_charging(di); + abx500_chargalg_state_to(di, STATE_OVV_PROTECT); + /* Intentional fallthrough */ + + case STATE_OVV_PROTECT: + if (!di->events.vbus_ovv && + !di->events.main_ovv && + !di->events.batt_ovv && + di->chg_info.usb_chg_ok && + di->chg_info.ac_chg_ok) + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_CHG_NOT_OK_INIT: + abx500_chargalg_stop_charging(di); + abx500_chargalg_state_to(di, STATE_CHG_NOT_OK); + /* Intentional fallthrough */ + + case STATE_CHG_NOT_OK: + if (!di->events.mainextchnotok && + !di->events.usbchargernotok) + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_SAFETY_TIMER_EXPIRED_INIT: + abx500_chargalg_stop_charging(di); + abx500_chargalg_state_to(di, STATE_SAFETY_TIMER_EXPIRED); + /* Intentional fallthrough */ + + case STATE_SAFETY_TIMER_EXPIRED: + /* We exit this state when charger is removed */ + break; + + case STATE_NORMAL_INIT: + abx500_chargalg_start_charging(di, + di->bat->bat_type[di->bat->batt_id].normal_vol_lvl, + di->bat->bat_type[di->bat->batt_id].normal_cur_lvl); + abx500_chargalg_state_to(di, STATE_NORMAL); + abx500_chargalg_start_safety_timer(di); + abx500_chargalg_stop_maintenance_timer(di); + init_maxim_chg_curr(di); + di->charge_status = POWER_SUPPLY_STATUS_CHARGING; + di->eoc_cnt = 0; + di->maintenance_chg = false; + power_supply_changed(&di->chargalg_psy); + + break; + + case STATE_NORMAL: + handle_maxim_chg_curr(di); + if (di->charge_status == POWER_SUPPLY_STATUS_FULL && + di->maintenance_chg) { + if (di->bat->no_maintenance) + abx500_chargalg_state_to(di, + STATE_WAIT_FOR_RECHARGE_INIT); + else + abx500_chargalg_state_to(di, + STATE_MAINTENANCE_A_INIT); + } + break; + + /* This state will be used when the maintenance state is disabled */ + case STATE_WAIT_FOR_RECHARGE_INIT: + abx500_chargalg_hold_charging(di); + abx500_chargalg_state_to(di, STATE_WAIT_FOR_RECHARGE); + di->rch_cnt = RCH_COND_CNT; + /* Intentional fallthrough */ + + case STATE_WAIT_FOR_RECHARGE: + if (di->batt_data.volt <= + di->bat->bat_type[di->bat->batt_id].recharge_vol) { + if (di->rch_cnt-- == 0) + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + } else + di->rch_cnt = RCH_COND_CNT; + break; + + case STATE_MAINTENANCE_A_INIT: + abx500_chargalg_stop_safety_timer(di); + abx500_chargalg_start_maintenance_timer(di, + di->bat->bat_type[ + di->bat->batt_id].maint_a_chg_timer_h); + abx500_chargalg_start_charging(di, + di->bat->bat_type[ + di->bat->batt_id].maint_a_vol_lvl, + di->bat->bat_type[ + di->bat->batt_id].maint_a_cur_lvl); + abx500_chargalg_state_to(di, STATE_MAINTENANCE_A); + power_supply_changed(&di->chargalg_psy); + /* Intentional fallthrough*/ + + case STATE_MAINTENANCE_A: + if (di->events.maintenance_timer_expired) { + abx500_chargalg_stop_maintenance_timer(di); + abx500_chargalg_state_to(di, STATE_MAINTENANCE_B_INIT); + } + break; + + case STATE_MAINTENANCE_B_INIT: + abx500_chargalg_start_maintenance_timer(di, + di->bat->bat_type[ + di->bat->batt_id].maint_b_chg_timer_h); + abx500_chargalg_start_charging(di, + di->bat->bat_type[ + di->bat->batt_id].maint_b_vol_lvl, + di->bat->bat_type[ + di->bat->batt_id].maint_b_cur_lvl); + abx500_chargalg_state_to(di, STATE_MAINTENANCE_B); + power_supply_changed(&di->chargalg_psy); + /* Intentional fallthrough*/ + + case STATE_MAINTENANCE_B: + if (di->events.maintenance_timer_expired) { + abx500_chargalg_stop_maintenance_timer(di); + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + } + break; + + case STATE_TEMP_LOWHIGH_INIT: + abx500_chargalg_start_charging(di, + di->bat->bat_type[ + di->bat->batt_id].low_high_vol_lvl, + di->bat->bat_type[ + di->bat->batt_id].low_high_cur_lvl); + abx500_chargalg_stop_maintenance_timer(di); + di->charge_status = POWER_SUPPLY_STATUS_CHARGING; + abx500_chargalg_state_to(di, STATE_TEMP_LOWHIGH); + power_supply_changed(&di->chargalg_psy); + /* Intentional fallthrough */ + + case STATE_TEMP_LOWHIGH: + if (!di->events.btemp_lowhigh) + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_WD_EXPIRED_INIT: + abx500_chargalg_stop_charging(di); + abx500_chargalg_state_to(di, STATE_WD_EXPIRED); + /* Intentional fallthrough */ + + case STATE_WD_EXPIRED: + if (!di->events.ac_wd_expired && + !di->events.usb_wd_expired) + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_TEMP_UNDEROVER_INIT: + abx500_chargalg_stop_charging(di); + abx500_chargalg_state_to(di, STATE_TEMP_UNDEROVER); + /* Intentional fallthrough */ + + case STATE_TEMP_UNDEROVER: + if (!di->events.btemp_underover) + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + } + + /* Start charging directly if the new state is a charge state */ + if (di->charge_state == STATE_NORMAL_INIT || + di->charge_state == STATE_MAINTENANCE_A_INIT || + di->charge_state == STATE_MAINTENANCE_B_INIT) + queue_work(di->chargalg_wq, &di->chargalg_work); +} + +/** + * abx500_chargalg_periodic_work() - Periodic work for the algorithm + * @work: pointer to the work_struct structure + * + * Work queue function for the charging algorithm + */ +static void abx500_chargalg_periodic_work(struct work_struct *work) +{ + struct abx500_chargalg *di = container_of(work, + struct abx500_chargalg, chargalg_periodic_work.work); + + abx500_chargalg_algorithm(di); + + /* + * If a charger is connected then the battery has to be monitored + * frequently, else the work can be delayed. + */ + if (di->chg_info.conn_chg) + queue_delayed_work(di->chargalg_wq, + &di->chargalg_periodic_work, + di->bat->interval_charging * HZ); + else + queue_delayed_work(di->chargalg_wq, + &di->chargalg_periodic_work, + di->bat->interval_not_charging * HZ); +} + +/** + * abx500_chargalg_wd_work() - periodic work to kick the charger watchdog + * @work: pointer to the work_struct structure + * + * Work queue function for kicking the charger watchdog + */ +static void abx500_chargalg_wd_work(struct work_struct *work) +{ + int ret; + struct abx500_chargalg *di = container_of(work, + struct abx500_chargalg, chargalg_wd_work.work); + + dev_dbg(di->dev, "abx500_chargalg_wd_work\n"); + + ret = abx500_chargalg_kick_watchdog(di); + if (ret < 0) + dev_err(di->dev, "failed to kick watchdog\n"); + + queue_delayed_work(di->chargalg_wq, + &di->chargalg_wd_work, CHG_WD_INTERVAL); +} + +/** + * abx500_chargalg_work() - Work to run the charging algorithm instantly + * @work: pointer to the work_struct structure + * + * Work queue function for calling the charging algorithm + */ +static void abx500_chargalg_work(struct work_struct *work) +{ + struct abx500_chargalg *di = container_of(work, + struct abx500_chargalg, chargalg_work); + + abx500_chargalg_algorithm(di); +} + +/** + * abx500_chargalg_get_property() - get the chargalg properties + * @psy: pointer to the power_supply structure + * @psp: pointer to the power_supply_property structure + * @val: pointer to the power_supply_propval union + * + * This function gets called when an application tries to get the + * chargalg properties by reading the sysfs files. + * status: charging/discharging/full/unknown + * health: health of the battery + * Returns error code in case of failure else 0 on success + */ +static int abx500_chargalg_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct abx500_chargalg *di; + + di = to_abx500_chargalg_device_info(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = di->charge_status; + break; + case POWER_SUPPLY_PROP_HEALTH: + if (di->events.batt_ovv) { + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + } else if (di->events.btemp_underover) { + if (di->batt_data.temp <= di->bat->temp_under) + val->intval = POWER_SUPPLY_HEALTH_COLD; + else + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + } else { + val->intval = POWER_SUPPLY_HEALTH_GOOD; + } + break; + default: + return -EINVAL; + } + return 0; +} + +/* Exposure to the sysfs interface */ + +/** + * abx500_chargalg_sysfs_charger() - sysfs store operations + * @kobj: pointer to the struct kobject + * @attr: pointer to the struct attribute + * @buf: buffer that holds the parameter passed from userspace + * @length: length of the parameter passed + * + * Returns length of the buffer(input taken from user space) on success + * else error code on failure + * The operation to be performed on passing the parameters from the user space. + */ +static ssize_t abx500_chargalg_sysfs_charger(struct kobject *kobj, + struct attribute *attr, const char *buf, size_t length) +{ + struct abx500_chargalg *di = container_of(kobj, + struct abx500_chargalg, chargalg_kobject); + long int param; + int ac_usb; + int ret; + char entry = *attr->name; + + switch (entry) { + case 'c': + ret = strict_strtol(buf, 10, ¶m); + if (ret < 0) + return ret; + + ac_usb = param; + switch (ac_usb) { + case 0: + /* Disable charging */ + di->susp_status.ac_suspended = true; + di->susp_status.usb_suspended = true; + di->susp_status.suspended_change = true; + /* Trigger a state change */ + queue_work(di->chargalg_wq, + &di->chargalg_work); + break; + case 1: + /* Enable AC Charging */ + di->susp_status.ac_suspended = false; + di->susp_status.suspended_change = true; + /* Trigger a state change */ + queue_work(di->chargalg_wq, + &di->chargalg_work); + break; + case 2: + /* Enable USB charging */ + di->susp_status.usb_suspended = false; + di->susp_status.suspended_change = true; + /* Trigger a state change */ + queue_work(di->chargalg_wq, + &di->chargalg_work); + break; + default: + dev_info(di->dev, "Wrong input\n" + "Enter 0. Disable AC/USB Charging\n" + "1. Enable AC charging\n" + "2. Enable USB Charging\n"); + }; + break; + }; + return strlen(buf); +} + +static struct attribute abx500_chargalg_en_charger = \ +{ + .name = "chargalg", + .mode = S_IWUGO, +}; + +static struct attribute *abx500_chargalg_chg[] = { + &abx500_chargalg_en_charger, + NULL +}; + +static const struct sysfs_ops abx500_chargalg_sysfs_ops = { + .store = abx500_chargalg_sysfs_charger, +}; + +static struct kobj_type abx500_chargalg_ktype = { + .sysfs_ops = &abx500_chargalg_sysfs_ops, + .default_attrs = abx500_chargalg_chg, +}; + +/** + * abx500_chargalg_sysfs_exit() - de-init of sysfs entry + * @di: pointer to the struct abx500_chargalg + * + * This function removes the entry in sysfs. + */ +static void abx500_chargalg_sysfs_exit(struct abx500_chargalg *di) +{ + kobject_del(&di->chargalg_kobject); +} + +/** + * abx500_chargalg_sysfs_init() - init of sysfs entry + * @di: pointer to the struct abx500_chargalg + * + * This function adds an entry in sysfs. + * Returns error code in case of failure else 0(on success) + */ +static int abx500_chargalg_sysfs_init(struct abx500_chargalg *di) +{ + int ret = 0; + + ret = kobject_init_and_add(&di->chargalg_kobject, + &abx500_chargalg_ktype, + NULL, "abx500_chargalg"); + if (ret < 0) + dev_err(di->dev, "failed to create sysfs entry\n"); + + return ret; +} +/* Exposure to the sysfs interface <<END>> */ + +#if defined(CONFIG_PM) +static int abx500_chargalg_resume(struct platform_device *pdev) +{ + struct abx500_chargalg *di = platform_get_drvdata(pdev); + + /* Kick charger watchdog if charging (any charger online) */ + if (di->chg_info.online_chg) + queue_delayed_work(di->chargalg_wq, &di->chargalg_wd_work, 0); + + /* + * Run the charging algorithm directly to be sure we don't + * do it too seldom + */ + queue_delayed_work(di->chargalg_wq, &di->chargalg_periodic_work, 0); + + return 0; +} + +static int abx500_chargalg_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct abx500_chargalg *di = platform_get_drvdata(pdev); + + if (di->chg_info.online_chg) + cancel_delayed_work_sync(&di->chargalg_wd_work); + + cancel_delayed_work_sync(&di->chargalg_periodic_work); + + return 0; +} +#else +#define abx500_chargalg_suspend NULL +#define abx500_chargalg_resume NULL +#endif + +static int __devexit abx500_chargalg_remove(struct platform_device *pdev) +{ + struct abx500_chargalg *di = platform_get_drvdata(pdev); + + /* sysfs interface to enable/disbale charging from user space */ + abx500_chargalg_sysfs_exit(di); + + /* Delete the work queue */ + destroy_workqueue(di->chargalg_wq); + + flush_scheduled_work(); + power_supply_unregister(&di->chargalg_psy); + platform_set_drvdata(pdev, NULL); + kfree(di); + + return 0; +} + +static int __devinit abx500_chargalg_probe(struct platform_device *pdev) +{ + struct abx500_bm_plat_data *plat_data; + int ret = 0; + + struct abx500_chargalg *di = + kzalloc(sizeof(struct abx500_chargalg), GFP_KERNEL); + if (!di) + return -ENOMEM; + + /* get device struct */ + di->dev = &pdev->dev; + + plat_data = pdev->dev.platform_data; + di->pdata = plat_data->chargalg; + di->bat = plat_data->battery; + + /* chargalg supply */ + di->chargalg_psy.name = "abx500_chargalg"; + di->chargalg_psy.type = POWER_SUPPLY_TYPE_BATTERY; + di->chargalg_psy.properties = abx500_chargalg_props; + di->chargalg_psy.num_properties = ARRAY_SIZE(abx500_chargalg_props); + di->chargalg_psy.get_property = abx500_chargalg_get_property; + di->chargalg_psy.supplied_to = di->pdata->supplied_to; + di->chargalg_psy.num_supplicants = di->pdata->num_supplicants; + di->chargalg_psy.external_power_changed = + abx500_chargalg_external_power_changed; + + /* Initilialize safety timer */ + init_timer(&di->safety_timer); + di->safety_timer.function = abx500_chargalg_safety_timer_expired; + di->safety_timer.data = (unsigned long) di; + + /* Initilialize maintenance timer */ + init_timer(&di->maintenance_timer); + di->maintenance_timer.function = + abx500_chargalg_maintenance_timer_expired; + di->maintenance_timer.data = (unsigned long) di; + + /* Create a work queue for the chargalg */ + di->chargalg_wq = + create_singlethread_workqueue("abx500_chargalg_wq"); + if (di->chargalg_wq == NULL) { + dev_err(di->dev, "failed to create work queue\n"); + goto free_device_info; + } + + /* Init work for chargalg */ + INIT_DELAYED_WORK_DEFERRABLE(&di->chargalg_periodic_work, + abx500_chargalg_periodic_work); + INIT_DELAYED_WORK_DEFERRABLE(&di->chargalg_wd_work, + abx500_chargalg_wd_work); + + /* Init work for chargalg */ + INIT_WORK(&di->chargalg_work, abx500_chargalg_work); + + /* To detect charger at startup */ + di->chg_info.prev_conn_chg = -1; + + /* Register chargalg power supply class */ + ret = power_supply_register(di->dev, &di->chargalg_psy); + if (ret) { + dev_err(di->dev, "failed to register chargalg psy\n"); + goto free_chargalg_wq; + } + + platform_set_drvdata(pdev, di); + + /* sysfs interface to enable/disable charging from user space */ + ret = abx500_chargalg_sysfs_init(di); + if (ret) { + dev_err(di->dev, "failed to create sysfs entry\n"); + goto free_psy; + } + + /* Run the charging algorithm */ + queue_delayed_work(di->chargalg_wq, &di->chargalg_periodic_work, 0); + + dev_info(di->dev, "probe success\n"); + return ret; + +free_psy: + power_supply_unregister(&di->chargalg_psy); +free_chargalg_wq: + destroy_workqueue(di->chargalg_wq); +free_device_info: + kfree(di); + + return ret; +} + +static struct platform_driver abx500_chargalg_driver = { + .probe = abx500_chargalg_probe, + .remove = __devexit_p(abx500_chargalg_remove), + .suspend = abx500_chargalg_suspend, + .resume = abx500_chargalg_resume, + .driver = { + .name = "abx500-chargalg", + .owner = THIS_MODULE, + }, +}; + +static int __init abx500_chargalg_init(void) +{ + return platform_driver_register(&abx500_chargalg_driver); +} + +static void __exit abx500_chargalg_exit(void) +{ + platform_driver_unregister(&abx500_chargalg_driver); +} + +module_init(abx500_chargalg_init); +module_exit(abx500_chargalg_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Johan Palsson, Karl Komierowski"); +MODULE_ALIAS("platform:abx500-chargalg"); +MODULE_DESCRIPTION("abx500 battery charging algorithm"); diff --git a/drivers/power/android_battery.c b/drivers/power/android_battery.c new file mode 100644 index 00000000..e0cefafb --- /dev/null +++ b/drivers/power/android_battery.c @@ -0,0 +1,767 @@ +/* + * android_battery.c + * Android Battery Driver + * + * Copyright (C) 2012 Google, Inc. + * Copyright (C) 2012 Samsung Electronics + * + * Based on work by himihee.seo@samsung.com, ms925.kim@samsung.com, and + * joshua.chang@samsung.com. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#include <linux/types.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/jiffies.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/slab.h> +#include <linux/wakelock.h> +#include <linux/workqueue.h> +#include <linux/alarmtimer.h> +#include <linux/timer.h> +#include <linux/mutex.h> +#include <linux/debugfs.h> +#include <linux/platform_data/android_battery.h> + +#define FAST_POLL (1 * 60) +#define SLOW_POLL (10 * 60) + +struct android_bat_data { + struct android_bat_platform_data *pdata; + struct android_bat_callbacks callbacks; + + struct device *dev; + + struct power_supply psy_bat; + struct power_supply psy_usb; + struct power_supply psy_ac; + + struct wake_lock monitor_wake_lock; + struct wake_lock charger_wake_lock; + + int charge_source; + + int batt_temp; + int batt_current; + unsigned int batt_health; + unsigned int batt_vcell; + unsigned int batt_soc; + unsigned int charging_status; + bool recharging; + unsigned long charging_start_time; + + struct workqueue_struct *monitor_wqueue; + struct work_struct monitor_work; + struct work_struct charger_work; + + struct alarm monitor_alarm; + ktime_t last_poll; + + struct dentry *debugfs_entry; +}; + +static char *supply_list[] = { + "android-battery", +}; + +static enum power_supply_property android_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CURRENT_NOW, +}; + +static enum power_supply_property android_power_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static DEFINE_MUTEX(android_bat_state_lock); + +static void android_bat_update_data(struct android_bat_data *battery); +static int android_bat_enable_charging(struct android_bat_data *battery, + bool enable); + +static char *charge_source_str(int charge_source) +{ + switch (charge_source) { + case CHARGE_SOURCE_NONE: + return "none"; + case CHARGE_SOURCE_AC: + return "ac"; + case CHARGE_SOURCE_USB: + return "usb"; + default: + break; + } + + return "?"; +} + +static int android_bat_get_property(struct power_supply *ps, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct android_bat_data *battery = + container_of(ps, struct android_bat_data, psy_bat); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = battery->charging_status; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = battery->batt_health; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 1; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = battery->batt_temp; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = 1; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + android_bat_update_data(battery); + val->intval = battery->batt_vcell; + if (val->intval == -1) + return -EINVAL; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = battery->batt_soc; + if (val->intval == -1) + return -EINVAL; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + android_bat_update_data(battery); + val->intval = battery->batt_current; + break; + default: + return -EINVAL; + } + return 0; +} + +static int android_usb_get_property(struct power_supply *ps, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct android_bat_data *battery = container_of(ps, + struct android_bat_data, psy_usb); + + if (psp != POWER_SUPPLY_PROP_ONLINE) + return -EINVAL; + + val->intval = (battery->charge_source == CHARGE_SOURCE_USB); + + return 0; +} + +static int android_ac_get_property(struct power_supply *ps, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct android_bat_data *battery = container_of(ps, + struct android_bat_data, psy_ac); + + if (psp != POWER_SUPPLY_PROP_ONLINE) + return -EINVAL; + + val->intval = (battery->charge_source == CHARGE_SOURCE_AC); + + return 0; +} + +static void android_bat_get_temp(struct android_bat_data *battery) +{ + int batt_temp = 42; /* 4.2C */ + int health = battery->batt_health; + + if (battery->pdata->get_temperature) + battery->pdata->get_temperature(&batt_temp); + + if (battery->charge_source != CHARGE_SOURCE_NONE) { + if (batt_temp >= battery->pdata->temp_high_threshold) { + if (health != POWER_SUPPLY_HEALTH_OVERHEAT && + health != POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) { + pr_info("battery overheat (%d>=%d), " \ + "charging unavailable\n", + batt_temp, + battery->pdata->temp_high_threshold); + battery->batt_health = + POWER_SUPPLY_HEALTH_OVERHEAT; + } + } else if (batt_temp <= battery->pdata->temp_high_recovery && + batt_temp >= battery->pdata->temp_low_recovery) { + if (health == POWER_SUPPLY_HEALTH_OVERHEAT || + health == POWER_SUPPLY_HEALTH_COLD) { + pr_info("battery recovery (%d,%d~%d)," \ + "charging available\n", + batt_temp, + battery->pdata->temp_low_recovery, + battery->pdata->temp_high_recovery); + battery->batt_health = + POWER_SUPPLY_HEALTH_GOOD; + } + } else if (batt_temp <= battery->pdata->temp_low_threshold) { + if (health != POWER_SUPPLY_HEALTH_COLD && + health != POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) { + pr_info("battery cold (%d <= %d)," \ + "charging unavailable\n", + batt_temp, + battery->pdata->temp_low_threshold); + battery->batt_health = + POWER_SUPPLY_HEALTH_COLD; + } + } + } + + battery->batt_temp = batt_temp; +} + +/* + * android_bat_state_lock not held, may call back into + * android_bat_charge_source_changed. Gathering data here can be + * non-atomic; updating our state based on the data may need to be + * atomic. + */ + +static void android_bat_update_data(struct android_bat_data *battery) +{ + int ret; + int v; + + if (battery->pdata->poll_charge_source) + battery->charge_source = battery->pdata->poll_charge_source(); + + if (battery->pdata->get_voltage_now) { + ret = battery->pdata->get_voltage_now(); + battery->batt_vcell = ret >= 0 ? ret : 4242000; + } + + if (battery->pdata->get_capacity) { + ret = battery->pdata->get_capacity(); + battery->batt_soc = ret >= 0 ? ret : 42; + } + + if (battery->pdata->get_current_now) { + ret = battery->pdata->get_current_now(&v); + + if (!ret) + battery->batt_current = v; + } + + android_bat_get_temp(battery); +} + +static void android_bat_set_charge_time(struct android_bat_data *battery, + bool enable) +{ + if (enable && !battery->charging_start_time) { + struct timespec cur_time; + + get_monotonic_boottime(&cur_time); + /* record start time for charge timeout timer */ + battery->charging_start_time = cur_time.tv_sec; + } else if (!enable) { + /* clear charge timeout timer */ + battery->charging_start_time = 0; + } +} + +static int android_bat_enable_charging(struct android_bat_data *battery, + bool enable) +{ + if (enable && (battery->batt_health != POWER_SUPPLY_HEALTH_GOOD)) { + battery->charging_status = + POWER_SUPPLY_STATUS_NOT_CHARGING; + return -EPERM; + } + + if (enable) { + if (battery->pdata && battery->pdata->set_charging_current) + battery->pdata->set_charging_current + (battery->charge_source); + } + + if (battery->pdata && battery->pdata->set_charging_enable) + battery->pdata->set_charging_enable(enable); + + android_bat_set_charge_time(battery, enable); + pr_info("battery: enable=%d charger: %s\n", enable, + charge_source_str(battery->charge_source)); + return 0; +} + +static bool android_bat_charge_timeout(struct android_bat_data *battery, + unsigned long timeout) +{ + struct timespec cur_time; + + if (!battery->charging_start_time) + return 0; + + get_monotonic_boottime(&cur_time); + pr_debug("%s: Start time: %ld, End time: %ld, current time: %ld\n", + __func__, battery->charging_start_time, + battery->charging_start_time + timeout, + cur_time.tv_sec); + return cur_time.tv_sec >= battery->charging_start_time + timeout; +} + +static void android_bat_charging_timer(struct android_bat_data *battery) +{ + if (!battery->charging_start_time && + battery->charging_status == POWER_SUPPLY_STATUS_CHARGING) { + android_bat_enable_charging(battery, true); + battery->recharging = true; + pr_debug("%s: charge status charging but timer is expired\n", + __func__); + } else if (battery->charging_start_time == 0) { + pr_debug("%s: charging_start_time never initialized\n", + __func__); + return; + } + + if (android_bat_charge_timeout( + battery, + battery->recharging ? battery->pdata->recharging_time : + battery->pdata->full_charging_time)) { + android_bat_enable_charging(battery, false); + if (battery->batt_vcell > + battery->pdata->recharging_voltage && + battery->batt_soc == 100) + battery->charging_status = + POWER_SUPPLY_STATUS_FULL; + battery->recharging = false; + battery->charging_start_time = 0; + pr_info("battery: charging timer expired\n"); + } + + return; +} + +static void android_bat_charge_source_changed(struct android_bat_callbacks *ptr, + int charge_source) +{ + struct android_bat_data *battery = + container_of(ptr, struct android_bat_data, callbacks); + + wake_lock(&battery->charger_wake_lock); + mutex_lock(&android_bat_state_lock); + battery->charge_source = charge_source; + + pr_info("battery: charge source type was changed: %s\n", + charge_source_str(battery->charge_source)); + + mutex_unlock(&android_bat_state_lock); + queue_work(battery->monitor_wqueue, &battery->charger_work); +} + +static void android_bat_set_full_status(struct android_bat_callbacks *ptr) +{ + struct android_bat_data *battery = + container_of(ptr, struct android_bat_data, callbacks); + + mutex_lock(&android_bat_state_lock); + pr_info("battery: battery full\n"); + battery->charging_status = POWER_SUPPLY_STATUS_FULL; + android_bat_enable_charging(battery, false); + battery->recharging = false; + mutex_unlock(&android_bat_state_lock); + power_supply_changed(&battery->psy_bat); +} + +static void android_bat_charger_work(struct work_struct *work) +{ + struct android_bat_data *battery = + container_of(work, struct android_bat_data, charger_work); + + mutex_lock(&android_bat_state_lock); + + switch (battery->charge_source) { + case CHARGE_SOURCE_NONE: + battery->charging_status = POWER_SUPPLY_STATUS_DISCHARGING; + android_bat_enable_charging(battery, false); + battery->batt_health = POWER_SUPPLY_HEALTH_GOOD; + battery->recharging = false; + battery->charging_start_time = 0; + break; + case CHARGE_SOURCE_USB: + case CHARGE_SOURCE_AC: + /* + * If charging status indicates a charger was already + * connected prior to this and the status is something + * other than charging ("full" or "not-charging"), leave + * the status alone. + */ + if (battery->charging_status == + POWER_SUPPLY_STATUS_DISCHARGING || + battery->charging_status == POWER_SUPPLY_STATUS_UNKNOWN) + battery->charging_status = POWER_SUPPLY_STATUS_CHARGING; + + /* + * Don't re-enable charging if the battery is full and we + * are not actively re-charging it, or if "not-charging" + * status is set. + */ + if (!((battery->charging_status == POWER_SUPPLY_STATUS_FULL + && !battery->recharging) || battery->charging_status == + POWER_SUPPLY_STATUS_NOT_CHARGING)) + android_bat_enable_charging(battery, true); + + break; + default: + pr_err("%s: Invalid charger type\n", __func__); + break; + } + + mutex_unlock(&android_bat_state_lock); + wake_lock_timeout(&battery->charger_wake_lock, HZ * 2); + power_supply_changed(&battery->psy_ac); + power_supply_changed(&battery->psy_usb); +} + + +static void android_bat_monitor_set_alarm(struct android_bat_data *battery, + int seconds) +{ + alarm_start(&battery->monitor_alarm, + ktime_add(battery->last_poll, ktime_set(seconds, 0))); +} + +static void android_bat_monitor_work(struct work_struct *work) +{ + struct android_bat_data *battery = + container_of(work, struct android_bat_data, monitor_work); + struct timespec cur_time; + + wake_lock(&battery->monitor_wake_lock); + android_bat_update_data(battery); + mutex_lock(&android_bat_state_lock); + + switch (battery->charging_status) { + case POWER_SUPPLY_STATUS_FULL: + if (battery->batt_vcell < battery->pdata->recharging_voltage && + !battery->recharging) { + battery->recharging = true; + android_bat_enable_charging(battery, true); + pr_info("battery: start recharging, v=%d\n", + battery->batt_vcell/1000); + } + break; + case POWER_SUPPLY_STATUS_DISCHARGING: + break; + case POWER_SUPPLY_STATUS_CHARGING: + switch (battery->batt_health) { + case POWER_SUPPLY_HEALTH_OVERHEAT: + case POWER_SUPPLY_HEALTH_COLD: + case POWER_SUPPLY_HEALTH_OVERVOLTAGE: + case POWER_SUPPLY_HEALTH_DEAD: + case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE: + battery->charging_status = + POWER_SUPPLY_STATUS_NOT_CHARGING; + android_bat_enable_charging(battery, false); + + pr_info("battery: Not charging, health=%d\n", + battery->batt_health); + break; + default: + break; + } + break; + case POWER_SUPPLY_STATUS_NOT_CHARGING: + if (battery->batt_health == POWER_SUPPLY_HEALTH_GOOD) { + pr_info("battery: battery health recovered\n"); + if (battery->charge_source != CHARGE_SOURCE_NONE) { + android_bat_enable_charging(battery, true); + battery->charging_status + = POWER_SUPPLY_STATUS_CHARGING; + } else { + battery->charging_status + = POWER_SUPPLY_STATUS_DISCHARGING; + } + } + break; + default: + pr_err("%s: Undefined battery status: %d\n", __func__, + battery->charging_status); + break; + } + + android_bat_charging_timer(battery); + get_monotonic_boottime(&cur_time); + pr_info("battery: l=%d v=%d c=%d temp=%s%ld.%ld h=%d st=%d%s ct=%lu type=%s\n", + battery->batt_soc, battery->batt_vcell/1000, + battery->batt_current, battery->batt_temp < 0 ? "-" : "", + abs(battery->batt_temp / 10), abs(battery->batt_temp % 10), + battery->batt_health, battery->charging_status, + battery->recharging ? "r" : "", + battery->charging_start_time ? + cur_time.tv_sec - battery->charging_start_time : 0, + charge_source_str(battery->charge_source)); + mutex_unlock(&android_bat_state_lock); + power_supply_changed(&battery->psy_bat); + battery->last_poll = ktime_get_boottime(); + android_bat_monitor_set_alarm(battery, FAST_POLL); + wake_unlock(&battery->monitor_wake_lock); + return; +} + +static enum alarmtimer_restart android_bat_monitor_alarm( + struct alarm *alarm, ktime_t now) +{ + struct android_bat_data *battery = + container_of(alarm, struct android_bat_data, monitor_alarm); + + wake_lock(&battery->monitor_wake_lock); + queue_work(battery->monitor_wqueue, &battery->monitor_work); + return ALARMTIMER_NORESTART; +} + +static int android_power_debug_dump(struct seq_file *s, void *unused) +{ + struct android_bat_data *battery = s->private; + struct timespec cur_time; + + android_bat_update_data(battery); + get_monotonic_boottime(&cur_time); + mutex_lock(&android_bat_state_lock); + seq_printf(s, "l=%d v=%d c=%d temp=%s%ld.%ld h=%d st=%d%s ct=%lu type=%s\n", + battery->batt_soc, battery->batt_vcell/1000, + battery->batt_current, battery->batt_temp < 0 ? "-" : "", + abs(battery->batt_temp / 10), abs(battery->batt_temp % 10), + battery->batt_health, battery->charging_status, + battery->recharging ? "r" : "", + battery->charging_start_time ? + cur_time.tv_sec - battery->charging_start_time : 0, + charge_source_str(battery->charge_source)); + mutex_unlock(&android_bat_state_lock); + return 0; +} + +static int android_power_debug_open(struct inode *inode, struct file *file) +{ + return single_open(file, android_power_debug_dump, inode->i_private); +} + +static const struct file_operations android_power_debug_fops = { + .open = android_power_debug_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static __devinit int android_bat_probe(struct platform_device *pdev) +{ + struct android_bat_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct android_bat_data *battery; + int ret = 0; + + dev_info(&pdev->dev, "Android Battery Driver\n"); + battery = kzalloc(sizeof(*battery), GFP_KERNEL); + if (!battery) + return -ENOMEM; + + battery->pdata = pdata; + if (!battery->pdata) { + pr_err("%s : No platform data\n", __func__); + ret = -EINVAL; + goto err_pdata; + } + + battery->dev = &pdev->dev; + platform_set_drvdata(pdev, battery); + battery->batt_health = POWER_SUPPLY_HEALTH_GOOD; + + battery->psy_bat.name = "android-battery", + battery->psy_bat.type = POWER_SUPPLY_TYPE_BATTERY, + battery->psy_bat.properties = android_battery_props, + battery->psy_bat.num_properties = ARRAY_SIZE(android_battery_props), + battery->psy_bat.get_property = android_bat_get_property, + + battery->psy_usb.name = "android-usb", + battery->psy_usb.type = POWER_SUPPLY_TYPE_USB, + battery->psy_usb.supplied_to = supply_list, + battery->psy_usb.num_supplicants = ARRAY_SIZE(supply_list), + battery->psy_usb.properties = android_power_props, + battery->psy_usb.num_properties = ARRAY_SIZE(android_power_props), + battery->psy_usb.get_property = android_usb_get_property, + + battery->psy_ac.name = "android-ac", + battery->psy_ac.type = POWER_SUPPLY_TYPE_MAINS, + battery->psy_ac.supplied_to = supply_list, + battery->psy_ac.num_supplicants = ARRAY_SIZE(supply_list), + battery->psy_ac.properties = android_power_props, + battery->psy_ac.num_properties = ARRAY_SIZE(android_power_props), + battery->psy_ac.get_property = android_ac_get_property; + + battery->batt_vcell = -1; + battery->batt_soc = -1; + + wake_lock_init(&battery->monitor_wake_lock, WAKE_LOCK_SUSPEND, + "android-battery-monitor"); + wake_lock_init(&battery->charger_wake_lock, WAKE_LOCK_SUSPEND, + "android-chargerdetect"); + + ret = power_supply_register(&pdev->dev, &battery->psy_bat); + if (ret) { + dev_err(battery->dev, "%s: failed to register psy_bat\n", + __func__); + goto err_psy_bat_reg; + } + + ret = power_supply_register(&pdev->dev, &battery->psy_usb); + if (ret) { + dev_err(battery->dev, "%s: failed to register psy_usb\n", + __func__); + goto err_psy_usb_reg; + } + + ret = power_supply_register(&pdev->dev, &battery->psy_ac); + if (ret) { + dev_err(battery->dev, "%s: failed to register psy_ac\n", + __func__); + goto err_psy_ac_reg; + } + + battery->monitor_wqueue = + alloc_workqueue(dev_name(&pdev->dev), WQ_FREEZABLE, 1); + if (!battery->monitor_wqueue) { + dev_err(battery->dev, "%s: fail to create workqueue\n", + __func__); + goto err_wq; + } + + INIT_WORK(&battery->monitor_work, android_bat_monitor_work); + INIT_WORK(&battery->charger_work, android_bat_charger_work); + + battery->callbacks.charge_source_changed = + android_bat_charge_source_changed; + battery->callbacks.battery_set_full = + android_bat_set_full_status; + if (battery->pdata && battery->pdata->register_callbacks) + battery->pdata->register_callbacks(&battery->callbacks); + + /* get initial charger status */ + if (battery->pdata->poll_charge_source) + battery->charge_source = battery->pdata->poll_charge_source(); + + wake_lock(&battery->charger_wake_lock); + queue_work(battery->monitor_wqueue, &battery->charger_work); + + wake_lock(&battery->monitor_wake_lock); + battery->last_poll = ktime_get_boottime(); + alarm_init(&battery->monitor_alarm, ALARM_BOOTTIME, + android_bat_monitor_alarm); + queue_work(battery->monitor_wqueue, &battery->monitor_work); + + battery->debugfs_entry = + debugfs_create_file("android-power", S_IRUGO, NULL, + battery, &android_power_debug_fops); + if (!battery->debugfs_entry) + pr_err("failed to create android-power debugfs entry\n"); + + return 0; + +err_wq: + power_supply_unregister(&battery->psy_ac); +err_psy_ac_reg: + power_supply_unregister(&battery->psy_usb); +err_psy_usb_reg: + power_supply_unregister(&battery->psy_bat); +err_psy_bat_reg: + wake_lock_destroy(&battery->monitor_wake_lock); + wake_lock_destroy(&battery->charger_wake_lock); +err_pdata: + kfree(battery); + + return ret; +} + +static int __devexit android_bat_remove(struct platform_device *pdev) +{ + struct android_bat_data *battery = platform_get_drvdata(pdev); + + alarm_cancel(&battery->monitor_alarm); + flush_workqueue(battery->monitor_wqueue); + destroy_workqueue(battery->monitor_wqueue); + power_supply_unregister(&battery->psy_bat); + wake_lock_destroy(&battery->monitor_wake_lock); + wake_lock_destroy(&battery->charger_wake_lock); + debugfs_remove(battery->debugfs_entry); + kfree(battery); + return 0; +} + +static int android_bat_suspend(struct device *dev) +{ + struct android_bat_data *battery = dev_get_drvdata(dev); + + cancel_work_sync(&battery->monitor_work); + android_bat_monitor_set_alarm( + battery, + battery->charge_source == CHARGE_SOURCE_NONE ? + SLOW_POLL : FAST_POLL); + return 0; +} + +static void android_bat_resume(struct device *dev) +{ + struct android_bat_data *battery = dev_get_drvdata(dev); + + android_bat_monitor_set_alarm(battery, FAST_POLL); + return; +} + +static const struct dev_pm_ops android_bat_pm_ops = { + .prepare = android_bat_suspend, + .complete = android_bat_resume, +}; + +static struct platform_driver android_bat_driver = { + .driver = { + .name = "android-battery", + .owner = THIS_MODULE, + .pm = &android_bat_pm_ops, + }, + .probe = android_bat_probe, + .remove = __devexit_p(android_bat_remove), +}; + +static int __init android_bat_init(void) +{ + return platform_driver_register(&android_bat_driver); +} + +static void __exit android_bat_exit(void) +{ + platform_driver_unregister(&android_bat_driver); +} + +late_initcall(android_bat_init); +module_exit(android_bat_exit); + +MODULE_DESCRIPTION("Android battery driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/apm_power.c b/drivers/power/apm_power.c new file mode 100644 index 00000000..39763015 --- /dev/null +++ b/drivers/power/apm_power.c @@ -0,0 +1,376 @@ +/* + * Copyright © 2007 Anton Vorontsov <cbou@mail.ru> + * Copyright © 2007 Eugeny Boger <eugenyboger@dgap.mipt.ru> + * + * Author: Eugeny Boger <eugenyboger@dgap.mipt.ru> + * + * 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 <linux/module.h> +#include <linux/device.h> +#include <linux/power_supply.h> +#include <linux/apm-emulation.h> + + +#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 <eugenyboger@dgap.mipt.ru>"); +MODULE_DESCRIPTION("APM emulation driver for battery monitoring class"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/bq27x00_battery.c b/drivers/power/bq27x00_battery.c new file mode 100644 index 00000000..222ccd87 --- /dev/null +++ b/drivers/power/bq27x00_battery.c @@ -0,0 +1,917 @@ +/* + * BQ27x00 battery driver + * + * Copyright (C) 2008 Rodolfo Giometti <giometti@linux.it> + * Copyright (C) 2008 Eurotech S.p.A. <info@eurotech.it> + * Copyright (C) 2010-2011 Lars-Peter Clausen <lars@metafoo.de> + * Copyright (C) 2011 Pali Rohár <pali.rohar@gmail.com> + * + * 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 <linux/module.h> +#include <linux/param.h> +#include <linux/jiffies.h> +#include <linux/workqueue.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/idr.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <asm/unaligned.h> + +#include <linux/power/bq27x00_battery.h> + +#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 capacity */ +#define BQ27x00_REG_LMD 0x12 /* Last measured discharge */ +#define BQ27x00_REG_CYCT 0x2A /* Cycle count total */ +#define BQ27x00_REG_AE 0x22 /* Available energy */ + +#define BQ27000_REG_RSOC 0x0B /* Relative State-of-Charge */ +#define BQ27000_REG_ILMD 0x76 /* Initial last measured discharge */ +#define BQ27000_FLAG_EDVF BIT(0) /* Final End-of-Discharge-Voltage flag */ +#define BQ27000_FLAG_EDV1 BIT(1) /* First End-of-Discharge-Voltage flag */ +#define BQ27000_FLAG_CI BIT(4) /* Capacity Inaccurate flag */ +#define BQ27000_FLAG_FC BIT(5) +#define BQ27000_FLAG_CHGS BIT(7) /* Charge state flag */ + +#define BQ27500_REG_SOC 0x2C +#define BQ27500_REG_DCAP 0x3C /* Design capacity */ +#define BQ27500_FLAG_DSC BIT(0) +#define BQ27500_FLAG_SOCF BIT(1) /* State-of-Charge threshold final */ +#define BQ27500_FLAG_SOC1 BIT(2) /* State-of-Charge threshold 1 */ +#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 energy; + int flags; +}; + +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_CAPACITY_LEVEL, + 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_dbg(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_dbg(di->dev, "error reading charge register %02x: %d\n", + reg, charge); + 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_dbg(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 Available energy in µWh + * Or < 0 if something fails. + */ +static int bq27x00_battery_read_energy(struct bq27x00_device_info *di) +{ + int ae; + + ae = bq27x00_read(di, BQ27x00_REG_AE, false); + if (ae < 0) { + dev_dbg(di->dev, "error reading available energy\n"); + return ae; + } + + if (di->chip == BQ27500) + ae *= 1000; + else + ae = ae * 29200 / BQ27000_RS; + + return ae; +} + +/* + * Return the battery temperature in tenths of degree Celsius + * Or < 0 if something fails. + */ +static int bq27x00_battery_read_temperature(struct bq27x00_device_info *di) +{ + int temp; + + temp = bq27x00_read(di, BQ27x00_REG_TEMP, false); + if (temp < 0) { + dev_err(di->dev, "error reading temperature\n"); + return temp; + } + + if (di->chip == BQ27500) + temp -= 2731; + else + temp = ((temp * 5) - 5463) / 2; + + return temp; +} + +/* + * 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_dbg(di->dev, "error reading time 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) { + if (!is_bq27500 && (cache.flags & BQ27000_FLAG_CI)) { + dev_info(di->dev, "battery is not calibrated! ignoring capacity values\n"); + cache.capacity = -ENODATA; + cache.energy = -ENODATA; + cache.time_to_empty = -ENODATA; + cache.time_to_empty_avg = -ENODATA; + cache.time_to_full = -ENODATA; + cache.charge_full = -ENODATA; + } else { + cache.capacity = bq27x00_battery_read_rsoc(di); + cache.energy = bq27x00_battery_read_energy(di); + 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.temperature = bq27x00_battery_read_temperature(di); + cache.cycle_count = bq27x00_battery_read_cyct(di); + + /* We only have to read charge design full once */ + if (di->charge_design_full <= 0) + di->charge_design_full = bq27x00_battery_read_ilmd(di); + } + + if (memcmp(&di->cache, &cache, sizeof(cache)) != 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 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; + int flags; + + curr = bq27x00_read(di, BQ27x00_REG_AI, false); + if (curr < 0) { + dev_err(di->dev, "error reading current\n"); + return curr; + } + + if (di->chip == BQ27500) { + /* bq27500 returns signed value */ + val->intval = (int)((s16)curr) * 1000; + } else { + flags = bq27x00_read(di, BQ27x00_REG_FLAGS, false); + if (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; +} + +static int bq27x00_battery_capacity_level(struct bq27x00_device_info *di, + union power_supply_propval *val) +{ + int level; + + if (di->chip == BQ27500) { + if (di->cache.flags & BQ27500_FLAG_FC) + level = POWER_SUPPLY_CAPACITY_LEVEL_FULL; + else if (di->cache.flags & BQ27500_FLAG_SOC1) + level = POWER_SUPPLY_CAPACITY_LEVEL_LOW; + else if (di->cache.flags & BQ27500_FLAG_SOCF) + level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; + else + level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; + } else { + if (di->cache.flags & BQ27000_FLAG_FC) + level = POWER_SUPPLY_CAPACITY_LEVEL_FULL; + else if (di->cache.flags & BQ27000_FLAG_EDV1) + level = POWER_SUPPLY_CAPACITY_LEVEL_LOW; + else if (di->cache.flags & BQ27000_FLAG_EDVF) + level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; + else + level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; + } + + val->intval = level; + + 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) { + dev_err(di->dev, "error reading voltage\n"); + return volt; + } + + val->intval = volt * 1000; + + 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_CAPACITY_LEVEL: + ret = bq27x00_battery_capacity_level(di, val); + break; + case POWER_SUPPLY_PROP_TEMP: + ret = bq27x00_simple_value(di->cache.temperature, 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_simple_value(di->cache.energy, 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) +{ + /* + * power_supply_unregister call bq27x00_battery_get_property which + * call bq27x00_battery_poll. + * Make sure that bq27x00_battery_poll will not call + * schedule_delayed_work again after unregister (which cause OOPS). + */ + poll_interval = 0; + + 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 <giometti@linux.it>"); +MODULE_DESCRIPTION("BQ27x00 battery monitor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/charger-manager.c b/drivers/power/charger-manager.c new file mode 100644 index 00000000..9eca9f1f --- /dev/null +++ b/drivers/power/charger-manager.c @@ -0,0 +1,1063 @@ +/* + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + * MyungJoo Ham <myungjoo.ham@samsung.com> + * + * This driver enables to monitor battery health and control charger + * during suspend-to-mem. + * Charger manager depends on other devices. register this later than + * the depending devices. + * + * 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 <linux/io.h> +#include <linux/module.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/rtc.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include <linux/platform_device.h> +#include <linux/power/charger-manager.h> +#include <linux/regulator/consumer.h> + +/* + * Regard CM_JIFFIES_SMALL jiffies is small enough to ignore for + * delayed works so that we can run delayed works with CM_JIFFIES_SMALL + * without any delays. + */ +#define CM_JIFFIES_SMALL (2) + +/* If y is valid (> 0) and smaller than x, do x = y */ +#define CM_MIN_VALID(x, y) x = (((y > 0) && ((x) > (y))) ? (y) : (x)) + +/* + * Regard CM_RTC_SMALL (sec) is small enough to ignore error in invoking + * rtc alarm. It should be 2 or larger + */ +#define CM_RTC_SMALL (2) + +#define UEVENT_BUF_SIZE 32 + +static LIST_HEAD(cm_list); +static DEFINE_MUTEX(cm_list_mtx); + +/* About in-suspend (suspend-again) monitoring */ +static struct rtc_device *rtc_dev; +/* + * Backup RTC alarm + * Save the wakeup alarm before entering suspend-to-RAM + */ +static struct rtc_wkalrm rtc_wkalarm_save; +/* Backup RTC alarm time in terms of seconds since 01-01-1970 00:00:00 */ +static unsigned long rtc_wkalarm_save_time; +static bool cm_suspended; +static bool cm_rtc_set; +static unsigned long cm_suspend_duration_ms; + +/* Global charger-manager description */ +static struct charger_global_desc *g_desc; /* init with setup_charger_manager */ + +/** + * is_batt_present - See if the battery presents in place. + * @cm: the Charger Manager representing the battery. + */ +static bool is_batt_present(struct charger_manager *cm) +{ + union power_supply_propval val; + bool present = false; + int i, ret; + + switch (cm->desc->battery_present) { + case CM_FUEL_GAUGE: + ret = cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_PRESENT, &val); + if (ret == 0 && val.intval) + present = true; + break; + case CM_CHARGER_STAT: + for (i = 0; cm->charger_stat[i]; i++) { + ret = cm->charger_stat[i]->get_property( + cm->charger_stat[i], + POWER_SUPPLY_PROP_PRESENT, &val); + if (ret == 0 && val.intval) { + present = true; + break; + } + } + break; + } + + return present; +} + +/** + * is_ext_pwr_online - See if an external power source is attached to charge + * @cm: the Charger Manager representing the battery. + * + * Returns true if at least one of the chargers of the battery has an external + * power source attached to charge the battery regardless of whether it is + * actually charging or not. + */ +static bool is_ext_pwr_online(struct charger_manager *cm) +{ + union power_supply_propval val; + bool online = false; + int i, ret; + + /* If at least one of them has one, it's yes. */ + for (i = 0; cm->charger_stat[i]; i++) { + ret = cm->charger_stat[i]->get_property( + cm->charger_stat[i], + POWER_SUPPLY_PROP_ONLINE, &val); + if (ret == 0 && val.intval) { + online = true; + break; + } + } + + return online; +} + +/** + * get_batt_uV - Get the voltage level of the battery + * @cm: the Charger Manager representing the battery. + * @uV: the voltage level returned. + * + * Returns 0 if there is no error. + * Returns a negative value on error. + */ +static int get_batt_uV(struct charger_manager *cm, int *uV) +{ + union power_supply_propval val; + int ret; + + if (!cm->fuel_gauge) + return -ENODEV; + + ret = cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_VOLTAGE_NOW, &val); + if (ret) + return ret; + + *uV = val.intval; + return 0; +} + +/** + * is_charging - Returns true if the battery is being charged. + * @cm: the Charger Manager representing the battery. + */ +static bool is_charging(struct charger_manager *cm) +{ + int i, ret; + bool charging = false; + union power_supply_propval val; + + /* If there is no battery, it cannot be charged */ + if (!is_batt_present(cm)) + return false; + + /* If at least one of the charger is charging, return yes */ + for (i = 0; cm->charger_stat[i]; i++) { + /* 1. The charger sholuld not be DISABLED */ + if (cm->emergency_stop) + continue; + if (!cm->charger_enabled) + continue; + + /* 2. The charger should be online (ext-power) */ + ret = cm->charger_stat[i]->get_property( + cm->charger_stat[i], + POWER_SUPPLY_PROP_ONLINE, &val); + if (ret) { + dev_warn(cm->dev, "Cannot read ONLINE value from %s.\n", + cm->desc->psy_charger_stat[i]); + continue; + } + if (val.intval == 0) + continue; + + /* + * 3. The charger should not be FULL, DISCHARGING, + * or NOT_CHARGING. + */ + ret = cm->charger_stat[i]->get_property( + cm->charger_stat[i], + POWER_SUPPLY_PROP_STATUS, &val); + if (ret) { + dev_warn(cm->dev, "Cannot read STATUS value from %s.\n", + cm->desc->psy_charger_stat[i]); + continue; + } + if (val.intval == POWER_SUPPLY_STATUS_FULL || + val.intval == POWER_SUPPLY_STATUS_DISCHARGING || + val.intval == POWER_SUPPLY_STATUS_NOT_CHARGING) + continue; + + /* Then, this is charging. */ + charging = true; + break; + } + + return charging; +} + +/** + * is_polling_required - Return true if need to continue polling for this CM. + * @cm: the Charger Manager representing the battery. + */ +static bool is_polling_required(struct charger_manager *cm) +{ + switch (cm->desc->polling_mode) { + case CM_POLL_DISABLE: + return false; + case CM_POLL_ALWAYS: + return true; + case CM_POLL_EXTERNAL_POWER_ONLY: + return is_ext_pwr_online(cm); + case CM_POLL_CHARGING_ONLY: + return is_charging(cm); + default: + dev_warn(cm->dev, "Incorrect polling_mode (%d)\n", + cm->desc->polling_mode); + } + + return false; +} + +/** + * try_charger_enable - Enable/Disable chargers altogether + * @cm: the Charger Manager representing the battery. + * @enable: true: enable / false: disable + * + * Note that Charger Manager keeps the charger enabled regardless whether + * the charger is charging or not (because battery is full or no external + * power source exists) except when CM needs to disable chargers forcibly + * bacause of emergency causes; when the battery is overheated or too cold. + */ +static int try_charger_enable(struct charger_manager *cm, bool enable) +{ + int err = 0, i; + struct charger_desc *desc = cm->desc; + + /* Ignore if it's redundent command */ + if (enable == cm->charger_enabled) + return 0; + + if (enable) { + if (cm->emergency_stop) + return -EAGAIN; + err = regulator_bulk_enable(desc->num_charger_regulators, + desc->charger_regulators); + } else { + /* + * Abnormal battery state - Stop charging forcibly, + * even if charger was enabled at the other places + */ + err = regulator_bulk_disable(desc->num_charger_regulators, + desc->charger_regulators); + + for (i = 0; i < desc->num_charger_regulators; i++) { + if (regulator_is_enabled( + desc->charger_regulators[i].consumer)) { + regulator_force_disable( + desc->charger_regulators[i].consumer); + dev_warn(cm->dev, + "Disable regulator(%s) forcibly.\n", + desc->charger_regulators[i].supply); + } + } + } + + if (!err) + cm->charger_enabled = enable; + + return err; +} + +/** + * uevent_notify - Let users know something has changed. + * @cm: the Charger Manager representing the battery. + * @event: the event string. + * + * If @event is null, it implies that uevent_notify is called + * by resume function. When called in the resume function, cm_suspended + * should be already reset to false in order to let uevent_notify + * notify the recent event during the suspend to users. While + * suspended, uevent_notify does not notify users, but tracks + * events so that uevent_notify can notify users later after resumed. + */ +static void uevent_notify(struct charger_manager *cm, const char *event) +{ + static char env_str[UEVENT_BUF_SIZE + 1] = ""; + static char env_str_save[UEVENT_BUF_SIZE + 1] = ""; + + if (cm_suspended) { + /* Nothing in suspended-event buffer */ + if (env_str_save[0] == 0) { + if (!strncmp(env_str, event, UEVENT_BUF_SIZE)) + return; /* status not changed */ + strncpy(env_str_save, event, UEVENT_BUF_SIZE); + return; + } + + if (!strncmp(env_str_save, event, UEVENT_BUF_SIZE)) + return; /* Duplicated. */ + strncpy(env_str_save, event, UEVENT_BUF_SIZE); + return; + } + + if (event == NULL) { + /* No messages pending */ + if (!env_str_save[0]) + return; + + strncpy(env_str, env_str_save, UEVENT_BUF_SIZE); + kobject_uevent(&cm->dev->kobj, KOBJ_CHANGE); + env_str_save[0] = 0; + + return; + } + + /* status not changed */ + if (!strncmp(env_str, event, UEVENT_BUF_SIZE)) + return; + + /* save the status and notify the update */ + strncpy(env_str, event, UEVENT_BUF_SIZE); + kobject_uevent(&cm->dev->kobj, KOBJ_CHANGE); + + dev_info(cm->dev, event); +} + +/** + * _cm_monitor - Monitor the temperature and return true for exceptions. + * @cm: the Charger Manager representing the battery. + * + * Returns true if there is an event to notify for the battery. + * (True if the status of "emergency_stop" changes) + */ +static bool _cm_monitor(struct charger_manager *cm) +{ + struct charger_desc *desc = cm->desc; + int temp = desc->temperature_out_of_range(&cm->last_temp_mC); + + dev_dbg(cm->dev, "monitoring (%2.2d.%3.3dC)\n", + cm->last_temp_mC / 1000, cm->last_temp_mC % 1000); + + /* It has been stopped or charging already */ + if (!!temp == !!cm->emergency_stop) + return false; + + if (temp) { + cm->emergency_stop = temp; + if (!try_charger_enable(cm, false)) { + if (temp > 0) + uevent_notify(cm, "OVERHEAT"); + else + uevent_notify(cm, "COLD"); + } + } else { + cm->emergency_stop = 0; + if (!try_charger_enable(cm, true)) + uevent_notify(cm, "CHARGING"); + } + + return true; +} + +/** + * cm_monitor - Monitor every battery. + * + * Returns true if there is an event to notify from any of the batteries. + * (True if the status of "emergency_stop" changes) + */ +static bool cm_monitor(void) +{ + bool stop = false; + struct charger_manager *cm; + + mutex_lock(&cm_list_mtx); + + list_for_each_entry(cm, &cm_list, entry) { + if (_cm_monitor(cm)) + stop = true; + } + + mutex_unlock(&cm_list_mtx); + + return stop; +} + +static int charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct charger_manager *cm = container_of(psy, + struct charger_manager, charger_psy); + struct charger_desc *desc = cm->desc; + int ret = 0; + int uV; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (is_charging(cm)) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else if (is_ext_pwr_online(cm)) + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + else + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case POWER_SUPPLY_PROP_HEALTH: + if (cm->emergency_stop > 0) + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (cm->emergency_stop < 0) + val->intval = POWER_SUPPLY_HEALTH_COLD; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_PRESENT: + if (is_batt_present(cm)) + val->intval = 1; + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = get_batt_uV(cm, &val->intval); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_CURRENT_NOW, val); + break; + case POWER_SUPPLY_PROP_TEMP: + /* in thenth of centigrade */ + if (cm->last_temp_mC == INT_MIN) + desc->temperature_out_of_range(&cm->last_temp_mC); + val->intval = cm->last_temp_mC / 100; + if (!desc->measure_battery_temp) + ret = -ENODEV; + break; + case POWER_SUPPLY_PROP_TEMP_AMBIENT: + /* in thenth of centigrade */ + if (cm->last_temp_mC == INT_MIN) + desc->temperature_out_of_range(&cm->last_temp_mC); + val->intval = cm->last_temp_mC / 100; + if (desc->measure_battery_temp) + ret = -ENODEV; + break; + case POWER_SUPPLY_PROP_CAPACITY: + if (!cm->fuel_gauge) { + ret = -ENODEV; + break; + } + + if (!is_batt_present(cm)) { + /* There is no battery. Assume 100% */ + val->intval = 100; + break; + } + + ret = cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_CAPACITY, val); + if (ret) + break; + + if (val->intval > 100) { + val->intval = 100; + break; + } + if (val->intval < 0) + val->intval = 0; + + /* Do not adjust SOC when charging: voltage is overrated */ + if (is_charging(cm)) + break; + + /* + * If the capacity value is inconsistent, calibrate it base on + * the battery voltage values and the thresholds given as desc + */ + ret = get_batt_uV(cm, &uV); + if (ret) { + /* Voltage information not available. No calibration */ + ret = 0; + break; + } + + if (desc->fullbatt_uV > 0 && uV >= desc->fullbatt_uV && + !is_charging(cm)) { + val->intval = 100; + break; + } + + break; + case POWER_SUPPLY_PROP_ONLINE: + if (is_ext_pwr_online(cm)) + val->intval = 1; + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + if (cm->fuel_gauge) { + if (cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_CHARGE_FULL, val) == 0) + break; + } + + if (is_ext_pwr_online(cm)) { + /* Not full if it's charging. */ + if (is_charging(cm)) { + val->intval = 0; + break; + } + /* + * Full if it's powered but not charging andi + * not forced stop by emergency + */ + if (!cm->emergency_stop) { + val->intval = 1; + break; + } + } + + /* Full if it's over the fullbatt voltage */ + ret = get_batt_uV(cm, &uV); + if (!ret && desc->fullbatt_uV > 0 && uV >= desc->fullbatt_uV && + !is_charging(cm)) { + val->intval = 1; + break; + } + + /* Full if the cap is 100 */ + if (cm->fuel_gauge) { + ret = cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_CAPACITY, val); + if (!ret && val->intval >= 100 && !is_charging(cm)) { + val->intval = 1; + break; + } + } + + val->intval = 0; + ret = 0; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + if (is_charging(cm)) { + ret = cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_CHARGE_NOW, + val); + if (ret) { + val->intval = 1; + ret = 0; + } else { + /* If CHARGE_NOW is supplied, use it */ + val->intval = (val->intval > 0) ? + val->intval : 1; + } + } else { + val->intval = 0; + } + break; + default: + return -EINVAL; + } + return ret; +} + +#define NUM_CHARGER_PSY_OPTIONAL (4) +static enum power_supply_property default_charger_props[] = { + /* Guaranteed to provide */ + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CHARGE_FULL, + /* + * Optional properties are: + * POWER_SUPPLY_PROP_CHARGE_NOW, + * POWER_SUPPLY_PROP_CURRENT_NOW, + * POWER_SUPPLY_PROP_TEMP, and + * POWER_SUPPLY_PROP_TEMP_AMBIENT, + */ +}; + +static struct power_supply psy_default = { + .name = "battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = default_charger_props, + .num_properties = ARRAY_SIZE(default_charger_props), + .get_property = charger_get_property, +}; + +/** + * cm_setup_timer - For in-suspend monitoring setup wakeup alarm + * for suspend_again. + * + * Returns true if the alarm is set for Charger Manager to use. + * Returns false if + * cm_setup_timer fails to set an alarm, + * cm_setup_timer does not need to set an alarm for Charger Manager, + * or an alarm previously configured is to be used. + */ +static bool cm_setup_timer(void) +{ + struct charger_manager *cm; + unsigned int wakeup_ms = UINT_MAX; + bool ret = false; + + mutex_lock(&cm_list_mtx); + + list_for_each_entry(cm, &cm_list, entry) { + /* Skip if polling is not required for this CM */ + if (!is_polling_required(cm) && !cm->emergency_stop) + continue; + if (cm->desc->polling_interval_ms == 0) + continue; + CM_MIN_VALID(wakeup_ms, cm->desc->polling_interval_ms); + } + + mutex_unlock(&cm_list_mtx); + + if (wakeup_ms < UINT_MAX && wakeup_ms > 0) { + pr_info("Charger Manager wakeup timer: %u ms.\n", wakeup_ms); + if (rtc_dev) { + struct rtc_wkalrm tmp; + unsigned long time, now; + unsigned long add = DIV_ROUND_UP(wakeup_ms, 1000); + + /* + * Set alarm with the polling interval (wakeup_ms) + * except when rtc_wkalarm_save comes first. + * However, the alarm time should be NOW + + * CM_RTC_SMALL or later. + */ + tmp.enabled = 1; + rtc_read_time(rtc_dev, &tmp.time); + rtc_tm_to_time(&tmp.time, &now); + if (add < CM_RTC_SMALL) + add = CM_RTC_SMALL; + time = now + add; + + ret = true; + + if (rtc_wkalarm_save.enabled && + rtc_wkalarm_save_time && + rtc_wkalarm_save_time < time) { + if (rtc_wkalarm_save_time < now + CM_RTC_SMALL) + time = now + CM_RTC_SMALL; + else + time = rtc_wkalarm_save_time; + + /* The timer is not appointed by CM */ + ret = false; + } + + pr_info("Waking up after %lu secs.\n", + time - now); + + rtc_time_to_tm(time, &tmp.time); + rtc_set_alarm(rtc_dev, &tmp); + cm_suspend_duration_ms += wakeup_ms; + return ret; + } + } + + if (rtc_dev) + rtc_set_alarm(rtc_dev, &rtc_wkalarm_save); + return false; +} + +/** + * cm_suspend_again - Determine whether suspend again or not + * + * Returns true if the system should be suspended again + * Returns false if the system should be woken up + */ +bool cm_suspend_again(void) +{ + struct charger_manager *cm; + bool ret = false; + + if (!g_desc || !g_desc->rtc_only_wakeup || !g_desc->rtc_only_wakeup() || + !cm_rtc_set) + return false; + + if (cm_monitor()) + goto out; + + ret = true; + mutex_lock(&cm_list_mtx); + list_for_each_entry(cm, &cm_list, entry) { + if (cm->status_save_ext_pwr_inserted != is_ext_pwr_online(cm) || + cm->status_save_batt != is_batt_present(cm)) { + ret = false; + break; + } + } + mutex_unlock(&cm_list_mtx); + + cm_rtc_set = cm_setup_timer(); +out: + /* It's about the time when the non-CM appointed timer goes off */ + if (rtc_wkalarm_save.enabled) { + unsigned long now; + struct rtc_time tmp; + + rtc_read_time(rtc_dev, &tmp); + rtc_tm_to_time(&tmp, &now); + + if (rtc_wkalarm_save_time && + now + CM_RTC_SMALL >= rtc_wkalarm_save_time) + return false; + } + return ret; +} +EXPORT_SYMBOL_GPL(cm_suspend_again); + +/** + * setup_charger_manager - initialize charger_global_desc data + * @gd: pointer to instance of charger_global_desc + */ +int setup_charger_manager(struct charger_global_desc *gd) +{ + if (!gd) + return -EINVAL; + + if (rtc_dev) + rtc_class_close(rtc_dev); + rtc_dev = NULL; + g_desc = NULL; + + if (!gd->rtc_only_wakeup) { + pr_err("The callback rtc_only_wakeup is not given.\n"); + return -EINVAL; + } + + if (gd->rtc_name) { + rtc_dev = rtc_class_open(gd->rtc_name); + if (IS_ERR_OR_NULL(rtc_dev)) { + rtc_dev = NULL; + /* Retry at probe. RTC may be not registered yet */ + } + } else { + pr_warn("No wakeup timer is given for charger manager." + "In-suspend monitoring won't work.\n"); + } + + g_desc = gd; + return 0; +} +EXPORT_SYMBOL_GPL(setup_charger_manager); + +static int charger_manager_probe(struct platform_device *pdev) +{ + struct charger_desc *desc = dev_get_platdata(&pdev->dev); + struct charger_manager *cm; + int ret = 0, i = 0; + union power_supply_propval val; + + if (g_desc && !rtc_dev && g_desc->rtc_name) { + rtc_dev = rtc_class_open(g_desc->rtc_name); + if (IS_ERR_OR_NULL(rtc_dev)) { + rtc_dev = NULL; + dev_err(&pdev->dev, "Cannot get RTC %s.\n", + g_desc->rtc_name); + ret = -ENODEV; + goto err_alloc; + } + } + + if (!desc) { + dev_err(&pdev->dev, "No platform data (desc) found.\n"); + ret = -ENODEV; + goto err_alloc; + } + + cm = kzalloc(sizeof(struct charger_manager), GFP_KERNEL); + if (!cm) { + dev_err(&pdev->dev, "Cannot allocate memory.\n"); + ret = -ENOMEM; + goto err_alloc; + } + + /* Basic Values. Unspecified are Null or 0 */ + cm->dev = &pdev->dev; + cm->desc = kzalloc(sizeof(struct charger_desc), GFP_KERNEL); + if (!cm->desc) { + dev_err(&pdev->dev, "Cannot allocate memory.\n"); + ret = -ENOMEM; + goto err_alloc_desc; + } + memcpy(cm->desc, desc, sizeof(struct charger_desc)); + cm->last_temp_mC = INT_MIN; /* denotes "unmeasured, yet" */ + + if (!desc->charger_regulators || desc->num_charger_regulators < 1) { + ret = -EINVAL; + dev_err(&pdev->dev, "charger_regulators undefined.\n"); + goto err_no_charger; + } + + if (!desc->psy_charger_stat || !desc->psy_charger_stat[0]) { + dev_err(&pdev->dev, "No power supply defined.\n"); + ret = -EINVAL; + goto err_no_charger_stat; + } + + /* Counting index only */ + while (desc->psy_charger_stat[i]) + i++; + + cm->charger_stat = kzalloc(sizeof(struct power_supply *) * (i + 1), + GFP_KERNEL); + if (!cm->charger_stat) { + ret = -ENOMEM; + goto err_no_charger_stat; + } + + for (i = 0; desc->psy_charger_stat[i]; i++) { + cm->charger_stat[i] = power_supply_get_by_name( + desc->psy_charger_stat[i]); + if (!cm->charger_stat[i]) { + dev_err(&pdev->dev, "Cannot find power supply " + "\"%s\"\n", + desc->psy_charger_stat[i]); + ret = -ENODEV; + goto err_chg_stat; + } + } + + cm->fuel_gauge = power_supply_get_by_name(desc->psy_fuel_gauge); + if (!cm->fuel_gauge) { + dev_err(&pdev->dev, "Cannot find power supply \"%s\"\n", + desc->psy_fuel_gauge); + ret = -ENODEV; + goto err_chg_stat; + } + + if (desc->polling_interval_ms == 0 || + msecs_to_jiffies(desc->polling_interval_ms) <= CM_JIFFIES_SMALL) { + dev_err(&pdev->dev, "polling_interval_ms is too small\n"); + ret = -EINVAL; + goto err_chg_stat; + } + + if (!desc->temperature_out_of_range) { + dev_err(&pdev->dev, "there is no temperature_out_of_range\n"); + ret = -EINVAL; + goto err_chg_stat; + } + + platform_set_drvdata(pdev, cm); + + memcpy(&cm->charger_psy, &psy_default, sizeof(psy_default)); + + if (!desc->psy_name) { + strncpy(cm->psy_name_buf, psy_default.name, PSY_NAME_MAX); + } else { + strncpy(cm->psy_name_buf, desc->psy_name, PSY_NAME_MAX); + } + cm->charger_psy.name = cm->psy_name_buf; + + /* Allocate for psy properties because they may vary */ + cm->charger_psy.properties = kzalloc(sizeof(enum power_supply_property) + * (ARRAY_SIZE(default_charger_props) + + NUM_CHARGER_PSY_OPTIONAL), + GFP_KERNEL); + if (!cm->charger_psy.properties) { + dev_err(&pdev->dev, "Cannot allocate for psy properties.\n"); + ret = -ENOMEM; + goto err_chg_stat; + } + memcpy(cm->charger_psy.properties, default_charger_props, + sizeof(enum power_supply_property) * + ARRAY_SIZE(default_charger_props)); + cm->charger_psy.num_properties = psy_default.num_properties; + + /* Find which optional psy-properties are available */ + if (!cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_CHARGE_NOW, &val)) { + cm->charger_psy.properties[cm->charger_psy.num_properties] = + POWER_SUPPLY_PROP_CHARGE_NOW; + cm->charger_psy.num_properties++; + } + if (!cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_CURRENT_NOW, + &val)) { + cm->charger_psy.properties[cm->charger_psy.num_properties] = + POWER_SUPPLY_PROP_CURRENT_NOW; + cm->charger_psy.num_properties++; + } + + if (desc->measure_battery_temp) { + cm->charger_psy.properties[cm->charger_psy.num_properties] = + POWER_SUPPLY_PROP_TEMP; + cm->charger_psy.num_properties++; + } else { + cm->charger_psy.properties[cm->charger_psy.num_properties] = + POWER_SUPPLY_PROP_TEMP_AMBIENT; + cm->charger_psy.num_properties++; + } + + ret = power_supply_register(NULL, &cm->charger_psy); + if (ret) { + dev_err(&pdev->dev, "Cannot register charger-manager with" + " name \"%s\".\n", cm->charger_psy.name); + goto err_register; + } + + ret = regulator_bulk_get(&pdev->dev, desc->num_charger_regulators, + desc->charger_regulators); + if (ret) { + dev_err(&pdev->dev, "Cannot get charger regulators.\n"); + goto err_bulk_get; + } + + ret = try_charger_enable(cm, true); + if (ret) { + dev_err(&pdev->dev, "Cannot enable charger regulators\n"); + goto err_chg_enable; + } + + /* Add to the list */ + mutex_lock(&cm_list_mtx); + list_add(&cm->entry, &cm_list); + mutex_unlock(&cm_list_mtx); + + return 0; + +err_chg_enable: + regulator_bulk_free(desc->num_charger_regulators, + desc->charger_regulators); +err_bulk_get: + power_supply_unregister(&cm->charger_psy); +err_register: + kfree(cm->charger_psy.properties); +err_chg_stat: + kfree(cm->charger_stat); +err_no_charger_stat: +err_no_charger: + kfree(cm->desc); +err_alloc_desc: + kfree(cm); +err_alloc: + return ret; +} + +static int __devexit charger_manager_remove(struct platform_device *pdev) +{ + struct charger_manager *cm = platform_get_drvdata(pdev); + struct charger_desc *desc = cm->desc; + + /* Remove from the list */ + mutex_lock(&cm_list_mtx); + list_del(&cm->entry); + mutex_unlock(&cm_list_mtx); + + regulator_bulk_free(desc->num_charger_regulators, + desc->charger_regulators); + power_supply_unregister(&cm->charger_psy); + kfree(cm->charger_psy.properties); + kfree(cm->charger_stat); + kfree(cm->desc); + kfree(cm); + + return 0; +} + +static const struct platform_device_id charger_manager_id[] = { + { "charger-manager", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(platform, charger_manager_id); + +static int cm_suspend_prepare(struct device *dev) +{ + struct charger_manager *cm = dev_get_drvdata(dev); + + if (!cm_suspended) { + if (rtc_dev) { + struct rtc_time tmp; + unsigned long now; + + rtc_read_alarm(rtc_dev, &rtc_wkalarm_save); + rtc_read_time(rtc_dev, &tmp); + + if (rtc_wkalarm_save.enabled) { + rtc_tm_to_time(&rtc_wkalarm_save.time, + &rtc_wkalarm_save_time); + rtc_tm_to_time(&tmp, &now); + if (now > rtc_wkalarm_save_time) + rtc_wkalarm_save_time = 0; + } else { + rtc_wkalarm_save_time = 0; + } + } + cm_suspended = true; + } + + cm->status_save_ext_pwr_inserted = is_ext_pwr_online(cm); + cm->status_save_batt = is_batt_present(cm); + + if (!cm_rtc_set) { + cm_suspend_duration_ms = 0; + cm_rtc_set = cm_setup_timer(); + } + + return 0; +} + +static void cm_suspend_complete(struct device *dev) +{ + struct charger_manager *cm = dev_get_drvdata(dev); + + if (cm_suspended) { + if (rtc_dev) { + struct rtc_wkalrm tmp; + + rtc_read_alarm(rtc_dev, &tmp); + rtc_wkalarm_save.pending = tmp.pending; + rtc_set_alarm(rtc_dev, &rtc_wkalarm_save); + } + cm_suspended = false; + cm_rtc_set = false; + } + + uevent_notify(cm, NULL); +} + +static const struct dev_pm_ops charger_manager_pm = { + .prepare = cm_suspend_prepare, + .complete = cm_suspend_complete, +}; + +static struct platform_driver charger_manager_driver = { + .driver = { + .name = "charger-manager", + .owner = THIS_MODULE, + .pm = &charger_manager_pm, + }, + .probe = charger_manager_probe, + .remove = __devexit_p(charger_manager_remove), + .id_table = charger_manager_id, +}; + +static int __init charger_manager_init(void) +{ + return platform_driver_register(&charger_manager_driver); +} +late_initcall(charger_manager_init); + +static void __exit charger_manager_cleanup(void) +{ + platform_driver_unregister(&charger_manager_driver); +} +module_exit(charger_manager_cleanup); + +MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>"); +MODULE_DESCRIPTION("Charger Manager"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/collie_battery.c b/drivers/power/collie_battery.c new file mode 100644 index 00000000..74c6b23a --- /dev/null +++ b/drivers/power/collie_battery.c @@ -0,0 +1,390 @@ +/* + * 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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/power_supply.h> +#include <linux/delay.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include <linux/mfd/ucb1x00.h> + +#include <asm/mach/sharpsl_param.h> +#include <asm/mach-types.h> +#include <mach/collie.h> + +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\n"); + 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 gpio collie_batt_gpios[] = { + { COLLIE_GPIO_CO, GPIOF_IN, "main battery full" }, + { COLLIE_GPIO_MAIN_BAT_LOW, GPIOF_IN, "main battery low" }, + { COLLIE_GPIO_CHARGE_ON, GPIOF_OUT_INIT_LOW, "main charge on" }, + { COLLIE_GPIO_MBAT_ON, GPIOF_OUT_INIT_LOW, "main battery" }, + { COLLIE_GPIO_TMP_ON, GPIOF_OUT_INIT_LOW, "main battery temp" }, + { COLLIE_GPIO_BBAT_ON, GPIOF_OUT_INIT_LOW, "backup battery" }, +}; + +#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; + + if (!machine_is_collie()) + return -ENODEV; + + ucb = dev->ucb; + + ret = gpio_request_array(collie_batt_gpios, + ARRAY_SIZE(collie_batt_gpios)); + if (ret) + return ret; + + 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); + gpio_free_array(collie_batt_gpios, ARRAY_SIZE(collie_batt_gpios)); + return ret; +} + +static void __devexit collie_bat_remove(struct ucb1x00_dev *dev) +{ + 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); + gpio_free_array(collie_batt_gpios, ARRAY_SIZE(collie_batt_gpios)); +} + +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..3fd3e95d --- /dev/null +++ b/drivers/power/da9030_battery.c @@ -0,0 +1,595 @@ +/* + * Battery charger driver for Dialog Semiconductor DA9030 + * + * Copyright (C) 2008 Compulab, Ltd. + * Mike Rapoport <mike@compulab.co.il> + * + * 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 <linux/kernel.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/device.h> +#include <linux/workqueue.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/mfd/da903x.h> + +#include <linux/debugfs.h> +#include <linux/seq_file.h> + +#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, +}; + +module_platform_driver(da903x_battery_driver); + +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..a5f6a0ec --- /dev/null +++ b/drivers/power/da9052-battery.c @@ -0,0 +1,655 @@ +/* + * Batttery Driver for Dialog DA9052 PMICs + * + * Copyright(c) 2011 Dialog Semiconductor Ltd. + * + * Author: David Dajun Chen <dchen@diasemi.com> + * + * 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 <linux/delay.h> +#include <linux/freezer.h> +#include <linux/fs.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/timer.h> +#include <linux/uaccess.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> + +#include <linux/mfd/da9052/da9052.h> +#include <linux/mfd/da9052/pdata.h> +#include <linux/mfd/da9052/reg.h> + +/* STATIC CONFIGURATION */ +#define DA9052_BAT_CUTOFF_VOLT 2800 +#define DA9052_BAT_TSH 62000 +#define DA9052_BAT_LOW_CAP 4 +#define DA9052_AVG_SZ 4 +#define DA9052_VC_TBL_SZ 68 +#define DA9052_VC_TBL_REF_SZ 3 + +#define DA9052_ISET_USB_MASK 0x0F +#define DA9052_CHG_USB_ILIM_MASK 0x40 +#define DA9052_CHG_LIM_COLS 16 + +#define DA9052_MEAN(x, y) ((x + y) / 2) + +enum charger_type_enum { + DA9052_NOCHARGER = 1, + DA9052_CHARGER, +}; + +static const u16 da9052_chg_current_lim[2][DA9052_CHG_LIM_COLS] = { + {70, 80, 90, 100, 110, 120, 400, 450, + 500, 550, 600, 650, 700, 900, 1100, 1300}, + {80, 90, 100, 110, 120, 400, 450, 500, + 550, 600, 800, 1000, 1200, 1400, 1600, 1800}, +}; + +static const u16 vc_tbl_ref[3] = {10, 25, 40}; +/* Lookup table for voltage vs capacity */ +static u32 const vc_tbl[3][68][2] = { + /* For temperature 10 degree Celsius */ + { + {4082, 100}, {4036, 98}, + {4020, 96}, {4008, 95}, + {3997, 93}, {3983, 91}, + {3964, 90}, {3943, 88}, + {3926, 87}, {3912, 85}, + {3900, 84}, {3890, 82}, + {3881, 80}, {3873, 79}, + {3865, 77}, {3857, 76}, + {3848, 74}, {3839, 73}, + {3829, 71}, {3820, 70}, + {3811, 68}, {3802, 67}, + {3794, 65}, {3785, 64}, + {3778, 62}, {3770, 61}, + {3763, 59}, {3756, 58}, + {3750, 56}, {3744, 55}, + {3738, 53}, {3732, 52}, + {3727, 50}, {3722, 49}, + {3717, 47}, {3712, 46}, + {3708, 44}, {3703, 43}, + {3700, 41}, {3696, 40}, + {3693, 38}, {3691, 37}, + {3688, 35}, {3686, 34}, + {3683, 32}, {3681, 31}, + {3678, 29}, {3675, 28}, + {3672, 26}, {3669, 25}, + {3665, 23}, {3661, 22}, + {3656, 21}, {3651, 19}, + {3645, 18}, {3639, 16}, + {3631, 15}, {3622, 13}, + {3611, 12}, {3600, 10}, + {3587, 9}, {3572, 7}, + {3548, 6}, {3503, 5}, + {3420, 3}, {3268, 2}, + {2992, 1}, {2746, 0} + }, + /* For temperature 25 degree Celsius */ + { + {4102, 100}, {4065, 98}, + {4048, 96}, {4034, 95}, + {4021, 93}, {4011, 92}, + {4001, 90}, {3986, 88}, + {3968, 87}, {3952, 85}, + {3938, 84}, {3926, 82}, + {3916, 81}, {3908, 79}, + {3900, 77}, {3892, 76}, + {3883, 74}, {3874, 73}, + {3864, 71}, {3855, 70}, + {3846, 68}, {3836, 67}, + {3827, 65}, {3819, 64}, + {3810, 62}, {3801, 61}, + {3793, 59}, {3786, 58}, + {3778, 56}, {3772, 55}, + {3765, 53}, {3759, 52}, + {3754, 50}, {3748, 49}, + {3743, 47}, {3738, 46}, + {3733, 44}, {3728, 43}, + {3724, 41}, {3720, 40}, + {3716, 38}, {3712, 37}, + {3709, 35}, {3706, 34}, + {3703, 33}, {3701, 31}, + {3698, 30}, {3696, 28}, + {3693, 27}, {3690, 25}, + {3687, 24}, {3683, 22}, + {3680, 21}, {3675, 19}, + {3671, 18}, {3666, 17}, + {3660, 15}, {3654, 14}, + {3647, 12}, {3639, 11}, + {3630, 9}, {3621, 8}, + {3613, 6}, {3606, 5}, + {3597, 4}, {3582, 2}, + {3546, 1}, {2747, 0} + }, + /* For temperature 40 degree Celsius */ + { + {4114, 100}, {4081, 98}, + {4065, 96}, {4050, 95}, + {4036, 93}, {4024, 92}, + {4013, 90}, {4002, 88}, + {3990, 87}, {3976, 85}, + {3962, 84}, {3950, 82}, + {3939, 81}, {3930, 79}, + {3921, 77}, {3912, 76}, + {3902, 74}, {3893, 73}, + {3883, 71}, {3874, 70}, + {3865, 68}, {3856, 67}, + {3847, 65}, {3838, 64}, + {3829, 62}, {3820, 61}, + {3812, 59}, {3803, 58}, + {3795, 56}, {3787, 55}, + {3780, 53}, {3773, 52}, + {3767, 50}, {3761, 49}, + {3756, 47}, {3751, 46}, + {3746, 44}, {3741, 43}, + {3736, 41}, {3732, 40}, + {3728, 38}, {3724, 37}, + {3720, 35}, {3716, 34}, + {3713, 33}, {3710, 31}, + {3707, 30}, {3704, 28}, + {3701, 27}, {3698, 25}, + {3695, 24}, {3691, 22}, + {3686, 21}, {3681, 19}, + {3676, 18}, {3671, 17}, + {3666, 15}, {3661, 14}, + {3655, 12}, {3648, 11}, + {3640, 9}, {3632, 8}, + {3622, 6}, {3616, 5}, + {3611, 4}, {3604, 2}, + {3594, 1}, {2747, 0} + } +}; + +struct da9052_battery { + struct da9052 *da9052; + struct power_supply psy; + struct notifier_block nb; + int charger_type; + int status; + int health; +}; + +static inline int volt_reg_to_mV(int value) +{ + return ((value * 1000) / 512) + 2500; +} + +static inline int ichg_reg_to_mA(int value) +{ + return (value * 3900) / 1000; +} + +static int da9052_read_chgend_current(struct da9052_battery *bat, + int *current_mA) +{ + int ret; + + if (bat->status == POWER_SUPPLY_STATUS_DISCHARGING) + return -EINVAL; + + ret = da9052_reg_read(bat->da9052, DA9052_ICHG_END_REG); + if (ret < 0) + return ret; + + *current_mA = ichg_reg_to_mA(ret & DA9052_ICHGEND_ICHGEND); + + return 0; +} + +static int da9052_read_chg_current(struct da9052_battery *bat, int *current_mA) +{ + int ret; + + if (bat->status == POWER_SUPPLY_STATUS_DISCHARGING) + return -EINVAL; + + ret = da9052_reg_read(bat->da9052, DA9052_ICHG_AV_REG); + if (ret < 0) + return ret; + + *current_mA = ichg_reg_to_mA(ret & DA9052_ICHGAV_ICHGAV); + + return 0; +} + +static int da9052_bat_check_status(struct da9052_battery *bat, int *status) +{ + u8 v[2] = {0, 0}; + u8 bat_status; + u8 chg_end; + int ret; + int chg_current; + int chg_end_current; + bool dcinsel; + bool dcindet; + bool vbussel; + bool vbusdet; + bool dc; + bool vbus; + + ret = da9052_group_read(bat->da9052, DA9052_STATUS_A_REG, 2, v); + if (ret < 0) + return ret; + + bat_status = v[0]; + chg_end = v[1]; + + dcinsel = bat_status & DA9052_STATUSA_DCINSEL; + dcindet = bat_status & DA9052_STATUSA_DCINDET; + vbussel = bat_status & DA9052_STATUSA_VBUSSEL; + vbusdet = bat_status & DA9052_STATUSA_VBUSDET; + dc = dcinsel && dcindet; + vbus = vbussel && vbusdet; + + /* Preference to WALL(DCIN) charger unit */ + if (dc || vbus) { + bat->charger_type = DA9052_CHARGER; + + /* If charging end flag is set and Charging current is greater + * than charging end limit then battery is charging + */ + if ((chg_end & DA9052_STATUSB_CHGEND) != 0) { + ret = da9052_read_chg_current(bat, &chg_current); + if (ret < 0) + return ret; + ret = da9052_read_chgend_current(bat, &chg_end_current); + if (ret < 0) + return ret; + + if (chg_current >= chg_end_current) + bat->status = POWER_SUPPLY_STATUS_CHARGING; + else + bat->status = POWER_SUPPLY_STATUS_NOT_CHARGING; + } else { + /* If Charging end flag is cleared then battery is + * charging + */ + bat->status = POWER_SUPPLY_STATUS_CHARGING; + } + } else if (dcindet || vbusdet) { + bat->charger_type = DA9052_CHARGER; + bat->status = POWER_SUPPLY_STATUS_NOT_CHARGING; + } else { + bat->charger_type = DA9052_NOCHARGER; + bat->status = POWER_SUPPLY_STATUS_DISCHARGING; + } + + if (status != NULL) + *status = bat->status; + return 0; +} + +static int da9052_bat_read_volt(struct da9052_battery *bat, int *volt_mV) +{ + int volt; + + volt = da9052_adc_manual_read(bat->da9052, DA9052_ADC_MAN_MUXSEL_VBAT); + if (volt < 0) + return volt; + + *volt_mV = volt_reg_to_mV(volt); + + return 0; +} + +static int da9052_bat_check_presence(struct da9052_battery *bat, int *illegal) +{ + int bat_temp; + + bat_temp = da9052_adc_read_temp(bat->da9052); + if (bat_temp < 0) + return bat_temp; + + if (bat_temp > DA9052_BAT_TSH) + *illegal = 1; + else + *illegal = 0; + + return 0; +} + +static int da9052_bat_interpolate(int vbat_lower, int vbat_upper, + int level_lower, int level_upper, + int bat_voltage) +{ + int tmp; + + tmp = ((level_upper - level_lower) * 1000) / (vbat_upper - vbat_lower); + tmp = level_lower + (((bat_voltage - vbat_lower) * tmp) / 1000); + + return tmp; +} + +unsigned char da9052_determine_vc_tbl_index(unsigned char adc_temp) +{ + int i; + + if (adc_temp <= vc_tbl_ref[0]) + return 0; + + if (adc_temp > vc_tbl_ref[DA9052_VC_TBL_REF_SZ - 1]) + return DA9052_VC_TBL_REF_SZ - 1; + + for (i = 0; i < DA9052_VC_TBL_REF_SZ; i++) { + if ((adc_temp > vc_tbl_ref[i]) && + (adc_temp <= DA9052_MEAN(vc_tbl_ref[i], vc_tbl_ref[i + 1]))) + return i; + if ((adc_temp > DA9052_MEAN(vc_tbl_ref[i], vc_tbl_ref[i + 1])) + && (adc_temp <= vc_tbl_ref[i])) + return i + 1; + } +} + +static int da9052_bat_read_capacity(struct da9052_battery *bat, int *capacity) +{ + int adc_temp; + int bat_voltage; + int vbat_lower; + int vbat_upper; + int level_upper; + int level_lower; + int ret; + int flag; + int i = 0; + int j; + + ret = da9052_bat_read_volt(bat, &bat_voltage); + if (ret < 0) + return ret; + + adc_temp = da9052_adc_read_temp(bat->da9052); + if (adc_temp < 0) + return adc_temp; + + i = da9052_determine_vc_tbl_index(adc_temp); + + if (bat_voltage >= vc_tbl[i][0][0]) { + *capacity = 100; + return 0; + } + if (bat_voltage <= vc_tbl[i][DA9052_VC_TBL_SZ - 1][0]) { + *capacity = 0; + return 0; + } + flag = 0; + + for (j = 0; j < (DA9052_VC_TBL_SZ-1); j++) { + if ((bat_voltage <= vc_tbl[i][j][0]) && + (bat_voltage >= vc_tbl[i][j + 1][0])) { + vbat_upper = vc_tbl[i][j][0]; + vbat_lower = vc_tbl[i][j + 1][0]; + level_upper = vc_tbl[i][j][1]; + level_lower = vc_tbl[i][j + 1][1]; + flag = 1; + break; + } + } + if (!flag) + return -EIO; + + *capacity = da9052_bat_interpolate(vbat_lower, vbat_upper, level_lower, + level_upper, bat_voltage); + + return 0; +} + +static int da9052_bat_check_health(struct da9052_battery *bat, int *health) +{ + int ret; + int bat_illegal; + int capacity; + + ret = da9052_bat_check_presence(bat, &bat_illegal); + if (ret < 0) + return ret; + + if (bat_illegal) { + bat->health = POWER_SUPPLY_HEALTH_UNKNOWN; + return 0; + } + + if (bat->health != POWER_SUPPLY_HEALTH_OVERHEAT) { + ret = da9052_bat_read_capacity(bat, &capacity); + if (ret < 0) + return ret; + if (capacity < DA9052_BAT_LOW_CAP) + bat->health = POWER_SUPPLY_HEALTH_DEAD; + else + bat->health = POWER_SUPPLY_HEALTH_GOOD; + } + + *health = bat->health; + + return 0; +} + +static irqreturn_t da9052_bat_irq(int irq, void *data) +{ + struct da9052_battery *bat = data; + + irq -= bat->da9052->irq_base; + + if (irq == DA9052_IRQ_CHGEND) + bat->status = POWER_SUPPLY_STATUS_FULL; + else + da9052_bat_check_status(bat, NULL); + + if (irq == DA9052_IRQ_CHGEND || irq == DA9052_IRQ_DCIN || + irq == DA9052_IRQ_VBUS || irq == DA9052_IRQ_TBAT) { + power_supply_changed(&bat->psy); + } + + return IRQ_HANDLED; +} + +static int da9052_USB_current_notifier(struct notifier_block *nb, + unsigned long events, void *data) +{ + u8 row; + u8 col; + int *current_mA = data; + int ret; + struct da9052_battery *bat = container_of(nb, struct da9052_battery, + nb); + + if (bat->status == POWER_SUPPLY_STATUS_DISCHARGING) + return -EPERM; + + ret = da9052_reg_read(bat->da9052, DA9052_CHGBUCK_REG); + if (ret & DA9052_CHG_USB_ILIM_MASK) + return -EPERM; + + if (bat->da9052->chip_id == DA9052) + row = 0; + else + row = 1; + + if (*current_mA < da9052_chg_current_lim[row][0] || + *current_mA > da9052_chg_current_lim[row][DA9052_CHG_LIM_COLS - 1]) + return -EINVAL; + + for (col = 0; col <= DA9052_CHG_LIM_COLS - 1 ; col++) { + if (*current_mA <= da9052_chg_current_lim[row][col]) + break; + } + + return da9052_reg_update(bat->da9052, DA9052_ISET_REG, + DA9052_ISET_USB_MASK, col); +} + +static int da9052_bat_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret; + int illegal; + struct da9052_battery *bat = container_of(psy, struct da9052_battery, + psy); + + ret = da9052_bat_check_presence(bat, &illegal); + if (ret < 0) + return ret; + + if (illegal && psp != POWER_SUPPLY_PROP_PRESENT) + return -ENODEV; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + ret = da9052_bat_check_status(bat, &val->intval); + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = + (bat->charger_type == DA9052_NOCHARGER) ? 0 : 1; + break; + case POWER_SUPPLY_PROP_PRESENT: + ret = da9052_bat_check_presence(bat, &val->intval); + break; + case POWER_SUPPLY_PROP_HEALTH: + ret = da9052_bat_check_health(bat, &val->intval); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = DA9052_BAT_CUTOFF_VOLT * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + ret = da9052_bat_read_volt(bat, &val->intval); + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + ret = da9052_read_chg_current(bat, &val->intval); + break; + case POWER_SUPPLY_PROP_CAPACITY: + ret = da9052_bat_read_capacity(bat, &val->intval); + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = da9052_adc_read_temp(bat->da9052); + ret = val->intval; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + default: + return -EINVAL; + } + return ret; +} + +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_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 struct power_supply template_battery = { + .name = "da9052-bat", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = da9052_bat_props, + .num_properties = ARRAY_SIZE(da9052_bat_props), + .get_property = da9052_bat_get_property, +}; + +static const char *const da9052_bat_irqs[] = { + "BATT TEMP", + "DCIN DET", + "DCIN REM", + "VBUS DET", + "VBUS REM", + "CHG END", +}; + +static s32 __devinit da9052_bat_probe(struct platform_device *pdev) +{ + struct da9052_pdata *pdata; + struct da9052_battery *bat; + int ret; + int irq; + int i; + + bat = kzalloc(sizeof(struct da9052_battery), GFP_KERNEL); + if (!bat) + return -ENOMEM; + + bat->da9052 = dev_get_drvdata(pdev->dev.parent); + bat->psy = template_battery; + bat->charger_type = DA9052_NOCHARGER; + bat->status = POWER_SUPPLY_STATUS_UNKNOWN; + bat->health = POWER_SUPPLY_HEALTH_UNKNOWN; + bat->nb.notifier_call = da9052_USB_current_notifier; + + pdata = bat->da9052->dev->platform_data; + if (pdata != NULL && pdata->use_for_apm) + bat->psy.use_for_apm = pdata->use_for_apm; + else + bat->psy.use_for_apm = 1; + + for (i = 0; i < ARRAY_SIZE(da9052_bat_irqs); i++) { + irq = platform_get_irq_byname(pdev, da9052_bat_irqs[i]); + ret = request_threaded_irq(bat->da9052->irq_base + irq, + NULL, da9052_bat_irq, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + da9052_bat_irqs[i], bat); + if (ret != 0) { + dev_err(bat->da9052->dev, + "DA9052 failed to request %s IRQ %d: %d\n", + da9052_bat_irqs[i], irq, ret); + goto err; + } + } + + ret = power_supply_register(&pdev->dev, &bat->psy); + if (ret) + goto err; + + platform_set_drvdata(pdev, bat); + return 0; + +err: + for (; i >= 0; i--) { + irq = platform_get_irq_byname(pdev, da9052_bat_irqs[i]); + free_irq(bat->da9052->irq_base + irq, bat); + } + kfree(bat); + return ret; +} +static int __devexit da9052_bat_remove(struct platform_device *pdev) +{ + int i; + int irq; + struct da9052_battery *bat = platform_get_drvdata(pdev); + + for (i = 0; i < ARRAY_SIZE(da9052_bat_irqs); i++) { + irq = platform_get_irq_byname(pdev, da9052_bat_irqs[i]); + free_irq(bat->da9052->irq_base + irq, bat); + } + power_supply_unregister(&bat->psy); + kfree(bat); + + return 0; +} + +static struct platform_driver da9052_bat_driver = { + .probe = da9052_bat_probe, + .remove = __devexit_p(da9052_bat_remove), + .driver = { + .name = "da9052-bat", + .owner = THIS_MODULE, + }, +}; +module_platform_driver(da9052_bat_driver); + +MODULE_DESCRIPTION("DA9052 BAT Device Driver"); +MODULE_AUTHOR("David Dajun Chen <dchen@diasemi.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:da9052-bat"); diff --git a/drivers/power/ds2760_battery.c b/drivers/power/ds2760_battery.c new file mode 100644 index 00000000..076e211a --- /dev/null +++ b/drivers/power/ds2760_battery.c @@ -0,0 +1,650 @@ +/* + * 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 <cbou@mail.ru> + * February 2007 + * + * Matt Reimer <mreimer@vpop.net> + * April 2004, 2005, 2007 + * + * Szabolcs Gyurko <szabolcs.gyurko@tlt.hu> + * September 2004 + */ + +#include <linux/module.h> +#include <linux/param.h> +#include <linux/jiffies.h> +#include <linux/workqueue.h> +#include <linux/pm.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> + +#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 bool 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 */ +#ifdef CONFIG_MACH_H4700 + 0, + 3600, /* HP iPAQ hx4700 3.7V 3600mAh (359114-001) */ +#endif +}; + +/* 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, +}; + +module_platform_driver(ds2760_battery_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Szabolcs Gyurko <szabolcs.gyurko@tlt.hu>, " + "Matt Reimer <mreimer@vpop.net>, " + "Anton Vorontsov <cbou@mail.ru>"); +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..de31cae1 --- /dev/null +++ b/drivers/power/ds2780_battery.c @@ -0,0 +1,857 @@ +/* + * 1-wire client/driver for the Maxim/Dallas DS2780 Stand-Alone Fuel Gauge IC + * + * Copyright (C) 2010 Indesign, LLC + * + * Author: Clifton Barnes <cabarnes@indesign-llc.com> + * + * 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 <linux/module.h> +#include <linux/slab.h> +#include <linux/param.h> +#include <linux/pm.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/idr.h> + +#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; +} + +static struct platform_driver ds2780_battery_driver = { + .driver = { + .name = "ds2780-battery", + }, + .probe = ds2780_battery_probe, + .remove = __devexit_p(ds2780_battery_remove), +}; + +module_platform_driver(ds2780_battery_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Clifton Barnes <cabarnes@indesign-llc.com>"); +MODULE_DESCRIPTION("Maxim/Dallas DS2780 Stand-Alone Fuel Gauage IC driver"); +MODULE_ALIAS("platform:ds2780-battery"); diff --git a/drivers/power/ds2781_battery.c b/drivers/power/ds2781_battery.c new file mode 100644 index 00000000..ca0d653d --- /dev/null +++ b/drivers/power/ds2781_battery.c @@ -0,0 +1,874 @@ +/* + * 1-wire client/driver for the Maxim/Dallas DS2781 Stand-Alone Fuel Gauge IC + * + * Author: Renata Sayakhova <renata@oktetlabs.ru> + * + * Based on ds2780_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 <linux/module.h> +#include <linux/slab.h> +#include <linux/param.h> +#include <linux/pm.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/idr.h> + +#include "../w1/w1.h" +#include "../w1/slaves/w1_ds2781.h" + +/* Current unit measurement in uA for a 1 milli-ohm sense resistor */ +#define DS2781_CURRENT_UNITS 1563 +/* Charge unit measurement in uAh for a 1 milli-ohm sense resistor */ +#define DS2781_CHARGE_UNITS 6250 +/* Number of bytes in user EEPROM space */ +#define DS2781_USER_EEPROM_SIZE (DS2781_EEPROM_BLOCK0_END - \ + DS2781_EEPROM_BLOCK0_START + 1) +/* Number of bytes in parameter EEPROM space */ +#define DS2781_PARAM_EEPROM_SIZE (DS2781_EEPROM_BLOCK1_END - \ + DS2781_EEPROM_BLOCK1_START + 1) + +struct ds2781_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[] = "DS2781"; +static const char manufacturer[] = "Maxim/Dallas"; + +static inline struct ds2781_device_info * +to_ds2781_device_info(struct power_supply *psy) +{ + return container_of(psy, struct ds2781_device_info, bat); +} + +static inline struct power_supply *to_power_supply(struct device *dev) +{ + return dev_get_drvdata(dev); +} + +static inline int ds2781_battery_io(struct ds2781_device_info *dev_info, + char *buf, int addr, size_t count, int io) +{ + if (dev_info->mutex_holder == current) + return w1_ds2781_io_nolock(dev_info->w1_dev, buf, addr, + count, io); + else + return w1_ds2781_io(dev_info->w1_dev, buf, addr, count, io); +} + +int w1_ds2781_read(struct ds2781_device_info *dev_info, char *buf, + int addr, size_t count) +{ + return ds2781_battery_io(dev_info, buf, addr, count, 0); +} + +static inline int ds2781_read8(struct ds2781_device_info *dev_info, u8 *val, + int addr) +{ + return ds2781_battery_io(dev_info, val, addr, sizeof(u8), 0); +} + +static int ds2781_read16(struct ds2781_device_info *dev_info, s16 *val, + int addr) +{ + int ret; + u8 raw[2]; + + ret = ds2781_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 ds2781_read_block(struct ds2781_device_info *dev_info, + u8 *val, int addr, size_t count) +{ + return ds2781_battery_io(dev_info, val, addr, count, 0); +} + +static inline int ds2781_write(struct ds2781_device_info *dev_info, u8 *val, + int addr, size_t count) +{ + return ds2781_battery_io(dev_info, val, addr, count, 1); +} + +static inline int ds2781_store_eeprom(struct device *dev, int addr) +{ + return w1_ds2781_eeprom_cmd(dev, addr, W1_DS2781_COPY_DATA); +} + +static inline int ds2781_recall_eeprom(struct device *dev, int addr) +{ + return w1_ds2781_eeprom_cmd(dev, addr, W1_DS2781_RECALL_DATA); +} + +static int ds2781_save_eeprom(struct ds2781_device_info *dev_info, int reg) +{ + int ret; + + ret = ds2781_store_eeprom(dev_info->w1_dev, reg); + if (ret < 0) + return ret; + + ret = ds2781_recall_eeprom(dev_info->w1_dev, reg); + if (ret < 0) + return ret; + + return 0; +} + +/* Set sense resistor value in mhos */ +static int ds2781_set_sense_register(struct ds2781_device_info *dev_info, + u8 conductance) +{ + int ret; + + ret = ds2781_write(dev_info, &conductance, + DS2781_RSNSP, sizeof(u8)); + if (ret < 0) + return ret; + + return ds2781_save_eeprom(dev_info, DS2781_RSNSP); +} + +/* Get RSGAIN value from 0 to 1.999 in steps of 0.001 */ +static int ds2781_get_rsgain_register(struct ds2781_device_info *dev_info, + u16 *rsgain) +{ + return ds2781_read16(dev_info, rsgain, DS2781_RSGAIN_MSB); +} + +/* Set RSGAIN value from 0 to 1.999 in steps of 0.001 */ +static int ds2781_set_rsgain_register(struct ds2781_device_info *dev_info, + u16 rsgain) +{ + int ret; + u8 raw[] = {rsgain >> 8, rsgain & 0xFF}; + + ret = ds2781_write(dev_info, raw, + DS2781_RSGAIN_MSB, sizeof(raw)); + if (ret < 0) + return ret; + + return ds2781_save_eeprom(dev_info, DS2781_RSGAIN_MSB); +} + +static int ds2781_get_voltage(struct ds2781_device_info *dev_info, + int *voltage_uV) +{ + int ret; + char val[2]; + int voltage_raw; + + ret = w1_ds2781_read(dev_info, val, DS2781_VOLT_MSB, 2 * sizeof(u8)); + if (ret < 0) + return ret; + /* + * 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 + */ + voltage_raw = (val[0] << 3) | + (val[1] >> 5); + + /* DS2781 reports voltage in units of 9.76mV, but the battery class + * reports in units of uV, so convert by multiplying by 9760. */ + *voltage_uV = voltage_raw * 9760; + + return 0; +} + +static int ds2781_get_temperature(struct ds2781_device_info *dev_info, + int *temp) +{ + int ret; + char val[2]; + int temp_raw; + + ret = w1_ds2781_read(dev_info, val, DS2781_TEMP_MSB, 2 * sizeof(u8)); + if (ret < 0) + return ret; + /* + * 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 + */ + temp_raw = ((val[0]) << 3) | + (val[1] >> 5); + *temp = temp_raw + (temp_raw / 4); + + return 0; +} + +static int ds2781_get_current(struct ds2781_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 = ds2781_read8(dev_info, &sense_res_raw, DS2781_RSNSP); + 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 = DS2781_CURRENT_MSB; + else if (type == CURRENT_AVG) + reg_msb = DS2781_IAVG_MSB; + 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 = ds2781_read16(dev_info, ¤t_raw, reg_msb); + if (ret < 0) + return ret; + + *current_uA = current_raw * (DS2781_CURRENT_UNITS / sense_res); + return 0; +} + +static int ds2781_get_accumulated_current(struct ds2781_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 = ds2781_read8(dev_info, &sense_res_raw, DS2781_RSNSP); + 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; + + /* + * 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 = ds2781_read16(dev_info, ¤t_raw, DS2781_ACR_MSB); + if (ret < 0) + return ret; + + *accumulated_current = current_raw * (DS2781_CHARGE_UNITS / sense_res); + return 0; +} + +static int ds2781_get_capacity(struct ds2781_device_info *dev_info, + int *capacity) +{ + int ret; + u8 raw; + + ret = ds2781_read8(dev_info, &raw, DS2781_RARC); + if (ret < 0) + return ret; + + *capacity = raw; + return 0; +} + +static int ds2781_get_status(struct ds2781_device_info *dev_info, int *status) +{ + int ret, current_uA, capacity; + + ret = ds2781_get_current(dev_info, CURRENT_NOW, ¤t_uA); + if (ret < 0) + return ret; + + ret = ds2781_get_capacity(dev_info, &capacity); + if (ret < 0) + return ret; + + if (power_supply_am_i_supplied(&dev_info->bat)) { + if (capacity == 100) + *status = POWER_SUPPLY_STATUS_FULL; + else if (current_uA > 50000) + *status = POWER_SUPPLY_STATUS_CHARGING; + else + *status = POWER_SUPPLY_STATUS_NOT_CHARGING; + } else { + *status = POWER_SUPPLY_STATUS_DISCHARGING; + } + return 0; +} + +static int ds2781_get_charge_now(struct ds2781_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 = ds2781_read16(dev_info, &charge_raw, DS2781_RAAC_MSB); + if (ret < 0) + return ret; + + *charge_now = charge_raw * 1600; + return 0; +} + +static int ds2781_get_control_register(struct ds2781_device_info *dev_info, + u8 *control_reg) +{ + return ds2781_read8(dev_info, control_reg, DS2781_CONTROL); +} + +static int ds2781_set_control_register(struct ds2781_device_info *dev_info, + u8 control_reg) +{ + int ret; + + ret = ds2781_write(dev_info, &control_reg, + DS2781_CONTROL, sizeof(u8)); + if (ret < 0) + return ret; + + return ds2781_save_eeprom(dev_info, DS2781_CONTROL); +} + +static int ds2781_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret = 0; + struct ds2781_device_info *dev_info = to_ds2781_device_info(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = ds2781_get_voltage(dev_info, &val->intval); + break; + + case POWER_SUPPLY_PROP_TEMP: + ret = ds2781_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 = ds2781_get_current(dev_info, CURRENT_NOW, &val->intval); + break; + + case POWER_SUPPLY_PROP_CURRENT_AVG: + ret = ds2781_get_current(dev_info, CURRENT_AVG, &val->intval); + break; + + case POWER_SUPPLY_PROP_STATUS: + ret = ds2781_get_status(dev_info, &val->intval); + break; + + case POWER_SUPPLY_PROP_CAPACITY: + ret = ds2781_get_capacity(dev_info, &val->intval); + break; + + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + ret = ds2781_get_accumulated_current(dev_info, &val->intval); + break; + + case POWER_SUPPLY_PROP_CHARGE_NOW: + ret = ds2781_get_charge_now(dev_info, &val->intval); + break; + + default: + ret = -EINVAL; + } + + return ret; +} + +static enum power_supply_property ds2781_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 ds2781_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 ds2781_device_info *dev_info = to_ds2781_device_info(psy); + + /* Get power mode */ + ret = ds2781_get_control_register(dev_info, &control_reg); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", + !!(control_reg & DS2781_CONTROL_PMOD)); +} + +static ssize_t ds2781_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 ds2781_device_info *dev_info = to_ds2781_device_info(psy); + + /* Set power mode */ + ret = ds2781_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 |= DS2781_CONTROL_PMOD; + else + control_reg &= ~DS2781_CONTROL_PMOD; + + ret = ds2781_set_control_register(dev_info, control_reg); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t ds2781_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 ds2781_device_info *dev_info = to_ds2781_device_info(psy); + + ret = ds2781_read8(dev_info, &sense_resistor, DS2781_RSNSP); + if (ret < 0) + return ret; + + ret = sprintf(buf, "%d\n", sense_resistor); + return ret; +} + +static ssize_t ds2781_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 ds2781_device_info *dev_info = to_ds2781_device_info(psy); + + ret = kstrtou8(buf, 0, &new_setting); + if (ret < 0) + return ret; + + ret = ds2781_set_sense_register(dev_info, new_setting); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t ds2781_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 ds2781_device_info *dev_info = to_ds2781_device_info(psy); + + ret = ds2781_get_rsgain_register(dev_info, &rsgain); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", rsgain); +} + +static ssize_t ds2781_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 ds2781_device_info *dev_info = to_ds2781_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 = ds2781_set_rsgain_register(dev_info, new_setting); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t ds2781_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 ds2781_device_info *dev_info = to_ds2781_device_info(psy); + + ret = ds2781_read8(dev_info, &sfr, DS2781_SFR); + if (ret < 0) + return ret; + + ret = sprintf(buf, "%d\n", sfr & DS2781_SFR_PIOSC); + return ret; +} + +static ssize_t ds2781_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 ds2781_device_info *dev_info = to_ds2781_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 = ds2781_write(dev_info, &new_setting, + DS2781_SFR, sizeof(u8)); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t ds2781_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 ds2781_device_info *dev_info = to_ds2781_device_info(psy); + + count = min_t(loff_t, count, + DS2781_EEPROM_BLOCK1_END - + DS2781_EEPROM_BLOCK1_START + 1 - off); + + return ds2781_read_block(dev_info, buf, + DS2781_EEPROM_BLOCK1_START + off, count); +} + +static ssize_t ds2781_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 ds2781_device_info *dev_info = to_ds2781_device_info(psy); + int ret; + + count = min_t(loff_t, count, + DS2781_EEPROM_BLOCK1_END - + DS2781_EEPROM_BLOCK1_START + 1 - off); + + ret = ds2781_write(dev_info, buf, + DS2781_EEPROM_BLOCK1_START + off, count); + if (ret < 0) + return ret; + + ret = ds2781_save_eeprom(dev_info, DS2781_EEPROM_BLOCK1_START); + if (ret < 0) + return ret; + + return count; +} + +static struct bin_attribute ds2781_param_eeprom_bin_attr = { + .attr = { + .name = "param_eeprom", + .mode = S_IRUGO | S_IWUSR, + }, + .size = DS2781_EEPROM_BLOCK1_END - DS2781_EEPROM_BLOCK1_START + 1, + .read = ds2781_read_param_eeprom_bin, + .write = ds2781_write_param_eeprom_bin, +}; + +static ssize_t ds2781_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 ds2781_device_info *dev_info = to_ds2781_device_info(psy); + + count = min_t(loff_t, count, + DS2781_EEPROM_BLOCK0_END - + DS2781_EEPROM_BLOCK0_START + 1 - off); + + return ds2781_read_block(dev_info, buf, + DS2781_EEPROM_BLOCK0_START + off, count); + +} + +static ssize_t ds2781_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 ds2781_device_info *dev_info = to_ds2781_device_info(psy); + int ret; + + count = min_t(loff_t, count, + DS2781_EEPROM_BLOCK0_END - + DS2781_EEPROM_BLOCK0_START + 1 - off); + + ret = ds2781_write(dev_info, buf, + DS2781_EEPROM_BLOCK0_START + off, count); + if (ret < 0) + return ret; + + ret = ds2781_save_eeprom(dev_info, DS2781_EEPROM_BLOCK0_START); + if (ret < 0) + return ret; + + return count; +} + +static struct bin_attribute ds2781_user_eeprom_bin_attr = { + .attr = { + .name = "user_eeprom", + .mode = S_IRUGO | S_IWUSR, + }, + .size = DS2781_EEPROM_BLOCK0_END - DS2781_EEPROM_BLOCK0_START + 1, + .read = ds2781_read_user_eeprom_bin, + .write = ds2781_write_user_eeprom_bin, +}; + +static DEVICE_ATTR(pmod_enabled, S_IRUGO | S_IWUSR, ds2781_get_pmod_enabled, + ds2781_set_pmod_enabled); +static DEVICE_ATTR(sense_resistor_value, S_IRUGO | S_IWUSR, + ds2781_get_sense_resistor_value, ds2781_set_sense_resistor_value); +static DEVICE_ATTR(rsgain_setting, S_IRUGO | S_IWUSR, ds2781_get_rsgain_setting, + ds2781_set_rsgain_setting); +static DEVICE_ATTR(pio_pin, S_IRUGO | S_IWUSR, ds2781_get_pio_pin, + ds2781_set_pio_pin); + + +static struct attribute *ds2781_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 ds2781_attr_group = { + .attrs = ds2781_attributes, +}; + +static int __devinit ds2781_battery_probe(struct platform_device *pdev) +{ + int ret = 0; + struct ds2781_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 = ds2781_battery_props; + dev_info->bat.num_properties = ARRAY_SIZE(ds2781_battery_props); + dev_info->bat.get_property = ds2781_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, &ds2781_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, + &ds2781_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, + &ds2781_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, + &ds2781_param_eeprom_bin_attr); +fail_remove_group: + sysfs_remove_group(&dev_info->bat.dev->kobj, &ds2781_attr_group); +fail_unregister: + power_supply_unregister(&dev_info->bat); +fail_free_info: + kfree(dev_info); +fail: + return ret; +} + +static int __devexit ds2781_battery_remove(struct platform_device *pdev) +{ + struct ds2781_device_info *dev_info = platform_get_drvdata(pdev); + + dev_info->mutex_holder = current; + + /* remove attributes */ + sysfs_remove_group(&dev_info->bat.dev->kobj, &ds2781_attr_group); + + power_supply_unregister(&dev_info->bat); + + kfree(dev_info); + return 0; +} + +static struct platform_driver ds2781_battery_driver = { + .driver = { + .name = "ds2781-battery", + }, + .probe = ds2781_battery_probe, + .remove = __devexit_p(ds2781_battery_remove), +}; + +static int __init ds2781_battery_init(void) +{ + return platform_driver_register(&ds2781_battery_driver); +} + +static void __exit ds2781_battery_exit(void) +{ + platform_driver_unregister(&ds2781_battery_driver); +} + +module_init(ds2781_battery_init); +module_exit(ds2781_battery_exit); + + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Renata Sayakhova <renata@oktetlabs.ru>"); +MODULE_DESCRIPTION("Maxim/Dallas DS2781 Stand-Alone Fuel Gauage IC driver"); +MODULE_ALIAS("platform:ds2781-battery"); + diff --git a/drivers/power/ds2782_battery.c b/drivers/power/ds2782_battery.c new file mode 100644 index 00000000..6bb6e2f5 --- /dev/null +++ b/drivers/power/ds2782_battery.c @@ -0,0 +1,410 @@ +/* + * 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 <vilensky@compulab.co.il> + * + * 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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/swab.h> +#include <linux/i2c.h> +#include <linux/idr.h> +#include <linux/power_supply.h> +#include <linux/slab.h> +#include <linux/ds2782_battery.h> + +#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, +}; +module_i2c_driver(ds278x_battery_driver); + +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..8672c917 --- /dev/null +++ b/drivers/power/gpio-charger.c @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de> + * 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 <linux/device.h> +#include <linux/gpio.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/slab.h> + +#include <linux/power/gpio-charger.h> + +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 < 0) + 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, + }, +}; + +module_platform_driver(gpio_charger_driver); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +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..d0964970 --- /dev/null +++ b/drivers/power/intel_mid_battery.c @@ -0,0 +1,786 @@ +/* + * 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 <nithish.mahalingam@intel.com> + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/jiffies.h> +#include <linux/param.h> +#include <linux/device.h> +#include <linux/spi/spi.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> + +#include <asm/intel_scu_ipc.h> + +#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 0x86 + +#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_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; + if (r8 & PMIC_BATT_CHR_SDCLMT_MASK) { + /* PMIC will change charging current automatically */ + pmic_battery_log_event(BATT_EVENT_DCLMT_EXCPT); + } + } + } + + /* 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), +}; + +module_platform_driver(platform_pmic_battery_driver); + +MODULE_AUTHOR("Nithish Mahalingam <nithish.mahalingam@intel.com>"); +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..39eb50f3 --- /dev/null +++ b/drivers/power/isp1704_charger.c @@ -0,0 +1,513 @@ +/* + * 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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/device.h> +#include <linux/sysfs.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/delay.h> + +#include <linux/usb/otg.h> +#include <linux/usb/ulpi.h> +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/power/isp1704_charger.h> + +/* 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 usb_phy *phy; + 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; +}; + +static inline int isp1704_read(struct isp1704_charger *isp, u32 reg) +{ + return usb_phy_io_read(isp->phy, reg); +} + +static inline int isp1704_write(struct isp1704_charger *isp, u32 val, u32 reg) +{ + return usb_phy_io_write(isp->phy, val, reg); +} + +/* + * 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 && 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 = isp1704_read(isp, ULPI_FUNC_CTRL); + otg_ctrl = isp1704_read(isp, ULPI_OTG_CTRL); + + /* disable pulldowns */ + reg = ULPI_OTG_CTRL_DM_PULLDOWN | ULPI_OTG_CTRL_DP_PULLDOWN; + isp1704_write(isp, ULPI_CLR(ULPI_OTG_CTRL), reg); + + /* full speed */ + isp1704_write(isp, ULPI_CLR(ULPI_FUNC_CTRL), + ULPI_FUNC_CTRL_XCVRSEL_MASK); + isp1704_write(isp, 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; + isp1704_write(isp, ULPI_SET(ULPI_FUNC_CTRL), reg); + usleep_range(1000, 2000); + + reg = isp1704_read(isp, ULPI_DEBUG); + if ((reg & 3) != 3) + type = POWER_SUPPLY_TYPE_USB_CDP; + + /* recover original state */ + isp1704_write(isp, ULPI_FUNC_CTRL, func_ctrl); + isp1704_write(isp, 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 = isp1704_read(isp, ULPI_FUNC_CTRL); + r |= ULPI_FUNC_CTRL_RESET; + isp1704_write(isp, ULPI_FUNC_CTRL, r); + usleep_range(1000, 2000); + + /* Set normal mode */ + r &= ~(ULPI_FUNC_CTRL_RESET | ULPI_FUNC_CTRL_OPMODE_MASK); + isp1704_write(isp, ULPI_FUNC_CTRL, r); + + /* Clear the DP and DM pull-down bits */ + r = ULPI_OTG_CTRL_DP_PULLDOWN | ULPI_OTG_CTRL_DM_PULLDOWN; + isp1704_write(isp, 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; + isp1704_write(isp, ULPI_SET(ULPI_FUNC_CTRL), r); + usleep_range(1000, 2000); + + /* Read the line state */ + if (!isp1704_read(isp, ULPI_DEBUG)) { + /* Disable strong pull-up on DP (1.5K) */ + isp1704_write(isp, 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 */ + isp1704_write(isp, ULPI_SET(ISP1704_PWR_CTRL), + ISP1704_PWR_CTRL_DP_WKPU_EN); + + /* Disable strong pull-up on DP (1.5K) */ + isp1704_write(isp, ULPI_CLR(ULPI_FUNC_CTRL), + ULPI_FUNC_CTRL_TERMSELECT); + + /* Enable weak pull-down resistor on DM */ + isp1704_write(isp, ULPI_SET(ULPI_OTG_CTRL), + ULPI_OTG_CTRL_DM_PULLDOWN); + + /* It's a charger if the line states are clear */ + if (!(isp1704_read(isp, ULPI_DEBUG))) + ret = 1; + + /* Disable weak pull-up resistor on DP */ + isp1704_write(isp, 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 = isp1704_read(isp, ISP1704_PWR_CTRL); + + /* set SW control bit in PWR_CTRL register */ + isp1704_write(isp, ISP1704_PWR_CTRL, + ISP1704_PWR_CTRL_SWCTRL); + + /* enable manual charger detection */ + isp1704_write(isp, 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 (isp1704_read(isp, ISP1704_PWR_CTRL) + & ISP1704_PWR_CTRL_VDAT_DET) { + ret = isp1704_charger_verify(isp); + break; + } + } while (!time_after(jiffies, timeout) && isp->online); + + /* recover original state */ + isp1704_write(isp, 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->phy->otg->gadget) + usb_gadget_connect(isp->phy->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->phy->otg->gadget) + usb_gadget_disconnect(isp->phy->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 = isp1704_write(isp, ULPI_SCRATCH, 0xaa); + if (ret < 0) + return ret; + + ret = isp1704_read(isp, ULPI_SCRATCH); + if (ret < 0) + return ret; + + if (ret != 0xaa) + return -ENODEV; + + /* Verify the product and vendor id matches */ + vendor = isp1704_read(isp, ULPI_VENDOR_ID_LOW); + vendor |= isp1704_read(isp, ULPI_VENDOR_ID_HIGH) << 8; + if (vendor != NXP_VENDOR_ID) + return -ENODEV; + + product = isp1704_read(isp, ULPI_PRODUCT_ID_LOW); + product |= isp1704_read(isp, 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->phy = usb_get_transceiver(); + if (!isp->phy) + 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 usb notifications to be + * made atomically in the future. + */ + INIT_WORK(&isp->work, isp1704_charger_work); + + isp->nb.notifier_call = isp1704_notifier_call; + + ret = usb_register_notifier(isp->phy, &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->phy->otg->gadget) + usb_gadget_disconnect(isp->phy->otg->gadget); + + /* Detect charger if VBUS is valid (the cable was already plugged). */ + ret = isp1704_read(isp, ULPI_USB_INT_STS); + isp1704_charger_set_power(isp, 0); + if ((ret & ULPI_INT_VBUS_VALID) && !isp->phy->otg->default_a) { + isp->event = USB_EVENT_VBUS; + schedule_work(&isp->work); + } + + return 0; +fail2: + power_supply_unregister(&isp->psy); +fail1: + usb_put_transceiver(isp->phy); +fail0: + kfree(isp); + + dev_err(&pdev->dev, "failed to register isp1704 with error %d\n", ret); + + isp1704_charger_set_power(isp, 0); + return ret; +} + +static int __devexit isp1704_charger_remove(struct platform_device *pdev) +{ + struct isp1704_charger *isp = platform_get_drvdata(pdev); + + usb_unregister_notifier(isp->phy, &isp->nb); + power_supply_unregister(&isp->psy); + usb_put_transceiver(isp->phy); + 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), +}; + +module_platform_driver(isp1704_charger_driver); + +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..8dbc7bfa --- /dev/null +++ b/drivers/power/jz4740-battery.c @@ -0,0 +1,449 @@ +/* + * Battery measurement code for Ingenic JZ SOC. + * + * Copyright (C) 2009 Jiejing Zhang <kzjeef@gmail.com> + * Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de> + * + * based on tosa_battery.c + * + * Copyright (C) 2008 Marek Vasut <marek.vasut@gmail.com> +* + * 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 <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/io.h> + +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/mfd/core.h> +#include <linux/power_supply.h> + +#include <linux/power/jz4740-battery.h> +#include <linux/jz4740-adc.h> + +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) +{ + 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, + }, +}; + +module_platform_driver(jz_battery_driver); + +MODULE_ALIAS("platform:jz4740-battery"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("JZ4740 SoC battery driver"); diff --git a/drivers/power/lp8727_charger.c b/drivers/power/lp8727_charger.c new file mode 100644 index 00000000..d8b75780 --- /dev/null +++ b/drivers/power/lp8727_charger.c @@ -0,0 +1,498 @@ +/* + * Driver for LP8727 Micro/Mini USB IC with integrated charger + * + * Copyright (C) 2011 Texas Instruments + * Copyright (C) 2011 National Semiconductor + * + * 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 <linux/module.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/power_supply.h> +#include <linux/lp8727.h> + +#define DEBOUNCE_MSEC 270 + +/* Registers */ +#define CTRL1 0x1 +#define CTRL2 0x2 +#define SWCTRL 0x3 +#define INT1 0x4 +#define INT2 0x5 +#define STATUS1 0x6 +#define STATUS2 0x7 +#define CHGCTRL2 0x9 + +/* CTRL1 register */ +#define CP_EN (1 << 0) +#define ADC_EN (1 << 1) +#define ID200_EN (1 << 4) + +/* CTRL2 register */ +#define CHGDET_EN (1 << 1) +#define INT_EN (1 << 6) + +/* SWCTRL register */ +#define SW_DM1_DM (0x0 << 0) +#define SW_DM1_U1 (0x1 << 0) +#define SW_DM1_HiZ (0x7 << 0) +#define SW_DP2_DP (0x0 << 3) +#define SW_DP2_U2 (0x1 << 3) +#define SW_DP2_HiZ (0x7 << 3) + +/* INT1 register */ +#define IDNO (0xF << 0) +#define VBUS (1 << 4) + +/* STATUS1 register */ +#define CHGSTAT (3 << 4) +#define CHPORT (1 << 6) +#define DCPORT (1 << 7) + +/* STATUS2 register */ +#define TEMP_STAT (3 << 5) + +enum lp8727_dev_id { + ID_NONE, + ID_TA, + ID_DEDICATED_CHG, + ID_USB_CHG, + ID_USB_DS, + ID_MAX, +}; + +enum lp8727_chg_stat { + PRECHG, + CC, + CV, + EOC, +}; + +struct lp8727_psy { + struct power_supply ac; + struct power_supply usb; + struct power_supply batt; +}; + +struct lp8727_chg { + struct device *dev; + struct i2c_client *client; + struct mutex xfer_lock; + struct delayed_work work; + struct workqueue_struct *irqthread; + struct lp8727_platform_data *pdata; + struct lp8727_psy *psy; + struct lp8727_chg_param *chg_parm; + enum lp8727_dev_id devid; +}; + +static int lp8727_read_bytes(struct lp8727_chg *pchg, u8 reg, u8 *data, u8 len) +{ + s32 ret; + + mutex_lock(&pchg->xfer_lock); + ret = i2c_smbus_read_i2c_block_data(pchg->client, reg, len, data); + mutex_unlock(&pchg->xfer_lock); + + return (ret != len) ? -EIO : 0; +} + +static inline int lp8727_read_byte(struct lp8727_chg *pchg, u8 reg, u8 *data) +{ + return lp8727_read_bytes(pchg, reg, data, 1); +} + +static int lp8727_write_byte(struct lp8727_chg *pchg, u8 reg, u8 data) +{ + int ret; + + mutex_lock(&pchg->xfer_lock); + ret = i2c_smbus_write_byte_data(pchg->client, reg, data); + mutex_unlock(&pchg->xfer_lock); + + return ret; +} + +static int lp8727_is_charger_attached(const char *name, int id) +{ + if (name) { + if (!strcmp(name, "ac")) + return (id == ID_TA || id == ID_DEDICATED_CHG) ? 1 : 0; + else if (!strcmp(name, "usb")) + return (id == ID_USB_CHG) ? 1 : 0; + } + + return (id >= ID_TA && id <= ID_USB_CHG) ? 1 : 0; +} + +static int lp8727_init_device(struct lp8727_chg *pchg) +{ + u8 val; + int ret; + + val = ID200_EN | ADC_EN | CP_EN; + ret = lp8727_write_byte(pchg, CTRL1, val); + if (ret) + return ret; + + val = INT_EN | CHGDET_EN; + ret = lp8727_write_byte(pchg, CTRL2, val); + if (ret) + return ret; + + return 0; +} + +static int lp8727_is_dedicated_charger(struct lp8727_chg *pchg) +{ + u8 val; + lp8727_read_byte(pchg, STATUS1, &val); + return val & DCPORT; +} + +static int lp8727_is_usb_charger(struct lp8727_chg *pchg) +{ + u8 val; + lp8727_read_byte(pchg, STATUS1, &val); + return val & CHPORT; +} + +static void lp8727_ctrl_switch(struct lp8727_chg *pchg, u8 sw) +{ + lp8727_write_byte(pchg, SWCTRL, sw); +} + +static void lp8727_id_detection(struct lp8727_chg *pchg, u8 id, int vbusin) +{ + u8 devid = ID_NONE; + u8 swctrl = SW_DM1_HiZ | SW_DP2_HiZ; + + switch (id) { + case 0x5: + devid = ID_TA; + pchg->chg_parm = &pchg->pdata->ac; + break; + case 0xB: + if (lp8727_is_dedicated_charger(pchg)) { + pchg->chg_parm = &pchg->pdata->ac; + devid = ID_DEDICATED_CHG; + } else if (lp8727_is_usb_charger(pchg)) { + pchg->chg_parm = &pchg->pdata->usb; + devid = ID_USB_CHG; + swctrl = SW_DM1_DM | SW_DP2_DP; + } else if (vbusin) { + devid = ID_USB_DS; + swctrl = SW_DM1_DM | SW_DP2_DP; + } + break; + default: + devid = ID_NONE; + pchg->chg_parm = NULL; + break; + } + + pchg->devid = devid; + lp8727_ctrl_switch(pchg, swctrl); +} + +static void lp8727_enable_chgdet(struct lp8727_chg *pchg) +{ + u8 val; + + lp8727_read_byte(pchg, CTRL2, &val); + val |= CHGDET_EN; + lp8727_write_byte(pchg, CTRL2, val); +} + +static void lp8727_delayed_func(struct work_struct *_work) +{ + u8 intstat[2], idno, vbus; + struct lp8727_chg *pchg = + container_of(_work, struct lp8727_chg, work.work); + + if (lp8727_read_bytes(pchg, INT1, intstat, 2)) { + dev_err(pchg->dev, "can not read INT registers\n"); + return; + } + + idno = intstat[0] & IDNO; + vbus = intstat[0] & VBUS; + + lp8727_id_detection(pchg, idno, vbus); + lp8727_enable_chgdet(pchg); + + power_supply_changed(&pchg->psy->ac); + power_supply_changed(&pchg->psy->usb); + power_supply_changed(&pchg->psy->batt); +} + +static irqreturn_t lp8727_isr_func(int irq, void *ptr) +{ + struct lp8727_chg *pchg = ptr; + unsigned long delay = msecs_to_jiffies(DEBOUNCE_MSEC); + + queue_delayed_work(pchg->irqthread, &pchg->work, delay); + + return IRQ_HANDLED; +} + +static int lp8727_intr_config(struct lp8727_chg *pchg) +{ + INIT_DELAYED_WORK(&pchg->work, lp8727_delayed_func); + + pchg->irqthread = create_singlethread_workqueue("lp8727-irqthd"); + if (!pchg->irqthread) { + dev_err(pchg->dev, "can not create thread for lp8727\n"); + return -ENOMEM; + } + + return request_threaded_irq(pchg->client->irq, + NULL, + lp8727_isr_func, + IRQF_TRIGGER_FALLING, + "lp8727_irq", + pchg); +} + +static enum power_supply_property lp8727_charger_prop[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static enum power_supply_property lp8727_battery_prop[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TEMP, +}; + +static char *battery_supplied_to[] = { + "main_batt", +}; + +static int lp8727_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct lp8727_chg *pchg = dev_get_drvdata(psy->dev->parent); + + if (psp == POWER_SUPPLY_PROP_ONLINE) + val->intval = lp8727_is_charger_attached(psy->name, + pchg->devid); + + return 0; +} + +static int lp8727_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct lp8727_chg *pchg = dev_get_drvdata(psy->dev->parent); + u8 read; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (lp8727_is_charger_attached(psy->name, pchg->devid)) { + lp8727_read_byte(pchg, STATUS1, &read); + if (((read & CHGSTAT) >> 4) == EOC) + val->intval = POWER_SUPPLY_STATUS_FULL; + else + val->intval = POWER_SUPPLY_STATUS_CHARGING; + } else { + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + } + break; + case POWER_SUPPLY_PROP_HEALTH: + lp8727_read_byte(pchg, STATUS2, &read); + read = (read & TEMP_STAT) >> 5; + if (read >= 0x1 && read <= 0x3) + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_PRESENT: + if (pchg->pdata->get_batt_present) + val->intval = pchg->pdata->get_batt_present(); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + if (pchg->pdata->get_batt_level) + val->intval = pchg->pdata->get_batt_level(); + break; + case POWER_SUPPLY_PROP_CAPACITY: + if (pchg->pdata->get_batt_capacity) + val->intval = pchg->pdata->get_batt_capacity(); + break; + case POWER_SUPPLY_PROP_TEMP: + if (pchg->pdata->get_batt_temp) + val->intval = pchg->pdata->get_batt_temp(); + break; + default: + break; + } + + return 0; +} + +static void lp8727_charger_changed(struct power_supply *psy) +{ + struct lp8727_chg *pchg = dev_get_drvdata(psy->dev->parent); + u8 val; + u8 eoc_level, ichg; + + if (lp8727_is_charger_attached(psy->name, pchg->devid)) { + if (pchg->chg_parm) { + eoc_level = pchg->chg_parm->eoc_level; + ichg = pchg->chg_parm->ichg; + val = (ichg << 4) | eoc_level; + lp8727_write_byte(pchg, CHGCTRL2, val); + } + } +} + +static int lp8727_register_psy(struct lp8727_chg *pchg) +{ + struct lp8727_psy *psy; + + psy = kzalloc(sizeof(*psy), GFP_KERNEL); + if (!psy) + goto err_mem; + + pchg->psy = psy; + + psy->ac.name = "ac"; + psy->ac.type = POWER_SUPPLY_TYPE_MAINS; + psy->ac.properties = lp8727_charger_prop; + psy->ac.num_properties = ARRAY_SIZE(lp8727_charger_prop); + psy->ac.get_property = lp8727_charger_get_property; + psy->ac.supplied_to = battery_supplied_to; + psy->ac.num_supplicants = ARRAY_SIZE(battery_supplied_to); + + if (power_supply_register(pchg->dev, &psy->ac)) + goto err_psy; + + psy->usb.name = "usb"; + psy->usb.type = POWER_SUPPLY_TYPE_USB; + psy->usb.properties = lp8727_charger_prop; + psy->usb.num_properties = ARRAY_SIZE(lp8727_charger_prop); + psy->usb.get_property = lp8727_charger_get_property; + psy->usb.supplied_to = battery_supplied_to; + psy->usb.num_supplicants = ARRAY_SIZE(battery_supplied_to); + + if (power_supply_register(pchg->dev, &psy->usb)) + goto err_psy; + + psy->batt.name = "main_batt"; + psy->batt.type = POWER_SUPPLY_TYPE_BATTERY; + psy->batt.properties = lp8727_battery_prop; + psy->batt.num_properties = ARRAY_SIZE(lp8727_battery_prop); + psy->batt.get_property = lp8727_battery_get_property; + psy->batt.external_power_changed = lp8727_charger_changed; + + if (power_supply_register(pchg->dev, &psy->batt)) + goto err_psy; + + return 0; + +err_mem: + return -ENOMEM; +err_psy: + kfree(psy); + return -EPERM; +} + +static void lp8727_unregister_psy(struct lp8727_chg *pchg) +{ + struct lp8727_psy *psy = pchg->psy; + + if (!psy) + return; + + power_supply_unregister(&psy->ac); + power_supply_unregister(&psy->usb); + power_supply_unregister(&psy->batt); + kfree(psy); +} + +static int lp8727_probe(struct i2c_client *cl, const struct i2c_device_id *id) +{ + struct lp8727_chg *pchg; + int ret; + + if (!i2c_check_functionality(cl->adapter, I2C_FUNC_SMBUS_I2C_BLOCK)) + return -EIO; + + pchg = kzalloc(sizeof(*pchg), GFP_KERNEL); + if (!pchg) + return -ENOMEM; + + pchg->client = cl; + pchg->dev = &cl->dev; + pchg->pdata = cl->dev.platform_data; + i2c_set_clientdata(cl, pchg); + + mutex_init(&pchg->xfer_lock); + + ret = lp8727_init_device(pchg); + if (ret) { + dev_err(pchg->dev, "i2c communication err: %d", ret); + goto error; + } + + ret = lp8727_intr_config(pchg); + if (ret) { + dev_err(pchg->dev, "irq handler err: %d", ret); + goto error; + } + + ret = lp8727_register_psy(pchg); + if (ret) { + dev_err(pchg->dev, "power supplies register err: %d", ret); + goto error; + } + + return 0; + +error: + kfree(pchg); + return ret; +} + +static int __devexit lp8727_remove(struct i2c_client *cl) +{ + struct lp8727_chg *pchg = i2c_get_clientdata(cl); + + lp8727_unregister_psy(pchg); + free_irq(pchg->client->irq, pchg); + flush_workqueue(pchg->irqthread); + destroy_workqueue(pchg->irqthread); + kfree(pchg); + return 0; +} + +static const struct i2c_device_id lp8727_ids[] = { + {"lp8727", 0}, + { } +}; +MODULE_DEVICE_TABLE(i2c, lp8727_ids); + +static struct i2c_driver lp8727_driver = { + .driver = { + .name = "lp8727", + }, + .probe = lp8727_probe, + .remove = __devexit_p(lp8727_remove), + .id_table = lp8727_ids, +}; +module_i2c_driver(lp8727_driver); + +MODULE_DESCRIPTION("TI/National Semiconductor LP8727 charger driver"); +MODULE_AUTHOR("Woogyom Kim <milo.kim@ti.com>, " + "Daniel Jeong <daniel.jeong@ti.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/max17040_battery.c b/drivers/power/max17040_battery.c new file mode 100644 index 00000000..c284143c --- /dev/null +++ b/drivers/power/max17040_battery.c @@ -0,0 +1,297 @@ +/* + * max17040_battery.c + * fuel-gauge systems for lithium-ion (Li+) batteries + * + * Copyright (C) 2009 Samsung Electronics + * Minkyu Kang <mk7.kang@samsung.com> + * + * 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 <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/mutex.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/power_supply.h> +#include <linux/max17040_battery.h> +#include <linux/slab.h> + +#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, +}; +module_i2c_driver(max17040_i2c_driver); + +MODULE_AUTHOR("Minkyu Kang <mk7.kang@samsung.com>"); +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..04620c2c --- /dev/null +++ b/drivers/power/max17042_battery.c @@ -0,0 +1,751 @@ +/* + * 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 <myungjoo.ham@samsung.com> + * + * 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 <linux/init.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/mod_devicetable.h> +#include <linux/power_supply.h> +#include <linux/power/max17042_battery.h> +#include <linux/of.h> + +/* Status register bits */ +#define STATUS_POR_BIT (1 << 1) +#define STATUS_BST_BIT (1 << 3) +#define STATUS_VMN_BIT (1 << 8) +#define STATUS_TMN_BIT (1 << 9) +#define STATUS_SMN_BIT (1 << 10) +#define STATUS_BI_BIT (1 << 11) +#define STATUS_VMX_BIT (1 << 12) +#define STATUS_TMX_BIT (1 << 13) +#define STATUS_SMX_BIT (1 << 14) +#define STATUS_BR_BIT (1 << 15) + +/* Interrupt mask bits */ +#define CONFIG_ALRT_BIT_ENBL (1 << 2) +#define STATUS_INTR_SOCMIN_BIT (1 << 10) +#define STATUS_INTR_SOCMAX_BIT (1 << 14) + +#define VFSOC0_LOCK 0x0000 +#define VFSOC0_UNLOCK 0x0080 +#define MODEL_UNLOCK1 0X0059 +#define MODEL_UNLOCK2 0X00C4 +#define MODEL_LOCK1 0X0000 +#define MODEL_LOCK2 0X0000 + +#define dQ_ACC_DIV 0x4 +#define dP_ACC_100 0x1900 +#define dP_ACC_200 0x3200 + +struct max17042_chip { + struct i2c_client *client; + struct power_supply battery; + struct max17042_platform_data *pdata; + struct work_struct work; + int init_complete; +}; + +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 void max17042_set_reg(struct i2c_client *client, + struct max17042_reg_data *data, int size) +{ + int i; + + for (i = 0; i < size; i++) + max17042_write_reg(client, data[i].addr, data[i].data); +} + +static enum power_supply_property max17042_battery_props[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, +}; + +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); + int ret; + + if (!chip->init_complete) + return -EAGAIN; + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + ret = max17042_read_reg(chip->client, MAX17042_STATUS); + if (ret < 0) + return ret; + + if (ret & MAX17042_STATUS_BattAbsent) + val->intval = 0; + else + val->intval = 1; + break; + case POWER_SUPPLY_PROP_CYCLE_COUNT: + ret = max17042_read_reg(chip->client, MAX17042_Cycles); + if (ret < 0) + return ret; + + val->intval = ret; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + ret = max17042_read_reg(chip->client, MAX17042_MinMaxVolt); + if (ret < 0) + return ret; + + val->intval = ret >> 8; + val->intval *= 20000; /* Units of LSB = 20mV */ + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + ret = max17042_read_reg(chip->client, MAX17042_V_empty); + if (ret < 0) + return ret; + + val->intval = ret >> 7; + val->intval *= 10000; /* Units of LSB = 10mV */ + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = max17042_read_reg(chip->client, MAX17042_VCELL); + if (ret < 0) + return ret; + + val->intval = ret * 625 / 8; + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + ret = max17042_read_reg(chip->client, MAX17042_AvgVCELL); + if (ret < 0) + return ret; + + val->intval = ret * 625 / 8; + break; + case POWER_SUPPLY_PROP_CAPACITY: + ret = max17042_read_reg(chip->client, MAX17042_RepSOC); + if (ret < 0) + return ret; + + val->intval = ret >> 8; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + ret = max17042_read_reg(chip->client, MAX17042_FullCAP); + if (ret < 0) + return ret; + + val->intval = ret * 1000 / 2; + break; + case POWER_SUPPLY_PROP_TEMP: + ret = max17042_read_reg(chip->client, MAX17042_TEMP); + if (ret < 0) + return ret; + + val->intval = ret; + /* The value is signed. */ + if (val->intval & 0x8000) { + val->intval = (0x7fff & ~val->intval) + 1; + val->intval *= -1; + } + /* The value is converted into deci-centigrade scale */ + /* Units of LSB = 1 / 256 degree Celsius */ + val->intval = val->intval * 10 / 256; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + if (chip->pdata->enable_current_sense) { + ret = max17042_read_reg(chip->client, MAX17042_Current); + if (ret < 0) + return ret; + + val->intval = ret; + if (val->intval & 0x8000) { + /* Negative */ + val->intval = ~val->intval & 0x7fff; + val->intval++; + val->intval *= -1; + } + val->intval *= 1562500 / chip->pdata->r_sns; + } else { + return -EINVAL; + } + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + if (chip->pdata->enable_current_sense) { + ret = max17042_read_reg(chip->client, + MAX17042_AvgCurrent); + if (ret < 0) + return ret; + + val->intval = ret; + if (val->intval & 0x8000) { + /* Negative */ + val->intval = ~val->intval & 0x7fff; + val->intval++; + val->intval *= -1; + } + val->intval *= 1562500 / chip->pdata->r_sns; + } else { + return -EINVAL; + } + break; + default: + return -EINVAL; + } + return 0; +} + +static int max17042_write_verify_reg(struct i2c_client *client, + u8 reg, u16 value) +{ + int retries = 8; + int ret; + u16 read_value; + + do { + ret = i2c_smbus_write_word_data(client, reg, value); + read_value = max17042_read_reg(client, reg); + if (read_value != value) { + ret = -EIO; + retries--; + } + } while (retries && read_value != value); + + if (ret < 0) + dev_err(&client->dev, "%s: err %d\n", __func__, ret); + + return ret; +} + +static inline void max17042_override_por( + struct i2c_client *client, u8 reg, u16 value) +{ + if (value) + max17042_write_reg(client, reg, value); +} + +static inline void max10742_unlock_model(struct max17042_chip *chip) +{ + struct i2c_client *client = chip->client; + max17042_write_reg(client, MAX17042_MLOCKReg1, MODEL_UNLOCK1); + max17042_write_reg(client, MAX17042_MLOCKReg2, MODEL_UNLOCK2); +} + +static inline void max10742_lock_model(struct max17042_chip *chip) +{ + struct i2c_client *client = chip->client; + max17042_write_reg(client, MAX17042_MLOCKReg1, MODEL_LOCK1); + max17042_write_reg(client, MAX17042_MLOCKReg2, MODEL_LOCK2); +} + +static inline void max17042_write_model_data(struct max17042_chip *chip, + u8 addr, int size) +{ + struct i2c_client *client = chip->client; + int i; + for (i = 0; i < size; i++) + max17042_write_reg(client, addr + i, + chip->pdata->config_data->cell_char_tbl[i]); +} + +static inline void max17042_read_model_data(struct max17042_chip *chip, + u8 addr, u16 *data, int size) +{ + struct i2c_client *client = chip->client; + int i; + + for (i = 0; i < size; i++) + data[i] = max17042_read_reg(client, addr + i); +} + +static inline int max17042_model_data_compare(struct max17042_chip *chip, + u16 *data1, u16 *data2, int size) +{ + int i; + + if (memcmp(data1, data2, size)) { + dev_err(&chip->client->dev, "%s compare failed\n", __func__); + for (i = 0; i < size; i++) + dev_info(&chip->client->dev, "0x%x, 0x%x", + data1[i], data2[i]); + dev_info(&chip->client->dev, "\n"); + return -EINVAL; + } + return 0; +} + +static int max17042_init_model(struct max17042_chip *chip) +{ + int ret; + int table_size = + sizeof(chip->pdata->config_data->cell_char_tbl)/sizeof(u16); + u16 *temp_data; + + temp_data = kzalloc(table_size, GFP_KERNEL); + if (!temp_data) + return -ENOMEM; + + max10742_unlock_model(chip); + max17042_write_model_data(chip, MAX17042_MODELChrTbl, + table_size); + max17042_read_model_data(chip, MAX17042_MODELChrTbl, temp_data, + table_size); + + ret = max17042_model_data_compare( + chip, + chip->pdata->config_data->cell_char_tbl, + temp_data, + table_size); + + max10742_lock_model(chip); + kfree(temp_data); + + return ret; +} + +static int max17042_verify_model_lock(struct max17042_chip *chip) +{ + int i; + int table_size = + sizeof(chip->pdata->config_data->cell_char_tbl); + u16 *temp_data; + int ret = 0; + + temp_data = kzalloc(table_size, GFP_KERNEL); + if (!temp_data) + return -ENOMEM; + + max17042_read_model_data(chip, MAX17042_MODELChrTbl, temp_data, + table_size); + for (i = 0; i < table_size; i++) + if (temp_data[i]) + ret = -EINVAL; + + kfree(temp_data); + return ret; +} + +static void max17042_write_config_regs(struct max17042_chip *chip) +{ + struct max17042_config_data *config = chip->pdata->config_data; + + max17042_write_reg(chip->client, MAX17042_CONFIG, config->config); + max17042_write_reg(chip->client, MAX17042_LearnCFG, config->learn_cfg); + max17042_write_reg(chip->client, MAX17042_FilterCFG, + config->filter_cfg); + max17042_write_reg(chip->client, MAX17042_RelaxCFG, config->relax_cfg); +} + +static void max17042_write_custom_regs(struct max17042_chip *chip) +{ + struct max17042_config_data *config = chip->pdata->config_data; + + max17042_write_verify_reg(chip->client, MAX17042_RCOMP0, + config->rcomp0); + max17042_write_verify_reg(chip->client, MAX17042_TempCo, + config->tcompc0); + max17042_write_reg(chip->client, MAX17042_EmptyTempCo, + config->empty_tempco); + max17042_write_verify_reg(chip->client, MAX17042_K_empty0, + config->kempty0); + max17042_write_verify_reg(chip->client, MAX17042_ICHGTerm, + config->ichgt_term); +} + +static void max17042_update_capacity_regs(struct max17042_chip *chip) +{ + struct max17042_config_data *config = chip->pdata->config_data; + + max17042_write_verify_reg(chip->client, MAX17042_FullCAP, + config->fullcap); + max17042_write_reg(chip->client, MAX17042_DesignCap, + config->design_cap); + max17042_write_verify_reg(chip->client, MAX17042_FullCAPNom, + config->fullcapnom); +} + +static void max17042_reset_vfsoc0_reg(struct max17042_chip *chip) +{ + u16 vfSoc; + + vfSoc = max17042_read_reg(chip->client, MAX17042_VFSOC); + max17042_write_reg(chip->client, MAX17042_VFSOC0Enable, VFSOC0_UNLOCK); + max17042_write_verify_reg(chip->client, MAX17042_VFSOC0, vfSoc); + max17042_write_reg(chip->client, MAX17042_VFSOC0Enable, VFSOC0_LOCK); +} + +static void max17042_load_new_capacity_params(struct max17042_chip *chip) +{ + u16 full_cap0, rep_cap, dq_acc, vfSoc; + u32 rem_cap; + + struct max17042_config_data *config = chip->pdata->config_data; + + full_cap0 = max17042_read_reg(chip->client, MAX17042_FullCAP0); + vfSoc = max17042_read_reg(chip->client, MAX17042_VFSOC); + + /* fg_vfSoc needs to shifted by 8 bits to get the + * perc in 1% accuracy, to get the right rem_cap multiply + * full_cap0, fg_vfSoc and devide by 100 + */ + rem_cap = ((vfSoc >> 8) * full_cap0) / 100; + max17042_write_verify_reg(chip->client, MAX17042_RemCap, (u16)rem_cap); + + rep_cap = (u16)rem_cap; + max17042_write_verify_reg(chip->client, MAX17042_RepCap, rep_cap); + + /* Write dQ_acc to 200% of Capacity and dP_acc to 200% */ + dq_acc = config->fullcap / dQ_ACC_DIV; + max17042_write_verify_reg(chip->client, MAX17042_dQacc, dq_acc); + max17042_write_verify_reg(chip->client, MAX17042_dPacc, dP_ACC_200); + + max17042_write_verify_reg(chip->client, MAX17042_FullCAP, + config->fullcap); + max17042_write_reg(chip->client, MAX17042_DesignCap, + config->design_cap); + max17042_write_verify_reg(chip->client, MAX17042_FullCAPNom, + config->fullcapnom); +} + +/* + * Block write all the override values coming from platform data. + * This function MUST be called before the POR initialization proceedure + * specified by maxim. + */ +static inline void max17042_override_por_values(struct max17042_chip *chip) +{ + struct i2c_client *client = chip->client; + struct max17042_config_data *config = chip->pdata->config_data; + + max17042_override_por(client, MAX17042_TGAIN, config->tgain); + max17042_override_por(client, MAx17042_TOFF, config->toff); + max17042_override_por(client, MAX17042_CGAIN, config->cgain); + max17042_override_por(client, MAX17042_COFF, config->coff); + + max17042_override_por(client, MAX17042_VALRT_Th, config->valrt_thresh); + max17042_override_por(client, MAX17042_TALRT_Th, config->talrt_thresh); + max17042_override_por(client, MAX17042_SALRT_Th, + config->soc_alrt_thresh); + max17042_override_por(client, MAX17042_CONFIG, config->config); + max17042_override_por(client, MAX17042_SHDNTIMER, config->shdntimer); + + max17042_override_por(client, MAX17042_DesignCap, config->design_cap); + max17042_override_por(client, MAX17042_ICHGTerm, config->ichgt_term); + + max17042_override_por(client, MAX17042_AtRate, config->at_rate); + max17042_override_por(client, MAX17042_LearnCFG, config->learn_cfg); + max17042_override_por(client, MAX17042_FilterCFG, config->filter_cfg); + max17042_override_por(client, MAX17042_RelaxCFG, config->relax_cfg); + max17042_override_por(client, MAX17042_MiscCFG, config->misc_cfg); + max17042_override_por(client, MAX17042_MaskSOC, config->masksoc); + + max17042_override_por(client, MAX17042_FullCAP, config->fullcap); + max17042_override_por(client, MAX17042_FullCAPNom, config->fullcapnom); + max17042_override_por(client, MAX17042_SOC_empty, config->socempty); + max17042_override_por(client, MAX17042_LAvg_empty, config->lavg_empty); + max17042_override_por(client, MAX17042_dQacc, config->dqacc); + max17042_override_por(client, MAX17042_dPacc, config->dpacc); + + max17042_override_por(client, MAX17042_V_empty, config->vempty); + max17042_override_por(client, MAX17042_TempNom, config->temp_nom); + max17042_override_por(client, MAX17042_TempLim, config->temp_lim); + max17042_override_por(client, MAX17042_FCTC, config->fctc); + max17042_override_por(client, MAX17042_RCOMP0, config->rcomp0); + max17042_override_por(client, MAX17042_TempCo, config->tcompc0); + max17042_override_por(client, MAX17042_EmptyTempCo, + config->empty_tempco); + max17042_override_por(client, MAX17042_K_empty0, config->kempty0); +} + +static int max17042_init_chip(struct max17042_chip *chip) +{ + int ret; + int val; + + max17042_override_por_values(chip); + /* After Power up, the MAX17042 requires 500mS in order + * to perform signal debouncing and initial SOC reporting + */ + msleep(500); + + /* Initialize configaration */ + max17042_write_config_regs(chip); + + /* write cell characterization data */ + ret = max17042_init_model(chip); + if (ret) { + dev_err(&chip->client->dev, "%s init failed\n", + __func__); + return -EIO; + } + max17042_verify_model_lock(chip); + if (ret) { + dev_err(&chip->client->dev, "%s lock verify failed\n", + __func__); + return -EIO; + } + /* write custom parameters */ + max17042_write_custom_regs(chip); + + /* update capacity params */ + max17042_update_capacity_regs(chip); + + /* delay must be atleast 350mS to allow VFSOC + * to be calculated from the new configuration + */ + msleep(350); + + /* reset vfsoc0 reg */ + max17042_reset_vfsoc0_reg(chip); + + /* load new capacity params */ + max17042_load_new_capacity_params(chip); + + /* Init complete, Clear the POR bit */ + val = max17042_read_reg(chip->client, MAX17042_STATUS); + max17042_write_reg(chip->client, MAX17042_STATUS, + val & (~STATUS_POR_BIT)); + return 0; +} + +static void max17042_set_soc_threshold(struct max17042_chip *chip, u16 off) +{ + u16 soc, soc_tr; + + /* program interrupt thesholds such that we should + * get interrupt for every 'off' perc change in the soc + */ + soc = max17042_read_reg(chip->client, MAX17042_RepSOC) >> 8; + soc_tr = (soc + off) << 8; + soc_tr |= (soc - off); + max17042_write_reg(chip->client, MAX17042_SALRT_Th, soc_tr); +} + +static irqreturn_t max17042_thread_handler(int id, void *dev) +{ + struct max17042_chip *chip = dev; + u16 val; + + val = max17042_read_reg(chip->client, MAX17042_STATUS); + if ((val & STATUS_INTR_SOCMIN_BIT) || + (val & STATUS_INTR_SOCMAX_BIT)) { + dev_info(&chip->client->dev, "SOC threshold INTR\n"); + max17042_set_soc_threshold(chip, 1); + } + + power_supply_changed(&chip->battery); + return IRQ_HANDLED; +} + +static void max17042_init_worker(struct work_struct *work) +{ + struct max17042_chip *chip = container_of(work, + struct max17042_chip, work); + int ret; + + /* Initialize registers according to values from the platform data */ + if (chip->pdata->enable_por_init && chip->pdata->config_data) { + ret = max17042_init_chip(chip); + if (ret) + return; + } + + chip->init_complete = 1; +} + +#ifdef CONFIG_OF +static struct max17042_platform_data * +max17042_get_pdata(struct device *dev) +{ + struct device_node *np = dev->of_node; + u32 prop; + struct max17042_platform_data *pdata; + + if (!np) + return dev->platform_data; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return NULL; + + /* + * Require current sense resistor value to be specified for + * current-sense functionality to be enabled at all. + */ + if (of_property_read_u32(np, "maxim,rsns-microohm", &prop) == 0) { + pdata->r_sns = prop; + pdata->enable_current_sense = true; + } + + return pdata; +} +#else +static struct max17042_platform_data * +max17042_get_pdata(struct device *dev) +{ + return dev->platform_data; +} +#endif + +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; + int reg; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) + return -EIO; + + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->client = client; + chip->pdata = max17042_get_pdata(&client->dev); + if (!chip->pdata) { + dev_err(&client->dev, "no platform data provided\n"); + return -EINVAL; + } + + 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); + + /* When current is not measured, + * CURRENT_NOW and CURRENT_AVG properties should be invisible. */ + if (!chip->pdata->enable_current_sense) + chip->battery.num_properties -= 2; + + if (chip->pdata->r_sns == 0) + chip->pdata->r_sns = MAX17042_DEFAULT_SNS_RESISTOR; + + if (chip->pdata->init_data) + max17042_set_reg(client, chip->pdata->init_data, + chip->pdata->num_init_data); + + 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); + } + + if (client->irq) { + ret = request_threaded_irq(client->irq, NULL, + max17042_thread_handler, + IRQF_TRIGGER_FALLING, + chip->battery.name, chip); + if (!ret) { + reg = max17042_read_reg(client, MAX17042_CONFIG); + reg |= CONFIG_ALRT_BIT_ENBL; + max17042_write_reg(client, MAX17042_CONFIG, reg); + max17042_set_soc_threshold(chip, 1); + } else + dev_err(&client->dev, "%s(): cannot get IRQ\n", + __func__); + } + + reg = max17042_read_reg(chip->client, MAX17042_STATUS); + + if (reg & STATUS_POR_BIT) { + INIT_WORK(&chip->work, max17042_init_worker); + schedule_work(&chip->work); + } else { + chip->init_complete = 1; + } + + ret = power_supply_register(&client->dev, &chip->battery); + if (ret) + dev_err(&client->dev, "failed: power supply register\n"); + return ret; +} + +static int __devexit max17042_remove(struct i2c_client *client) +{ + struct max17042_chip *chip = i2c_get_clientdata(client); + + power_supply_unregister(&chip->battery); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id max17042_dt_match[] = { + { .compatible = "maxim,max17042" }, + { }, +}; +MODULE_DEVICE_TABLE(of, max17042_dt_match); +#endif + +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", + .of_match_table = of_match_ptr(max17042_dt_match), + }, + .probe = max17042_probe, + .remove = __devexit_p(max17042_remove), + .id_table = max17042_id, +}; +module_i2c_driver(max17042_i2c_driver); + +MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>"); +MODULE_DESCRIPTION("MAX17042 Fuel Gauge"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/max8903_charger.c b/drivers/power/max8903_charger.c new file mode 100644 index 00000000..3e23f43e --- /dev/null +++ b/drivers/power/max8903_charger.c @@ -0,0 +1,382 @@ +/* + * max8903_charger.c - Maxim 8903 USB/Adapter Charger Driver + * + * Copyright (C) 2011 Samsung Electronics + * MyungJoo Ham <myungjoo.ham@samsung.com> + * + * 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 <linux/gpio.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/power_supply.h> +#include <linux/platform_device.h> +#include <linux/power/max8903_charger.h> + +struct max8903_data { + struct max8903_pdata pdata; + struct device *dev; + struct power_supply psy; + bool fault; + bool usb_in; + bool ta_in; +}; + +static enum power_supply_property max8903_charger_props[] = { + POWER_SUPPLY_PROP_STATUS, /* Charger status output */ + POWER_SUPPLY_PROP_ONLINE, /* External power source */ + POWER_SUPPLY_PROP_HEALTH, /* Fault or OK */ +}; + +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) + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + else + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + } + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = 0; + if (data->usb_in || data->ta_in) + val->intval = 1; + 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; + + /* 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"); + + 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); + + 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; + + /* 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"); + + 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); + + 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 device *dev = &pdev->dev; + struct max8903_pdata *pdata = pdev->dev.platform_data; + int ret = 0; + int gpio; + int ta_in = 0; + int usb_in = 0; + + data = kzalloc(sizeof(struct max8903_data), GFP_KERNEL); + if (data == NULL) { + dev_err(dev, "Cannot allocate memory.\n"); + return -ENOMEM; + } + memcpy(&data->pdata, pdata, sizeof(struct max8903_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 { + dev_err(dev, "When DC is wired, DOK and DCM should" + " be wired as well.\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; + 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->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; + } + } + + 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; + } + } + + data->fault = false; + data->ta_in = ta_in; + data->usb_in = usb_in; + + data->psy.name = "max8903_charger"; + 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; + } + + 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; + } + } + + 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); +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); + power_supply_unregister(&data->psy); + 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, + }, +}; + +module_platform_driver(max8903_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("MAX8903 Charger Driver"); +MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>"); +MODULE_ALIAS("platform:max8903-charger"); diff --git a/drivers/power/max8925_power.c b/drivers/power/max8925_power.c new file mode 100644 index 00000000..daa333bd --- /dev/null +++ b/drivers/power/max8925_power.c @@ -0,0 +1,530 @@ +/* + * Battery driver for Maxim MAX8925 + * + * Copyright (c) 2009-2010 Marvell International Ltd. + * Haojian Zhuang <haojian.zhuang@marvell.com> + * + * 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 <linux/module.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/mfd/max8925.h> + +/* 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; + unsigned no_temp_support:1; + unsigned no_insert_detect:1; + + 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 removed\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_cmd; + int meas_reg = 0, ret; + + switch (type) { + case MEASURE_VCHG: + meas_cmd = MAX8925_CMD_VCHG; + meas_reg = MAX8925_ADC_VCHG; + break; + case MEASURE_VBBATT: + meas_cmd = MAX8925_CMD_VBBATT; + meas_reg = MAX8925_ADC_VBBATT; + break; + case MEASURE_VMBATT: + meas_cmd = MAX8925_CMD_VMBATT; + meas_reg = MAX8925_ADC_VMBATT; + break; + case MEASURE_ISNS: + meas_cmd = MAX8925_CMD_ISNS; + meas_reg = MAX8925_ADC_ISNS; + break; + default: + return -EINVAL; + } + + max8925_reg_write(info->adc, meas_cmd, 0); + max8925_bulk_read(info->adc, meas_reg, 2, buf); + ret = ((buf[0]<<8) | 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 * 2000; /* unit is uV */ + 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 * 2000; /* unit is uV */ + 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); + 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 * 2000; /* unit is uV */ + 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) { + /* assume r_sns is 0.02 */ + ret = ((ret * 6250) - 3125) /* uA */; + 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"); + if (!info->no_insert_detect) { + REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_F, "ac-remove"); + REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_R, "ac-insert"); + } + if (!info->no_temp_support) { + 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->usb_online = 0; + info->bat_online = 0; + + /* check for power - can miss interrupt at boot time */ + if (start_measure(info, MEASURE_VCHG) * 2000 > 500000) + info->ac_online = 1; + else + info->ac_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; + info->ac.supplied_to = pdata->supplied_to; + info->ac.num_supplicants = pdata->num_supplicants; + 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; + info->usb.supplied_to = pdata->supplied_to; + info->usb.num_supplicants = pdata->num_supplicants; + + 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; + info->no_temp_support = pdata->no_temp_support; + info->no_insert_detect = pdata->no_insert_detect; + + 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", + }, +}; + +module_platform_driver(max8925_power_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Power supply driver for MAX8925"); +MODULE_ALIAS("platform:max8925-power"); diff --git a/drivers/power/max8997_charger.c b/drivers/power/max8997_charger.c new file mode 100644 index 00000000..6e88c5d0 --- /dev/null +++ b/drivers/power/max8997_charger.c @@ -0,0 +1,208 @@ +/* + * max8997_charger.c - Power supply consumer driver for the Maxim 8997/8966 + * + * Copyright (C) 2011 Samsung Electronics + * MyungJoo Ham <myungjoo.ham@samsung.com> + * + * 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 <linux/err.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/mfd/max8997.h> +#include <linux/mfd/max8997-private.h> + +struct charger_data { + struct device *dev; + struct max8997_dev *iodev; + struct power_supply battery; +}; + +static enum power_supply_property max8997_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, /* "FULL" or "NOT FULL" only. */ + POWER_SUPPLY_PROP_PRESENT, /* the presence of battery */ + POWER_SUPPLY_PROP_ONLINE, /* charger is active or not */ +}; + +/* Note that the charger control is done by a current regulator "CHARGER" */ +static int max8997_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct charger_data *charger = container_of(psy, + struct charger_data, battery); + struct i2c_client *i2c = charger->iodev->i2c; + int ret; + u8 reg; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = 0; + ret = max8997_read_reg(i2c, MAX8997_REG_STATUS4, ®); + if (ret) + return ret; + if ((reg & (1 << 0)) == 0x1) + val->intval = POWER_SUPPLY_STATUS_FULL; + + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 0; + ret = max8997_read_reg(i2c, MAX8997_REG_STATUS4, ®); + if (ret) + return ret; + if ((reg & (1 << 2)) == 0x0) + val->intval = 1; + + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = 0; + ret = max8997_read_reg(i2c, MAX8997_REG_STATUS4, ®); + if (ret) + return ret; + /* DCINOK */ + if (reg & (1 << 1)) + val->intval = 1; + + break; + default: + return -EINVAL; + } + + return 0; +} + +static __devinit int max8997_battery_probe(struct platform_device *pdev) +{ + int ret = 0; + struct charger_data *charger; + struct max8997_dev *iodev = dev_get_drvdata(pdev->dev.parent); + struct max8997_platform_data *pdata = dev_get_platdata(iodev->dev); + + if (!pdata) + return -EINVAL; + + if (pdata->eoc_mA) { + int val = (pdata->eoc_mA - 50) / 10; + if (val < 0) + val = 0; + if (val > 0xf) + val = 0xf; + + ret = max8997_update_reg(iodev->i2c, + MAX8997_REG_MBCCTRL5, val, 0xf); + if (ret < 0) { + dev_err(&pdev->dev, "Cannot use i2c bus.\n"); + return ret; + } + } + + switch (pdata->timeout) { + case 5: + ret = max8997_update_reg(iodev->i2c, MAX8997_REG_MBCCTRL1, + 0x2 << 4, 0x7 << 4); + break; + case 6: + ret = max8997_update_reg(iodev->i2c, MAX8997_REG_MBCCTRL1, + 0x3 << 4, 0x7 << 4); + break; + case 7: + ret = max8997_update_reg(iodev->i2c, MAX8997_REG_MBCCTRL1, + 0x4 << 4, 0x7 << 4); + break; + case 0: + ret = max8997_update_reg(iodev->i2c, MAX8997_REG_MBCCTRL1, + 0x7 << 4, 0x7 << 4); + break; + default: + dev_err(&pdev->dev, "incorrect timeout value (%d)\n", + pdata->timeout); + return -EINVAL; + } + if (ret < 0) { + dev_err(&pdev->dev, "Cannot use i2c bus.\n"); + return ret; + } + + charger = kzalloc(sizeof(struct charger_data), GFP_KERNEL); + if (charger == NULL) { + dev_err(&pdev->dev, "Cannot allocate memory.\n"); + return -ENOMEM; + } + + platform_set_drvdata(pdev, charger); + + charger->battery.name = "max8997_pmic"; + charger->battery.type = POWER_SUPPLY_TYPE_BATTERY; + charger->battery.get_property = max8997_battery_get_property; + charger->battery.properties = max8997_battery_props; + charger->battery.num_properties = ARRAY_SIZE(max8997_battery_props); + + charger->dev = &pdev->dev; + charger->iodev = iodev; + + ret = power_supply_register(&pdev->dev, &charger->battery); + if (ret) { + dev_err(&pdev->dev, "failed: power supply register\n"); + goto err; + } + + return 0; +err: + kfree(charger); + return ret; +} + +static int __devexit max8997_battery_remove(struct platform_device *pdev) +{ + struct charger_data *charger = platform_get_drvdata(pdev); + + power_supply_unregister(&charger->battery); + kfree(charger); + return 0; +} + +static const struct platform_device_id max8997_battery_id[] = { + { "max8997-battery", 0 }, + { } +}; + +static struct platform_driver max8997_battery_driver = { + .driver = { + .name = "max8997-battery", + .owner = THIS_MODULE, + }, + .probe = max8997_battery_probe, + .remove = __devexit_p(max8997_battery_remove), + .id_table = max8997_battery_id, +}; + +static int __init max8997_battery_init(void) +{ + return platform_driver_register(&max8997_battery_driver); +} +subsys_initcall(max8997_battery_init); + +static void __exit max8997_battery_cleanup(void) +{ + platform_driver_unregister(&max8997_battery_driver); +} +module_exit(max8997_battery_cleanup); + +MODULE_DESCRIPTION("MAXIM 8997/8966 battery control driver"); +MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/max8998_charger.c b/drivers/power/max8998_charger.c new file mode 100644 index 00000000..6dc01c25 --- /dev/null +++ b/drivers/power/max8998_charger.c @@ -0,0 +1,211 @@ +/* + * max8998_charger.c - Power supply consumer driver for the Maxim 8998/LP3974 + * + * Copyright (C) 2009-2010 Samsung Electronics + * MyungJoo Ham <myungjoo.ham@samsung.com> + * + * 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 <linux/err.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/mfd/max8998.h> +#include <linux/mfd/max8998-private.h> + +struct max8998_battery_data { + struct device *dev; + struct max8998_dev *iodev; + struct power_supply battery; +}; + +static enum power_supply_property max8998_battery_props[] = { + POWER_SUPPLY_PROP_PRESENT, /* the presence of battery */ + POWER_SUPPLY_PROP_ONLINE, /* charger is active or not */ +}; + +/* Note that the charger control is done by a current regulator "CHARGER" */ +static int max8998_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max8998_battery_data *max8998 = container_of(psy, + struct max8998_battery_data, battery); + struct i2c_client *i2c = max8998->iodev->i2c; + int ret; + u8 reg; + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + ret = max8998_read_reg(i2c, MAX8998_REG_STATUS2, ®); + if (ret) + return ret; + if (reg & (1 << 4)) + val->intval = 0; + else + val->intval = 1; + break; + case POWER_SUPPLY_PROP_ONLINE: + ret = max8998_read_reg(i2c, MAX8998_REG_STATUS2, ®); + if (ret) + return ret; + if (reg & (1 << 3)) + val->intval = 0; + else + val->intval = 1; + break; + default: + return -EINVAL; + } + + return 0; +} + +static __devinit int max8998_battery_probe(struct platform_device *pdev) +{ + struct max8998_dev *iodev = dev_get_drvdata(pdev->dev.parent); + struct max8998_platform_data *pdata = dev_get_platdata(iodev->dev); + struct max8998_battery_data *max8998; + struct i2c_client *i2c; + int ret = 0; + + if (!pdata) { + dev_err(pdev->dev.parent, "No platform init data supplied\n"); + return -ENODEV; + } + + max8998 = kzalloc(sizeof(struct max8998_battery_data), GFP_KERNEL); + if (!max8998) + return -ENOMEM; + + max8998->dev = &pdev->dev; + max8998->iodev = iodev; + platform_set_drvdata(pdev, max8998); + i2c = max8998->iodev->i2c; + + /* Setup "End of Charge" */ + /* If EOC value equals 0, + * remain value set from bootloader or default value */ + if (pdata->eoc >= 10 && pdata->eoc <= 45) { + max8998_update_reg(i2c, MAX8998_REG_CHGR1, + (pdata->eoc / 5 - 2) << 5, 0x7 << 5); + } else if (pdata->eoc == 0) { + dev_dbg(max8998->dev, + "EOC value not set: leave it unchanged.\n"); + } else { + dev_err(max8998->dev, "Invalid EOC value\n"); + ret = -EINVAL; + goto err; + } + + /* Setup Charge Restart Level */ + switch (pdata->restart) { + case 100: + max8998_update_reg(i2c, MAX8998_REG_CHGR1, 0x1 << 3, 0x3 << 3); + break; + case 150: + max8998_update_reg(i2c, MAX8998_REG_CHGR1, 0x0 << 3, 0x3 << 3); + break; + case 200: + max8998_update_reg(i2c, MAX8998_REG_CHGR1, 0x2 << 3, 0x3 << 3); + break; + case -1: + max8998_update_reg(i2c, MAX8998_REG_CHGR1, 0x3 << 3, 0x3 << 3); + break; + case 0: + dev_dbg(max8998->dev, + "Restart Level not set: leave it unchanged.\n"); + break; + default: + dev_err(max8998->dev, "Invalid Restart Level\n"); + ret = -EINVAL; + goto err; + } + + /* Setup Charge Full Timeout */ + switch (pdata->timeout) { + case 5: + max8998_update_reg(i2c, MAX8998_REG_CHGR2, 0x0 << 4, 0x3 << 4); + break; + case 6: + max8998_update_reg(i2c, MAX8998_REG_CHGR2, 0x1 << 4, 0x3 << 4); + break; + case 7: + max8998_update_reg(i2c, MAX8998_REG_CHGR2, 0x2 << 4, 0x3 << 4); + break; + case -1: + max8998_update_reg(i2c, MAX8998_REG_CHGR2, 0x3 << 4, 0x3 << 4); + break; + case 0: + dev_dbg(max8998->dev, + "Full Timeout not set: leave it unchanged.\n"); + break; + default: + dev_err(max8998->dev, "Invalid Full Timeout value\n"); + ret = -EINVAL; + goto err; + } + + max8998->battery.name = "max8998_pmic"; + max8998->battery.type = POWER_SUPPLY_TYPE_BATTERY; + max8998->battery.get_property = max8998_battery_get_property; + max8998->battery.properties = max8998_battery_props; + max8998->battery.num_properties = ARRAY_SIZE(max8998_battery_props); + + ret = power_supply_register(max8998->dev, &max8998->battery); + if (ret) { + dev_err(max8998->dev, "failed: power supply register\n"); + goto err; + } + + return 0; +err: + kfree(max8998); + return ret; +} + +static int __devexit max8998_battery_remove(struct platform_device *pdev) +{ + struct max8998_battery_data *max8998 = platform_get_drvdata(pdev); + + power_supply_unregister(&max8998->battery); + kfree(max8998); + + return 0; +} + +static const struct platform_device_id max8998_battery_id[] = { + { "max8998-battery", TYPE_MAX8998 }, + { } +}; + +static struct platform_driver max8998_battery_driver = { + .driver = { + .name = "max8998-battery", + .owner = THIS_MODULE, + }, + .probe = max8998_battery_probe, + .remove = __devexit_p(max8998_battery_remove), + .id_table = max8998_battery_id, +}; + +module_platform_driver(max8998_battery_driver); + +MODULE_DESCRIPTION("MAXIM 8998 battery control driver"); +MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:max8998-battery"); diff --git a/drivers/power/olpc_battery.c b/drivers/power/olpc_battery.c new file mode 100644 index 00000000..7385092f --- /dev/null +++ b/drivers/power/olpc_battery.c @@ -0,0 +1,637 @@ +/* + * Battery driver for One Laptop Per Child board. + * + * Copyright © 2006-2010 David Woodhouse <dwmw2@infradead.org> + * + * 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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/err.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/jiffies.h> +#include <linux/sched.h> +#include <asm/olpc.h> + + +#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 power_supply olpc_bat = { + .name = "olpc-battery", + .get_property = olpc_bat_get_property, + .use_for_apm = 1, +}; + +static int olpc_battery_suspend(struct platform_device *pdev, + pm_message_t state) +{ + if (device_may_wakeup(olpc_ac.dev)) + olpc_ec_wakeup_set(EC_SCI_SRC_ACPWR); + else + olpc_ec_wakeup_clear(EC_SCI_SRC_ACPWR); + + if (device_may_wakeup(olpc_bat.dev)) + olpc_ec_wakeup_set(EC_SCI_SRC_BATTERY | EC_SCI_SRC_BATSOC + | EC_SCI_SRC_BATERR); + else + olpc_ec_wakeup_clear(EC_SCI_SRC_BATTERY | EC_SCI_SRC_BATSOC + | EC_SCI_SRC_BATERR); + + return 0; +} + +static int __devinit olpc_battery_probe(struct platform_device *pdev) +{ + int ret; + uint8_t status; + + /* + * 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 */ + + ret = power_supply_register(&pdev->dev, &olpc_ac); + if (ret) + return ret; + + 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(&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; + + if (olpc_ec_wakeup_available()) { + device_set_wakeup_capable(olpc_ac.dev, true); + device_set_wakeup_capable(olpc_bat.dev, true); + } + + return 0; + +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); + return ret; +} + +static int __devexit olpc_battery_remove(struct platform_device *pdev) +{ + 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); + return 0; +} + +static const struct of_device_id olpc_battery_ids[] __devinitconst = { + { .compatible = "olpc,xo1-battery" }, + {} +}; +MODULE_DEVICE_TABLE(of, olpc_battery_ids); + +static struct platform_driver olpc_battery_driver = { + .driver = { + .name = "olpc-battery", + .owner = THIS_MODULE, + .of_match_table = olpc_battery_ids, + }, + .probe = olpc_battery_probe, + .remove = __devexit_p(olpc_battery_remove), + .suspend = olpc_battery_suspend, +}; + +module_platform_driver(olpc_battery_driver); + +MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); +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..3d1e9efb --- /dev/null +++ b/drivers/power/pcf50633-charger.c @@ -0,0 +1,482 @@ +/* NXP PCF50633 Main Battery Charger Driver + * + * (C) 2006-2008 by Openmoko, Inc. + * Author: Balaji Rao <balajirrao@openmoko.org> + * 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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/device.h> +#include <linux/sysfs.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> + +#include <linux/mfd/pcf50633/core.h> +#include <linux/mfd/pcf50633/mbc.h> + +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), +}; + +module_platform_driver(pcf50633_mbc_driver); + +MODULE_AUTHOR("Balaji Rao <balajirrao@openmoko.org>"); +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..214468f4 --- /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 <cbou@mail.ru> + * + * 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 <linux/module.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/notifier.h> +#include <linux/power_supply.h> +#include <linux/pda_power.h> +#include <linux/regulator/consumer.h> +#include <linux/timer.h> +#include <linux/jiffies.h> +#include <linux/usb/otg.h> + +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; + +#ifdef CONFIG_USB_OTG_UTILS +static struct usb_phy *transceiver; +static struct notifier_block otg_nb; +#endif + +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); + } + +#ifdef CONFIG_USB_OTG_UTILS + transceiver = usb_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; + } +#endif + + 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; + } + } + +#ifdef CONFIG_USB_OTG_UTILS + if (transceiver && pdata->use_otg_notifier) { + otg_nb.notifier_call = otg_handle_notification; + ret = usb_register_notifier(transceiver, &otg_nb); + if (ret) { + dev_err(dev, "failure to register otg notifier\n"); + goto otg_reg_notifier_failed; + } + polling = 0; + } +#endif + + 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; + +#ifdef CONFIG_USB_OTG_UTILS +otg_reg_notifier_failed: + if (pdata->is_usb_online && usb_irq) + free_irq(usb_irq->start, &pda_psy_usb); +#endif +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); +#ifdef CONFIG_USB_OTG_UTILS + if (transceiver) + usb_put_transceiver(transceiver); +#endif +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) + usb_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 */ + +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, +}; + +module_platform_driver(pda_power_pdrv); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Anton Vorontsov <cbou@mail.ru>"); +MODULE_ALIAS("platform:pda-power"); 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 <dwmw2@infradead.org> + * + * 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 <linux/module.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/power_supply.h> +#include <linux/adb.h> +#include <linux/pmu.h> +#include <linux/slab.h> + +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 <dwmw2@infradead.org>"); +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..cc439fd8 --- /dev/null +++ b/drivers/power/power_supply.h @@ -0,0 +1,42 @@ +/* + * Functions private to power supply class + * + * Copyright © 2007 Anton Vorontsov <cbou@mail.ru> + * Copyright © 2004 Szabolcs Gyurko + * Copyright © 2003 Ian Molton <spyro@f2s.com> + * + * Modified: 2004, Oct Szabolcs Gyurko + * + * You may use this code as per GPL version 2 + */ + +struct device; +struct device_type; +struct power_supply; + +#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..773fca15 --- /dev/null +++ b/drivers/power/power_supply_core.c @@ -0,0 +1,275 @@ +/* + * Universal power supply monitor class + * + * Copyright © 2007 Anton Vorontsov <cbou@mail.ru> + * Copyright © 2004 Szabolcs Gyurko + * Copyright © 2003 Ian Molton <spyro@f2s.com> + * + * Modified: 2004, Oct Szabolcs Gyurko + * + * You may use this code as per GPL version 2 + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/power_supply.h> +#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) + pm_relax(psy->dev); + 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; + pm_stay_awake(psy->dev); + 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_is_system_supplied(struct device *dev, void *data) +{ + union power_supply_propval ret = {0,}; + struct power_supply *psy = dev_get_drvdata(dev); + unsigned int *count = data; + + (*count)++; + 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; + unsigned int count = 0; + + error = class_for_each_device(power_supply_class, NULL, &count, + __power_supply_is_system_supplied); + + /* + * If no power class device was found at all, most probably we are + * running on a desktop system, so assume we are on mains power. + */ + if (count == 0) + return 1; + + 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); + +int power_supply_powers(struct power_supply *psy, struct device *dev) +{ + return sysfs_create_link(&psy->dev->kobj, &dev->kobj, "powers"); +} +EXPORT_SYMBOL_GPL(power_supply_powers); + +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; + + rc = device_add(dev); + if (rc) + goto device_add_failed; + + spin_lock_init(&psy->changed_lock); + rc = device_init_wakeup(dev, true); + if (rc) + goto wakeup_init_failed; + + rc = power_supply_create_triggers(psy); + if (rc) + goto create_triggers_failed; + + power_supply_changed(psy); + + goto success; + +create_triggers_failed: +wakeup_init_failed: + 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); + sysfs_remove_link(&psy->dev->kobj, "powers"); + power_supply_remove_triggers(psy); + 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 <spyro@f2s.com>, " + "Szabolcs Gyurko, " + "Anton Vorontsov <cbou@mail.ru>"); +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..995f966e --- /dev/null +++ b/drivers/power/power_supply_leds.c @@ -0,0 +1,180 @@ +/* + * LEDs triggers for power supply class + * + * Copyright © 2007 Anton Vorontsov <cbou@mail.ru> + * Copyright © 2004 Szabolcs Gyurko + * Copyright © 2003 Ian Molton <spyro@f2s.com> + * + * Modified: 2004, Oct Szabolcs Gyurko + * + * You may use this code as per GPL version 2 + */ + +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/power_supply.h> +#include <linux/slab.h> + +#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..b20acfa9 --- /dev/null +++ b/drivers/power/power_supply_sysfs.c @@ -0,0 +1,317 @@ +/* + * Sysfs interface for the universal power supply monitor class + * + * Copyright © 2007 David Woodhouse <dwmw2@infradead.org> + * Copyright © 2007 Anton Vorontsov <cbou@mail.ru> + * Copyright © 2004 Szabolcs Gyurko + * Copyright © 2003 Ian Molton <spyro@f2s.com> + * + * Modified: 2004, Oct Szabolcs Gyurko + * + * You may use this code as per GPL version 2 + */ + +#include <linux/ctype.h> +#include <linux/device.h> +#include <linux/power_supply.h> +#include <linux/slab.h> +#include <linux/stat.h> + +#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[] = { + "Unknown", "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" + }; + static char *scope_text[] = { + "Unknown", "System", "Device" + }; + 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: %zd\n", + attr->attr.name, ret); + 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_SCOPE) + return sprintf(buf, "%s\n", scope_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), + POWER_SUPPLY_ATTR(scope), + /* Local extensions */ + POWER_SUPPLY_ATTR(usb_hc), + POWER_SUPPLY_ATTR(usb_otg), + POWER_SUPPLY_ATTR(charge_enabled), + /* 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 umode_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); + umode_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/s3c_adc_battery.c b/drivers/power/s3c_adc_battery.c new file mode 100644 index 00000000..8b804a56 --- /dev/null +++ b/drivers/power/s3c_adc_battery.c @@ -0,0 +1,447 @@ +/* + * 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 <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/leds.h> +#include <linux/gpio.h> +#include <linux/err.h> +#include <linux/timer.h> +#include <linux/jiffies.h> +#include <linux/s3c_adc_battery.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/module.h> + +#include <plat/adc.h> + +#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 int gather_samples(struct s3c_adc_client *client, int num, int channel) +{ + int value, i; + + /* default to 1 if nothing is set */ + if (num < 1) + num = 1; + + value = 0; + for (i = 0; i < num; i++) + value += s3c_adc_read(client, channel); + value /= num; + + return value; +} + +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 = gather_samples(bat->client, + bat->pdata->backup_volt_samples, + 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 = gather_samples(bat->client, + bat->pdata->volt_samples, + bat->pdata->volt_channel) * bat->pdata->volt_mult; + bat->cur_value = gather_samples(bat->client, + bat->pdata->current_samples, + 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 __devinit 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, +}; + +module_platform_driver(s3c_adc_bat_driver); + +MODULE_AUTHOR("Vasily Khoruzhick <anarsoul@gmail.com>"); +MODULE_DESCRIPTION("iPAQ H1930/H1940/RX1950 battery controller driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/sbs-battery.c b/drivers/power/sbs-battery.c new file mode 100644 index 00000000..06b659d9 --- /dev/null +++ b/drivers/power/sbs-battery.c @@ -0,0 +1,858 @@ +/* + * Gas Gauge driver for SBS Compliant Batteries + * + * 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 <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/power_supply.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/gpio.h> + +#include <linux/power/sbs-battery.h> + +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 sbs_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 SBS_DATA(_psp, _addr, _min_value, _max_value) { \ + .psp = _psp, \ + .addr = _addr, \ + .min_value = _min_value, \ + .max_value = _max_value, \ +} + +static const struct chip_data { + enum power_supply_property psp; + u8 addr; + int min_value; + int max_value; +} sbs_data[] = { + [REG_MANUFACTURER_DATA] = + SBS_DATA(POWER_SUPPLY_PROP_PRESENT, 0x00, 0, 65535), + [REG_TEMPERATURE] = + SBS_DATA(POWER_SUPPLY_PROP_TEMP, 0x08, 0, 65535), + [REG_VOLTAGE] = + SBS_DATA(POWER_SUPPLY_PROP_VOLTAGE_NOW, 0x09, 0, 20000), + [REG_CURRENT] = + SBS_DATA(POWER_SUPPLY_PROP_CURRENT_NOW, 0x0A, -32768, 32767), + [REG_CAPACITY] = + SBS_DATA(POWER_SUPPLY_PROP_CAPACITY, 0x0E, 0, 100), + [REG_REMAINING_CAPACITY] = + SBS_DATA(POWER_SUPPLY_PROP_ENERGY_NOW, 0x0F, 0, 65535), + [REG_REMAINING_CAPACITY_CHARGE] = + SBS_DATA(POWER_SUPPLY_PROP_CHARGE_NOW, 0x0F, 0, 65535), + [REG_FULL_CHARGE_CAPACITY] = + SBS_DATA(POWER_SUPPLY_PROP_ENERGY_FULL, 0x10, 0, 65535), + [REG_FULL_CHARGE_CAPACITY_CHARGE] = + SBS_DATA(POWER_SUPPLY_PROP_CHARGE_FULL, 0x10, 0, 65535), + [REG_TIME_TO_EMPTY] = + SBS_DATA(POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, 0x12, 0, 65535), + [REG_TIME_TO_FULL] = + SBS_DATA(POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, 0x13, 0, 65535), + [REG_STATUS] = + SBS_DATA(POWER_SUPPLY_PROP_STATUS, 0x16, 0, 65535), + [REG_CYCLE_COUNT] = + SBS_DATA(POWER_SUPPLY_PROP_CYCLE_COUNT, 0x17, 0, 65535), + [REG_DESIGN_CAPACITY] = + SBS_DATA(POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, 0x18, 0, 65535), + [REG_DESIGN_CAPACITY_CHARGE] = + SBS_DATA(POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, 0x18, 0, 65535), + [REG_DESIGN_VOLTAGE] = + SBS_DATA(POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, 0x19, 0, 65535), + [REG_SERIAL_NUMBER] = + SBS_DATA(POWER_SUPPLY_PROP_SERIAL_NUMBER, 0x1C, 0, 65535), +}; + +static enum power_supply_property sbs_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 sbs_info { + struct i2c_client *client; + struct power_supply power_supply; + struct sbs_platform_data *pdata; + bool is_present; + bool gpio_detect; + bool enable_detection; + int irq; + int last_state; + int poll_time; + struct delayed_work work; + int ignore_changes; +}; + +static int sbs_read_word_data(struct i2c_client *client, u8 address) +{ + struct sbs_info *chip = i2c_get_clientdata(client); + s32 ret = 0; + int retries = 1; + + if (chip->pdata) + retries = max(chip->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 sbs_write_word_data(struct i2c_client *client, u8 address, + u16 value) +{ + struct sbs_info *chip = i2c_get_clientdata(client); + s32 ret = 0; + int retries = 1; + + if (chip->pdata) + retries = max(chip->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 sbs_get_battery_presence_and_health( + struct i2c_client *client, enum power_supply_property psp, + union power_supply_propval *val) +{ + s32 ret; + struct sbs_info *chip = i2c_get_clientdata(client); + + if (psp == POWER_SUPPLY_PROP_PRESENT && + chip->gpio_detect) { + ret = gpio_get_value(chip->pdata->battery_detect); + if (ret == chip->pdata->battery_detect_present) + val->intval = 1; + else + val->intval = 0; + chip->is_present = val->intval; + return ret; + } + + /* Write to ManufacturerAccess with + * ManufacturerAccess command and then + * read the status */ + ret = sbs_write_word_data(client, sbs_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 = sbs_read_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr); + if (ret < 0) + return ret; + + if (ret < sbs_data[REG_MANUFACTURER_DATA].min_value || + ret > sbs_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 sbs_get_battery_property(struct i2c_client *client, + int reg_offset, enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sbs_info *chip = i2c_get_clientdata(client); + s32 ret; + + ret = sbs_read_word_data(client, sbs_data[reg_offset].addr); + if (ret < 0) + return ret; + + /* returned values are 16 bit */ + if (sbs_data[reg_offset].min_value < 0) + ret = (s16)ret; + + if (ret >= sbs_data[reg_offset].min_value && + ret <= sbs_data[reg_offset].max_value) { + val->intval = ret; + if (psp != POWER_SUPPLY_PROP_STATUS) + return 0; + + 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; + + if (chip->poll_time == 0) + chip->last_state = val->intval; + else if (chip->last_state != val->intval) { + cancel_delayed_work_sync(&chip->work); + power_supply_changed(&chip->power_supply); + chip->poll_time = 0; + } + } else { + if (psp == POWER_SUPPLY_PROP_STATUS) + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + else + val->intval = 0; + } + + return 0; +} + +static void sbs_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: + /* sbs 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: + /* sbs 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: + /* sbs 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 sbs_battery_mode sbs_set_battery_mode(struct i2c_client *client, + enum sbs_battery_mode mode) +{ + int ret, original_val; + + original_val = sbs_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 = sbs_write_word_data(client, BATTERY_MODE_OFFSET, ret); + if (ret < 0) + return ret; + + return original_val & BATTERY_MODE_MASK; +} + +static int sbs_get_battery_capacity(struct i2c_client *client, + int reg_offset, enum power_supply_property psp, + union power_supply_propval *val) +{ + s32 ret; + enum sbs_battery_mode mode = BATTERY_MODE_WATTS; + + if (power_supply_is_amp_property(psp)) + mode = BATTERY_MODE_AMPS; + + mode = sbs_set_battery_mode(client, mode); + if (mode < 0) + return mode; + + ret = sbs_read_word_data(client, sbs_data[reg_offset].addr); + if (ret < 0) + return ret; + + if (psp == POWER_SUPPLY_PROP_CAPACITY) { + /* sbs spec says that this can be >100 % + * even if max value is 100 % */ + val->intval = min(ret, 100); + } else + val->intval = ret; + + ret = sbs_set_battery_mode(client, mode); + if (ret < 0) + return ret; + + return 0; +} + +static char sbs_serial[5]; +static int sbs_get_battery_serial_number(struct i2c_client *client, + union power_supply_propval *val) +{ + int ret; + + ret = sbs_read_word_data(client, sbs_data[REG_SERIAL_NUMBER].addr); + if (ret < 0) + return ret; + + ret = sprintf(sbs_serial, "%04x", ret); + val->strval = sbs_serial; + + return 0; +} + +static int sbs_get_property_index(struct i2c_client *client, + enum power_supply_property psp) +{ + int count; + for (count = 0; count < ARRAY_SIZE(sbs_data); count++) + if (psp == sbs_data[count].psp) + return count; + + dev_warn(&client->dev, + "%s: Invalid Property - %d\n", __func__, psp); + + return -EINVAL; +} + +static int sbs_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret = 0; + struct sbs_info *chip = container_of(psy, + struct sbs_info, power_supply); + struct i2c_client *client = chip->client; + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + case POWER_SUPPLY_PROP_HEALTH: + ret = sbs_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 = sbs_get_property_index(client, psp); + if (ret < 0) + break; + + ret = sbs_get_battery_capacity(client, ret, psp, val); + break; + + case POWER_SUPPLY_PROP_SERIAL_NUMBER: + ret = sbs_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 = sbs_get_property_index(client, psp); + if (ret < 0) + break; + + ret = sbs_get_battery_property(client, ret, psp, val); + break; + + default: + dev_err(&client->dev, + "%s: INVALID property\n", __func__); + return -EINVAL; + } + + if (!chip->enable_detection) + goto done; + + if (!chip->gpio_detect && + chip->is_present != (ret >= 0)) { + chip->is_present = (ret >= 0); + power_supply_changed(&chip->power_supply); + } + +done: + if (!ret) { + /* Convert units to match requirements for power supply class */ + sbs_unit_adjustment(client, psp, val); + } + + dev_dbg(&client->dev, + "%s: property = %d, value = %x\n", __func__, psp, val->intval); + + if (ret && chip->is_present) + return ret; + + /* battery not present, so return NODATA for properties */ + if (ret) + return -ENODATA; + + return 0; +} + +static irqreturn_t sbs_irq(int irq, void *devid) +{ + struct power_supply *battery = devid; + + power_supply_changed(battery); + + return IRQ_HANDLED; +} + +static void sbs_external_power_changed(struct power_supply *psy) +{ + struct sbs_info *chip; + + chip = container_of(psy, struct sbs_info, power_supply); + + if (chip->ignore_changes > 0) { + chip->ignore_changes--; + return; + } + + /* cancel outstanding work */ + cancel_delayed_work_sync(&chip->work); + + schedule_delayed_work(&chip->work, HZ); + chip->poll_time = chip->pdata->poll_retry_count; +} + +static void sbs_delayed_work(struct work_struct *work) +{ + struct sbs_info *chip; + s32 ret; + + chip = container_of(work, struct sbs_info, work.work); + + ret = sbs_read_word_data(chip->client, sbs_data[REG_STATUS].addr); + /* if the read failed, give up on this work */ + if (ret < 0) { + chip->poll_time = 0; + return; + } + + if (ret & BATTERY_FULL_CHARGED) + ret = POWER_SUPPLY_STATUS_FULL; + else if (ret & BATTERY_FULL_DISCHARGED) + ret = POWER_SUPPLY_STATUS_NOT_CHARGING; + else if (ret & BATTERY_DISCHARGING) + ret = POWER_SUPPLY_STATUS_DISCHARGING; + else + ret = POWER_SUPPLY_STATUS_CHARGING; + + if (chip->last_state != ret) { + chip->poll_time = 0; + power_supply_changed(&chip->power_supply); + return; + } + if (chip->poll_time > 0) { + schedule_delayed_work(&chip->work, HZ); + chip->poll_time--; + return; + } +} + +#if defined(CONFIG_OF) + +#include <linux/of_device.h> +#include <linux/of_gpio.h> + +static const struct of_device_id sbs_dt_ids[] = { + { .compatible = "sbs,sbs-battery" }, + { .compatible = "ti,bq20z75" }, + { } +}; +MODULE_DEVICE_TABLE(of, sbs_dt_ids); + +static struct sbs_platform_data *sbs_of_populate_pdata( + struct i2c_client *client) +{ + struct device_node *of_node = client->dev.of_node; + struct sbs_platform_data *pdata = client->dev.platform_data; + enum of_gpio_flags gpio_flags; + int rc; + u32 prop; + + /* verify this driver matches this device */ + if (!of_node) + return NULL; + + /* if platform data is set, honor it */ + if (pdata) + return pdata; + + /* first make sure at least one property is set, otherwise + * it won't change behavior from running without pdata. + */ + if (!of_get_property(of_node, "sbs,i2c-retry-count", NULL) && + !of_get_property(of_node, "sbs,poll-retry-count", NULL) && + !of_get_property(of_node, "sbs,battery-detect-gpios", NULL)) + goto of_out; + + pdata = devm_kzalloc(&client->dev, sizeof(struct sbs_platform_data), + GFP_KERNEL); + if (!pdata) + goto of_out; + + rc = of_property_read_u32(of_node, "sbs,i2c-retry-count", &prop); + if (!rc) + pdata->i2c_retry_count = prop; + + rc = of_property_read_u32(of_node, "sbs,poll-retry-count", &prop); + if (!rc) + pdata->poll_retry_count = prop; + + if (!of_get_property(of_node, "sbs,battery-detect-gpios", NULL)) { + pdata->battery_detect = -1; + goto of_out; + } + + pdata->battery_detect = of_get_named_gpio_flags(of_node, + "sbs,battery-detect-gpios", 0, &gpio_flags); + + if (gpio_flags & OF_GPIO_ACTIVE_LOW) + pdata->battery_detect_present = 0; + else + pdata->battery_detect_present = 1; + +of_out: + return pdata; +} +#else +#define sbs_dt_ids NULL +static struct sbs_platform_data *sbs_of_populate_pdata( + struct i2c_client *client) +{ + return client->dev.platform_data; +} +#endif + +static int __devinit sbs_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct sbs_info *chip; + struct sbs_platform_data *pdata = client->dev.platform_data; + int rc; + int irq; + char *name; + + name = kasprintf(GFP_KERNEL, "sbs-%s", dev_name(&client->dev)); + if (!name) { + dev_err(&client->dev, "Failed to allocate device name\n"); + return -ENOMEM; + } + + chip = kzalloc(sizeof(struct sbs_info), GFP_KERNEL); + if (!chip) { + rc = -ENOMEM; + goto exit_free_name; + } + + chip->client = client; + chip->enable_detection = false; + chip->gpio_detect = false; + chip->power_supply.name = name; + chip->power_supply.type = POWER_SUPPLY_TYPE_BATTERY; + chip->power_supply.properties = sbs_properties; + chip->power_supply.num_properties = ARRAY_SIZE(sbs_properties); + chip->power_supply.get_property = sbs_get_property; + /* ignore first notification of external change, it is generated + * from the power_supply_register call back + */ + chip->ignore_changes = 1; + chip->last_state = POWER_SUPPLY_STATUS_UNKNOWN; + chip->power_supply.external_power_changed = sbs_external_power_changed; + + pdata = sbs_of_populate_pdata(client); + + if (pdata) { + chip->gpio_detect = gpio_is_valid(pdata->battery_detect); + chip->pdata = pdata; + } + + i2c_set_clientdata(client, chip); + + if (!chip->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); + chip->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); + chip->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); + chip->gpio_detect = false; + goto skip_gpio; + } + + rc = request_irq(irq, sbs_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + dev_name(&client->dev), &chip->power_supply); + if (rc) { + dev_warn(&client->dev, "Failed to request irq: %d\n", rc); + gpio_free(pdata->battery_detect); + chip->gpio_detect = false; + goto skip_gpio; + } + + chip->irq = irq; + +skip_gpio: + + rc = power_supply_register(&client->dev, &chip->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); + + INIT_DELAYED_WORK(&chip->work, sbs_delayed_work); + + chip->enable_detection = true; + + return 0; + +exit_psupply: + if (chip->irq) + free_irq(chip->irq, &chip->power_supply); + if (chip->gpio_detect) + gpio_free(pdata->battery_detect); + + kfree(chip); + +exit_free_name: + kfree(name); + + return rc; +} + +static int __devexit sbs_remove(struct i2c_client *client) +{ + struct sbs_info *chip = i2c_get_clientdata(client); + + if (chip->irq) + free_irq(chip->irq, &chip->power_supply); + if (chip->gpio_detect) + gpio_free(chip->pdata->battery_detect); + + power_supply_unregister(&chip->power_supply); + + cancel_delayed_work_sync(&chip->work); + + kfree(chip->power_supply.name); + kfree(chip); + chip = NULL; + + return 0; +} + +#if defined CONFIG_PM +static int sbs_suspend(struct i2c_client *client, + pm_message_t state) +{ + struct sbs_info *chip = i2c_get_clientdata(client); + s32 ret; + + if (chip->poll_time > 0) + cancel_delayed_work_sync(&chip->work); + + /* write to manufacturer access with sleep command */ + ret = sbs_write_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr, + MANUFACTURER_ACCESS_SLEEP); + if (chip->is_present && ret < 0) + return ret; + + return 0; +} +#else +#define sbs_suspend NULL +#endif +/* any smbus transaction will wake up sbs */ +#define sbs_resume NULL + +static const struct i2c_device_id sbs_id[] = { + { "bq20z75", 0 }, + { "sbs-battery", 1 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, sbs_id); + +static struct i2c_driver sbs_battery_driver = { + .probe = sbs_probe, + .remove = __devexit_p(sbs_remove), + .suspend = sbs_suspend, + .resume = sbs_resume, + .id_table = sbs_id, + .driver = { + .name = "sbs-battery", + .of_match_table = sbs_dt_ids, + }, +}; +module_i2c_driver(sbs_battery_driver); + +MODULE_DESCRIPTION("SBS battery monitor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/smb347-charger.c b/drivers/power/smb347-charger.c new file mode 100644 index 00000000..fb56ec75 --- /dev/null +++ b/drivers/power/smb347-charger.c @@ -0,0 +1,1600 @@ +/* + * Summit Microelectronics SMB347 Battery Charger Driver + * + * Copyright (C) 2011, Intel Corporation + * + * Authors: Bruce E. Robertson <bruce.e.robertson@intel.com> + * Mika Westerberg <mika.westerberg@linux.intel.com> + * + * 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 <linux/debugfs.h> +#include <linux/gpio.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/power_supply.h> +#include <linux/power/smb347-charger.h> +#include <linux/seq_file.h> +#include <linux/delay.h> + +/* + * Configuration registers. These are mirrored to volatile RAM and can be + * written once %CMD_A_ALLOW_WRITE is set in %CMD_A register. They will be + * reloaded from non-volatile registers after POR. + */ +#define CFG_CHARGE_CURRENT 0x00 +#define CFG_CHARGE_CURRENT_FCC_MASK 0xe0 +#define CFG_CHARGE_CURRENT_FCC_SHIFT 5 +#define CFG_CHARGE_CURRENT_PCC_MASK 0x18 +#define CFG_CHARGE_CURRENT_PCC_SHIFT 3 +#define CFG_CHARGE_CURRENT_TC_MASK 0x07 +#define CFG_CURRENT_LIMIT 0x01 +#define CFG_CURRENT_LIMIT_DC_MASK 0xf0 +#define CFG_CURRENT_LIMIT_DC_SHIFT 4 +#define CFG_CURRENT_LIMIT_USB_MASK 0x0f +#define CFG_VARIOUS_FUNCTION 0x02 +#define CFG_INPUT_SOURCE_PRIORITY BIT(2) +#define CFG_FLOAT_VOLTAGE 0x03 +#define CFG_FLOAT_VOLTAGE_THRESHOLD_MASK 0xc0 +#define CFG_FLOAT_VOLTAGE_MASK 0x3F +#define CFG_FLOAT_VOLTAGE_THRESHOLD_SHIFT 6 +#define CFG_CHARGE_CONTROL 0x04 +#define CFG_AUTOMATIC_RECHARGE_DISABLE BIT(7) +#define CFG_STAT 0x05 +#define CFG_STAT_DISABLED BIT(5) +#define CFG_STAT_ACTIVE_HIGH BIT(7) +#define CFG_PIN 0x06 +#define CFG_PIN_EN_CTRL_MASK 0x60 +#define CFG_PIN_USB_MODE_CTRL BIT(4) +#define CFG_PIN_EN_CTRL_ACTIVE_HIGH 0x40 +#define CFG_PIN_EN_CTRL_ACTIVE_LOW 0x60 +#define CFG_PIN_EN_APSD_IRQ BIT(1) +#define CFG_PIN_EN_CHARGER_ERROR BIT(2) +#define CFG_THERM 0x07 +#define CFG_THERM_SOFT_HOT_COMPENSATION_MASK 0x03 +#define CFG_THERM_SOFT_HOT_COMPENSATION_SHIFT 0 +#define CFG_THERM_SOFT_COLD_COMPENSATION_MASK 0x0c +#define CFG_THERM_SOFT_COLD_COMPENSATION_SHIFT 2 +#define CFG_THERM_MONITOR_DISABLED BIT(4) +#define CFG_SYSOK 0x08 +#define CFG_SYSOK_SUSPEND_HARD_LIMIT_DISABLED BIT(2) +#define CFG_OTHER 0x09 +#define CFG_OTHER_RID_MASK 0xc0 +#define CFG_OTHER_RID_ENABLED_AUTO_OTG 0xc0 +#define CFG_OTG 0x0a +#define CFG_OTG_TEMP_THRESHOLD_MASK 0x30 +#define CFG_OTG_TEMP_THRESHOLD_SHIFT 4 +#define CFG_OTG_CC_COMPENSATION_MASK 0xc0 +#define CFG_OTG_CC_COMPENSATION_SHIFT 6 +#define CFG_TEMP_LIMIT 0x0b +#define CFG_TEMP_LIMIT_SOFT_HOT_MASK 0x03 +#define CFG_TEMP_LIMIT_SOFT_HOT_SHIFT 0 +#define CFG_TEMP_LIMIT_SOFT_COLD_MASK 0x0c +#define CFG_TEMP_LIMIT_SOFT_COLD_SHIFT 2 +#define CFG_TEMP_LIMIT_HARD_HOT_MASK 0x30 +#define CFG_TEMP_LIMIT_HARD_HOT_SHIFT 4 +#define CFG_TEMP_LIMIT_HARD_COLD_MASK 0xc0 +#define CFG_TEMP_LIMIT_HARD_COLD_SHIFT 6 +#define CFG_FAULT_IRQ 0x0c +#define CFG_FAULT_IRQ_DCIN_UV BIT(2) +#define CFG_STATUS_IRQ 0x0d +#define CFG_STATUS_IRQ_TERMINATION_OR_TAPER BIT(4) +#define CFG_ADDRESS 0x0e + +/* Command registers */ +#define CMD_A 0x30 +#define CMD_A_CHG_ENABLED BIT(1) +#define CMD_A_SUSPEND_ENABLED BIT(2) +#define CMD_A_OTG_ENABLE BIT(4) +#define CMD_A_ALLOW_WRITE BIT(7) +#define CMD_B 0x31 +#define CMD_B_POR BIT(7) +#define CMD_B_USB59_MODE BIT(1) +#define CMD_B_HC_MODE BIT(0) +#define CMD_C 0x33 + +/* Interrupt Status registers */ +#define IRQSTAT_A 0x35 +#define IRQSTAT_C 0x37 +#define IRQSTAT_C_TERMINATION_STAT BIT(0) +#define IRQSTAT_C_TERMINATION_IRQ BIT(1) +#define IRQSTAT_C_TAPER_IRQ BIT(3) +#define IRQSTAT_E 0x39 +#define IRQSTAT_E_USBIN_UV_STAT BIT(0) +#define IRQSTAT_E_USBIN_UV_IRQ BIT(1) +#define IRQSTAT_E_DCIN_UV_STAT BIT(4) +#define IRQSTAT_E_DCIN_UV_IRQ BIT(5) +#define IRQSTAT_F 0x3a + +/* Status registers */ +#define STAT_A 0x3b +#define STAT_A_FLOAT_VOLTAGE_MASK 0x3f +#define STAT_B 0x3c +#define STAT_C 0x3d +#define STAT_C_CHG_ENABLED BIT(0) +#define STAT_C_CHG_STATUS BIT(5) +#define STAT_C_CHG_MASK 0x06 +#define STAT_C_CHG_SHIFT 1 +#define STAT_C_CHARGER_ERROR BIT(6) +#define STAT_E 0x3f + +/** + * struct smb347_charger - smb347 charger instance + * @lock: protects concurrent access to online variables + * @client: pointer to i2c client + * @mains: power_supply instance for AC/DC power + * @usb: power_supply instance for USB power + * @battery: power_supply instance for battery + * @mains_online: is AC/DC input connected + * @usb_online: is USB input connected + * @charging_enabled: is charging enabled + * @dentry: for debugfs + * @pdata: pointer to platform data + */ +struct smb347_charger { + struct mutex lock; + struct i2c_client *client; + struct power_supply mains; + struct power_supply usb; + struct power_supply battery; + bool mains_online; + bool usb_online; + bool charging_enabled; + unsigned int mains_current_limit; + bool usb_hc_mode; + bool usb_otg_enabled; + bool is_fully_charged; + int en_gpio; + struct dentry *dentry; + const struct smb347_charger_platform_data *pdata; +}; + +/* Fast charge current in uA */ +static const unsigned int fcc_tbl[] = { + 700000, + 900000, + 1200000, + 1500000, + 1800000, + 2000000, + 2200000, + 2500000, +}; + +/* Pre-charge current in uA */ +static const unsigned int pcc_tbl[] = { + 100000, + 150000, + 200000, + 250000, +}; + +/* Termination current in uA */ +static const unsigned int tc_tbl[] = { + 37500, + 50000, + 100000, + 150000, + 200000, + 250000, + 500000, + 600000, +}; + +/* Input current limit in uA */ +static const unsigned int icl_tbl[] = { + 300000, + 500000, + 700000, + 900000, + 1200000, + 1500000, + 1800000, + 2000000, + 2200000, + 2500000, +}; + +/* Charge current compensation in uA */ +static const unsigned int ccc_tbl[] = { + 250000, + 700000, + 900000, + 1200000, +}; + +/* Convert register value to current using lookup table */ +static int hw_to_current(const unsigned int *tbl, size_t size, unsigned int val) +{ + if (val >= size) + return -EINVAL; + return tbl[val]; +} + +/* Convert current to register value using lookup table */ +static int current_to_hw(const unsigned int *tbl, size_t size, unsigned int val) +{ + size_t i; + + for (i = 0; i < size; i++) + if (val < tbl[i]) + break; + return i > 0 ? i - 1 : -EINVAL; +} + +static int smb347_read(struct smb347_charger *smb, u8 reg) +{ + int ret; + + ret = i2c_smbus_read_byte_data(smb->client, reg); + if (ret < 0) + dev_warn(&smb->client->dev, "failed to read reg 0x%x: %d\n", + reg, ret); + return ret; +} + +static int smb347_write(struct smb347_charger *smb, u8 reg, u8 val) +{ + int ret; + + ret = i2c_smbus_write_byte_data(smb->client, reg, val); + if (ret < 0) + dev_warn(&smb->client->dev, "failed to write reg 0x%x: %d\n", + reg, ret); + return ret; +} + +/** + * smb347_update_status - updates the charging status + * @smb: pointer to smb347 charger instance + * + * Function checks status of the charging and updates internal state + * accordingly. Returns %0 if there is no change in status, %1 if the + * status has changed and negative errno in case of failure. + */ +static int smb347_update_status(struct smb347_charger *smb) +{ + bool usb = false; + bool dc = false; + int ret; + + ret = smb347_read(smb, IRQSTAT_E); + if (ret < 0) + return ret; + + /* + * Dc and usb are set depending on whether they are enabled in + * platform data _and_ whether corresponding undervoltage is set. + */ + if (smb->pdata->use_mains) + dc = !(ret & IRQSTAT_E_DCIN_UV_STAT); + if (smb->pdata->use_usb) + usb = !(ret & IRQSTAT_E_USBIN_UV_STAT); + + mutex_lock(&smb->lock); + ret = smb->mains_online != dc || smb->usb_online != usb; + smb->mains_online = dc; + smb->usb_online = usb; + mutex_unlock(&smb->lock); + + return ret; +} + +/* + * smb347_is_online - returns whether input power source is connected + * @smb: pointer to smb347 charger instance + * + * Returns %true if input power source is connected. Note that this is + * dependent on what platform has configured for usable power sources. For + * example if USB is disabled, this will return %false even if the USB + * cable is connected. + */ +static bool smb347_is_online(struct smb347_charger *smb) +{ + bool ret; + + mutex_lock(&smb->lock); + ret = smb->usb_online || smb->mains_online; + mutex_unlock(&smb->lock); + + return ret; +} + +/** + * smb347_charging_status - returns status of charging + * @smb: pointer to smb347 charger instance + * + * Function returns charging status. %0 means no charging is in progress, + * %1 means pre-charging, %2 fast-charging and %3 taper-charging. + */ +static int smb347_charging_status(struct smb347_charger *smb) +{ + int ret; + + if (!smb347_is_online(smb)) + return 0; + + ret = smb347_read(smb, STAT_C); + if (ret < 0) + return 0; + + return (ret & STAT_C_CHG_MASK) >> STAT_C_CHG_SHIFT; +} + +static int smb347_charging_set(struct smb347_charger *smb, bool enable) +{ + int ret = 0; + + if (enable && !smb->charging_enabled) + smb->is_fully_charged = false; + + if (smb->pdata->enable_control != SMB347_CHG_ENABLE_SW) { + smb->charging_enabled = enable; + + if (smb->en_gpio) + gpio_set_value( + smb->en_gpio, + (smb->pdata->enable_control == + SMB347_CHG_ENABLE_PIN_ACTIVE_LOW) ^ enable); + return 0; + } + + mutex_lock(&smb->lock); + if (smb->charging_enabled != enable) { + ret = smb347_read(smb, CMD_A); + if (ret < 0) + goto out; + + smb->charging_enabled = enable; + + if (enable) + ret |= CMD_A_CHG_ENABLED; + else + ret &= ~CMD_A_CHG_ENABLED; + + ret = smb347_write(smb, CMD_A, ret); + } +out: + mutex_unlock(&smb->lock); + return ret; +} + +static inline int smb347_charging_enable(struct smb347_charger *smb) +{ + return smb347_charging_set(smb, true); +} + +static inline int smb347_charging_disable(struct smb347_charger *smb) +{ + return smb347_charging_set(smb, false); +} + +static int smb347_update_online(struct smb347_charger *smb) +{ + int ret; + + /* + * Depending on whether valid power source is connected or not, we + * disable or enable the charging. We do it manually because it + * depends on how the platform has configured the valid inputs. + */ + if (smb347_is_online(smb)) { + ret = smb347_charging_enable(smb); + if (ret < 0) + dev_err(&smb->client->dev, + "failed to enable charging\n"); + } else { + ret = smb347_charging_disable(smb); + if (ret < 0) + dev_err(&smb->client->dev, + "failed to disable charging\n"); + } + + return ret; +} + +static int smb347_set_charge_current(struct smb347_charger *smb) +{ + int ret, val; + + ret = smb347_read(smb, CFG_CHARGE_CURRENT); + if (ret < 0) + return ret; + + if (smb->pdata->max_charge_current) { + val = current_to_hw(fcc_tbl, ARRAY_SIZE(fcc_tbl), + smb->pdata->max_charge_current); + if (val < 0) + return val; + + ret &= ~CFG_CHARGE_CURRENT_FCC_MASK; + ret |= val << CFG_CHARGE_CURRENT_FCC_SHIFT; + } + + if (smb->pdata->pre_charge_current) { + val = current_to_hw(pcc_tbl, ARRAY_SIZE(pcc_tbl), + smb->pdata->pre_charge_current); + if (val < 0) + return val; + + ret &= ~CFG_CHARGE_CURRENT_PCC_MASK; + ret |= val << CFG_CHARGE_CURRENT_PCC_SHIFT; + } + + if (smb->pdata->termination_current) { + val = current_to_hw(tc_tbl, ARRAY_SIZE(tc_tbl), + smb->pdata->termination_current); + if (val < 0) + return val; + + ret &= ~CFG_CHARGE_CURRENT_TC_MASK; + ret |= val; + } + + return smb347_write(smb, CFG_CHARGE_CURRENT, ret); +} + +static int smb347_set_current_limits(struct smb347_charger *smb) +{ + int ret, val; + + ret = smb347_read(smb, CFG_CURRENT_LIMIT); + if (ret < 0) + return ret; + + if (smb->mains_current_limit) { + val = current_to_hw(icl_tbl, ARRAY_SIZE(icl_tbl), + smb->mains_current_limit); + if (val < 0) + return val; + + ret &= ~CFG_CURRENT_LIMIT_DC_MASK; + ret |= val << CFG_CURRENT_LIMIT_DC_SHIFT; + } + + if (smb->pdata->usb_hc_current_limit) { + val = current_to_hw(icl_tbl, ARRAY_SIZE(icl_tbl), + smb->pdata->usb_hc_current_limit); + if (val < 0) + return val; + + ret &= ~CFG_CURRENT_LIMIT_USB_MASK; + ret |= val; + } + + return smb347_write(smb, CFG_CURRENT_LIMIT, ret); +} + +static int smb347_set_voltage_limits(struct smb347_charger *smb) +{ + int ret, val; + + ret = smb347_read(smb, CFG_FLOAT_VOLTAGE); + if (ret < 0) + return ret; + + if (smb->pdata->pre_to_fast_voltage) { + val = smb->pdata->pre_to_fast_voltage; + + /* uV */ + val = clamp_val(val, 2400000, 3000000) - 2400000; + val /= 200000; + + ret &= ~CFG_FLOAT_VOLTAGE_THRESHOLD_MASK; + ret |= val << CFG_FLOAT_VOLTAGE_THRESHOLD_SHIFT; + } + + if (smb->pdata->max_charge_voltage) { + val = smb->pdata->max_charge_voltage; + + /* uV */ + val = clamp_val(val, 3500000, 4500000) - 3500000; + val /= 20000; + + ret &= ~CFG_FLOAT_VOLTAGE_MASK; + ret |= val; + } + + return smb347_write(smb, CFG_FLOAT_VOLTAGE, ret); +} + +static int smb347_set_temp_limits(struct smb347_charger *smb) +{ + bool enable_therm_monitor = false; + int ret, val; + + if (smb->pdata->chip_temp_threshold) { + val = smb->pdata->chip_temp_threshold; + + /* degree C */ + val = clamp_val(val, 100, 130) - 100; + val /= 10; + + ret = smb347_read(smb, CFG_OTG); + if (ret < 0) + return ret; + + ret &= ~CFG_OTG_TEMP_THRESHOLD_MASK; + ret |= val << CFG_OTG_TEMP_THRESHOLD_SHIFT; + + ret = smb347_write(smb, CFG_OTG, ret); + if (ret < 0) + return ret; + } + + ret = smb347_read(smb, CFG_TEMP_LIMIT); + if (ret < 0) + return ret; + + if (smb->pdata->soft_cold_temp_limit != SMB347_TEMP_USE_DEFAULT) { + val = smb->pdata->soft_cold_temp_limit; + + val = clamp_val(val, 0, 15); + val /= 5; + /* this goes from higher to lower so invert the value */ + val = ~val & 0x3; + + ret &= ~CFG_TEMP_LIMIT_SOFT_COLD_MASK; + ret |= val << CFG_TEMP_LIMIT_SOFT_COLD_SHIFT; + + enable_therm_monitor = true; + } + + if (smb->pdata->soft_hot_temp_limit != SMB347_TEMP_USE_DEFAULT) { + val = smb->pdata->soft_hot_temp_limit; + + val = clamp_val(val, 40, 55) - 40; + val /= 5; + + ret &= ~CFG_TEMP_LIMIT_SOFT_HOT_MASK; + ret |= val << CFG_TEMP_LIMIT_SOFT_HOT_SHIFT; + + enable_therm_monitor = true; + } + + if (smb->pdata->hard_cold_temp_limit != SMB347_TEMP_USE_DEFAULT) { + val = smb->pdata->hard_cold_temp_limit; + + val = clamp_val(val, -5, 10) + 5; + val /= 5; + /* this goes from higher to lower so invert the value */ + val = ~val & 0x3; + + ret &= ~CFG_TEMP_LIMIT_HARD_COLD_MASK; + ret |= val << CFG_TEMP_LIMIT_HARD_COLD_SHIFT; + + enable_therm_monitor = true; + } + + if (smb->pdata->hard_hot_temp_limit != SMB347_TEMP_USE_DEFAULT) { + val = smb->pdata->hard_hot_temp_limit; + + val = clamp_val(val, 50, 65) - 50; + val /= 5; + + ret &= ~CFG_TEMP_LIMIT_HARD_HOT_MASK; + ret |= val << CFG_TEMP_LIMIT_HARD_HOT_SHIFT; + + enable_therm_monitor = true; + } + + ret = smb347_write(smb, CFG_TEMP_LIMIT, ret); + if (ret < 0) + return ret; + + /* + * If any of the temperature limits are set, we also enable the + * thermistor monitoring. + * + * When soft limits are hit, the device will start to compensate + * current and/or voltage depending on the configuration. + * + * When hard limit is hit, the device will suspend charging + * depending on the configuration. + */ + if (enable_therm_monitor) { + ret = smb347_read(smb, CFG_THERM); + if (ret < 0) + return ret; + + ret &= ~CFG_THERM_MONITOR_DISABLED; + + ret = smb347_write(smb, CFG_THERM, ret); + if (ret < 0) + return ret; + } + + if (smb->pdata->suspend_on_hard_temp_limit) { + ret = smb347_read(smb, CFG_SYSOK); + if (ret < 0) + return ret; + + ret &= ~CFG_SYSOK_SUSPEND_HARD_LIMIT_DISABLED; + + ret = smb347_write(smb, CFG_SYSOK, ret); + if (ret < 0) + return ret; + } + + if (smb->pdata->soft_temp_limit_compensation != + SMB347_SOFT_TEMP_COMPENSATE_DEFAULT) { + val = smb->pdata->soft_temp_limit_compensation & 0x3; + + ret = smb347_read(smb, CFG_THERM); + if (ret < 0) + return ret; + + ret &= ~CFG_THERM_SOFT_HOT_COMPENSATION_MASK; + ret |= val << CFG_THERM_SOFT_HOT_COMPENSATION_SHIFT; + + ret &= ~CFG_THERM_SOFT_COLD_COMPENSATION_MASK; + ret |= val << CFG_THERM_SOFT_COLD_COMPENSATION_SHIFT; + + ret = smb347_write(smb, CFG_THERM, ret); + if (ret < 0) + return ret; + } + + if (smb->pdata->charge_current_compensation) { + val = current_to_hw(ccc_tbl, ARRAY_SIZE(ccc_tbl), + smb->pdata->charge_current_compensation); + if (val < 0) + return val; + + ret = smb347_read(smb, CFG_OTG); + if (ret < 0) + return ret; + + ret &= ~CFG_OTG_CC_COMPENSATION_MASK; + ret |= (val & 0x3) << CFG_OTG_CC_COMPENSATION_SHIFT; + + ret = smb347_write(smb, CFG_OTG, ret); + if (ret < 0) + return ret; + } + + return ret; +} + +/* + * smb347_set_writable - enables/disables writing to non-volatile registers + * @smb: pointer to smb347 charger instance + * + * You can enable/disable writing to the non-volatile configuration + * registers by calling this function. + * + * Returns %0 on success and negative errno in case of failure. + */ +static int smb347_set_writable(struct smb347_charger *smb, bool writable) +{ + int ret; + + ret = smb347_read(smb, CMD_A); + if (ret < 0) + return ret; + + if (writable) + ret |= CMD_A_ALLOW_WRITE; + else + ret &= ~CMD_A_ALLOW_WRITE; + + return smb347_write(smb, CMD_A, ret); +} + +static int smb347_irq_set(struct smb347_charger *smb, bool enable) +{ + int ret; + + ret = smb347_set_writable(smb, true); + if (ret < 0) + return ret; + + /* + * Enable/disable interrupts for: + * - under voltage + * - termination current reached + * - charger error + */ + if (enable) { + ret = smb347_write(smb, CFG_FAULT_IRQ, CFG_FAULT_IRQ_DCIN_UV); + if (ret < 0) + goto fail; + + ret = smb347_write(smb, CFG_STATUS_IRQ, + CFG_STATUS_IRQ_TERMINATION_OR_TAPER); + if (ret < 0) + goto fail; + + ret = smb347_read(smb, CFG_PIN); + if (ret < 0) + goto fail; + + ret |= CFG_PIN_EN_CHARGER_ERROR; + + ret = smb347_write(smb, CFG_PIN, ret); + } else { + ret = smb347_write(smb, CFG_FAULT_IRQ, 0); + if (ret < 0) + goto fail; + + ret = smb347_write(smb, CFG_STATUS_IRQ, 0); + if (ret < 0) + goto fail; + + ret = smb347_read(smb, CFG_PIN); + if (ret < 0) + goto fail; + + ret &= ~CFG_PIN_EN_CHARGER_ERROR; + + ret = smb347_write(smb, CFG_PIN, ret); + } + +fail: + smb347_set_writable(smb, false); + return ret; +} + +static inline int smb347_irq_enable(struct smb347_charger *smb) +{ + return smb347_irq_set(smb, true); +} + +static inline int smb347_irq_disable(struct smb347_charger *smb) +{ + return smb347_irq_set(smb, false); +} + +static irqreturn_t smb347_interrupt(int irq, void *data) +{ + struct smb347_charger *smb = data; + int stat_c, t; + u8 irqstat[6]; + irqreturn_t ret = IRQ_NONE; + + t = i2c_smbus_read_i2c_block_data(smb->client, IRQSTAT_A, 6, irqstat); + if (t < 0) { + dev_warn(&smb->client->dev, + "reading IRQSTAT registers failed\n"); + return IRQ_NONE; + } + + stat_c = smb347_read(smb, STAT_C); + if (stat_c < 0) { + dev_warn(&smb->client->dev, "reading STAT_C failed\n"); + return IRQ_NONE; + } + + pr_debug("%s: stat c=%x irq a=%x b=%x c=%x d=%x e=%x f=%x\n", + __func__, stat_c, irqstat[0], irqstat[1], irqstat[2], + irqstat[3], irqstat[4], irqstat[5]); + + /* + * If we get charger error we report the error back to user and + * disable charging. + */ + if (stat_c & STAT_C_CHARGER_ERROR) { + dev_err(&smb->client->dev, + "error in charger, disabling charging\n"); + + smb347_charging_disable(smb); + power_supply_changed(&smb->battery); + + ret = IRQ_HANDLED; + } else if (((stat_c & STAT_C_CHG_STATUS) || + (irqstat[2] & (IRQSTAT_C_TERMINATION_IRQ | + IRQSTAT_C_TERMINATION_STAT))) && + !smb->is_fully_charged) { + dev_info(&smb->client->dev, "charge terminated\n"); + smb->is_fully_charged = true; + smb347_charging_disable(smb); + power_supply_changed(&smb->battery); + ret = IRQ_HANDLED; + } + + if (irqstat[2] & IRQSTAT_C_TAPER_IRQ) + ret = IRQ_HANDLED; + + /* + * If we got an under voltage interrupt it means that AC/USB input + * was disconnected. + */ + if (irqstat[4] & (IRQSTAT_E_USBIN_UV_IRQ | IRQSTAT_E_DCIN_UV_IRQ)) + ret = IRQ_HANDLED; + + if (smb347_update_status(smb) > 0) { + smb347_update_online(smb); + power_supply_changed(&smb->mains); + power_supply_changed(&smb->usb); + ret = IRQ_HANDLED; + } + + return ret; +} + +static int smb347_irq_init(struct smb347_charger *smb) +{ + const struct smb347_charger_platform_data *pdata = smb->pdata; + int ret, irq = gpio_to_irq(pdata->irq_gpio); + + ret = gpio_request_one(pdata->irq_gpio, GPIOF_IN, smb->client->name); + if (ret < 0) + goto fail; + + ret = request_threaded_irq(irq, NULL, smb347_interrupt, + pdata->disable_stat_interrupts ? + IRQF_TRIGGER_RISING | IRQF_ONESHOT : + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + smb->client->name, smb); + if (ret < 0) + goto fail_gpio; + + ret = enable_irq_wake(irq); + if (ret) + pr_err("%s: failed to enable wake on irq %d\n", __func__, irq); + + smb->client->irq = irq; + return 0; + +fail_gpio: + gpio_free(pdata->irq_gpio); +fail: + smb->client->irq = 0; + return ret; +} + +static int smb347_hw_init(struct smb347_charger *smb) +{ + int ret; + + ret = smb347_set_writable(smb, true); + if (ret < 0) + return ret; + + /* + * Program the platform specific configuration values to the device + * first. + */ + ret = smb347_set_charge_current(smb); + if (ret < 0) + goto fail; + + ret = smb347_set_current_limits(smb); + if (ret < 0) + goto fail; + + ret = smb347_set_voltage_limits(smb); + if (ret < 0) + goto fail; + +// HACK for Manta pre-alpha 0.2, TH_BATTERY not connected properly +#if 0 // HACK + ret = smb347_set_temp_limits(smb); + if (ret < 0) + goto fail; +#endif // HACK + + /* If USB charging is disabled we put the USB in suspend mode */ + if (!smb->pdata->use_usb) { + ret = smb347_read(smb, CMD_A); + if (ret < 0) + goto fail; + + ret |= CMD_A_SUSPEND_ENABLED; + + ret = smb347_write(smb, CMD_A, ret); + if (ret < 0) + goto fail; + } + + ret = smb347_read(smb, CFG_OTHER); + if (ret < 0) + goto fail; + + /* + * If configured by platform data, we enable hardware Auto-OTG + * support for driving VBUS. Otherwise we disable it. + */ + ret &= ~CFG_OTHER_RID_MASK; + if (smb->pdata->use_usb_otg) + ret |= CFG_OTHER_RID_ENABLED_AUTO_OTG; + + ret = smb347_write(smb, CFG_OTHER, ret); + if (ret < 0) + goto fail; + + /* If configured by platform data, disable AUTOMATIC RECHARGE */ + if (smb->pdata->disable_automatic_recharge) { + ret = smb347_read(smb, CFG_CHARGE_CONTROL); + if (ret < 0) + goto fail; + + ret |= CFG_AUTOMATIC_RECHARGE_DISABLE; + + ret = smb347_write(smb, CFG_CHARGE_CONTROL, ret); + if (ret < 0) + goto fail; + } + + ret = smb347_read(smb, CFG_PIN); + if (ret < 0) + goto fail; + + /* + * Make the charging functionality controllable by a write to the + * command register unless pin control is specified in the platform + * data. + */ + ret &= ~(CFG_PIN_EN_CTRL_MASK | CFG_PIN_USB_MODE_CTRL); + + switch (smb->pdata->enable_control) { + case SMB347_CHG_ENABLE_SW: + /* Do nothing, 0 means i2c control */ + break; + case SMB347_CHG_ENABLE_PIN_ACTIVE_LOW: + ret |= CFG_PIN_EN_CTRL_ACTIVE_LOW; + break; + case SMB347_CHG_ENABLE_PIN_ACTIVE_HIGH: + ret |= CFG_PIN_EN_CTRL_ACTIVE_HIGH; + break; + } + + if (smb->pdata->usb_mode_pin_ctrl) + ret |= CFG_PIN_USB_MODE_CTRL; + + /* Disable Automatic Power Source Detection (APSD) interrupt. */ + ret &= ~CFG_PIN_EN_APSD_IRQ; + + ret = smb347_write(smb, CFG_PIN, ret); + if (ret < 0) + goto fail; + + ret = smb347_update_status(smb); + if (ret < 0) + goto fail; + + ret = smb347_update_online(smb); + + if ((smb->pdata->irq_gpio >= 0) && + !smb->pdata->disable_stat_interrupts) { + /* + * Configure the STAT output to be suitable for interrupts: + * disable all other output (except interrupts) and make it + * active low. + */ + ret = smb347_read(smb, CFG_STAT); + if (ret < 0) + goto fail; + + ret &= ~CFG_STAT_ACTIVE_HIGH; + ret |= CFG_STAT_DISABLED; + + ret = smb347_write(smb, CFG_STAT, ret); + if (ret < 0) + goto fail; + + ret = smb347_irq_enable(smb); + if (ret < 0) + goto fail; + } + +fail: + smb347_set_writable(smb, false); + return ret; +} + +static int smb347_mains_get_property(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *val) +{ + struct smb347_charger *smb = + container_of(psy, struct smb347_charger, mains); + + switch (prop) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = smb->mains_online; + return 0; + + case POWER_SUPPLY_PROP_CURRENT_MAX: + val->intval = smb->mains_current_limit; + return 0; + + default: + return -EINVAL; + } + return -EINVAL; +} + +static int smb347_mains_set_property(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + struct smb347_charger *smb = + container_of(psy, struct smb347_charger, mains); + int ret; + bool oldval; + + switch (prop) { + case POWER_SUPPLY_PROP_ONLINE: + oldval = smb->mains_online; + + smb->mains_online = val->intval; + + smb347_set_writable(smb, true); + + ret = smb347_read(smb, CMD_A); + if (ret < 0) + return -EINVAL; + + ret &= ~CMD_A_SUSPEND_ENABLED; + if (val->intval) + ret |= CMD_A_SUSPEND_ENABLED; + + ret = smb347_write(smb, CMD_A, ret); + + smb347_hw_init(smb); + + smb347_set_writable(smb, false); + + if (smb->mains_online != oldval) + power_supply_changed(psy); + return 0; + case POWER_SUPPLY_PROP_CURRENT_MAX: + smb->mains_current_limit = val->intval; + smb347_hw_init(smb); + return 0; + + default: + return -EINVAL; + } + + return -EINVAL; +} + +static int smb347_mains_property_is_writeable(struct power_supply *psy, + enum power_supply_property prop) +{ + switch (prop) { + case POWER_SUPPLY_PROP_CURRENT_MAX: + return 1; + default: + break; + } + + return 0; +} + +static enum power_supply_property smb347_mains_properties[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CURRENT_MAX, +}; + +static int smb347_usb_get_property(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *val) +{ + struct smb347_charger *smb = + container_of(psy, struct smb347_charger, usb); + + switch (prop) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = smb->usb_online; + return 0; + + case POWER_SUPPLY_PROP_USB_HC: + val->intval = smb->usb_hc_mode; + return 0; + + case POWER_SUPPLY_PROP_USB_OTG: + val->intval = smb->usb_otg_enabled; + return 0; + + default: + break; + } + return -EINVAL; +} + +static int smb347_usb_set_property(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + int ret = -EINVAL; + struct smb347_charger *smb = + container_of(psy, struct smb347_charger, usb); + bool oldval; + + switch (prop) { + case POWER_SUPPLY_PROP_ONLINE: + oldval = smb->usb_online; + smb->usb_online = val->intval; + + if (smb->usb_online != oldval) + power_supply_changed(psy); + ret = 0; + break; + case POWER_SUPPLY_PROP_USB_HC: + smb347_set_writable(smb, true); + ret = smb347_write(smb, CMD_B, val->intval ? + CMD_B_HC_MODE : CMD_B_USB59_MODE); + smb347_set_writable(smb, false); + smb->usb_hc_mode = val->intval; + break; + + case POWER_SUPPLY_PROP_USB_OTG: + ret = smb347_read(smb, CMD_A); + + if (ret < 0) + return ret; + + if (val->intval) + ret |= CMD_A_OTG_ENABLE; + else + ret &= ~CMD_A_OTG_ENABLE; + + ret = smb347_write(smb, CMD_A, ret); + + if (ret >= 0) + smb->usb_otg_enabled = val->intval; + + break; + + default: + break; + } + + return ret; +} + +static int smb347_usb_property_is_writeable(struct power_supply *psy, + enum power_supply_property prop) +{ + switch (prop) { + case POWER_SUPPLY_PROP_USB_HC: + case POWER_SUPPLY_PROP_USB_OTG: + return 1; + default: + break; + } + + return 0; +} + +static enum power_supply_property smb347_usb_properties[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_USB_HC, + POWER_SUPPLY_PROP_USB_OTG, +}; + +static int smb347_battery_get_property(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *val) +{ + struct smb347_charger *smb = + container_of(psy, struct smb347_charger, battery); + const struct smb347_charger_platform_data *pdata = smb->pdata; + int ret; + + ret = smb347_update_status(smb); + if (ret < 0) + return ret; + + if (ret > 0) { + smb347_update_online(smb); + power_supply_changed(&smb->mains); + power_supply_changed(&smb->usb); + } + + switch (prop) { + case POWER_SUPPLY_PROP_STATUS: + if (!smb347_is_online(smb)) { + smb->is_fully_charged = false; + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + break; + } + if (smb347_charging_status(smb)) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else + val->intval = smb->is_fully_charged ? + POWER_SUPPLY_STATUS_FULL : + POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + + case POWER_SUPPLY_PROP_CHARGE_TYPE: + if (!smb347_is_online(smb)) + return -ENODATA; + + /* + * We handle trickle and pre-charging the same, and taper + * and none the same. + */ + switch (smb347_charging_status(smb)) { + case 1: + val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + break; + case 2: + val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; + break; + default: + val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; + break; + } + break; + + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = pdata->battery_info.technology; + break; + + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = pdata->battery_info.voltage_min_design; + break; + + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = pdata->battery_info.voltage_max_design; + break; + + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + if (!smb347_is_online(smb)) + return -ENODATA; + ret = smb347_read(smb, STAT_A); + if (ret < 0) + return ret; + + ret &= STAT_A_FLOAT_VOLTAGE_MASK; + if (ret > 0x3d) + ret = 0x3d; + + val->intval = 3500000 + ret * 20000; + break; + + case POWER_SUPPLY_PROP_CURRENT_NOW: + if (!smb347_is_online(smb)) + return -ENODATA; + + ret = smb347_read(smb, STAT_B); + if (ret < 0) + return ret; + + /* + * The current value is composition of FCC and PCC values + * and we can detect which table to use from bit 5. + */ + if (ret & 0x20) { + val->intval = hw_to_current(fcc_tbl, + ARRAY_SIZE(fcc_tbl), + ret & 7); + } else { + ret >>= 3; + val->intval = hw_to_current(pcc_tbl, + ARRAY_SIZE(pcc_tbl), + ret & 7); + } + break; + + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval = pdata->battery_info.charge_full_design; + break; + + case POWER_SUPPLY_PROP_CHARGE_ENABLED: + val->intval = smb->charging_enabled; + break; + + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = pdata->battery_info.name; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int smb347_battery_set_property(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + int ret = -EINVAL; + struct smb347_charger *smb = + container_of(psy, struct smb347_charger, battery); + + switch (prop) { + case POWER_SUPPLY_PROP_CHARGE_ENABLED: + ret = smb347_charging_set(smb, val->intval); + break; + + default: + break; + } + + return ret; +} + +static int smb347_battery_property_is_writeable(struct power_supply *psy, + enum power_supply_property prop) +{ + switch (prop) { + case POWER_SUPPLY_PROP_CHARGE_ENABLED: + return 1; + default: + break; + } + + return 0; +} + +static enum power_supply_property smb347_battery_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_ENABLED, + POWER_SUPPLY_PROP_MODEL_NAME, +}; + +static int smb347_debugfs_show(struct seq_file *s, void *data) +{ + struct smb347_charger *smb = s->private; + int ret; + u8 reg; + + seq_printf(s, "Control registers:\n"); + seq_printf(s, "==================\n"); + for (reg = CFG_CHARGE_CURRENT; reg <= CFG_ADDRESS; reg++) { + ret = smb347_read(smb, reg); + seq_printf(s, "0x%02x:\t0x%02x\n", reg, ret); + } + seq_printf(s, "\n"); + + seq_printf(s, "Command registers:\n"); + seq_printf(s, "==================\n"); + ret = smb347_read(smb, CMD_A); + seq_printf(s, "0x%02x:\t0x%02x\n", CMD_A, ret); + ret = smb347_read(smb, CMD_B); + seq_printf(s, "0x%02x:\t0x%02x\n", CMD_B, ret); + ret = smb347_read(smb, CMD_C); + seq_printf(s, "0x%02x:\t0x%02x\n", CMD_C, ret); + seq_printf(s, "\n"); + + seq_printf(s, "Interrupt status registers:\n"); + seq_printf(s, "===========================\n"); + for (reg = IRQSTAT_A; reg <= IRQSTAT_F; reg++) { + ret = smb347_read(smb, reg); + seq_printf(s, "0x%02x:\t0x%02x\n", reg, ret); + } + seq_printf(s, "\n"); + + seq_printf(s, "Status registers:\n"); + seq_printf(s, "=================\n"); + for (reg = STAT_A; reg <= STAT_E; reg++) { + ret = smb347_read(smb, reg); + seq_printf(s, "0x%02x:\t0x%02x\n", reg, ret); + } + + return 0; +} + +static int smb347_debugfs_open(struct inode *inode, struct file *file) +{ + return single_open(file, smb347_debugfs_show, inode->i_private); +} + +static const struct file_operations smb347_debugfs_fops = { + .open = smb347_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int smb347_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + static char *battery[] = { "smb347-battery" }; + const struct smb347_charger_platform_data *pdata; + struct device *dev = &client->dev; + struct smb347_charger *smb; + int ret; + + pdata = dev->platform_data; + if (!pdata) + return -EINVAL; + + if (!pdata->use_mains && !pdata->use_usb) + return -EINVAL; + + smb = devm_kzalloc(dev, sizeof(*smb), GFP_KERNEL); + if (!smb) + return -ENOMEM; + + i2c_set_clientdata(client, smb); + + mutex_init(&smb->lock); + smb->client = client; + smb->pdata = pdata; + + smb->mains_current_limit = smb->pdata->mains_current_limit; + + if (pdata->en_gpio) { + ret = gpio_request_one( + pdata->en_gpio, + smb->pdata->enable_control == + SMB347_CHG_ENABLE_PIN_ACTIVE_LOW ? + GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW, + smb->client->name); + if (ret < 0) + dev_warn(dev, "failed to claim EN GPIO: %d\n", ret); + else + smb->en_gpio = pdata->en_gpio; + } + + ret = smb347_write(smb, CMD_B, CMD_B_POR); + if (ret < 0) + return ret; + + msleep(20); + + ret = smb347_read(smb, CMD_B); + if (ret < 0) { + dev_err(dev, "failed read after reset\n"); + return ret; + } + + ret = smb347_hw_init(smb); + if (ret < 0) + return ret; + + smb->mains.name = "smb347-mains"; + smb->mains.type = POWER_SUPPLY_TYPE_MAINS; + smb->mains.get_property = smb347_mains_get_property; + smb->mains.set_property = smb347_mains_set_property; + smb->mains.property_is_writeable = smb347_mains_property_is_writeable; + smb->mains.properties = smb347_mains_properties; + smb->mains.num_properties = ARRAY_SIZE(smb347_mains_properties); + smb->mains.supplied_to = battery; + smb->mains.num_supplicants = ARRAY_SIZE(battery); + + smb->usb.name = "smb347-usb"; + smb->usb.type = POWER_SUPPLY_TYPE_USB; + smb->usb.get_property = smb347_usb_get_property; + smb->usb.set_property = smb347_usb_set_property; + smb->usb.property_is_writeable = smb347_usb_property_is_writeable; + smb->usb.properties = smb347_usb_properties; + smb->usb.num_properties = ARRAY_SIZE(smb347_usb_properties); + smb->usb.supplied_to = battery; + smb->usb.num_supplicants = ARRAY_SIZE(battery); + + smb->battery.name = "smb347-battery"; + smb->battery.type = POWER_SUPPLY_TYPE_BATTERY; + smb->battery.get_property = smb347_battery_get_property; + smb->battery.set_property = smb347_battery_set_property; + smb->battery.property_is_writeable = smb347_battery_property_is_writeable; + smb->battery.properties = smb347_battery_properties; + smb->battery.num_properties = ARRAY_SIZE(smb347_battery_properties); + + if (smb->pdata->supplied_to) { + smb->battery.supplied_to = smb->pdata->supplied_to; + smb->battery.num_supplicants = smb->pdata->num_supplicants; + smb->battery.external_power_changed = power_supply_changed; + } + + ret = power_supply_register(dev, &smb->mains); + if (ret < 0) + return ret; + + ret = power_supply_register(dev, &smb->usb); + if (ret < 0) { + power_supply_unregister(&smb->mains); + return ret; + } + + ret = power_supply_register(dev, &smb->battery); + if (ret < 0) { + power_supply_unregister(&smb->usb); + power_supply_unregister(&smb->mains); + return ret; + } + + /* + * Interrupt pin is optional. If it is connected, we setup the + * interrupt support here. + */ + if (pdata->irq_gpio >= 0) { + ret = smb347_irq_init(smb); + if (ret < 0) { + dev_warn(dev, "failed to initialize IRQ: %d\n", ret); + dev_warn(dev, "disabling IRQ support\n"); + } + } + + smb->dentry = debugfs_create_file("smb347-regs", S_IRUSR, NULL, smb, + &smb347_debugfs_fops); + return 0; +} + +static int smb347_remove(struct i2c_client *client) +{ + struct smb347_charger *smb = i2c_get_clientdata(client); + + if (!IS_ERR_OR_NULL(smb->dentry)) + debugfs_remove(smb->dentry); + + if (client->irq) { + smb347_irq_disable(smb); + disable_irq_wake(client->irq); + free_irq(client->irq, smb); + gpio_free(smb->pdata->irq_gpio); + } + + power_supply_unregister(&smb->battery); + power_supply_unregister(&smb->usb); + power_supply_unregister(&smb->mains); + return 0; +} + +static int smb347_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + + if (client->irq) + disable_irq(client->irq); + return 0; +} + +static int smb347_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + + if (client->irq) + enable_irq(client->irq); + return 0; +} + +static const struct dev_pm_ops smb347_pm_ops = { + .suspend = smb347_suspend, + .resume = smb347_resume, +}; + +static const struct i2c_device_id smb347_id[] = { + { "smb347", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, smb347_id); + +static struct i2c_driver smb347_driver = { + .driver = { + .name = "smb347", + .pm = &smb347_pm_ops, + }, + .probe = smb347_probe, + .remove = __devexit_p(smb347_remove), + .id_table = smb347_id, +}; + +static int __init smb347_init(void) +{ + return i2c_add_driver(&smb347_driver); +} +module_init(smb347_init); + +static void __exit smb347_exit(void) +{ + i2c_del_driver(&smb347_driver); +} +module_exit(smb347_exit); + +MODULE_AUTHOR("Bruce E. Robertson <bruce.e.robertson@intel.com>"); +MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>"); +MODULE_DESCRIPTION("SMB347 battery charger driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("i2c:smb347"); 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 <cbouatmailru@gmail.com> + * + * Dynamic module parameter code from the Virtual Battery Driver + * Copyright (C) 2008 Pylone, Inc. + * By: Masashi YOKOTA <yokota@pylone.jp> + * 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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/power_supply.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/vermagic.h> + +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 <on|off>"); + +module_param(battery_status, battery_status, 0644); +MODULE_PARM_DESC(battery_status, + "battery status <charging|discharging|not-charging|full>"); + +module_param(battery_present, battery_present, 0644); +MODULE_PARM_DESC(battery_present, + "battery presence state <good|overheat|dead|overvoltage|failure>"); + +module_param(battery_technology, battery_technology, 0644); +MODULE_PARM_DESC(battery_technology, + "battery technology <NiMH|LION|LIPO|LiFe|NiCd|LiMn>"); + +module_param(battery_health, battery_health, 0644); +MODULE_PARM_DESC(battery_health, + "battery health state <good|overheat|dead|overvoltage|failure>"); + +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 <cbouatmailru@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/tosa_battery.c b/drivers/power/tosa_battery.c new file mode 100644 index 00000000..28bbe7e0 --- /dev/null +++ b/drivers/power/tosa_battery.c @@ -0,0 +1,446 @@ +/* + * 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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/power_supply.h> +#include <linux/wm97xx.h> +#include <linux/delay.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/gpio.h> + +#include <asm/mach-types.h> +#include <mach/tosa.h> + +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 gpio tosa_bat_gpios[] = { + { TOSA_GPIO_CHARGE_OFF, GPIOF_OUT_INIT_HIGH, "main charge off" }, + { TOSA_GPIO_CHARGE_OFF_JC, GPIOF_OUT_INIT_HIGH, "jacket charge off" }, + { TOSA_GPIO_BAT_SW_ON, GPIOF_OUT_INIT_LOW, "battery switch" }, + { TOSA_GPIO_BAT0_V_ON, GPIOF_OUT_INIT_LOW, "main battery" }, + { TOSA_GPIO_BAT1_V_ON, GPIOF_OUT_INIT_LOW, "jacket battery" }, + { TOSA_GPIO_BAT1_TH_ON, GPIOF_OUT_INIT_LOW, "main battery temp" }, + { TOSA_GPIO_BAT0_TH_ON, GPIOF_OUT_INIT_LOW, "jacket battery temp" }, + { TOSA_GPIO_BU_CHRG_ON, GPIOF_OUT_INIT_LOW, "backup battery" }, + { TOSA_GPIO_BAT0_CRG, GPIOF_IN, "main battery full" }, + { TOSA_GPIO_BAT1_CRG, GPIOF_IN, "jacket battery full" }, + { TOSA_GPIO_BAT0_LOW, GPIOF_IN, "main battery low" }, + { TOSA_GPIO_BAT1_LOW, GPIOF_IN, "jacket battery low" }, + { TOSA_GPIO_JACKET_DETECT, GPIOF_IN, "jacket detect" }, +}; + +#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; + + if (!machine_is_tosa()) + return -ENODEV; + + ret = gpio_request_array(tosa_bat_gpios, ARRAY_SIZE(tosa_bat_gpios)); + if (ret) + return ret; + + 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); + + gpio_free_array(tosa_bat_gpios, ARRAY_SIZE(tosa_bat_gpios)); + return ret; +} + +static int __devexit tosa_bat_remove(struct platform_device *dev) +{ + 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); + gpio_free_array(tosa_bat_gpios, ARRAY_SIZE(tosa_bat_gpios)); + 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, +}; + +module_platform_driver(tosa_bat_driver); + +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..fdad850c --- /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 <notasas@gmail.com> + * + * 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 <linux/init.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/i2c/twl.h> +#include <linux/power_supply.h> +#include <linux/notifier.h> +#include <linux/usb/otg.h> + +#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, 0644); +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 usb_phy *transceiver; + struct notifier_block usb_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, usb_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; + u32 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 = usb_get_transceiver(); + if (bci->transceiver != NULL) { + bci->usb_nb.notifier_call = twl4030_bci_usb_ncb; + usb_register_notifier(bci->transceiver, &bci->usb_nb); + } + + /* Enable interrupts now. */ + reg = ~(u32)(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 = ~(u32)(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) { + usb_unregister_notifier(bci->transceiver, &bci->usb_nb); + usb_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) { + usb_unregister_notifier(bci->transceiver, &bci->usb_nb); + usb_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žvydas 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..6243e697 --- /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 <linux/module.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/slab.h> + +#include <linux/mfd/wm831x/core.h> +#include <linux/mfd/wm831x/auxadc.h> +#include <linux/mfd/wm831x/pmu.h> +#include <linux/mfd/wm831x/pdata.h> + +struct wm831x_backup { + struct wm831x *wm831x; + struct power_supply backup; + char name[20]; +}; + +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_pdata *wm831x_pdata = wm831x->dev->platform_data; + 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); + + if (wm831x_pdata && wm831x_pdata->wm831x_num) + snprintf(devdata->name, sizeof(devdata->name), + "wm831x-backup.%d", wm831x_pdata->wm831x_num); + else + snprintf(devdata->name, sizeof(devdata->name), + "wm831x-backup"); + + backup->name = devdata->name; + 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->backup.name); + 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", + }, +}; + +module_platform_driver(wm831x_backup_driver); + +MODULE_DESCRIPTION("Backup battery charger driver for WM831x PMICs"); +MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); +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..987332b7 --- /dev/null +++ b/drivers/power/wm831x_power.c @@ -0,0 +1,663 @@ +/* + * 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 <linux/module.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/slab.h> + +#include <linux/mfd/wm831x/core.h> +#include <linux/mfd/wm831x/auxadc.h> +#include <linux/mfd/wm831x/pmu.h> +#include <linux/mfd/wm831x/pdata.h> + +struct wm831x_power { + struct wm831x *wm831x; + struct power_supply wall; + struct power_supply usb; + struct power_supply battery; + char wall_name[20]; + char usb_name[20]; + char battery_name[20]; + bool have_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 */ + if (wm831x_power->have_battery) + 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. */ + if (wm831x_power->have_battery) + 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_pdata *wm831x_pdata = wm831x->dev->platform_data; + 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; + + if (wm831x_pdata && wm831x_pdata->wm831x_num) { + snprintf(power->wall_name, sizeof(power->wall_name), + "wm831x-wall.%d", wm831x_pdata->wm831x_num); + snprintf(power->battery_name, sizeof(power->wall_name), + "wm831x-battery.%d", wm831x_pdata->wm831x_num); + snprintf(power->usb_name, sizeof(power->wall_name), + "wm831x-usb.%d", wm831x_pdata->wm831x_num); + } else { + snprintf(power->wall_name, sizeof(power->wall_name), + "wm831x-wall"); + snprintf(power->battery_name, sizeof(power->wall_name), + "wm831x-battery"); + snprintf(power->usb_name, sizeof(power->wall_name), + "wm831x-usb"); + } + + /* We ignore configuration failures since we can still read back + * the status without enabling the charger. + */ + wm831x_config_battery(wm831x); + + wall->name = power->wall_name; + 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; + + usb->name = power->usb_name, + 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_wall; + + ret = wm831x_reg_read(wm831x, WM831X_CHARGER_CONTROL_1); + if (ret < 0) + goto err_wall; + power->have_battery = ret & WM831X_CHG_ENA; + + if (power->have_battery) { + battery->name = power->battery_name; + 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_usb; + } + + 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_battery; + } + + 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_battery: + if (power->have_battery) + power_supply_unregister(battery); +err_usb: + power_supply_unregister(usb); +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); + + if (wm831x_power->have_battery) + 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", + }, +}; + +module_platform_driver(wm831x_power_driver); + +MODULE_DESCRIPTION("Power supply driver for WM831x PMICs"); +MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); +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..fae04d38 --- /dev/null +++ b/drivers/power/wm8350_power.c @@ -0,0 +1,529 @@ +/* + * Battery driver for wm8350 PMIC + * + * Copyright 2007, 2008 Wolfson Microelectronics PLC. + * + * Based on OLPC Battery Driver + * + * Copyright 2006 David Woodhouse <dwmw2@infradead.org> + * + * 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 <linux/module.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/mfd/wm8350/supply.h> +#include <linux/mfd/wm8350/core.h> +#include <linux/mfd/wm8350/comparator.h> + +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", + }, +}; + +module_platform_driver(wm8350_power_driver); + +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..d2d4c08c --- /dev/null +++ b/drivers/power/wm97xx_battery.c @@ -0,0 +1,295 @@ +/* + * linux/drivers/power/wm97xx_battery.c + * + * Battery measurement code for WM97xx + * + * based on tosa_battery.c + * + * Copyright (C) 2008 Marek Vasut <marek.vasut@gmail.com> + * + * 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 <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/wm97xx.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include <linux/irq.h> +#include <linux/slab.h> + +static struct work_struct bat_work; +static DEFINE_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; + + 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, 0, + "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), +}; + +module_platform_driver(wm97xx_bat_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>"); +MODULE_DESCRIPTION("WM97xx battery driver"); diff --git a/drivers/power/wmt_battery/Makefile b/drivers/power/wmt_battery/Makefile new file mode 100755 index 00000000..e4287ba0 --- /dev/null +++ b/drivers/power/wmt_battery/Makefile @@ -0,0 +1,15 @@ + +# charger +obj-y += charger/g2214/ +obj-y += charger/mp2625/ + +# gauge +obj-y += gauge/vt1603/ +obj-y += gauge/ug31xx/ +obj-y += gauge/sp2541/ +obj-y += gauge/bq27xx/ +obj-y += gauge/saradc/ + +# core +obj-y += wmt_battery.o + diff --git a/drivers/power/wmt_battery/charger/g2214/Kconfig b/drivers/power/wmt_battery/charger/g2214/Kconfig new file mode 100755 index 00000000..1ca457f8 --- /dev/null +++ b/drivers/power/wmt_battery/charger/g2214/Kconfig @@ -0,0 +1,5 @@ +config G2214_CHARGER
+ tristate "GMT G2214 Charger driver (GMT & others)"
+ depends on I2C
+ help
+ Say Y here to enable support for charger with g2214 chip.
\ No newline at end of file diff --git a/drivers/power/wmt_battery/charger/g2214/Makefile b/drivers/power/wmt_battery/charger/g2214/Makefile new file mode 100755 index 00000000..188872a7 --- /dev/null +++ b/drivers/power/wmt_battery/charger/g2214/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for GMT charger core. +# + +obj-y += g2214_charger.o + + diff --git a/drivers/power/wmt_battery/charger/g2214/g2214_charger.c b/drivers/power/wmt_battery/charger/g2214/g2214_charger.c new file mode 100755 index 00000000..944790e8 --- /dev/null +++ b/drivers/power/wmt_battery/charger/g2214/g2214_charger.c @@ -0,0 +1,762 @@ +/* + * g2214_charger.c - WonderMedia Charger Driver. + * + * Copyright (C) 2013 WonderMedia Technologies, 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. + * + * 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 <linux/module.h> +#include <linux/slab.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.h> +#include <linux/power_supply.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <asm/uaccess.h> +#include <mach/hardware.h> +#include <mach/wmt_env.h> +#include <linux/power/wmt_battery.h> +#include <mach/gmt-core.h> +#include <linux/reboot.h> + +#define DRVNAME "gmt-charger" + +#undef pr_err +#define pr_err(fmt, args...) printk("[" DRVNAME "] " fmt, ##args) + +#define REG_A0 0x00 +#define REG_A1 0x01 +#define REG_A2 0x02 +#define REG_A3 0x03 +#define REG_A4 0x04 +#define REG_A5 0x05 +#define REG_A6 0x06 +#define REG_A7 0x07 +#define REG_A8 0x08 +#define REG_A9 0x09 +#define REG_A10 0x0A +#define REG_A11 0x0B +#define REG_A12 0x0C +#define REG_A13 0x0D + +struct g2214_charger { + struct gmt2214_dev *gmt_dev; + struct device *dev; + struct mutex lock; + + struct power_supply psy_ac; + struct power_supply psy_usb; + struct delayed_work monitor_work; + + int ac_online; + int usb_online; + int charger_status; + int sleeping; + + union { + struct { + unsigned int cable_type:4; + unsigned int current_sw_mode:1; + unsigned int pc_charging:1; + }; + uint32_t flag; + }; + + int iset_dcin; + int iset_vbus; + int vseta; + int iseta_small; + int iseta_large; + int safety_time; + int otg_power; + int pc_power_mode; +}; + +static struct g2214_charger *g_charger = NULL; + +static int parse_charger_param(struct g2214_charger *ch) +{ + static const char uboot_env[] = "wmt.charger.param"; + char buf[64]; + size_t l = sizeof(buf); + int n; + + if (wmt_getsyspara((char *)uboot_env, buf, &l)) + return -ENODEV; + if (prefixcmp(buf, "g2214:")) + return -ENODEV; + if (!ch) + return 0; + + n = sscanf(buf + 6, "%x:%d:%d:%d:%d:%d:%d:%d:%d", + &ch->flag, + &ch->iset_dcin, &ch->iset_vbus, &ch->vseta, + &ch->iseta_small, &ch->iseta_large, + &ch->safety_time, &ch->otg_power, &ch->pc_power_mode); + if (n < 8) { + pr_err("%s invalid\n", uboot_env); + return -EINVAL; + } + + pr_info("charger match g2214, %s cable, %s current switch\n" + "PC connected is %scharging\n" + "dcin %d mA, vbus %d mA, %d mV\n" + "charging current %d~%d mA %d hour, %s otg power\n" + "pc power in %s mode\n", + (ch->cable_type == CABLE_TYPE_DC) ? "DC" : "USB", + (ch->current_sw_mode == CURRENT_SWITCH_DYNAMIC) ? "dynamic" : "sleep", + (ch->pc_charging == PC_CONNECTED_NOT_CHARGING) ? "not " : "", + ch->iset_dcin, ch->iset_vbus, ch->vseta, + ch->iseta_small, ch->iseta_large, + ch->safety_time, ch->otg_power ? "switch" : "no", + ch->pc_power_mode ? "lowpower" : "normal"); + return 0; +} + +static int g2214_read(struct g2214_charger *ch, uint8_t reg) +{ + unsigned int rt_value = 0; + gmt2214_reg_read(ch->gmt_dev, reg, &rt_value); + return rt_value; +} + +static int g2214_write(struct g2214_charger *ch, uint8_t reg, uint8_t val) +{ + return gmt2214_reg_write(ch->gmt_dev, reg, val); +} + +static inline void g2214_enotg_config(struct g2214_charger *ch, int enable) +{ + int val = g2214_read(ch, REG_A8); + if (enable) + val |= BIT3; + else + val &= ~BIT3; + g2214_write(ch, REG_A8, val); +} + +static inline void g2214_vseta_config(struct g2214_charger *ch) +{ + int val, vseta; + + if (ch->vseta < 4150) + vseta = 0; + else if (ch->vseta < 4200) + vseta = 1; + else if (ch->vseta < 4350) + vseta = 2; + else + vseta = 3; + + val = g2214_read(ch, REG_A8); + val &= ~(3 << 6); + val |= vseta << 6; + g2214_write(ch, REG_A8, val); +} + +static inline void g2214_current_config(struct g2214_charger *ch, + int dcin_mA, int vbus_mA, int charge_mA) +{ + int iset_dcin, iset_vbus, iseta; + + if (dcin_mA <= 1000) + iset_dcin = 0; + else if (dcin_mA <= 1500) + iset_dcin = 1; + else if (dcin_mA <= 2000) + iset_dcin = 2; + else + iset_dcin = 3; + + if (vbus_mA <= 95) + iset_vbus = 0; + else if (vbus_mA <= 475) + iset_vbus = 1; + else if (vbus_mA <= 950) + iset_vbus = 2; + else + iset_vbus = 3; + + if (charge_mA < 300 || charge_mA > 1800) + iseta = 2; + else + iseta = ((charge_mA - 300) / 100); + + g2214_write(ch, REG_A5, iset_dcin << 6 | iset_vbus << 4 | iseta); +} + +static void current_refresh(struct g2214_charger *ch) +{ + int dcin_mA, vbus_mA, charge_mA; + + dcin_mA = ch->iset_dcin; + + switch (ch->charger_status) { + case POWER_SUPPLY_STATUS_DISCHARGING: + vbus_mA = ch->pc_power_mode ? 95 : 475; + charge_mA = ch->iseta_small; + break; + case POWER_SUPPLY_STATUS_FULL: + case POWER_SUPPLY_STATUS_CHARGING: + vbus_mA = ch->iset_vbus; + if (ch->current_sw_mode == CURRENT_SWITCH_DYNAMIC) { + charge_mA = ch->iseta_large; + } else { + charge_mA = ch->sleeping ? ch->iseta_large + : ch->iseta_small; + } + if (ch->cable_type == CABLE_TYPE_USB && wmt_is_pc_connected()) { + vbus_mA = 475; + charge_mA = ch->iseta_small; + } + break; + default: + return; + } + + printk(KERN_DEBUG " ## %s: dcin_mA %d, vbus_mA %d, charge_mA %d\n", + __func__, dcin_mA, vbus_mA, charge_mA); + g2214_current_config(ch, dcin_mA, vbus_mA, charge_mA); + g2214_vseta_config(ch); +} + +static void g2214_endpm_config(struct g2214_charger *ch, int en) +{ + int val = g2214_read(ch, REG_A0); + if (en) + val |= BIT3; + else + val &= ~BIT3; + g2214_write(ch, REG_A0, val); +} + +static void g2214_safety_time_init(struct g2214_charger *ch) +{ + int val; + int safety_time = ch->safety_time - 1; + + if (safety_time < 0) + safety_time = 0; + else if (safety_time > 16) + safety_time = 15; + + val = g2214_read(ch, REG_A6); + val &= (~(BIT4 | BIT5 | BIT6 | BIT7)); + val |= (safety_time << 4); + g2214_write(ch, REG_A6, val); +} + +static void g2214_ntc_init(struct g2214_charger *ch) +{ + int val; + val = g2214_read(ch, REG_A0); + val &= ~BIT1; //Enable Auto NTC-R Type Detection + g2214_write(ch, REG_A0, val); + g2214_write(ch, REG_A7, 0); //Set HOT boundary to 60 +} + +static int g2214_reg_init(struct g2214_charger *ch) +{ + g2214_safety_time_init(ch); + g2214_enotg_config(ch, 0); + g2214_current_config(ch, ch->iset_dcin, ch->iset_vbus, ch->iseta_small); + g2214_endpm_config(ch, ch->current_sw_mode == CURRENT_SWITCH_DYNAMIC); + g2214_ntc_init(ch); + return 0; +} + +static void g2214_regs_dump(struct g2214_charger *ch) +{ + int reg; + for (reg = REG_A0; reg <= REG_A13; reg++) + printk(KERN_DEBUG " ## reg A%d: 0x%x\n ", + reg, g2214_read(ch, reg)); +} + +static void g2214_charge_enable(struct g2214_charger *ch, int en) +{ + int val = g2214_read(ch, REG_A6); + if (en) + val &= ~BIT3; + else + val |= BIT3; + g2214_write(ch, REG_A6, val); +} + +static inline int g2214_is_full(struct g2214_charger *ch) +{ + return !!(g2214_read(ch, REG_A12) & 0x10); +} + +static int g2214_read_status(struct g2214_charger *ch, int *pst) +{ + if (!wmt_is_dc_plugin()) { + *pst = POWER_SUPPLY_STATUS_DISCHARGING; + return 0; + } + + switch (ch->cable_type) { + case CABLE_TYPE_DC: + *pst = POWER_SUPPLY_STATUS_CHARGING; + break; + case CABLE_TYPE_USB: + if (wmt_is_otg_plugin()) { + *pst = POWER_SUPPLY_STATUS_DISCHARGING; + } else if (wmt_is_pc_connected()) { + if (ch->pc_charging == PC_CONNECTED_CHARGING) + *pst = POWER_SUPPLY_STATUS_CHARGING; + else + *pst = POWER_SUPPLY_STATUS_DISCHARGING; + } else { + *pst = POWER_SUPPLY_STATUS_CHARGING; + } + break; + default: + return -EINVAL; + } + + if (*pst == POWER_SUPPLY_STATUS_CHARGING) { + if (g2214_is_full(ch)) + *pst = POWER_SUPPLY_STATUS_FULL; + } + return 0; +} + +static void charger_monitor_work(struct work_struct *work) +{ + struct g2214_charger *ch= + container_of(work, struct g2214_charger, monitor_work.work); + int ac_online = 0; + int usb_online = 0; + int charger_status; + int ts_meter = 0; + + g2214_regs_dump(ch); + + if (wmt_is_otg_plugin() && ch->otg_power) + g2214_enotg_config(ch, 1); + else { + g2214_enotg_config(ch, 0); + msleep(30); + } + + g2214_write(ch, REG_A9, 0xFF); + + g2214_read_status(ch, &charger_status); + + ts_meter = (g2214_read(ch, REG_A10) & 0xE0) >> 5; + if (ts_meter == 0x11) { + charger_status = POWER_SUPPLY_STATUS_DISCHARGING; + g2214_charge_enable(ch, 0); + printk("Battery Overheat, Charge Disable\n"); + } else if (ts_meter <= 0) + g2214_charge_enable(ch, 1); + + if (charger_status == POWER_SUPPLY_STATUS_CHARGING || + charger_status == POWER_SUPPLY_STATUS_FULL) { + if (ch->cable_type == CABLE_TYPE_USB && + ch->pc_charging == PC_CONNECTED_CHARGING && + wmt_is_pc_connected()) + usb_online = 1; + else + ac_online = 1; + } + + if (ch->charger_status != charger_status || + ch->ac_online != ac_online) { + ch->charger_status = charger_status; + ch->ac_online = ac_online; + power_supply_changed(&ch->psy_ac); + } + + if (ch->pc_charging == PC_CONNECTED_CHARGING && + ch->usb_online != usb_online) { + ch->usb_online = usb_online; + power_supply_changed(&ch->psy_usb); + } + + current_refresh(ch); + led_power_enable(charger_status == POWER_SUPPLY_STATUS_CHARGING || + charger_status == POWER_SUPPLY_STATUS_FULL); + + g2214_write(ch, REG_A9, 0xF0); + g2214_write(ch, REG_A12, g2214_read(ch, REG_A12) & (~BIT0)); +} + +void g2214_pc_connected(void) +{ + if (g_charger) + schedule_delayed_work(&g_charger->monitor_work, 1.5*HZ); +} + +static irqreturn_t otg_irq(int irq, void *data) +{ + struct g2214_charger *ch = data; + + if (REG8_VAL(USB_BASE_ADD + 0x7F1) & BIT7) { + REG8_VAL(USB_BASE_ADD + 0x7F1) = BIT7; + schedule_delayed_work(&ch->monitor_work, 0); + return IRQ_HANDLED; + } + return IRQ_NONE; +} + +// g2214 + dcdet use wakeup0 interrupt +static irqreturn_t g2214_dcdet_irq(int irq, void *data) +{ + struct g2214_charger *ch = data; + + // turn off the led immediately + if (!wmt_is_dc_plugin()) + led_power_enable(0); + + if (PMCIS_VAL & BIT0) { + pmc_clear_intr_status(WKS_WK0); + schedule_delayed_work(&ch->monitor_work, 0); + return IRQ_HANDLED; + } + + if (PMCIS_VAL & BIT27) { + pmc_clear_intr_status(WKS_DCDET); + schedule_delayed_work(&ch->monitor_work, 1.5*HZ); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static inline void pid_check_irq_enable(void) +{ + REG8_VAL(USB_BASE_ADD+0x7F2) |= BIT1; +} + +static inline void pid_check_irq_disable(void) +{ + REG8_VAL(USB_BASE_ADD+0x7F2) &= ~BIT1; +} + +static int irqs_init(struct g2214_charger *ch) +{ + unsigned long iflag = IRQF_SHARED; + int ret; + + ret = devm_request_irq(ch->dev, IRQ_UHDC, otg_irq, iflag, "USBOTG", ch); + if (ret < 0) { + dev_err(ch->dev, "IRQ_UHDC irq request failed %d\n", ret); + return ret; + } + + if (REG32_VAL(0xfe120000) == 0x35100101) + iflag |= IRQF_NO_SUSPEND; + + ret = devm_request_irq(ch->dev, IRQ_PMC_WAKEUP, g2214_dcdet_irq, iflag, + "G2214-DCDET", ch); + if (ret < 0) { + pr_err("register DCDET irq failed\n"); + return ret; + } + + pid_check_irq_enable(); + pmc_enable_wakeup_isr(WKS_WK0, 2); + wmt_dcdet_irq_enable(); + return 0; +} + +static void irqs_release(struct g2214_charger *ch) +{ + pid_check_irq_disable(); + wmt_dcdet_irq_disable(); + pmc_disable_wakeup_isr(WKS_WK0); +} + +static enum power_supply_property ac_properties[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_STATUS, +}; + +static int ac_get_property(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *val) +{ + struct g2214_charger *ch = + container_of(psy, struct g2214_charger, psy_ac); + switch (prop) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = ch->ac_online; + break; + case POWER_SUPPLY_PROP_STATUS: + return g2214_read_status(ch, &val->intval); + default: + return -EINVAL; + } + return 0; +} + +static int usb_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct g2214_charger *ch = + container_of(psy, struct g2214_charger, psy_usb); + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = ch->usb_online; + break; + default: + return -EINVAL; + } + return 0; +} + +static enum power_supply_property usb_properties[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static char *power_supplied_to[] = { + "battery", +}; + +static int power_supply_init(struct g2214_charger *ch) +{ + int ret; + + ch->psy_ac.name = "ac", + ch->psy_ac.type = POWER_SUPPLY_TYPE_MAINS, + ch->psy_ac.supplied_to = power_supplied_to, + ch->psy_ac.num_supplicants = ARRAY_SIZE(power_supplied_to), + ch->psy_ac.properties = ac_properties, + ch->psy_ac.num_properties = ARRAY_SIZE(ac_properties), + ch->psy_ac.get_property = ac_get_property, + ret = power_supply_register(ch->dev, &ch->psy_ac); + if (ret) { + dev_err(ch->dev, "register ac power supply failed.\n"); + } + + if (ch->pc_charging == PC_CONNECTED_CHARGING) { + ch->psy_usb.name = "usb", + ch->psy_usb.type = POWER_SUPPLY_TYPE_USB, + ch->psy_usb.supplied_to = power_supplied_to, + ch->psy_usb.num_supplicants = ARRAY_SIZE(power_supplied_to), + ch->psy_usb.properties = usb_properties, + ch->psy_usb.num_properties = ARRAY_SIZE(usb_properties), + ch->psy_usb.get_property = usb_get_property, + ret = power_supply_register(ch->dev, &ch->psy_usb); + if (ret) { + dev_err(ch->dev, "register ac power supply failed.\n"); + return ret; + } + } + + return ret; +} + +static void power_supply_release(struct g2214_charger *ch) +{ + power_supply_unregister(&ch->psy_ac); +} + +#define G2214_PROC_NAME "driver/g2214_regs" + +static int g2214_proc_show(struct seq_file *seq, void *offset) +{ + int reg; + for (reg = REG_A0; reg <= REG_A13; reg++) + seq_printf(seq, "reg A%d: 0x%x\n ", + reg, g2214_read(g_charger, reg)); + return 0; +} + +static int g2214_proc_open(struct inode *inode, struct file *file) +{ + int ret; + if (!try_module_get(THIS_MODULE)) + return -ENODEV; + + ret = single_open(file, g2214_proc_show, NULL); + if (ret) + module_put(THIS_MODULE); + return ret; +} + +static ssize_t g2214_proc_write(struct file *file, const char __user *buffer, + size_t count, loff_t *pos) +{ + char cmd[32]; + unsigned long len = count; + int reg, val; + + if (len > sizeof(cmd)) + len = sizeof(cmd); + + if (copy_from_user(cmd, buffer, len)) + return -EFAULT; + if (sscanf(cmd, "r%d=0x%02x", ®, &val) != 2) + return -EINVAL; + if (reg > REG_A13) + return -EINVAL; + + if (g_charger) { + g2214_write(g_charger, reg, val); + pr_info("## %s: reg %d -> 0x%02x\n", __func__, reg, val); + } + + return count; +} + +static int g2214_proc_release(struct inode *inode, struct file *file) +{ + int res = single_release(inode, file); + module_put(THIS_MODULE); + return res; +} + +static const struct file_operations g2214_proc_fops = { + .open = g2214_proc_open, + .read = seq_read, + .write = g2214_proc_write, + .llseek = seq_lseek, + .release = g2214_proc_release, +}; + +static inline void g2214_proc_setup(void) +{ + proc_create(G2214_PROC_NAME, 0, NULL, &g2214_proc_fops); +} + +static inline void g2214_proc_cleanup(void) +{ + remove_proc_entry(G2214_PROC_NAME, NULL); +} + +static int g2214_reboot_notifie(struct notifier_block *nb, unsigned long event, void *unused) +{ + struct g2214_charger *ch = g_charger; + cancel_delayed_work_sync(&ch->monitor_work); + g2214_enotg_config(ch, 0); + g2214_write(ch, REG_A9, 0xFF); + g2214_write(ch, REG_A11, 0xFF); + return NOTIFY_OK; +} + +static struct notifier_block g2214_reboot_notifier = { + .notifier_call = g2214_reboot_notifie, +}; + + +static int __devinit g2214_probe(struct platform_device *pdev) +{ + struct g2214_charger *ch; + int ret; + + ch = devm_kzalloc(&pdev->dev, sizeof(*ch), GFP_KERNEL); + if (!ch) + return -ENOMEM; + + if ((ret = parse_charger_param(ch))) + return ret; + parse_charger_led(); + + ch->dev = &pdev->dev; + ch->gmt_dev = dev_get_drvdata(pdev->dev.parent); + platform_set_drvdata(pdev, ch); + + if ((ret = power_supply_init(ch))) + return ret; + + INIT_DELAYED_WORK(&ch->monitor_work, charger_monitor_work); + + if ((ret = irqs_init(ch))) { + power_supply_release(ch); + return ret; + } + + g_charger = ch; + g2214_reg_init(ch); + g2214_proc_setup(); + + register_reboot_notifier(&g2214_reboot_notifier); + schedule_delayed_work(&ch->monitor_work, 0); + pr_info(DRVNAME " install success.\n"); + return 0; +} + +static int __devexit g2214_remove(struct platform_device *pdev) +{ + struct g2214_charger *ch = platform_get_drvdata(pdev); + irqs_release(ch); + cancel_delayed_work_sync(&ch->monitor_work); + power_supply_release(ch); + g2214_proc_cleanup(); + g_charger = NULL; + return 0; +} + +static int g2214_suspend(struct device *dev) +{ + struct g2214_charger *ch = dev_get_drvdata(dev); + cancel_delayed_work_sync(&ch->monitor_work); + ch->sleeping = 1; + current_refresh(ch); + g2214_write(ch, REG_A11, 0xCF); + return 0; +} + +static int g2214_resume(struct device *dev) +{ + struct g2214_charger *ch = dev_get_drvdata(dev); + + // turn off the led immediately + if (!wmt_is_dc_plugin()) + led_power_enable(0); + + pid_check_irq_enable(); + ch->sleeping = 0; + current_refresh(ch); + schedule_delayed_work(&ch->monitor_work, HZ); + g2214_write(ch, REG_A11, 0xFF); + return 0; +} + +static const struct dev_pm_ops g2214_pm_ops = { + .suspend = g2214_suspend, + .resume = g2214_resume, +}; + +static struct platform_driver g2214_driver = { + .driver = { + .name = DRVNAME, + .owner = THIS_MODULE, + .pm = &g2214_pm_ops, + }, + .probe = g2214_probe, + .remove = __devexit_p(g2214_remove), +}; + +static int __init g2214_init(void) +{ + if (parse_charger_param(NULL)) + return -ENODEV; + return platform_driver_register(&g2214_driver); +} + +static void __exit g2214_exit(void) +{ + return platform_driver_unregister(&g2214_driver); +} + +module_init(g2214_init); +module_exit(g2214_exit); + +MODULE_AUTHOR("WonderMedia Technologies, Inc."); +MODULE_DESCRIPTION("GMT2144 battery charger driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("i2c:g2214"); + diff --git a/drivers/power/wmt_battery/charger/mp2625/Kconfig b/drivers/power/wmt_battery/charger/mp2625/Kconfig new file mode 100755 index 00000000..38282c65 --- /dev/null +++ b/drivers/power/wmt_battery/charger/mp2625/Kconfig @@ -0,0 +1,5 @@ +config MP2625_CHARGER
+ tristate "MP2625 Charger driver"
+ depends on I2C
+ help
+ Say Y here to enable support for charger with mp2625 chip.
diff --git a/drivers/power/wmt_battery/charger/mp2625/Makefile b/drivers/power/wmt_battery/charger/mp2625/Makefile new file mode 100755 index 00000000..9bcfa83d --- /dev/null +++ b/drivers/power/wmt_battery/charger/mp2625/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for MP2625 charger core. +# + +obj-y += mp2625_charger.o + diff --git a/drivers/power/wmt_battery/charger/mp2625/mp2625_charger.c b/drivers/power/wmt_battery/charger/mp2625/mp2625_charger.c new file mode 100755 index 00000000..581e89d8 --- /dev/null +++ b/drivers/power/wmt_battery/charger/mp2625/mp2625_charger.c @@ -0,0 +1,428 @@ +/* + * mp2625_charger.c - WonderMedia Charger Driver. + * + * Copyright (C) 2013 WonderMedia Technologies, 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. + * + * 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 <linux/module.h> +#include <linux/slab.h> +#include <linux/power_supply.h> +#include <linux/platform_device.h> +#include <mach/wmt_env.h> +#include <mach/hardware.h> +#include <mach/wmt_iomux.h> +#include <linux/gpio.h> +#include <linux/power/wmt_battery.h> + +#define DRVNAME "mp2625-charger" + +#undef pr_err +#undef pr_info +#define pr_err(fmt, args...) printk("[" DRVNAME "] " fmt, ##args) +#define pr_info(fmt, args...) printk("[" DRVNAME "] " fmt, ##args) + +struct mp2625_charger { + struct device *dev; + int ac_online; + int usb_online; + int charger_status; + int sleeping; + + union { + struct { + unsigned int cable_type:4; + unsigned int current_sw_mode:1; + unsigned int pc_charging:1; + }; + uint32_t flag; + }; + + int full_pin; + int full_level; + int current_pin; + int current_large_level; + + struct power_supply psy_ac; + struct power_supply psy_usb; + struct delayed_work dwork; +}; + +static struct mp2625_charger *g_charger; + +static int parse_charger_param(struct mp2625_charger *ch) +{ + static char uboot_env[] = "wmt.charger.param"; + char buf[64]; + size_t l = sizeof(buf); + int ret; + + if (wmt_getsyspara(uboot_env, buf, &l)) + return -ENODEV; + if (prefixcmp(buf, "mp2625:")) + return -ENODEV; + if (!ch) + return 0; + + ret = sscanf(buf + 7, "%x:%d:%d:%d:%d", + &ch->flag, + &ch->full_pin, &ch->full_level, + &ch->current_pin, &ch->current_large_level); + if (ret < 5) { + pr_err("Invalid uboot env: %s\n", uboot_env); + return -EINVAL; + } + + if (ch->cable_type != CABLE_TYPE_DC && + ch->cable_type != CABLE_TYPE_USB) { + pr_err("Invalid type %d\n", ch->cable_type); + return -EINVAL; + } + + if (gpio_is_valid(ch->full_pin)) { + ret = devm_gpio_request(ch->dev, ch->full_pin, "charger full"); + if (ret) { + pr_err("gpio%d request fail %d\n", ch->full_pin, ret); + return ret; + } + wmt_gpio_setpull(ch->full_pin, (ch->full_level) ? + WMT_GPIO_PULL_DOWN : WMT_GPIO_PULL_UP); + gpio_direction_input(ch->full_pin); + } + + if (gpio_is_valid(ch->current_pin)) { + ret = devm_gpio_request(ch->dev, ch->current_pin, "charger current"); + if (ret) { + pr_err("gpio%d request fail %d\n", ch->current_pin, ret); + return ret; + } + gpio_direction_output(ch->current_pin, !ch->current_large_level); + } + + pr_info("charger match " DRVNAME ", %s cable, full %d, current %d\n" + "%s current switch, PC connected is %scharging\n", + (ch->cable_type == CABLE_TYPE_DC) ? "DC" : "USB", + ch->full_pin, ch->current_pin, + (ch->current_sw_mode == CURRENT_SWITCH_DYNAMIC) ? "dynamic" : "sleep", + (ch->pc_charging == PC_CONNECTED_NOT_CHARGING) ? "not " : ""); + return 0; +} + +static inline void set_current(struct mp2625_charger *ch) +{ + int large; + int charging = (ch->charger_status == POWER_SUPPLY_STATUS_CHARGING || + ch->charger_status == POWER_SUPPLY_STATUS_FULL); + + if (ch->current_sw_mode == CURRENT_SWITCH_DYNAMIC) + large = charging; + else + large = ch->sleeping ? 1 : 0; + + if (ch->cable_type == CABLE_TYPE_USB && wmt_is_pc_connected()) + large = 0; + + if (gpio_is_valid(ch->current_pin)) { + gpio_direction_output(ch->current_pin, + large ? ch->current_large_level : + !ch->current_large_level); + printk(KERN_DEBUG "set %s current\n", large ? "large" : "small"); + } +} + +static int ac_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct mp2625_charger *ch = + container_of(psy, struct mp2625_charger, psy_ac); + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = ch->ac_online; + break; + case POWER_SUPPLY_PROP_STATUS: + val->intval = ch->charger_status; + break; + default: + return -EINVAL; + } + return 0; +} + +static enum power_supply_property ac_properties[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_STATUS, +}; + +static int usb_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct mp2625_charger *ch = + container_of(psy, struct mp2625_charger, psy_usb); + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = ch->usb_online; + break; + default: + return -EINVAL; + } + return 0; +} + +static enum power_supply_property usb_properties[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static char *power_supplied_to[] = { + "battery", +}; + +static int power_supply_init(struct mp2625_charger *ch) +{ + int ret; + + ch->psy_ac.name = "ac", + ch->psy_ac.type = POWER_SUPPLY_TYPE_MAINS, + ch->psy_ac.supplied_to = power_supplied_to, + ch->psy_ac.num_supplicants = ARRAY_SIZE(power_supplied_to), + ch->psy_ac.properties = ac_properties, + ch->psy_ac.num_properties = ARRAY_SIZE(ac_properties), + ch->psy_ac.get_property = ac_get_property, + ret = power_supply_register(ch->dev, &ch->psy_ac); + if (ret) { + dev_err(ch->dev, "register ac power supply failed.\n"); + return ret; + } + + if (ch->pc_charging == PC_CONNECTED_CHARGING) { + ch->psy_usb.name = "usb", + ch->psy_usb.type = POWER_SUPPLY_TYPE_USB, + ch->psy_usb.supplied_to = power_supplied_to, + ch->psy_usb.num_supplicants = ARRAY_SIZE(power_supplied_to), + ch->psy_usb.properties = usb_properties, + ch->psy_usb.num_properties = ARRAY_SIZE(usb_properties), + ch->psy_usb.get_property = usb_get_property, + ret = power_supply_register(ch->dev, &ch->psy_usb); + if (ret) { + dev_err(ch->dev, "register ac power supply failed.\n"); + return ret; + } + } + + return 0; +} + +static void power_supply_release(struct mp2625_charger *ch) +{ + power_supply_unregister(&ch->psy_ac); + if (ch->pc_charging == PC_CONNECTED_CHARGING) + power_supply_unregister(&ch->psy_usb); +} + +static void mp2625_charger_work(struct work_struct *work) +{ + struct mp2625_charger *ch = + container_of(work, struct mp2625_charger, dwork.work); + int ac_online = 0; + int usb_online = 0; + int charger_status = 0; + + if (wmt_is_dc_plugin()) { + if (ch->cable_type == CABLE_TYPE_USB && wmt_is_pc_connected()) { + if (ch->pc_charging == PC_CONNECTED_CHARGING) { + charger_status = POWER_SUPPLY_STATUS_CHARGING; + usb_online = 1; + } else + charger_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + } else { + charger_status = POWER_SUPPLY_STATUS_CHARGING; + ac_online = 1; + } + + if (charger_status == POWER_SUPPLY_STATUS_CHARGING && + gpio_get_value(ch->full_pin) == ch->full_level) + charger_status = POWER_SUPPLY_STATUS_FULL; + } else + charger_status = POWER_SUPPLY_STATUS_DISCHARGING; + + if (ch->ac_online != ac_online || + ch->charger_status != charger_status) { + ch->ac_online = ac_online; + ch->charger_status = charger_status; + power_supply_changed(&ch->psy_ac); + } + + if (ch->pc_charging == PC_CONNECTED_CHARGING && + ch->usb_online != usb_online) { + ch->usb_online = usb_online; + power_supply_changed(&ch->psy_usb); + } + + set_current(ch); + led_power_enable(charger_status == POWER_SUPPLY_STATUS_CHARGING || + charger_status == POWER_SUPPLY_STATUS_FULL); +} + +void mp2625_pc_connected(void) +{ + if (g_charger) + schedule_delayed_work(&g_charger->dwork, 0); +} + +static irqreturn_t dcdet_irq(int irq, void *data) +{ + struct mp2625_charger *ch = data; + + if (PMCIS_VAL & BIT27) { + pmc_clear_intr_status(WKS_DCDET); + schedule_delayed_work(&ch->dwork, HZ/2); + return IRQ_HANDLED; + } + return IRQ_NONE; +} + +static int irqs_init(struct mp2625_charger *ch) +{ + unsigned long iflag = IRQF_SHARED; + int ret; + + if (REG32_VAL(0xfe120000) == 0x35100101) + iflag |= IRQF_NO_SUSPEND; + + ret = devm_request_irq(ch->dev, IRQ_PMC_WAKEUP, dcdet_irq, + iflag, "WMT-DCDET", ch); + if (ret < 0) { + pr_err("register DCDET irq failed\n"); + return ret; + } + + wmt_dcdet_irq_enable(); + return 0; +} + +static void irqs_release(struct mp2625_charger *ch) +{ + wmt_dcdet_irq_disable(); +} + +static int mp2625_probe(struct platform_device *pdev) +{ + struct mp2625_charger *ch; + int ret; + + ch = devm_kzalloc(&pdev->dev, sizeof(*ch), GFP_KERNEL); + if (!ch) + return -ENOMEM; + + ch->dev = &pdev->dev; + platform_set_drvdata(pdev, ch); + + ret = parse_charger_param(ch); + if (ret) + return ret;; + parse_charger_led(); + + if ((ret = power_supply_init(ch))) + return ret; + + INIT_DELAYED_WORK(&ch->dwork, mp2625_charger_work); + + if ((ret = irqs_init(ch))) { + power_supply_release(ch); + return ret; + } + + g_charger = ch; + schedule_delayed_work(&ch->dwork, 0); + + pr_info(DRVNAME " install success.\n"); + return 0; +} + +static int __devexit mp2625_remove(struct platform_device *pdev) +{ + struct mp2625_charger *ch = platform_get_drvdata(pdev); + irqs_release(ch); + cancel_delayed_work_sync(&ch->dwork); + power_supply_release(ch); + g_charger = NULL; + return 0; +} + +static int mp2625_suspend(struct device *dev) +{ + struct mp2625_charger *ch = dev_get_drvdata(dev); + cancel_delayed_work_sync(&ch->dwork); + ch->sleeping = 1; + set_current(ch); + return 0; +} + +static int mp2625_resume(struct device *dev) +{ + struct mp2625_charger *ch = dev_get_drvdata(dev); + schedule_delayed_work(&ch->dwork, HZ/2); + ch->sleeping = 0; + set_current(ch); + return 0; +} + +static const struct dev_pm_ops mp2625_pm_ops = { + .suspend = mp2625_suspend, + .resume = mp2625_resume, +}; + +static struct platform_driver mp2625_driver = { + .driver = { + .owner = THIS_MODULE, + .name = DRVNAME, + .pm = &mp2625_pm_ops, + }, + .probe = mp2625_probe, + .remove = mp2625_remove, +}; + +static struct platform_device *pdev; + +static int __init mp2625_init(void) +{ + int ret; + + ret = parse_charger_param(NULL); + if (ret) + return ret; + + ret = platform_driver_register(&mp2625_driver); + if (ret) + return ret; + + pdev = platform_device_register_simple(DRVNAME, -1, NULL, 0); + if (IS_ERR(pdev)) { + ret = PTR_ERR(pdev); + platform_driver_unregister(&mp2625_driver); + } + return ret; +} + +static void __exit mp2625_exit(void) +{ + platform_device_unregister(pdev); + platform_driver_unregister(&mp2625_driver); +} + +module_init(mp2625_init); +module_exit(mp2625_exit); + +MODULE_AUTHOR("WonderMedia"); +MODULE_DESCRIPTION("MP2625 Charger Driver"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/power/wmt_battery/gauge/bq27xx/Makefile b/drivers/power/wmt_battery/gauge/bq27xx/Makefile new file mode 100755 index 00000000..446fbf59 --- /dev/null +++ b/drivers/power/wmt_battery/gauge/bq27xx/Makefile @@ -0,0 +1,6 @@ + +s_wmt_batt_bq27xx-objs += \ + bq27x00_battery.o + +obj-m += s_wmt_batt_bq27xx.o + diff --git a/drivers/power/wmt_battery/gauge/bq27xx/bq27x00_battery.c b/drivers/power/wmt_battery/gauge/bq27xx/bq27x00_battery.c new file mode 100755 index 00000000..7a123615 --- /dev/null +++ b/drivers/power/wmt_battery/gauge/bq27xx/bq27x00_battery.c @@ -0,0 +1,1011 @@ +/* + * BQ27x00 battery driver + * + * Copyright (C) 2008 Rodolfo Giometti <giometti@linux.it> + * Copyright (C) 2008 Eurotech S.p.A. <info@eurotech.it> + * Copyright (C) 2010-2011 Lars-Peter Clausen <lars@metafoo.de> + * Copyright (C) 2011 Pali Rohár <pali.rohar@gmail.com> + * + * 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 <linux/module.h> +#include <linux/param.h> +#include <linux/jiffies.h> +#include <linux/workqueue.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/idr.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <asm/unaligned.h> +#include <mach/wmt_env.h> +#include <linux/power/wmt_battery.h> + +#include <linux/power/bq27x00_battery.h> + +#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 capacity */ +#define BQ27x00_REG_LMD 0x12 /* Last measured discharge */ +#define BQ27x00_REG_CYCT 0x2A /* Cycle count total */ +#define BQ27x00_REG_AE 0x22 /* Available energy */ + +#define BQ27000_REG_RSOC 0x0B /* Relative State-of-Charge */ +#define BQ27000_REG_ILMD 0x76 /* Initial last measured discharge */ +#define BQ27000_FLAG_EDVF BIT(0) /* Final End-of-Discharge-Voltage flag */ +#define BQ27000_FLAG_EDV1 BIT(1) /* First End-of-Discharge-Voltage flag */ +#define BQ27000_FLAG_CI BIT(4) /* Capacity Inaccurate flag */ +#define BQ27000_FLAG_FC BIT(5) +#define BQ27000_FLAG_CHGS BIT(7) /* Charge state flag */ + +#define BQ27500_REG_SOC 0x2C +#define BQ27500_REG_DCAP 0x3C /* Design capacity */ +#define BQ27500_FLAG_DSC BIT(0) +#define BQ27500_FLAG_SOCF BIT(1) /* State-of-Charge threshold final */ +#define BQ27500_FLAG_SOC1 BIT(2) /* State-of-Charge threshold 1 */ +#define BQ27500_FLAG_FC BIT(9) + +#define BQ27000_RS 20 /* Resistor sense */ + +#define CONFIG_BATTERY_BQ27X00_I2C + +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 energy; + int flags; +}; + +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_CAPACITY_LEVEL, + 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 int i2c_bus = 0; +static unsigned int poll_interval = 5; +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_dbg(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_dbg(di->dev, "error reading charge register %02x: %d\n", + reg, charge); + 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_dbg(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 Available energy in µWh + * Or < 0 if something fails. + */ +static int bq27x00_battery_read_energy(struct bq27x00_device_info *di) +{ + int ae; + + ae = bq27x00_read(di, BQ27x00_REG_AE, false); + if (ae < 0) { + dev_dbg(di->dev, "error reading available energy\n"); + return ae; + } + + if (di->chip == BQ27500) + ae *= 1000; + else + ae = ae * 29200 / BQ27000_RS; + + return ae; +} + +/* + * Return the battery temperature in tenths of degree Celsius + * Or < 0 if something fails. + */ +static int bq27x00_battery_read_temperature(struct bq27x00_device_info *di) +{ + int temp; + + temp = bq27x00_read(di, BQ27x00_REG_TEMP, false); + if (temp < 0) { + dev_err(di->dev, "error reading temperature\n"); + return temp; + } + + if (di->chip == BQ27500) + temp -= 2731; + else + temp = ((temp * 5) - 5463) / 2; + + return temp; +} + +/* + * 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_dbg(di->dev, "error reading time 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; + short voltage_now = 0,current_now = 0,capacity_now = 0; + + cache.flags = bq27x00_read(di, BQ27x00_REG_FLAGS, !is_bq27500); + if (cache.flags >= 0) { + if (!is_bq27500 && (cache.flags & BQ27000_FLAG_CI)) { + dev_info(di->dev, "battery is not calibrated! ignoring capacity values\n"); + cache.capacity = -ENODATA; + cache.energy = -ENODATA; + cache.time_to_empty = -ENODATA; + cache.time_to_empty_avg = -ENODATA; + cache.time_to_full = -ENODATA; + cache.charge_full = -ENODATA; + } else { + cache.capacity = bq27x00_battery_read_rsoc(di); + cache.energy = bq27x00_battery_read_energy(di); + 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.temperature = bq27x00_battery_read_temperature(di); + cache.cycle_count = bq27x00_battery_read_cyct(di); + + /* We only have to read charge design full once */ + if (di->charge_design_full <= 0) + di->charge_design_full = bq27x00_battery_read_ilmd(di); + voltage_now = bq27x00_read(di, BQ27x00_REG_VOLT, false)*2; + current_now = bq27x00_read(di, BQ27x00_REG_AI, false); + capacity_now = bq27x00_read(di, BQ27x00_REG_NAC, false); + } + + if (memcmp(&di->cache, &cache, sizeof(cache)) != 0) { + di->cache = cache; + power_supply_changed(&di->bat); + } + + //printk("%s percentage=%d,voltage=%d,current=%d,temperature=%d,%d,flag=%d\n",__FUNCTION__,cache.capacity,voltage_now,current_now,cache.temperature,capacity_now,cache.flags); + + 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 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; + int flags; + + curr = bq27x00_read(di, BQ27x00_REG_AI, false); + if (curr < 0) { + dev_err(di->dev, "error reading current\n"); + return curr; + } + + if (di->chip == BQ27500) { + /* bq27500 returns signed value */ + val->intval = (int)((s16)curr) * 1000; + } else { + flags = bq27x00_read(di, BQ27x00_REG_FLAGS, false); + if (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; + + status = charger_get_status(); + if (status < 0) + return status; + + if (status == POWER_SUPPLY_STATUS_CHARGING) { + 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; +} + +static int bq27x00_battery_capacity_level(struct bq27x00_device_info *di, + union power_supply_propval *val) +{ + int level; + + if (di->chip == BQ27500) { + if (di->cache.flags & BQ27500_FLAG_FC) + level = POWER_SUPPLY_CAPACITY_LEVEL_FULL; + else if (di->cache.flags & BQ27500_FLAG_SOC1) + level = POWER_SUPPLY_CAPACITY_LEVEL_LOW; + else if (di->cache.flags & BQ27500_FLAG_SOCF) + level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; + else + level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; + } else { + if (di->cache.flags & BQ27000_FLAG_FC) + level = POWER_SUPPLY_CAPACITY_LEVEL_FULL; + else if (di->cache.flags & BQ27000_FLAG_EDV1) + level = POWER_SUPPLY_CAPACITY_LEVEL_LOW; + else if (di->cache.flags & BQ27000_FLAG_EDVF) + level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; + else + level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; + } + + val->intval = level; + + 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) { + dev_err(di->dev, "error reading voltage\n"); + return volt; + } + + val->intval = volt * 1000 * 2; + + 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_CAPACITY_LEVEL: + ret = bq27x00_battery_capacity_level(di, val); + break; + case POWER_SUPPLY_PROP_TEMP: + ret = bq27x00_simple_value(di->cache.temperature, 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_simple_value(di->cache.energy, 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) +{ + /* + * power_supply_unregister call bq27x00_battery_get_property which + * call bq27x00_battery_poll. + * Make sure that bq27x00_battery_poll will not call + * schedule_delayed_work again after unregister (which cause OOPS). + */ + poll_interval = 0; + + 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 int bq27x00_battery_suspend(struct i2c_client *client, pm_message_t mesg) +{ + struct bq27x00_device_info *di = i2c_get_clientdata(client); + cancel_delayed_work_sync(&di->work); + return 0; +} + +static int bq27x00_battery_resume(struct i2c_client *client) +{ + struct bq27x00_device_info *di = i2c_get_clientdata(client); + schedule_delayed_work(&di->work, 1 * HZ); + 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, + .suspend = bq27x00_battery_suspend, + .resume = bq27x00_battery_resume, + .id_table = bq27x00_id, +}; + +static struct i2c_board_info bq27x00_board_info = { + I2C_BOARD_INFO("bq27500", 0x55), +}; + +static inline int bq27x00_battery_i2c_init(void) +{ + struct i2c_client *client; + struct i2c_adapter *adap; + + int ret = i2c_add_driver(&bq27x00_battery_driver); + if (ret) + printk(KERN_ERR "Unable to register BQ27x00 i2c driver\n"); + adap = i2c_get_adapter(i2c_bus); + if (!adap) + return -ENODEV; + client = i2c_new_device(adap, &bq27x00_board_info); + i2c_put_adapter(adap); + if (!client) { + printk("bq27x00 i2c_new_device failed\n"); + return -ENODEV; + } + + 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 int bq27000_battery_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct bq27x00_device_info *di = platform_get_drvdata(pdev); + cancel_delayed_work_sync(&di->work); + return 0; +} + +static int bq27000_battery_resume(struct platform_device *pdev) +{ + struct bq27x00_device_info *di = platform_get_drvdata(pdev); + schedule_delayed_work(&di->work, 1 * HZ); + return 0; +} + +static struct platform_driver bq27000_battery_driver = { + .probe = bq27000_battery_probe, + .remove = __devexit_p(bq27000_battery_remove), + .suspend = bq27000_battery_suspend; + .resume = bq27000_battery_resume; + .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 parse_battery_param(void) +{ + char env[] = "wmt.battery.param"; + char buf[64]; + char *p; + size_t l = sizeof(buf); + int i; + + if (wmt_getsyspara(env, buf, &l)) + return -EINVAL; + + if (prefixcmp(buf, "bq27xx:")) + return -ENODEV; + + p = strchr(buf, ':'); + + i = sscanf(p + 1, "%d", &i2c_bus); + if (i < 1) { + printk("Parse_param err: Can't get i2c bus num\n"); + return -EINVAL; + } + + return 0; +} + + +static int __init bq27x00_battery_init(void) +{ + int ret; + + if (parse_battery_param()) + return -ENODEV; + + 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 <giometti@linux.it>"); +MODULE_DESCRIPTION("BQ27x00 battery monitor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/wmt_battery/gauge/saradc/Makefile b/drivers/power/wmt_battery/gauge/saradc/Makefile new file mode 100644 index 00000000..8b63f3f3 --- /dev/null +++ b/drivers/power/wmt_battery/gauge/saradc/Makefile @@ -0,0 +1,4 @@ + +s_wmt_batt_saradc-objs += saradc_battery.o +obj-m += s_wmt_batt_saradc.o + diff --git a/drivers/power/wmt_battery/gauge/saradc/saradc_battery.c b/drivers/power/wmt_battery/gauge/saradc/saradc_battery.c new file mode 100644 index 00000000..9bdf50c8 --- /dev/null +++ b/drivers/power/wmt_battery/gauge/saradc/saradc_battery.c @@ -0,0 +1,341 @@ +/* + * adc_battery.c - WonderMedia Adc Battery Driver. + * + * Copyright (C) 2013 WonderMedia Technologies, 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. + * + * 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 <linux/init.h> +#include <linux/module.h> +#include <linux/proc_fs.h> +#include <linux/power_supply.h> +#include <linux/platform_device.h> +#include <linux/power/wmt_battery.h> + +#define DRVNAME "adc-batt" + +#undef pr_err +#define pr_err(fmt, args...) printk("[" DRVNAME "] " fmt, ##args) +#undef pr_info +#define pr_info(fmt, args...) printk("[" DRVNAME "] " fmt, ##args) + +enum { + COMPENSATION_VOLUME = 0, + COMPENSATION_BRIGHTNESS, + COMPENSATION_WIFI, + COMPENSATION_VIDEO, + COMPENSATION_USB, + COMPENSATION_HDMI, + COMPENSATION_COUNT +}; + +static const char *compensation_strings[] = { + "volume", + "brightness", + "wifi", + "video", + "usb", + "hdmi" +}; + +struct adc_device_info { + struct device *dev; + struct power_supply ps_bat; + struct mutex mutex; + + int compensation[COMPENSATION_COUNT]; + int capacity; + int sleeping; + int debug; +}; + +static struct adc_device_info *adc_dev_info = NULL; + +static inline int adc_manual_read_volt(void) +{ + extern unsigned int ReadBattery(void); + return ReadBattery(); +} + +static inline int volt_reg_to_mV(int value) +{ + // voltage = adc * (3300/128) * (1430/1000) = adc * 4719 / 128 + + return ((value * 4719) / 128); +} + +static int adc_bat_read_voltage(struct adc_device_info *di, int *intval) +{ + int ret; + + ret = adc_manual_read_volt(); + if (ret < 0) + return ret; + + *intval = volt_reg_to_mV(ret); + return 0; +} + +static int adc_bat_read_status(struct adc_device_info *di, int *intval) +{ + int status; + + status = charger_get_status(); + if (status < 0) + return status; + + if (status == POWER_SUPPLY_STATUS_CHARGING && di->capacity == 100) + status = POWER_SUPPLY_STATUS_FULL; + + *intval = status; + return 0; +} + +static int adc_proc_read(char *buf, char **start, off_t offset, int len, + int *eof, void *data) +{ + int l = 0, i; + int ret, status, dcin, voltage, full; + struct adc_device_info *di = adc_dev_info; + + mutex_lock(&di->mutex); + + ret = adc_bat_read_status(di, &status); + if (ret) { + pr_err("adc_bat_read_status failed\n"); + return 0; + } + ret = adc_bat_read_voltage(di, &voltage); + if (ret) { + pr_err("adc_bat_read_voltage failed\n"); + return 0; + } + dcin = power_supply_is_system_supplied(); + full = charger_is_full(); + + l += sprintf(buf + l, "status : %d\n", status); + l += sprintf(buf + l, "dcin : %d\n", dcin); + l += sprintf(buf + l, "voltage : %d\n", voltage); + l += sprintf(buf + l, "full : %d\n", full); + l += sprintf(buf + l, "sleeping : %d\n", di->sleeping); + l += sprintf(buf + l, "debug : %d\n", di->debug); + + for (i = 0; i < COMPENSATION_COUNT; i++) { + l += sprintf(buf +l, "compensation %10s : %d\n", + compensation_strings[i], di->compensation[i]); + } + + /* clear after read */ + di->sleeping = 0; + + mutex_unlock(&di->mutex); + return l; +} + +static int adc_proc_write(struct file *file, const char *buffer, + unsigned long count, void *data) +{ + int bm, usage; + struct adc_device_info *di = adc_dev_info; + + if (sscanf(buffer, "capacity=%d", &di->capacity)) { + power_supply_changed(&di->ps_bat); + goto out; + } + + if (sscanf(buffer, "debug=%d", &di->debug)) + goto out; + + if (sscanf(buffer, "MODULE_CHANGE:%d-%d", &bm, &usage) < 2) { + return 0; + } + + if (bm < 0 || bm >= COMPENSATION_COUNT) { + printk("bm %d error, [0, %d)\n", bm, COMPENSATION_COUNT); + return 0; + } + + if (usage > 100 || usage < 0) { + printk("usage %d error\n", usage); + return 0; + } + + mutex_lock(&di->mutex); + di->compensation[bm] = usage; + mutex_unlock(&di->mutex); +out: + return count; +} + +#define BATTERY_PROC_NAME "battery_calibration" + +static void adc_proc_init(void) +{ + struct proc_dir_entry *entry; + + entry = create_proc_entry(BATTERY_PROC_NAME, 0666, NULL); + if (entry) { + entry->read_proc = adc_proc_read; + entry->write_proc = adc_proc_write; + } +} + +static void adc_proc_cleanup(void) +{ + remove_proc_entry(BATTERY_PROC_NAME, NULL); +} + +#define to_adc_device_info(x) container_of((x), \ + struct adc_device_info, ps_bat); + +static int adc_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret = 0; + struct adc_device_info *di = to_adc_device_info(psy); + + mutex_lock(&di->mutex); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + ret = adc_bat_read_status(di, &val->intval); + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = di->capacity; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = adc_bat_read_voltage(di, &val->intval); + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 1; + break; + default: + ret = -EINVAL; + break; + } + + mutex_unlock(&di->mutex); + return ret; +} + +static void adc_external_power_changed(struct power_supply *psy) +{ + power_supply_changed(psy); +} + +static enum power_supply_property adc_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_TECHNOLOGY, +}; + +static int __devinit adc_batt_probe(struct platform_device *pdev) +{ + struct adc_device_info *di; + int ret; + + di = kzalloc(sizeof(*di), GFP_KERNEL); + if (!di) { + dev_err(&pdev->dev, "no memery\n"); + return -ENOMEM; + } + + di->dev = &pdev->dev; + di->capacity = 50; + + mutex_init(&di->mutex); + + di->ps_bat.name = "battery"; + di->ps_bat.type = POWER_SUPPLY_TYPE_BATTERY; + di->ps_bat.properties = adc_battery_props; + di->ps_bat.num_properties = ARRAY_SIZE(adc_battery_props); + di->ps_bat.get_property = adc_battery_get_property; + di->ps_bat.external_power_changed = adc_external_power_changed; + + ret = power_supply_register(di->dev, &di->ps_bat); + if (ret) { + dev_err(di->dev, "failed to register battery: %d\n", ret); + kfree(di); + return ret; + } + + platform_set_drvdata(pdev, di); + adc_dev_info = di; + + adc_proc_init(); + + pr_info("ADC Battery Driver Installed!\n"); + return 0; +} + +static int __devexit adc_batt_remove(struct platform_device *pdev) +{ + struct adc_device_info *di = platform_get_drvdata(pdev); + adc_proc_cleanup(); + power_supply_unregister(&di->ps_bat); + kfree(di); + adc_dev_info = NULL; + pr_info("ADC Battery Driver Removed!\n"); + return 0; +} + +static int adc_suspend(struct platform_device *pdev, pm_message_t state) +{ + return 0; +} + +static int adc_resume(struct platform_device *pdev) +{ + struct adc_device_info *di = platform_get_drvdata(pdev); + di->sleeping = 1; + return 0; +} + +static struct platform_driver adc_batt_driver = { + .driver = { + .name = DRVNAME, + }, + .probe = adc_batt_probe, + .remove = __devexit_p(adc_batt_remove), + .suspend = adc_suspend, + .resume = adc_resume, +}; + +static struct platform_device *pdev; + +static int __init adc_batt_init(void) +{ + pdev = platform_device_register_simple(DRVNAME, -1, NULL, 0); + if (IS_ERR(pdev)) { + return PTR_ERR(pdev); + } + return platform_driver_register(&adc_batt_driver); +} + +static void __exit adc_batt_exit(void) +{ + platform_driver_unregister(&adc_batt_driver); + platform_device_unregister(pdev); +} + +module_init(adc_batt_init); +module_exit(adc_batt_exit); + +MODULE_AUTHOR("WonderMedia"); +MODULE_DESCRIPTION("WonderMedia Adc Battery Driver"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/power/wmt_battery/gauge/sp2541/Makefile b/drivers/power/wmt_battery/gauge/sp2541/Makefile new file mode 100755 index 00000000..43637d92 --- /dev/null +++ b/drivers/power/wmt_battery/gauge/sp2541/Makefile @@ -0,0 +1,5 @@ + +s_wmt_batt_sp2541-objs += sp2541_battery.o + +obj-m += s_wmt_batt_sp2541.o + diff --git a/drivers/power/wmt_battery/gauge/sp2541/sp2541_battery.c b/drivers/power/wmt_battery/gauge/sp2541/sp2541_battery.c new file mode 100755 index 00000000..2c619925 --- /dev/null +++ b/drivers/power/wmt_battery/gauge/sp2541/sp2541_battery.c @@ -0,0 +1,765 @@ +/* + * SP2541 battery driver + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include <linux/module.h> +#include <linux/param.h> +#include <linux/jiffies.h> +#include <linux/workqueue.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/idr.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <asm/unaligned.h> +#include <linux/proc_fs.h> +#include <asm/uaccess.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/power/wmt_battery.h> +#include <mach/wmt_env.h> +#include <linux/firmware.h> + +#define RAM_READ_CMD 0x55 +#define EEPROM_READ_CMD 0xF5 +#define EEPROM_WRITE_CMD 0xFA + +#define DRIVER_VERSION "1.1.0" +#define SP2541_REG_TEMP 0x06 +#define SP2541_REG_VOLT 0x08 +#define SP2541_REG_AI 0x14 +#define SP2541_REG_FLAGS 0x0A +#define SP2541_REG_TTE 0x16 +#define SP2541_REG_TTF 0x18 +#define SP2541_REG_TTECP 0x26 +#define SP2541_REG_RSOC 0x0B /* Relative State-of-Charge */ +#define SP2541_REG_SOC 0x2c + +#define SP2541_FLAG_DSC BIT(0) +#define SP2541_FLAG_CHGS BIT(8) +#define SP2541_FLAG_FC BIT(9) +#define SP2541_FLAG_OTD BIT(14) +#define SP2541_FLAG_OTC BIT(15) + +#define SP2541_SPEED 100 * 1000 + +struct battery_param { + char rom_name[32]; + int i2c_bus; + int interval; +}; + +static struct battery_param battery_param; + +/* 0x07a0 ~ 0x07af + * 0x07b0 ~ 0x07bf + */ +union rom_version { + struct { + uint32_t magic; + uint32_t version; + } v; + uint8_t bytes[8]; +}; + +#define __ROM_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c)) +#define SP2514_MAGIC __ROM_VERSION('W', 'M', 'T') + +#define ROM_VERSION(ver) \ + { \ + .v.magic = SP2514_MAGIC, \ + .v.version = ver, \ + } + +struct rom_entry { + uint16_t address; + uint16_t value; +}; +#define ROM_ENTRY(a, l, v) { .address = a, .length = l, .value = v } + +struct rom_struct { + union rom_version rv; + struct rom_entry *table; + size_t table_size; +}; + +#define ROM_STRUCT(ver) \ + { \ + .rv = ROM_VERSION(ver), \ + } + +static struct rom_entry *rom_entry; +static struct rom_struct rom_table = ROM_STRUCT(0); + +struct sp2541_struct { + struct device *dev; + struct i2c_client *client; + int capacity; + struct rom_struct *rom; + + struct power_supply psy_bat; + struct delayed_work dwork; + unsigned int interval; +}; + +static int parse_battery_param(void) +{ + char env[] = "wmt.battery.param"; + char buf[64]; + char *p; + size_t l = sizeof(buf); + int i; + + if (wmt_getsyspara(env, buf, &l)) + return -EINVAL; + + if (prefixcmp(buf, "sp2541_")) + return -ENODEV; + + p = strchr(buf, ':'); + strncpy(battery_param.rom_name, buf + 7, p - buf - 7); + pr_info("rom name -- %s\n", battery_param.rom_name); + + i = sscanf(p + 1, "%d:%d", + &battery_param.i2c_bus, &battery_param.interval); + if (i < 2) + return -EINVAL; + + return 0; +} + +//#define SP2541_BIG_ENDIAN +#ifdef SP2541_BIG_ENDIAN +static u16 get_bigend_le16(const void *_ptr) +{ + const uint8_t *ptr = _ptr; + return (ptr[0]<<8) | (ptr[1]); +} + +static inline uint16_t __get_le16(const void *buf) +{ + return get_bigend_le16(buf); +} +#else +static inline uint16_t __get_le16(const void *buf) +{ + return get_unaligned_le16(buf); +} +#endif + +static int sp2541_read(struct sp2541_struct *sp, uint8_t cmd, uint8_t reg, + uint8_t buf[], unsigned len) +{ + struct i2c_client *client = sp->client; + struct i2c_msg xfer[2]; + char data[2] = { cmd, reg }; + int ret; + + xfer[0].addr = client->addr; + xfer[0].flags = 0 | I2C_M_NOSTART; + xfer[0].len = 2; + xfer[0].buf = data; + xfer[1].addr = client->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = len; + xfer[1].buf = buf; + + ret = i2c_transfer(client->adapter, xfer, ARRAY_SIZE(xfer)); + if (ret != ARRAY_SIZE(xfer)) { + pr_err("read[r:%d] errcode[%d]\n", reg, ret); + if (ret < 0) + return ret; + else + return -EIO; + } + + return 0; +} + +static int sp2541_write(struct sp2541_struct *sp, uint8_t cmd, uint8_t reg, + const uint8_t buf) +{ + struct i2c_client *client = sp->client; + struct i2c_msg xfer; + char data[3] = { cmd, reg, buf }; + int ret; + + xfer.addr = client->addr; + xfer.flags = 0; + xfer.len = 3; + xfer.buf = data; + + ret = i2c_transfer(client->adapter, &xfer, 1); + if (ret != 1) { + pr_err("read[r:%d] errcode[%d]\n", reg, ret); + if (ret < 0) + return ret; + else + return -EIO; + } + + return 0; +} + +static int eeprom_read_byte(struct sp2541_struct *sp, uint16_t addr, uint8_t *value) +{ + uint8_t tmp; + int i; + + if (sp2541_write(sp, EEPROM_WRITE_CMD, 0x00, (addr >> 0) & 0xff) || + sp2541_write(sp, EEPROM_WRITE_CMD, 0x01, (addr >> 8) & 0xff) || + sp2541_write(sp, EEPROM_WRITE_CMD, 0x03, 0x06)) + return -EIO; + + for (i = 0; i < 10; i++) { + if (sp2541_read(sp, EEPROM_READ_CMD, 0x03, &tmp, 1)) + return -EIO; + if (tmp == 0) + break; + } + if (i == 10) + return -EBUSY; + + if (sp2541_read(sp, EEPROM_READ_CMD, 0x02, value, 1)) + return -EIO; + + return 0; +} + +static int eeprom_write_byte(struct sp2541_struct *sp, uint16_t addr, uint8_t value) +{ + uint8_t tmp; + int i; + + if (sp2541_write(sp, EEPROM_WRITE_CMD, 0x00, (addr >> 0) & 0xff) || + sp2541_write(sp, EEPROM_WRITE_CMD, 0x01, (addr >> 8) & 0xff) || + sp2541_write(sp, EEPROM_WRITE_CMD, 0x02, value) || + sp2541_write(sp, EEPROM_WRITE_CMD, 0x03, 0x05)) + return -EIO; + + for (i = 0; i < 10; i++) { + if (sp2541_read(sp, EEPROM_READ_CMD, 0x03, &tmp, 1)) + return -EIO; + if (tmp == 0) + break; + } + if (i == 10) + return -EBUSY; + + return 0; +} + +static int eeprom_read(struct sp2541_struct *sp, uint16_t start, + uint8_t *buf, size_t len) +{ + int ret, i; + for (i = 0; i < len; i++) { + ret = eeprom_read_byte(sp, start + i, buf + len - i - 1); + if (ret) + return ret; + } + return 0; +} + +static int eeprom_write(struct sp2541_struct *sp, uint16_t start, + uint8_t *buf, size_t len) +{ + int ret, i, j; + for (i = 0; i < len; i++) { + //pr_info("wirte addr=0x%x,val=0x%x\n", start + i, buf[len - i - 1]); + for (j = 0; j < 3; j++) { + ret = eeprom_write_byte(sp, start + i, buf[len - i - 1]); + if (ret) + pr_err("eeprom write byte err #%d\n", j); + else + break; + } + if (j == 3) + return ret; + } + return 0; +} + +static int eeprom_update(struct sp2541_struct *sp, + struct rom_entry *table, size_t count) +{ + int ret, i; + for (i = 0; i < count; i++) { + ret = eeprom_write(sp, table[i].address, + (uint8_t *)&table[i].value, + 1);//table[i].length); + if (ret) + return ret; + } + return 0; +} + +static int sp2541_load_romtable(struct sp2541_struct *sp) +{ + char table_name[32]; + const struct firmware *fw_entry; + int count, i; + unsigned int val1, val2, version; + + sprintf(table_name, "%s.EEP", battery_param.rom_name); + for (i = 0; i < 3; i++) { + if(request_firmware(&fw_entry, table_name, sp->dev)!=0) + pr_err("cat't request firmware #%d\n", i); + else + break; + } + if (i == 3) + return -EINVAL; + + if (fw_entry->size <= 0) { + pr_err("load firmware error\n"); + release_firmware(fw_entry); + return -EINVAL; + } + count = (fw_entry->size - 13) / 13; //13 bytes per line, last line is version + rom_entry = kzalloc(count * sizeof(*rom_entry), GFP_KERNEL); + + for (i = 0; i < count; i++) { + sscanf(fw_entry->data + i * 13, "%x=%x", &val1, &val2); + rom_entry[i].address = val1 & 0xFFFF; + rom_entry[i].value = val2 & 0xFF; + //pr_info("%d: %x = %x\n", i, rom_entry[i].address, rom_entry[i].value); + } + sscanf(fw_entry->data + i * 13, "version=%d", &version); + //pr_info("version is %d\n", version); + + rom_table.rv.v.version = version; + rom_table.table = rom_entry; + rom_table.table_size = count; + release_firmware(fw_entry); + return 0; +} + +static int sp2541_eeprom_check(struct sp2541_struct *sp) +{ + union rom_version version; + int ret; + + if (sp2541_load_romtable(sp)) + return 0; + sp->rom = &rom_table; + + ret = eeprom_read(sp, 0x7a0, (uint8_t *)&version, sizeof(version)); + if (ret) + return ret; + + if (version.v.magic != SP2514_MAGIC || + version.v.version != sp->rom->rv.v.version) { + pr_info("old version %d\nnew version %d\n", version.v.version, + sp->rom->rv.v.version); + ret = eeprom_update(sp, sp->rom->table, sp->rom->table_size); + if (ret) { + pr_err("eeprom_update failed\n"); + return ret; + } + + // update version + ret = eeprom_write(sp, 0x07a0, + (uint8_t *)&sp->rom->rv, + sizeof(sp->rom->rv)); + if (ret) { + pr_err("sp2541 version update failed\n"); + return ret; + } + } + + if (rom_entry != NULL) + kfree(rom_entry); + rom_table.table = NULL; + rom_table.table_size = 0; + + return 0; +} + +static int sp2541_battery_temperature(struct sp2541_struct *sp) +{ + int ret; + int temp = 0; + uint8_t buf[2] ={0}; + + ret = sp2541_read(sp, RAM_READ_CMD,SP2541_REG_TEMP,buf,2); + if (ret<0) { + dev_err(sp->dev, "error reading temperature\n"); + return ret; + } + + temp = __get_le16(buf); + + temp = (temp/10) - 273; + return temp; +} + +static int sp2541_battery_voltage(struct sp2541_struct *sp) +{ + uint8_t buf[2] = {0}; + int volt = 0; + int ret; + + ret = sp2541_read(sp, RAM_READ_CMD,SP2541_REG_VOLT,buf,2); + if (ret<0) { + dev_err(sp->dev, "error reading voltage\n"); + return ret; + } + + volt = __get_le16(buf); + + return volt; +} + +/* + * Return the battery average current + * Note that current can be negative signed as well + * Or 0 if something fails. + */ +static int sp2541_battery_current(struct sp2541_struct *sp) +{ + int ret; + int curr = 0; + uint8_t buf[2] = {0}; + + ret = sp2541_read(sp, RAM_READ_CMD,SP2541_REG_AI,buf,2); + if (ret<0) { + dev_err(sp->dev, "error reading current\n"); + return 0; + } + + curr = __get_le16(buf); + + if (curr > 0x8000) { + //curr = 0xFFFF^(curr-1); + curr = curr-0x10000; + } + + return curr; +} + +/* + * Return the battery Relative State-of-Charge + * Or < 0 if something fails. + */ +static int sp2541_battery_rsoc(struct sp2541_struct *sp) +{ + int ret; + int rsoc = 0; + uint8_t buf[2]; + + ret = sp2541_read(sp, RAM_READ_CMD,SP2541_REG_SOC,buf,2); + if (ret<0) { + dev_err(sp->dev, "error reading relative State-of-Charge\n"); + return ret; + } + + rsoc = __get_le16(buf); + + return rsoc; +} + +static int sp2541_battery_status(struct sp2541_struct *sp, + union power_supply_propval *val) +{ + int status = 0; + int capacity; +#if 0 + uint8_t buf[2] = {0}; + int flags = 0; + int ret = 0; + + ret = sp2541_read(sp, RAM_READ_CMD,SP2541_REG_FLAGS, buf, 2); + if (ret < 0) { + dev_err(sp->dev, "error reading flags\n"); + return ret; + } + + flags = __get_le16(buf); +#endif + + status = charger_get_status(); + if (status < 0) + return status; + capacity = sp2541_battery_rsoc(sp); + + if (status == POWER_SUPPLY_STATUS_CHARGING && capacity == 100)//(flags & SP2541_FLAG_FC)) + status = POWER_SUPPLY_STATUS_FULL; + + val->intval = status; + return 0; +} + +static int sp2541_health_status(struct sp2541_struct *sp, + union power_supply_propval *val) +{ + uint8_t buf[2] = {0}; + int flags = 0; + int status; + int ret; + + ret = sp2541_read(sp, RAM_READ_CMD,SP2541_REG_FLAGS, buf, 2); + if (ret < 0) { + dev_err(sp->dev, "error reading flags\n"); + return ret; + } + + flags = __get_le16(buf); + + if ((flags & SP2541_FLAG_OTD)||(flags & SP2541_FLAG_OTC)) + status = POWER_SUPPLY_HEALTH_OVERHEAT; + else + status = POWER_SUPPLY_HEALTH_GOOD; + + val->intval = status; + return 0; +} + +static int sp2541_battery_time(struct sp2541_struct *sp, int reg, + union power_supply_propval *val) +{ + uint8_t buf[2] = {0}; + int tval = 0; + int ret; + + ret = sp2541_read(sp, RAM_READ_CMD,reg,buf,2); + if (ret < 0) { + dev_err(sp->dev, "error reading register %02x\n", reg); + return ret; + } + + tval = __get_le16(buf); + + if (tval == 65535) + return -ENODATA; + + val->intval = tval * 60; + return 0; +} + +#define to_sp2541_struct(x) container_of((x), struct sp2541_struct, psy_bat) + +static int sp2541_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sp2541_struct *sp = to_sp2541_struct(psy); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + ret = sp2541_battery_status(sp, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + case POWER_SUPPLY_PROP_PRESENT: + val->intval = sp2541_battery_voltage(sp); + if (psp == POWER_SUPPLY_PROP_PRESENT) { + val->intval = val->intval <= 0 ? 0 : 1; + } + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = sp2541_battery_current(sp); + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = sp2541_battery_rsoc(sp); + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = sp2541_battery_temperature(sp); + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_HEALTH: + ret = sp2541_health_status(sp, val); + break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: + ret = sp2541_battery_time(sp, SP2541_REG_TTE, val); + break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: + ret = sp2541_battery_time(sp, SP2541_REG_TTECP, val); + break; + case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW: + ret = sp2541_battery_time(sp, SP2541_REG_TTF, val); + break; + default: + return -EINVAL; + } + + return ret; +} + +static enum power_supply_property sp2541_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_TECHNOLOGY, + POWER_SUPPLY_PROP_HEALTH, +}; + +static void sp2541_external_power_changed(struct power_supply *psy) +{ + power_supply_changed(psy); +} + +static void sp2541_battery_work(struct work_struct *work) +{ + struct sp2541_struct *sp = + container_of(work, struct sp2541_struct, dwork.work); + static int last_cap = -1; + + int curr_cap = sp2541_battery_rsoc(sp); + if (curr_cap != last_cap) { + power_supply_changed(&sp->psy_bat); + last_cap = curr_cap; + } + + schedule_delayed_work(&sp->dwork, sp->interval); +} + +static int sp2541_battery_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct sp2541_struct *sp; + int ret; + + sp = devm_kzalloc(&client->dev, sizeof(*sp), GFP_KERNEL); + if (!sp) { + dev_err(&client->dev, "failed to allocate device info data\n"); + return -ENOMEM; + } + + sp->client = client; + sp->dev = &client->dev; + i2c_set_clientdata(client, sp); + + ret = sp2541_eeprom_check(sp); + if (ret) { + dev_err(sp->dev, "eeprom check failed\n"); + return ret; + } + + sp->interval = msecs_to_jiffies(battery_param.interval); + + sp->psy_bat.name = "battery"; + sp->psy_bat.type = POWER_SUPPLY_TYPE_BATTERY; + sp->psy_bat.properties = sp2541_battery_props; + sp->psy_bat.num_properties = ARRAY_SIZE(sp2541_battery_props); + sp->psy_bat.get_property = sp2541_battery_get_property; + sp->psy_bat.external_power_changed = sp2541_external_power_changed; + + ret = power_supply_register(&client->dev, &sp->psy_bat); + if (ret) { + dev_err(&client->dev, "failed to register battery\n"); + return ret; + } + + INIT_DELAYED_WORK(&sp->dwork, sp2541_battery_work); + + schedule_delayed_work(&sp->dwork, sp->interval); + dev_info(&client->dev, "support ver. %s enabled\n", DRIVER_VERSION); + return 0; +} + +static int sp2541_battery_remove(struct i2c_client *client) +{ + struct sp2541_struct *sp = i2c_get_clientdata(client); + cancel_delayed_work_sync(&sp->dwork); + power_supply_unregister(&sp->psy_bat); + return 0; +} + +static int sp2541_i2c_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct sp2541_struct *sp = i2c_get_clientdata(client); + cancel_delayed_work_sync(&sp->dwork); + return 0; +} + +static int sp2541_i2c_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct sp2541_struct *sp = i2c_get_clientdata(client); + schedule_delayed_work(&sp->dwork, 1*HZ); + return 0; +} + +static SIMPLE_DEV_PM_OPS(sp2541_dev_pm_ops, + sp2541_i2c_suspend, sp2541_i2c_resume); + +static const struct i2c_device_id sp2541_id[] = { + { "sp2541", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, sp2541_id); + +static struct i2c_driver sp2541_battery_driver = { + .driver = { + .name = "sp2541", + .owner = THIS_MODULE, + .pm = &sp2541_dev_pm_ops, + }, + .probe = sp2541_battery_probe, + .remove = __devexit_p(sp2541_battery_remove), + .id_table = sp2541_id, +}; + +static struct i2c_board_info sp2541_i2c_info = { + I2C_BOARD_INFO("sp2541", 0x14), +}; + +static struct i2c_client *i2c_client; + +static int __init sp2541_battery_init(void) +{ + struct i2c_adapter *i2c_adap; + int ret; + + if (parse_battery_param()) + return -ENODEV; + + i2c_adap = i2c_get_adapter(battery_param.i2c_bus); + if (!i2c_adap) { + pr_err("get i2c%d adapter failed\n", battery_param.i2c_bus); + return -ENODEV; + } + i2c_client = i2c_new_device(i2c_adap, &sp2541_i2c_info); + i2c_put_adapter(i2c_adap); + if (!i2c_client) { + pr_err("Unable to add I2C device for 0x%x\n", sp2541_i2c_info.addr); + return -ENODEV; + } + + ret = i2c_add_driver(&sp2541_battery_driver); + if (ret) { + pr_err("Unable to register sp2541_struct driver\n"); + } + + return ret; +} + +static void __exit sp2541_battery_exit(void) +{ + i2c_del_driver(&sp2541_battery_driver); + i2c_unregister_device(i2c_client); +} + +module_init(sp2541_battery_init); +module_exit(sp2541_battery_exit); + +MODULE_AUTHOR("clb"); +MODULE_DESCRIPTION("sp2541_struct battery monitor driver"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/power/wmt_battery/gauge/ug31xx/Makefile b/drivers/power/wmt_battery/gauge/ug31xx/Makefile new file mode 100755 index 00000000..758c6033 --- /dev/null +++ b/drivers/power/wmt_battery/gauge/ug31xx/Makefile @@ -0,0 +1,13 @@ + +s_wmt_batt_ug31xx-objs += \ + uG31xx_API.o \ + ug31xx_i2c.o \ + ug31xx_gauge.o \ + uG31xx_API_Measurement.o \ + uG31xx_API_Otp.o \ + uG31xx_API_System.o \ + uG31xx_API_Backup.o \ + uG31xx_API_Capacity.b \ + +obj-m += s_wmt_batt_ug31xx.o + diff --git a/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_cw500_20130801_103638.h b/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_cw500_20130801_103638.h new file mode 100755 index 00000000..ee375b15 --- /dev/null +++ b/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_cw500_20130801_103638.h @@ -0,0 +1,233 @@ +
+/* This file is auto-generated. Don't edit this file. */
+
+/// R-Sense = 20
+/// ILMD = 4000
+/// EDVF = 3500
+/// Taper Current = 150
+/// Taper Voltage = 4150
+/// Taper Time = 60
+
+char FactoryGGBXFile_cw500 [] = {
+ 0x5f,0x47,0x47,0x5f,0xed,0xc9,0x00,0x00,
+ 0xb6,0xfa,0x3a,0x95,0x0e,0x00,0x00,0x00,
+ 0xbd,0x06,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x57,0x6f,0x6e,0x64,
+ 0x65,0x72,0x6d,0x65,0x64,0x69,0x61,0x00,
+ 0x00,0x00,0x00,0x00,0x43,0x57,0x35,0x30,
+ 0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x6d,0x4d,
+ 0x61,0x78,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x53,0x5a,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x02,0x01,0x00,0x01,0x01,0x00,0x00,
+ 0x70,0x00,0x64,0x00,0x00,0x00,0x00,0x14,
+ 0x00,0x00,0xa0,0x0f,0xac,0x0d,0x14,0x00,
+ 0x96,0x00,0x36,0x10,0x3c,0x00,0x06,0x00,
+ 0x00,0x00,0x00,0x00,0xe4,0x0c,0x48,0x0d,
+ 0x00,0x00,0x32,0x00,0x26,0x02,0xf4,0x01,
+ 0x5c,0xff,0x77,0x01,0x63,0x05,0x00,0x00,
+ 0x00,0x00,0x01,0x00,0xf0,0xdf,0xf4,0x67,
+ 0xe8,0xcf,0xee,0x1b,0xe8,0x03,0xe8,0x03,
+ 0x00,0x00,0xe8,0x03,0x00,0x00,0x10,0x27,
+ 0x1e,0x61,0xae,0x5c,0xce,0x57,0x94,0x52,
+ 0x14,0x4d,0x6a,0x47,0xac,0x41,0xff,0x3b,
+ 0x77,0x36,0x2b,0x31,0x2c,0x2c,0x85,0x27,
+ 0x3d,0x23,0x58,0x1f,0xd3,0x1b,0xab,0x18,
+ 0xdb,0x15,0x5c,0x13,0x27,0x11,0xe8,0x03,
+ 0xbc,0x02,0xc2,0x01,0xc8,0x00,0x00,0x00,
+ 0x1e,0x00,0xe8,0x03,0xff,0x77,0x00,0x00,
+ 0x00,0x00,0x01,0x00,0x00,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x58,
+ 0x10,0x37,0x10,0x19,0x10,0xfe,0x0f,0xe5,
+ 0x0f,0xcf,0x0f,0xbb,0x0f,0xa9,0x0f,0x98,
+ 0x0f,0x87,0x0f,0x75,0x0f,0x5c,0x0f,0x3e,
+ 0x0f,0x24,0x0f,0xf5,0x0e,0xac,0x0d,0x68,
+ 0x10,0x25,0x10,0xfb,0x0f,0xd3,0x0f,0xab,
+ 0x0f,0x85,0x0f,0x62,0x0f,0x41,0x0f,0x23,
+ 0x0f,0x0a,0x0f,0xf4,0x0e,0xe3,0x0e,0xd0,
+ 0x0e,0xbd,0x0e,0xaa,0x0e,0x96,0x0e,0x80,
+ 0x0e,0x67,0x0e,0x51,0x0e,0x2d,0x0e,0xac,
+ 0x0d,0x68,0x10,0x25,0x10,0xfb,0x0f,0xd3,
+ 0x0f,0xab,0x0f,0x85,0x0f,0x62,0x0f,0x41,
+ 0x0f,0x23,0x0f,0x0a,0x0f,0xf4,0x0e,0xe3,
+ 0x0e,0xd0,0x0e,0xbd,0x0e,0xaa,0x0e,0x96,
+ 0x0e,0x80,0x0e,0x67,0x0e,0x51,0x0e,0x2d,
+ 0x0e,0xac,0x0d,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x64,
+ 0x10,0x46,0x10,0x2b,0x10,0x11,0x10,0xfc,
+ 0x0f,0xe8,0x0f,0xd6,0x0f,0xc5,0x0f,0xb5,
+ 0x0f,0xa4,0x0f,0x8f,0x0f,0x73,0x0f,0x54,
+ 0x0f,0x2c,0x0f,0xac,0x0d,0x68,0x10,0x1b,
+ 0x10,0xf2,0x0f,0xcb,0x0f,0xa0,0x0f,0x7a,
+ 0x0f,0x58,0x0f,0x38,0x0f,0x1d,0x0f,0x04,
+ 0x0f,0xef,0x0e,0xde,0x0e,0xcc,0x0e,0xba,
+ 0x0e,0xa9,0x0e,0x98,0x0e,0x84,0x0e,0x6d,
+ 0x0e,0x57,0x0e,0x31,0x0e,0xac,0x0d,0x68,
+ 0x10,0x1b,0x10,0xf2,0x0f,0xcb,0x0f,0xa0,
+ 0x0f,0x7a,0x0f,0x58,0x0f,0x38,0x0f,0x1d,
+ 0x0f,0x04,0x0f,0xef,0x0e,0xde,0x0e,0xcc,
+ 0x0e,0xba,0x0e,0xa9,0x0e,0x98,0x0e,0x84,
+ 0x0e,0x6d,0x0e,0x57,0x0e,0x31,0x0e,0xac,
+ 0x0d,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x65,0x10,0x4e,0x10,0x39,
+ 0x10,0x26,0x10,0x14,0x10,0x03,0x10,0xf2,
+ 0x0f,0xdd,0x0f,0xc3,0x0f,0xa2,0x0f,0x69,
+ 0x0f,0xac,0x0d,0x68,0x10,0x00,0x10,0xdb,
+ 0x0f,0xbc,0x0f,0x92,0x0f,0x6e,0x0f,0x4e,
+ 0x0f,0x30,0x0f,0x16,0x0f,0xfe,0x0e,0xe8,
+ 0x0e,0xd8,0x0e,0xc5,0x0e,0xb4,0x0e,0xa3,
+ 0x0e,0x93,0x0e,0x7f,0x0e,0x69,0x0e,0x4e,
+ 0x0e,0x19,0x0e,0xac,0x0d,0x68,0x10,0x00,
+ 0x10,0xdb,0x0f,0xbc,0x0f,0x92,0x0f,0x6e,
+ 0x0f,0x4e,0x0f,0x30,0x0f,0x16,0x0f,0xfe,
+ 0x0e,0xe8,0x0e,0xd8,0x0e,0xc5,0x0e,0xb4,
+ 0x0e,0xa3,0x0e,0x93,0x0e,0x7f,0x0e,0x69,
+ 0x0e,0x4e,0x0e,0x19,0x0e,0xac,0x0d,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x49,0x10,0x06,0x10,0xac,
+ 0x0d,0x68,0x10,0x2a,0x10,0xfc,0x0f,0xa3,
+ 0x0f,0x59,0x0f,0x35,0x0f,0x1b,0x0f,0xff,
+ 0x0e,0xe6,0x0e,0xd2,0x0e,0xc1,0x0e,0xb5,
+ 0x0e,0xa9,0x0e,0x9e,0x0e,0x92,0x0e,0x81,
+ 0x0e,0x6f,0x0e,0x54,0x0e,0x2f,0x0e,0xf6,
+ 0x0d,0xac,0x0d,0x68,0x10,0x2a,0x10,0xfc,
+ 0x0f,0xa3,0x0f,0x59,0x0f,0x35,0x0f,0x1b,
+ 0x0f,0xff,0x0e,0xe6,0x0e,0xd2,0x0e,0xc1,
+ 0x0e,0xb5,0x0e,0xa9,0x0e,0x9e,0x0e,0x92,
+ 0x0e,0x81,0x0e,0x6f,0x0e,0x54,0x0e,0x2f,
+ 0x0e,0xf6,0x0d,0xac,0x0d,0x68,0x10,0xb9,
+ 0x0f,0x0a,0x0f,0x5b,0x0e,0xac,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xb9,0x0f,0x0a,0x0f,0x5b,0x0e,0xac,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xb9,0x0f,0x0a,0x0f,0x5b,
+ 0x0e,0xac,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xb9,0x0f,0x0a,
+ 0x0f,0x5b,0x0e,0xac,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x68,0x10,0xb9,
+ 0x0f,0x0a,0x0f,0x5b,0x0e,0xac,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xb9,0x0f,0x0a,0x0f,0x5b,0x0e,0xac,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xb9,0x0f,0x0a,0x0f,0x5b,
+ 0x0e,0xac,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xb9,0x0f,0x0a,
+ 0x0f,0x5b,0x0e,0xac,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x68,0x10,0xb9,
+ 0x0f,0x0a,0x0f,0x5b,0x0e,0xac,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xb9,0x0f,0x0a,0x0f,0x5b,0x0e,0xac,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xb9,0x0f,0x0a,0x0f,0x5b,
+ 0x0e,0xac,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xb9,0x0f,0x0a,
+ 0x0f,0x5b,0x0e,0xac,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x4a,0x09,0x00,
+ 0x00,0x54,0x01,0x12,0x03,0xe3,0x04,0x4b,
+ 0x04,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,
+ 0x0c,0xed,0x00,0x02,0x03,0xcd,0x04,0x32,
+ 0x04,0xb9,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x27,0x0d,0xba,0x01,0x46,0x03,0x92,
+ 0x05,0x94,0x02,0x8b,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x99,0x07,0x00,0x00,0x97,
+ 0x00,0xbd,0x02,0x44,0x04,0x9e,0x05,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x86,0x0c,0x7d,
+ 0x00,0xc2,0x02,0x8a,0x04,0xbb,0x04,0xe3,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xea,
+ 0x0c,0x62,0x01,0x1f,0x03,0xb8,0x05,0xaf,
+ 0x02,0x87,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xc1,0x04,0x00,0x00,0xed,0x00,0xe3,
+ 0x00,0xf0,0x02,0x1c,0x07,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xa5,0x0b,0x17,0x00,0x4a,
+ 0x02,0xf9,0x03,0x4a,0x05,0x7e,0x01,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x8e,0x0c,0xe2,
+ 0x00,0xf2,0x02,0x76,0x05,0x42,0x03,0xa0,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x08,
+ 0x02,0x00,0x00,0x0e,0x00,0x9e,0x00,0x5b,
+ 0x01,0x06,0x0b,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x6f,0x09,0x60,0x00,0x7a,0x01,0xec,
+ 0x01,0xa7,0x05,0xe0,0x03,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x8d,0x0b,0x1f,0x01,0x3c,
+ 0x01,0x73,0x04,0xbe,0x04,0x3f,0x01,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,
+};
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_mp718_20131004_070110.h b/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_mp718_20131004_070110.h new file mode 100755 index 00000000..1ea6271b --- /dev/null +++ b/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_mp718_20131004_070110.h @@ -0,0 +1,233 @@ +
+/* This file is auto-generated. Don't edit this file. */
+
+/// R-Sense = 20
+/// ILMD = 6800
+/// EDVF = 3400
+/// Taper Current = 100
+/// Taper Voltage = 4150
+/// Taper Time = 60
+
+char FactoryGGBXFile_mp718 [] = {
+ 0x5f,0x47,0x47,0x5f,0x65,0xcb,0x00,0x00,
+ 0xb6,0xd6,0x8d,0x95,0x0e,0x00,0x00,0x00,
+ 0xbd,0x06,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x57,0x6f,0x6e,0x64,
+ 0x65,0x72,0x6d,0x65,0x64,0x69,0x61,0x00,
+ 0x00,0x00,0x00,0x00,0x4d,0x37,0x31,0x38,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x57,0x6f,
+ 0x6e,0x64,0x65,0x72,0x6d,0x65,0x64,0x69,
+ 0x61,0x00,0x00,0x00,0x00,0x00,0x53,0x5a,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x01,0x02,0x00,0x01,0x01,0x00,0x00,
+ 0x70,0x00,0x64,0x00,0x00,0x00,0x00,0x14,
+ 0x00,0x00,0x90,0x1a,0x48,0x0d,0x14,0x00,
+ 0x64,0x00,0x36,0x10,0x3c,0x00,0x06,0x00,
+ 0x00,0x00,0x00,0x00,0xe4,0x0c,0x48,0x0d,
+ 0x00,0x00,0x32,0x00,0x26,0x02,0xf4,0x01,
+ 0x5c,0xff,0x77,0x01,0x63,0x05,0x00,0x00,
+ 0x00,0x00,0x01,0x00,0xf0,0xdf,0xf4,0x67,
+ 0xe8,0xcf,0xee,0x1b,0xe8,0x03,0xe8,0x03,
+ 0x00,0x00,0xe8,0x03,0x00,0x00,0x10,0x27,
+ 0x1e,0x61,0xae,0x5c,0xce,0x57,0x94,0x52,
+ 0x14,0x4d,0x6a,0x47,0xac,0x41,0xff,0x3b,
+ 0x77,0x36,0x2b,0x31,0x2c,0x2c,0x85,0x27,
+ 0x3d,0x23,0x58,0x1f,0xd3,0x1b,0xab,0x18,
+ 0xdb,0x15,0x5c,0x13,0x27,0x11,0xe8,0x03,
+ 0xbc,0x02,0xc2,0x01,0xc8,0x00,0x00,0x00,
+ 0x1e,0x00,0xe8,0x03,0xff,0x77,0x00,0x00,
+ 0x00,0x00,0x01,0x00,0x00,0x68,0x10,0x68,
+ 0x10,0x4a,0x10,0x1b,0x10,0xf2,0x0f,0xcc,
+ 0x0f,0xaa,0x0f,0x8c,0x0f,0x71,0x0f,0x58,
+ 0x0f,0x3e,0x0f,0x27,0x0f,0x17,0x0f,0x09,
+ 0x0f,0xfe,0x0e,0xf5,0x0e,0xe3,0x0e,0xc3,
+ 0x0e,0xa0,0x0e,0x87,0x0e,0x48,0x0d,0x68,
+ 0x10,0x2a,0x10,0xf6,0x0f,0xca,0x0f,0xa1,
+ 0x0f,0x7c,0x0f,0x5a,0x0f,0x3b,0x0f,0x1d,
+ 0x0f,0xff,0x0e,0xe5,0x0e,0xd2,0x0e,0xc3,
+ 0x0e,0xb7,0x0e,0xac,0x0e,0x9d,0x0e,0x88,
+ 0x0e,0x68,0x0e,0x49,0x0e,0x1b,0x0e,0x48,
+ 0x0d,0x68,0x10,0x2a,0x10,0xf6,0x0f,0xca,
+ 0x0f,0xa1,0x0f,0x7c,0x0f,0x5a,0x0f,0x3b,
+ 0x0f,0x1d,0x0f,0xff,0x0e,0xe5,0x0e,0xd2,
+ 0x0e,0xc3,0x0e,0xb7,0x0e,0xac,0x0e,0x9d,
+ 0x0e,0x88,0x0e,0x68,0x0e,0x49,0x0e,0x1b,
+ 0x0e,0x48,0x0d,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x35,0x10,0x09,0x10,0xe3,0x0f,0xc1,
+ 0x0f,0xa3,0x0f,0x89,0x0f,0x70,0x0f,0x59,
+ 0x0f,0x42,0x0f,0x31,0x0f,0x23,0x0f,0x19,
+ 0x0f,0x0f,0x0f,0x00,0x0f,0xe2,0x0e,0xbe,
+ 0x0e,0xa3,0x0e,0x48,0x0d,0x68,0x10,0x26,
+ 0x10,0xf2,0x0f,0xc4,0x0f,0x9c,0x0f,0x77,
+ 0x0f,0x55,0x0f,0x35,0x0f,0x17,0x0f,0xfc,
+ 0x0e,0xe5,0x0e,0xd3,0x0e,0xc5,0x0e,0xba,
+ 0x0e,0xb1,0x0e,0xa5,0x0e,0x91,0x0e,0x71,
+ 0x0e,0x50,0x0e,0x19,0x0e,0x48,0x0d,0x68,
+ 0x10,0x26,0x10,0xf2,0x0f,0xc4,0x0f,0x9c,
+ 0x0f,0x77,0x0f,0x55,0x0f,0x35,0x0f,0x17,
+ 0x0f,0xfc,0x0e,0xe5,0x0e,0xd3,0x0e,0xc5,
+ 0x0e,0xba,0x0e,0xb1,0x0e,0xa5,0x0e,0x91,
+ 0x0e,0x71,0x0e,0x50,0x0e,0x19,0x0e,0x48,
+ 0x0d,0x68,0x10,0x68,0x10,0x68,0x10,0x54,
+ 0x10,0x24,0x10,0xfb,0x0f,0xd8,0x0f,0xba,
+ 0x0f,0x9f,0x0f,0x87,0x0f,0x71,0x0f,0x5c,
+ 0x0f,0x4a,0x0f,0x3c,0x0f,0x31,0x0f,0x26,
+ 0x0f,0x19,0x0f,0xfe,0x0e,0xda,0x0e,0xbe,
+ 0x0e,0x48,0x0d,0x68,0x10,0x23,0x10,0xf1,
+ 0x0f,0xc1,0x0f,0x97,0x0f,0x71,0x0f,0x4e,
+ 0x0f,0x2e,0x0f,0x11,0x0f,0xf8,0x0e,0xe4,
+ 0x0e,0xd2,0x0e,0xc5,0x0e,0xba,0x0e,0xb1,
+ 0x0e,0xa5,0x0e,0x92,0x0e,0x71,0x0e,0x50,
+ 0x0e,0x0c,0x0e,0x48,0x0d,0x68,0x10,0x23,
+ 0x10,0xf1,0x0f,0xc1,0x0f,0x97,0x0f,0x71,
+ 0x0f,0x4e,0x0f,0x2e,0x0f,0x11,0x0f,0xf8,
+ 0x0e,0xe4,0x0e,0xd2,0x0e,0xc5,0x0e,0xba,
+ 0x0e,0xb1,0x0e,0xa5,0x0e,0x92,0x0e,0x71,
+ 0x0e,0x50,0x0e,0x0c,0x0e,0x48,0x0d,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x64,
+ 0x10,0x36,0x10,0x0d,0x10,0xeb,0x0f,0xcd,
+ 0x0f,0xb4,0x0f,0x9e,0x0f,0x8a,0x0f,0x79,
+ 0x0f,0x6a,0x0f,0x5e,0x0f,0x53,0x0f,0x46,
+ 0x0f,0x2f,0x0f,0x0b,0x0f,0xee,0x0e,0x48,
+ 0x0d,0x68,0x10,0x1e,0x10,0xee,0x0f,0xc0,
+ 0x0f,0x93,0x0f,0x6c,0x0f,0x47,0x0f,0x28,
+ 0x0f,0x0c,0x0f,0xf5,0x0e,0xe1,0x0e,0xd3,
+ 0x0e,0xc6,0x0e,0xbb,0x0e,0xb0,0x0e,0xa4,
+ 0x0e,0x91,0x0e,0x74,0x0e,0x4b,0x0e,0xf5,
+ 0x0d,0x48,0x0d,0x68,0x10,0x1e,0x10,0xee,
+ 0x0f,0xc0,0x0f,0x93,0x0f,0x6c,0x0f,0x47,
+ 0x0f,0x28,0x0f,0x0c,0x0f,0xf5,0x0e,0xe1,
+ 0x0e,0xd3,0x0e,0xc6,0x0e,0xbb,0x0e,0xb0,
+ 0x0e,0xa4,0x0e,0x91,0x0e,0x74,0x0e,0x4b,
+ 0x0e,0xf5,0x0d,0x48,0x0d,0x68,0x10,0xa0,
+ 0x0f,0xd8,0x0e,0x10,0x0e,0x48,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xa0,0x0f,0xd8,0x0e,0x10,0x0e,0x48,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xa0,0x0f,0xd8,0x0e,0x10,
+ 0x0e,0x48,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xa0,0x0f,0xd8,
+ 0x0e,0x10,0x0e,0x48,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x68,0x10,0xa0,
+ 0x0f,0xd8,0x0e,0x10,0x0e,0x48,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xa0,0x0f,0xd8,0x0e,0x10,0x0e,0x48,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xa0,0x0f,0xd8,0x0e,0x10,
+ 0x0e,0x48,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xa0,0x0f,0xd8,
+ 0x0e,0x10,0x0e,0x48,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x68,0x10,0xa0,
+ 0x0f,0xd8,0x0e,0x10,0x0e,0x48,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xa0,0x0f,0xd8,0x0e,0x10,0x0e,0x48,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xa0,0x0f,0xd8,0x0e,0x10,
+ 0x0e,0x48,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xa0,0x0f,0xd8,
+ 0x0e,0x10,0x0e,0x48,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x42,0x1a,0x70,
+ 0x01,0x50,0x06,0x08,0x0d,0x78,0x05,0x20,
+ 0x01,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xf7,
+ 0x1a,0x89,0x03,0xd3,0x07,0x7a,0x0d,0x20,
+ 0x02,0xb5,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x2d,0x1b,0x75,0x04,0x57,0x08,0x39,
+ 0x0d,0x27,0x01,0x9a,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xbf,0x18,0xa1,0x00,0x67,
+ 0x05,0xbb,0x0a,0xfa,0x07,0xee,0x01,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x4b,0x1a,0xc2,
+ 0x02,0x4f,0x07,0xab,0x0d,0x8e,0x02,0xe2,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xcb,
+ 0x1a,0xee,0x03,0x17,0x08,0x63,0x0d,0x60,
+ 0x01,0xa6,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x84,0x16,0x3a,0x00,0x03,0x04,0x71,
+ 0x08,0xd5,0x09,0xda,0x02,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x79,0x19,0x22,0x02,0xbc,
+ 0x06,0xa3,0x0d,0xf7,0x02,0x35,0x01,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x5b,0x1a,0x69,
+ 0x03,0xc1,0x07,0x84,0x0d,0xad,0x01,0xc3,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x22,
+ 0x12,0x0c,0x00,0x1c,0x02,0xf3,0x05,0x05,
+ 0x0a,0x69,0x06,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x09,0x18,0x57,0x01,0xe5,0x05,0xc1,
+ 0x0c,0x0a,0x04,0x05,0x02,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x5d,0x19,0x9a,0x02,0x1e,
+ 0x07,0x67,0x0d,0x3c,0x02,0x35,0x01,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,
+};
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_t73v_20131120_001204.h b/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_t73v_20131120_001204.h new file mode 100755 index 00000000..12e6c67b --- /dev/null +++ b/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_t73v_20131120_001204.h @@ -0,0 +1,233 @@ +
+/* This file is auto-generated. Don't edit this file. */
+
+/// R-Sense = 20
+/// ILMD = 3000
+/// EDVF = 3400
+/// Taper Current = 150
+/// Taper Voltage = 4150
+/// Taper Time = 60
+
+char FactoryGGBXFile_t73v [] = {
+ 0x5f,0x47,0x47,0x5f,0x00,0xc6,0x00,0x00,
+ 0xd4,0x1b,0xca,0x95,0x0e,0x00,0x00,0x00,
+ 0xbd,0x06,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x47,0x6f,0x6f,0x64,
+ 0x74,0x69,0x6d,0x65,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x54,0x37,0x33,0x56,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x4c,0x55,
+ 0x53,0x54,0x59,0x2d,0x33,0x37,0x37,0x30,
+ 0x31,0x30,0x30,0x50,0x4c,0x00,0x31,0x33,
+ 0x31,0x30,0x31,0x36,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x02,0x01,0x00,0x01,0x01,0x00,0x00,
+ 0x70,0x00,0x64,0x00,0x00,0x00,0x00,0x14,
+ 0x00,0x00,0xb8,0x0b,0x48,0x0d,0x14,0x00,
+ 0x96,0x00,0x36,0x10,0x3c,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x74,0x0e,0xd8,0x0e,
+ 0x00,0x00,0x32,0x00,0x26,0x02,0xf4,0x01,
+ 0x5c,0xff,0x77,0x01,0x63,0x05,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0xf0,0xdf,0xf4,0x67,
+ 0xe8,0xcf,0xee,0x1b,0xe8,0x03,0xe8,0x03,
+ 0x00,0x00,0xe8,0x03,0x00,0x00,0x10,0x27,
+ 0x05,0x62,0x8a,0x5d,0x9c,0x58,0x4c,0x53,
+ 0xb3,0x4d,0xeb,0x47,0x15,0x42,0x4b,0x3c,
+ 0xc7,0x36,0x80,0x31,0x86,0x2c,0xe5,0x27,
+ 0xa2,0x23,0xc0,0x1f,0x3d,0x1c,0x17,0x19,
+ 0x47,0x16,0xc6,0x13,0x8e,0x11,0xe8,0x03,
+ 0x84,0x03,0xee,0x02,0x58,0x02,0xc2,0x01,
+ 0x1e,0x00,0xe8,0x03,0xff,0x77,0x00,0x00,
+ 0x00,0x00,0x21,0x38,0x00,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x49,0x10,0x23,
+ 0x10,0x01,0x10,0xe2,0x0f,0xc8,0x0f,0xb0,
+ 0x0f,0x9b,0x0f,0x8a,0x0f,0x7b,0x0f,0x6e,
+ 0x0f,0x63,0x0f,0x58,0x0f,0x44,0x0f,0x27,
+ 0x0f,0x09,0x0f,0xd8,0x0e,0x48,0x0d,0x68,
+ 0x10,0x25,0x10,0xf4,0x0f,0xc7,0x0f,0x9c,
+ 0x0f,0x76,0x0f,0x53,0x0f,0x2f,0x0f,0x0e,
+ 0x0f,0xf3,0x0e,0xdf,0x0e,0xce,0x0e,0xbf,
+ 0x0e,0xb3,0x0e,0xa7,0x0e,0x99,0x0e,0x88,
+ 0x0e,0x6f,0x0e,0x53,0x0e,0x24,0x0e,0x48,
+ 0x0d,0x68,0x10,0x25,0x10,0xf4,0x0f,0xc7,
+ 0x0f,0x9c,0x0f,0x76,0x0f,0x53,0x0f,0x2f,
+ 0x0f,0x0e,0x0f,0xf3,0x0e,0xdf,0x0e,0xce,
+ 0x0e,0xbf,0x0e,0xb3,0x0e,0xa7,0x0e,0x99,
+ 0x0e,0x88,0x0e,0x6f,0x0e,0x53,0x0e,0x24,
+ 0x0e,0x48,0x0d,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x49,0x10,0x23,0x10,0x01,
+ 0x10,0xe2,0x0f,0xc8,0x0f,0xb0,0x0f,0x9b,
+ 0x0f,0x8a,0x0f,0x7b,0x0f,0x6e,0x0f,0x63,
+ 0x0f,0x58,0x0f,0x44,0x0f,0x27,0x0f,0x09,
+ 0x0f,0xd8,0x0e,0x48,0x0d,0x68,0x10,0x25,
+ 0x10,0xf4,0x0f,0xc7,0x0f,0x9c,0x0f,0x76,
+ 0x0f,0x53,0x0f,0x2f,0x0f,0x0e,0x0f,0xf3,
+ 0x0e,0xdf,0x0e,0xce,0x0e,0xbf,0x0e,0xb3,
+ 0x0e,0xa7,0x0e,0x99,0x0e,0x88,0x0e,0x6f,
+ 0x0e,0x53,0x0e,0x24,0x0e,0x48,0x0d,0x68,
+ 0x10,0x25,0x10,0xf4,0x0f,0xc7,0x0f,0x9c,
+ 0x0f,0x76,0x0f,0x53,0x0f,0x2f,0x0f,0x0e,
+ 0x0f,0xf3,0x0e,0xdf,0x0e,0xce,0x0e,0xbf,
+ 0x0e,0xb3,0x0e,0xa7,0x0e,0x99,0x0e,0x88,
+ 0x0e,0x6f,0x0e,0x53,0x0e,0x24,0x0e,0x48,
+ 0x0d,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x49,0x10,0x23,0x10,0x01,0x10,0xe2,
+ 0x0f,0xc8,0x0f,0xb0,0x0f,0x9b,0x0f,0x8a,
+ 0x0f,0x7b,0x0f,0x6e,0x0f,0x63,0x0f,0x58,
+ 0x0f,0x44,0x0f,0x27,0x0f,0x09,0x0f,0xd8,
+ 0x0e,0x48,0x0d,0x68,0x10,0x25,0x10,0xf4,
+ 0x0f,0xc7,0x0f,0x9c,0x0f,0x76,0x0f,0x53,
+ 0x0f,0x2f,0x0f,0x0e,0x0f,0xf3,0x0e,0xdf,
+ 0x0e,0xce,0x0e,0xbf,0x0e,0xb3,0x0e,0xa7,
+ 0x0e,0x99,0x0e,0x88,0x0e,0x6f,0x0e,0x53,
+ 0x0e,0x24,0x0e,0x48,0x0d,0x68,0x10,0x25,
+ 0x10,0xf4,0x0f,0xc7,0x0f,0x9c,0x0f,0x76,
+ 0x0f,0x53,0x0f,0x2f,0x0f,0x0e,0x0f,0xf3,
+ 0x0e,0xdf,0x0e,0xce,0x0e,0xbf,0x0e,0xb3,
+ 0x0e,0xa7,0x0e,0x99,0x0e,0x88,0x0e,0x6f,
+ 0x0e,0x53,0x0e,0x24,0x0e,0x48,0x0d,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x49,
+ 0x10,0x23,0x10,0x01,0x10,0xe2,0x0f,0xc8,
+ 0x0f,0xb0,0x0f,0x9b,0x0f,0x8a,0x0f,0x7b,
+ 0x0f,0x6e,0x0f,0x63,0x0f,0x58,0x0f,0x44,
+ 0x0f,0x27,0x0f,0x09,0x0f,0xd8,0x0e,0x48,
+ 0x0d,0x68,0x10,0x25,0x10,0xf4,0x0f,0xc7,
+ 0x0f,0x9c,0x0f,0x76,0x0f,0x53,0x0f,0x2f,
+ 0x0f,0x0e,0x0f,0xf3,0x0e,0xdf,0x0e,0xce,
+ 0x0e,0xbf,0x0e,0xb3,0x0e,0xa7,0x0e,0x99,
+ 0x0e,0x88,0x0e,0x6f,0x0e,0x53,0x0e,0x24,
+ 0x0e,0x48,0x0d,0x68,0x10,0x25,0x10,0xf4,
+ 0x0f,0xc7,0x0f,0x9c,0x0f,0x76,0x0f,0x53,
+ 0x0f,0x2f,0x0f,0x0e,0x0f,0xf3,0x0e,0xdf,
+ 0x0e,0xce,0x0e,0xbf,0x0e,0xb3,0x0e,0xa7,
+ 0x0e,0x99,0x0e,0x88,0x0e,0x6f,0x0e,0x53,
+ 0x0e,0x24,0x0e,0x48,0x0d,0x68,0x10,0xa0,
+ 0x0f,0xd8,0x0e,0x10,0x0e,0x48,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xa0,0x0f,0xd8,0x0e,0x10,0x0e,0x48,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xa0,0x0f,0xd8,0x0e,0x10,
+ 0x0e,0x48,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xa0,0x0f,0xd8,
+ 0x0e,0x10,0x0e,0x48,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x68,0x10,0xa0,
+ 0x0f,0xd8,0x0e,0x10,0x0e,0x48,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xa0,0x0f,0xd8,0x0e,0x10,0x0e,0x48,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xa0,0x0f,0xd8,0x0e,0x10,
+ 0x0e,0x48,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xa0,0x0f,0xd8,
+ 0x0e,0x10,0x0e,0x48,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x68,0x10,0xa0,
+ 0x0f,0xd8,0x0e,0x10,0x0e,0x48,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xa0,0x0f,0xd8,0x0e,0x10,0x0e,0x48,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xa0,0x0f,0xd8,0x0e,0x10,
+ 0x0e,0x48,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xa0,0x0f,0xd8,
+ 0x0e,0x10,0x0e,0x48,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x3b,0x0b,0x66,
+ 0x00,0x7d,0x02,0xe4,0x04,0x72,0x03,0x82,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x82,
+ 0x0b,0x79,0x01,0x1b,0x03,0x3b,0x06,0xb1,
+ 0x00,0x4c,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x96,0x0b,0xe7,0x01,0x69,0x03,0xd9,
+ 0x05,0x6c,0x00,0x41,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x3b,0x0b,0x66,0x00,0x7d,
+ 0x02,0xe4,0x04,0x72,0x03,0x82,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x82,0x0b,0x79,
+ 0x01,0x1b,0x03,0x3b,0x06,0xb1,0x00,0x4c,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x96,
+ 0x0b,0xe7,0x01,0x69,0x03,0xd9,0x05,0x6c,
+ 0x00,0x41,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x3b,0x0b,0x66,0x00,0x7d,0x02,0xe4,
+ 0x04,0x72,0x03,0x82,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x82,0x0b,0x79,0x01,0x1b,
+ 0x03,0x3b,0x06,0xb1,0x00,0x4c,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x96,0x0b,0xe7,
+ 0x01,0x69,0x03,0xd9,0x05,0x6c,0x00,0x41,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3b,
+ 0x0b,0x66,0x00,0x7d,0x02,0xe4,0x04,0x72,
+ 0x03,0x82,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x82,0x0b,0x79,0x01,0x1b,0x03,0x3b,
+ 0x06,0xb1,0x00,0x4c,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x96,0x0b,0xe7,0x01,0x69,
+ 0x03,0xd9,0x05,0x6c,0x00,0x41,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,
+};
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_wms7320_20130718_200031.h b/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_wms7320_20130718_200031.h new file mode 100755 index 00000000..9520f0f7 --- /dev/null +++ b/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_wms7320_20130718_200031.h @@ -0,0 +1,233 @@ +
+/* This file is auto-generated. Don't edit this file. */
+
+/// R-Sense = 20
+/// ILMD = 4000
+/// EDVF = 3400
+/// Taper Current = 100
+/// Taper Voltage = 4150
+/// Taper Time = 60
+
+char FactoryGGBXFile_wms7320 [] = {
+ 0x5f,0x47,0x47,0x5f,0x6d,0xc8,0x00,0x00,
+ 0x5f,0x5b,0x2a,0x95,0x0e,0x00,0x00,0x00,
+ 0xbd,0x06,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x57,0x6f,0x6e,0x64,
+ 0x65,0x72,0x6d,0x65,0x64,0x69,0x61,0x00,
+ 0x00,0x00,0x00,0x00,0x57,0x4d,0x53,0x37,
+ 0x33,0x32,0x30,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x57,0x6f,
+ 0x6e,0x64,0x65,0x72,0x6d,0x65,0x64,0x69,
+ 0x61,0x00,0x00,0x00,0x00,0x00,0x53,0x5a,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x01,0x02,0x00,0x01,0x01,0x00,0x00,
+ 0x70,0x00,0x64,0x00,0x00,0x00,0x00,0x14,
+ 0x00,0x00,0xa0,0x0f,0x48,0x0d,0x14,0x00,
+ 0x64,0x00,0x36,0x10,0x3c,0x00,0x06,0x00,
+ 0x00,0x00,0x00,0x00,0xe4,0x0c,0x48,0x0d,
+ 0x00,0x00,0x32,0x00,0x26,0x02,0xf4,0x01,
+ 0x5c,0xff,0x77,0x01,0x63,0x05,0x00,0x00,
+ 0x00,0x00,0x01,0x00,0xf0,0xdf,0xf4,0x67,
+ 0xe8,0xcf,0xee,0x1b,0xe8,0x03,0xe8,0x03,
+ 0x00,0x00,0xe8,0x03,0x00,0x00,0x10,0x27,
+ 0x1e,0x61,0xae,0x5c,0xce,0x57,0x94,0x52,
+ 0x14,0x4d,0x6a,0x47,0xac,0x41,0xff,0x3b,
+ 0x77,0x36,0x2b,0x31,0x2c,0x2c,0x85,0x27,
+ 0x3d,0x23,0x58,0x1f,0xd3,0x1b,0xab,0x18,
+ 0xdb,0x15,0x5c,0x13,0x27,0x11,0xe8,0x03,
+ 0xbc,0x02,0xc2,0x01,0xc8,0x00,0x00,0x00,
+ 0x1e,0x00,0xe8,0x03,0xff,0x77,0x00,0x00,
+ 0x00,0x00,0x01,0x00,0x00,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x5b,
+ 0x10,0x4d,0x10,0x2d,0x10,0x20,0x10,0x0f,
+ 0x10,0xff,0x0f,0xe1,0x0f,0xcc,0x0f,0xb2,
+ 0x0f,0x99,0x0f,0x4f,0x0f,0x48,0x0d,0x68,
+ 0x10,0xdd,0x0f,0xb0,0x0f,0x85,0x0f,0x5f,
+ 0x0f,0x3d,0x0f,0x1c,0x0f,0xf9,0x0e,0xcf,
+ 0x0e,0xb9,0x0e,0xab,0x0e,0xa4,0x0e,0x9f,
+ 0x0e,0x98,0x0e,0x8b,0x0e,0x72,0x0e,0x45,
+ 0x0e,0x05,0x0e,0xc9,0x0d,0xa2,0x0d,0x48,
+ 0x0d,0x68,0x10,0xdd,0x0f,0xb0,0x0f,0x85,
+ 0x0f,0x5f,0x0f,0x3d,0x0f,0x1c,0x0f,0xf9,
+ 0x0e,0xcf,0x0e,0xb9,0x0e,0xab,0x0e,0xa4,
+ 0x0e,0x9f,0x0e,0x98,0x0e,0x8b,0x0e,0x72,
+ 0x0e,0x45,0x0e,0x05,0x0e,0xc9,0x0d,0xa2,
+ 0x0d,0x48,0x0d,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x66,
+ 0x10,0x14,0x10,0x48,0x0d,0x68,0x10,0xdd,
+ 0x0f,0xb0,0x0f,0x85,0x0f,0x5f,0x0f,0x3d,
+ 0x0f,0x1c,0x0f,0xf9,0x0e,0xcf,0x0e,0xb9,
+ 0x0e,0xab,0x0e,0xa4,0x0e,0x9f,0x0e,0x98,
+ 0x0e,0x8b,0x0e,0x72,0x0e,0x45,0x0e,0x05,
+ 0x0e,0xc9,0x0d,0xa2,0x0d,0x48,0x0d,0x68,
+ 0x10,0xdd,0x0f,0xb0,0x0f,0x85,0x0f,0x5f,
+ 0x0f,0x3d,0x0f,0x1c,0x0f,0xf9,0x0e,0xcf,
+ 0x0e,0xb9,0x0e,0xab,0x0e,0xa4,0x0e,0x9f,
+ 0x0e,0x98,0x0e,0x8b,0x0e,0x72,0x0e,0x45,
+ 0x0e,0x05,0x0e,0xc9,0x0d,0xa2,0x0d,0x48,
+ 0x0d,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x3f,
+ 0x10,0x48,0x0d,0x68,0x10,0xdd,0x0f,0xb0,
+ 0x0f,0x85,0x0f,0x5f,0x0f,0x3d,0x0f,0x1c,
+ 0x0f,0xf9,0x0e,0xcf,0x0e,0xb9,0x0e,0xab,
+ 0x0e,0xa4,0x0e,0x9f,0x0e,0x98,0x0e,0x8b,
+ 0x0e,0x72,0x0e,0x45,0x0e,0x05,0x0e,0xc9,
+ 0x0d,0xa2,0x0d,0x48,0x0d,0x68,0x10,0xdd,
+ 0x0f,0xb0,0x0f,0x85,0x0f,0x5f,0x0f,0x3d,
+ 0x0f,0x1c,0x0f,0xf9,0x0e,0xcf,0x0e,0xb9,
+ 0x0e,0xab,0x0e,0xa4,0x0e,0x9f,0x0e,0x98,
+ 0x0e,0x8b,0x0e,0x72,0x0e,0x45,0x0e,0x05,
+ 0x0e,0xc9,0x0d,0xa2,0x0d,0x48,0x0d,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x48,
+ 0x0d,0x68,0x10,0xdd,0x0f,0xb0,0x0f,0x85,
+ 0x0f,0x5f,0x0f,0x3d,0x0f,0x1c,0x0f,0xf9,
+ 0x0e,0xcf,0x0e,0xb9,0x0e,0xab,0x0e,0xa4,
+ 0x0e,0x9f,0x0e,0x98,0x0e,0x8b,0x0e,0x72,
+ 0x0e,0x45,0x0e,0x05,0x0e,0xc9,0x0d,0xa2,
+ 0x0d,0x48,0x0d,0x68,0x10,0xdd,0x0f,0xb0,
+ 0x0f,0x85,0x0f,0x5f,0x0f,0x3d,0x0f,0x1c,
+ 0x0f,0xf9,0x0e,0xcf,0x0e,0xb9,0x0e,0xab,
+ 0x0e,0xa4,0x0e,0x9f,0x0e,0x98,0x0e,0x8b,
+ 0x0e,0x72,0x0e,0x45,0x0e,0x05,0x0e,0xc9,
+ 0x0d,0xa2,0x0d,0x48,0x0d,0x68,0x10,0xa0,
+ 0x0f,0xd8,0x0e,0x10,0x0e,0x48,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xa0,0x0f,0xd8,0x0e,0x10,0x0e,0x48,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xa0,0x0f,0xd8,0x0e,0x10,
+ 0x0e,0x48,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xa0,0x0f,0xd8,
+ 0x0e,0x10,0x0e,0x48,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x68,0x10,0xa0,
+ 0x0f,0xd8,0x0e,0x10,0x0e,0x48,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xa0,0x0f,0xd8,0x0e,0x10,0x0e,0x48,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xa0,0x0f,0xd8,0x0e,0x10,
+ 0x0e,0x48,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xa0,0x0f,0xd8,
+ 0x0e,0x10,0x0e,0x48,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x68,0x10,0xa0,
+ 0x0f,0xd8,0x0e,0x10,0x0e,0x48,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xa0,0x0f,0xd8,0x0e,0x10,0x0e,0x48,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xa0,0x0f,0xd8,0x0e,0x10,
+ 0x0e,0x48,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xa0,0x0f,0xd8,
+ 0x0e,0x10,0x0e,0x48,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xb9,0x07,0x00,
+ 0x00,0x7c,0x00,0xaa,0x02,0x91,0x04,0xa2,
+ 0x04,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xf1,
+ 0x0b,0xb2,0x00,0xde,0x02,0xcf,0x04,0x91,
+ 0x03,0x9c,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xec,0x0b,0x3b,0x01,0xf1,0x02,0x7f,
+ 0x05,0x3f,0x02,0x81,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xe0,0x02,0x00,0x00,0x00,
+ 0x00,0x1b,0x00,0xc5,0x02,0xcf,0x0a,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xe8,0x0b,0x00,
+ 0x00,0x83,0x01,0xdc,0x03,0x88,0x06,0x4f,
+ 0x03,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xbe,
+ 0x0d,0x43,0x00,0x72,0x03,0x24,0x07,0xe3,
+ 0x02,0x85,0x01,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xe2,0x03,0x00,0x00,0x83,0x00,0xe2,
+ 0x02,0x7c,0x00,0x82,0x06,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xa2,0x09,0x49,0x00,0xcc,
+ 0x00,0x57,0x03,0x34,0x05,0xb6,0x01,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x8a,0x0a,0x61,
+ 0x00,0xd6,0x01,0x83,0x05,0xcf,0x02,0xa8,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xb3,
+ 0x00,0x00,0x00,0x00,0x00,0x29,0x00,0x89,
+ 0x00,0x17,0x04,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xf4,0x04,0x00,0x00,0x58,0x01,0x7c,
+ 0x00,0x20,0x03,0x1c,0x07,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xa5,0x09,0xab,0x00,0x38,
+ 0x03,0x87,0x00,0x39,0x05,0x6c,0x02,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,
+};
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_wms8309_c7_20130725_164935.h b/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_wms8309_c7_20130725_164935.h new file mode 100755 index 00000000..7cf4a54a --- /dev/null +++ b/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_wms8309_c7_20130725_164935.h @@ -0,0 +1,233 @@ +
+/* This file is auto-generated. Don't edit this file. */
+
+/// R-Sense = 20
+/// ILMD = 3900
+/// EDVF = 3500
+/// Taper Current = 150
+/// Taper Voltage = 4150
+/// Taper Time = 60
+
+char FactoryGGBXFile_wms8309_c7_3900mAh [] = {
+ 0x5f,0x47,0x47,0x5f,0xbb,0xfa,0x00,0x00,
+ 0x1f,0x69,0x33,0x95,0x0e,0x00,0x00,0x00,
+ 0xbd,0x06,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x57,0x6f,0x6e,0x64,
+ 0x65,0x72,0x6d,0x65,0x64,0x69,0x61,0x00,
+ 0x00,0x00,0x00,0x00,0x57,0x4d,0x53,0x38,
+ 0x33,0x30,0x39,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x6d,0x4d,
+ 0x61,0x78,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x53,0x5a,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x01,0x02,0x00,0x01,0x01,0x00,0x00,
+ 0x70,0x00,0x64,0x00,0x00,0x00,0x00,0x14,
+ 0x00,0x00,0x3c,0x0f,0xac,0x0d,0x14,0x00,
+ 0x96,0x00,0x36,0x10,0x3c,0x00,0x06,0x00,
+ 0x00,0x00,0x00,0x00,0xe4,0x0c,0x48,0x0d,
+ 0x00,0x00,0x32,0x00,0x26,0x02,0xf4,0x01,
+ 0x5c,0xff,0x77,0x01,0x63,0x05,0x00,0x00,
+ 0x00,0x00,0x01,0x00,0xf0,0xdf,0xf4,0x67,
+ 0xe8,0xcf,0xee,0x1b,0xe8,0x03,0xe8,0x03,
+ 0x00,0x00,0xe8,0x03,0x00,0x00,0x10,0x27,
+ 0x1e,0x61,0xae,0x5c,0xce,0x57,0x94,0x52,
+ 0x14,0x4d,0x6a,0x47,0xac,0x41,0xff,0x3b,
+ 0x77,0x36,0x2b,0x31,0x2c,0x2c,0x85,0x27,
+ 0x3d,0x23,0x58,0x1f,0xd3,0x1b,0xab,0x18,
+ 0xdb,0x15,0x5c,0x13,0x27,0x11,0xe8,0x03,
+ 0xbc,0x02,0xc2,0x01,0xc8,0x00,0x00,0x00,
+ 0x1e,0x00,0xe8,0x03,0xff,0x77,0x00,0x00,
+ 0x00,0x00,0x01,0x00,0x00,0x68,0x10,0x68,
+ 0x10,0x5b,0x10,0x2f,0x10,0x03,0x10,0xde,
+ 0x0f,0xbb,0x0f,0x9c,0x0f,0x7e,0x0f,0x62,
+ 0x0f,0x4a,0x0f,0x36,0x0f,0x28,0x0f,0x1e,
+ 0x0f,0x15,0x0f,0x0a,0x0f,0xf7,0x0e,0xdb,
+ 0x0e,0xbe,0x0e,0xa4,0x0e,0xac,0x0d,0x68,
+ 0x10,0x38,0x10,0x11,0x10,0xed,0x0f,0xca,
+ 0x0f,0xab,0x0f,0x8a,0x0f,0x6e,0x0f,0x52,
+ 0x0f,0x39,0x0f,0x22,0x0f,0x65,0x0f,0x57,
+ 0x0f,0x4c,0x0f,0x40,0x0f,0x31,0x0f,0x1b,
+ 0x0f,0x02,0x0f,0xea,0x0e,0xa0,0x0e,0xac,
+ 0x0d,0x68,0x10,0x38,0x10,0x11,0x10,0xed,
+ 0x0f,0xca,0x0f,0xab,0x0f,0x8a,0x0f,0x6e,
+ 0x0f,0x52,0x0f,0x39,0x0f,0x22,0x0f,0x65,
+ 0x0f,0x57,0x0f,0x4c,0x0f,0x40,0x0f,0x31,
+ 0x0f,0x1b,0x0f,0x02,0x0f,0xea,0x0e,0xa0,
+ 0x0e,0xac,0x0d,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x4a,0x10,0x10,0x10,0xed,0x0f,0xcd,
+ 0x0f,0xaf,0x0f,0x93,0x0f,0x7b,0x0f,0x65,
+ 0x0f,0x52,0x0f,0x45,0x0f,0x3a,0x0f,0x31,
+ 0x0f,0x27,0x0f,0x14,0x0f,0xf9,0x0e,0xdb,
+ 0x0e,0xbd,0x0e,0xac,0x0d,0x68,0x10,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xac,0x0d,0x68,
+ 0x10,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xac,
+ 0x0d,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x34,0x10,0x0f,0x10,0xee,0x0f,0xcf,
+ 0x0f,0xb4,0x0f,0x9d,0x0f,0x8a,0x0f,0x7a,
+ 0x0f,0x6c,0x0f,0x5f,0x0f,0x53,0x0f,0x47,
+ 0x0f,0x37,0x0f,0x1e,0x0f,0xfe,0x0e,0xd9,
+ 0x0e,0xac,0x0d,0x68,0x10,0x1f,0x10,0xf3,
+ 0x0f,0xc1,0x0f,0x87,0x0f,0x68,0x0f,0x46,
+ 0x0f,0x25,0x0f,0x09,0x0f,0xf2,0x0e,0xde,
+ 0x0e,0xd1,0x0e,0xc2,0x0e,0xb7,0x0e,0xae,
+ 0x0e,0xa3,0x0e,0x92,0x0e,0x7b,0x0e,0x63,
+ 0x0e,0x31,0x0e,0xac,0x0d,0x68,0x10,0x1f,
+ 0x10,0xf3,0x0f,0xc1,0x0f,0x87,0x0f,0x68,
+ 0x0f,0x46,0x0f,0x25,0x0f,0x09,0x0f,0xf2,
+ 0x0e,0xde,0x0e,0xd1,0x0e,0xc2,0x0e,0xb7,
+ 0x0e,0xae,0x0e,0xa3,0x0e,0x92,0x0e,0x7b,
+ 0x0e,0x63,0x0e,0x31,0x0e,0xac,0x0d,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x51,0x10,0x37,0x10,0x1d,0x10,0x05,
+ 0x10,0xee,0x0f,0xdb,0x0f,0xc8,0x0f,0xb8,
+ 0x0f,0xa8,0x0f,0x99,0x0f,0x8a,0x0f,0x78,
+ 0x0f,0x61,0x0f,0x43,0x0f,0x12,0x0f,0xac,
+ 0x0d,0x68,0x10,0x05,0x10,0xc9,0x0f,0x8a,
+ 0x0f,0x67,0x0f,0x4c,0x0f,0x2d,0x0f,0x17,
+ 0x0f,0xfe,0x0e,0xe8,0x0e,0xd5,0x0e,0xc5,
+ 0x0e,0xb9,0x0e,0xaf,0x0e,0xa7,0x0e,0x9b,
+ 0x0e,0x8b,0x0e,0x75,0x0e,0x5c,0x0e,0x1c,
+ 0x0e,0xac,0x0d,0x68,0x10,0x05,0x10,0xc9,
+ 0x0f,0x8a,0x0f,0x67,0x0f,0x4c,0x0f,0x2d,
+ 0x0f,0x17,0x0f,0xfe,0x0e,0xe8,0x0e,0xd5,
+ 0x0e,0xc5,0x0e,0xb9,0x0e,0xaf,0x0e,0xa7,
+ 0x0e,0x9b,0x0e,0x8b,0x0e,0x75,0x0e,0x5c,
+ 0x0e,0x1c,0x0e,0xac,0x0d,0x68,0x10,0xb9,
+ 0x0f,0x0a,0x0f,0x5b,0x0e,0xac,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xb9,0x0f,0x0a,0x0f,0x5b,0x0e,0xac,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xb9,0x0f,0x0a,0x0f,0x5b,
+ 0x0e,0xac,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xb9,0x0f,0x0a,
+ 0x0f,0x5b,0x0e,0xac,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x68,0x10,0xb9,
+ 0x0f,0x0a,0x0f,0x5b,0x0e,0xac,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xb9,0x0f,0x0a,0x0f,0x5b,0x0e,0xac,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xb9,0x0f,0x0a,0x0f,0x5b,
+ 0x0e,0xac,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xb9,0x0f,0x0a,
+ 0x0f,0x5b,0x0e,0xac,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x68,0x10,0xb9,
+ 0x0f,0x0a,0x0f,0x5b,0x0e,0xac,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xb9,0x0f,0x0a,0x0f,0x5b,0x0e,0xac,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xb9,0x0f,0x0a,0x0f,0x5b,
+ 0x0e,0xac,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xb9,0x0f,0x0a,
+ 0x0f,0x5b,0x0e,0xac,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x1c,0x0e,0x02,
+ 0x01,0xef,0x02,0x18,0x05,0x12,0x05,0xd8,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x62,
+ 0x0e,0xf2,0x01,0x50,0x03,0xcb,0x06,0x53,
+ 0x02,0x9e,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x74,0x0e,0x4c,0x02,0x6c,0x03,0xf3,
+ 0x06,0xc8,0x01,0x95,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x83,0x0d,0x85,0x00,0x83,
+ 0x02,0x8e,0x04,0xec,0x05,0x24,0x01,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x20,0x0e,0xa4,
+ 0x01,0x17,0x03,0x03,0x07,0x60,0x02,0xa1,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x48,
+ 0x0e,0x24,0x02,0x3d,0x03,0x45,0x07,0xa0,
+ 0x01,0x8f,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x76,0x0c,0x2b,0x00,0x0b,0x02,0xd2,
+ 0x03,0x6c,0x06,0xd6,0x01,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xb6,0x0d,0x2c,0x01,0xed,
+ 0x02,0xa8,0x06,0xf3,0x02,0xc5,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xf5,0x0d,0xd1,
+ 0x01,0x12,0x03,0x4b,0x07,0xc5,0x01,0x97,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7d,
+ 0x09,0x0d,0x00,0xf4,0x00,0x9d,0x02,0xde,
+ 0x05,0xfe,0x03,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x95,0x0c,0x7e,0x00,0x1b,0x02,0x32,
+ 0x05,0xc8,0x04,0x37,0x01,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x0f,0x0d,0xef,0x00,0xd3,
+ 0x02,0x15,0x07,0x36,0x02,0xc5,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,
+};
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_wms8309_c7_20130910_130553.h b/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_wms8309_c7_20130910_130553.h new file mode 100755 index 00000000..aa7d7316 --- /dev/null +++ b/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_wms8309_c7_20130910_130553.h @@ -0,0 +1,233 @@ +
+/* This file is auto-generated. Don't edit this file. */
+
+/// R-Sense = 20
+/// ILMD = 3000
+/// EDVF = 3500
+/// Taper Current = 150
+/// Taper Voltage = 4150
+/// Taper Time = 60
+
+char FactoryGGBXFile_wms8309_c7_3000mAh [] = {
+ 0x5f,0x47,0x47,0x5f,0x56,0xbe,0x00,0x00,
+ 0x31,0x88,0x6e,0x95,0x0e,0x00,0x00,0x00,
+ 0xbd,0x06,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x57,0x6f,0x6e,0x64,
+ 0x65,0x72,0x6d,0x65,0x64,0x69,0x61,0x00,
+ 0x00,0x00,0x00,0x00,0x43,0x57,0x35,0x30,
+ 0x30,0x2d,0x33,0x30,0x30,0x30,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x6d,0x4d,
+ 0x61,0x78,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x53,0x5a,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x02,0x01,0x00,0x01,0x01,0x00,0x00,
+ 0x70,0x00,0x64,0x00,0x00,0x00,0x00,0x14,
+ 0x00,0x00,0xb8,0x0b,0xac,0x0d,0x14,0x00,
+ 0x96,0x00,0x36,0x10,0x3c,0x00,0x06,0x00,
+ 0x00,0x00,0x00,0x00,0xe4,0x0c,0x48,0x0d,
+ 0x00,0x00,0x32,0x00,0x26,0x02,0xf4,0x01,
+ 0x5c,0xff,0x77,0x01,0x63,0x05,0x00,0x00,
+ 0x00,0x00,0x01,0x00,0xf0,0xdf,0xf4,0x67,
+ 0xe8,0xcf,0xee,0x1b,0xe8,0x03,0xe8,0x03,
+ 0x00,0x00,0xe8,0x03,0x00,0x00,0x10,0x27,
+ 0x1e,0x61,0xae,0x5c,0xce,0x57,0x94,0x52,
+ 0x14,0x4d,0x6a,0x47,0xac,0x41,0xff,0x3b,
+ 0x77,0x36,0x2b,0x31,0x2c,0x2c,0x85,0x27,
+ 0x3d,0x23,0x58,0x1f,0xd3,0x1b,0xab,0x18,
+ 0xdb,0x15,0x5c,0x13,0x27,0x11,0xe8,0x03,
+ 0xbc,0x02,0xc2,0x01,0xc8,0x00,0x00,0x00,
+ 0x1e,0x00,0xe8,0x03,0xff,0x77,0x00,0x00,
+ 0x00,0x00,0x01,0x00,0x00,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x59,0x10,0x44,0x10,0x32,0x10,0x20,
+ 0x10,0x0b,0x10,0xef,0x0f,0xc8,0x0f,0x9c,
+ 0x0f,0x77,0x0f,0x28,0x0f,0xac,0x0d,0x68,
+ 0x10,0x0c,0x10,0xeb,0x0f,0xd5,0x0f,0xc4,
+ 0x0f,0xb4,0x0f,0xa0,0x0f,0x83,0x0f,0x66,
+ 0x0f,0x4d,0x0f,0x37,0x0f,0x28,0x0f,0x16,
+ 0x0f,0x03,0x0f,0xea,0x0e,0xcc,0x0e,0xab,
+ 0x0e,0x84,0x0e,0x5c,0x0e,0x2d,0x0e,0xac,
+ 0x0d,0x68,0x10,0x0c,0x10,0xeb,0x0f,0xd5,
+ 0x0f,0xc4,0x0f,0xb4,0x0f,0xa0,0x0f,0x83,
+ 0x0f,0x66,0x0f,0x4d,0x0f,0x37,0x0f,0x28,
+ 0x0f,0x16,0x0f,0x03,0x0f,0xea,0x0e,0xcc,
+ 0x0e,0xab,0x0e,0x84,0x0e,0x5c,0x0e,0x2d,
+ 0x0e,0xac,0x0d,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x63,
+ 0x10,0x4b,0x10,0x36,0x10,0x22,0x10,0x0d,
+ 0x10,0xf5,0x0f,0xd4,0x0f,0xac,0x0f,0x84,
+ 0x0f,0x3d,0x0f,0xac,0x0d,0x68,0x10,0x0a,
+ 0x10,0xe9,0x0f,0xd3,0x0f,0xc1,0x0f,0xaf,
+ 0x0f,0x99,0x0f,0x7d,0x0f,0x62,0x0f,0x4b,
+ 0x0f,0x35,0x0f,0x24,0x0f,0x11,0x0f,0xfc,
+ 0x0e,0xe5,0x0e,0xcb,0x0e,0xac,0x0e,0x86,
+ 0x0e,0x61,0x0e,0x34,0x0e,0xac,0x0d,0x68,
+ 0x10,0x0a,0x10,0xe9,0x0f,0xd3,0x0f,0xc1,
+ 0x0f,0xaf,0x0f,0x99,0x0f,0x7d,0x0f,0x62,
+ 0x0f,0x4b,0x0f,0x35,0x0f,0x24,0x0f,0x11,
+ 0x0f,0xfc,0x0e,0xe5,0x0e,0xcb,0x0e,0xac,
+ 0x0e,0x86,0x0e,0x61,0x0e,0x34,0x0e,0xac,
+ 0x0d,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x52,0x10,0x3c,0x10,0x25,0x10,0x0e,
+ 0x10,0xf2,0x0f,0xcd,0x0f,0xa3,0x0f,0x5f,
+ 0x0f,0xac,0x0d,0x68,0x10,0x05,0x10,0xe5,
+ 0x0f,0xcf,0x0f,0xbb,0x0f,0xa6,0x0f,0x8e,
+ 0x0f,0x74,0x0f,0x5c,0x0f,0x45,0x0f,0x30,
+ 0x0f,0x1f,0x0f,0x0b,0x0f,0xf5,0x0e,0xdd,
+ 0x0e,0xc4,0x0e,0xa5,0x0e,0x82,0x0e,0x60,
+ 0x0e,0x24,0x0e,0xac,0x0d,0x68,0x10,0x05,
+ 0x10,0xe5,0x0f,0xcf,0x0f,0xbb,0x0f,0xa6,
+ 0x0f,0x8e,0x0f,0x74,0x0f,0x5c,0x0f,0x45,
+ 0x0f,0x30,0x0f,0x1f,0x0f,0x0b,0x0f,0xf5,
+ 0x0e,0xdd,0x0e,0xc4,0x0e,0xa5,0x0e,0x82,
+ 0x0e,0x60,0x0e,0x24,0x0e,0xac,0x0d,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x67,0x10,0x4e,0x10,0x34,
+ 0x10,0x16,0x10,0xed,0x0f,0xa6,0x0f,0xac,
+ 0x0d,0x68,0x10,0xf6,0x0f,0xda,0x0f,0xc3,
+ 0x0f,0xac,0x0f,0x95,0x0f,0x7c,0x0f,0x68,
+ 0x0f,0x51,0x0f,0x3b,0x0f,0x26,0x0f,0x11,
+ 0x0f,0xfb,0x0e,0xe3,0x0e,0xcf,0x0e,0xb5,
+ 0x0e,0x99,0x0e,0x79,0x0e,0x4f,0x0e,0x0b,
+ 0x0e,0xac,0x0d,0x68,0x10,0xf6,0x0f,0xda,
+ 0x0f,0xc3,0x0f,0xac,0x0f,0x95,0x0f,0x7c,
+ 0x0f,0x68,0x0f,0x51,0x0f,0x3b,0x0f,0x26,
+ 0x0f,0x11,0x0f,0xfb,0x0e,0xe3,0x0e,0xcf,
+ 0x0e,0xb5,0x0e,0x99,0x0e,0x79,0x0e,0x4f,
+ 0x0e,0x0b,0x0e,0xac,0x0d,0x68,0x10,0xb9,
+ 0x0f,0x0a,0x0f,0x5b,0x0e,0xac,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xb9,0x0f,0x0a,0x0f,0x5b,0x0e,0xac,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xb9,0x0f,0x0a,0x0f,0x5b,
+ 0x0e,0xac,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xb9,0x0f,0x0a,
+ 0x0f,0x5b,0x0e,0xac,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x68,0x10,0xb9,
+ 0x0f,0x0a,0x0f,0x5b,0x0e,0xac,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xb9,0x0f,0x0a,0x0f,0x5b,0x0e,0xac,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xb9,0x0f,0x0a,0x0f,0x5b,
+ 0x0e,0xac,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xb9,0x0f,0x0a,
+ 0x0f,0x5b,0x0e,0xac,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x68,0x10,0xb9,
+ 0x0f,0x0a,0x0f,0x5b,0x0e,0xac,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xb9,0x0f,0x0a,0x0f,0x5b,0x0e,0xac,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xb9,0x0f,0x0a,0x0f,0x5b,
+ 0x0e,0xac,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xb9,0x0f,0x0a,
+ 0x0f,0x5b,0x0e,0xac,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x2a,0x09,0x00,
+ 0x00,0xcf,0x00,0x51,0x04,0x0a,0x04,0x51,
+ 0x02,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xed,
+ 0x0a,0x7d,0x00,0x26,0x04,0x44,0x04,0x05,
+ 0x02,0x9d,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x0e,0x0b,0x6c,0x01,0x7f,0x04,0xc0,
+ 0x03,0x62,0x01,0x7e,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xe5,0x08,0x00,0x00,0xad,
+ 0x00,0x1b,0x04,0x1c,0x04,0x85,0x02,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xe7,0x0a,0x69,
+ 0x00,0x07,0x04,0x5b,0x04,0x19,0x02,0x99,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0e,
+ 0x0b,0x4d,0x01,0x7b,0x04,0xe0,0x03,0x64,
+ 0x01,0x75,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x09,0x08,0x00,0x00,0x59,0x00,0x7e,
+ 0x03,0x31,0x04,0x30,0x03,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x94,0x0a,0x3e,0x00,0xb1,
+ 0x03,0x5a,0x04,0x4a,0x02,0xc4,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xeb,0x0a,0x05,
+ 0x01,0x70,0x04,0xf7,0x03,0x7e,0x01,0x79,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xeb,
+ 0x05,0x00,0x00,0x14,0x00,0xd9,0x01,0xfd,
+ 0x03,0xbf,0x04,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x95,0x09,0x10,0x00,0x99,0x02,0x53,
+ 0x04,0x97,0x02,0x52,0x01,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x53,0x0a,0x79,0x00,0x10,
+ 0x04,0x11,0x04,0xb7,0x01,0xa0,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,
+};
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_wms8309_wm8_20130820_110949.h b/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_wms8309_wm8_20130820_110949.h new file mode 100755 index 00000000..c107e274 --- /dev/null +++ b/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_wms8309_wm8_20130820_110949.h @@ -0,0 +1,233 @@ +
+/* This file is auto-generated. Don't edit this file. */
+
+/// R-Sense = 20
+/// ILMD = 5000
+/// EDVF = 3500
+/// Taper Current = 200
+/// Taper Voltage = 4150
+/// Taper Time = 60
+
+char FactoryGGBXFile_wms8309_wm8 [] = {
+ 0x5f,0x47,0x47,0x5f,0x1d,0xcc,0x00,0x00,
+ 0xfd,0x0e,0x54,0x95,0x0e,0x00,0x00,0x00,
+ 0xbd,0x06,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x57,0x4d,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x57,0x4d,0x53,0x38,
+ 0x33,0x30,0x39,0x2d,0x31,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x57,0x4d,
+ 0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x33,0x37,
+ 0x31,0x30,0x38,0x31,0x30,0x33,0x50,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x01,0x02,0x00,0x01,0x01,0x00,0x00,
+ 0x70,0x00,0x64,0x00,0x00,0x00,0x00,0x14,
+ 0x00,0x00,0x88,0x13,0xac,0x0d,0x14,0x00,
+ 0xc8,0x00,0x36,0x10,0x3c,0x00,0x06,0x00,
+ 0x00,0x00,0x00,0x00,0xe4,0x0c,0x48,0x0d,
+ 0x00,0x00,0x32,0x00,0x26,0x02,0xf4,0x01,
+ 0x5c,0xff,0x77,0x01,0x63,0x05,0x00,0x00,
+ 0x00,0x00,0x01,0x00,0xf0,0xdf,0xf4,0x67,
+ 0xe8,0xcf,0xee,0x1b,0xe8,0x03,0xe8,0x03,
+ 0x00,0x00,0xe8,0x03,0x00,0x00,0x10,0x27,
+ 0x1e,0x61,0xae,0x5c,0xce,0x57,0x94,0x52,
+ 0x14,0x4d,0x6a,0x47,0xac,0x41,0xff,0x3b,
+ 0x77,0x36,0x2b,0x31,0x2c,0x2c,0x85,0x27,
+ 0x3d,0x23,0x58,0x1f,0xd3,0x1b,0xab,0x18,
+ 0xdb,0x15,0x5c,0x13,0x27,0x11,0xe8,0x03,
+ 0xbc,0x02,0xc2,0x01,0xc8,0x00,0x00,0x00,
+ 0x1e,0x00,0xe8,0x03,0xff,0x77,0x00,0x00,
+ 0x00,0x00,0x01,0x00,0x00,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x41,0x10,0x1a,
+ 0x10,0xf8,0x0f,0xd9,0x0f,0xbe,0x0f,0xa5,
+ 0x0f,0x8d,0x0f,0x77,0x0f,0x65,0x0f,0x56,
+ 0x0f,0x48,0x0f,0x3b,0x0f,0x27,0x0f,0x0a,
+ 0x0f,0xeb,0x0e,0xce,0x0e,0xac,0x0d,0x68,
+ 0x10,0x2f,0x10,0x00,0x10,0xd3,0x0f,0xaa,
+ 0x0f,0x84,0x0f,0x61,0x0f,0x3f,0x0f,0x1c,
+ 0x0f,0xfe,0x0e,0xe8,0x0e,0xd9,0x0e,0xc9,
+ 0x0e,0xbb,0x0e,0xad,0x0e,0x9f,0x0e,0x8c,
+ 0x0e,0x70,0x0e,0x55,0x0e,0x3a,0x0e,0xac,
+ 0x0d,0x68,0x10,0x2f,0x10,0x00,0x10,0xd3,
+ 0x0f,0xaa,0x0f,0x84,0x0f,0x61,0x0f,0x3f,
+ 0x0f,0x1c,0x0f,0xfe,0x0e,0xe8,0x0e,0xd9,
+ 0x0e,0xc9,0x0e,0xbb,0x0e,0xad,0x0e,0x9f,
+ 0x0e,0x8c,0x0e,0x70,0x0e,0x55,0x0e,0x3a,
+ 0x0e,0xac,0x0d,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x62,0x10,0x38,0x10,0x13,
+ 0x10,0xf3,0x0f,0xd7,0x0f,0xbf,0x0f,0xab,
+ 0x0f,0x9a,0x0f,0x8b,0x0f,0x7e,0x0f,0x72,
+ 0x0f,0x67,0x0f,0x57,0x0f,0x3c,0x0f,0x17,
+ 0x0f,0xf7,0x0e,0xac,0x0d,0x68,0x10,0x20,
+ 0x10,0xee,0x0f,0xc0,0x0f,0x95,0x0f,0x6e,
+ 0x0f,0x4b,0x0f,0x29,0x0f,0x09,0x0f,0xef,
+ 0x0e,0xdb,0x0e,0xce,0x0e,0xc1,0x0e,0xb7,
+ 0x0e,0xaf,0x0e,0xa5,0x0e,0x96,0x0e,0x7b,
+ 0x0e,0x59,0x0e,0x3b,0x0e,0xac,0x0d,0x68,
+ 0x10,0x20,0x10,0xee,0x0f,0xc0,0x0f,0x95,
+ 0x0f,0x6e,0x0f,0x4b,0x0f,0x29,0x0f,0x09,
+ 0x0f,0xef,0x0e,0xdb,0x0e,0xce,0x0e,0xc1,
+ 0x0e,0xb7,0x0e,0xaf,0x0e,0xa5,0x0e,0x96,
+ 0x0e,0x7b,0x0e,0x59,0x0e,0x3b,0x0e,0xac,
+ 0x0d,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x53,0x10,0x31,
+ 0x10,0x14,0x10,0xfa,0x0f,0xe4,0x0f,0xd2,
+ 0x0f,0xc1,0x0f,0xb2,0x0f,0xa4,0x0f,0x96,
+ 0x0f,0x86,0x0f,0x6c,0x0f,0x48,0x0f,0x27,
+ 0x0f,0xac,0x0d,0x68,0x10,0x21,0x10,0xf3,
+ 0x0f,0xc8,0x0f,0x9c,0x0f,0x76,0x0f,0x51,
+ 0x0f,0x2e,0x0f,0x10,0x0f,0xf8,0x0e,0xe4,
+ 0x0e,0xd6,0x0e,0xc7,0x0e,0xba,0x0e,0xaf,
+ 0x0e,0xa3,0x0e,0x91,0x0e,0x77,0x0e,0x62,
+ 0x0e,0x3f,0x0e,0xac,0x0d,0x68,0x10,0x21,
+ 0x10,0xf3,0x0f,0xc8,0x0f,0x9c,0x0f,0x76,
+ 0x0f,0x51,0x0f,0x2e,0x0f,0x10,0x0f,0xf8,
+ 0x0e,0xe4,0x0e,0xd6,0x0e,0xc7,0x0e,0xba,
+ 0x0e,0xaf,0x0e,0xa3,0x0e,0x91,0x0e,0x77,
+ 0x0e,0x62,0x0e,0x3f,0x0e,0xac,0x0d,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x5d,0x10,0x51,0x10,0x42,0x10,0x31,
+ 0x10,0x1f,0x10,0x0f,0x10,0xfe,0x0f,0xeb,
+ 0x0f,0xd1,0x0f,0xae,0x0f,0x85,0x0f,0xac,
+ 0x0d,0x68,0x10,0x25,0x10,0xed,0x0f,0xae,
+ 0x0f,0x81,0x0f,0x5c,0x0f,0x3a,0x0f,0x1c,
+ 0x0f,0x03,0x0f,0xee,0x0e,0xdc,0x0e,0xcf,
+ 0x0e,0xc0,0x0e,0xb3,0x0e,0xa7,0x0e,0x99,
+ 0x0e,0x86,0x0e,0x71,0x0e,0x56,0x0e,0x1c,
+ 0x0e,0xac,0x0d,0x68,0x10,0x25,0x10,0xed,
+ 0x0f,0xae,0x0f,0x81,0x0f,0x5c,0x0f,0x3a,
+ 0x0f,0x1c,0x0f,0x03,0x0f,0xee,0x0e,0xdc,
+ 0x0e,0xcf,0x0e,0xc0,0x0e,0xb3,0x0e,0xa7,
+ 0x0e,0x99,0x0e,0x86,0x0e,0x71,0x0e,0x56,
+ 0x0e,0x1c,0x0e,0xac,0x0d,0x68,0x10,0xb9,
+ 0x0f,0x0a,0x0f,0x5b,0x0e,0xac,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xb9,0x0f,0x0a,0x0f,0x5b,0x0e,0xac,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xb9,0x0f,0x0a,0x0f,0x5b,
+ 0x0e,0xac,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xb9,0x0f,0x0a,
+ 0x0f,0x5b,0x0e,0xac,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x68,0x10,0xb9,
+ 0x0f,0x0a,0x0f,0x5b,0x0e,0xac,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xb9,0x0f,0x0a,0x0f,0x5b,0x0e,0xac,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xb9,0x0f,0x0a,0x0f,0x5b,
+ 0x0e,0xac,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xb9,0x0f,0x0a,
+ 0x0f,0x5b,0x0e,0xac,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x68,0x10,0xb9,
+ 0x0f,0x0a,0x0f,0x5b,0x0e,0xac,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xb9,0x0f,0x0a,0x0f,0x5b,0x0e,0xac,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xb9,0x0f,0x0a,0x0f,0x5b,
+ 0x0e,0xac,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xb9,0x0f,0x0a,
+ 0x0f,0x5b,0x0e,0xac,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xf9,0x11,0xc1,
+ 0x00,0xb7,0x03,0xc5,0x05,0xba,0x07,0xa4,
+ 0x01,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0c,
+ 0x13,0x48,0x02,0x75,0x04,0x96,0x08,0xb7,
+ 0x03,0xbd,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x35,0x13,0xdf,0x02,0xb8,0x04,0xd9,
+ 0x08,0xc4,0x02,0xa8,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x1a,0x12,0x20,0x00,0x23,
+ 0x03,0x2c,0x05,0xaa,0x09,0x97,0x02,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x11,0x14,0xa2,
+ 0x01,0x50,0x04,0xca,0x09,0x53,0x04,0xcb,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x4c,
+ 0x14,0x5e,0x02,0xd0,0x04,0x51,0x0a,0xcc,
+ 0x02,0xaa,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x2e,0x0f,0x11,0x00,0x9b,0x02,0x7c,
+ 0x04,0x05,0x08,0x05,0x04,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x35,0x12,0x48,0x01,0xec,
+ 0x03,0xd7,0x07,0x28,0x05,0xec,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x9b,0x12,0x23,
+ 0x02,0x58,0x04,0x31,0x09,0xef,0x02,0xa3,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7a,
+ 0x0a,0x00,0x00,0xb4,0x00,0x89,0x03,0x3d,
+ 0x06,0x96,0x08,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xb9,0x10,0x73,0x01,0xae,0x01,0xe4,
+ 0x05,0xb3,0x07,0xb6,0x01,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x48,0x12,0xab,0x01,0x0d,
+ 0x04,0xb1,0x08,0xde,0x03,0xdd,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,
+};
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/global.h b/drivers/power/wmt_battery/gauge/ug31xx/global.h new file mode 100755 index 00000000..e328bec3 --- /dev/null +++ b/drivers/power/wmt_battery/gauge/ug31xx/global.h @@ -0,0 +1,31 @@ +#ifndef _GLOBAL_H_
+#define _GLOBAL_H_
+
+#define UG31XX_DEBUG_ENABLE
+//#define UG31XX_RESET_DATABASE ///< [AT-PM] : DEFAULT off ; 04/13/2013
+
+#if defined (uG31xx_OS_WINDOWS)
+
+ #pragma pack(push)
+ #pragma pack(1)
+
+ #ifdef UG31XX_DEBUG_ENABLE
+ #define DEBUG_LOG
+ #define uG31xx_NAC_LMD_ADJUST_DEBUG_ENABLE
+ #define TABLE_BACKUP_DEBUG_ENABLE
+ #define DEBUG_LOG_AT_PM
+ #define CALIBRATE_ADC_DEBUG_LOG
+ #endif ///< end of UG31XX_DEBUG_ENABLE
+
+ #define EXPORTS _declspec(dllexport)
+
+#elif defined(uG31xx_OS_ANDROID)
+
+ #define EXPORTS
+
+#endif
+
+#define ENABLE_BQ27520_SW_CMD
+//#define ENABLE_NTC_CHECK
+
+#endif
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/stdafx.h b/drivers/power/wmt_battery/gauge/ug31xx/stdafx.h new file mode 100755 index 00000000..8db0f146 --- /dev/null +++ b/drivers/power/wmt_battery/gauge/ug31xx/stdafx.h @@ -0,0 +1,8 @@ +/** + * @filename stdafx.h + * + * Dummy header file + * + * @author AllenTeng <allen_teng@upi-semi.com> + */ + diff --git a/drivers/power/wmt_battery/gauge/ug31xx/typeDefine.h b/drivers/power/wmt_battery/gauge/ug31xx/typeDefine.h new file mode 100755 index 00000000..d4034026 --- /dev/null +++ b/drivers/power/wmt_battery/gauge/ug31xx/typeDefine.h @@ -0,0 +1,462 @@ +/// ===========================================
+/// typeDefine.h
+/// ===========================================
+
+#ifndef _TYPE_DEFINE_H_
+#define _TYPE_DEFINE_H_
+
+typedef unsigned char _upi_u8_;
+typedef unsigned short _upi_u16_;
+typedef unsigned int _upi_u32_;
+typedef unsigned long long _upi_u64_;
+typedef char _upi_s8_;
+typedef short _upi_s16_;
+typedef int _upi_s32_;
+typedef long long _upi_s64_;
+typedef char _upi_bool_;
+
+#define _UPI_TRUE_ (1)
+#define _UPI_FALSE_ (0)
+#define _UPI_NULL_ (NULL)
+
+
+#ifdef uG31xx_OS_WINDOWS
+#pragma pack(push)
+#pragma pack(1)
+#endif
+
+#define CELL_PARAMETER_ALARM_EN_UV (1<<0)
+#define CELL_PARAMETER_ALARM_EN_UET (1<<1)
+#define CELL_PARAMETER_ALARM_EN_OET (1<<2)
+
+typedef struct CELL_PARAMETER
+{
+ _upi_u16_ totalSize; //Total struct size
+ _upi_u16_ fw_ver; //CellParameter struct version
+
+ char customer[16]; //Customer name defined by uPI //####2012/08/29#####
+ char project[16]; //Project name defined by uPI
+ _upi_u16_ ggb_version; //0x0102 => 2.1
+
+ char customerSelfDef[16]; //Customer name record by customer
+ char projectSelfDef[16]; //Project name record by customer
+ _upi_u16_ cell_type_code;
+
+ _upi_u8_ ICType; /*[2:0]=000 -> uG3100 [2:0]=001 -> uG3101
+ [2:0]=010 -> uG3102 [2:0]=100 -> uG3103_2
+ [2:0]=101 -> uG3103_3 */
+
+ _upi_u8_ gpio1; /*bit[4] cbc_en32
+ bit[3] cbc_en21
+ bit[2] pwm
+ bit[1] alarm
+ bit[0] gpio */
+ _upi_u8_ gpio2; /*bit[4] cbc_en32
+ bit[3] cbc_en21
+ bit[2] pwm
+ bit[1] alarm
+ bit[0] gpio */
+ _upi_u8_ gpio34; //11/22/2011 -->reg92
+
+ _upi_u8_ cellNumber;
+ _upi_u8_ assignCellOneTo;
+ _upi_u8_ assignCellTwoTo;
+ _upi_u8_ assignCellThreeTo;
+
+ _upi_u16_ i2cAddress; //I2C Address(Hex)
+ _upi_u16_ clock;
+
+ _upi_u8_ tenBitAddressMode;
+ _upi_u8_ highSpeedMode;
+ _upi_u8_ chopCtrl; //11/22/2011 -->regC1
+ _upi_u8_ rSense;
+
+ _upi_s16_ adc1Offset; //11/22/2011 -->reg58/59
+ _upi_u16_ ILMD;
+
+ _upi_u16_ edv1Voltage;
+ _upi_u16_ standbyCurrent;
+
+ _upi_u16_ TPCurrent;
+ _upi_u16_ TPVoltage;
+
+ _upi_u16_ TPTime;
+ _upi_u16_ offsetR;
+
+ _upi_u16_ deltaR;
+ _upi_u16_ TpBypassCurrent; //20121029
+
+ _upi_s16_ uvAlarm;
+ _upi_s16_ uvRelease;
+
+ _upi_s16_ uetAlarm;
+ _upi_s16_ uetRelease;
+
+ _upi_s16_ oetAlarm;
+ _upi_s16_ oetRelease;
+
+ _upi_s16_ oscTuneJ;
+ _upi_s16_ oscTuneK;
+
+ _upi_u8_ maxDeltaQ;
+ _upi_u8_ timeInterval;
+ _upi_u8_ alarm_timer; //11/22/2011 00:*5,01:*10,10:*15,11:*20
+ _upi_u8_ pwm_timer; /*[1:0]=00:32k [1:0]=01:16k
+ [1:0]=10:8k [1:0]=11: 4k */
+
+ _upi_u8_ clkDivA; //11/22/2011
+ _upi_u8_ clkDivB; //11/22/2011
+ _upi_u8_ alarmEnable; /*[7]:COC [6]:DOC [5]:IT [4]:ET
+ [3]:VP [2]:V3 [1]:V2 [0]:V1 */
+ _upi_u8_ cbcEnable; /*[1]:CBC_EN32 [0]:CBC_EN21 */
+
+ _upi_u16_ vBat2_8V_IdealAdcCode; //ideal ADC Code
+ _upi_u16_ vBat2_6V_IdealAdcCode;
+
+ _upi_u16_ vBat3_12V_IdealAdcCode;
+ _upi_u16_ vBat3_9V_IdealAdcCode;
+
+ _upi_s16_ adc1_pgain;
+ _upi_s16_ adc1_ngain;
+
+ _upi_s16_ adc1_pos_offset;
+ _upi_u16_ adc2_gain;
+
+ _upi_s16_ adc2_offset;
+ _upi_u16_ R;
+
+ _upi_u16_ rtTable[ET_NUMS];
+ // SOV_TABLE %
+ _upi_u16_ SOV_TABLE[SOV_NUMS];
+
+ _upi_s16_ adc_d1; //2012/06/06/update for IT25
+ _upi_s16_ adc_d2; //2012/06/06/update for IT80
+
+ _upi_s16_ adc_d3; ///< [AT-PM] : Used for ADC calibration IT code ; 08/15/2012
+ _upi_s16_ adc_d4;
+
+ _upi_s16_ adc_d5;
+ _upi_u16_ NacLmdAdjustCfg;
+
+ _upi_u8_ otp1Scale;
+#if defined(uG31xx_OS_ANDROID)
+}__attribute__((packed)) CELL_PARAMETER;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+}CELL_PARAMETER;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+typedef struct CELL_TABLE
+{
+ _upi_s16_ INIT_OCV[TEMPERATURE_NUMS][OCV_TABLE_IDX_COUNT][OCV_NUMS]; //initial OCV Table,0.1C/0.2C OCV/charge table
+ _upi_s16_ CELL_VOLTAGE_TABLE[TEMPERATURE_NUMS][C_RATE_NUMS][OCV_NUMS]; //cell behavior Model,the voltage data
+ _upi_s16_ CELL_NAC_TABLE[TEMPERATURE_NUMS][C_RATE_NUMS][OCV_NUMS]; //cell behavior Model,the deltaQ
+#if defined(uG31xx_OS_ANDROID)
+} __attribute__((packed)) CELL_TABLE;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+}CELL_TABLE;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+typedef struct CELL_DATA
+{
+ CELL_PARAMETER cellParameter;
+ CELL_TABLE cellTable1;
+#if defined(uG31xx_OS_ANDROID)
+} __attribute__((packed)) CELL_DATA;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+}CELL_DATA;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+#if defined(uG31xx_OS_ANDROID)
+
+ //<ASUS-WAD+>
+ typedef struct _GGBX_FILE_HEADER
+ {
+ _upi_u32_ ggb_tag; //'_GG_'
+ _upi_u32_ sum16; //16 bits checksum, but store as 4 bytes
+ _upi_u64_ time_stamp; //seconds pass since 1970 year, 00:00:00
+ _upi_u64_ length; //size that not only include ggb content. (multi-file support)
+ _upi_u64_ num_ggb; //number of ggb files.
+ } __attribute__((packed)) GGBX_FILE_HEADER;
+ //<ASUS-WAD->
+
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+typedef struct ADC_CHECK
+{
+ _upi_u16_ regCounter; //check adc counter
+ _upi_u16_ regVbat1Ave; //check average voltage
+ _upi_u16_ lastCounter;
+ _upi_u16_ lastVBat1Ave;
+ _upi_u16_ failCounterCurrent;
+ _upi_u16_ failCounterVoltage;
+#if defined(uG31xx_OS_ANDROID)
+} __attribute__((packed)) ADC_CHECK;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+}ADC_CHECK;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+typedef struct USER_REGISTER
+{
+ _upi_u8_ mode;
+ _upi_u8_ ctrl1;
+ _upi_u8_ charge_low;
+ _upi_u8_ charge_high;
+ _upi_u8_ counter_low;
+ _upi_u8_ counter_high;
+ _upi_u8_ current_low;
+ _upi_u8_ current_high;
+ _upi_u8_ vbat1_low;
+ _upi_u8_ vbat1_high;
+ _upi_u8_ intr_temper_low;
+ _upi_u8_ intr_temper_high;
+ _upi_u8_ ave_current_low;
+ _upi_u8_ ave_current_high;
+ _upi_u8_ extr_temper_low;
+ _upi_u8_ extr_temper_high;
+ _upi_u8_ rid_low;
+ _upi_u8_ rid_high;
+ _upi_u8_ alarm1_status;
+ _upi_u8_ alarm2_status;
+ _upi_u8_ intr_status;
+ _upi_u8_ alram_en;
+ _upi_u8_ ctrl2;
+#if defined(uG31xx_OS_ANDROID)
+}__attribute__((packed)) USER_REGISTER;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+}USER_REGISTER;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+//2012/08/24/new add for system suspend
+typedef struct _GG_SUSPEND_INFO{
+ _upi_u16_ LMD; //battery Qmax (maH)
+ _upi_u16_ NAC; //battery NAC(maH)
+ _upi_u16_ RSOC; //Battery Current RSOC(%)
+ _upi_u32_ currentTime; //the time tick
+
+#if defined(uG31xx_OS_ANDROID)
+ }__attribute__((packed)) GG_SUSPEND_INFO,*PGG_SUSPEND_INFO;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+}GG_SUSPEND_INFO,*PGG_SUSPEND_INFO;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+typedef struct _GG_BATTERY_INFO{
+ _upi_u16_ LMD; //battery Qmax (maH)
+ _upi_u16_ NAC; //battery NAC(maH)
+ _upi_u16_ RSOC; //Battery Current RSOC(%)
+#if defined(uG31xx_OS_ANDROID)
+ }__attribute__((packed)) GG_BATTERY_INFO;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+}GG_BATTERY_INFO;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+/// [AT-PM] : Used for TI bq27520 like command ; 10/11/2012
+typedef struct GG_TI_BQ27520 {
+ _upi_u16_ CntlControlStatus;
+ _upi_u16_ CntlPrevMacWrite;
+
+ _upi_s16_ AR;
+
+ _upi_u16_ Temp;
+
+ _upi_u16_ Flags;
+ _upi_s16_ SINow;
+ _upi_s32_ SIBuf;
+ _upi_s16_ SISample;
+ _upi_u16_ SIWindow;
+
+ _upi_s16_ Mli;
+ _upi_u8_ MliDsgSoc;
+
+ _upi_u16_ AE;
+
+ _upi_s16_ AP;
+ _upi_u16_ APStartChgE;
+ _upi_u16_ APStartDsgE;
+ _upi_u32_ APChgTime;
+ _upi_u32_ APDsgTime;
+
+ _upi_u16_ CC;
+ _upi_u16_ CCBuf;
+ _upi_u16_ CCLastNac;
+
+ _upi_u16_ Dli;
+
+ _upi_u16_ Dlb;
+
+ _upi_s8_ FCSet;
+ _upi_s8_ FCClear;
+ _upi_u8_ Soc1Set;
+ _upi_u8_ Soc1Clear;
+ _upi_s8_ InitSI;
+ _upi_s16_ InitMaxLoadCurrent;
+ _upi_s16_ CCThreshold;
+ _upi_u32_ Opcfg;
+ _upi_u16_ Dcap;
+
+ _upi_u32_ LastTime;
+ _upi_u16_ DeltaSec;
+#if defined(uG31xx_OS_ANDROID)
+}__attribute__((packed)) GG_TI_BQ27520, *PGG_TI_BQ27520;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+} GG_TI_BQ27520, *PGG_TI_BQ27520;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+typedef struct GGAdcDeltaCodeMappingST {
+ _upi_s32_ Adc1V100;
+ _upi_s32_ Adc1V200;
+ _upi_s32_ Adc2V100;
+ _upi_s32_ Adc2V200;
+#if defined(uG31xx_OS_ANDROID)
+}__attribute__((packed)) GGAdcDeltaCodeMappingType;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+} GGAdcDeltaCodeMappingType;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+typedef struct TableBackupST {
+ _upi_u16_ lowerBound;
+ _upi_u8_ resolution;
+#if defined(uG31xx_OS_ANDROID)
+} __attribute__((packed)) TableBackupType;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+} TableBackupType;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+/* define the register of uG31xx */
+typedef struct _GG_USER_REG{
+ _upi_u8_ regMode; ///< [AT-PM] : 0x00 - MODE ; 04/08/2013
+ _upi_u8_ regCtrl1; ///< [AT-PM] : 0x01 - CTRL1 ; 04/08/2013
+ _upi_s16_ regCharge; ///< [AT-PM] : 0x02 - Charge ; 04/08/2013
+ _upi_u16_ regCounter; ///< [AT-PM] : 0x04 - Counter ; 04/08/2013
+ _upi_s16_ regCurrentAve; ///< [AT-PM] : 0x06 - Ave Current ; 04/08/2013
+ _upi_s16_ regVbat1Ave; ///< [AT-PM] : 0x08 - Ave VBat1 ; 04/08/2013
+ _upi_u16_ regITAve; ///< [AT-PM] : 0x0A - Ave IT ; 04/08/2013
+ _upi_s16_ regOffsetCurrentAve; ///< [AT-PM] : 0x0C - Ave Offset Current ; 04/08/2013
+ _upi_u16_ regETAve; ///< [AT-PM] : 0x0E - Ave ET ; 04/08/2013
+ _upi_u16_ regRidAve; ///< [AT-PM] : 0x10 - Ave RID ; 04/08/2013
+ _upi_u8_ regAlarm1Status; ///< [AT-PM] : 0x12 - Alarm1 Status ; 04/08/2013
+ _upi_u8_ regAlarm2Status; ///< [AT-PM] : 0x13 - Alarm2 Status ; 04/08/2013
+ _upi_u8_ regIntrStatus; ///< [AT-PM] : 0x14 - INTR Status ; 04/08/2013
+ _upi_u8_ regAlarmEnable; ///< [AT-PM] : 0x15 - Alarm EN ; 04/08/2013
+ _upi_u8_ regCtrl2; ///< [AT-PM] : 0x16 - CTRL2 ; 04/08/2013
+#if defined(uG31xx_OS_ANDROID)
+} __attribute__((packed)) GG_USER_REG, *PGG_USER_REG;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+} GG_USER_REG, *PGG_USER_REG;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+/* define the register of uG31xx */
+typedef struct _GG_USER2_REG{
+ _upi_s16_ regVbat2; //0x40,vBat2
+ _upi_s16_ regVbat3; //0x42,vBat3
+ _upi_s16_ regVbat1; //0x44,vBat1 Average
+ _upi_s16_ regVbat2Ave; //0x46,vBat2 Average
+ _upi_s16_ regVbat3Ave; //0x48,vBat3 Average
+ _upi_u16_ regV1; //0x4a,cell 1 Voltage
+ _upi_u16_ regV2; //0x4c,0xcell 2 Voltage
+ _upi_u16_ regV3; //0x4e,cell 3 Voltage
+ _upi_s16_ regIT; //0x50
+ _upi_s16_ regET; //0x52
+ _upi_u16_ regRID; //0x54
+ _upi_s16_ regCurrent; //0x56
+ _upi_s16_ regAdc1Offset; //0x58ADC1 offset
+#if defined(uG31xx_OS_ANDROID)
+}__attribute__((packed)) GG_USER2_REG, *PGG_USER2_REG;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+} GG_USER2_REG, *PGG_USER2_REG;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+#define ALARM_STATUS_UV (1<<0)
+#define ALARM_STATUS_UET (1<<1)
+#define ALARM_STATUS_OET (1<<2)
+
+/* define device information */
+typedef struct _GG_DEVICE_INFO{
+
+ _upi_s16_ oldRegCurrent; //for skip the ADC code Error
+ _upi_s16_ oldRegVbat1; //for skip the ADC code Error
+
+ _upi_s16_ vBat1_AdcCode; //debug use
+ _upi_s16_ vBat1_AveAdcCode;
+
+ _upi_s16_ fwCalAveCurrent_mA; //f/w calculate average current
+ _upi_u32_ lastTime; //
+ _upi_s16_ chargeRegister; //coulomb counter register
+ _upi_u16_ AdcCounter; //ADC convert counter
+
+ _upi_s16_ preChargeRegister; //coulomb counter register
+ _upi_s16_ aveCurrentRegister; //2012/0711/jacky
+ _upi_u16_ preAdcCounter;
+ _upi_s32_ fwChargeData_mAH; //fw calculate maH (Q= I * T)
+
+ _upi_s32_ chargeData_mAh; //maH calculate from charge register
+ _upi_u16_ voltage_mV; //total voltage
+ _upi_s16_ current_mA; // now current
+ _upi_s16_ AveCurrent_mA; // average current
+ _upi_s16_ IT; // internal temperature
+ _upi_s16_ ET; // external temperature
+
+ _upi_u16_ v1_mV; //v1 from hw register
+ _upi_u16_ v2_mV; //v2
+ _upi_u16_ v3_mV; //v3
+
+ _upi_u16_ vBat1Average_mV; //vbat1
+ _upi_u16_ vBat2Average_mV;
+ _upi_u16_ vBat3Average_mV;
+
+
+ _upi_u16_ vCell1_mV; //v Cell1
+ _upi_u16_ vCell2_mV; //v Cell2
+ _upi_u16_ vCell3_mV; //v Cell3
+
+ _upi_s16_ CaliAdc1Code; //2012/08/29/Jacky
+ _upi_s16_ CaliAdc2Code;
+
+ _upi_s16_ CoulombCounter;
+
+ _upi_s32_ CaliChargeReg;
+ _upi_u16_ Adc1ConvTime;
+
+ _upi_u8_ alarmStatus;
+#if defined(uG31xx_OS_ANDROID)
+} __attribute__((packed)) GG_DEVICE_INFO, *PGG_DEVICE_INFO;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+} GG_DEVICE_INFO, *PGG_DEVICE_INFO;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+
+/* define battery capacity */
+typedef struct _GG_CAPACITY {
+ _upi_u16_ LMD; //battery Qmax (maH)
+ _upi_u16_ NAC; //battery NAC(maH)
+ _upi_u16_ RSOC; //Battery Current RSOC(%)
+
+ _upi_u8_ Ready;
+#if defined(uG31xx_OS_ANDROID)
+} __attribute__((packed)) GG_CAPACITY, *PGG_CAPACITY;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+}GG_CAPACITY, *PGG_CAPACITY;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+typedef struct GG_MEAS_PARA_ST
+{
+ _upi_s16_ Adc1Gain;
+ _upi_s16_ Adc1Offset;
+
+ _upi_s16_ Adc2Gain;
+ _upi_s16_ Adc2Offset;
+
+ _upi_s16_ ITOffset;
+ _upi_s16_ ETOffset;
+
+ _upi_u8_ ProductType;
+#if defined(uG31xx_OS_ANDROID)
+}__attribute__((packed)) GG_MEAS_PARA_TYPE;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+} GG_MEAS_PARA_TYPE;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+#endif
+
+/// ===========================================
+/// End of typeDefine.h
+/// ===========================================
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/uG31xx.h b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx.h new file mode 100755 index 00000000..b3fd8ee3 --- /dev/null +++ b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx.h @@ -0,0 +1,127 @@ +/// ===========================================
+/// uG31xx.h
+/// ===========================================
+
+#ifndef _UG31XX_H_
+#define _UG31XX_H_
+
+#define SECURITY 1 //Security Mode enable
+#define NORMAL 0 //Security Mode OFF
+
+#define HIGH_SPEED 1 //HS Mode
+#define FULL_SPEED 0 //FIL speed
+
+#define TEN_BIT_ADDR 1 //10-bit address Mode
+#define SEVEN_BIT_ADDR 0 //7-bit address Mode
+
+#define I2C_SUCESS 1 //
+#define I2C_FAIL 0
+
+/// ===========================================================================
+/// Constant for Calibration
+/// ===========================================================================
+
+#define IT_TARGET_CODE25 (12155)
+#define IT_TARGET_CODE80 (14306)
+
+#define IT_CODE_25 (23171)
+#define IT_CODE_80 (27341)
+
+//constant
+//define IC type
+#define uG3100 0
+#define uG3101 1
+#define uG3102 2
+#define uG3103_2 4
+#define uG3103_3 5
+
+//constant
+//GPIO1/2 define
+#define FUN_GPIO 0x01
+#define FUN_ALARM 0x02
+#define FUN_PWM 0x04
+#define FUN_CBC_EN21 0x08
+#define FUN_CBC_EN32 0x10
+
+#define BIT_MACRO(x) ((_upi_u8_)1 << (x))
+
+#define MAX_CRATE_AVAILABLE (20)
+
+#define I2C_ADDRESS 0x70
+#define I2C_CLOCK 0x100
+
+//const for CELL_TABLE table
+#define TEMPERATURE_NUMS (4)
+#define C_RATE_NUMS (3) ///< [AT-PM] : 0.5, 0.2, 0.1, 0.02 ; 12/17/2013
+#define OCV_NUMS (21) //include the 0% & 100%
+#define SOV_NUMS (5) ///< [AT-PM] : 100%, 70%, 45%, 20%, 0% ; 12/17/2012
+#define ET_NUMS (19) ///< [AT-PM] : -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80 ; 01/25/2013
+
+#define NAC_LMD_ADJUST_CFG_NO_LMD_UPDATE_BETWEEN_10_90_EN (1<<0)
+#define NAC_LMD_ADJUST_CFG_LOCK_AND_SPEED_UP_START_SOV_MASK (15<<1)
+ #define NAC_LMD_ADJUST_CFG_LOCK_AND_SPEED_UP_START_SOV_00 (0<<1)
+ #define NAC_LMD_ADJUST_CFG_LOCK_AND_SPEED_UP_START_SOV_01 (1<<1)
+ #define NAC_LMD_ADJUST_CFG_LOCK_AND_SPEED_UP_START_SOV_02 (2<<1)
+ #define NAC_LMD_ADJUST_CFG_LOCK_AND_SPEED_UP_START_SOV_03 (3<<1)
+ #define NAC_LMD_ADJUST_CFG_LOCK_AND_SPEED_UP_START_SOV_04 (4<<1)
+ #define NAC_LMD_ADJUST_CFG_LOCK_AND_SPEED_UP_START_SOV_05 (5<<1)
+ #define NAC_LMD_ADJUST_CFG_LOCK_AND_SPEED_UP_START_SOV_06 (6<<1)
+ #define NAC_LMD_ADJUST_CFG_LOCK_AND_SPEED_UP_START_SOV_07 (7<<1)
+ #define NAC_LMD_ADJUST_CFG_LOCK_AND_SPEED_UP_START_SOV_08 (8<<1)
+ #define NAC_LMD_ADJUST_CFG_LOCK_AND_SPEED_UP_START_SOV_09 (9<<1)
+ #define NAC_LMD_ADJUST_CFG_LOCK_AND_SPEED_UP_START_SOV_10 (10<<1)
+ #define NAC_LMD_ADJUST_CFG_LOCK_AND_SPEED_UP_START_SOV_11 (11<<1)
+ #define NAC_LMD_ADJUST_CFG_LOCK_AND_SPEED_UP_START_SOV_12 (12<<1)
+ #define NAC_LMD_ADJUST_CFG_LOCK_AND_SPEED_UP_START_SOV_13 (13<<1)
+ #define NAC_LMD_ADJUST_CFG_LOCK_AND_SPEED_UP_START_SOV_14 (14<<1)
+ #define NAC_LMD_ADJUST_CFG_LOCK_AND_SPEED_UP_START_SOV_15 (15<<1)
+#define NAC_LMD_ADJUST_CFG_DISPLAY_CC_AS_FCC (1<<5)
+
+#if defined(uG31xx_OS_ANDROID)
+
+ #define GGBX_FILE_TAG 0x5F47475F // _GG_
+ #define GGBX_FACTORY_FILE_TAG 0x5F67675F // _gg_
+
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+enum C_RATE_TABLE_VALUES {
+ C_RATE_TABLE_VALUE_0 = 50,
+ C_RATE_TABLE_VALUE_1 = 20,
+ C_RATE_TABLE_VALUE_2 = 10,
+ C_RATE_TABLE_VALUE_3 = 2
+};
+
+enum OCV_TABLE_IDX {
+ OCV_TABLE_IDX_CHARGE = 0,
+ OCV_TABLE_IDX_STAND_ALONE,
+ OCV_TABLE_IDX_100MA,
+ OCV_TABLE_IDX_COUNT,
+};
+
+#if defined (uG31xx_OS_WINDOWS)
+
+ #define SleepMiniSecond(x) Sleep(x)
+
+ #ifdef DEBUG_LOG
+
+ #define _L(X) __L(X)
+ #define __L(X) L##X
+
+ #endif
+
+#elif defined (uG31xx_OS_ANDROID)
+
+ #define SleepMiniSecond(x) mdelay(x)
+
+#endif
+
+#define CONST_PERCENTAGE (100)
+#define TIME_CONVERT_TIME_TO_MSEC (10)
+#define CONST_CONVERSION_COUNT_THRESHOLD (300)
+
+#endif
+
+/// ===========================================
+/// End of uG31xx.h
+/// ===========================================
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API.c b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API.c new file mode 100755 index 00000000..e4752190 --- /dev/null +++ b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API.c @@ -0,0 +1,2307 @@ +/// ===========================================
+/// uG31xx_API.cpp
+/// ===========================================
+
+#include "stdafx.h" //windows need this??
+#include "uG31xx_API.h"
+
+static _upi_bool_ Ug31DebugEnable;
+static _upi_s32_ dsg_charge_before_suspend;
+static _upi_s32_ delta_cap_during_suspend;
+static _upi_u8_ wakeup_predict_rsoc;
+static _upi_u8_ fix_et_at_initial_cnt;
+#if defined (uG31xx_OS_WINDOWS)
+
+ #ifdef DEBUG_LOG
+
+ unsigned int debugViewLines = 0;
+ CString debugViewFileName = _T("uG3100-1");
+ CString BackupFile;
+
+ #endif
+
+#elif defined (uG31xx_OS_ANDROID)
+
+ _upi_u32_ GetTickCount(void)
+ {
+ return jiffies_to_msecs(jiffies); //20121121/jacky
+ }
+
+ _upi_u32_ GetSysTickCount(void)
+ {
+
+ struct timeval current_tick;
+
+ do_gettimeofday(¤t_tick);
+
+ return current_tick.tv_sec * 1000 + current_tick.tv_usec/1000;
+ }
+
+#endif
+
+#if defined(uG31xx_OS_ANDROID)
+
+ int ug31_printk(int level, const char *fmt, ...)
+ {
+ #ifdef UG31XX_DEBUG_ENABLE
+ va_list args;
+ int r;
+
+ r = 0;
+ if(Ug31DebugEnable == _UPI_TRUE_)
+ {
+ va_start(args, fmt);
+ r = vprintk(fmt, args);
+ va_end(args);
+ }
+ return (r);
+ #else ///< else of UG31XX_DEBUG_ENABLE
+ return (0);
+ #endif ///< end of UG31XX_DEBUG_ENABLE
+ }
+
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+
+
+/// ===========================================
+/// uG31xx_API.cpp (VAR)
+/// ===========================================
+
+
+/* uPI ug31xx hardware control interface */
+struct ug31xx_data {
+
+ /// [AT-PM] : Following variables are used for uG31xx operation ; 11/01/2012
+ _upi_u8_ totalCellNums;
+ _upi_bool_ bFirstData;
+
+ // Global variable
+ CELL_TABLE cellTable; // data from .GGB file
+ CELL_PARAMETER cellParameter; // data from .GGB file
+ GG_BATTERY_INFO batteryInfo;
+ GG_DEVICE_INFO deviceInfo;
+ GG_USER_REG userReg; //user register 0x00 ~0x10
+ GG_USER2_REG user2Reg; //user register 0x40 ~0x4f
+ GG_TI_BQ27520 bq27520Cmd;
+
+ OtpDataType otpData;
+ MeasDataType measData;
+ CapacityDataType capData;
+ SystemDataType sysData;
+ BackupDataType backupData;
+
+ _upi_u8_ EncriptTableStatus;
+ _upi_u16_ PreviousITAve;
+};
+
+/// ===========================================
+/// End of uG31xx_API.cpp (VAR)
+/// ===========================================
+
+/**
+ * @brief upiGG_GetAlarmStatus
+ *
+ * Get alarm status
+ *
+ * @para pAlarmStatus address of alarm status
+ * @return UG_READ_DEVICE_ALARM_SUCCESS if success
+ */
+GGSTATUS upiGG_GetAlarmStatus(char *pObj, _upi_u8_ *pAlarmStatus)
+{
+ struct ug31xx_data *pUg31xx;
+
+ pUg31xx = (struct ug31xx_data *)pObj;
+
+ UpiMeasAlarmThreshold(&pUg31xx->measData);
+ *pAlarmStatus = UpiAlarmStatus(&pUg31xx->sysData);
+
+ pUg31xx->userReg.regAlarm1Status = (_upi_u8_)(pUg31xx->sysData.alarmSts & 0x00ff);
+ pUg31xx->userReg.regAlarm2Status = (_upi_u8_)(pUg31xx->sysData.alarmSts >> 8);
+
+ return (UG_READ_DEVICE_ALARM_SUCCESS);
+}
+
+// Read GG_USER_REG from device to global variable and output
+GGSTATUS upiGG_ReadAllRegister(char *pObj, GG_USER_REG* pUserReg, GG_USER2_REG* pUser2Reg)
+{
+ struct ug31xx_data *pUg31xx;
+
+ pUg31xx = (struct ug31xx_data *)pObj;
+
+ if(!API_I2C_Read(NORMAL,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_MODE,
+ sizeof(GG_USER_REG),
+ &pUg31xx->userReg.regMode))
+ {
+ return (UG_READ_REG_FAIL);
+ }
+ if(!API_I2C_Read(NORMAL,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_VBAT2_LOW,
+ sizeof(GG_USER2_REG),
+ (_upi_u8_* )&pUg31xx->user2Reg.regVbat2)) //read
+ {
+ return (UG_READ_REG_FAIL);
+ }
+
+ return (UG_READ_REG_SUCCESS);
+}
+
+// 07/04/1022/Jacky
+_upi_u16_ CalculateVoltageFromUserReg(struct ug31xx_data *pUg31xx, _upi_s16_ voltageAdcCode, _upi_s16_ curr, _upi_u16_ offsetR, _upi_u16_ deltaR)
+{
+ _upi_u16_ voltage_return;
+
+ voltage_return = (_upi_u16_)voltageAdcCode;
+ if(curr < 0) {
+ voltage_return = voltage_return + offsetR*abs(curr)/1000 + deltaR;
+ } else{
+ voltage_return = voltage_return - offsetR*abs(curr)/1000 + deltaR;
+ }
+ return (voltage_return);
+}
+
+// Read GG_USER_REG from device and calculate GG_DEVICE_INFO, then write to global variable and output
+// TODO: offsetR and deltaR will input from .GGB in the future modify
+GGSTATUS upiGG_ReadDeviceInfo(char *pObj, GG_DEVICE_INFO* pExtDeviceInfo)
+{
+ // Get current user register data
+ GGSTATUS status = UG_READ_DEVICE_INFO_SUCCESS;
+ struct ug31xx_data *pUg31xx;
+ _upi_s32_ tmp;
+ MEAS_RTN_CODE rtn;
+
+ pUg31xx = (struct ug31xx_data *)pObj;
+
+ if(!API_I2C_Read(NORMAL,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_MODE,
+ REG_AVE_RID_HIGH - REG_MODE + 1,
+ &pUg31xx->userReg.regMode))
+ {
+ status = UG_READ_ADC_FAIL;
+ }
+ else
+ {
+ if(!API_I2C_Read(NORMAL,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_INTR_STATUS,
+ REG_CTRL2 - REG_INTR_STATUS + 1,
+ &pUg31xx->userReg.regIntrStatus))
+ {
+ status = UG_READ_ADC_FAIL;
+ }
+ else
+ {
+ if(!API_I2C_Read(NORMAL,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_VBAT2_LOW,
+ sizeof(GG_USER2_REG),
+ (_upi_u8_* )&pUg31xx->user2Reg.regVbat2)) //read
+ {
+ status = UG_READ_ADC_FAIL;
+ }
+ }
+ }
+
+ /// [AT-PM] : Check IT AVE code, which should be continuous ; 12/28/2012
+ if(pUg31xx->PreviousITAve != 0)
+ {
+ tmp = (_upi_s32_)pUg31xx->PreviousITAve;
+ tmp = tmp - pUg31xx->userReg.regITAve;
+ if((tmp > 1000) || (tmp < -1000))
+ {
+ #ifdef DEBUG_LOG
+ wDebug::LOGE(debugViewFileName.GetBuffer(),debugViewLines++,_T("[%s]: IT AVE Code abnormal -> %d/%d"),
+ _L(__FUNCTION__), pUg31xx->userReg.regITAve, pUg31xx->PreviousITAve);
+ debugViewFileName.ReleaseBuffer();
+ #endif
+ pUg31xx->userReg.regITAve = pUg31xx->PreviousITAve;
+ }
+ }
+ pUg31xx->PreviousITAve = pUg31xx->userReg.regITAve;
+
+ /// [AT-PM] : Check OTP is empty or not ; 01/25/2013
+ if(pUg31xx->otpData.empty == OTP_IS_EMPTY)
+ {
+ return (UG_OTP_ISEMPTY);
+ }
+
+ /// [AT-PM] : Check product type ; 01/25/2013
+ if(pUg31xx->otpData.productType != UG31XX_PRODUCT_TYPE_0)
+ {
+ return (UG_OTP_PRODUCT_DISMATCH);
+ }
+
+ pUg31xx->measData.sysData = &pUg31xx->sysData;
+ pUg31xx->measData.otp = &pUg31xx->otpData;
+ rtn = UpiMeasurement(&pUg31xx->measData);
+ if(rtn != MEAS_RTN_PASS)
+ {
+ return ((GGSTATUS)(rtn + UG_MEAS_FAIL));
+ }
+
+ pUg31xx->deviceInfo.chargeRegister = pUg31xx->userReg.regCharge; //coulomb counter
+ pUg31xx->deviceInfo.AdcCounter = pUg31xx->userReg.regCounter; //adc1 convert counter
+ pUg31xx->deviceInfo.aveCurrentRegister = pUg31xx->userReg.regCurrentAve; //2012/07/11
+ pUg31xx->deviceInfo.current_mA = pUg31xx->measData.curr;
+ pUg31xx->deviceInfo.AveCurrent_mA = pUg31xx->measData.curr;
+ pUg31xx->deviceInfo.IT = pUg31xx->measData.intTemperature;
+ pUg31xx->deviceInfo.ET = pUg31xx->measData.extTemperature;
+ pUg31xx->deviceInfo.v1_mV = pUg31xx->measData.bat1Voltage;
+ pUg31xx->deviceInfo.vCell1_mV = pUg31xx->measData.bat1Voltage;
+ pUg31xx->deviceInfo.vBat1Average_mV = CalculateVoltageFromUserReg(pUg31xx,
+ pUg31xx->measData.bat1Voltage,
+ pUg31xx->measData.curr,
+ pUg31xx->cellParameter.offsetR,
+ pUg31xx->cellParameter.deltaR);
+ pUg31xx->deviceInfo.voltage_mV = pUg31xx->deviceInfo.vBat1Average_mV;
+ pUg31xx->deviceInfo.chargeData_mAh = pUg31xx->measData.deltaCap;
+
+ pUg31xx->sysData.otpData = &pUg31xx->otpData;
+ UpiCalibrationOsc(&pUg31xx->sysData); //osc calibration
+
+ UpiAdcStatus(&pUg31xx->sysData);
+
+ if(fix_et_at_initial_cnt > 0)
+ {
+ fix_et_at_initial_cnt = fix_et_at_initial_cnt - 1;
+
+ if(pUg31xx->deviceInfo.ET > UG31XX_MAX_TEMPERATURE_BEFORE_READY)
+ {
+ pUg31xx->deviceInfo.ET = UG31XX_MAX_TEMPERATURE_BEFORE_READY;
+ }
+ if(pUg31xx->deviceInfo.ET < UG31XX_MIN_TEMPERATURE_BEFORE_READY)
+ {
+ pUg31xx->deviceInfo.ET = UG31XX_MIN_TEMPERATURE_BEFORE_READY;
+ }
+ }
+ memcpy(pExtDeviceInfo, &pUg31xx->deviceInfo, sizeof(GG_DEVICE_INFO));
+ return (status);
+}
+
+void dumpInfo(struct ug31xx_data *pUg31xx)
+{
+ int i=0;
+ int j;
+ int k;
+
+/// dump parameter setting
+ UG31_LOGI("/// 2012/12/16/1611====================================\n");
+ UG31_LOGI("/// CELL_PARAMETER\n");
+ UG31_LOGI("/// ====================================2012/12/16/1611\n");
+ UG31_LOGI("Total struct size: %d\n", pUg31xx->cellParameter.totalSize);
+ UG31_LOGI("firmware version: 0x%02X\n", pUg31xx->cellParameter.fw_ver)
+ UG31_LOGI("customer: %s\n", pUg31xx->cellParameter.customer)
+ UG31_LOGI("project: %s\n", pUg31xx->cellParameter.project)
+ UG31_LOGI("ggb version: 0x%02X\n", pUg31xx->cellParameter.ggb_version)
+ UG31_LOGI("customer self-define: %s\n", pUg31xx->cellParameter.customerSelfDef)
+ UG31_LOGI("project self-define: %s\n", pUg31xx->cellParameter.projectSelfDef)
+ UG31_LOGI("cell type : 0x%04X\n", pUg31xx->cellParameter.cell_type_code);
+ UG31_LOGI("ICType: 0x%02X\n", pUg31xx->cellParameter.ICType);
+ UG31_LOGI("gpio1: 0x%02X\n", pUg31xx->cellParameter.gpio1);
+ UG31_LOGI("gpio2: 0x%02X\n", pUg31xx->cellParameter.gpio2);
+ UG31_LOGI("gpio34: 0x%02X\n", pUg31xx->cellParameter.gpio34);
+ UG31_LOGI("Chop control ?? : 0x%02X\n", pUg31xx->cellParameter.chopCtrl);
+ UG31_LOGI("ADC1 offset ?? : %d\n", pUg31xx->cellParameter.adc1Offset);
+ UG31_LOGI("Cell number ?? : %d\n", pUg31xx->cellParameter.cellNumber);
+ UG31_LOGI("Assign cell one to: %d\n", pUg31xx->cellParameter.assignCellOneTo);
+ UG31_LOGI("Assign cell two to: %d\n", pUg31xx->cellParameter.assignCellTwoTo);
+ UG31_LOGI("Assign cell three to: %d\n", pUg31xx->cellParameter.assignCellThreeTo);
+ UG31_LOGI("I2C Address: : 0x%02X\n", pUg31xx->cellParameter.i2cAddress);
+ UG31_LOGI("I2C 10bit address: : 0x%02X\n", pUg31xx->cellParameter.tenBitAddressMode);
+ UG31_LOGI("I2C high speed: 0x%02X\n", pUg31xx->cellParameter.highSpeedMode);
+ UG31_LOGI("clock(kHz): %d\n", pUg31xx->cellParameter.clock);
+ UG31_LOGI("RSense(m ohm): %d\n", pUg31xx->cellParameter.rSense);
+ UG31_LOGI("ILMD(mAH) ?? : %d\n", pUg31xx->cellParameter.ILMD);
+ UG31_LOGI("EDV1 Voltage(mV): %d\n", pUg31xx->cellParameter.edv1Voltage);
+ UG31_LOGI("Standby current ?? : %d\n", pUg31xx->cellParameter.standbyCurrent);
+ UG31_LOGI("TP Current(mA)?? : %d\n", pUg31xx->cellParameter.TPCurrent);
+ UG31_LOGI("TP Voltage(mV)?? : %d\n", pUg31xx->cellParameter.TPVoltage);
+ UG31_LOGI("TP Time ?? : %d\n", pUg31xx->cellParameter.TPTime);
+ UG31_LOGI("Offset R ?? : %d\n", pUg31xx->cellParameter.offsetR);
+ UG31_LOGI("Delta R ?? : %d\n", pUg31xx->cellParameter.deltaR);
+ UG31_LOGI("max delta Q(%%) ?? : %d\n", pUg31xx->cellParameter.maxDeltaQ);
+ UG31_LOGI("TP Bypass Current ?? : %d\n", pUg31xx->cellParameter.TpBypassCurrent); //20121029/Jacky
+ UG31_LOGI("time interval (s) : %d\n", pUg31xx->cellParameter.timeInterval);
+ UG31_LOGI("ADC1 pgain: %d\n", pUg31xx->cellParameter.adc1_pgain);
+ UG31_LOGI("ADC1 ngain: %d\n", pUg31xx->cellParameter.adc1_ngain);
+ UG31_LOGI("ADC1 pos. offset: %d\n", pUg31xx->cellParameter.adc1_pos_offset);
+ UG31_LOGI("ADC2 gain: %d\n", pUg31xx->cellParameter.adc2_gain);
+ UG31_LOGI("ADC2 offset: %d\n", pUg31xx->cellParameter.adc2_offset);
+ UG31_LOGI("R ?? : %d\n", pUg31xx->cellParameter.R);
+ for (i=0; i<sizeof(pUg31xx->cellParameter.rtTable)/sizeof(_upi_u16_); i++) {
+ UG31_LOGI("RTTable[%02d]: %d\n", i, pUg31xx->cellParameter.rtTable[i]);
+ }
+ for (i=0; i<sizeof(pUg31xx->cellParameter.SOV_TABLE)/sizeof(_upi_u16_); i++) {
+ UG31_LOGI("SOV Table[%02d]: %d\n", i, pUg31xx->cellParameter.SOV_TABLE[i]/10);
+ }
+ UG31_LOGI("ADC d1: %d\n", pUg31xx->cellParameter.adc_d1);
+ UG31_LOGI("ADC d2: %d\n", pUg31xx->cellParameter.adc_d2);
+ UG31_LOGI("ADC d3: %d\n", pUg31xx->cellParameter.adc_d3);
+ UG31_LOGI("ADC d4: %d\n", pUg31xx->cellParameter.adc_d4);
+ UG31_LOGI("ADC d5: %d\n", pUg31xx->cellParameter.adc_d5);
+ UG31_LOGI("NacLmdAdjustCfg: %d\n", pUg31xx->cellParameter.NacLmdAdjustCfg); //20121124
+
+ /// [AT-PM] : Dump NAC table ; 01/27/2013
+ i = 0;
+ while(i < TEMPERATURE_NUMS)
+ {
+ j = 0;
+ while(j < C_RATE_NUMS)
+ {
+ k = 0;
+ while(k < SOV_NUMS)
+ {
+ UG31_LOGI("NAC Table [%d][%d][%d] = %d\n", i, j, k, pUg31xx->cellTable.CELL_NAC_TABLE[i][j][k]);
+ k = k + 1;
+ }
+ j = j + 1;
+ }
+ i = i + 1;
+ }
+}
+
+/// count Time Elapsed in suspend/power Off
+_upi_u32_ CountTotalTime(_upi_u32_ savedTimeTag)
+{
+ _upi_u32_ totalTime;
+ _upi_u32_ currentTime;
+
+ totalTime = 0;
+#if defined(uG31xx_OS_ANDROID)
+ currentTime = GetSysTickCount();
+#else ///< else of defined(uG31xx_OS_ANDROID)
+ currentTime = GetTickCount();
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+ if(currentTime > savedTimeTag)
+ {
+ totalTime = currentTime - savedTimeTag; //count the delta Time
+ }
+ else
+ {
+ totalTime = currentTime;
+ }
+ UG31_LOGE("[%s]current time/save Time/totalTime = %d/%d/%d \n",
+ __func__,
+ currentTime,
+ savedTimeTag,
+ totalTime
+ );
+ return(totalTime);
+}
+
+#define MS_IN_A_DAY (86400000)
+#define INIT_CAP_FROM_CC_FACTOR (10)
+
+/**
+ * @brief CheckInitCapacityFromCC
+ *
+ * Check the initial capacity from coulomb counter with time interval
+ * The delta RSOC should be less than n days x 0.1%
+ *
+ * @para pUg31xx address of struct ug31xx_data
+ * @para lastRsoc last RSOC before employ coulomb counter value
+ * @return _UPI_NULL_
+ */
+void CheckInitCapacityFromCC(struct ug31xx_data *pUg31xx, _sys_u8_ lastRsoc)
+{
+ _upi_s32_ tmp32;
+
+ tmp32 = (_upi_s16_)pUg31xx->sysData.rsocFromIC;
+ tmp32 = tmp32 - lastRsoc;
+ if(tmp32 < 0)
+ {
+ tmp32 = (_upi_s32_)CountTotalTime(pUg31xx->sysData.timeTagFromIC)/MS_IN_A_DAY/INIT_CAP_FROM_CC_FACTOR;
+ tmp32 = tmp32*(-1) + lastRsoc;
+ if(tmp32 < 0)
+ {
+ tmp32 = 1;
+ }
+ UG31_LOGI("[%s]: RSOC should be limited to %d (%d <-> %d)\n", __func__,
+ tmp32, lastRsoc, pUg31xx->sysData.rsocFromIC);
+ pUg31xx->sysData.rsocFromIC = (_sys_u8_)tmp32;
+ tmp32 = tmp32*pUg31xx->sysData.fccFromIC/CONST_PERCENTAGE;
+ pUg31xx->sysData.rmFromIC = (_sys_u16_)tmp32;
+ }
+}
+
+#define MAX_DELTA_RSOC_THRESHOLD_FOR_WAKEUP (25)
+#define MIN_DELTA_RSOC_THRESHOLD_FOR_WAKEUP (-25)
+#define MAX_DELTA_TIME_THRESHOLD_FOR_WAKEUP (MS_IN_A_DAY*5)
+#define MAX_DELTA_RSOC_THRESHOLD_FOR_TABLE (0)
+#define MIN_DELTA_RSOC_THRESHOLD_FOR_TABLE (-5)
+
+// Read GGB file and initial
+#ifdef uG31xx_OS_WINDOWS
+GGSTATUS upiGG_Initial(char **pObj,const wchar_t* GGBFilename,const wchar_t* OtpFileName, const wchar_t* BackupFileName, char ForceReset, unsigned char MaxETFixCnt)
+#elif defined(uG31xx_OS_ANDROID)
+GGSTATUS upiGG_Initial(char **pObj,GGBX_FILE_HEADER *pGGBXBuf, char ForceReset, unsigned char MaxETFixCnt)
+#endif
+{
+ _upi_bool_ firstPowerOn;
+ struct ug31xx_data *pUg31xx;
+ SYSTEM_RTN_CODE rtn;
+ _upi_s16_ deltaQC = 0;
+ _upi_s32_ tmp32;
+ MEAS_RTN_CODE rtnMeas;
+ _sys_u8_ lastRsocFromIC;
+
+ UG31_LOGI("[%s]: uG31xx API Version = %d.%08x.%04x\n", __func__,
+ UG31XX_API_MAIN_VERSION, UG31XX_API_OTP_VERSION, UG31XX_API_SUB_VERSION);
+
+ Ug31DebugEnable = _UPI_TRUE_;
+ firstPowerOn = _UPI_FALSE_;
+ #if defined(uG31xx_OS_ANDROID)
+ #ifdef uG31xx_BOOT_LOADER
+ *pObj = (char *)malloc(sizeof(struct ug31xx_data));
+ #else ///< else of uG31xx_BOOT_LOADER
+ *pObj = (char *)kmalloc(sizeof(struct ug31xx_data),GFP_KERNEL);
+ #endif ///< end of uG31xx_BOOT_LOADER
+ #else ///< else of defined(uG31xx_OS_ANDROID)
+ *pObj = (char *)malloc(sizeof(struct ug31xx_data));
+ #endif ///< end of defined(uG31xx_OS_ANDROID)
+ pUg31xx = (struct ug31xx_data *)(*pObj);
+
+ memset(pUg31xx, 0, sizeof(struct ug31xx_data));
+
+ #ifdef uG31xx_OS_WINDOWS
+ pUg31xx->sysData.ggbFilename = GGBFilename;
+ pUg31xx->sysData.otpFileName = OtpFileName;
+ pUg31xx->sysData.backupFileName = BackupFileName;
+ BackupFile = BackupFileName;
+ #elif defined(uG31xx_OS_ANDROID)
+ pUg31xx->sysData.ggbXBuf = pGGBXBuf;
+ #endif
+ pUg31xx->sysData.ggbParameter = &pUg31xx->cellParameter;
+ pUg31xx->sysData.ggbCellTable = &pUg31xx->cellTable;
+ rtn = UpiInitSystemData(&pUg31xx->sysData);
+ if(rtn != SYSTEM_RTN_PASS)
+ {
+ if(rtn == SYSTEM_RTN_READ_GGB_FAIL)
+ {
+ return (UG_READ_GGB_FAIL);
+ }
+ return (UG_NOT_DEF);
+ }
+
+ // Initial I2C and Open HID
+ #if defined(uG31xx_OS_WINDOWS)
+
+ if(!API_I2C_Init(pUg31xx->cellParameter.clock, pUg31xx->cellParameter.i2cAddress))
+ {
+ return UG_I2C_INIT_FAIL;
+ }
+
+ #endif ///< end of defined(uG31xx_OS_WINDOWS)
+ UpiLoadBatInfoFromIC(&pUg31xx->sysData);
+
+ pUg31xx->capData.ggbTable = &pUg31xx->cellTable;
+ pUg31xx->capData.ggbParameter = &pUg31xx->cellParameter;
+ pUg31xx->capData.measurement = &pUg31xx->measData;
+ UpiInitNacTable(&pUg31xx->capData);
+
+ /// [AT-PM] : Check IC is active or not ; 01/28/2013
+ #ifdef UG31XX_RESET_DATABASE
+ firstPowerOn = _UPI_TRUE_;
+ #else ///< else of UG31XX_RESET_DATABASE
+ if(ForceReset == 0)
+ {
+ firstPowerOn = UpiCheckICActive();
+ }
+ else
+ {
+ firstPowerOn = _UPI_TRUE_;
+ UG31_LOGI("[%s]: Force to reset uG3105 driver. (%d)\n", __func__, ForceReset);
+ }
+ #endif ///< end of UG31XX_RESET_DATABASE
+ if(firstPowerOn == _UPI_TRUE_)
+ {
+ UG31_LOGE("[%s]#####firstPowerOn= %d \n",__func__,firstPowerOn);
+ rtn = UpiActiveUg31xx();
+ if(rtn != SYSTEM_RTN_PASS)
+ {
+ return (UG_ACTIVE_FAIL);
+ }
+
+ UpiSetupAdc(&pUg31xx->sysData);
+ UpiSetupSystem(&pUg31xx->sysData);
+
+ pUg31xx->backupData.icDataAvailable = BACKUP_BOOL_FALSE;
+ }
+ else
+ {
+ UG31_LOGE("[%s]#####Last time tag = %d\n", __func__, pUg31xx->sysData.timeTagFromIC);
+ pUg31xx->measData.lastTimeTick = pUg31xx->sysData.timeTagFromIC;
+ pUg31xx->measData.lastDeltaCap = pUg31xx->sysData.deltaCapFromIC;
+ pUg31xx->measData.adc1ConvertTime = pUg31xx->sysData.adc1ConvTime;
+
+ pUg31xx->backupData.icDataAvailable = BACKUP_BOOL_TRUE;
+ }
+
+ /// [AT-PM] : Load OTP data ; 01/31/2013
+ API_I2C_Read(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, OTP1_BYTE1, OTP1_SIZE, pUg31xx->otpData.otp1);
+ API_I2C_Read(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, OTP2_BYTE1, OTP2_SIZE, pUg31xx->otpData.otp2);
+ API_I2C_Read(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, OTP6_BYTE1, OTP3_SIZE, pUg31xx->otpData.otp3);
+ UpiConvertOtp(&pUg31xx->otpData);
+
+ /// [AT-PM] : Check product type ; 01/25/2013
+ if(pUg31xx->otpData.productType != UG31XX_PRODUCT_TYPE_0)
+ {
+ return (UG_OTP_PRODUCT_DISMATCH);
+ }
+
+ if(firstPowerOn == _UPI_TRUE_)
+ {
+ SleepMiniSecond(1000);
+ fix_et_at_initial_cnt = MaxETFixCnt;
+ }
+
+ UG31_LOGI("[%s]: Do measurement\n", __func__);
+
+ pUg31xx->measData.sysData = &pUg31xx->sysData;
+ pUg31xx->measData.otp = &pUg31xx->otpData;
+ rtnMeas = UpiMeasurement(&pUg31xx->measData);
+ if(rtnMeas != MEAS_RTN_PASS)
+ {
+ return ((GGSTATUS)(rtnMeas + UG_MEAS_FAIL));
+ }
+ if(firstPowerOn == _UPI_TRUE_)
+ {
+ /// [AT-PM] : Initialize alarm function ; 04/08/2013
+ UpiMeasAlarmThreshold(&pUg31xx->measData);
+ UpiInitAlarm(&pUg31xx->sysData);
+ }
+
+ UG31_LOGI("[%s]: Current Status = %d mV / %d mA / %d 0.1oC\n", __func__,
+ pUg31xx->measData.bat1Voltage, pUg31xx->measData.curr, pUg31xx->measData.intTemperature);
+
+ UpiInitCapacity(&pUg31xx->capData);
+ pUg31xx->capData.rsoc = CalculateRsoc(pUg31xx->capData.rm, pUg31xx->capData.fcc);
+ if((firstPowerOn == _UPI_TRUE_) || (pUg31xx->sysData.fccFromIC == 0))
+ {
+ pUg31xx->batteryInfo.NAC = (_upi_u16_)pUg31xx->capData.rm;
+ pUg31xx->batteryInfo.LMD = (_upi_u16_)pUg31xx->capData.fcc;
+ pUg31xx->batteryInfo.RSOC = (_upi_u16_)pUg31xx->capData.rsoc;
+ UG31_LOGI("[%s]: Init data from table -> %d/%d = %d\n", __func__,
+ pUg31xx->batteryInfo.NAC, pUg31xx->batteryInfo.LMD, pUg31xx->batteryInfo.RSOC);
+ }
+ else
+ {
+ pUg31xx->capData.tableUpdateIdx = pUg31xx->sysData.tableUpdateIdxFromIC;
+
+ /// [AT-PM] : Calculate the RSOC/NAC/LMD from coulomb counter ; 01/27/2013
+ deltaQC = (_upi_s16_)pUg31xx->measData.stepCap;
+ pUg31xx->sysData.voltage = pUg31xx->measData.bat1Voltage;
+ lastRsocFromIC = pUg31xx->sysData.rsocFromIC;
+ UpiUpdateBatInfoFromIC(&pUg31xx->sysData, deltaQC);
+ if(CountTotalTime(pUg31xx->sysData.timeTagFromIC) > MAX_DELTA_TIME_THRESHOLD_FOR_WAKEUP)
+ {
+ /// [AT-PM] : Check the data accuracy ; 01/27/2013
+ deltaQC = (_upi_s16_)pUg31xx->sysData.rsocFromIC;
+ deltaQC = deltaQC - pUg31xx->capData.rsoc;
+ if((deltaQC > MAX_DELTA_RSOC_THRESHOLD_FOR_WAKEUP) || (deltaQC < MIN_DELTA_RSOC_THRESHOLD_FOR_WAKEUP))
+ {
+ deltaQC = (_upi_s16_)pUg31xx->capData.rsoc;
+ deltaQC = deltaQC - pUg31xx->sysData.rsocFromICBackup;
+ if((deltaQC > MAX_DELTA_RSOC_THRESHOLD_FOR_TABLE) || (deltaQC < MIN_DELTA_RSOC_THRESHOLD_FOR_TABLE))
+ {
+ if(deltaQC > MAX_DELTA_RSOC_THRESHOLD_FOR_TABLE)
+ {
+ deltaQC = MAX_DELTA_RSOC_THRESHOLD_FOR_TABLE;
+ }
+ if(deltaQC < MIN_DELTA_RSOC_THRESHOLD_FOR_TABLE)
+ {
+ deltaQC = MIN_DELTA_RSOC_THRESHOLD_FOR_TABLE;
+ }
+ deltaQC = deltaQC + pUg31xx->sysData.rsocFromICBackup;
+ pUg31xx->capData.rsoc = (_cap_u8_)deltaQC;
+
+ }
+ pUg31xx->capData.fcc = pUg31xx->sysData.fccFromIC;
+ tmp32 = (_upi_s32_)pUg31xx->capData.fcc;
+ tmp32 = tmp32*pUg31xx->capData.rsoc/CONST_PERCENTAGE;
+ pUg31xx->capData.rm = (_cap_u16_)tmp32;
+ UG31_LOGI("[%s]: Coulomb counter is not available -> Use data from table (%d/%d = %d)\n", __func__,
+ pUg31xx->capData.rm, pUg31xx->capData.fcc, pUg31xx->capData.rsoc);
+ }
+ else
+ {
+ CheckInitCapacityFromCC(pUg31xx, lastRsocFromIC);
+ pUg31xx->capData.rm = (_cap_u16_)pUg31xx->sysData.rmFromIC;
+ pUg31xx->capData.fcc = (_cap_u16_)pUg31xx->sysData.fccFromIC;
+ pUg31xx->capData.rsoc = (_cap_u8_)pUg31xx->sysData.rsocFromIC;
+ UG31_LOGI("[%s]: Use data from coulomb counter (%d/%d = %d)\n", __func__,
+ pUg31xx->capData.rm, pUg31xx->capData.fcc, pUg31xx->capData.rsoc);
+ }
+ }
+ else
+ {
+ CheckInitCapacityFromCC(pUg31xx, lastRsocFromIC);
+ pUg31xx->capData.rm = (_cap_u16_)pUg31xx->sysData.rmFromIC;
+ pUg31xx->capData.fcc = (_cap_u16_)pUg31xx->sysData.fccFromIC;
+ pUg31xx->capData.rsoc = (_cap_u8_)pUg31xx->sysData.rsocFromIC;
+ UG31_LOGI("[%s]: Use data from coulomb counter (%d/%d = %d)\n", __func__,
+ pUg31xx->capData.rm, pUg31xx->capData.fcc, pUg31xx->capData.rsoc);
+ }
+ pUg31xx->batteryInfo.NAC = (_upi_u16_)pUg31xx->capData.rm;
+ pUg31xx->batteryInfo.LMD = (_upi_u16_)pUg31xx->capData.fcc;
+ pUg31xx->batteryInfo.RSOC = (_upi_u16_)pUg31xx->capData.rsoc;
+ UpiResetCoulombCounter(&pUg31xx->measData);
+ }
+ UpiInitDsgCharge(&pUg31xx->capData);
+
+ /// [AT-PM] : Save battery information to IC ; 01/31/2013
+ pUg31xx->sysData.rmFromIC = pUg31xx->batteryInfo.NAC;
+ pUg31xx->sysData.fccFromIC = pUg31xx->batteryInfo.LMD;
+ pUg31xx->sysData.rsocFromIC = (_sys_u8_)pUg31xx->batteryInfo.RSOC;
+ pUg31xx->sysData.tableUpdateIdxFromIC = pUg31xx->capData.tableUpdateIdx;
+ pUg31xx->sysData.deltaCapFromIC = pUg31xx->measData.lastDeltaCap;
+ pUg31xx->sysData.adc1ConvTime = pUg31xx->measData.adc1ConvertTime;
+ UpiSaveBatInfoTOIC(&pUg31xx->sysData);
+
+ UG31_LOGI("[%s]: Driver version = %d\n", __func__, UG31XX_API_RELEASE_VERSION);
+ pUg31xx->backupData.targetFileVer = UG31XX_API_RELEASE_VERSION;
+ pUg31xx->backupData.backupDataIdx = BACKUP_MAX_LOG_SUSPEND_DATA;
+ while(pUg31xx->backupData.backupDataIdx)
+ {
+ pUg31xx->backupData.backupDataIdx = pUg31xx->backupData.backupDataIdx - 1;
+ pUg31xx->backupData.logData[pUg31xx->backupData.backupDataIdx] = (BackupSuspendDataType *)kmalloc(sizeof(BackupSuspendDataType), GFP_KERNEL);
+ memset(pUg31xx->backupData.logData[pUg31xx->backupData.backupDataIdx], 0, sizeof(BackupSuspendDataType));
+ }
+
+ dumpInfo(pUg31xx);
+ return (UG_INIT_SUCCESS);
+}
+
+GGSTATUS upiGG_PreSuspend(char *pObj)
+{
+ GGSTATUS Status = UG_READ_DEVICE_INFO_SUCCESS;
+ struct ug31xx_data *pUg31xx;
+
+ pUg31xx = (struct ug31xx_data *)pObj;
+ UG31_LOGE("[%s]:*****upiGG_PreSuspend *****\n", __func__);
+
+ UpiResetCoulombCounter(&pUg31xx->measData);
+
+ /// [AT-PM] : Save battery information to IC ; 01/31/2013
+ pUg31xx->sysData.rmFromIC = pUg31xx->batteryInfo.NAC;
+ pUg31xx->sysData.fccFromIC = pUg31xx->batteryInfo.LMD;
+ pUg31xx->sysData.rsocFromIC = (_sys_u8_)pUg31xx->batteryInfo.RSOC;
+ pUg31xx->sysData.tableUpdateIdxFromIC = pUg31xx->capData.tableUpdateIdx;
+ pUg31xx->sysData.deltaCapFromIC = pUg31xx->measData.lastDeltaCap;
+ pUg31xx->sysData.adc1ConvTime = pUg31xx->measData.adc1ConvertTime;
+ pUg31xx->sysData.voltage = pUg31xx->measData.bat1Voltage;
+ UpiUpdateBatInfoFromIC(&pUg31xx->sysData, (_sys_s16_) pUg31xx->measData.stepCap);
+ UpiSaveBatInfoTOIC(&pUg31xx->sysData);
+
+ /// [AT-PM] : Save dsgCharge before suspend ; 08/14/2013
+ dsg_charge_before_suspend = (_upi_s32_)pUg31xx->capData.dsgCharge;
+ if(dsg_charge_before_suspend < 0)
+ {
+ dsg_charge_before_suspend = (_upi_s32_)pUg31xx->sysData.fccFromIC;
+ dsg_charge_before_suspend = dsg_charge_before_suspend - pUg31xx->sysData.rmFromIC;
+ }
+ UG31_LOGI("[%s]: dsg_charge_before_suspend = %d\n", __func__, dsg_charge_before_suspend);
+
+ pUg31xx->backupData.capData = &pUg31xx->capData;
+ pUg31xx->backupData.sysData = &pUg31xx->sysData;
+ pUg31xx->backupData.measData = &pUg31xx->measData;
+ UpiUpdateSuspendData(&pUg31xx->backupData, _UPI_FALSE_);
+
+ /// [AT-PM] : Set CAP_STS_NAC_UPDATE_DISQ ; 11/08/2013
+ pUg31xx->capData.status = pUg31xx->capData.status | 0x0400;
+
+ #ifdef DEBUG_LOG
+ wDebug::LOGE(debugViewFileName.GetBuffer(),debugViewLines++,_T("[%s]:Reset Coulumb counter"), _L(__FUNCTION__));
+ debugViewFileName.ReleaseBuffer();
+ #endif
+ return(Status);
+}
+
+//====================================================
+//API Call to get the Battery Capacity
+// charge full condition:
+// if((Iav <TP current) && (Voltage >= TP Voltage))
+//====================================================
+void upiGG_ReadCapacity(char *pObj, GG_CAPACITY *pExtCapacity)
+{
+ struct ug31xx_data *pUg31xx;
+
+ pUg31xx = (struct ug31xx_data *)pObj;
+
+ pUg31xx->capData.ggbTable = &pUg31xx->cellTable;
+ pUg31xx->capData.ggbParameter = &pUg31xx->cellParameter;
+ pUg31xx->capData.measurement = &pUg31xx->measData;
+ UpiReadCapacity(&pUg31xx->capData);
+ pUg31xx->capData.tableUpdateDelayCnt = 1;
+ pUg31xx->batteryInfo.NAC = (_upi_u16_)pUg31xx->capData.rm;
+ pUg31xx->batteryInfo.LMD = (_upi_u16_)pUg31xx->capData.fcc;
+ pUg31xx->batteryInfo.RSOC = (_upi_u16_)pUg31xx->capData.rsoc;
+
+ // Output result by assign value from global variable
+ pExtCapacity->LMD = pUg31xx->batteryInfo.LMD;
+ pExtCapacity->NAC = pUg31xx->batteryInfo.NAC;
+ pExtCapacity->RSOC = pUg31xx->batteryInfo.RSOC;
+
+ /// [AT-PM] : If fully charged and keeps charging, reset coulomb counter ; 02/11/2013
+ if((pUg31xx->batteryInfo.RSOC == 100) && (pUg31xx->measData.curr >= pUg31xx->cellParameter.standbyCurrent))
+ {
+ pUg31xx->measData.sysData = &pUg31xx->sysData;
+ pUg31xx->measData.otp = &pUg31xx->otpData;
+ UpiResetCoulombCounter(&pUg31xx->measData);
+ }
+
+ /// [AT-PM] : Save battery information to IC ; 01/31/2013
+ pUg31xx->sysData.rmFromIC = pUg31xx->batteryInfo.NAC;
+ pUg31xx->sysData.fccFromIC = pUg31xx->batteryInfo.LMD;
+ pUg31xx->sysData.rsocFromIC = (_sys_u8_)pUg31xx->batteryInfo.RSOC;
+ pUg31xx->sysData.tableUpdateIdxFromIC = pUg31xx->capData.tableUpdateIdx;
+ pUg31xx->sysData.deltaCapFromIC = pUg31xx->measData.lastDeltaCap;
+ pUg31xx->sysData.adc1ConvTime = pUg31xx->measData.adc1ConvertTime;
+ UpiSaveBatInfoTOIC(&pUg31xx->sysData);
+
+ /// [AT-PM] : Check data from IC and file ; 06/19/2013
+ if((pUg31xx->backupData.backupFileSts == BACKUP_FILE_STS_EXIST) &&
+ (pUg31xx->backupData.icDataAvailable == BACKUP_BOOL_FALSE))
+ {
+ pUg31xx->backupData.icDataAvailable = BACKUP_BOOL_TRUE;
+ pUg31xx->sysData.timeTagFromIC = pUg31xx->measData.lastTimeTick;
+ pUg31xx->sysData.deltaCapFromIC = pUg31xx->measData.lastDeltaCap;
+ UpiSaveBatInfoTOIC(&pUg31xx->sysData);
+ pUg31xx->capData.rm = (_cap_u16_)pUg31xx->sysData.rmFromIC;
+ pUg31xx->capData.fcc = (_cap_u16_)pUg31xx->sysData.fccFromIC;
+ pUg31xx->capData.rsoc = (_cap_u8_)pUg31xx->sysData.rsocFromIC;
+ pUg31xx->capData.tableUpdateIdx = pUg31xx->sysData.tableUpdateIdxFromIC;
+ UpiSaveNacTable(&pUg31xx->capData);
+ pUg31xx->batteryInfo.NAC = (_upi_u16_)pUg31xx->capData.rm;
+ pUg31xx->batteryInfo.LMD = (_upi_u16_)pUg31xx->capData.fcc;
+ pUg31xx->batteryInfo.RSOC = (_upi_u16_)pUg31xx->capData.rsoc;
+ UpiInitDsgCharge(&pUg31xx->capData);
+ pUg31xx->measData.adc1ConvertTime = pUg31xx->sysData.adc1ConvTime;
+ UG31_LOGI("[%s]: Refresh driver information from file\n", __func__);
+ }
+
+ if(pUg31xx->backupData.icDataAvailable == BACKUP_BOOL_TRUE)
+ {
+ pExtCapacity->Ready = UG_CAP_DATA_READY;
+ }
+ else
+ {
+ pExtCapacity->Ready = UG_CAP_DATA_NOT_READY;
+ }
+}
+
+_upi_u8_ upiGG_CheckBackupFile(char *pObj)
+{
+ struct ug31xx_data *pUg31xx;
+
+ pUg31xx = (struct ug31xx_data *)pObj;
+
+ pUg31xx->capData.ggbTable = &pUg31xx->cellTable;
+ pUg31xx->capData.ggbParameter = &pUg31xx->cellParameter;
+ pUg31xx->capData.measurement = &pUg31xx->measData;
+ pUg31xx->measData.sysData = &pUg31xx->sysData;
+ pUg31xx->measData.otp = &pUg31xx->otpData;
+
+ pUg31xx->sysData.rmFromIC = pUg31xx->batteryInfo.NAC;
+ pUg31xx->sysData.fccFromIC = pUg31xx->batteryInfo.LMD;
+ pUg31xx->sysData.rsocFromIC = (_sys_u8_)pUg31xx->batteryInfo.RSOC;
+ pUg31xx->sysData.tableUpdateIdxFromIC = pUg31xx->capData.tableUpdateIdx;
+ pUg31xx->sysData.deltaCapFromIC = pUg31xx->measData.lastDeltaCap;
+ pUg31xx->sysData.adc1ConvTime = pUg31xx->measData.adc1ConvertTime;
+
+ /// [AT-PM] : Backup data to file routine ; 02/21/2013
+ pUg31xx->backupData.capData = &pUg31xx->capData;
+ pUg31xx->backupData.sysData = &pUg31xx->sysData;
+ pUg31xx->backupData.measData = &pUg31xx->measData;
+ #ifdef uG31xx_OS_WINDOWS
+ pUg31xx->sysData.backupFileName = BackupFile.GetBuffer();
+ #endif ///< end of uG31xx_OS_WINDOWS
+ UpiBackupData(&pUg31xx->backupData);
+ #ifdef uG31xx_OS_WINDOWS
+ BackupFile.ReleaseBuffer();
+ #endif ///< end of uG31xx_OS_WINDOWS
+
+ if(pUg31xx->backupData.backupFileSts == BACKUP_FILE_STS_CHECKING)
+ {
+ UG31_LOGI("[%s]: Backup File check fail\n", __func__);
+ return (UPI_CHECK_BACKUP_FILE_FAIL);
+ }
+ if(pUg31xx->backupData.backupFileSts == BACKUP_FILE_STS_COMPARE)
+ {
+ UG31_LOGI("[%s]: Backup File Version = %d\n", __func__, pUg31xx->backupData.backupFileVer);
+ if(pUg31xx->backupData.backupFileVer != UG31XX_API_RELEASE_VERSION)
+ {
+ return (UPI_CHECK_BACKUP_FILE_MISMATCH);
+ }
+ return (UPI_CHECK_BACKUP_FILE_EXIST);
+ }
+ return (UPI_CHECK_BACKUP_FILE_FAIL);
+}
+
+//system wakeup
+// to read back the preSuspend information from uG31xx RAM area
+// re-calculate the deltaQmax( the charge/discharge) during the suspend time
+GGSTATUS upiGG_Wakeup(char *pObj, int dc_in_before)
+{
+ GGSTATUS Status = UG_READ_DEVICE_INFO_SUCCESS;
+ _upi_s16_ deltaQC = 0; //coulomb counter's deltaQ
+ _upi_u32_ totalTime;
+ _upi_s32_ tmp32;
+ MEAS_RTN_CODE rtn;
+ _upi_u16_ rmBefore;
+ _upi_u16_ fccBefore;
+ _upi_u16_ rsocBefore;
+
+ struct ug31xx_data *pUg31xx;
+ pUg31xx = (struct ug31xx_data *)pObj;
+
+ ///Load the Saved time tag NAC LMD
+ UpiLoadBatInfoFromIC(&pUg31xx->sysData);
+ rmBefore = (_upi_u16_)pUg31xx->sysData.rmFromIC;
+ fccBefore = (_upi_u16_)pUg31xx->sysData.fccFromIC;
+ rsocBefore = (_upi_u16_)pUg31xx->sysData.rsocFromIC;
+
+ /// Count total Time
+ totalTime = CountTotalTime(pUg31xx->sysData.timeTagFromIC);
+ /// count the deltaQ during suspend
+ pUg31xx->measData.sysData = &pUg31xx->sysData;
+ pUg31xx->measData.otp = &pUg31xx->otpData;
+ pUg31xx->measData.lastTimeTick = pUg31xx->sysData.timeTagFromIC;
+ pUg31xx->measData.lastDeltaCap = pUg31xx->sysData.deltaCapFromIC;
+ pUg31xx->measData.adc1ConvertTime = pUg31xx->sysData.adc1ConvTime;
+ rtn = UpiMeasurement(&pUg31xx->measData);
+ pUg31xx->measData.adc1ConvertTime = pUg31xx->sysData.adc1ConvTime;
+ if(rtn != MEAS_RTN_PASS)
+ {
+ pUg31xx->backupData.capData = &pUg31xx->capData;
+ pUg31xx->backupData.sysData = &pUg31xx->sysData;
+ pUg31xx->backupData.measData = &pUg31xx->measData;
+ UpiUpdateSuspendData(&pUg31xx->backupData, _UPI_TRUE_);
+ return ((GGSTATUS)(rtn + UG_MEAS_FAIL));
+ }
+
+ /// [AT-PM] : Calculate delta capacity; 08/13/2013
+ tmp32 = (_upi_s32_)pUg31xx->measData.deltaCap;
+ tmp32 = tmp32*(pUg31xx->sysData.adc1ConvTime)/(pUg31xx->measData.adc1ConvertTime);
+ pUg31xx->measData.deltaCap = (_upi_s16_)tmp32;
+ pUg31xx->measData.lastDeltaCap = pUg31xx->measData.deltaCap;
+ tmp32 = tmp32 - pUg31xx->sysData.deltaCapFromIC;
+ pUg31xx->measData.stepCap = (_upi_s16_)tmp32;
+ deltaQC = (_upi_s16_)pUg31xx->measData.stepCap;
+ delta_cap_during_suspend = (_upi_s32_)deltaQC;
+
+ /// [AT-PM] : Calculate the RSOC/NAC/LMD from coulomb counter ; 01/27/2013
+ pUg31xx->sysData.voltage = pUg31xx->measData.bat1Voltage;
+ UpiUpdateBatInfoFromIC(&pUg31xx->sysData, deltaQC);
+ UG31_LOGE("[%s]: suspend time = %d ms,deltaQ = %d mAh, RSOC =%d, LMD = %d mAh, NAC=%d mAh\n",
+ __func__,
+ totalTime,
+ deltaQC,
+ pUg31xx->sysData.rsocFromIC,
+ pUg31xx->sysData.fccFromIC,
+ pUg31xx->sysData.rmFromIC);
+ /// [AT-PM] : Calculate the RSOC/NAC/LMD from table ; 01/28/2013
+ pUg31xx->capData.ggbTable = &pUg31xx->cellTable;
+ pUg31xx->capData.ggbParameter = &pUg31xx->cellParameter;
+ pUg31xx->capData.measurement = &pUg31xx->measData;
+ pUg31xx->capData.tableUpdateIdx = pUg31xx->sysData.tableUpdateIdxFromIC;
+ UpiTableCapacity(&pUg31xx->capData);
+ pUg31xx->capData.rsoc = CalculateRsoc(pUg31xx->capData.rm, pUg31xx->capData.fcc);
+ wakeup_predict_rsoc = (_upi_u8_)pUg31xx->capData.rsoc;
+ /// [AT-PM] : Check the data accuracy ; 01/27/2013
+ if(totalTime > MAX_DELTA_TIME_THRESHOLD_FOR_WAKEUP)
+ {
+ deltaQC = (_upi_s16_)pUg31xx->sysData.rsocFromIC;
+ deltaQC = deltaQC - pUg31xx->capData.rsoc;
+ if((deltaQC > MAX_DELTA_RSOC_THRESHOLD_FOR_WAKEUP) || (deltaQC < MIN_DELTA_RSOC_THRESHOLD_FOR_WAKEUP))
+ {
+ deltaQC = (_upi_s16_)pUg31xx->capData.rsoc;
+ deltaQC = deltaQC - pUg31xx->sysData.rsocFromICBackup;
+ if((deltaQC > MAX_DELTA_RSOC_THRESHOLD_FOR_TABLE) || (deltaQC < MIN_DELTA_RSOC_THRESHOLD_FOR_TABLE))
+ {
+ if(deltaQC > MAX_DELTA_RSOC_THRESHOLD_FOR_TABLE)
+ {
+ deltaQC = MAX_DELTA_RSOC_THRESHOLD_FOR_TABLE;
+ }
+ if(deltaQC < MIN_DELTA_RSOC_THRESHOLD_FOR_TABLE)
+ {
+ deltaQC = MIN_DELTA_RSOC_THRESHOLD_FOR_TABLE;
+ }
+ deltaQC = deltaQC + pUg31xx->sysData.rsocFromICBackup;
+ pUg31xx->capData.rsoc = (_cap_u8_)deltaQC;
+
+ }
+ pUg31xx->capData.fcc = pUg31xx->sysData.fccFromIC;
+ tmp32 = (_upi_s32_)pUg31xx->capData.fcc;
+ tmp32 = tmp32*pUg31xx->capData.rsoc/CONST_PERCENTAGE;
+ pUg31xx->capData.rm = (_cap_u16_)tmp32;
+ UG31_LOGI("[%s]: Coulomb counter is not available -> Use data from table (%d/%d = %d)\n", __func__,
+ pUg31xx->capData.rm, pUg31xx->capData.fcc, pUg31xx->capData.rsoc);
+ }
+ else
+ {
+ pUg31xx->capData.rm = (_cap_u16_)pUg31xx->sysData.rmFromIC;
+ pUg31xx->capData.fcc = (_cap_u16_)pUg31xx->sysData.fccFromIC;
+ pUg31xx->capData.rsoc = (_cap_u8_)pUg31xx->sysData.rsocFromIC;
+ UG31_LOGI("[%s]: Use data from coulomb counter (%d/%d = %d)\n", __func__,
+ pUg31xx->capData.rm, pUg31xx->capData.fcc, pUg31xx->capData.rsoc);
+ }
+ }
+ else
+ {
+ pUg31xx->capData.rm = (_cap_u16_)pUg31xx->sysData.rmFromIC;
+ pUg31xx->capData.fcc = (_cap_u16_)pUg31xx->sysData.fccFromIC;
+ pUg31xx->capData.rsoc = (_cap_u8_)pUg31xx->sysData.rsocFromIC;
+ UG31_LOGI("[%s]: Use data from coulomb counter (%d/%d = %d)\n", __func__,
+ pUg31xx->capData.rm, pUg31xx->capData.fcc, pUg31xx->capData.rsoc);
+ }
+
+ /// [AT-PM] : Check dc in before suspend ; 10/22/2013
+ if(dc_in_before == 0)
+ {
+ if(pUg31xx->batteryInfo.RSOC < pUg31xx->capData.rsoc)
+ {
+ UG31_LOGI("[%s]: Fix the RSOC the same value before suspend = %d (%d)\n", __func__, rsocBefore, pUg31xx->capData.rsoc);
+ pUg31xx->capData.rm = (_cap_u16_)rmBefore;
+ pUg31xx->capData.fcc = (_cap_u16_)fccBefore;
+ pUg31xx->capData.rsoc = (_cap_u16_)rsocBefore;
+ }
+
+ tmp32 = (_upi_s32_)totalTime;
+ tmp32 = tmp32/1000*(pUg31xx->cellParameter.standbyCurrent)/2/3600*(-1);
+ UG31_LOGI("[%s]: Estimated capacity = %d\n", __func__, tmp32);
+
+ if(deltaQC >= tmp32)
+ {
+ UG31_LOGI("[%s]: Apply static discharging current\n", __func__);
+
+ tmp32 = tmp32 + deltaQC;
+ tmp32 = tmp32/2;
+ tmp32 = tmp32 + rmBefore;
+ if(tmp32 < 0)
+ {
+ tmp32 = 0;
+ }
+ UG31_LOGI("[%s]: New RM = %d (%d)\n", __func__, tmp32, rmBefore);
+
+ pUg31xx->capData.rm = (_cap_u16_)tmp32;
+ pUg31xx->capData.rsoc = CalculateRsoc(pUg31xx->capData.rm, pUg31xx->capData.fcc);
+ UG31_LOGI("[%s]: Battery status -> %d / %d = %d\n", __func__, pUg31xx->capData.rm, pUg31xx->capData.fcc, pUg31xx->capData.rsoc);
+ }
+
+ if(pUg31xx->measData.rawCodeCharge > 0)
+ {
+ pUg31xx->measData.ccOffsetAdj = pUg31xx->measData.ccOffsetAdj + 1;
+ if(pUg31xx->measData.ccOffsetAdj > pUg31xx->cellParameter.standbyCurrent)
+ {
+ pUg31xx->measData.ccOffsetAdj = pUg31xx->cellParameter.standbyCurrent;
+ }
+ UG31_LOGI("[%s]: Adjust ADC1 offset = %d\n", __func__, pUg31xx->measData.ccOffsetAdj);
+ }
+ }
+
+ pUg31xx->batteryInfo.NAC = (_upi_u16_)pUg31xx->capData.rm;
+ pUg31xx->batteryInfo.LMD = (_upi_u16_)pUg31xx->capData.fcc;
+ pUg31xx->batteryInfo.RSOC = (_upi_u16_)pUg31xx->capData.rsoc;
+ UpiInitDsgCharge(&pUg31xx->capData);
+
+ UpiResetCoulombCounter(&pUg31xx->measData);
+
+ /// [AT-PM] : Save battery information to IC ; 01/31/2013
+ pUg31xx->sysData.rmFromIC = pUg31xx->batteryInfo.NAC;
+ pUg31xx->sysData.fccFromIC = pUg31xx->batteryInfo.LMD;
+ pUg31xx->sysData.rsocFromIC = (_sys_u8_)pUg31xx->batteryInfo.RSOC;
+ pUg31xx->sysData.tableUpdateIdxFromIC = pUg31xx->capData.tableUpdateIdx;
+ pUg31xx->sysData.deltaCapFromIC = pUg31xx->measData.lastDeltaCap;
+ pUg31xx->sysData.adc1ConvTime = pUg31xx->measData.adc1ConvertTime;
+ UpiSaveBatInfoTOIC(&pUg31xx->sysData);
+
+ pUg31xx->backupData.capData = &pUg31xx->capData;
+ pUg31xx->backupData.sysData = &pUg31xx->sysData;
+ pUg31xx->backupData.measData = &pUg31xx->measData;
+ UpiUpdateSuspendData(&pUg31xx->backupData, _UPI_TRUE_);
+
+#if 0
+ /// [AT-PM] : Backup data to file routine ; 02/21/2013
+ pUg31xx->backupData.capData = &pUg31xx->capData;
+ pUg31xx->backupData.sysData = &pUg31xx->sysData;
+ pUg31xx->backupData.measData = &pUg31xx->measData;
+ UpiBackupData(&pUg31xx->backupData);
+#endif
+ return (Status);
+}
+
+/**
+ * @brief upiGG_AccessMeasurementParameter
+ *
+ * Access measurement parameter
+ *
+ * @para read set _UPI_TRUE_ to read data from API
+ * @para pMeasPara pointer of GG_MEAS_PARA_TYPE
+ * @return GGSTATUS
+ */
+GGSTATUS upiGG_AccessMeasurementParameter(char *pObj, _upi_bool_ read, GG_MEAS_PARA_TYPE *pMeasPara)
+{
+ struct ug31xx_data *pUg31xx;
+
+ pUg31xx = (struct ug31xx_data *)pObj;
+
+ /// [AT-PM] : Read data ; 08/29/2012
+ if(read == _UPI_TRUE_)
+ {
+ pMeasPara->Adc1Gain = pUg31xx->cellParameter.adc1_ngain;
+ pMeasPara->Adc1Offset = pUg31xx->cellParameter.adc1_pos_offset;
+ pMeasPara->Adc2Gain = pUg31xx->cellParameter.adc2_gain;
+ pMeasPara->Adc2Offset = pUg31xx->cellParameter.adc2_offset;
+ pMeasPara->ITOffset = pUg31xx->cellParameter.adc_d5;
+ pMeasPara->ETOffset = pUg31xx->cellParameter.adc_d4;
+ pMeasPara->ProductType = pUg31xx->otpData.productType;
+ return (UG_SUCCESS);
+ }
+
+ /// [AT-PM] : Write data ; 08/29/2012
+ pUg31xx->cellParameter.adc1_ngain = pMeasPara->Adc1Gain;
+ pUg31xx->cellParameter.adc1_pos_offset = pMeasPara->Adc1Offset;
+ pUg31xx->cellParameter.adc2_gain = pMeasPara->Adc2Gain;
+ pUg31xx->cellParameter.adc2_offset = pMeasPara->Adc2Offset;
+ pUg31xx->cellParameter.adc_d5 = pMeasPara->ITOffset;
+ pUg31xx->cellParameter.adc_d4 = pMeasPara->ETOffset;
+ return (UG_SUCCESS);
+}
+
+#ifdef ENABLE_BQ27520_SW_CMD
+
+/**
+ * @brief TI_Cntl
+ *
+ * Control() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Cntl(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ _upi_u16_ CntlData;
+
+ CntlData = *pData;
+ switch(CntlData)
+ {
+ case UG_STD_CMD_CNTL_CONTROL_STATUS:
+ *pData = pUg31xx->bq27520Cmd.CntlControlStatus;
+ break;
+ case UG_STD_CMD_CNTL_DEVICE_TYPE:
+ *pData = 0x3103;
+ break;
+ case UG_STD_CMD_CNTL_FW_VERSION:
+ *pData = 0x0001;
+ break;
+ case UG_STD_CMD_CNTL_PREV_MACWRITE:
+ *pData = pUg31xx->bq27520Cmd.CntlPrevMacWrite;
+ break;
+ case UG_STD_CMD_CNTL_CHEM_ID:
+ *pData = 0x0001;
+ break;
+ case UG_STD_CMD_CNTL_OCV_CMD:
+ break;
+ case UG_STD_CMD_CNTL_BAT_INSERT:
+ if(!(pUg31xx->bq27520Cmd.Opcfg & UG_STD_CMD_OPCFG_BIE))
+ {
+ pUg31xx->bq27520Cmd.Flags = pUg31xx->bq27520Cmd.Flags | UG_STD_CMD_FLAGS_BAT_DET;
+ }
+ break;
+ case UG_STD_CMD_CNTL_BAT_REMOVE:
+ if(!(pUg31xx->bq27520Cmd.Opcfg & UG_STD_CMD_OPCFG_BIE))
+ {
+ pUg31xx->bq27520Cmd.Flags = pUg31xx->bq27520Cmd.Flags & (~UG_STD_CMD_FLAGS_BAT_DET);
+ }
+ break;
+ case UG_STD_CMD_CNTL_SET_HIBERNATE:
+ pUg31xx->bq27520Cmd.CntlControlStatus = pUg31xx->bq27520Cmd.CntlControlStatus | UG_STD_CMD_CNTL_CONTROL_STATUS_HIBERNATE;
+ break;
+ case UG_STD_CMD_CNTL_CLEAR_HIBERNATE:
+ pUg31xx->bq27520Cmd.CntlControlStatus = pUg31xx->bq27520Cmd.CntlControlStatus & (~UG_STD_CMD_CNTL_CONTROL_STATUS_HIBERNATE);
+ break;
+ case UG_STD_CMD_CNTL_SET_SLEEP_PLUS:
+ pUg31xx->bq27520Cmd.CntlControlStatus = pUg31xx->bq27520Cmd.CntlControlStatus | UG_STD_CMD_CNTL_CONTROL_STATUS_SNOOZE;
+ break;
+ case UG_STD_CMD_CNTL_CLEAR_SLEEP_PLUS:
+ pUg31xx->bq27520Cmd.CntlControlStatus = pUg31xx->bq27520Cmd.CntlControlStatus & (~UG_STD_CMD_CNTL_CONTROL_STATUS_SNOOZE);
+ break;
+ case UG_STD_CMD_CNTL_FACTORY_RESTORE:
+ break;
+ case UG_STD_CMD_CNTL_ENABLE_DLOG:
+ pUg31xx->bq27520Cmd.CntlControlStatus = pUg31xx->bq27520Cmd.CntlControlStatus | UG_STD_CMD_CNTL_CONTROL_STATUS_DLOGEN;
+ break;
+ case UG_STD_CMD_CNTL_DISABLE_DLOG:
+ pUg31xx->bq27520Cmd.CntlControlStatus = pUg31xx->bq27520Cmd.CntlControlStatus & (~UG_STD_CMD_CNTL_CONTROL_STATUS_DLOGEN);
+ break;
+ case UG_STD_CMD_CNTL_DF_VERSION:
+ *pData = 0x0000;
+ break;
+ case UG_STD_CMD_CNTL_SEALED:
+ pUg31xx->bq27520Cmd.CntlControlStatus = pUg31xx->bq27520Cmd.CntlControlStatus | UG_STD_CMD_CNTL_CONTROL_STATUS_SS;
+ break;
+ case UG_STD_CMD_CNTL_RESET:
+ if(!(pUg31xx->bq27520Cmd.CntlControlStatus & UG_STD_CMD_CNTL_CONTROL_STATUS_SS))
+ {
+ }
+ break;
+ default:
+ *pData = 0x0000;
+ break;
+ }
+
+ pUg31xx->bq27520Cmd.CntlPrevMacWrite = ((CntlData) > UG_STD_CMD_CNTL_PREV_MACWRITE) ? UG_STD_CMD_CNTL_PREV_MACWRITE : CntlData;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_AR
+ *
+ * AtRate() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_AR(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ _upi_s16_ AR;
+
+ AR = (_upi_s16_)(*pData);
+ if(AR != pUg31xx->bq27520Cmd.AR)
+ {
+ pUg31xx->bq27520Cmd.AR = AR;
+ }
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Artte
+ *
+ * AtRateTimeToEmpty() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Artte(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ _upi_s32_ Artte;
+
+ if(pUg31xx->bq27520Cmd.AR >= 0)
+ {
+ *pData = 65535;
+ }
+
+ Artte = (_upi_s32_)pUg31xx->batteryInfo.NAC;
+ Artte = Artte*60*(-1)/pUg31xx->bq27520Cmd.AR;
+ *pData = (_upi_u16_)Artte;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Temp
+ *
+ * Temperature() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Temp(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ if(pUg31xx->bq27520Cmd.Opcfg & UG_STD_CMD_OPCFG_WRTEMP)
+ {
+ /// [AT-PM] : Temperature is from host ; 10/11/2012
+ pUg31xx->bq27520Cmd.Temp = *pData;
+ }
+ else
+ {
+ /// [AT-PM] : Temperature is measured by uG31xx ; 10/11/2012
+ if(pUg31xx->bq27520Cmd.Opcfg & UG_STD_CMD_OPCFG_TEMPS)
+ {
+ /// [AT-PM] : Report external temperature ; 10/11/2012
+ *pData = pUg31xx->deviceInfo.ET;
+ }
+ else
+ {
+ /// [AT-PM] : Report internal temperature ; 10/11/2012
+ *pData = pUg31xx->deviceInfo.IT;
+ }
+ }
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Volt
+ *
+ * Voltage() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Volt(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ *pData = pUg31xx->deviceInfo.voltage_mV;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Flags
+ *
+ * Flags() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Flags(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ /// [AT-PM] : OTC - Overtemperature in charge ; 10/11/2012
+
+ /// [AT-PM] : OTD - Overtemperature in discharge ; 10/11/2012
+
+ /// [AT-PM] : CHG_INH - Charge inhibit ; 10/11/2012
+
+ /// [AT-PM] : XCHG - Charge suspend alert ; 10/11/2012
+
+ /// [AT-PM] : FC - Full-charged ; 10/11/2012
+ if(pUg31xx->batteryInfo.RSOC < pUg31xx->bq27520Cmd.FCClear)
+ {
+ pUg31xx->bq27520Cmd.Flags = pUg31xx->bq27520Cmd.Flags & (~UG_STD_CMD_FLAGS_FC);
+ }
+ if(pUg31xx->bq27520Cmd.FCSet < 0)
+ {
+ if(pUg31xx->batteryInfo.RSOC == 100)
+ {
+ pUg31xx->bq27520Cmd.Flags = pUg31xx->bq27520Cmd.Flags | UG_STD_CMD_FLAGS_FC;
+ }
+ }
+ else
+ {
+ if(pUg31xx->batteryInfo.RSOC > pUg31xx->bq27520Cmd.FCSet)
+ {
+ pUg31xx->bq27520Cmd.Flags = pUg31xx->bq27520Cmd.Flags | UG_STD_CMD_FLAGS_FC;
+ }
+ }
+
+ /// [AT-PM] : CHG - (Fast) charging allowed ; 10/11/2012
+ if(pUg31xx->bq27520Cmd.Flags & UG_STD_CMD_FLAGS_FC)
+ {
+ pUg31xx->bq27520Cmd.Flags = pUg31xx->bq27520Cmd.Flags & (~UG_STD_CMD_FLAGS_CHG);
+ }
+ else
+ {
+ pUg31xx->bq27520Cmd.Flags = pUg31xx->bq27520Cmd.Flags | UG_STD_CMD_FLAGS_CHG;
+ }
+
+ /// [AT-PM] : OCV_GD - Good OCV measurement taken ; 10/11/2012
+
+ /// [AT-PM] : WAIT_ID - Waiting to identify inserted battery ; 10/11/2012
+
+ /// [AT-PM] : BAT_DET - Battery detected ; 10/11/2012
+ if(pUg31xx->userReg.regAlarm2Status & ALARM2_STATUS_OV1_ALARM)
+ {
+ pUg31xx->bq27520Cmd.Flags = pUg31xx->bq27520Cmd.Flags | UG_STD_CMD_FLAGS_BAT_DET;
+ }
+ else
+ {
+ pUg31xx->bq27520Cmd.Flags = pUg31xx->bq27520Cmd.Flags & (~UG_STD_CMD_FLAGS_BAT_DET);
+ }
+
+ /// [AT-PM] : SOC1 - State-of-charge threshold 1 (SOC1 Set) reached ; 10/11/2012
+ if(pUg31xx->batteryInfo.NAC > pUg31xx->bq27520Cmd.Soc1Clear)
+ {
+ pUg31xx->bq27520Cmd.Flags = pUg31xx->bq27520Cmd.Flags & (~UG_STD_CMD_FLAGS_SOC1);
+ }
+ if(pUg31xx->batteryInfo.NAC < pUg31xx->bq27520Cmd.Soc1Set)
+ {
+ pUg31xx->bq27520Cmd.Flags = pUg31xx->bq27520Cmd.Flags | UG_STD_CMD_FLAGS_SOC1;
+ }
+
+ /// [AT-PM] : SYSDOWN - System should shut down ; 10/11/2012
+
+ /// [AT-PM] : DSG - Discharging detected ; 10/11/2012
+ if(pUg31xx->deviceInfo.AveCurrent_mA <= 0)
+ {
+ pUg31xx->bq27520Cmd.Flags = pUg31xx->bq27520Cmd.Flags | UG_STD_CMD_FLAGS_DSG;
+ }
+ else
+ {
+ pUg31xx->bq27520Cmd.Flags = pUg31xx->bq27520Cmd.Flags & (~UG_STD_CMD_FLAGS_DSG);
+ }
+
+ *pData = pUg31xx->bq27520Cmd.Flags;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Nac
+ *
+ * NominalAvailableCapacity() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Nac(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ *pData = pUg31xx->batteryInfo.NAC;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Fac
+ *
+ * FullAvailableCapacity() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Fac(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ *pData = pUg31xx->batteryInfo.LMD;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_RM
+ *
+ * RemainingCapacity() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_RM(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ *pData = pUg31xx->batteryInfo.NAC;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Fcc
+ *
+ * FullChargeCapacity() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Fcc(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ *pData = pUg31xx->batteryInfo.LMD;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_AI
+ *
+ * AverageCurrent() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_AI(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ *pData = pUg31xx->deviceInfo.AveCurrent_mA;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Tte
+ *
+ * TimeToEmpty() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Tte(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ _upi_s32_ Tte;
+
+ if(pUg31xx->deviceInfo.AveCurrent_mA >= 0)
+ {
+ *pData = 65535;
+ return (UG_SUCCESS);
+ }
+
+ Tte = (_upi_s32_)pUg31xx->batteryInfo.NAC;
+ Tte = Tte*60*(-1)/pUg31xx->deviceInfo.AveCurrent_mA;
+ *pData = (_upi_u16_)Tte;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Ttf
+ *
+ * TimeToFull() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Ttf(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ _upi_s32_ Ttf;
+
+ if(pUg31xx->deviceInfo.AveCurrent_mA <= 0)
+ {
+ *pData = 65535;
+ return (UG_SUCCESS);
+ }
+
+ Ttf = (_upi_s32_)pUg31xx->batteryInfo.LMD;
+ Ttf = Ttf - pUg31xx->batteryInfo.NAC;
+ Ttf = Ttf*90/pUg31xx->deviceInfo.AveCurrent_mA;
+ *pData = (_upi_u16_)Ttf;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_SI
+ *
+ * StandbyCurrent() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_SI(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ _upi_s16_ LowerBound;
+ _upi_s32_ NewSI;
+
+ /// [AT-PM] : Set initial SI ; 10/11/2012
+ if(pUg31xx->bq27520Cmd.SINow == 0)
+ {
+ pUg31xx->bq27520Cmd.SINow = pUg31xx->bq27520Cmd.InitSI;
+ }
+
+ LowerBound = pUg31xx->bq27520Cmd.InitSI*2;
+ if(LowerBound > 0)
+ {
+ LowerBound = LowerBound*(-1);
+ }
+
+ /// [AT-PM] : SI criteria - 2 x InitSI < Current < 0 ; 10/11/2012
+ if((pUg31xx->deviceInfo.AveCurrent_mA < 0) && (pUg31xx->deviceInfo.AveCurrent_mA > LowerBound))
+ {
+ /// [AT-PM] : Update SI every 1 minute ; 10/11/2012
+ if((pUg31xx->bq27520Cmd.SIWindow >= 60) && (pUg31xx->bq27520Cmd.SISample > 0))
+ {
+ NewSI = pUg31xx->bq27520Cmd.SIBuf/pUg31xx->bq27520Cmd.SISample;
+ NewSI = NewSI*7 + pUg31xx->bq27520Cmd.SINow*93;
+ NewSI = NewSI/100;
+ pUg31xx->bq27520Cmd.SINow = (_upi_s16_)NewSI;
+ pUg31xx->bq27520Cmd.SISample = -1;
+ pUg31xx->bq27520Cmd.SIBuf = 0;
+ pUg31xx->bq27520Cmd.SIWindow = 0;
+ }
+ else
+ {
+ pUg31xx->bq27520Cmd.SISample = pUg31xx->bq27520Cmd.SISample + 1;
+ pUg31xx->bq27520Cmd.SIWindow = pUg31xx->bq27520Cmd.SIWindow + pUg31xx->bq27520Cmd.DeltaSec;
+
+ /// [AT-PM] : Ignore the first sample ; 10/11/2012
+ if(pUg31xx->bq27520Cmd.SISample > 0)
+ {
+ pUg31xx->bq27520Cmd.SIBuf = pUg31xx->bq27520Cmd.SIBuf + pUg31xx->deviceInfo.AveCurrent_mA;
+ }
+ }
+ }
+ else
+ {
+ /// [AT-PM] : Ignore the last sample ; 10/11/2012
+ if(pUg31xx->bq27520Cmd.SISample > 0)
+ {
+ NewSI = pUg31xx->bq27520Cmd.SIBuf/pUg31xx->bq27520Cmd.SISample;
+ NewSI = NewSI*7 + pUg31xx->bq27520Cmd.SINow*93;
+ NewSI = NewSI/100;
+ pUg31xx->bq27520Cmd.SINow = (_upi_s16_)NewSI;
+ }
+ pUg31xx->bq27520Cmd.SISample = -1;
+ pUg31xx->bq27520Cmd.SIBuf = 0;
+ pUg31xx->bq27520Cmd.SIWindow = 0;
+ }
+
+ *pData = pUg31xx->bq27520Cmd.SINow;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Stte
+ *
+ * StandbyTimeToEmpty() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Stte(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ _upi_s32_ Stte;
+
+ if(pUg31xx->bq27520Cmd.SINow >= 0)
+ {
+ *pData = 65535;
+ return (UG_SUCCESS);
+ }
+
+ Stte = (_upi_s32_)pUg31xx->batteryInfo.NAC;
+ Stte = Stte*60*(-1)/pUg31xx->bq27520Cmd.SINow;
+ *pData = (_upi_u16_)Stte;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Mli
+ *
+ * MaxLoadCurrent() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Mli(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ _upi_s32_ NewMli;
+
+ /// [AT-PM] : Set initial MLI ; 10/11/2012
+ if(pUg31xx->bq27520Cmd.Mli == 0)
+ {
+ pUg31xx->bq27520Cmd.Mli = pUg31xx->bq27520Cmd.InitMaxLoadCurrent;
+ }
+
+ /// [AT-PM] : Get the start charging SOC ; 10/11/2012
+ if(pUg31xx->bq27520Cmd.Flags & UG_STD_CMD_FLAGS_DSG)
+ {
+ pUg31xx->bq27520Cmd.MliDsgSoc = (_upi_u8_)pUg31xx->batteryInfo.RSOC;
+ }
+
+ /// [AT-PM] : MLI criteria - Current < MLI ; 10/11/2012
+ if(pUg31xx->deviceInfo.AveCurrent_mA < pUg31xx->bq27520Cmd.Mli)
+ {
+ pUg31xx->bq27520Cmd.Mli = pUg31xx->deviceInfo.AveCurrent_mA;
+ }
+
+ /// [AT-PM] : Reduce MLI at FC ; 10/11/2012
+ if((pUg31xx->bq27520Cmd.Flags & UG_STD_CMD_FLAGS_FC) && (pUg31xx->bq27520Cmd.MliDsgSoc < 50))
+ {
+ NewMli = (_upi_s32_)pUg31xx->bq27520Cmd.InitMaxLoadCurrent;
+ NewMli = NewMli + pUg31xx->bq27520Cmd.Mli;
+ NewMli = NewMli/2;
+ pUg31xx->bq27520Cmd.Mli = (_upi_s16_)NewMli;
+ }
+
+ *pData = (_upi_u16_)pUg31xx->bq27520Cmd.Mli;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Stte
+ *
+ * MaxLoadTimeToEmpty() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Mltte(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ _upi_s32_ Mltte;
+
+ if(pUg31xx->deviceInfo.AveCurrent_mA >= 0)
+ {
+ *pData = 65535;
+ return (UG_SUCCESS);
+ }
+
+ Mltte = (_upi_s32_)pUg31xx->batteryInfo.NAC;
+ Mltte = Mltte*60*(-1)/pUg31xx->bq27520Cmd.Mli;
+ *pData = (_upi_u16_)Mltte;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_AE
+ *
+ * AvailableEnergy() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_AE(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ _upi_u32_ AE;
+
+ AE = (_upi_u32_)pUg31xx->batteryInfo.NAC;
+ AE = AE*pUg31xx->deviceInfo.voltage_mV/1000;
+ pUg31xx->bq27520Cmd.AE = (_upi_u16_)AE;
+ *pData = pUg31xx->bq27520Cmd.AE;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_AP
+ *
+ * AveragePower() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_AP(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ _upi_s32_ AP;
+
+ if((pUg31xx->deviceInfo.AveCurrent_mA == 0) || (pUg31xx->bq27520Cmd.APDsgTime == 0))
+ {
+ pUg31xx->bq27520Cmd.AP = 0;
+ *pData = 0;
+ return (UG_SUCCESS);
+ }
+
+ AP = (_upi_s32_)pUg31xx->batteryInfo.NAC;
+ AP = AP*pUg31xx->deviceInfo.voltage_mV/1000;
+
+ /// [AT-PM] : Average discharging power ; 10/11/2012
+ if(pUg31xx->deviceInfo.AveCurrent_mA > 0)
+ {
+ pUg31xx->bq27520Cmd.APStartDsgE = AP;
+ pUg31xx->bq27520Cmd.APDsgTime = 0;
+
+ pUg31xx->bq27520Cmd.APChgTime = pUg31xx->bq27520Cmd.APChgTime + pUg31xx->bq27520Cmd.DeltaSec;
+ AP = AP - pUg31xx->bq27520Cmd.APStartChgE;
+ AP = AP*3600/pUg31xx->bq27520Cmd.APChgTime;
+ pUg31xx->bq27520Cmd.AP = (_upi_s16_)AP;
+ }
+
+ /// [AT-PM] : Average charging power ; 10/11/2012
+ if(pUg31xx->deviceInfo.AveCurrent_mA < 0)
+ {
+ pUg31xx->bq27520Cmd.APStartChgE = AP;
+ pUg31xx->bq27520Cmd.APChgTime = 0;
+
+ pUg31xx->bq27520Cmd.APDsgTime = pUg31xx->bq27520Cmd.APDsgTime + pUg31xx->bq27520Cmd.DeltaSec;
+ AP = AP - pUg31xx->bq27520Cmd.APStartDsgE;
+ AP = AP*3600/pUg31xx->bq27520Cmd.APDsgTime;
+ pUg31xx->bq27520Cmd.AP = (_upi_s16_)AP;
+ }
+
+ *pData = (_upi_u16_)pUg31xx->bq27520Cmd.AP;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Ttecp
+ *
+ * TimeToEmptyAtConstantPower() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Ttecp(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ _upi_s32_ Ttecp;
+
+ if(pUg31xx->bq27520Cmd.AP >= 0)
+ {
+ *pData = 65535;
+ return (UG_SUCCESS);
+ }
+
+ Ttecp = (_upi_s32_)pUg31xx->bq27520Cmd.AE;
+ Ttecp = Ttecp*60*(-1)/pUg31xx->bq27520Cmd.AP;
+ *pData = (_upi_u16_)Ttecp;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Soh
+ *
+ * StateOfHealth() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Soh(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ _upi_u32_ Soh;
+
+ Soh = (_upi_u32_)pUg31xx->batteryInfo.LMD;
+ Soh = Soh*100/pUg31xx->bq27520Cmd.Dcap;
+
+ Soh = Soh & UG_STD_CMD_SOH_VALUE_MASK;
+ Soh = Soh | UG_STD_CMD_SOH_STATUS_READY;
+ *pData = (_upi_u16_)Soh;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_CC
+ *
+ * CycleCount() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_CC(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ if(pUg31xx->deviceInfo.AveCurrent_mA < 0)
+ {
+ pUg31xx->bq27520Cmd.CCBuf = pUg31xx->bq27520Cmd.CCBuf + pUg31xx->bq27520Cmd.CCLastNac - pUg31xx->batteryInfo.NAC;
+ }
+ pUg31xx->bq27520Cmd.CCLastNac = pUg31xx->batteryInfo.NAC;
+
+ if(pUg31xx->bq27520Cmd.CCBuf >= pUg31xx->bq27520Cmd.CCThreshold)
+ {
+ pUg31xx->bq27520Cmd.CC = pUg31xx->bq27520Cmd.CC + 1;
+ pUg31xx->bq27520Cmd.CCBuf = pUg31xx->bq27520Cmd.CCBuf - pUg31xx->bq27520Cmd.CCThreshold;
+ }
+
+ *pData = pUg31xx->bq27520Cmd.CC;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Soc
+ *
+ * StateOfCharge() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Soc(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ *pData = pUg31xx->batteryInfo.RSOC;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Nic
+ *
+ * NormalizedImpedanceCal() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Nic(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ *pData = 0;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Icr
+ *
+ * InstantaneousCurrentReading() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Icr(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ *pData = pUg31xx->deviceInfo.current_mA;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Dli
+ *
+ * DataLogIndex() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Dli(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ *pData = pUg31xx->bq27520Cmd.Dli;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Dlb
+ *
+ * DataLogBuffer() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Dlb(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ *pData = pUg31xx->bq27520Cmd.Dlb;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Itemp
+ *
+ * InternalTemperature() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Itemp(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ *pData = pUg31xx->deviceInfo.IT;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Opcfg
+ *
+ * OperationConfiguration() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Opcfg(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ *pData = (_upi_u16_)pUg31xx->bq27520Cmd.Opcfg;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Dcap
+ *
+ * DesignCapacity() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Dcap(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ *pData = pUg31xx->bq27520Cmd.Dcap;
+ return (UG_SUCCESS);
+}
+
+typedef GGSTATUS (*TIBq27520FuncPtr)(struct ug31xx_data *pUg31xx, _upi_u16_ *pData);
+typedef struct TIBq27520FuncTableST {
+ TIBq27520FuncPtr pFunc;
+ _upi_u8_ CmdCode;
+} TIBq27520FuncTableType;
+
+
+TIBq27520FuncTableType TI_Command[] = {
+ { TI_Cntl, UG_STD_CMD_CNTL, },
+ { TI_AR, UG_STD_CMD_AR, },
+ { TI_Artte, UG_STD_CMD_ARTTE, },
+ { TI_Temp, UG_STD_CMD_TEMP, },
+ { TI_Volt, UG_STD_CMD_VOLT, },
+ { TI_Flags, UG_STD_CMD_FLAGS, },
+ { TI_Nac, UG_STD_CMD_NAC, },
+ { TI_Fac, UG_STD_CMD_FAC, },
+ { TI_RM, UG_STD_CMD_RM, },
+ { TI_Fcc, UG_STD_CMD_FCC, },
+ { TI_AI, UG_STD_CMD_AI, },
+ { TI_Tte, UG_STD_CMD_TTE, },
+ { TI_Ttf, UG_STD_CMD_TTF, },
+ { TI_SI, UG_STD_CMD_SI, },
+ { TI_Stte, UG_STD_CMD_STTE, },
+ { TI_Mli, UG_STD_CMD_MLI, },
+ { TI_Mltte, UG_STD_CMD_MLTTE, },
+ { TI_AE, UG_STD_CMD_AE, },
+ { TI_AP, UG_STD_CMD_AP, },
+ { TI_Ttecp, UG_STD_CMD_TTECP, },
+ { TI_Soh, UG_STD_CMD_SOH, },
+ { TI_CC, UG_STD_CMD_CC, },
+ { TI_Soc, UG_STD_CMD_SOC, },
+ { TI_Nic, UG_STD_CMD_NIC, },
+ { TI_Icr, UG_STD_CMD_ICR, },
+ { TI_Dli, UG_STD_CMD_DLI, },
+ { TI_Dlb, UG_STD_CMD_DLB, },
+ { TI_Itemp, UG_STD_CMD_ITEMP, },
+ { TI_Opcfg, UG_STD_CMD_OPCFG, },
+ { TI_Dcap, UG_EXT_CMD_DCAP, },
+};
+
+/**
+ * @brief upiGG_FetchDataCommand
+ *
+ * Read the gas gauge status following TI bq27520's interface
+ *
+ * @para CommandCode command code
+ * @para pData address of returned data
+ * @return GGSTATUS
+ */
+GGSTATUS upiGG_FetchDataCommand(char *pObj, _upi_u8_ CommandCode, _upi_u16_ *pData)
+{
+ GGSTATUS Rtn;
+ int TotalCmd;
+ GG_DEVICE_INFO DevInfo;
+ GG_CAPACITY CapData;
+ _upi_u32_ DeltaT;
+ int CmdIdx;
+ struct ug31xx_data *pUg31xx;
+
+ pUg31xx = (struct ug31xx_data *)pObj;
+
+ Rtn = upiGG_ReadDeviceInfo(pObj, &DevInfo);
+ if(Rtn != UG_READ_DEVICE_INFO_SUCCESS)
+ {
+ return (Rtn);
+ }
+
+ DeltaT = pUg31xx->measData.deltaTime;
+ if(DeltaT >= 5000000)
+ {
+ upiGG_ReadCapacity(pObj, &CapData);
+ }
+
+ DeltaT = GetTickCount();
+ DeltaT = DeltaT - pUg31xx->bq27520Cmd.LastTime;
+ pUg31xx->bq27520Cmd.LastTime = GetTickCount();
+ pUg31xx->bq27520Cmd.DeltaSec = (_upi_u16_)(DeltaT/1000);
+
+ Rtn = UG_SUCCESS;
+ TotalCmd = sizeof(TI_Command)/sizeof(TIBq27520FuncTableType);
+ CmdIdx = 0;
+ while(1)
+ {
+ if(TI_Command[CmdIdx].CmdCode == CommandCode)
+ {
+ Rtn = (*TI_Command[CmdIdx].pFunc)(pUg31xx, pData);
+ break;
+ }
+
+ CmdIdx = CmdIdx + 1;
+ if(CmdIdx >= TotalCmd)
+ {
+ Rtn = UG_TI_CMD_OVERFLOW;
+ }
+ }
+
+ return (Rtn);
+}
+
+/**
+ * @brief upiGG_FetchDataParameter
+ *
+ * Set the parameter for bq27520 like command
+ *
+ * @para data parameters of GG_FETCH_DATA_PARA_TYPE
+ * @return GGSTATUS
+ */
+GGSTATUS upiGG_FetchDataParameter(char *pObj, GG_FETCH_DATA_PARA_TYPE data)
+{
+ GGSTATUS Rtn;
+ struct ug31xx_data *pUg31xx;
+
+ pUg31xx = (struct ug31xx_data *)pObj;
+
+ Rtn = UG_SUCCESS;
+
+ pUg31xx->bq27520Cmd.FCSet = data.FCSet;
+ pUg31xx->bq27520Cmd.FCClear = data.FCClear;
+ pUg31xx->bq27520Cmd.Soc1Set = data.Soc1Set;
+ pUg31xx->bq27520Cmd.Soc1Clear = data.Soc1Clear;
+ pUg31xx->bq27520Cmd.InitSI = data.InitSI;
+ pUg31xx->bq27520Cmd.InitMaxLoadCurrent = data.InitMaxLoadCurrent;
+ pUg31xx->bq27520Cmd.CCThreshold = data.CCThreshold;
+ pUg31xx->bq27520Cmd.Opcfg = data.Opcfg;
+ pUg31xx->bq27520Cmd.Dcap = data.Dcap;
+ return (Rtn);
+}
+
+#endif ///< end of ENABLE_BQ27520_SW_CMD
+
+/**
+ * @brief upiGG_DumpRegister
+ *
+ * Dump whole register value
+ *
+ * @para pBuf address of register value buffer
+ * @return data size
+ */
+_upi_u16_ upiGG_DumpRegister(char *pObj, _upi_u8_ * pBuf)
+{
+ _upi_u16_ idx;
+ struct ug31xx_data *pUg31xx;
+
+ pUg31xx = (struct ug31xx_data *)pObj;
+
+ idx = 0;
+ memcpy(pBuf + idx, pUg31xx->otpData.otp1, OTP1_SIZE);
+ idx = idx + OTP1_SIZE;
+ memcpy(pBuf + idx, pUg31xx->otpData.otp2, OTP2_SIZE);
+ idx = idx + OTP2_SIZE;
+ memcpy(pBuf + idx, pUg31xx->otpData.otp3, OTP3_SIZE);
+ idx = idx + OTP3_SIZE;
+ memcpy(pBuf + idx, &pUg31xx->userReg, sizeof(GG_USER_REG));
+ idx = idx + sizeof(GG_USER_REG);
+ memcpy(pBuf + idx, &pUg31xx->user2Reg, sizeof(GG_USER2_REG));
+ idx = idx + sizeof(GG_USER2_REG);
+ return (idx);
+}
+
+/**
+ * @brief upiGG_DumpCellTable
+ *
+ * Dump cell NAC table
+ *
+ * @para pTable address of cell table
+ * @return _UPI_NULL_
+ */
+void upiGG_DumpCellTable(char *pObj, CELL_TABLE *pTable)
+{
+ struct ug31xx_data *pUg31xx;
+
+ pUg31xx = (struct ug31xx_data *)pObj;
+ memcpy(pTable, &pUg31xx->cellTable, sizeof(CELL_TABLE));
+}
+
+/**
+ * @brief upiGG_UnInitial
+ *
+ * Un-initialize uG31xx
+ *
+ * @para pObj address of memory buffer allocated for uG31xx
+ * @return GGSTATUS
+ */
+GGSTATUS upiGG_UnInitial(char **pObj)
+{
+ struct ug31xx_data *pUg31xx;
+ pUg31xx = (struct ug31xx_data *)(*pObj);
+
+ UG31_LOGE("[%s]***** upiGG_UnInitial() to free memory ***** \n", __func__);
+
+ pUg31xx->backupData.backupDataIdx = BACKUP_MAX_LOG_SUSPEND_DATA;
+ while(pUg31xx->backupData.backupDataIdx)
+ {
+ pUg31xx->backupData.backupDataIdx = pUg31xx->backupData.backupDataIdx - 1;
+ kfree(pUg31xx->backupData.logData[pUg31xx->backupData.backupDataIdx]);
+ }
+
+ #if defined(uG31xx_OS_ANDROID)
+ #ifdef uG31xx_BOOT_LOADER
+ free(*pObj);
+ #else ///< else of uG31xx_BOOT_LOADER
+ kfree(*pObj);
+ #endif ///< end of uG31xx_BOOT_LOADER
+ #else ///< else of defined(uG31xx_OS_ANDROID)
+ free(*pObj);
+ #endif ///< end of defined(uG31xx_OS_ANDROID)
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief upiGG_DumpParameter
+ *
+ * Dump all parameter setting
+ *
+ * @para pTable address of cell parameter
+ * @return _UPI_NULL_
+ */
+void upiGG_DumpParameter(char *pObj, CELL_PARAMETER *pTable)
+{
+ struct ug31xx_data *pUg31xx;
+
+ pUg31xx = (struct ug31xx_data *)pObj;
+ memcpy(pTable, &pUg31xx->cellParameter, sizeof(CELL_PARAMETER));
+
+ // memcpy(&pTable->CELL_NAC_TABLE, &pUg31xx->realData, sizeof(GG_REAL_DATA));
+}
+
+/**
+ * @brief upiGG_FetchDebugData
+ *
+ * Fetch debug information data
+ *
+ * @para pObj address of memory buffer
+ * @para data address of GG_FETCH_CAP_DATA_TYPE
+ * @return _UPI_NULL_
+ */
+void upiGG_FetchDebugData(char *pObj, GG_FETCH_DEBUG_DATA_TYPE *data)
+{
+ struct ug31xx_data *pUg31xx;
+
+ pUg31xx = (struct ug31xx_data *)pObj;
+
+ data->versionMain = UG31XX_API_MAIN_VERSION;
+ data->versionOtp = UG31XX_API_OTP_VERSION;
+ data->versionSub = UG31XX_API_SUB_VERSION;
+
+ data->capDelta = pUg31xx->measData.stepCap;
+ data->capDsgCharge = pUg31xx->capData.dsgCharge;
+ data->capDsgChargeStart = pUg31xx->capData.dsgChargeStart;
+ data->capDsgChargeTime = pUg31xx->capData.dsgChargeTime;
+ data->capPreDsgCharge = pUg31xx->capData.preDsgCharge;
+ data->capSelfHour = pUg31xx->capData.selfDsgHour;
+ data->capSelfMilliSec = pUg31xx->capData.selfDsgMilliSec;
+ data->capSelfMin = pUg31xx->capData.selfDsgMin;
+ data->capSelfSec = pUg31xx->capData.selfDsgSec;
+ data->capStatus = pUg31xx->capData.status;
+ data->capTableUpdateIdx = pUg31xx->capData.tableUpdateIdx;
+ data->capTPTime = pUg31xx->capData.tpTime;
+
+ data->measAdc1ConvertTime = pUg31xx->measData.adc1ConvertTime;
+ data->measAdc1Gain = pUg31xx->measData.adc1Gain;
+ data->measAdc1Offset = pUg31xx->measData.adc1Offset;
+ data->measAdc2Gain = pUg31xx->measData.adc2Gain;
+ data->measAdc2Offset = pUg31xx->measData.adc2Offset;
+ data->measCCOffset = pUg31xx->measData.ccOffset;
+ data->measCharge = pUg31xx->measData.codeCharge;
+ data->measCodeBat1 = pUg31xx->measData.codeBat1;
+ data->measCodeCurrent = pUg31xx->measData.codeCurrent;
+ data->measCodeET = pUg31xx->measData.codeExtTemperature;
+ data->measCodeIT = pUg31xx->measData.codeIntTemperature;
+ data->measLastCounter = pUg31xx->measData.lastCounter;
+ data->measLastDeltaQ = pUg31xx->measData.lastDeltaCap;
+ data->measLastTimeTick = pUg31xx->measData.lastTimeTick;
+}
+
+/**
+ * @brief upiGG_DebugSwitch
+ *
+ * Enable/disable debug information to UART
+ *
+ * @para Enable set _UPI_TRUE_ to enable it
+ * @return NULL
+ */
+void upiGG_DebugSwitch(_upi_bool_ enable)
+{
+ Ug31DebugEnable = enable;
+}
+
+/**
+ * @brief upiGG_ForceTaper
+ *
+ * Force taper condition reached
+ *
+ * @para pObj address of memory buffer
+ * @para charger_full 1 if charger detects full
+ * @para dc_in_before 1 if dc in before suspend mode
+ * @para dc_in_now 1 if dc in after suspend mode
+ * @return NULL
+ */
+void upiGG_ForceTaper(char *pObj, int charger_full, int dc_in_before, int dc_in_now)
+{
+ struct ug31xx_data *pUg31xx;
+ _meas_s16_ now_step_cap;
+ _meas_s16_ now_current;
+ _meas_u16_ now_voltage;
+ _meas_u32_ now_delta_time;
+ _upi_u8_ cnt;
+ _upi_s32_ tmp32;
+
+ pUg31xx = (struct ug31xx_data *)pObj;
+ pUg31xx->capData.ggbTable = &pUg31xx->cellTable;
+ pUg31xx->capData.ggbParameter = &pUg31xx->cellParameter;
+ pUg31xx->capData.measurement = &pUg31xx->measData;
+ pUg31xx->capData.status = pUg31xx->capData.status | 0x0100;
+
+ if(dc_in_before == 0)
+ {
+ UG31_LOGI("[%s]: Enter suspend without charger -> no force taper\n", __func__);
+ return;
+ }
+
+ tmp32 = dsg_charge_before_suspend - delta_cap_during_suspend;
+ if(dc_in_now == 0)
+ {
+ now_current = pUg31xx->measData.curr;
+ now_current = now_current/10;
+ now_voltage = pUg31xx->measData.bat1Voltage;
+ now_voltage = now_voltage - now_current;
+
+ UG31_LOGI("[%s]: No dc in now (%d)\n", __func__, now_voltage);
+ tmp32 = tmp32*100;
+ if((pUg31xx->capData.status & 0x0000000c) == 0x00000008) ///< [AT-PM] : Check current status is dischargign ; 10/11/2013
+ {
+ if((wakeup_predict_rsoc < 95) &&
+ (tmp32 > pUg31xx->cellParameter.ILMD) &&
+ (now_voltage < pUg31xx->cellParameter.TPVoltage))
+ {
+ UG31_LOGI("[%s]: Predicted RSOC < 95 and charged capacity is not enough (%d < %d) and voltage is low (%d < %d ) in discharging -> no force taper\n",
+ __func__, delta_cap_during_suspend, dsg_charge_before_suspend, now_voltage, pUg31xx->cellParameter.TPVoltage);
+ return;
+ }
+ }
+ else
+ {
+ if((wakeup_predict_rsoc < 98) && (tmp32 > pUg31xx->cellParameter.ILMD))
+ {
+ UG31_LOGI("[%s]: Predicted RSOC (%d) < 98 and charged capacity is not enough (%d < %d) -> no force taper\n",
+ __func__, wakeup_predict_rsoc, delta_cap_during_suspend, dsg_charge_before_suspend);
+ return;
+ }
+ }
+ }
+ else
+ {
+ UG31_LOGI("[%s]: DC in now\n", __func__);
+ if(charger_full == 0)
+ {
+ UG31_LOGI("[%s]: No charger detect full event -> no force taper\n", __func__);
+ return;
+ }
+ if(wakeup_predict_rsoc < 90)
+ {
+ UG31_LOGI("[%s]: Predicted RSOC (%d) < 90 -> no force taper\n", __func__, wakeup_predict_rsoc);
+ return;
+ }
+ }
+
+ now_step_cap = pUg31xx->measData.stepCap;
+ now_current = pUg31xx->measData.curr;
+ now_voltage = pUg31xx->measData.bat1Voltage;
+ now_delta_time = pUg31xx->measData.deltaTime;
+
+ UG31_LOGI("[%s]: Force to taper START\n", __func__);
+
+ cnt = 100;
+ while(cnt)
+ {
+ /// [AT-PM] : Enter charging mode ; 08/14/2013
+ pUg31xx->measData.stepCap = 0;
+ pUg31xx->measData.deltaTime = 0;
+ pUg31xx->measData.curr = (_meas_s16_)pUg31xx->cellParameter.TPCurrent - 1;
+
+ UpiReadCapacity(&pUg31xx->capData);
+ pUg31xx->capData.tableUpdateDelayCnt = 1;
+ pUg31xx->batteryInfo.NAC = (_upi_u16_)pUg31xx->capData.rm;
+ pUg31xx->batteryInfo.LMD = (_upi_u16_)pUg31xx->capData.fcc;
+ pUg31xx->batteryInfo.RSOC = (_upi_u16_)pUg31xx->capData.rsoc;
+
+ /// [AT-PM] : Force to TP ; 08/14/2013
+ pUg31xx->measData.bat1Voltage = (_meas_u16_)pUg31xx->cellParameter.TPVoltage + 1;
+ pUg31xx->capData.tpTime = (_cap_u32_)pUg31xx->cellParameter.TPTime + 1;
+ pUg31xx->capData.tpTime = pUg31xx->capData.tpTime*1000;
+
+ UpiReadCapacity(&pUg31xx->capData);
+ pUg31xx->capData.tableUpdateDelayCnt = 1;
+ pUg31xx->capData.rm = pUg31xx->capData.fcc;
+ pUg31xx->capData.rsoc = 100;
+ pUg31xx->batteryInfo.NAC = (_upi_u16_)pUg31xx->capData.rm;
+ pUg31xx->batteryInfo.LMD = (_upi_u16_)pUg31xx->capData.fcc;
+ pUg31xx->batteryInfo.RSOC = (_upi_u16_)pUg31xx->capData.rsoc;
+
+ /// [AT-PM] : Recover to initial state ; 08/14/2013
+ pUg31xx->measData.curr = now_current;
+ pUg31xx->measData.bat1Voltage = now_voltage;
+ pUg31xx->measData.deltaTime = now_delta_time;
+
+ UpiReadCapacity(&pUg31xx->capData);
+ pUg31xx->capData.tableUpdateDelayCnt = 1;
+ pUg31xx->batteryInfo.NAC = (_upi_u16_)pUg31xx->capData.rm;
+ pUg31xx->batteryInfo.LMD = (_upi_u16_)pUg31xx->capData.fcc;
+ pUg31xx->batteryInfo.RSOC = (_upi_u16_)pUg31xx->capData.rsoc;
+
+ UG31_LOGI("[%s]: Force to taper loop %d -> %d / %d = %d\n", __func__,
+ cnt, pUg31xx->batteryInfo.NAC, pUg31xx->batteryInfo.LMD, pUg31xx->batteryInfo.RSOC);
+ if(pUg31xx->capData.rsoc >= 100)
+ {
+ break;
+ }
+
+ cnt = cnt - 1;
+ }
+
+ pUg31xx->measData.stepCap = now_step_cap;
+
+ UG31_LOGI("[%s]: Force to taper END (%d / %d = %d)\n", __func__,
+ pUg31xx->batteryInfo.NAC, pUg31xx->batteryInfo.LMD, pUg31xx->batteryInfo.RSOC);
+}
+
+/// ===========================================
+/// End of uG31xx_API.cpp
+/// ===========================================
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API.h b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API.h new file mode 100755 index 00000000..c601852c --- /dev/null +++ b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API.h @@ -0,0 +1,521 @@ +/// ===========================================
+/// uG31xx_API.h
+/// ===========================================
+
+#ifndef _UG31XXAPI_H_
+#define _UG31XXAPI_H_
+
+#include "uG31xx_Platform.h"
+
+#if defined (uG31xx_OS_WINDOWS)
+
+ #include <windows.h>
+
+#endif
+
+#include "global.h"
+#include "uG31xx.h"
+
+#if defined (uG31xx_OS_WINDOWS)
+
+ #include "../../uG31xx_I2C_DLL/uG3100Dll/uG31xx_I2C.h"
+ #include <assert.h>
+
+ #ifdef DEBUG_LOG
+
+ #include "wDebug.h"
+
+ #endif
+
+#elif defined (uG31xx_OS_ANDROID)
+
+ #ifdef uG31xx_BOOT_LOADER
+
+ #include <sys/types.h>
+ #include <platform/timer.h>
+
+ #include "ug31xx_boot_i2c.h"
+
+ #else ///< else of uG31xx_BOOT_LOADER
+
+ #include <linux/module.h>
+ #include <linux/slab.h>
+ #include <linux/delay.h>
+ #include <linux/time.h>
+ #include <asm/div64.h>
+ #include <linux/jiffies.h>
+ #include <linux/types.h>
+ #include <linux/fs.h>
+ #include <asm/uaccess.h>
+
+ #include "ug31xx_i2c.h"
+
+ #endif ///< end of uG31xx_BOOT_LOADER
+
+#endif
+
+#include "typeDefine.h"
+#include "uG31xx_Reg_Def.h"
+#include "uG31xx_API_Otp.h"
+#include "uG31xx_API_System.h"
+#include "uG31xx_API_Measurement.h"
+#include "uG31xx_API_Capacity.h"
+#include "uG31xx_API_Backup.h"
+
+#define UG31XX_API_RELEASE_VERSION (20131108)
+
+#define UG31XX_API_MAIN_VERSION (13)
+#define UG31XX_API_OTP_VERSION ((UG31XX_OTP_VERSION_MAIN << 16) | UG31XX_OTP_VERSION_SUB)
+#define UG31XX_API_SUB_VERSION ((UG31XX_CAP_VERSION << 16) | (UG31XX_MEAS_VERSION << 8) | UG31XX_SYSTEM_VERSION)
+
+#define UG31XX_I2C_HIGH_SPEED_MODE (_UPI_FALSE_)
+#define UG31XX_I2C_TEM_BITS_MODE (_UPI_FALSE_)
+
+#define UG31XX_MAX_TEMPERATURE_BEFORE_READY (395)
+#define UG31XX_MIN_TEMPERATURE_BEFORE_READY (105)
+
+#ifdef UG31XX_DEBUG_ENABLE
+
+ #if defined(uG31xx_OS_ANDROID)
+
+ #ifdef uG31xx_BOOT_LOADER
+
+ #define __func__
+
+ #define UG31_LOGE(...)
+ #define UG31_LOGI(...)
+ #define UG31_LOGD(...)
+ #define UG31_LOGV(...)
+
+ extern int ug31_dprintk(int level, const char *fmt, ...);
+
+ #else ///< else of uG31xx_BOOT_LOADER
+
+ #define LOG_NORMAL (0)
+ #define LOG_ERROR (1)
+ #define LOG_DATA (2)
+ #define LOG_VERBOSE (3)
+
+ #define UG31_TAG "UG31"
+ #define UG31_LOGE(...) ug31_printk(LOG_ERROR, "<"UG31_TAG"/E>" __VA_ARGS__);
+ #define UG31_LOGI(...) ug31_printk(LOG_NORMAL, "<"UG31_TAG"/I>" __VA_ARGS__);
+ #define UG31_LOGD(...) ug31_printk(LOG_DATA, "<"UG31_TAG"/D>" __VA_ARGS__);
+ #define UG31_LOGV(...) ug31_printk(LOG_VERBOSE, "<"UG31_TAG"/V>" __VA_ARGS__);
+
+ extern int ug31_printk(int level, const char *fmt, ...);
+
+ #endif ///< end of uG31xx_BOOT_LOADER
+
+ #endif ///< end of defined(uG31xx_OS_ANDROID)
+
+ #if defined (uG31xx_OS_WINDOWS)
+
+ #define DEBUG_FILE (_T("uG3105"))
+ #define __func__ (_T(__FUNCTION__))
+
+ #define UG31_LOGE(...) wDebug::LOGE(DEBUG_FILE, 0, _T(__VA_ARGS__));
+ #define UG31_LOGI(...) wDebug::LOGE(DEBUG_FILE, 0, _T(__VA_ARGS__));
+ #define UG31_LOGD(...) wDebug::LOGE(DEBUG_FILE, 0, _T(__VA_ARGS__));
+ #define UG31_LOGV(...) wDebug::LOGE(DEBUG_FILE, 0, _T(__VA_ARGS__));
+
+ #endif ///< end of defined (uG31xx_OS_WINDOWS)
+
+#else ///< else of UG31XX_DEBUG_ENABLE
+
+ #define UG31_LOGE(...)
+ #define UG31_LOGI(...)
+ #define UG31_LOGD(...)
+ #define UG31_LOGV(...)
+
+#endif ///< end of UG31XX_DEBUG_ENABLE
+
+#if defined(uG31xx_OS_ANDROID)
+
+ extern _upi_u32_ GetTickCount(void);
+ extern _upi_u32_ GetSysTickCount(void);
+
+#endif
+
+/* data struct */
+typedef enum _GGSTATUS{
+ UG_SUCCESS = 0x00,
+ UG_FAIL = 0x01,
+ UG_NOT_DEF = 0x02,
+ UG_INIT_OCV_FAIL = 0x03,
+ UG_READ_GGB_FAIL = 0x04,
+ UG_ACTIVE_FAIL = 0x05,
+ UG_INIT_SUCCESS = 0x06,
+ UG_OTP_ISEMPTY = 0x07,
+ UG_OTP_PRODUCT_DISMATCH = 0x08,
+
+ UG_I2C_INIT_FAIL = 0x10,
+ UG_I2C_READ_SUCCESS = 0x11,
+ UG_I2C_READ_FAIL = 0x12,
+ UG_I2C_WRITE_SUCCESS = 0x13,
+ UG_I2C_WRITE_FAIL = 0x14,
+
+ UG_READ_REG_SUCCESS = 0x20,
+ UG_READ_REG_FAIL = 0x21,
+
+ UG_READ_DEVICE_INFO_SUCCESS = 0x22,
+ UG_READ_DEVICE_INFO_FAIL = 0x23,
+ UG_READ_DEVICE_ALARM_SUCCESS = 0x24,
+ UG_READ_DEVICE_ALARM_FAIL = 0x25,
+ UG_READ_DEVICE_RID_SUCCESS = 0x26,
+ UG_READ_DEVICE_RID_FAIL = 0x27,
+ UG_READ_ADC_FAIL = 0x28, //new add for filter ADC Error Code
+
+ UG_TI_CMD_OVERFLOW = 0x30,
+
+ UG_MEAS_FAIL = 0x40,
+ UG_MEAS_FAIL_BATTERY_REMOVED = 0x41,
+ UG_MEAS_FAIL_ADC_ABNORMAL = 0x42,
+ UG_MEAS_FAIL_NTC_SHORT = 0x43,
+
+ UG_CAP_DATA_READY = 0x50,
+ UG_CAP_DATA_NOT_READY = 0x51,
+}GGSTATUS;
+
+/*
+ GGSTATUS upiGG_Initial
+ Description: Initial and active uG31xx function
+ Input: .GGB(gas gauge battery) setting filename, need include complete path
+ Output: UG_INIT_SUCCESS -> initial uG31xx success
+ UG_READ_GGB_FAIL -> read GGB file fail
+ UG_INIT_I2C_FAIL -> initial I2C to open HID fail
+ UG_ACTIVE_FAIL -> active uG31xx fail
+*/
+#if defined (uG31xx_OS_WINDOWS)
+
+ EXPORTS GGSTATUS upiGG_Initial(char **pObj, const wchar_t* GGBFilename, const wchar_t* OtpFileName, const wchar_t* BackupFileName, char reset, unsigned char MaxETFixCnt);
+
+#endif
+
+#if defined(uG31xx_OS_ANDROID)
+
+ GGSTATUS upiGG_Initial(char **pObj, GGBX_FILE_HEADER *pGGBXBuf, char reset, unsigned char MaxETFixCnt);
+
+#endif
+
+/*
+ GGSTATUS upiGG_CountInitQmax
+ Description:
+ Input: None
+ Output: None
+*/
+//EXPORTS void upiGG_CountInitQmax(void);
+
+/*
+ GGSTATUS upiGG_ReadDevieRegister
+ Description: Read GG_USER_REG from device to global variable and output
+ Input: Pointer of sturct GG_USER_REG
+ Output: UG_READ_REG_SUCCESS -> read success
+ UG_READ_REG_FAIL -> read fail
+*/
+EXPORTS GGSTATUS upiGG_ReadAllRegister(char *pObj,GG_USER_REG* pExtUserReg, GG_USER2_REG* pExtUserReg2);
+
+/*
+ GGSTATUS upiGG_ReadDeviceInfo
+ Description: Read GG_USER_REG from device and calculate GG_DEVICE_INFO, then write to global variable and output
+ Input: Pointer of struct GG_DEVICE_INFO
+ Output: UG_READ_DEVICE_INFO_SUCCESS -> calculate derive information sucess
+ UG_READ_DEVICE_INFO_FAIL -> calculate derive information fail
+*/
+EXPORTS GGSTATUS upiGG_ReadDeviceInfo(char *pObj,GG_DEVICE_INFO* pExtDeviceInfo);
+
+/* GGSTATUS upiGG_ReadCapacity
+ Description:
+ Input:
+ Output: None
+*/
+EXPORTS void upiGG_ReadCapacity(char *pObj,GG_CAPACITY *pExtCapacity);
+
+/**
+ * @brief upiGG_GetAlarmStatus
+ *
+ * Get alarm status
+ *
+ * @para pAlarmStatus address of alarm status
+ * @return UG_READ_DEVICE_ALARM_SUCCESS if success
+ */
+EXPORTS GGSTATUS upiGG_GetAlarmStatus(char *pObj, _upi_u8_ *pAlarmStatus);
+
+/*
+new add function for System suspend & wakeup
+
+*/
+EXPORTS GGSTATUS upiGG_PreSuspend(char *pObj);
+EXPORTS GGSTATUS upiGG_Wakeup(char *pObj, int dc_in_before);
+
+/**
+ * @brief upiGG_DumpRegister
+ *
+ * Dump whole register value
+ *
+ * @para pBuf address of register value buffer
+ * @return data size
+ */
+EXPORTS _upi_u16_ upiGG_DumpRegister(char *pObj, _upi_u8_ *pBuf);
+
+/**
+ * @brief upiGG_DumpCellTable
+ *
+ * Dump cell NAC table
+ *
+ * @para pTable address of cell table
+ * @return _UPI_NULL_
+ */
+EXPORTS void upiGG_DumpCellTable(char *pObj, CELL_TABLE *pTable);
+EXPORTS GGSTATUS upiGG_UnInitial(char **pObj);
+EXPORTS void upiGG_DumpParameter(char *pObj, CELL_PARAMETER *pTable);
+
+#define upiGG_PrePowerOff (upiGG_PreSuspend)
+
+#ifdef ENABLE_BQ27520_SW_CMD
+
+/**
+ * @brief upiGG_AccessMeasurementParameter
+ *
+ * Access measurement parameter
+ *
+ * @para read set _UPI_TRUE_ to read data from API
+ * @para pMeasPara pointer of GG_MEAS_PARA_TYPE
+ * @return GGSTATUS
+ */
+EXPORTS GGSTATUS upiGG_AccessMeasurementParameter(char *pObj, _upi_bool_ read, GG_MEAS_PARA_TYPE *pMeasPara);
+
+#define UG_STD_CMD_CNTL (0x00)
+ #define UG_STD_CMD_CNTL_CONTROL_STATUS (0x0000)
+ #define UG_STD_CMD_CNTL_CONTROL_STATUS_DLOGEN (1<<15)
+ #define UG_STD_CMD_CNTL_CONTROL_STATUS_FAS (1<<14)
+ #define UG_STD_CMD_CNTL_CONTROL_STATUS_SS (1<<13)
+ #define UG_STD_CMD_CNTL_CONTROL_STATUS_CSV (1<<12)
+ #define UG_STD_CMD_CNTL_CONTROL_STATUS_CCA (1<<11)
+ #define UG_STD_CMD_CNTL_CONTROL_STATUS_BCA (1<<10)
+ #define UG_STD_CMD_CNTL_CONTROL_STATUS_OCVCMDCOMP (1<<9)
+ #define UG_STD_CMD_CNTL_CONTROL_STATUS_OCVFAIL (1<<8)
+ #define UG_STD_CMD_CNTL_CONTROL_STATUS_INITCOMP (1<<7)
+ #define UG_STD_CMD_CNTL_CONTROL_STATUS_HIBERNATE (1<<6)
+ #define UG_STD_CMD_CNTL_CONTROL_STATUS_SNOOZE (1<<5)
+ #define UG_STD_CMD_CNTL_CONTROL_STATUS_SLEEP (1<<4)
+ #define UG_STD_CMD_CNTL_CONTROL_STATUS_LDMD (1<<3)
+ #define UG_STD_CMD_CNTL_CONTROL_STATUS_RUP_DIS (1<<2)
+ #define UG_STD_CMD_CNTL_CONTROL_STATUS_VOK (1<<1)
+ #define UG_STD_CMD_CNTL_CONTROL_STATUS_QEN (1<<0)
+ #define UG_STD_CMD_CNTL_DEVICE_TYPE (0x0001)
+ #define UG_STD_CMD_CNTL_FW_VERSION (0x0002)
+ #define UG_STD_CMD_CNTL_PREV_MACWRITE (0x0007)
+ #define UG_STD_CMD_CNTL_CHEM_ID (0x0008)
+ #define UG_STD_CMD_CNTL_OCV_CMD (0x000C) ///< [AT-PM] : Not implemented ; 10/11/2012
+ #define UG_STD_CMD_CNTL_BAT_INSERT (0x000D)
+ #define UG_STD_CMD_CNTL_BAT_REMOVE (0x000E)
+ #define UG_STD_CMD_CNTL_SET_HIBERNATE (0x0011)
+ #define UG_STD_CMD_CNTL_CLEAR_HIBERNATE (0x0012)
+ #define UG_STD_CMD_CNTL_SET_SLEEP_PLUS (0x0013)
+ #define UG_STD_CMD_CNTL_CLEAR_SLEEP_PLUS (0x0014)
+ #define UG_STD_CMD_CNTL_FACTORY_RESTORE (0x0015)
+ #define UG_STD_CMD_CNTL_ENABLE_DLOG (0x0018)
+ #define UG_STD_CMD_CNTL_DISABLE_DLOG (0x0019)
+ #define UG_STD_CMD_CNTL_DF_VERSION (0x001F)
+ #define UG_STD_CMD_CNTL_SEALED (0x0020)
+ #define UG_STD_CMD_CNTL_RESET (0x0041)
+#define UG_STD_CMD_AR (0x02)
+#define UG_STD_CMD_ARTTE (0x04)
+#define UG_STD_CMD_TEMP (0x06)
+#define UG_STD_CMD_VOLT (0x08)
+#define UG_STD_CMD_FLAGS (0x0A)
+ #define UG_STD_CMD_FLAGS_OTC (1<<15)
+ #define UG_STD_CMD_FLAGS_OTD (1<<14)
+ #define UG_STD_CMD_FLAGS_RSVD13 (1<<13)
+ #define UG_STD_CMD_FLAGS_RSVD12 (1<<12)
+ #define UG_STD_CMD_FLAGS_CHG_INH (1<<11)
+ #define UG_STD_CMD_FLAGS_XCHG (1<<10)
+ #define UG_STD_CMD_FLAGS_FC (1<<9)
+ #define UG_STD_CMD_FLAGS_CHG (1<<8)
+ #define UG_STD_CMD_FLAGS_RSVD7 (1<<7)
+ #define UG_STD_CMD_FLAGS_RSVD6 (1<<6)
+ #define UG_STD_CMD_FLAGS_OCV_GD (1<<5)
+ #define UG_STD_CMD_FLAGS_WAIT_ID (1<<4)
+ #define UG_STD_CMD_FLAGS_BAT_DET (1<<3)
+ #define UG_STD_CMD_FLAGS_SOC1 (1<<2)
+ #define UG_STD_CMD_FLAGS_SYSDOWN (1<<1)
+ #define UG_STD_CMD_FLAGS_DSG (1<<0)
+#define UG_STD_CMD_NAC (0x0C)
+#define UG_STD_CMD_FAC (0x0E)
+#define UG_STD_CMD_RM (0x10)
+#define UG_STD_CMD_FCC (0x12)
+#define UG_STD_CMD_AI (0x14)
+#define UG_STD_CMD_TTE (0x16)
+#define UG_STD_CMD_TTF (0x18)
+#define UG_STD_CMD_SI (0x1A)
+#define UG_STD_CMD_STTE (0x1C)
+#define UG_STD_CMD_MLI (0x1E)
+#define UG_STD_CMD_MLTTE (0x20)
+#define UG_STD_CMD_AE (0x22)
+#define UG_STD_CMD_AP (0x24)
+#define UG_STD_CMD_TTECP (0x26)
+#define UG_STD_CMD_SOH (0x28)
+ #define UG_STD_CMD_SOH_VALUE_MASK (0x00FF)
+ #define UG_STD_CMD_SOH_STATUS_MASK (0xFF00)
+ #define UG_STD_CMD_SOH_STATUS_NOT_VALID (0x0000)
+ #define UG_STD_CMD_SOH_STATUS_INSTANT_READY (0x0100)
+ #define UG_STD_CMD_SOH_STATUS_INITIAL_READY (0x0200)
+ #define UG_STD_CMD_SOH_STATUS_READY (0x0300)
+#define UG_STD_CMD_CC (0x2A)
+#define UG_STD_CMD_SOC (0x2C)
+#define UG_STD_CMD_NIC (0x2E) ///< [AT-PM] : Not implemented ; 10/11/2012
+#define UG_STD_CMD_ICR (0x30)
+#define UG_STD_CMD_DLI (0x32)
+#define UG_STD_CMD_DLB (0x34)
+#define UG_STD_CMD_ITEMP (0x36)
+#define UG_STD_CMD_OPCFG (0x3A)
+ #define UG_STD_CMD_OPCFG_RESCAP (1<<31)
+ #define UG_STD_CMD_OPCFG_BATG_OVR (1<<30)
+ #define UG_STD_CMD_OPCFG_INT_BERM (1<<29)
+ #define UG_STD_CMD_OPCFG_PFC_CFG1 (1<<28)
+ #define UG_STD_CMD_OPCFG_PFC_CFG0 (1<<27)
+ #define UG_STD_CMD_OPCFG_IWAKE (1<<26)
+ #define UG_STD_CMD_OPCFG_RSNS1 (1<<25)
+ #define UG_STD_CMD_OPCFG_RSNS0 (1<<24)
+ #define UG_STD_CMD_OPCFG_INT_FOCV (1<<23)
+ #define UG_STD_CMD_OPCFG_IDSELEN (1<<22)
+ #define UG_STD_CMD_OPCFG_SLEEP (1<<21)
+ #define UG_STD_CMD_OPCFG_RMFCC (1<<20)
+ #define UG_STD_CMD_OPCFG_SOCI_POL (1<<19)
+ #define UG_STD_CMD_OPCFG_BATG_POL (1<<18)
+ #define UG_STD_CMD_OPCFG_BATL_POL (1<<17)
+ #define UG_STD_CMD_OPCFG_TEMPS (1<<16)
+ #define UG_STD_CMD_OPCFG_WRTEMP (1<<15)
+ #define UG_STD_CMD_OPCFG_BIE (1<<14)
+ #define UG_STD_CMD_OPCFG_BL_INT (1<<13)
+ #define UG_STD_CMD_OPCFG_GNDSEL (1<<12)
+ #define UG_STD_CMD_OPCFG_FCE (1<<11)
+ #define UG_STD_CMD_OPCFG_DFWRINDBL (1<<10)
+ #define UG_STD_CMD_OPCFG_RFACTSTEP (1<<9)
+ #define UG_STD_CMD_OPCFG_INDFACRES (1<<8)
+ #define UG_STD_CMD_OPCFG_BATGSPUEN (1<<7)
+ #define UG_STD_CMD_OPCFG_BATGWPUEN (1<<6)
+ #define UG_STD_CMD_OPCFG_BATLSPUEN (1<<5)
+ #define UG_STD_CMD_OPCFG_BATLWSPUEN (1<<4)
+ #define UG_STD_CMD_OPCFG_RSVD3 (1<<3)
+ #define UG_STD_CMD_OPCFG_SLPWKCHG (1<<2)
+ #define UG_STD_CMD_OPCFG_DELTAVOPT1 (1<<1)
+ #define UG_STD_CMD_OPCFG_DELTAVOPT0 (1<<0)
+#define UG_EXT_CMD_DCAP (0x3C)
+
+/**
+ * @brief upiGG_FetchDataCommand
+ *
+ * Fetch bq27520 like command
+ *
+ * @para read set _UPI_TRUE_ to read data from API
+ * @para pMeasPara pointer of GG_MEAS_PARA_TYPE
+ * @return GGSTATUS
+ */
+EXPORTS GGSTATUS upiGG_FetchDataCommand(char *pObj, _upi_u8_ CommandCode, _upi_u16_ *pData);
+
+typedef struct GG_FETCH_DATA_PARA_ST {
+ _upi_s8_ FCSet;
+ _upi_s8_ FCClear;
+ _upi_u8_ Soc1Set;
+ _upi_u8_ Soc1Clear;
+ _upi_s8_ InitSI;
+ _upi_s16_ InitMaxLoadCurrent;
+ _upi_u16_ CCThreshold;
+ _upi_u32_ Opcfg;
+ _upi_u16_ Dcap;
+#if defined(uG31xx_OS_ANDROID)
+}__attribute__((packed)) GG_FETCH_DATA_PARA_TYPE;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+} GG_FETCH_DATA_PARA_TYPE;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+/**
+ * @brief upiGG_FetchDataParameter
+ *
+ * Set the parameter for bq27520 like command
+ *
+ * @para data parameters of GG_FETCH_DATA_PARA_TYPE
+ * @return GGSTATUS
+ */
+EXPORTS GGSTATUS upiGG_FetchDataParameter(char *pObj, GG_FETCH_DATA_PARA_TYPE data);
+
+#endif //endif ENABLE_BQ27520_SW_CMD
+
+typedef struct GG_FETCH_DEBUG_DATA_ST {
+ /// [AT-PM] : Driver version ; 01/30/2013
+ int versionMain;
+ int versionOtp;
+ int versionSub;
+
+ /// [AT-PM] : Capacity related ; 01/30/2013
+ int capStatus;
+ int capSelfHour;
+ int capSelfMin;
+ int capSelfSec;
+ int capSelfMilliSec;
+ int capTPTime;
+ int capDelta;
+ int capDsgCharge;
+ int capDsgChargeStart;
+ int capDsgChargeTime;
+ int capPreDsgCharge;
+ int capTableUpdateIdx;
+
+ /// [AT-PM] : Measurement related ; 01/30/2013
+ int measCodeBat1;
+ int measCodeCurrent;
+ int measCodeIT;
+ int measCodeET;
+ int measCharge;
+ int measCCOffset;
+ int measAdc1ConvertTime;
+ int measAdc1Gain;
+ int measAdc1Offset;
+ int measAdc2Gain;
+ int measAdc2Offset;
+ int measLastCounter;
+ int measLastTimeTick;
+ int measLastDeltaQ;
+
+#if defined(uG31xx_OS_ANDROID)
+ }__attribute__((packed)) GG_FETCH_DEBUG_DATA_TYPE;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+ } GG_FETCH_DEBUG_DATA_TYPE;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+/**
+ * @brief upiGG_FetchDebugData
+ *
+ * Fetch debug information data
+ *
+ * @para pObj address of memory buffer
+ * @para data address of GG_FETCH_CAP_DATA_TYPE
+ * @return _UPI_NULL_
+ */
+EXPORTS void upiGG_FetchDebugData(char *pObj, GG_FETCH_DEBUG_DATA_TYPE *data);
+
+/**
+ * @brief upiGG_DebugSwitch
+ *
+ * Enable/disable debug information to UART
+ *
+ * @para Enable set _UPI_TRUE_ to enable it
+ * @return NULL
+ */
+EXPORTS void upiGG_DebugSwitch(_upi_bool_ enable);
+
+#define UPI_CHECK_BACKUP_FILE_FAIL (0)
+#define UPI_CHECK_BACKUP_FILE_EXIST (1)
+#define UPI_CHECK_BACKUP_FILE_MISMATCH (2)
+
+EXPORTS _upi_u8_ upiGG_CheckBackupFile(char *pObj);
+
+EXPORTS void upiGG_ForceTaper(char *pObj, int charger_full, int dc_in_before, int dc_in_now);
+
+#endif ///< end of _UG31XXAPI_H_
+
+/// ===========================================
+/// End of uG31xx_API.h
+/// ===========================================
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Backup.c b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Backup.c new file mode 100755 index 00000000..90a82633 --- /dev/null +++ b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Backup.c @@ -0,0 +1,601 @@ +/**
+ * @filename uG31xx_API_Backup.cpp
+ *
+ * Backup data on uG31xx to a file in system
+ *
+ * @author AllenTeng <allen_teng@upi-semi.com>
+ */
+
+#include "stdafx.h" //windows need this??
+#include "uG31xx_API.h"
+
+#define UG31XX_BACKUP_FILE_ENABLE
+
+#ifdef uG31xx_OS_WINDOWS
+
+typedef char mm_segment_t;
+typedef char loff_t;
+
+typedef struct file
+{
+ FILE *fp;
+} fileType;
+
+#define O_RDONLY (1<<0)
+#define O_WRONLY (1<<1)
+#define O_RDWR (3<<0)
+#define O_CREAT (1<<2)
+#define O_APPEND (1<<3)
+
+static struct file BackupFile;
+
+/**
+ * @brief filp_open
+ *
+ * Open file
+ *
+ * @para path address of file
+ * @para cntl FILE_CNTL parameter
+ * @para misc dummy parameter
+ * @return address of BackupFile
+ */
+struct file * filp_open(const wchar_t *path, int cntl, int misc)
+{
+ if(BackupFile.fp != _UPI_NULL_)
+ {
+ fclose(BackupFile.fp);
+ }
+ BackupFile.fp = _UPI_NULL_;
+
+ switch(cntl & O_RDWR)
+ {
+ case O_RDONLY:
+ _wfopen_s(&BackupFile.fp, path, _T("rb, ccs=UTF-8"));
+ break;
+ case O_WRONLY:
+ _wfopen_s(&BackupFile.fp, path, _T("wb, ccs=UTF-8"));
+ break;
+ case O_RDWR:
+ _wfopen_s(&BackupFile.fp, path, _T("r+b, ccs=UTF-8"));
+ break;
+ default:
+ _wfopen_s(&BackupFile.fp, path, _T("rb, ccs=UTF-8"));
+ break;
+ }
+ return (&BackupFile);
+}
+
+/**
+ * @brief filp_close
+ *
+ * Close file
+ *
+ * @para fp address of struct file
+ * @para misc dummy value
+ * @return _UPI_NULL_
+ */
+void filp_close(struct file *fp, int misc)
+{
+ if(fp->fp != _UPI_NULL_)
+ {
+ fclose(fp->fp);
+ }
+}
+
+/**
+ * @brief vfs_write
+ *
+ * Write data to binary file
+ *
+ * @para fp address of struct file
+ * @para data address of data to be written
+ * @para size size to be written
+ * @para pos start position in the file
+ * @return size be written
+ */
+size_t vfs_write(struct file *fp, char *data, int size, loff_t *pos)
+{
+ return (fwrite(data, sizeof(char), size, fp->fp));
+}
+
+/**
+ * @brief vfs_read
+ *
+ * Read data from binary file
+ *
+ * @para fp address of struct file
+ * @para data address of data to be read
+ * @para size size to be read
+ * @para pos start position in the file
+ * @return size be read
+ */
+size_t vfs_read(struct file *fp, char *data, int size, loff_t *pos)
+{
+ return (fread(data, sizeof(char), size, fp->fp));
+}
+
+/**
+ * @brief IS_ERR
+ *
+ * Check file is opened or not
+ *
+ * @para fp address of struct file
+ * @return _UPI_TRUE_ if file is opened
+ */
+_upi_bool_ IS_ERR(struct file *fp)
+{
+ return ((fp->fp == _UPI_NULL_) ? _UPI_TRUE_ : _UPI_FALSE_);
+}
+
+/**
+ * @brief get_fs
+ *
+ * Dummy function
+ *
+ * @return 0
+ */
+mm_segment_t get_fs(void)
+{
+ return (0);
+}
+
+/**
+ * @brief set_fs
+ *
+ * Dummy function
+ *
+ * @para value mm_segment_t value
+ * @return _UPI_NULL_
+ */
+void set_fs(mm_segment_t value)
+{
+}
+
+/**
+ * @brief get_ds
+ *
+ * Dummy function
+ *
+ * @return 0
+ */
+mm_segment_t get_ds(void)
+{
+ return (0);
+}
+
+#endif ///< end of uG31xx_OS_WINDOWS
+
+/**
+ * @brief ISFileExist
+ *
+ * Check file existed or not
+ *
+ * @para data address of BackupDataType
+ * @return BACKUP_BOOL_TRUE if file existed
+ */
+_backup_bool_ ISFileExist(BackupDataType *data)
+{
+ struct file *fp;
+
+ fp = filp_open(data->backupFileName, O_RDONLY, 0);
+ if(IS_ERR(fp))
+ {
+ UG31_LOGI("[%s]: Backup file is not existed\n", __func__);
+ return (BACKUP_BOOL_FALSE);
+ }
+ UG31_LOGI("[%s]: Backup file is existed\n", __func__);
+ filp_close(fp, _UPI_NULL_);
+ return (BACKUP_BOOL_TRUE);
+}
+
+/**
+ * @brief WriteFile
+ *
+ * Write data to file
+ *
+ * @para data address of BackupDataType
+ * @para fp address of struct fp
+ */
+void WriteFile(BackupDataType *data, struct file *fp)
+{
+ mm_segment_t oldFS;
+ loff_t pos;
+ size_t size;
+
+ oldFS = get_fs();
+ set_fs(get_ds());
+
+ pos = 0;
+ size = vfs_write(fp, (char *)data->capData->encriptTable, sizeof(data->capData->encriptTable), &pos);
+ UG31_LOGI("[%s]: Write table %d bytes\n", __func__, size);
+ size = vfs_write(fp, (char *)(&data->sysData->rmFromIC), sizeof(data->sysData->rmFromIC), &pos);
+ UG31_LOGI("[%s]: Write RM (%d) %d bytes\n", __func__, data->sysData->rmFromIC, size);
+ size = vfs_write(fp, (char *)(&data->sysData->fccFromIC), sizeof(data->sysData->fccFromIC), &pos);
+ UG31_LOGI("[%s]: Write FCC (%d) %d bytes\n", __func__, data->sysData->fccFromIC, size);
+ size = vfs_write(fp, (char *)(&data->sysData->timeTagFromIC), sizeof(data->sysData->timeTagFromIC), &pos);
+ UG31_LOGI("[%s]: Write Time Tag (%d) %d bytes\n", __func__, data->sysData->timeTagFromIC, size);
+ size = vfs_write(fp, (char *)(&data->sysData->tableUpdateIdxFromIC), sizeof(data->sysData->tableUpdateIdxFromIC), &pos);
+ UG31_LOGI("[%s]: Write Table Update Index (%d) %d bytes\n", __func__, data->sysData->tableUpdateIdxFromIC, size);
+ size = vfs_write(fp, (char *)(&data->sysData->deltaCapFromIC), sizeof(data->sysData->deltaCapFromIC), &pos);
+ UG31_LOGI("[%s]: Write Delta Capacity (%d) %d bytes\n", __func__, data->sysData->deltaCapFromIC, size);
+ size = vfs_write(fp, (char *)(&data->sysData->adc1ConvTime), sizeof(data->sysData->adc1ConvTime), &pos);
+ UG31_LOGI("[%s]: Write ADC1 Conversion Time (%d) %d bytes\n", __func__, data->sysData->adc1ConvTime, size);
+ size = vfs_write(fp, (char *)(&data->sysData->rsocFromIC), sizeof(data->sysData->rsocFromIC), &pos);
+ UG31_LOGI("[%s]: Write RSOC (%d) %d bytes\n", __func__, data->sysData->rsocFromIC, size);
+ size = vfs_write(fp, (char *)(&data->targetFileVer), sizeof(_backup_u32_), &pos);
+ UG31_LOGI("[%s]: Write version (%d) %d bytes\n", __func__, data->targetFileVer, size);
+
+ set_fs(oldFS);
+}
+
+/**
+ * @brief CreateBackupFile
+ *
+ * Create backup file in system
+ *
+ * @para data address of BackupDataType
+ * @return BACKUP_BOOL_TRUE if success
+ */
+_backup_bool_ CreateBackupFile(BackupDataType *data)
+{
+ struct file *fp;
+
+ fp = filp_open(data->backupFileName, O_CREAT | O_RDWR, 0);
+ if(IS_ERR(fp))
+ {
+ UG31_LOGI("[%s]: Create backup file fail\n", __func__);
+ return (BACKUP_BOOL_FALSE);
+ }
+
+ /// [AT-PM] : Write data to file ; 02/21/2013
+ WriteFile(data, fp);
+
+ filp_close(fp, _UPI_NULL_);
+ return (BACKUP_BOOL_TRUE);
+}
+
+/**
+ * @brief ReadFile
+ *
+ * Read data from file
+ *
+ * @para data address of BackupDataType
+ * @para fp address of struct fp
+ */
+void ReadFile(BackupDataType *data, struct file *fp)
+{
+ mm_segment_t oldFS;
+ loff_t pos;
+ size_t size;
+
+ oldFS = get_fs();
+ set_fs(get_ds());
+
+ pos = 0;
+ size = vfs_read(fp, (char *)&data->capData->encriptTable, sizeof(data->capData->encriptTable), &pos);
+ UG31_LOGI("[%s]: Read table %d bytes\n", __func__, size);
+ size = vfs_read(fp, (char *)(&data->sysData->rmFromIC), sizeof(data->sysData->rmFromIC), &pos);
+ UG31_LOGI("[%s]: Read RM (%d) %d bytes\n", __func__, data->sysData->rmFromIC, size);
+ size = vfs_read(fp, (char *)(&data->sysData->fccFromIC), sizeof(data->sysData->fccFromIC), &pos);
+ UG31_LOGI("[%s]: Read FCC (%d) %d bytes\n", __func__, data->sysData->fccFromIC, size);
+ size = vfs_read(fp, (char *)(&data->sysData->timeTagFromIC), sizeof(data->sysData->timeTagFromIC), &pos);
+ UG31_LOGI("[%s]: Read Time Tag (%d) %d bytes\n", __func__, data->sysData->timeTagFromIC, size);
+ size = vfs_read(fp, (char *)(&data->sysData->tableUpdateIdxFromIC), sizeof(data->sysData->tableUpdateIdxFromIC), &pos);
+ UG31_LOGI("[%s]: Read Table Update Index (%d) %d bytes\n", __func__, data->sysData->tableUpdateIdxFromIC, size);
+ size = vfs_read(fp, (char *)(&data->sysData->deltaCapFromIC), sizeof(data->sysData->deltaCapFromIC), &pos);
+ UG31_LOGI("[%s]: Read Delta Capacity (%d) %d bytes\n", __func__, data->sysData->deltaCapFromIC, size);
+ size = vfs_read(fp, (char *)(&data->sysData->adc1ConvTime), sizeof(data->sysData->adc1ConvTime), &pos);
+ UG31_LOGI("[%s]: Read ADC1 Conversion Time (%d) %d bytes\n", __func__, data->sysData->adc1ConvTime, size);
+ size = vfs_read(fp, (char *)(&data->sysData->rsocFromIC), sizeof(data->sysData->rsocFromIC), &pos);
+ UG31_LOGI("[%s]: Read RSOC (%d) %d bytes\n", __func__, data->sysData->rsocFromIC, size);
+ size = vfs_read(fp, (char *)(&data->backupFileVer), sizeof(_backup_u32_), &pos);
+ UG31_LOGI("[%s]: Read version (%d) %d bytes\n", __func__, data->backupFileVer, size);
+
+ set_fs(oldFS);
+}
+
+/**
+ * @brief CheckBackupFile
+ *
+ * Check backup file is consisted with IC or not
+ *
+ * @para data address of BackupDataType
+ * @return BACKUP_BOOL_TRUE if success
+ */
+_backup_bool_ CheckBackupFile(BackupDataType *data)
+{
+ struct file *fp;
+ CapacityDataType *bufCapData;
+ SystemDataType *bufSysData;
+
+ fp = filp_open(data->backupFileName, O_RDWR, 0);
+ if(IS_ERR(fp))
+ {
+ UG31_LOGI("[%s]: Open backup file fail\n", __func__);
+ return (BACKUP_BOOL_FALSE);
+ }
+
+ /// [AT-PM] : Create buffer ; 02/21/2013
+ #if defined(uG31xx_OS_ANDROID)
+ #ifdef uG31xx_BOOT_LOADER
+ bufCapData = (CapacityDataType *)malloc(sizeof(CapacityDataType));
+ bufSysData = (SystemDataType *)malloc(sizeof(SystemDataType));
+ #else ///< else of uG31xx_BOOT_LOADER
+ bufCapData = (CapacityDataType *)kmalloc(sizeof(CapacityDataType), GFP_KERNEL);
+ bufSysData = (SystemDataType *)kmalloc(sizeof(SystemDataType), GFP_KERNEL);
+ #endif ///< end of uG31xx_BOOT_LOADER
+ #else ///< else of defined(uG31xx_OS_ANDROID)
+ bufCapData = (CapacityDataType *)malloc(sizeof(CapacityDataType));
+ bufSysData = (SystemDataType *)malloc(sizeof(SystemDataType));
+ #endif ///< end of defined(uG31xx_OS_ANDROID)
+ memcpy(bufCapData, data->capData, sizeof(CapacityDataType));
+ memcpy(bufSysData, data->sysData, sizeof(SystemDataType));
+
+ /// [AT-PM] : Get data from file ; 02/21/2013
+ ReadFile(data, fp);
+
+ /// [AT-PM] : Following information is not checked ; 02/21/2013
+ data->sysData->rmFromIC = bufSysData->rmFromIC;
+ data->sysData->timeTagFromIC = bufSysData->timeTagFromIC;
+ data->sysData->deltaCapFromIC = bufSysData->deltaCapFromIC;
+ data->sysData->adc1ConvTime = bufSysData->adc1ConvTime;
+
+ /// [AT-PM] : Check data ; 02/21/2013
+ if((memcmp(bufCapData, data->capData, sizeof(CapacityDataType)) != 0) ||
+ (memcmp(bufSysData, data->sysData, sizeof(SystemDataType)) != 0) ||
+ (data->backupFileVer != data->targetFileVer))
+ {
+ UG31_LOGI("[%s]: Backup file needs to be updated\n", __func__);
+ /// [AT-PM] : Write data to file ; 02/21/2013
+ memcpy(data->capData, bufCapData, sizeof(CapacityDataType));
+ memcpy(data->sysData, bufSysData, sizeof(SystemDataType));
+ WriteFile(data, fp);
+ }
+
+ filp_close(fp, _UPI_NULL_);
+
+ #if defined(uG31xx_OS_ANDROID)
+ #ifdef uG31xx_BOOT_LOADER
+ free(bufCapData);
+ free(bufSysData);
+ #else ///< else of uG31xx_BOOT_LOADER
+ kfree(bufCapData);
+ kfree(bufSysData);
+ #endif ///< end of uG31xx_BOOT_LOADER
+ #else ///< else of defined(uG31xx_OS_ANDROID)
+ free(bufCapData);
+ free(bufSysData);
+ #endif ///< end of defined(uG31xx_OS_ANDROID)
+ return (BACKUP_BOOL_TRUE);
+}
+
+/**
+ * @brief WriteSuspendBackupData
+ *
+ * Write suspend backup data to file
+ *
+ * @para data address of BackupDataType
+ * @return BACKUP_BOOL_TRUE if success
+ */
+_backup_bool_ WriteSuspendBackupData(BackupDataType *data)
+{
+ struct file *fp;
+ _backup_u8_ idx;
+ mm_segment_t oldFS;
+ loff_t pos;
+ size_t size;
+ _backup_u8_ *buf;
+
+ /// [AT-PM] : Check backup file content ; 10/12/2013
+ fp = filp_open("/sdcard/upi_table", O_RDWR, 0);
+ if(!IS_ERR(fp))
+ {
+ oldFS = get_fs();
+ set_fs(get_ds());
+
+ idx = 0;
+ pos = 0;
+ buf = (_backup_u8_ *)kmalloc(sizeof(BackupSuspendDataType), GFP_KERNEL);
+ while(idx < BACKUP_MAX_LOG_SUSPEND_DATA)
+ {
+ size = vfs_read(fp, (char *)buf, sizeof(BackupSuspendDataType), &pos);
+
+ if(memcmp((char *)buf, (char *)data->logData[idx], sizeof(BackupSuspendDataType)) != 0)
+ {
+ UG31_LOGI("[%s]: Need to write suspend data to file (%d)\n", __func__, idx);
+ break;
+ }
+
+ idx = idx + 1;
+ }
+ kfree(buf);
+
+ set_fs(oldFS);
+
+ filp_close(fp, _UPI_NULL_);
+
+ if(idx >= BACKUP_MAX_LOG_SUSPEND_DATA)
+ {
+ UG31_LOGI("[%s]: No need to update suspend data.\n", __func__, idx);
+ return (BACKUP_BOOL_TRUE);
+ }
+ }
+
+ fp = filp_open("/sdcard/upi_table", O_CREAT | O_RDWR, 0);
+ if(IS_ERR(fp))
+ {
+ UG31_LOGI("[%s]: Open backup file fail\n", __func__);
+ return (BACKUP_BOOL_FALSE);
+ }
+
+ oldFS = get_fs();
+ set_fs(get_ds());
+
+ idx = 0;
+ pos = 0;
+ while(idx < BACKUP_MAX_LOG_SUSPEND_DATA)
+ {
+ size = vfs_write(fp, (char *)data->logData[idx], sizeof(BackupSuspendDataType), &pos);
+ UG31_LOGI("[%s]: Write suspend data %d with %d bytes\n", __func__, idx, size);
+
+ idx = idx + 1;
+ }
+
+ set_fs(oldFS);
+
+ filp_close(fp, _UPI_NULL_);
+ return (BACKUP_BOOL_TRUE);
+}
+
+/// =============================================
+/// Extern Function Region
+/// =============================================
+
+#define RETRY_CHECKING_THRESHOLD (CONST_CONVERSION_COUNT_THRESHOLD)
+
+/**
+ * @brief UpiBackupData
+ *
+ * Backup data from IC to system routine
+ *
+ * @para data address of BackupDataType
+ * @return _UPI_NULL_
+ */
+void UpiBackupData(BackupDataType *data)
+{
+ _backup_bool_ rtnBool;
+
+ #ifndef UG31XX_BACKUP_FILE_ENABLE
+ rtnBool = _UPI_TRUE_;
+ data->backupFileSts = BACKUP_FILE_STS_EXIST;
+ #endif ///< end of UG31XX_BACKUP_FILE_ENABLE
+
+ #if defined(uG31xx_OS_ANDROID)
+ #ifdef uG31xx_BOOT_LOADER
+ data->backupFileName = (char *)malloc(strlen(BACKUP_FILE_PATH));
+ #else ///< else of uG31xx_BOOT_LOADER
+ data->backupFileName = (char *)kmalloc(strlen(BACKUP_FILE_PATH), GFP_KERNEL);
+ #endif ///< end of uG31xx_BOOT_LOADER
+ sprintf(data->backupFileName, "%s", BACKUP_FILE_PATH);
+ #else ///< else of defined(uG31xx_OS_ANDROID)
+ data->backupFileName = data->sysData->backupFileName;
+ #endif ///< end of defined(uG31xx_OS_ANDROID)
+
+ switch(data->backupFileSts)
+ {
+ case BACKUP_FILE_STS_CHECKING:
+ /// [AT-PM] : Check backup file existed or not ; 02/21/2013
+ rtnBool = ISFileExist(data);
+ if(rtnBool == BACKUP_BOOL_TRUE)
+ {
+ #ifdef BACKUP_FILE_STS_NOT_EXIST
+ data->backupFileSts = BACKUP_FILE_STS_NOT_EXIST;
+ #else ///< else of BACKUP_FILE_STS_NOT_EXIST
+ data->backupFileSts = BACKUP_FILE_STS_EXIST;
+ #endif ///< end of BACKUP_FILE_STS_NOT_EXIST
+ }
+ else
+ {
+ if(data->measData->lastCounter > RETRY_CHECKING_THRESHOLD)
+ {
+ data->backupFileSts = BACKUP_FILE_STS_NOT_EXIST;
+ }
+ }
+ break;
+ case BACKUP_FILE_STS_NOT_EXIST:
+ /// [AT-PM] : Create backup file ; 02/21/2013
+ rtnBool = CreateBackupFile(data);
+ if(rtnBool == BACKUP_BOOL_TRUE)
+ {
+ data->backupFileSts = BACKUP_FILE_STS_EXIST;
+ }
+ break;
+ case BACKUP_FILE_STS_EXIST:
+ rtnBool = CheckBackupFile(data);
+ if(rtnBool != BACKUP_BOOL_TRUE)
+ {
+ data->backupFileSts = BACKUP_FILE_STS_NOT_EXIST;
+ }
+ else
+ {
+ data->backupFileSts = BACKUP_FILE_STS_COMPARE;
+ }
+ break;
+ case BACKUP_FILE_STS_COMPARE:
+ if(data->icDataAvailable == BACKUP_BOOL_TRUE)
+ {
+ /// [AT-PM] : Check content of file is consist with IC or not ; 02/21/2013
+ rtnBool = CheckBackupFile(data);
+ if(rtnBool != BACKUP_BOOL_TRUE)
+ {
+ data->backupFileSts = BACKUP_FILE_STS_NOT_EXIST;
+ }
+ rtnBool = WriteSuspendBackupData(data);
+ if(rtnBool != BACKUP_BOOL_TRUE)
+ {
+ data->backupFileSts = BACKUP_FILE_STS_NOT_EXIST;
+ }
+ }
+ else
+ {
+ data->backupFileSts = BACKUP_FILE_STS_CHECKING;
+ }
+ break;
+ default:
+ /// [AT-PM] : Un-known state ; 02/21/2013
+ data->backupFileSts = BACKUP_FILE_STS_NOT_EXIST;
+ break;
+ }
+
+ #if defined(uG31xx_OS_ANDROID)
+ #ifdef uG31xx_BOOT_LOADER
+ free(data->backupFileName);
+ #else ///< else of uG31xx_BOOT_LOADER
+ kfree(data->backupFileName);
+ #endif ///< end of uG31xx_BOOT_LOADER
+ #endif ///< end of defined(uG31xx_OS_ANDROID)
+
+ UG31_LOGI("[%s]: Backp file status = %d\n", __func__, data->backupFileSts);
+}
+
+/**
+ * @brief UpiUpdateSuspendData
+ *
+ * Update data for suspend backup
+ *
+ * @para data address of BackupDataType
+ * @para is_resume UPI_TRUE if called in resume
+ * @return NULL
+ */
+void UpiUpdateSuspendData(BackupDataType *data, _backup_bool_ is_resume)
+{
+ if(is_resume == _UPI_TRUE_)
+ {
+ data->logData[data->backupDataIdx]->afterCapData = *(data->capData);
+ data->logData[data->backupDataIdx]->afterMeasData = *(data->measData);
+ UG31_LOGI("[%s]: Backup resume data to buffer %d\n", __func__, data->backupDataIdx);
+
+ data->backupDataIdx = data->backupDataIdx + 1;
+ if(data->backupDataIdx >= BACKUP_MAX_LOG_SUSPEND_DATA)
+ {
+ data->backupDataIdx = BACKUP_MAX_LOG_SUSPEND_DATA;
+ }
+ }
+ else
+ {
+ if((data->backupDataIdx >= BACKUP_MAX_LOG_SUSPEND_DATA) && (is_resume == _UPI_FALSE_))
+ {
+ data->backupDataIdx = 0;
+ while(data->backupDataIdx < (BACKUP_MAX_LOG_SUSPEND_DATA - 1))
+ {
+ memcpy(data->logData[data->backupDataIdx], data->logData[data->backupDataIdx + 1], sizeof(BackupSuspendDataType));
+ data->backupDataIdx = data->backupDataIdx + 1;
+ }
+ }
+
+ data->logData[data->backupDataIdx]->beforeCapData = *(data->capData);
+ data->logData[data->backupDataIdx]->beforeMeasData = *(data->measData);
+ UG31_LOGI("[%s]: Backup suspend data to buffer %d\n", __func__, data->backupDataIdx);
+ }
+}
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Backup.h b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Backup.h new file mode 100755 index 00000000..03564f3f --- /dev/null +++ b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Backup.h @@ -0,0 +1,79 @@ +/**
+ * @filename uG31xx_API_Backup.h
+ *
+ * Header of uG31xx_API_Backup.cpp
+ *
+ * @author AllenTeng <allen_teng@upi-semi.com>
+ */
+
+#define BACKUP_BOOL_TRUE (1)
+#define BACKUP_BOOL_FALSE (0)
+
+#define BACKUP_FILE_PATH ("/sdcard/upi_gg")
+
+enum BACKUP_FILE_STS {
+ BACKUP_FILE_STS_CHECKING = 0,
+ BACKUP_FILE_STS_NOT_EXIST,
+ BACKUP_FILE_STS_EXIST,
+ BACKUP_FILE_STS_COMPARE,
+};
+
+typedef unsigned char _backup_bool_;
+typedef unsigned char _backup_u8_;
+typedef unsigned long _backup_u32_;
+
+typedef struct BackupSuspendDataST {
+ CapacityDataType beforeCapData;
+ MeasDataType beforeMeasData;
+
+ CapacityDataType afterCapData;
+ MeasDataType afterMeasData;
+} __attribute__ ((packed)) BackupSuspendDataType;
+
+#define BACKUP_MAX_LOG_SUSPEND_DATA (8)
+
+typedef struct BackupDataST {
+
+ CapacityDataType *capData;
+ SystemDataType *sysData;
+ MeasDataType *measData;
+
+ _backup_bool_ icDataAvailable;
+ _backup_u8_ backupFileSts;
+ _backup_u32_ backupFileVer;
+ _backup_u32_ targetFileVer;
+
+ _backup_u8_ backupDataIdx;
+ BackupSuspendDataType *logData[BACKUP_MAX_LOG_SUSPEND_DATA];
+
+ #if defined (uG31xx_OS_WINDOWS)
+ const wchar_t* backupFileName;
+ #elif defined(uG31xx_OS_ANDROID)
+ char *backupFileName;
+ #endif
+
+#if defined(uG31xx_OS_ANDROID)
+ } __attribute__ ((packed)) BackupDataType;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+ } BackupDataType;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+/**
+ * @brief UpiBackupData
+ *
+ * Backup data from IC to system routine
+ *
+ * @para data address of BackupDataType
+ * @return _UPI_NULL_
+ */
+extern void UpiBackupData(BackupDataType *data);
+
+/**
+ * @brief UpiUpdateSuspendData
+ *
+ * Update data for suspend backup
+ *
+ * @para data address of BackupDataType
+ * @return NULL
+ */
+extern void UpiUpdateSuspendData(BackupDataType *data, _backup_bool_ is_resume);
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Capacity.b b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Capacity.b Binary files differnew file mode 100755 index 00000000..52317121 --- /dev/null +++ b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Capacity.b diff --git a/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Capacity.h b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Capacity.h new file mode 100755 index 00000000..a45ea7b4 --- /dev/null +++ b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Capacity.h @@ -0,0 +1,143 @@ +/**
+ * @filename uG31xx_API_Capacity.h
+ *
+ * Header of uG31xx capacity algorithm
+ *
+ * @author AllenTeng <allen_teng@upi-semi.com>
+ */
+
+typedef _upi_bool_ _cap_bool_;
+typedef unsigned char _cap_u8_;
+typedef signed char _cap_s8_;
+typedef unsigned short _cap_u16_;
+typedef signed short _cap_s16_;
+typedef unsigned long _cap_u32_;
+typedef signed long _cap_s32_;
+
+#define CAP_FC_RELEASE_RSOC (99)
+#define UG31XX_CAP_VERSION (36)
+#define CAP_ENCRIPT_TABLE_SIZE (TEMPERATURE_NUMS*C_RATE_NUMS*(SOV_NUMS - 1))
+
+typedef struct CapacityDataST {
+
+ /// [AT-PM] : Data from GGB file ; 01/25/2013
+ CELL_PARAMETER *ggbParameter;
+ CELL_TABLE *ggbTable;
+
+ /// [AT-PM] : Measurement data ; 01/25/2013
+ MeasDataType *measurement;
+
+ /// [AT-PM] : Data for table backup ; 01/31/2013
+ TableBackupType tableBackup[SOV_NUMS];
+ _cap_u8_ encriptTable[CAP_ENCRIPT_TABLE_SIZE];
+
+ /// [AT-PM] : Capacity information ; 01/25/2013
+ _cap_u16_ rm;
+ _cap_u16_ fcc;
+ _cap_u16_ fccBackup;
+ _cap_u8_ rsoc;
+
+ /// [AT-PM] : Capacity operation variables ; 01/25/2013
+ _cap_u32_ status;
+
+ _cap_u32_ selfDsgMilliSec;
+ _cap_u8_ selfDsgSec;
+ _cap_u8_ selfDsgMin;
+ _cap_u8_ selfDsgHour;
+ _cap_u8_ selfDsgResidual;
+
+ _cap_u8_ lastRsoc;
+
+ _cap_u32_ tpTime;
+
+ _cap_s32_ dsgCharge;
+ _cap_s32_ dsgChargeStart;
+ _cap_u32_ dsgChargeTime;
+ _cap_s32_ preDsgCharge;
+
+ _cap_u8_ tableUpdateIdx;
+ _cap_u32_ tableUpdateDisqTime;
+ _cap_u8_ tableUpdateDelayCnt;
+
+ _cap_s8_ parseRMResidual;
+
+ _cap_s16_ reverseCap;
+ _cap_u8_ avgCRate;
+
+ _cap_s16_ ccRecord[SOV_NUMS];
+#if defined(uG31xx_OS_ANDROID)
+ } __attribute__ ((packed)) CapacityDataType;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+ } CapacityDataType;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+/**
+ * @brief UpiInitCapacity
+ *
+ * Initial capacity algorithm
+ *
+ * @para data address of CapacityDataType
+ * @return _UPI_NULL_
+ */
+extern void UpiInitCapacity(CapacityDataType *data);
+
+/**
+ * @brief UpiReadCapacity
+ *
+ * Read capacity information
+ *
+ * @para data address of CapacityDataType
+ * @return _UPI_NULL_
+ */
+extern void UpiReadCapacity(CapacityDataType *data);
+
+/**
+ * @brief UpiTableCapacity
+ *
+ * Look up capacity from table
+ *
+ * @para data address of CapacityDataType
+ * @return _UPI_NULL_
+ */
+extern void UpiTableCapacity(CapacityDataType *data);
+
+/**
+ * @brief UpiInitDsgCharge
+ *
+ * Initialize data->dsgCharge value
+ *
+ * @para data address of CapacityDataType
+ * @return _UPI_NULL_
+ */
+extern void UpiInitDsgCharge(CapacityDataType *data);
+
+/**
+ * @brief UpiInitNacTable
+ *
+ * Initialize NAC table
+ *
+ * @para data address of CapacityDataType
+ * @return _UPI_NULL_
+ */
+extern void UpiInitNacTable(CapacityDataType *data);
+
+/**
+ * @brief UpiSaveNacTable
+ *
+ * Save NAC table to IC
+ *
+ * @para data address of CapacityDataType
+ * @return _UPI_NULL_
+ */
+extern void UpiSaveNacTable(CapacityDataType *data);
+
+/**
+ * @brief CalculateRsoc
+ *
+ * RSOC = RM x 100 / FCC
+ *
+ * @para rm remaining capacity
+ * @para fcc full charged capacity
+ * @return relative state of charge
+ */
+extern _cap_u8_ CalculateRsoc(_cap_u16_ rm, _cap_u16_ fcc);
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Measurement.c b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Measurement.c new file mode 100755 index 00000000..29f5072c --- /dev/null +++ b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Measurement.c @@ -0,0 +1,1389 @@ +/**
+ * @filename uG31xx_API_Measurement.cpp
+ *
+ * guG31xx measurement API
+ *
+ * @author AllenTeng <allen_teng@upi-semi.com>
+ */
+
+#include "stdafx.h" //windows need this??
+#include "uG31xx_API.h"
+
+//#define MEAS_FAKE_INT_TEMP
+#ifdef MEAS_FAKE_INT_TEMP
+ #define MEAS_FAKE_INT_TEMP_OFFSET (200)
+#endif ///< end of MEAS_FAKE_INT_TEMP
+
+typedef struct MeasDataInternalST {
+
+ MeasDataType *info;
+
+ _meas_s16_ adc1CodeT25V100;
+ _meas_s16_ adc1CodeT25V200;
+ _meas_s16_ adc1CodeT80V100;
+ _meas_s16_ adc1CodeT80V200;
+
+ _meas_s16_ adc2CodeT25V100;
+ _meas_s16_ adc2CodeT25V200;
+ _meas_s16_ adc2CodeT80V100;
+ _meas_s16_ adc2CodeT80V200;
+
+ _meas_u32_ currTime;
+
+ _meas_u16_ codeBat1;
+ _meas_s16_ codeCurrent;
+ _meas_u16_ codeIntTemperature;
+ _meas_u16_ codeExtTemperature;
+ _meas_s16_ codeCharge;
+ _meas_u16_ codeCounter;
+ _meas_s16_ ccOffset;
+
+#if defined(uG31xx_OS_ANDROID)
+ } __attribute__ ((packed)) MeasDataInternalType;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+ } MeasDataInternalType;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+typedef struct AdcDeltaCodeMappingST {
+ _meas_s32_ Adc1V100;
+ _meas_s32_ Adc1V200;
+ _meas_s32_ Adc2V100;
+ _meas_s32_ Adc2V200;
+#if defined(uG31xx_OS_ANDROID)
+}__attribute__((packed)) AdcDeltaCodeMappingType;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+} AdcDeltaCodeMappingType;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+static AdcDeltaCodeMappingType AdcDeltaCodeMapping[] =
+{
+ { -12800, -25600, 1536, 0 }, ///< Index = 0
+ { -12544, -25088, 1600, 128 }, ///< Index = 1
+ { -13056, -26112, 1472, -128 }, ///< Index = 2
+ { -12288, -24576, 1664, 256 }, ///< Index = 3
+ { -13312, -26624, 1408, -256 }, ///< Index = 4
+ { -12032, -24064, 1728, 384 }, ///< Index = 5
+ { -13568, -27136, 1344, -384 }, ///< Index = 6
+ { -11776, -23552, 1792, 512 }, ///< Index = 7
+ { -13824, -27648, 1280, -512 }, ///< Index = 8
+ { -11520, -23040, 1856, 640 }, ///< Index = 9
+ { -14080, -28160, 1216, -640 }, ///< Index = 10
+ { -11264, -22528, 1920, 768 }, ///< Index = 11
+ { -14336, -28672, 1152, -768 }, ///< Index = 12
+ { -11008, -22016, 1984, 896 }, ///< Index = 13
+ { -14592, -29184, 1088, -896 }, ///< Index = 14
+ { -10752, -21504, 2048, 1024 }, ///< Index = 15
+ { -14848, -29696, 1024, -1024 }, ///< Index = 16
+ { -10496, -20992, 2112, 1152 }, ///< Index = 17
+ { -15104, -30208, 960, -1152 }, ///< Index = 18
+ { -10240, -20480, 2176, 1280 }, ///< Index = 19
+ { -15360, -30720, 896, -1280 }, ///< Index = 20
+ { -9984, -19968, 2240, 1408 }, ///< Index = 21
+ { -15616, -31232, 832, -1408 }, ///< Index = 22
+ { -9728, -19456, 2304, 1536 }, ///< Index = 23
+ { -15872, -31744, 768, -1536 }, ///< Index = 24
+ { -9472, -18944, 2368, 1664 }, ///< Index = 25
+ { -16128, -32256, 704, -1664 }, ///< Index = 26
+ { -9216, -18432, 2432, 1792 }, ///< Index = 27
+ { -16384, -32768, 640, -1792 }, ///< Index = 28
+ { -8960, -17920, 2496, 1920 }, ///< Index = 29
+ { -16640, -33280, 576, -1920 }, ///< Index = 30
+ { 0, 0, 0, 0 }, ///< Index = 31
+};
+
+#define ADC_TEMPERATURE_GAIN_CONST (1000)
+
+#define ADC1_CODE_100MV_NEGATIVE (0xFF00)
+#define ADC1_CODE_200MV_NEGATIVE (0xFE00)
+#define ADC1_CP_CODE_25_100MV (12288)
+#define ADC1_CP_CODE_25_200MV (24576)
+#define ADC1_DELTA_CODE_25_100MV_SIGN_BIT (1<<8)
+#define ADC1_DELTA_CODE_25_200MV_SIGN_BIT (1<<9)
+#define ADC1_TEMPERATURE_GAIN_100MV (869600)
+#define ADC1_TEMPERATURE_GAIN_200MV (-695680)
+
+/**
+ * @brief ConvertAdc1Data
+ *
+ * Convert ADC1 data from OTP
+ *
+ * @para obj address of MeasDataInternalType
+ * @return _UPI_NULL_
+ */
+void ConvertAdc1Data(MeasDataInternalType *obj)
+{
+ _meas_u16_ tmp16;
+ _meas_s32_ tmp32;
+
+ /// [AT-PM] : Get code T25 100mV ; 01/23/2013
+ tmp16 = obj->info->otp->adc1DeltaCodeT25V100;
+ if(tmp16 & ADC1_DELTA_CODE_25_100MV_SIGN_BIT)
+ {
+ tmp16 = tmp16 & (~ADC1_DELTA_CODE_25_100MV_SIGN_BIT);
+ if(tmp16 != 0)
+ {
+ tmp16 = tmp16 + ADC1_CODE_100MV_NEGATIVE;
+ }
+ }
+ tmp16 = tmp16 + ADC1_CP_CODE_25_100MV;
+ tmp32 = (_meas_s32_)(_meas_s16_)tmp16;
+ tmp32 = tmp32 + AdcDeltaCodeMapping[obj->info->otp->indexAdc1V100T25].Adc1V100;
+ obj->adc1CodeT25V100 = (_meas_s16_)tmp32;
+
+ /// [AT-PM] : Get code T25 200mV ; 01/23/2013
+ tmp16 = obj->info->otp->adc1DeltaCodeT25V200;
+ if(tmp16 & ADC1_DELTA_CODE_25_200MV_SIGN_BIT)
+ {
+ tmp16 = tmp16 & (~ADC1_DELTA_CODE_25_200MV_SIGN_BIT);
+ if(tmp16 != 0)
+ {
+ tmp16 = tmp16 + ADC1_CODE_200MV_NEGATIVE;
+ }
+ }
+ tmp16 = tmp16 + ADC1_CP_CODE_25_200MV;
+ tmp32 = (_meas_s32_)(_meas_s16_)tmp16;
+ tmp32 = tmp32 + AdcDeltaCodeMapping[obj->info->otp->indexAdc1V200T25].Adc1V200;
+ obj->adc1CodeT25V200 = (_meas_s16_)tmp32;
+
+ tmp32 = (_meas_s32_)obj->info->otp->aveIT80;
+ tmp32 = (tmp32 - obj->info->otp->aveIT25)*ADC_TEMPERATURE_GAIN_CONST;
+
+ /// [AT-PM] : Get code T80 100mV ; 01/23/2013
+ obj->adc1CodeT80V100 = (_meas_s16_)(tmp32/ADC1_TEMPERATURE_GAIN_100MV + obj->adc1CodeT25V100);
+
+ /// [AT-PM] : Get code T80 200mV ; 01/23/2013
+ obj->adc1CodeT80V200 = (_meas_s16_)(tmp32/ADC1_TEMPERATURE_GAIN_200MV + obj->adc1CodeT25V200);
+}
+
+#define ADC2_CODE_100MV_NEGATIVE (0xFFC0)
+#define ADC2_CODE_200MV_NEGATIVE (0xFF80)
+#define ADC2_CP_CODE_25_100MV (3072)
+#define ADC2_CP_CODE_25_200MV (6144)
+#define ADC2_DELTA_CODE_25_100MV_SIGN_BIT (1<<6)
+#define ADC2_DELTA_CODE_25_200MV_SIGN_BIT (1<<7)
+#define ADC2_TEMPERATURE_GAIN_100MV (-149130)
+#define ADC2_TEMPERATURE_GAIN_200MV (-136937)
+
+/**
+ * @brief ConvertAdc2Data
+ *
+ * Convert ADC2 data from OTP
+ *
+ * @para obj address of MeasDataInternalType
+ * @return _UPI_NULL_
+ */
+void ConvertAdc2Data(MeasDataInternalType *obj)
+{
+ _meas_u16_ tmp16;
+ _meas_s32_ tmp32;
+
+ /// [AT-PM] : Get code T25 100mV ; 01/23/2013
+ tmp16 = obj->info->otp->adc2DeltaCodeT25V100;
+ if(tmp16 & ADC2_DELTA_CODE_25_100MV_SIGN_BIT)
+ {
+ tmp16 = tmp16 & (~ADC2_DELTA_CODE_25_100MV_SIGN_BIT);
+ tmp16 = tmp16 + ADC2_CODE_100MV_NEGATIVE;
+ }
+ tmp16 = tmp16 + ADC2_CP_CODE_25_100MV;
+ tmp32 = (_meas_s32_)(_meas_s16_)tmp16;
+ tmp32 = tmp32 + AdcDeltaCodeMapping[obj->info->otp->indexAdc2V100T25].Adc2V100;
+ obj->adc2CodeT25V100 = (_meas_s16_)tmp32;
+
+ /// [AT-PM] : Get code T25 200mV ; 01/23/2013
+ tmp16 = obj->info->otp->adc2DeltaCodeT25V200;
+ if(tmp16 & ADC2_DELTA_CODE_25_200MV_SIGN_BIT)
+ {
+ tmp16 = tmp16 & (~ADC2_DELTA_CODE_25_200MV_SIGN_BIT);
+ tmp16 = tmp16 + ADC2_CODE_200MV_NEGATIVE;
+ }
+ tmp16 = tmp16 + ADC2_CP_CODE_25_200MV;
+ tmp32 = (_meas_s32_)(_meas_s16_)tmp16;
+ tmp32 = tmp32 + AdcDeltaCodeMapping[obj->info->otp->indexAdc2V200T25].Adc2V200;
+ obj->adc2CodeT25V200 = (_meas_s16_)tmp32;
+
+ tmp32 = (_meas_s32_)obj->info->otp->aveIT80;
+ tmp32 = (tmp32 - obj->info->otp->aveIT25)*ADC_TEMPERATURE_GAIN_CONST;
+
+ /// [AT-PM] : Get code T80 100mV ; 01/23/2013
+ obj->adc2CodeT80V100 = (_meas_s16_)(tmp32/ADC2_TEMPERATURE_GAIN_100MV + obj->adc2CodeT25V100);
+
+ /// [AT-PM] : Get code T80 200mV ; 01/23/2013
+ obj->adc2CodeT80V200 = (_meas_s16_)(tmp32/ADC2_TEMPERATURE_GAIN_200MV + obj->adc2CodeT25V200);
+}
+
+/**
+ * @brief CalAdc1Factors
+ *
+ * Calculate ADC1 gain slope and factor B
+ * Calculate ADC1 offset slope and factor O
+ *
+ * @para obj address of MeasDataInternalType
+ * @return _UPI_NULL_
+ */
+void CalAdc1Factors(MeasDataInternalType *obj)
+{
+ _meas_s32_ delta25;
+ _meas_s32_ delta80;
+ _meas_s64_ tmp64;
+
+ /// [AT-PM] : Calculate gain slope and factor B ; 01/23/2013
+ delta25 = (_meas_s32_)obj->adc1CodeT25V200;
+ delta25 = delta25 - obj->adc1CodeT25V100;
+ delta80 = (_meas_s32_)obj->adc1CodeT80V200;
+ delta80 = delta80 - obj->adc1CodeT80V100;
+
+ obj->info->adc1GainSlope = delta80 - delta25;
+ obj->info->adc1GainFactorB = delta25*(obj->info->otp->aveIT80) - delta80*(obj->info->otp->aveIT25);
+
+ /// [AT-PM] : Calculate offset slope and factor O ; 01/23/2013
+ delta25 = (_meas_s32_)obj->adc1CodeT25V100;
+ delta25 = delta25*2 - obj->adc1CodeT25V200;
+ delta80 = (_meas_s32_)obj->adc1CodeT80V100;
+ delta80 = delta80*2 - obj->adc1CodeT80V200;
+
+ obj->info->adc1OffsetSlope = delta80 - delta25;
+ obj->info->adc1OffsetFactorO = delta25*(obj->info->otp->aveIT80) - delta80*(obj->info->otp->aveIT25);
+
+ /// [AT-PM] : Calculate current ADC1 gain ; 01/23/2013
+ tmp64 = (_meas_s64_)obj->info->adc1GainSlope;
+ tmp64 = tmp64*(obj->codeIntTemperature) + obj->info->adc1GainFactorB;
+ obj->info->adc1Gain = (_meas_s32_)tmp64;
+
+ /// [AT-PM] : Calculate current ADC1 offset ; 01/23/2013
+ tmp64 = (_meas_s64_)obj->info->adc1OffsetSlope;
+ tmp64 = tmp64*(obj->codeIntTemperature) + obj->info->adc1OffsetFactorO;
+ obj->info->adc1Offset = (_meas_s32_)tmp64;
+}
+
+/**
+ * @brief CalAdc2Factors
+ *
+ * Calculate ADC2 gain slope and factor B
+ * Calculate ADC2 offset slope and factor O
+ *
+ * @para obj address of MeasDataInternalType
+ * @return _UPI_NULL_
+ */
+void CalAdc2Factors(MeasDataInternalType *obj)
+{
+ _meas_s32_ delta25;
+ _meas_s32_ delta80;
+ _meas_s64_ tmp64;
+
+ /// [AT-PM] : Calculate gain slope and factor B ; 01/23/2013
+ delta25 = (_meas_s32_)obj->adc2CodeT25V200;
+ delta25 = delta25 - obj->adc2CodeT25V100;
+ delta80 = (_meas_s32_)obj->adc2CodeT80V200;
+ delta80 = delta80 - obj->adc2CodeT80V100;
+
+ obj->info->adc2GainSlope = delta80 - delta25;
+ obj->info->adc2GainFactorB = delta25*(obj->info->otp->aveIT80) - delta80*(obj->info->otp->aveIT25);
+
+ /// [AT-PM] : Calculate offset slope and factor O ; 01/23/2013
+ delta25 = (_meas_s32_)obj->adc2CodeT25V100;
+ delta25 = delta25*2 - obj->adc2CodeT25V200;
+ delta80 = (_meas_s32_)obj->adc2CodeT80V100;
+ delta80 = delta80*2 - obj->adc2CodeT80V200;
+
+ obj->info->adc2OffsetSlope = delta80 - delta25;
+ obj->info->adc2OffsetFactorO = delta25*(obj->info->otp->aveIT80) - delta80*(obj->info->otp->aveIT25);
+
+ /// [AT-PM] : Calculate current ADC1 gain ; 01/23/2013
+ tmp64 = (_meas_s64_)obj->info->adc2GainSlope;
+ tmp64 = tmp64*(obj->codeIntTemperature) + obj->info->adc2GainFactorB;
+ obj->info->adc2Gain = (_meas_s32_)tmp64;
+
+ /// [AT-PM] : Calculate current ADC1 offset ; 01/23/2013
+ tmp64 = (_meas_s64_)obj->info->adc2OffsetSlope;
+ tmp64 = tmp64*(obj->codeIntTemperature) + obj->info->adc2OffsetFactorO;
+ obj->info->adc2Offset = (_meas_s32_)tmp64;
+}
+
+#define ADC1_IDEAL_CODE_100MV (614)
+#define ADC1_IDEAL_CODE_200MV (1229)
+#define ADC1_IDEAL_CODE_DELTA (ADC1_IDEAL_CODE_200MV - ADC1_IDEAL_CODE_100MV)
+
+/**
+ * @brief CalibrateAdc1Code
+ *
+ * Calibrate ADC1 code
+ *
+ * @para obj address of MeasDataInternalType
+ * @para code ADC1 code to be calibrated
+ * @return calibrated code
+ */
+_meas_s32_ CalibrateAdc1Code(MeasDataInternalType *obj, _meas_s32_ code)
+{
+ _meas_s64_ tmp64;
+ _meas_s32_ tmp32;
+ _meas_s32_ deltaIT;
+ _meas_s32_ gain;
+ _meas_s32_ offset;
+
+ deltaIT = (_meas_s32_)obj->info->otp->aveIT80;
+ deltaIT = deltaIT - obj->info->otp->aveIT25;
+
+ /// [AT-PM] : Pre-operation to avoid 64-bit division ; 01/23/2013
+ gain = obj->info->adc1Gain;
+ offset = obj->info->adc1Offset;
+ while(1)
+ {
+ tmp64 = (_meas_s64_)code;
+ tmp64 = tmp64*deltaIT - offset;
+ tmp64 = tmp64*ADC1_IDEAL_CODE_DELTA;
+ if((tmp64 < 2147483647) && (tmp64 > -2147483647))
+ {
+ break;
+ }
+ code = code/2;
+ deltaIT = deltaIT/2;
+ gain = gain/4;
+ offset = offset/4;
+ }
+
+ tmp32 = (_meas_s32_)tmp64;
+ tmp32 = tmp32/gain;
+ return (tmp32);
+}
+
+#define ADC2_IDEAL_CODE_100MV (ADC2_CP_CODE_25_100MV)
+#define ADC2_IDEAL_CODE_200MV (ADC2_CP_CODE_25_200MV)
+#define ADC2_IDEAL_CODE_DELTA (ADC2_IDEAL_CODE_200MV - ADC2_IDEAL_CODE_100MV)
+
+/**
+ * @brief CalibrateAdc2Code
+ *
+ * Calibrate ADC2 code
+ *
+ * @para obj address of MeasDataInternalType
+ * @para code ADC2 code to be calibrated
+ * @return calibrated code
+ */
+_meas_s32_ CalibrateAdc2Code(MeasDataInternalType *obj, _meas_s32_ code)
+{
+ _meas_s64_ tmp64;
+ _meas_s32_ tmp32;
+ _meas_s32_ deltaIT;
+ _meas_s32_ gain;
+ _meas_s32_ offset;
+
+ deltaIT = (_meas_s32_)obj->info->otp->aveIT80;
+ deltaIT = deltaIT - obj->info->otp->aveIT25;
+
+ /// [AT-PM] : Pre-operation to avoid 64-bit division ; 01/23/2013
+ gain = obj->info->adc2Gain;
+ offset = obj->info->adc2Offset;
+ while(1)
+ {
+ tmp64 = (_meas_s64_)code;
+ tmp64 = tmp64*deltaIT - offset;
+ tmp64 = tmp64*ADC2_IDEAL_CODE_DELTA;
+ if((tmp64 < 2147483647) && (tmp64 > -2147483647))
+ {
+ break;
+ }
+ code = code/2;
+ deltaIT = deltaIT/2;
+ gain = gain/4;
+ offset = offset/4;
+ }
+
+ tmp32 = (_meas_s32_)tmp64;
+ tmp32 = tmp32/gain;
+ return (tmp32);
+}
+
+#define IT_IDEAL_CODE_25 (24310)
+#define IT_IDEAL_CODE_80 (28612)
+#define IT_IDEAL_CODE_DELTA (IT_IDEAL_CODE_80 - IT_IDEAL_CODE_25)
+
+/**
+ * @brief CalibrateITCode
+ *
+ * Calibrate internal temperature code
+ *
+ * @para obj address of MeasDataInternalType
+ * @para itCode raw IT code
+ * @return calibrated IT code
+ */
+_meas_u16_ CalibrateITCode(MeasDataInternalType *obj, _meas_u16_ itCode)
+{
+ _meas_s32_ tmp32;
+
+ tmp32 = (_meas_s32_)itCode;
+ tmp32 = tmp32 - obj->info->otp->aveIT25;
+ tmp32 = tmp32*IT_IDEAL_CODE_DELTA;
+ tmp32 = tmp32/(obj->info->otp->aveIT80 - obj->info->otp->aveIT25);
+ tmp32 = tmp32 + IT_IDEAL_CODE_25;
+ return ((_meas_u16_)tmp32);
+}
+
+#define NORMAL_REGISTER (NORMAL)
+
+/**
+ * @brief CalibrateChargeCode
+ *
+ * Calibrate charge code
+ *
+ * @para obj address of MeasDataInternalType
+ * @return _UPI_NULL_
+ */
+void CalibrateChargeCode(MeasDataInternalType *obj)
+{
+ _meas_s32_ tmp32;
+ _meas_s64_ tmp64;
+ _meas_s32_ gain;
+ _meas_s32_ offset;
+
+ UG31_LOGI("[%s]: Raw Code = %d\n", __func__, obj->codeCharge);
+
+ /// [AT-PM] : Calibrate charge code ; 01/23/2013
+ obj->info->rawCodeCharge = obj->codeCharge;
+ obj->info->codeCharge = CalibrateAdc1Code(obj, ((_meas_s32_)obj->codeCharge)*2);
+
+ /// [AT-PM] : Calculate coulomb counter offset ; 01/23/2013
+ tmp32 = obj->info->adc1Offset/(obj->info->otp->aveIT80 - obj->info->otp->aveIT25)*(-1);
+ tmp32 = tmp32 + obj->info->ccOffsetAdj;
+ obj->info->ccOffset = (_meas_s16_)tmp32;
+
+ UG31_LOGI("[%s]: Calibrated Code = %d, Offset = %d (%d)\n", __func__, obj->info->codeCharge, obj->info->ccOffset, obj->info->ccOffsetAdj);
+
+ /// [AT-PM] : Set coulomb counter offset ; 01/27/2013
+ API_I2C_Write(NORMAL_REGISTER,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_ADC1_OFFSET_LOW,
+ REG_ADC1_OFFSET_HIGH - REG_ADC1_OFFSET_LOW + 1,
+ (unsigned char *)&obj->info->ccOffset);
+
+ /// [AT-PM] : Remove the offset in calibrated charge code ; 01/23/2013
+ gain = obj->info->adc1Gain;
+ offset = obj->info->adc1Offset;
+ while(1)
+ {
+ tmp64 = (_meas_s64_)offset;
+ tmp64 = tmp64*ADC1_IDEAL_CODE_DELTA;
+ if((tmp64 < 2147483647) && (tmp64 > -2147483647))
+ {
+ break;
+ }
+ gain = gain/2;
+ offset = offset/2;
+ }
+ tmp32 = (_meas_s32_)tmp64;
+ tmp32 = tmp32/gain;
+ UG31_LOGI("[%s]: Compensation = %d x %d / %d\n", __func__,
+ obj->info->adc1Offset, ADC1_IDEAL_CODE_DELTA, obj->info->adc1Gain);
+ obj->info->codeCharge = obj->info->codeCharge + tmp32;
+ UG31_LOGI("[%s]: Charge = %d\n", __func__, obj->info->codeCharge);
+}
+
+#define ADC2_VOLTAGE_100MV (3000) ///< [AT-PM] : Unit in mV ; 01/25/2013
+#define ADC2_VOLTAGE_200MV (4000) ///< [AT-PM] : Unit in mV ; 01/25/2013
+#define ADC2_VOLTAGE_DELTA (ADC2_VOLTAGE_200MV - ADC2_VOLTAGE_100MV)
+
+/**
+ * @brief ConvertBat1
+ *
+ * Convert code of BAT1
+ *
+ * @para obj address of MeasDataInternalType
+ * @return _UPI_NULL_
+ */
+void ConvertBat1(MeasDataInternalType *obj)
+{
+ _meas_s32_ tmp32;
+
+ /// [AT-PM] : Convert from calibrated ADC code ; 01/25/2013
+ tmp32 = (_meas_s32_)obj->info->codeBat1;
+ tmp32 = tmp32 - ADC2_IDEAL_CODE_100MV;
+ tmp32 = tmp32*ADC2_VOLTAGE_DELTA/ADC2_IDEAL_CODE_DELTA;
+ tmp32 = tmp32 + ADC2_VOLTAGE_100MV;
+
+ /// [AT-PM] : Apply board factor ; 01/25/2013
+ tmp32 = tmp32 - BOARD_FACTOR_VOLTAGE_OFFSET;
+ tmp32 = tmp32*BOARD_FACTOR_CONST/BOARD_FACTOR_VOLTAGE_GAIN;
+
+ /// [AT-PM] : Apply calibration parameter ; 01/25/2013
+ tmp32 = tmp32 - obj->info->sysData->ggbParameter->adc2_offset;
+ tmp32 = tmp32*CALIBRATION_FACTOR_CONST/obj->info->sysData->ggbParameter->adc2_gain;
+ obj->info->bat1Voltage = (_meas_u16_)tmp32;
+}
+
+#define ADC1_VOLTAGE_100MV (-5000) ///< [AT-PM] : Unit in uV ; 01/25/2013
+#define ADC1_VOLTAGE_200MV (-10000) ///< [AT-PM] : Unit in uV ; 01/25/2013
+#define ADC1_VOLTAGE_DELTA (ADC1_VOLTAGE_200MV - ADC1_VOLTAGE_100MV)
+/**
+ * @brief ConvertCurrent
+ *
+ * Convert code of Current
+ *
+ * @para obj address of MeasDataInternalType
+ * @return _UPI_NULL_
+ */
+void ConvertCurrent(MeasDataInternalType *obj)
+{
+ _meas_s32_ tmp32;
+
+ /// [AT-PM] : Convert from calibrated ADC code ; 01/25/2013
+ tmp32 = (_meas_s32_)obj->info->codeCurrent;
+ tmp32 = tmp32 - ADC1_IDEAL_CODE_100MV;
+ tmp32 = tmp32*ADC1_VOLTAGE_DELTA/ADC1_IDEAL_CODE_DELTA;
+ tmp32 = tmp32 + ADC1_VOLTAGE_100MV;
+ tmp32 = tmp32/obj->info->sysData->ggbParameter->rSense;
+
+ /// [AT-PM] : Apply board factor ; 01/25/2013
+ tmp32 = tmp32 - BOARD_FACTOR_CURR_OFFSET;
+ tmp32 = tmp32*BOARD_FACTOR_CONST/BOARD_FACTOR_CURR_GAIN;
+
+ /// [AT-PM] : Apply calibration factor ; 01/25/2013
+ tmp32 = tmp32 - obj->info->sysData->ggbParameter->adc1_pos_offset;
+ if(tmp32 > 0)
+ {
+ tmp32 = tmp32*CALIBRATION_FACTOR_CONST/obj->info->sysData->ggbParameter->adc1_pgain;
+ }
+ else
+ {
+ tmp32 = tmp32*CALIBRATION_FACTOR_CONST/obj->info->sysData->ggbParameter->adc1_ngain;
+ }
+ obj->info->curr = (_meas_s16_)tmp32;
+}
+
+#define AMBIENT_TEMPERATURE_IN_FT (220)
+#define IT_CONST (100)
+#define IT_GAIN (392)
+#define IT_OFFSET (11172)
+
+static _meas_s16_ FTAmbientMappingTable[] =
+{
+ AMBIENT_TEMPERATURE_IN_FT, ///< Index = 0
+ AMBIENT_TEMPERATURE_IN_FT + 10, ///< Index = 1
+ AMBIENT_TEMPERATURE_IN_FT - 10, ///< Index = 2
+ AMBIENT_TEMPERATURE_IN_FT + 20, ///< Index = 3
+ AMBIENT_TEMPERATURE_IN_FT - 20, ///< Index = 4
+ AMBIENT_TEMPERATURE_IN_FT + 30, ///< Index = 5
+ AMBIENT_TEMPERATURE_IN_FT - 30, ///< Index = 6
+ AMBIENT_TEMPERATURE_IN_FT + 40, ///< Index = 7
+ AMBIENT_TEMPERATURE_IN_FT - 40, ///< Index = 8
+ AMBIENT_TEMPERATURE_IN_FT + 50, ///< Index = 9
+ AMBIENT_TEMPERATURE_IN_FT - 50, ///< Index = 10
+ AMBIENT_TEMPERATURE_IN_FT + 60, ///< Index = 11
+ AMBIENT_TEMPERATURE_IN_FT - 60, ///< Index = 12
+ AMBIENT_TEMPERATURE_IN_FT + 70, ///< Index = 13
+ AMBIENT_TEMPERATURE_IN_FT - 70, ///< Index = 14
+ AMBIENT_TEMPERATURE_IN_FT, ///< Index = 15
+};
+
+/**
+ * @brief ConvertIntTemperature
+ *
+ * Convert code of internal temperature
+ *
+ * @para obj address of MeasDataInternalType
+ * @return _UPI_NULL_
+ */
+void ConvertIntTemperature(MeasDataInternalType *obj)
+{
+ _meas_s32_ tmp32;
+ _meas_s32_ ftIT;
+
+ /// [AT-PM] : Convert from calibrated ADC code ; 01/25/2013
+ tmp32 = (_meas_s32_)obj->info->codeIntTemperature;
+ tmp32 = tmp32/2;
+ tmp32 = tmp32 - IT_OFFSET;
+ tmp32 = tmp32*IT_CONST/IT_GAIN;
+
+ /// [AT-PM] : Apply FT information ; 01/25/2013
+ ftIT = (_meas_s32_)CalibrateITCode(obj, obj->info->otp->ftIT);
+ ftIT = ftIT/2;
+ ftIT = ftIT - IT_OFFSET;
+ ftIT = ftIT*IT_CONST/IT_GAIN;
+ tmp32 = tmp32 - (ftIT - FTAmbientMappingTable[obj->info->otp->deltaET]);
+
+ /// [AT-PM] : Apply board factor ; 01/25/2013
+ tmp32 = tmp32 - BOARD_FACTOR_INTT_OFFSET;
+
+ /// [AT-PM] : Apply calibration factor ; 01/25/2013
+ tmp32 = tmp32 - obj->info->sysData->ggbParameter->adc_d5;
+ obj->info->intTemperature = (_meas_s16_)tmp32;
+}
+
+static _meas_s16_ ExtTemperatureTable[] = {
+ -100, ///< Index = 0
+ -50, ///< Index = 1
+ 0, ///< Index = 2
+ 50, ///< Index = 3
+ 100, ///< Index = 4
+ 150, ///< Index = 5
+ 200, ///< Index = 6
+ 250, ///< Index = 7
+ 300, ///< Index = 8
+ 350, ///< Index = 9
+ 400, ///< Index = 10
+ 450, ///< Index = 11
+ 500, ///< Index = 12
+ 550, ///< Index = 13
+ 600, ///< Index = 14
+ 650, ///< Index = 15
+ 700, ///< Index = 16
+ 750, ///< Index = 17
+ 800, ///< Index = 18
+};
+
+/**
+ * @brief ConvertExtTemperature
+ *
+ * Convert code of external temperature
+ *
+ * @para obj address of MeasDataInternalType
+ * @return _UPI_NULL_
+ */
+void ConvertExtTemperature(MeasDataInternalType *obj)
+{
+ _meas_u8_ idx;
+ _meas_s32_ tmp32;
+
+ idx = 0;
+ while(idx < ET_NUMS)
+ {
+ if(obj->info->codeExtTemperature >= obj->info->sysData->ggbParameter->rtTable[idx])
+ {
+ break;
+ }
+ idx = idx + 1;
+ }
+
+ if(idx == 0)
+ {
+ /// [AT-PM] : Minimum measurable temperature ; 01/25/2013
+ tmp32 = (_meas_s32_)ExtTemperatureTable[0];
+ }
+ else if(idx == ET_NUMS)
+ {
+ /// [AT-PM] : Maximum measurable temperature ; 01/25/2013
+ tmp32 = (_meas_s32_)ExtTemperatureTable[ET_NUMS - 1];
+ }
+ else
+ {
+ /// [AT-PM] : Calculate external temperature ; 01/25/2013
+ tmp32 = (_meas_s32_)obj->info->codeExtTemperature;
+ tmp32 = tmp32 - obj->info->sysData->ggbParameter->rtTable[idx];
+ tmp32 = tmp32*(ExtTemperatureTable[idx - 1] - ExtTemperatureTable[idx]);
+ tmp32 = tmp32/(obj->info->sysData->ggbParameter->rtTable[idx - 1] - obj->info->sysData->ggbParameter->rtTable[idx]);
+ tmp32 = tmp32 + ExtTemperatureTable[idx];
+ }
+
+ /// [AT-PM] : Apply board factor ; 01/25/2013
+ tmp32 = tmp32 - BOARD_FACTOR_EXTT_OFFSET;
+
+ /// [AT-PM] : Apply calibration factor ; 01/25/2013
+ tmp32 = tmp32 - obj->info->sysData->ggbParameter->adc_d4;
+ obj->info->extTemperature = (_meas_s16_)tmp32;
+}
+
+#define TIME_DEFAULT_ADC1_CONVERT_TIME (1253)
+#define MINIMUM_ADC1_COUNTER_FOR_CONVERT_TIME (10)
+#define MAXIMUM_ADC1_CONVERSION_TIME (0xf8)
+#define MINIMUM_ADC1_CONVERSION_TIME (0x08)
+
+/**
+ * @brief CalculateAdc1ConvertTime
+ *
+ * Calculate ADC1 conversion time
+ *
+ * @para obj address of MeasDataInternalType
+ * @return _UPI_NULL_
+ */
+void CalculateAdc1ConvertTime(MeasDataInternalType *obj)
+{
+ _meas_u32_ tmp32;
+
+ UG31_LOGI("[%s]: Initial conversion time = %d\n", __func__, obj->info->adc1ConvertTime);
+
+ /// [AT-PM] : First time to calculate ADC1 conversion time ; 01/25/2013
+ if(obj->info->adc1ConvertTime == 0)
+ {
+ obj->info->adc1ConvertTime = TIME_DEFAULT_ADC1_CONVERT_TIME;
+ obj->info->lastCounter = obj->codeCounter;
+ return;
+ }
+
+ #ifdef uG31xx_BOOT_LOADER
+ /// [AT-PM] : In bootloader, ADC1 converstion time is not calculated ; 02/12/2013
+ return;
+ #endif ///< end of uG31xx_BOOT_LOADER
+
+ /// [AT-PM] : Check counter overflow or time overflow; 01/25/2013
+ if((obj->codeCounter <= obj->info->lastCounter) || (obj->info->deltaTime == 0))
+ {
+ obj->info->lastCounter = obj->codeCounter;
+ return;
+ }
+
+ /// [AT-PM] : Limit the minimum counter ; 02/11/2013
+ tmp32 = (_meas_u32_)obj->codeCounter;
+ tmp32 = tmp32 - obj->info->lastCounter;
+ if(tmp32 < MINIMUM_ADC1_COUNTER_FOR_CONVERT_TIME)
+ {
+ obj->info->lastCounter = obj->codeCounter;
+ return;
+ }
+
+ /// [AT-PM] : Average ADC1 conversion time ; 01/25/2013
+ tmp32 = obj->info->deltaTime;
+ tmp32 = tmp32*TIME_CONVERT_TIME_TO_MSEC/(obj->codeCounter - obj->info->lastCounter);
+ tmp32 = tmp32 + obj->info->adc1ConvertTime;
+ tmp32 = tmp32/2;
+
+ /// [AT-PM] : Check conversion time is valid or not ; 02/13/2013
+ if((tmp32 > (MAXIMUM_ADC1_CONVERSION_TIME*TIME_CONVERT_TIME_TO_MSEC)) ||
+ (tmp32 < (MINIMUM_ADC1_CONVERSION_TIME*TIME_CONVERT_TIME_TO_MSEC)))
+ {
+ UG31_LOGI("[%s]: ***************************************************************************************\n", __func__);
+ UG31_LOGI("[%s]: ***************************************************************************************\n", __func__);
+ UG31_LOGI("[%s]: #### ##### ## ## #### ##### ## ## #### ## ###### ###### ## ## ######\n", __func__);
+ UG31_LOGI("[%s]: ## ## ## ## ### ## ## ## ## ## ### ### ## ## ## ###### ## ### ### ##\n", __func__);
+ UG31_LOGI("[%s]: ###### ##### ###### ## ## ##### ####### ###### ## ## ## ####### ###\n", __func__);
+ UG31_LOGI("[%s]: ## ## ## ## ## ### ## ## ## ## ## ## ## ## ## ## ## ## ## ##\n", __func__);
+ UG31_LOGI("[%s]: ## ## ##### ## ## #### ## ## ## ## ## ## ###### ## ###### ## ## ######\n", __func__);
+ UG31_LOGI("[%s]:\n", __func__);
+ UG31_LOGI("[%s]: Previous Time Tag = %d\n", __func__, obj->info->lastTimeTick - obj->info->deltaTime);
+ UG31_LOGI("[%s]: Current Time Tag = %d\n", __func__, obj->info->lastTimeTick);
+ UG31_LOGI("[%s]: Delta Time = %d\n", __func__, obj->info->deltaTime);
+ UG31_LOGI("[%s]: Previous ADC Count = %d\n", __func__, obj->info->lastCounter);
+ UG31_LOGI("[%s]: Current ADC Count = %d\n", __func__, obj->codeCounter);
+ UG31_LOGI("[%s]: Delta ADC Count = %d\n", __func__, obj->codeCounter - obj->info->lastCounter);
+ UG31_LOGI("[%s]: Old ADC Convert Time = %d\n", __func__, obj->info->adc1ConvertTime);
+ UG31_LOGI("[%s]: New ADC Convert Time = %d\n", __func__, tmp32);
+ UG31_LOGI("[%s]: ***************************************************************************************\n", __func__);
+ UG31_LOGI("[%s]: ***************************************************************************************\n", __func__);
+ tmp32 = (_meas_u32_)obj->info->adc1ConvertTime;
+ }
+ UG31_LOGI("[%s]: Conversion Time = %d ((%d - %d)/%d)\n", __func__,
+ tmp32, obj->codeCounter, obj->info->lastCounter, obj->info->deltaTime);
+ obj->info->adc1ConvertTime = (_meas_u16_)tmp32;
+ obj->info->lastCounter = obj->codeCounter;
+}
+
+#define TIME_MSEC_TO_SEC (1000)
+#define TIME_SEC_TO_HOUR (3600)
+#define COULOMB_COUNTER_LSB (4096)
+
+/**
+ * @brief ConvertCharge
+ *
+ * Convert code of charge
+ *
+ * @para obj address of MeasDataInternalType
+ * @return _UPI_NULL_
+ */
+void ConvertCharge(MeasDataInternalType *obj)
+{
+ _meas_s16_ tmp16;
+ _meas_s32_ tmp32;
+ _meas_s64_ tmp64;
+
+ /// [AT-PM] : Convert from calibrated ADC code ; 01/25/2013
+ tmp16 = ADC1_IDEAL_CODE_DELTA;
+ tmp32 = (_meas_s32_)obj->info->codeCharge;
+ tmp32 = tmp32 - ADC1_IDEAL_CODE_100MV;
+ while(1)
+ {
+ tmp64 = (_meas_s64_)tmp32;
+ tmp64 = tmp64*ADC1_VOLTAGE_DELTA;
+ if((tmp64 < 2147483647) && (tmp64 > -2147483647))
+ {
+ break;
+ }
+ tmp16 = tmp16/2;
+ tmp32 = tmp32/2;
+ }
+ tmp32 = (_meas_s32_)tmp64;
+ tmp32 = tmp32/tmp16;
+ tmp32 = tmp32 + ADC1_VOLTAGE_100MV;
+ tmp32 = tmp32/obj->info->sysData->ggbParameter->rSense;
+ UG31_LOGI("[%s]: ((%d - %d) x %d / %d + %d) / %d = %d\n", __func__,
+ obj->info->codeCharge, ADC1_IDEAL_CODE_100MV, ADC1_VOLTAGE_DELTA,
+ ADC1_IDEAL_CODE_DELTA, ADC1_VOLTAGE_100MV, obj->info->sysData->ggbParameter->rSense, tmp32);
+ /// [AT-PM] : Apply board factor ; 01/25/2013
+ tmp32 = tmp32 - BOARD_FACTOR_CURR_OFFSET;
+ tmp32 = tmp32*BOARD_FACTOR_CONST/BOARD_FACTOR_CURR_GAIN;
+ UG31_LOGI("[%s]: Board Factor (%d/%d) -> %d\n", __func__,
+ BOARD_FACTOR_CURR_GAIN, BOARD_FACTOR_CURR_OFFSET, tmp32);
+
+ /// [AT-PM] : Apply calibration factor ; 01/25/2013
+ tmp32 = tmp32 - obj->info->sysData->ggbParameter->adc1_pos_offset;
+ if(tmp32 > 0)
+ {
+ tmp32 = tmp32*CALIBRATION_FACTOR_CONST/obj->info->sysData->ggbParameter->adc1_pgain;
+ }
+ else
+ {
+ tmp32 = tmp32*CALIBRATION_FACTOR_CONST/obj->info->sysData->ggbParameter->adc1_ngain;
+ }
+ UG31_LOGI("[%s]: Calibration Factor (%d|%d/%d) -> %d\n", __func__,
+ obj->info->sysData->ggbParameter->adc1_pgain, obj->info->sysData->ggbParameter->adc1_ngain,
+ obj->info->sysData->ggbParameter->adc1_pos_offset, tmp32);
+
+ /// [AT-PM] : Apply time information ; 01/25/2013
+ CalculateAdc1ConvertTime(obj);
+ tmp32 = tmp32*(obj->info->adc1ConvertTime)/TIME_MSEC_TO_SEC*COULOMB_COUNTER_LSB/TIME_SEC_TO_HOUR;
+ tmp32 = tmp32/TIME_CONVERT_TIME_TO_MSEC;
+
+ /// [AT-PM] : Update capacity information ; 01/25/2013
+ obj->info->deltaCap = (_meas_s16_)tmp32;
+ obj->info->stepCap = obj->info->deltaCap - obj->info->lastDeltaCap;
+ obj->info->lastDeltaCap = obj->info->deltaCap;
+ UG31_LOGI("[%s]: Capacity = %d (%d)\n", __func__, obj->info->deltaCap, obj->info->stepCap);
+}
+
+/**
+ * @brief TimeTick
+ *
+ * Get the time tick and calculate delta time
+ *
+ * @para obj address of MeasDataInternalType
+ * @return _UPI_NULL_
+ */
+void TimeTick(MeasDataInternalType *obj)
+{
+ obj->currTime = GetTickCount();
+
+ /// [AT-PM] : Prevent time tick overflow ; 01/25/2013
+ if(obj->currTime <= obj->info->lastTimeTick)
+ {
+ obj->info->deltaTime = 0;
+ obj->info->lastTimeTick = obj->currTime;
+ UG31_LOGI("[%s]: OVERFLOW -> %d < %d\n", __func__,
+ obj->currTime, obj->info->lastTimeTick);
+ return;
+ }
+
+ /// [AT-PM] : Calculate delta time ; 01/25/2013
+ obj->info->deltaTime = obj->currTime - obj->info->lastTimeTick;
+ UG31_LOGI("[%s]: Delta Time = %d - %d = %d\n", __func__,
+ obj->currTime, obj->info->lastTimeTick, obj->info->deltaTime);
+ obj->info->lastTimeTick = obj->currTime;
+}
+
+/**
+ * @brief ReadRegister
+ *
+ * Read measurement registers
+ *
+ * @para obj address of MeasDataInternalType
+ * @return _UPI_NULL_
+ */
+void ReadRegister(MeasDataInternalType *obj)
+{
+ /// [AT-PM] : Read VBat1Ave ; 01/27/2013
+ API_I2C_Read(NORMAL_REGISTER,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_AVE_VBAT1_LOW,
+ REG_AVE_VBAT1_HIGH - REG_AVE_VBAT1_LOW + 1,
+ (unsigned char *)&obj->codeBat1);
+
+ /// [AT-PM] : Read CurrentAve ; 01/27/2013
+ API_I2C_Read(NORMAL_REGISTER,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_AVE_CURRENT_LOW,
+ REG_AVE_CURRENT_HIGH - REG_AVE_CURRENT_LOW + 1,
+ (unsigned char *)&obj->codeCurrent);
+
+ /// [AT-PM] : Read ITAve ; 01/27/2013
+ API_I2C_Read(NORMAL_REGISTER,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_AVE_IT_LOW,
+ REG_AVE_IT_HIGH - REG_AVE_IT_LOW + 1,
+ (unsigned char *)&obj->codeIntTemperature);
+
+ /// [AT-PM] : Read ETAve ; 01/27/2013
+ API_I2C_Read(NORMAL_REGISTER,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_AVE_ET_LOW,
+ REG_AVE_ET_HIGH - REG_AVE_ET_LOW + 1,
+ (unsigned char *)&obj->codeExtTemperature);
+
+ /// [AT-PM] : Read Charge ; 01/27/2013
+ API_I2C_Read(NORMAL_REGISTER,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_CHARGE_LOW,
+ REG_CHARGE_HIGH - REG_CHARGE_LOW + 1,
+ (unsigned char *)&obj->codeCharge);
+
+ /// [AT-PM] : Read Counter ; 01/27/2013
+ API_I2C_Read(NORMAL_REGISTER,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_COUNTER_LOW,
+ REG_COUNTER_HIGH - REG_COUNTER_LOW + 1,
+ (unsigned char *)&obj->codeCounter);
+
+ /// [AT-PM] : Read Offset ; 01/27/2013
+ API_I2C_Read(NORMAL_REGISTER,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_ADC1_OFFSET_LOW,
+ REG_ADC1_OFFSET_HIGH - REG_ADC1_OFFSET_LOW + 1,
+ (unsigned char *)&obj->ccOffset);
+}
+
+/**
+ * @brief ResetCoulombCounter
+ *
+ * Reset coulomb counter
+ *
+ * @para obj address of MeasDataInternalType
+ * @return _UPI_NULL_
+ */
+void ResetCoulombCounter(MeasDataInternalType *obj)
+{
+ _meas_u8_ tmp8;
+
+ API_I2C_Read(NORMAL_REGISTER,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_CTRL1,
+ 1,
+ &tmp8);
+ tmp8 = tmp8 | CTRL1_GG_RST;
+ API_I2C_Write(NORMAL_REGISTER,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_CTRL1,
+ 1,
+ &tmp8);
+}
+
+/**
+ * @brief RevertCalibrateAdc2Code
+ *
+ * Revert calibrated ADC2 code
+ *
+ * @para data address of MeasDataType
+ * @para caliCode calibrated ADC2 code
+ * @return raw ADC2 code
+ */
+_meas_s32_ RevertCalibrateAdc2Code(MeasDataType *data, _meas_s32_ caliCode)
+{
+ _meas_s64_ tmp64;
+ _meas_s32_ tmp32;
+ _meas_s32_ deltaIT;
+ _meas_s32_ gain;
+ _meas_s32_ offset;
+ _meas_s32_ constant;
+
+ /// [AT-PM] : tmp32 = ( caliCode x gain / constant + offset ) / deltaIT ; 04/08/2013
+ gain = data->adc2Gain;
+ offset = data->adc2Offset;
+ deltaIT = (_meas_s32_)data->otp->aveIT80;
+ deltaIT = deltaIT - data->otp->aveIT25;
+ constant = ADC2_IDEAL_CODE_DELTA;
+ while(1)
+ {
+ tmp64 = (_meas_s64_)caliCode;
+ tmp64 = tmp64*gain;
+ if((tmp64 < 2147483647) && (tmp64 > -2147483647))
+ {
+ break;
+ }
+ caliCode = caliCode/2;
+ gain = gain/2;
+ constant = constant/4;
+ }
+ tmp32 = (_meas_s32_)tmp64;
+ tmp32 = tmp32/constant;
+ tmp32 = tmp32 + offset;
+ tmp32 = tmp32/deltaIT;
+ return (tmp32);
+}
+
+/**
+ * @brief RevertBat1Code
+ *
+ * Revert VBat1 code
+ *
+ * @para data address of MeasDataType
+ * @para volt voltage in mV to be reverted
+ * @return adc2 vbat1 code
+ */
+_meas_u16_ RevertBat1Code(MeasDataType *data, _upi_s16_ volt)
+{
+ _meas_s32_ tmp32;
+
+ tmp32 = (_meas_s32_)volt;
+
+ /// [AT-PM] : Revert calibration parameter ; 04/08/2013
+ tmp32 = tmp32*data->sysData->ggbParameter->adc2_gain/CALIBRATION_FACTOR_CONST;
+ tmp32 = tmp32 + data->sysData->ggbParameter->adc2_offset;
+
+ /// [AT-PM] : Revert board factor ; 04/08/2013
+ tmp32 = tmp32*BOARD_FACTOR_VOLTAGE_GAIN/BOARD_FACTOR_CONST;
+ tmp32 = tmp32 + BOARD_FACTOR_VOLTAGE_OFFSET;
+
+ /// [AT-PM] : Revert to calibrated ADC code ; 04/08/2013
+ tmp32 = tmp32 - ADC2_VOLTAGE_100MV;
+ tmp32 = tmp32*ADC2_IDEAL_CODE_DELTA/ADC2_VOLTAGE_DELTA;
+ tmp32 = tmp32 + ADC2_IDEAL_CODE_100MV;
+
+ /// [AT-PM] : Revert to raw code ; 04/08/2013
+ tmp32 = RevertCalibrateAdc2Code(data, tmp32);
+ return ((_meas_u16_)tmp32);
+}
+
+/**
+ * @brief RevertETCode
+ *
+ * Revert ET code
+ *
+ * @para data address of MeasDataType
+ * @para et external temperature in 0.1oC to be reverted
+ * @return adc1 et code
+ */
+_meas_u16_ RevertETCode(MeasDataType *data, _upi_s16_ et)
+{
+ _meas_s32_ tmp32;
+ _meas_u8_ idx;
+
+ tmp32 = (_meas_s32_)et;
+
+ /// [AT-PM] : Revert calibration factor ; 04/08/2013
+ tmp32 = tmp32 + data->sysData->ggbParameter->adc_d4;
+
+ /// [AT-PM] : Revert board factor ; 04/08/2013
+ tmp32 = tmp32 + BOARD_FACTOR_EXTT_OFFSET;
+
+ /// [AT-PM] : Revert external temperature calculation ; 04/08/2013
+ idx = 0;
+ while(idx < ET_NUMS)
+ {
+ if(tmp32 < ExtTemperatureTable[idx])
+ {
+ break;
+ }
+ idx = idx + 1;
+ }
+ if(idx == 0)
+ {
+ tmp32 = (_meas_s32_)data->sysData->ggbParameter->rtTable[0];
+ }
+ else if(idx >= ET_NUMS)
+ {
+ tmp32 = (_meas_s32_)data->sysData->ggbParameter->rtTable[ET_NUMS - 1];
+ }
+ else
+ {
+ tmp32 = tmp32 - ExtTemperatureTable[idx - 1];
+ tmp32 = tmp32*(data->sysData->ggbParameter->rtTable[idx] - data->sysData->ggbParameter->rtTable[idx - 1]);
+ tmp32 = tmp32/(ExtTemperatureTable[idx] - ExtTemperatureTable[idx - 1]);
+ tmp32 = tmp32 + data->sysData->ggbParameter->rtTable[idx - 1];
+ }
+ return ((_meas_u16_)tmp32);
+}
+
+/// =============================================
+/// [AT-PM] : Extern function region
+/// =============================================
+
+/**
+ * @brief UpiResetCoulombCounter
+ *
+ * Reset coulomb counter
+ *
+ * @para data address of MeasDataType
+ * @return _UPI_NULL_
+ */
+void UpiResetCoulombCounter(MeasDataType *data)
+{
+ MeasDataInternalType *obj;
+
+ #if defined(uG31xx_OS_ANDROID)
+ #ifdef uG31xx_BOOT_LOADER
+ obj = (MeasDataInternalType *)malloc(sizeof(MeasDataInternalType));
+ #else ///< else of uG31xx_BOOT_LOADER
+ obj = (MeasDataInternalType *)kmalloc(sizeof(MeasDataInternalType), GFP_KERNEL);
+ #endif ///< end of uG31xx_BOOT_LOADER
+ #else ///< else of defined(uG31xx_OS_ANDROID)
+ obj = (MeasDataInternalType *)malloc(sizeof(MeasDataInternalType));
+ #endif ///< end of defined(uG31xx_OS_ANDROID)
+ memset(obj, 0x00, sizeof(MeasDataInternalType));
+
+ obj->info = data;
+
+ /// [AT-PM] : Get delta time ; 01/25/2013
+ TimeTick(obj);
+
+ /// [AT-PM] : Read ADC code ; 01/27/2013
+ ReadRegister(obj);
+
+ /// [AT-PM] : Reset coulomb counter ; 01/30/2013
+ ResetCoulombCounter(obj);
+
+ /// [AT-PM] : Convert ADC characteristic from OTP ; 01/23/2013
+ ConvertAdc1Data(obj);
+
+ /// [AT-PM] : Calculate ADC gain and offset ; 01/23/2013
+ CalAdc1Factors(obj);
+
+ /// [AT-PM] : Calibrate ADC code ; 01/23/2013
+ data->codeCurrent = (_meas_s16_)CalibrateAdc1Code(obj, (_meas_s32_)obj->codeCurrent);
+ CalibrateChargeCode(obj);
+
+ /// [AT-PM] : Convert into physical value ; 01/23/2013
+ ConvertCurrent(obj);
+ ConvertCharge(obj);
+ data->lastDeltaCap = 0;
+
+ #if defined(uG31xx_OS_ANDROID)
+ #ifdef uG31xx_BOOT_LOADER
+ free(obj);
+ #else ///< else of uG31xx_BOOT_LOADER
+ kfree(obj);
+ #endif ///< end of uG31xx_BOOT_LOADER
+ #else ///< else of defined(uG31xx_OS_ANDROID)
+ free(obj);
+ #endif ///< end of defined(uG31xx_OS_ANDROID)
+}
+
+#define MAXIMUM_RETRY_CNT (5)
+#define MINIMUM_VBAT1_CODE (ADC2_IDEAL_CODE_100MV/2)
+#define MAXIMUM_CURRENT_CODE (ADC1_IDEAL_CODE_200MV*6)
+#define MINIMUM_CURRENT_CODE (ADC1_IDEAL_CODE_200MV*(-6))
+#define MINIMUM_IT_CODE (IT_IDEAL_CODE_25/2)
+#define MAXIMUM_IT_CODE (IT_IDEAL_CODE_80*11/10)
+#define MINIMUM_ET_CODE (1000)
+#define MAXIMUM_ET_CODE (28000)
+#define RESET_CC_CURRENT_MAGIC_NUMBER (2)
+
+/**
+ * @brief UpiMeasurement
+ *
+ * Measurement routine
+ *
+ * @para data address of MeasDataType
+ * @return MEAS_RTN_CODE
+ */
+MEAS_RTN_CODE UpiMeasurement(MeasDataType *data)
+{
+ MeasDataInternalType *obj;
+ _meas_u8_ retry;
+ MEAS_RTN_CODE rtn;
+ _meas_s16_ standbyUpper;
+ _meas_s16_ standbyLower;
+ _meas_s16_ tmp16;
+
+ UG31_LOGI("[%s]: Measurement version : %d\n", __func__, UG31XX_MEAS_VERSION);
+
+ #if defined(uG31xx_OS_ANDROID)
+ #ifdef uG31xx_BOOT_LOADER
+ obj = (MeasDataInternalType *)malloc(sizeof(MeasDataInternalType));
+ #else ///< else of uG31xx_BOOT_LOADER
+ obj = (MeasDataInternalType *)kmalloc(sizeof(MeasDataInternalType), GFP_KERNEL);
+ #endif ///< end of uG31xx_BOOT_LOADER
+ #else ///< else of defined(uG31xx_OS_ANDROID)
+ obj = (MeasDataInternalType *)malloc(sizeof(MeasDataInternalType));
+ #endif ///< end of defined(uG31xx_OS_ANDROID)
+ memset(obj, 0x00, sizeof(MeasDataInternalType));
+
+ obj->info = data;
+ rtn = MEAS_RTN_PASS;
+
+ /// [AT-PM] : Get delta time ; 01/25/2013
+ TimeTick(obj);
+
+ /// [AT-PM] : Read ADC code ; 01/27/2013
+ retry = 0;
+ while(retry < MAXIMUM_RETRY_CNT)
+ {
+ ReadRegister(obj);
+
+ if(obj->codeBat1 < MINIMUM_VBAT1_CODE)
+ {
+ UG31_LOGE("[%s]: Voltage code %d < %d -> Retry %d\n", __func__,
+ obj->codeBat1, MINIMUM_VBAT1_CODE, retry);
+ rtn = MEAS_RTN_BATTERY_REMOVED;
+ }
+ else if(obj->codeCurrent < MINIMUM_CURRENT_CODE)
+ {
+ UG31_LOGE("[%s]: Current code %d < %d\n", __func__,
+ obj->codeCurrent, MINIMUM_CURRENT_CODE);
+ obj->codeCurrent = MINIMUM_CURRENT_CODE;
+ break;
+ }
+ else if(obj->codeCurrent > MAXIMUM_CURRENT_CODE)
+ {
+ UG31_LOGE("[%s]: Current code %d > %d\n", __func__,
+ obj->codeCurrent, MAXIMUM_CURRENT_CODE);
+ obj->codeCurrent = MAXIMUM_CURRENT_CODE;
+ break;
+ }
+ else if(obj->codeIntTemperature < MINIMUM_IT_CODE)
+ {
+ UG31_LOGE("[%s]: Internal Temperature code %d < %d -> Retry %d\n", __func__,
+ obj->codeIntTemperature, MINIMUM_IT_CODE, retry);
+ rtn = MEAS_RTN_ADC_ABNORMAL;
+ }
+ else if(obj->codeIntTemperature > MAXIMUM_IT_CODE)
+ {
+ UG31_LOGE("[%s]: Internal Temperature code %d > %d -> Retry %d\n", __func__,
+ obj->codeIntTemperature, MAXIMUM_IT_CODE, retry);
+ rtn = MEAS_RTN_ADC_ABNORMAL;
+ }
+ else if(obj->codeExtTemperature < MINIMUM_ET_CODE)
+ {
+ UG31_LOGE("[%s]: External Temperature code %d < %d -> Retry %d\n", __func__,
+ obj->codeExtTemperature, MINIMUM_ET_CODE, retry);
+ rtn = MEAS_RTN_NTC_SHORT;
+ #ifndef ENABLE_NTC_CHECK
+ break;
+ #endif ///< end of ENABLE_NTC_CHECK
+ }
+ else if(obj->codeExtTemperature > MAXIMUM_ET_CODE)
+ {
+ UG31_LOGE("[%s]: External Temperature code %d > %d -> Retry %d\n", __func__,
+ obj->codeExtTemperature, MAXIMUM_ET_CODE, retry);
+ rtn = MEAS_RTN_BATTERY_REMOVED;
+ #ifndef ENABLE_NTC_CHECK
+ break;
+ #endif ///< end of ENABLE_NTC_CHECK
+ }
+ else
+ {
+ break;
+ }
+ retry = retry + 1;
+ SleepMiniSecond(1000);
+ }
+ if(retry >= MAXIMUM_RETRY_CNT)
+ {
+ #if defined(uG31xx_OS_ANDROID)
+ #ifdef uG31xx_BOOT_LOADER
+ free(obj);
+ #else ///< else of uG31xx_BOOT_LOADER
+ kfree(obj);
+ #endif ///< end of uG31xx_BOOT_LOADER
+ #else ///< else of defined(uG31xx_OS_ANDROID)
+ free(obj);
+ #endif ///< end of defined(uG31xx_OS_ANDROID)
+ return (rtn);
+ }
+ rtn = MEAS_RTN_PASS;
+
+ /// [AT-PM] : Convert ADC characteristic from OTP ; 01/23/2013
+ ConvertAdc1Data(obj);
+ ConvertAdc2Data(obj);
+
+ /// [AT-PM] : Calculate ADC gain and offset ; 01/23/2013
+ CalAdc1Factors(obj);
+ CalAdc2Factors(obj);
+
+ /// [AT-PM] : Calibrate ADC code ; 01/23/2013
+ data->codeBat1 = (_meas_u16_)CalibrateAdc2Code(obj, (_meas_s32_)obj->codeBat1);
+ UG31_LOGI("[%s]: VBat1 Code = %d -> %d\n", __func__, obj->codeBat1, data->codeBat1);
+ data->codeCurrent = (_meas_s16_)CalibrateAdc1Code(obj, (_meas_s32_)obj->codeCurrent);
+ UG31_LOGI("[%s]: Current Code = %d -> %d\n", __func__, obj->codeCurrent, data->codeCurrent);
+ CalibrateChargeCode(obj);
+ data->codeIntTemperature = CalibrateITCode(obj, obj->codeIntTemperature);
+ UG31_LOGI("[%s]: Internal Temperature Code = %d -> %d\n", __func__,
+ obj->codeIntTemperature, data->codeIntTemperature);
+ /// [AT-PM] : External temperature code ; 06/19/2013
+ tmp16 = (_meas_s16_)obj->codeExtTemperature;
+ tmp16 = tmp16 - (obj->codeCurrent + obj->ccOffset);
+ if(data->codeExtTemperature != 0)
+ {
+ tmp16 = tmp16 - data->codeExtTemperature;
+ if(tmp16 < -200)
+ {
+ tmp16 = -200;
+ }
+ if(tmp16 > 200)
+ {
+ tmp16 = 200;
+ }
+ tmp16 = tmp16 + data->codeExtTemperature;
+ }
+ data->codeExtTemperature = (_meas_u16_)tmp16;
+ UG31_LOGI("[%s]: External Temperature Code = %d -> %d\n", __func__,
+ obj->codeExtTemperature, data->codeExtTemperature);
+
+ /// [AT-PM] : Convert into physical value ; 01/23/2013
+ ConvertBat1(obj);
+ ConvertCurrent(obj);
+ ConvertIntTemperature(obj);
+ ConvertExtTemperature(obj);
+ ConvertCharge(obj);
+
+ /// [AT-PM] : Reset coulomb counter if necessary ; 01/27/2013
+ standbyUpper = (_meas_s16_)obj->info->sysData->ggbParameter->standbyCurrent;
+ standbyUpper = standbyUpper/RESET_CC_CURRENT_MAGIC_NUMBER;
+ standbyLower = standbyUpper*(-1);
+ if((obj->codeCounter > COULOMB_COUNTER_RESET_THRESHOLD_COUNTER) ||
+ (obj->codeCharge > COULOMB_COUNTER_RESET_THRESHOLD_CHARGE_CHG) ||
+ (obj->codeCharge < COULOMB_COUNTER_RESET_THREDHOLD_CHARGE_DSG) ||
+ ((obj->info->curr < standbyUpper) &&
+ (obj->info->curr > standbyLower) &&
+ (obj->codeCounter > CONST_CONVERSION_COUNT_THRESHOLD*RESET_CC_CURRENT_MAGIC_NUMBER)))
+ {
+ ResetCoulombCounter(obj);
+ data->lastDeltaCap = 0;
+ }
+
+ #ifdef MEAS_FAKE_INT_TEMP
+ data->extTemperature = data->intTemperature;
+ data->intTemperature = MEAS_FAKE_INT_TEMP_OFFSET + data->intTemperature%100;
+ #endif ///< end of MEAS_FAKE_INT_TEMP
+
+ UG31_LOGI("[%s]: %d mV / %d mA / %d 0.1oC / %d 0.1oC / %d mAh\n", __func__,
+ data->bat1Voltage, data->curr, data->intTemperature, data->extTemperature, data->deltaCap);
+ #if defined(uG31xx_OS_ANDROID)
+ #ifdef uG31xx_BOOT_LOADER
+ free(obj);
+ #else ///< else of uG31xx_BOOT_LOADER
+ kfree(obj);
+ #endif ///< end of uG31xx_BOOT_LOADER
+ #else ///< else of defined(uG31xx_OS_ANDROID)
+ free(obj);
+ #endif ///< end of defined(uG31xx_OS_ANDROID)
+ return (rtn);
+}
+
+/**
+ * @brief UpiMeasAlarmThreshold
+ *
+ * Get alarm threshold
+ *
+ * @para data address of MeasDataType
+ * @return MEAS_RTN_CODE
+ */
+MEAS_RTN_CODE UpiMeasAlarmThreshold(MeasDataType *data)
+{
+ MEAS_RTN_CODE rtn;
+
+ rtn = MEAS_RTN_PASS;
+
+ /// [AT-PM] : Calculate UV alarm and release threshold ; 04/08/2013
+ data->sysData->uvAlarm.alarmThrd = RevertBat1Code(data, data->sysData->ggbParameter->uvAlarm);
+ data->sysData->uvAlarm.releaseThrd = RevertBat1Code(data, data->sysData->ggbParameter->uvRelease);
+ UG31_LOGI("[%s]: UV Alarm -> %d / %d\n", __func__,
+ data->sysData->uvAlarm.alarmThrd, data->sysData->uvAlarm.releaseThrd);
+
+ /// [AT-PM] : Calculate UET alarm and release threshold ; 04/08/2013
+ data->sysData->uetAlarm.alarmThrd = RevertETCode(data, data->sysData->ggbParameter->uetAlarm);
+ data->sysData->uetAlarm.releaseThrd = RevertETCode(data, data->sysData->ggbParameter->uetRelease);
+ UG31_LOGI("[%s]: UET Alarm -> %d / %d\n", __func__,
+ data->sysData->uetAlarm.alarmThrd, data->sysData->uetAlarm.releaseThrd);
+
+ /// [AT-PM] : Calculate OET alarm and release threshold ; 04/08/2013
+ data->sysData->oetAlarm.alarmThrd = RevertETCode(data, data->sysData->ggbParameter->oetAlarm);
+ data->sysData->oetAlarm.releaseThrd = RevertETCode(data, data->sysData->ggbParameter->oetRelease);
+ UG31_LOGI("[%s]: OET Alarm -> %d / %d\n", __func__,
+ data->sysData->oetAlarm.alarmThrd, data->sysData->oetAlarm.releaseThrd);
+
+ return (rtn);
+}
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Measurement.h b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Measurement.h new file mode 100755 index 00000000..c6bcc76e --- /dev/null +++ b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Measurement.h @@ -0,0 +1,127 @@ +/**
+ * @filename uG31xx_API_Measurement.h
+ *
+ * Header for uG31xx measurement API
+ *
+ * @author AllenTeng <allen_teng@upi-semi.com>
+ */
+
+typedef signed char _meas_s8_;
+typedef unsigned char _meas_u8_;
+typedef signed short _meas_s16_;
+typedef unsigned short _meas_u16_;
+typedef signed long _meas_s32_;
+typedef unsigned long _meas_u32_;
+typedef signed long long _meas_s64_;
+
+#define UG31XX_MEAS_VERSION (9)
+
+#define BOARD_FACTOR_CONST (1000)
+#define BOARD_FACTOR_VOLTAGE_GAIN (1000) ///< [AT-PM] : VBat1 board factor - gain ; 01/25/2013
+#define BOARD_FACTOR_VOLTAGE_OFFSET (0) ///< [AT-PM] : VBat1 board factor - offset ; 01/25/2013
+#define BOARD_FACTOR_CURR_GAIN (1014) ///< [AT-PM] : Current board factor - gain ; 01/25/2013
+#define BOARD_FACTOR_CURR_OFFSET (-7) ///< [AT-PM] : Current board factor - offset ; 01/25/2013
+#define BOARD_FACTOR_INTT_OFFSET (-23) ///< [AT-PM] : Internal Temperature board factor - offset ; 01/25/2013
+#define BOARD_FACTOR_EXTT_OFFSET (13) ///< [AT-PM] : External Temperature board factor - offset ; 01/25/2013
+
+#define CALIBRATION_FACTOR_CONST (1000)
+
+#define COULOMB_COUNTER_RESET_THRESHOLD_COUNTER (10000)
+#define COULOMB_COUNTER_RESET_THRESHOLD_CHARGE_CHG (30000)
+#define COULOMB_COUNTER_RESET_THREDHOLD_CHARGE_DSG (-30000)
+
+typedef enum _MEAS_RTN_CODE {
+ MEAS_RTN_PASS = 0,
+ MEAS_RTN_BATTERY_REMOVED,
+ MEAS_RTN_ADC_ABNORMAL,
+ MEAS_RTN_NTC_SHORT,
+} MEAS_RTN_CODE;
+
+typedef struct MeasDataST {
+
+ /// [AT-PM] : System data ; 04/08/2013
+ SystemDataType *sysData;
+
+ /// [AT-PM] : OTP data ; 01/23/2013
+ OtpDataType *otp;
+
+ /// [AT-PM] : Physical value ; 01/23/2013
+ _meas_u16_ bat1Voltage;
+ _meas_s16_ curr;
+ _meas_s16_ intTemperature;
+ _meas_s16_ extTemperature;
+ _meas_s16_ deltaCap;
+ _meas_s16_ stepCap;
+ _meas_u32_ deltaTime;
+
+
+ /// [AT-PM] : ADC code ; 01/23/2013
+ _meas_u16_ codeBat1;
+ _meas_s16_ codeCurrent;
+ _meas_u16_ codeIntTemperature;
+ _meas_u16_ codeExtTemperature;
+ _meas_s32_ codeCharge;
+ _meas_s16_ rawCodeCharge;
+
+ /// [AT-PM] : Coulomb counter offset ; 01/23/2013
+ _meas_s16_ ccOffset;
+ _meas_s16_ ccOffsetAdj;
+
+ /// [AT-PM] : ADC1 characteristic ; 01/23/2013
+ _meas_u16_ adc1ConvertTime;
+ _meas_s32_ adc1Gain;
+ _meas_s32_ adc1GainSlope;
+ _meas_s32_ adc1GainFactorB;
+ _meas_s32_ adc1Offset;
+ _meas_s32_ adc1OffsetSlope;
+ _meas_s32_ adc1OffsetFactorO;
+
+ /// [AT-PM] : ADC2 characteristic ; 01/23/2013
+ _meas_s32_ adc2Gain;
+ _meas_s32_ adc2GainSlope;
+ _meas_s32_ adc2GainFactorB;
+ _meas_s32_ adc2Offset;
+ _meas_s32_ adc2OffsetSlope;
+ _meas_s32_ adc2OffsetFactorO;
+
+ /// [AT-PM] : Previous information ; 01/25/2013
+ _meas_u16_ lastCounter;
+ _meas_u32_ lastTimeTick;
+ _meas_s16_ lastDeltaCap;
+
+#if defined(uG31xx_OS_ANDROID)
+} __attribute__ ((packed)) MeasDataType;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+} MeasDataType;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+/**
+ * @brief UpiResetCoulombCounter
+ *
+ * Reset coulomb counter
+ *
+ * @para data address of MeasDataType
+ * @return _UPI_NULL_
+ */
+extern void UpiResetCoulombCounter(MeasDataType *data);
+
+/**
+ * @brief UpiMeasurement
+ *
+ * Measurement routine
+ *
+ * @para data address of MeasDataType
+ * @return MEAS_RTN_CODE
+ */
+extern MEAS_RTN_CODE UpiMeasurement(MeasDataType *data);
+
+/**
+ * @brief UpiMeasAlarmThreshold
+ *
+ * Get alarm threshold
+ *
+ * @para data address of MeasDataType
+ * @return MEAS_RTN_CODE
+ */
+extern MEAS_RTN_CODE UpiMeasAlarmThreshold(MeasDataType *data);
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Otp.c b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Otp.c new file mode 100755 index 00000000..0b1b2d1a --- /dev/null +++ b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Otp.c @@ -0,0 +1,876 @@ +/**
+ * @filename uG31xx_API_Otp.cpp
+ *
+ * Convert OTP registers into readable value
+ *
+ * @author AllenTeng <allen_teng@upi-semi.com>
+ */
+
+#include "stdafx.h" //windows need this??
+#include "uG31xx_API.h"
+
+/// =============================================
+/// [AT-PM] : OTP register definition
+/// =============================================
+
+#define OTP1_OFFSET_E0 (0)
+ #define INDEX_ADC1_200_25_0 (1<<0)
+ #define INDEX_ADC1_200_25_1 (1<<1)
+ #define INDEX_ADC1_200_25_2 (1<<2)
+ #define INDEX_ADC1_200_25_3 (1<<3)
+ #define DELTA_VREF_0 (1<<4)
+ #define DELTA_VREF_1 (1<<5)
+ #define DELTA_VREF_2 (1<<6)
+ #define DELTA_VREF_3 (1<<7)
+
+#define OTP1_OFFSET_E1 (OTP1_OFFSET_E0 + 1)
+ #define INDEX_ADC1_100_25_0 (1<<0)
+ #define INDEX_ADC1_100_25_1 (1<<1)
+ #define INDEX_ADC1_100_25_2 (1<<2)
+ #define INDEX_ADC1_100_25_3 (1<<3)
+ #define FT_IT_3 (1<<4)
+ #define FT_IT_4 (1<<5)
+ #define FT_IT_5 (1<<6)
+ #define FT_IT_6 (1<<7)
+
+#define OTP1_OFFSET_E2 (OTP1_OFFSET_E1 + 1)
+ #define INDEX_ADC2_200_25_0 (1<<0)
+ #define INDEX_ADC2_200_25_1 (1<<1)
+ #define INDEX_ADC2_200_25_2 (1<<2)
+ #define INDEX_ADC2_200_25_3 (1<<3)
+ #define FT_IT_7 (1<<4)
+ #define FT_IT_8 (1<<5)
+ #define FT_IT_9 (1<<6)
+ #define FT_IT_10 (1<<7)
+
+#define OTP1_OFFSET_E3 (OTP1_OFFSET_E2 + 1)
+ #define INDEX_ADC2_100_25_0 (1<<0)
+ #define INDEX_ADC2_100_25_1 (1<<1)
+ #define INDEX_ADC2_100_25_2 (1<<2)
+ #define INDEX_ADC2_100_25_3 (1<<3)
+ #define FT_IT_11 (1<<4)
+ #define FT_IT_12 (1<<5)
+ #define FT_IT_13 (1<<6)
+ #define FT_IT_14 (1<<7)
+
+#define OTP2_OFFSET_F0 (0)
+ #define OTP2_OFFSET_F0_RSVD_0 (1<<0)
+ #define OTP2_OFFSET_F0_RSVD_1 (1<<1)
+ #define OTP2_OFFSET_F0_RSVD_2 (1<<2)
+ #define PRODUCT_TYPE_0 (1<<3)
+ #define PRODUCT_TYPE_1 (1<<4)
+ #define DELTA_ET_0 (1<<5)
+ #define INDEX_ADC2_100_25_4 (1<<6)
+ #define DELTA_ET_1 (1<<7)
+
+#define OTP2_OFFSET_F1 (OTP2_OFFSET_F0 + 1)
+ #define OTP2_OFFSET_F1_RSVD_0 (1<<0)
+ #define OTP2_OFFSET_F1_RSVD_1 (1<<1)
+ #define OTP2_OFFSET_F1_RSVD_2 (1<<2)
+ #define OTP2_OFFSET_F1_RSVD_3 (1<<3)
+ #define OTP2_OFFSET_F1_RSVD_4 (1<<4)
+ #define OTP2_OFFSET_F1_RSVD_5 (1<<5)
+ #define OTP2_OFFSET_F1_RSVD_6 (1<<6)
+ #define OTP2_OFFSET_F1_RSVD_7 (1<<7)
+
+#define OTP2_OFFSET_F2 (OTP2_OFFSET_F1 + 1)
+ #define OTP2_OFFSET_F2_RSVD_0 (1<<0)
+ #define OTP2_OFFSET_F2_RSVD_1 (1<<1)
+ #define OTP2_OFFSET_F2_RSVD_2 (1<<2)
+ #define OTP_CELL_EN_0 (1<<3)
+ #define OTP_CELL_EN_1 (1<<4)
+ #define OTP_CELL_EN_2 (1<<5)
+ #define OTP_CELL_EN_3 (1<<6)
+ #define OTP_CELL_EN_4 (1<<7)
+
+#define OTP2_OFFSET_F3 (OTP2_OFFSET_F2 + 1)
+ #define OTP2_OFFSET_F3_RSVD_0 (1<<0)
+ #define OTP2_OFFSET_F3_RSVD_1 (1<<1)
+ #define OTP2_OFFSET_F3_RSVD_2 (1<<2)
+ #define OTP2_OFFSET_F3_RSVD_3 (1<<3)
+ #define OTP2_OFFSET_F3_RSVD_4 (1<<4)
+ #define OTP2_OFFSET_F3_RSVD_5 (1<<5)
+ #define OTP2_OFFSET_F3_RSVD_6 (1<<6)
+ #define OTP2_OFFSET_F3_RSVD_7 (1<<7)
+
+#define OTP2_OFFSET_F4 (OTP2_OFFSET_F3 + 1)
+ #define ADC1_DELTA_CODE_25_200MV_8 (1<<0)
+ #define ADC1_DELTA_CODE_25_200MV_9 (1<<1)
+ #define DEV_ADDR_0 (1<<2)
+ #define DEV_ADDR_1 (1<<3)
+ #define DEV_ADDR_2 (1<<4)
+ #define DEV_ADDR_7 (1<<5)
+ #define DEV_ADDR_8 (1<<6)
+ #define DEV_ADDR_9 (1<<7)
+
+#define OTP2_OFFSET_F5 (OTP2_OFFSET_F4 + 1)
+ #define OTP2_OFFSET_F5_RSVD_0 (1<<0)
+ #define OTP2_OFFSET_F5_RSVD_1 (1<<1)
+ #define BGR_TUNE_0 (1<<2)
+ #define BGR_TUNE_1 (1<<3)
+ #define BGR_TUNE_2 (1<<4)
+ #define BGR_TUNE_3 (1<<5)
+ #define BGR_TUNE_4 (1<<6)
+ #define BGR_TUNE_5 (1<<7)
+
+#define OTP2_OFFSET_F6 (OTP2_OFFSET_F5 + 1)
+ #define OSC_DELTA_CODE_25_0 (1<<0)
+ #define OSC_DELTA_CODE_25_1 (1<<1)
+ #define OSC_DELTA_CODE_25_2 (1<<2)
+ #define OSC_DELTA_CODE_25_3 (1<<3)
+ #define OSC_DELTA_CODE_25_4 (1<<4)
+ #define OSC_DELTA_CODE_25_5 (1<<5)
+ #define OSC_DELTA_CODE_25_6 (1<<6)
+ #define OSC_DELTA_CODE_25_7 (1<<7)
+
+#define OTP2_OFFSET_F7 (OTP2_OFFSET_F6 + 1)
+ #define OSC_DELTA_CODE_80_0 (1<<0)
+ #define OSC_DELTA_CODE_80_1 (1<<1)
+ #define OSC_DELTA_CODE_80_2 (1<<2)
+ #define OSC_DELTA_CODE_80_3 (1<<3)
+ #define OSC_DELTA_CODE_80_4 (1<<4)
+ #define OSC_DELTA_CODE_80_5 (1<<5)
+ #define OSC_DELTA_CODE_80_6 (1<<6)
+ #define OSC_DELTA_CODE_80_7 (1<<7)
+
+#define OTP2_OFFSET_F8 (OTP2_OFFSET_F7 + 1)
+ #define ADC1_DELTA_CODE_25_200MV_0 (1<<0)
+ #define ADC1_DELTA_CODE_25_200MV_1 (1<<1)
+ #define ADC1_DELTA_CODE_25_200MV_2 (1<<2)
+ #define ADC1_DELTA_CODE_25_200MV_3 (1<<3)
+ #define ADC1_DELTA_CODE_25_200MV_4 (1<<4)
+ #define ADC1_DELTA_CODE_25_200MV_5 (1<<5)
+ #define ADC1_DELTA_CODE_25_200MV_6 (1<<6)
+ #define ADC1_DELTA_CODE_25_200MV_7 (1<<7)
+
+#define OTP2_OFFSET_F9 (OTP2_OFFSET_F8 + 1)
+ #define OTP2_OFFSET_F9_RSVD_0 (1<<0)
+ #define OTP2_OFFSET_F9_RSVD_1 (1<<1)
+ #define OTP2_OFFSET_F9_RSVD_2 (1<<2)
+ #define OTP2_OFFSET_F9_RSVD_3 (1<<3)
+ #define OTP2_OFFSET_F9_RSVD_4 (1<<4)
+ #define OTP2_OFFSET_F9_RSVD_5 (1<<5)
+ #define OTP2_OFFSET_F9_RSVD_6 (1<<6)
+ #define OTP2_OFFSET_F9_RSVD_7 (1<<7)
+
+#define OTP2_OFFSET_FA (OTP2_OFFSET_F9 + 1)
+ #define ADC1_DELTA_CODE_25_100MV_0 (1<<0)
+ #define ADC1_DELTA_CODE_25_100MV_1 (1<<1)
+ #define ADC1_DELTA_CODE_25_100MV_2 (1<<2)
+ #define ADC1_DELTA_CODE_25_100MV_3 (1<<3)
+ #define ADC1_DELTA_CODE_25_100MV_4 (1<<4)
+ #define ADC1_DELTA_CODE_25_100MV_5 (1<<5)
+ #define ADC1_DELTA_CODE_25_100MV_6 (1<<6)
+ #define ADC1_DELTA_CODE_25_100MV_7 (1<<7)
+
+#define OTP2_OFFSET_FB (OTP2_OFFSET_FA + 1)
+ #define OTP2_OFFSET_FB_RSVD_0 (1<<0)
+ #define OTP2_OFFSET_FB_RSVD_1 (1<<1)
+ #define OTP2_OFFSET_FB_RSVD_2 (1<<2)
+ #define OTP2_OFFSET_FB_RSVD_3 (1<<3)
+ #define OTP2_OFFSET_FB_RSVD_4 (1<<4)
+ #define OTP2_OFFSET_FB_RSVD_5 (1<<5)
+ #define OTP2_OFFSET_FB_RSVD_6 (1<<6)
+ #define OTP2_OFFSET_FB_RSVD_7 (1<<7)
+
+#define OTP2_OFFSET_FC (OTP2_OFFSET_FB + 1)
+ #define ADC1_DELTA_CODE_25_100MV_8 (1<<0)
+ #define ADC2_DELTA_CODE_25_100MV_0 (1<<1)
+ #define ADC2_DELTA_CODE_25_100MV_1 (1<<2)
+ #define ADC2_DELTA_CODE_25_100MV_2 (1<<3)
+ #define ADC2_DELTA_CODE_25_100MV_3 (1<<4)
+ #define ADC2_DELTA_CODE_25_100MV_4 (1<<5)
+ #define ADC2_DELTA_CODE_25_100MV_5 (1<<6)
+ #define ADC2_DELTA_CODE_25_100MV_6 (1<<7)
+
+#define OTP2_OFFSET_FD (OTP2_OFFSET_FC + 1)
+ #define OTP2_OFFSET_FD_RSVD_0 (1<<0)
+ #define OTP2_OFFSET_FD_RSVD_1 (1<<1)
+ #define OTP2_OFFSET_FD_RSVD_2 (1<<2)
+ #define OTP2_OFFSET_FD_RSVD_3 (1<<3)
+ #define OTP2_OFFSET_FD_RSVD_4 (1<<4)
+ #define OTP2_OFFSET_FD_RSVD_5 (1<<5)
+ #define OTP2_OFFSET_FD_RSVD_6 (1<<6)
+ #define OTP2_OFFSET_FD_RSVD_7 (1<<7)
+
+#define OTP2_OFFSET_FE (OTP2_OFFSET_FD + 1)
+ #define ADC2_DELTA_CODE_25_200MV_0 (1<<0)
+ #define ADC2_DELTA_CODE_25_200MV_1 (1<<1)
+ #define ADC2_DELTA_CODE_25_200MV_2 (1<<2)
+ #define ADC2_DELTA_CODE_25_200MV_3 (1<<3)
+ #define ADC2_DELTA_CODE_25_200MV_4 (1<<4)
+ #define ADC2_DELTA_CODE_25_200MV_5 (1<<5)
+ #define ADC2_DELTA_CODE_25_200MV_6 (1<<6)
+ #define ADC2_DELTA_CODE_25_200MV_7 (1<<7)
+
+#define OTP2_OFFSET_FF (OTP2_OFFSET_FE + 1)
+ #define OTP2_OFFSET_FF_RSVD_0 (1<<0)
+ #define OTP2_OFFSET_FF_RSVD_1 (1<<1)
+ #define OTP2_OFFSET_FF_RSVD_2 (1<<2)
+ #define OTP2_OFFSET_FF_RSVD_3 (1<<3)
+ #define OTP2_OFFSET_FF_RSVD_4 (1<<4)
+ #define OTP2_OFFSET_FF_RSVD_5 (1<<5)
+ #define OTP2_OFFSET_FF_RSVD_6 (1<<6)
+ #define OTP2_OFFSET_FF_RSVD_7 (1<<7)
+
+
+#define OTP3_OFFSET_70 (0)
+ #define DELTA_VREF_4 (1<<0)
+ #define DELTA_ET_2 (1<<1)
+ #define DELTA_ET_3 (1<<2)
+ #define AVE_IT_25_3 (1<<3)
+ #define AVE_IT_25_4 (1<<4)
+ #define AVE_IT_25_5 (1<<5)
+ #define AVE_IT_25_6 (1<<6)
+ #define AVE_IT_25_7 (1<<7)
+
+#define OTP3_OFFSET_71 (OTP3_OFFSET_70 + 1)
+ #define AVE_IT_25_8 (1<<0)
+ #define AVE_IT_25_9 (1<<1)
+ #define AVE_IT_25_10 (1<<2)
+ #define AVE_IT_25_11 (1<<3)
+ #define AVE_IT_25_12 (1<<4)
+ #define AVE_IT_25_13 (1<<5)
+ #define AVE_IT_25_14 (1<<6)
+ #define AVE_IT_25_15 (1<<7)
+
+#define OTP3_OFFSET_72 (OTP3_OFFSET_71 + 1)
+ #define INDEX_ADC2_200_25_4 (1<<0)
+ #define INDEX_ADC1_100_25_4 (1<<1)
+ #define INDEX_ADC1_200_25_4 (1<<2)
+ #define AVE_IT_80_3 (1<<3)
+ #define AVE_IT_80_4 (1<<4)
+ #define AVE_IT_80_5 (1<<5)
+ #define AVE_IT_80_6 (1<<6)
+ #define AVE_IT_80_7 (1<<7)
+
+#define OTP3_OFFSET_73 (OTP3_OFFSET_72 + 1)
+ #define AVE_IT_80_8 (1<<0)
+ #define AVE_IT_80_9 (1<<1)
+ #define AVE_IT_80_10 (1<<2)
+ #define AVE_IT_80_11 (1<<3)
+ #define AVE_IT_80_12 (1<<4)
+ #define AVE_IT_80_13 (1<<5)
+ #define AVE_IT_80_14 (1<<6)
+ #define AVE_IT_80_15 (1<<7)
+
+/// =============================================
+/// [AT-PM] : OTP register conversion routine
+/// =============================================
+
+/**
+ * @brief ConvOtp1E0
+ *
+ * Convert OTP1 0xE0
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp1E0(OtpDataType *obj)
+{
+ _otp_u8_ value;
+
+ value = obj->otp1[OTP1_OFFSET_E0];
+
+ obj->indexAdc1V200T25 = obj->indexAdc1V200T25 + (value & (INDEX_ADC1_200_25_0 |
+ INDEX_ADC1_200_25_1 |
+ INDEX_ADC1_200_25_2 |
+ INDEX_ADC1_200_25_3));
+
+ obj->deltaVref = obj->deltaVref + ((value & (DELTA_VREF_0| DELTA_VREF_1 | DELTA_VREF_2 | DELTA_VREF_3)) >> 4);
+}
+
+/**
+ * @brief ConvOtp1E1
+ *
+ * Convert OTP1 0xE1
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp1E1(OtpDataType *obj)
+{
+ _otp_u8_ value;
+
+ value = obj->otp1[OTP1_OFFSET_E1];
+
+ obj->indexAdc1V100T25 = obj->indexAdc1V100T25 + (value & (INDEX_ADC1_100_25_0 |
+ INDEX_ADC1_100_25_1 |
+ INDEX_ADC1_100_25_2 |
+ INDEX_ADC1_100_25_3));
+
+ obj->ftIT = obj->ftIT + ((value & (FT_IT_3 | FT_IT_4 | FT_IT_5 | FT_IT_6)) >> 1);
+}
+
+/**
+ * @brief ConvOtp1E2
+ *
+ * Convert OTP1 0xE2
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp1E2(OtpDataType *obj)
+{
+ _otp_u8_ value;
+ _otp_u16_ tmp;
+
+ value = obj->otp1[OTP1_OFFSET_E2];
+
+ obj->indexAdc2V200T25 = obj->indexAdc2V200T25 + (value & (INDEX_ADC2_200_25_0 |
+ INDEX_ADC2_200_25_1 |
+ INDEX_ADC2_200_25_2 |
+ INDEX_ADC2_200_25_3));
+
+ tmp = (value & (FT_IT_7 | FT_IT_8 | FT_IT_9 | FT_IT_10));
+ obj->ftIT = obj->ftIT + (tmp << 3);
+}
+
+/**
+ * @brief ConvOtp1E3
+ *
+ * Convert OTP1 0xE3
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp1E3(OtpDataType *obj)
+{
+ _otp_u8_ value;
+ _otp_u16_ tmp;
+
+ value = obj->otp1[OTP1_OFFSET_E3];
+
+ obj->indexAdc2V100T25 = obj->indexAdc2V100T25 + (value & (INDEX_ADC2_100_25_0 |
+ INDEX_ADC2_100_25_1 |
+ INDEX_ADC2_100_25_2 |
+ INDEX_ADC2_100_25_3));
+
+ tmp = (value & (FT_IT_11 | FT_IT_12 | FT_IT_13 | FT_IT_14));
+ obj->ftIT = obj->ftIT + (tmp << 7);
+}
+
+/**
+ * @brief ConvOtp2F0
+ *
+ * Convert OTP2 0xF0
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp2F0(OtpDataType *obj)
+{
+ _otp_u8_ value;
+
+ value = obj->otp2[OTP2_OFFSET_F0];
+
+ obj->productType = (value & (PRODUCT_TYPE_0 | PRODUCT_TYPE_1)) >> 3;
+
+ obj->deltaET = obj->deltaET + ((value & DELTA_ET_0) >> 5) + ((value & DELTA_ET_1) >> 6);
+
+ obj->indexAdc2V100T25 = obj->indexAdc2V100T25 + ((value & INDEX_ADC2_100_25_4) >> 2);
+}
+
+/**
+ * @brief ConvOtp2F1
+ *
+ * Convert OTP2 0xF1
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp2F1(OtpDataType *obj)
+{
+}
+
+/**
+ * @brief ConvOtp2F2
+ *
+ * Convert OTP2 0xF2
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp2F2(OtpDataType *obj)
+{
+ _otp_u8_ value;
+
+ value = obj->otp2[OTP2_OFFSET_F2];
+
+ obj->otpCellEN = obj->otpCellEN + ((value & (OTP_CELL_EN_0 |
+ OTP_CELL_EN_1 |
+ OTP_CELL_EN_2 |
+ OTP_CELL_EN_3 |
+ OTP_CELL_EN_4)) >> 3);
+}
+
+/**
+ * @brief ConvOtp2F3
+ *
+ * Convert OTP2 0xF3
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp2F3(OtpDataType *obj)
+{
+}
+
+/**
+ * @brief ConvOtp2F4
+ *
+ * Convert OTP2 0xF4
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp2F4(OtpDataType *obj)
+{
+ _otp_u8_ value;
+ _otp_u16_ tmp;
+
+ value = obj->otp2[OTP2_OFFSET_F4];
+
+ tmp = value & (ADC1_DELTA_CODE_25_200MV_8| ADC1_DELTA_CODE_25_200MV_9);
+ obj->adc1DeltaCodeT25V200 = obj->adc1DeltaCodeT25V200 + (tmp << 8);
+
+ tmp = value & (DEV_ADDR_7 | DEV_ADDR_8 | DEV_ADDR_9);
+ obj->devAddr = (tmp << 2) + ((value & (DEV_ADDR_0 | DEV_ADDR_1 | DEV_ADDR_2)) >> 2);
+}
+
+/**
+ * @brief ConvOtp2F5
+ *
+ * Convert OTP2 0xF5
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp2F5(OtpDataType *obj)
+{
+ _otp_u8_ value;
+
+ value = obj->otp2[OTP2_OFFSET_F5];
+
+ obj->bgrTune = obj->bgrTune + ((value & (BGR_TUNE_0 |
+ BGR_TUNE_1 |
+ BGR_TUNE_2 |
+ BGR_TUNE_3 |
+ BGR_TUNE_4 |
+ BGR_TUNE_5)) >> 2);
+}
+
+/**
+ * @brief ConvOtp2F6
+ *
+ * Convert OTP2 0xF6
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp2F6(OtpDataType *obj)
+{
+ _otp_u8_ value;
+
+ value = obj->otp2[OTP2_OFFSET_F6];
+
+ obj->oscDeltaCode25 = obj->oscDeltaCode25 + (value & (OSC_DELTA_CODE_25_0 |
+ OSC_DELTA_CODE_25_1 |
+ OSC_DELTA_CODE_25_2 |
+ OSC_DELTA_CODE_25_3 |
+ OSC_DELTA_CODE_25_4 |
+ OSC_DELTA_CODE_25_5 |
+ OSC_DELTA_CODE_25_6 |
+ OSC_DELTA_CODE_25_7));
+}
+
+/**
+ * @brief ConvOtp2F7
+ *
+ * Convert OTP2 0xF7
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp2F7(OtpDataType *obj)
+{
+ _otp_u8_ value;
+
+ value = obj->otp2[OTP2_OFFSET_F7];
+
+ obj->oscDeltaCode80 = obj->oscDeltaCode80 + (value & (OSC_DELTA_CODE_80_0 |
+ OSC_DELTA_CODE_80_1 |
+ OSC_DELTA_CODE_80_2 |
+ OSC_DELTA_CODE_80_3 |
+ OSC_DELTA_CODE_80_4 |
+ OSC_DELTA_CODE_80_5 |
+ OSC_DELTA_CODE_80_6 |
+ OSC_DELTA_CODE_80_7));
+}
+
+/**
+ * @brief ConvOtp2F8
+ *
+ * Convert OTP2 0xF8
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp2F8(OtpDataType *obj)
+{
+ _otp_u8_ value;
+
+ value = obj->otp2[OTP2_OFFSET_F8];
+
+ obj->adc1DeltaCodeT25V200 = obj->adc1DeltaCodeT25V200 + (value & (ADC1_DELTA_CODE_25_200MV_0 |
+ ADC1_DELTA_CODE_25_200MV_1 |
+ ADC1_DELTA_CODE_25_200MV_2 |
+ ADC1_DELTA_CODE_25_200MV_3 |
+ ADC1_DELTA_CODE_25_200MV_4 |
+ ADC1_DELTA_CODE_25_200MV_5 |
+ ADC1_DELTA_CODE_25_200MV_6 |
+ ADC1_DELTA_CODE_25_200MV_7));
+}
+
+/**
+ * @brief ConvOtp2F9
+ *
+ * Convert OTP2 0xF9
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp2F9(OtpDataType *obj)
+{
+}
+
+/**
+ * @brief ConvOtp2FA
+ *
+ * Convert OTP2 0xFA
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp2FA(OtpDataType *obj)
+{
+ _otp_u8_ value;
+
+ value = obj->otp2[OTP2_OFFSET_FA];
+
+ obj->adc1DeltaCodeT25V100 = obj->adc1DeltaCodeT25V100 + (value & (ADC1_DELTA_CODE_25_100MV_0 |
+ ADC1_DELTA_CODE_25_100MV_1 |
+ ADC1_DELTA_CODE_25_100MV_2 |
+ ADC1_DELTA_CODE_25_100MV_3 |
+ ADC1_DELTA_CODE_25_100MV_4 |
+ ADC1_DELTA_CODE_25_100MV_5 |
+ ADC1_DELTA_CODE_25_100MV_6 |
+ ADC1_DELTA_CODE_25_100MV_7));
+}
+
+/**
+ * @brief ConvOtp2FB
+ *
+ * Convert OTP2 0xFB
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp2FB(OtpDataType *obj)
+{
+}
+
+/**
+ * @brief ConvOtp2FC
+ *
+ * Convert OTP2 0xFC
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp2FC(OtpDataType *obj)
+{
+ _otp_u8_ value;
+ _otp_u16_ tmp;
+
+ value = obj->otp2[OTP2_OFFSET_FC];
+
+ tmp = value & ADC1_DELTA_CODE_25_100MV_8;
+ obj->adc1DeltaCodeT25V100 = obj->adc1DeltaCodeT25V100 + (tmp << 8);
+
+ obj->adc2DeltaCodeT25V100 = obj->adc2DeltaCodeT25V100 + ((value & (ADC2_DELTA_CODE_25_100MV_0 |
+ ADC2_DELTA_CODE_25_100MV_1 |
+ ADC2_DELTA_CODE_25_100MV_2 |
+ ADC2_DELTA_CODE_25_100MV_3 |
+ ADC2_DELTA_CODE_25_100MV_4 |
+ ADC2_DELTA_CODE_25_100MV_5 |
+ ADC2_DELTA_CODE_25_100MV_6)) >> 1);
+}
+
+/**
+ * @brief ConvOtp2FD
+ *
+ * Convert OTP2 0xFD
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp2FD(OtpDataType *obj)
+{
+}
+
+/**
+ * @brief ConvOtp2FE
+ *
+ * Convert OTP2 0xFE
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp2FE(OtpDataType *obj)
+{
+ _otp_u8_ value;
+
+ value = obj->otp2[OTP2_OFFSET_FE];
+
+ obj->adc2DeltaCodeT25V200 = obj->adc2DeltaCodeT25V200 + (value & (ADC2_DELTA_CODE_25_200MV_0 |
+ ADC2_DELTA_CODE_25_200MV_1 |
+ ADC2_DELTA_CODE_25_200MV_2 |
+ ADC2_DELTA_CODE_25_200MV_3 |
+ ADC2_DELTA_CODE_25_200MV_4 |
+ ADC2_DELTA_CODE_25_200MV_5 |
+ ADC2_DELTA_CODE_25_200MV_6 |
+ ADC2_DELTA_CODE_25_200MV_7));
+}
+
+/**
+ * @brief ConvOtp2FF
+ *
+ * Convert OTP2 0xFF
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp2FF(OtpDataType *obj)
+{
+}
+
+/**
+ * @brief ConvOtp370
+ *
+ * Convert OTP3 0x70
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp370(OtpDataType *obj)
+{
+ _otp_u8_ value;
+
+ value = obj->otp3[OTP3_OFFSET_70];
+
+ obj->deltaVref = obj->deltaVref + ((value & DELTA_VREF_4) << 4);
+
+ obj->deltaET = obj->deltaET + ((value & (DELTA_ET_2 | DELTA_ET_3)) << 1);
+
+ obj->aveIT25 = obj->aveIT25 + (value & (AVE_IT_25_3 | AVE_IT_25_4 | AVE_IT_25_5 | AVE_IT_25_6 | AVE_IT_25_7));
+}
+
+/**
+ * @brief ConvOtp371
+ *
+ * Convert OTP3 0x71
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp371(OtpDataType *obj)
+{
+ _otp_u8_ value;
+ _otp_u16_ tmp;
+
+ value = obj->otp3[OTP3_OFFSET_71];
+
+ tmp = value & (AVE_IT_25_8 |
+ AVE_IT_25_9 |
+ AVE_IT_25_10 |
+ AVE_IT_25_11 |
+ AVE_IT_25_12 |
+ AVE_IT_25_13 |
+ AVE_IT_25_14 |
+ AVE_IT_25_15);
+ obj->aveIT25 = obj->aveIT25 + (tmp << 8);
+}
+
+/**
+ * @brief ConvOtp372
+ *
+ * Convert OTP3 0x72
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp372(OtpDataType *obj)
+{
+ _otp_u8_ value;
+
+ value = obj->otp3[OTP3_OFFSET_72];
+
+ obj->indexAdc2V200T25 = obj->indexAdc2V200T25 + ((value & INDEX_ADC2_200_25_4) << 4);
+
+ obj->indexAdc1V100T25 = obj->indexAdc1V100T25 + ((value & INDEX_ADC1_100_25_4) << 3);
+
+ obj->indexAdc1V200T25 = obj->indexAdc1V200T25 + ((value & INDEX_ADC1_200_25_4) << 2);
+
+ obj->aveIT80 = obj->aveIT80 + (value & (AVE_IT_80_3 | AVE_IT_80_4 | AVE_IT_80_5 | AVE_IT_80_6 | AVE_IT_80_7));
+}
+
+/**
+ * @brief ConvOtp373
+ *
+ * Convert OTP3 0x73
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp373(OtpDataType *obj)
+{
+ _otp_u8_ value;
+ _otp_u16_ tmp;
+
+ value = obj->otp3[OTP3_OFFSET_73];
+
+ tmp = value & (AVE_IT_80_8 |
+ AVE_IT_80_9 |
+ AVE_IT_80_10 |
+ AVE_IT_80_11 |
+ AVE_IT_80_12 |
+ AVE_IT_80_13 |
+ AVE_IT_80_14 |
+ AVE_IT_80_15);
+ obj->aveIT80 = obj->aveIT80 + (tmp << 8);
+}
+
+#define CONV_FUNC_PTR_NULL (0)
+
+typedef void (*ConvFuncPtr)(OtpDataType *obj);
+
+static ConvFuncPtr ConvFuncTable[] = {
+ ConvOtp1E0,
+ ConvOtp1E1,
+ ConvOtp1E2,
+ ConvOtp1E3,
+
+ ConvOtp2F0,
+ ConvOtp2F1,
+ ConvOtp2F2,
+ ConvOtp2F3,
+ ConvOtp2F4,
+ ConvOtp2F5,
+ ConvOtp2F6,
+ ConvOtp2F7,
+ ConvOtp2F8,
+ ConvOtp2F9,
+ ConvOtp2FA,
+ ConvOtp2FB,
+ ConvOtp2FC,
+ ConvOtp2FD,
+ ConvOtp2FE,
+ ConvOtp2FF,
+
+ ConvOtp370,
+ ConvOtp371,
+ ConvOtp372,
+ ConvOtp373,
+
+ CONV_FUNC_PTR_NULL,
+};
+
+/**
+ * @brief CheckOtpISEmpty
+ *
+ * Check OTP is empty or not
+ *
+ * @para data address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void CheckOtpISEmpty(OtpDataType *data)
+{
+ _otp_u8_ idx;
+
+ /// [AT-PM] : Check OTP1 ; 01/25/2013
+ idx = 0;
+ while(idx < OTP1_SIZE)
+ {
+ if(data->otp1[idx] != 0)
+ {
+ data->empty = OTP_IS_NOT_EMPTY;
+ return;
+ }
+ }
+
+ /// [AT-PM] : Check OTP2 ; 01/25/2013
+ idx = 0;
+ while(idx < OTP2_SIZE)
+ {
+ if(data->otp2[idx] != 0)
+ {
+ data->empty = OTP_IS_NOT_EMPTY;
+ return;
+ }
+ }
+
+ /// [AT-PM] : Check OTP3 ; 01/25/2013
+ idx = 0;
+ while(idx < OTP3_SIZE)
+ {
+ if(data->otp3[idx] != 0)
+ {
+ data->empty = OTP_IS_NOT_EMPTY;
+ return;
+ }
+ }
+
+ /// [AT-PM] : Set OTP is empty ; 01/25/2013
+ data->empty = OTP_IS_EMPTY;
+}
+
+/// =============================================
+/// [AT-PM] : Extern function region
+/// =============================================
+
+/**
+ * @brief UpiConvertOtp
+ *
+ * Convert OTP register value to readable value
+ *
+ * @para data address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void UpiConvertOtp(OtpDataType *data)
+{
+ _otp_u8_ idx;
+
+ UG31_LOGI("[%s]: OTP version : %d.%02x\n", __func__, UG31XX_OTP_VERSION_MAIN, UG31XX_OTP_VERSION_SUB);
+
+ /// [AT-PM] : Set version ; 01/25/2013
+ data->versionMain = UG31XX_OTP_VERSION_MAIN;
+ data->versionSub = UG31XX_OTP_VERSION_SUB;
+
+ /// [AT-PM] : Conversion ; 01/23/2013
+ idx = 0;
+ while(1)
+ {
+ (*ConvFuncTable[idx])(data);
+
+ idx = idx + 1;
+ if(ConvFuncTable[idx] == CONV_FUNC_PTR_NULL)
+ {
+ break;
+ }
+ }
+
+ /// [AT-PM] : Check OTP is empty ; 01/25/2013
+ CheckOtpISEmpty(data);
+}
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Otp.h b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Otp.h new file mode 100755 index 00000000..fffdc867 --- /dev/null +++ b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Otp.h @@ -0,0 +1,84 @@ +/**
+ * @filename uG31xx_API_Otp.h
+ *
+ * Header of OTP conversion module
+ *
+ * @author AllenTeng <allen_teng@upi-semi.com>
+ */
+
+/// [AT-PM] : Product Type definition in OTP ; 01/23/2013
+enum UG31XX_PRODUCT_TYPE {
+ UG31XX_PRODUCT_TYPE_0 = 0,
+ UG31XX_PRODUCT_TYPE_1 = 1,
+ UG31XX_PRODUCT_TYPE_2 = 2,
+ UG31XX_PRODUCT_TYPE_3 = 3,
+};
+
+#define UG31XX_OTP_VERSION_MAIN (0x2013)
+#define UG31XX_OTP_VERSION_SUB (0x0110)
+
+#define OTP_IS_EMPTY (1)
+#define OTP_IS_NOT_EMPTY (0)
+
+#define OTP1_SIZE (4)
+#define OTP2_SIZE (16)
+#define OTP3_SIZE (4)
+
+typedef unsigned char _otp_u8_;
+typedef unsigned short _otp_u16_;
+
+typedef struct OtpDataST {
+
+ /// [AT-PM] : Version ; 01/23/2013
+ _otp_u16_ versionMain;
+ _otp_u16_ versionSub;
+ _otp_u8_ empty;
+
+ /// [AT-PM] : Raw data ; 01/23/2013
+ _otp_u8_ otp1[OTP1_SIZE];
+ _otp_u8_ otp2[OTP2_SIZE];
+ _otp_u8_ otp3[OTP3_SIZE];
+
+ /// [AT-PM] : Converted value ; 01/23/2013
+ _otp_u16_ adc1DeltaCodeT25V100;
+ _otp_u16_ adc1DeltaCodeT25V200;
+ _otp_u16_ adc2DeltaCodeT25V100;
+ _otp_u16_ adc2DeltaCodeT25V200;
+ _otp_u16_ aveIT25;
+ _otp_u16_ aveIT80;
+
+ _otp_u8_ bgrTune;
+
+ _otp_u8_ deltaET;
+ _otp_u8_ deltaVref;
+ _otp_u16_ devAddr;
+
+ _otp_u16_ ftIT;
+
+ _otp_u8_ indexAdc1V100T25;
+ _otp_u8_ indexAdc1V200T25;
+ _otp_u8_ indexAdc2V100T25;
+ _otp_u8_ indexAdc2V200T25;
+
+ _otp_u8_ oscDeltaCode25;
+ _otp_u8_ oscDeltaCode80;
+ _otp_u8_ otpCellEN;
+
+ _otp_u8_ productType;
+
+#if defined(uG31xx_OS_ANDROID)
+} __attribute__ ((packed)) OtpDataType;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+} OtpDataType;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+/**
+ * @brief UpiConvertOtp
+ *
+ * Convert OTP register value to readable value
+ *
+ * @para data address of OtpDataType
+ * @return _UPI_NULL_
+ */
+extern void UpiConvertOtp(OtpDataType *data);
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_System.c b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_System.c new file mode 100755 index 00000000..e23f858e --- /dev/null +++ b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_System.c @@ -0,0 +1,1196 @@ +/**
+ * @filename uG31xx_API_System.cpp
+ *
+ * uG31xx system control
+ *
+ * @author AllenTeng <allen_teng@upi-semi.com>
+ */
+
+#include "stdafx.h" //windows need this??
+#include "uG31xx_API.h"
+
+#if defined(uG31xx_OS_ANDROID)
+
+_upi_bool_ ReadGGBXFileToCellDataAndInitSetting(SystemDataType *obj)
+{
+ _sys_u8_ *p_start = _UPI_NULL_;
+ _sys_u8_ *p_end = _UPI_NULL_;
+ _sys_u16_ sum16=0;
+ _sys_s32_ i=0;
+
+ /*
+ * check GGBX_FILE tag
+ */
+ if(obj->ggbXBuf->ggb_tag != GGBX_FILE_TAG)
+ {
+ UG31_LOGE("[%s] GGBX file tag not correct. tag: %08X\n", __func__, obj->ggbXBuf->ggb_tag);
+ return (_UPI_FALSE_);
+ }
+
+ /*
+ * check GGBX_FILE checksum
+ */
+ p_start = (_sys_u8_ *)obj->ggbXBuf + sizeof(GGBX_FILE_HEADER);
+ p_end = p_start + obj->ggbXBuf->length - 1;
+ for (; p_start <= p_end; p_start++)
+ {
+ sum16 += *p_start;
+ }
+
+ /* check done. prepare copy data */
+ memset(obj->ggbCellTable, 0x00, sizeof(CELL_TABLE));
+ memset(obj->ggbParameter, 0x00, sizeof(CELL_PARAMETER));
+
+ p_start = (_sys_u8_ *)obj->ggbXBuf + sizeof(GGBX_FILE_HEADER);
+ for (i=0; i<obj->ggbXBuf->num_ggb; i++)
+ {
+ /* TODO: boundary checking */
+ /* TODO: select right ggb content by sku */
+ memcpy(obj->ggbParameter, p_start, sizeof(CELL_PARAMETER));
+ memcpy(obj->ggbCellTable, p_start + sizeof(CELL_PARAMETER), sizeof(CELL_TABLE));
+ p_start += (sizeof(CELL_PARAMETER) + sizeof(CELL_TABLE));
+ }
+ return (_UPI_TRUE_);
+}
+
+#else ///< else of defined(uG31xx_OS_ANDROID)
+
+ _upi_bool_ ReadGGBFileToCellDataAndInitSetting(SystemDataType *obj)
+{
+ FILE* stream;
+ _wfopen_s(&stream, obj->ggbFilename, _T("rb, ccs=UTF-8"));
+
+ memset(obj->ggbCellTable, 0x00, sizeof(CELL_TABLE));
+ memset(obj->ggbParameter, 0x00, sizeof(CELL_PARAMETER));
+
+ if(!stream)
+ {
+ return (_UPI_FALSE_);
+ }
+ if(fread(obj->ggbParameter, sizeof(char), sizeof(CELL_PARAMETER), stream) != sizeof(CELL_PARAMETER))
+ {
+ fclose(stream);
+ return (_UPI_FALSE_);
+ }
+ if(fread(obj->ggbCellTable, sizeof(char), sizeof(CELL_TABLE), stream) != sizeof(CELL_TABLE))
+ {
+ fclose(stream);
+ return (_UPI_FALSE_);
+ }
+
+ fclose(stream);
+
+ return (_UPI_TRUE_);
+}
+
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+/**
+ * @brief GetCellNum
+ *
+ * Get cell number from ggbParameter->ICType
+ *
+ * @para data address of SystemDataType
+ * @return _UPI_NULL_
+ */
+void GetCellNum(SystemDataType *data)
+{
+ _sys_u8_ cellNum[6] = {1, 1, 2, 0, 2, 3};
+
+ data->cellNum = cellNum[data->ggbParameter->ICType];
+}
+
+/**
+ * @brief SetupAdcChopFunction
+ *
+ * Setup ADC chop function
+ *
+ * @para data address of SystemDataType
+ * @return _UPI_NULL_
+ */
+void SetupAdcChopFunction(SystemDataType *data)
+{
+ API_I2C_SingleWrite(SECURITY,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_FW_CTRL,
+ data->ggbParameter->chopCtrl);
+}
+
+/**
+ * @brief SetupAdc1Queue
+ *
+ * Setup ADC1 conversion queue
+ *
+ * @para data address of SystemDataType
+ * @return _UPI_NULL_
+ */
+void SetupAdc1Queue(SystemDataType *data)
+{
+ _cap_u8_ adcQueue[4];
+
+ adcQueue[0] = SET_A_IT | SET_B_IT | SET_C_CURRENT | SET_D_CURRENT;
+ adcQueue[1] = SET_E_CURRENT | SET_F_CURRENT | SET_G_CURRENT | SET_H_CURRENT;
+ adcQueue[2] = SET_I_ET | SET_J_ET | SET_K_CURRENT | SET_L_CURRENT;
+ adcQueue[3] = SET_M_CURRENT | SET_N_CURRENT | SET_O_CURRENT | SET_P_CURRENT;
+
+ API_I2C_Write(SECURITY,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_ADC_CTR_A,
+ 4,
+ &adcQueue[0]);
+}
+
+/**
+ * @brief SetupAdc2Queue
+ *
+ * Set ADC2 conversion queue
+ *
+ * @para data address of SystemDataType
+ * @return _UPI_NULL_
+ */
+void SetupAdc2Quene(SystemDataType *data)
+{
+ _sys_u8_ adc2Queue[3];
+
+ /// [AT-PM] : Set sell type ; 01/31/2013
+ if(data->cellNum == 1)
+ {
+ adc2Queue[0] = SET_V1_VBAT1 | SET_V2_VBAT1 | SET_V3_VBAT1 | SET_V4_VBAT1;
+ adc2Queue[1] = SET_V5_VBAT1 | SET_V6_VBAT1 | SET_V7_VBAT1 | SET_V8_VBAT1;
+ adc2Queue[2] = SET_V9_VBAT1 | SET_V10_VBAT1 | SET_V11_VBAT1 | SET_V12_VBAT1;
+ }
+ else if(data->cellNum == 2)
+ {
+ adc2Queue[0] = SET_V1_VBAT1 | SET_V2_VBAT1 | SET_V3_VBAT2 | SET_V4_VBAT2;
+ adc2Queue[1] = SET_V5_VBAT1 | SET_V6_VBAT1 | SET_V7_VBAT2 | SET_V8_VBAT2;
+ adc2Queue[2] = SET_V9_VBAT1 | SET_V10_VBAT1 | SET_V11_VBAT2 | SET_V12_VBAT2;
+ }
+ else if(data->cellNum == 3)
+ {
+ adc2Queue[0] = SET_V1_VBAT1 | SET_V2_VBAT1 | SET_V3_VBAT2 | SET_V4_VBAT2;
+ adc2Queue[1] = SET_V5_VBAT3 | SET_V6_VBAT3 | SET_V7_VBAT1 | SET_V8_VBAT1;
+ adc2Queue[2] = SET_V9_VBAT2 | SET_V10_VBAT2 | SET_V11_VBAT3 | SET_V12_VBAT3;
+ }
+ else
+ {
+ /// [AT-PM] : 1-cell ; 01/31/2013
+ adc2Queue[0] = SET_V1_VBAT1 | SET_V2_VBAT1 | SET_V3_VBAT1 | SET_V4_VBAT1;
+ adc2Queue[1] = SET_V5_VBAT1 | SET_V6_VBAT1 | SET_V7_VBAT1 | SET_V8_VBAT1;
+ adc2Queue[2] = SET_V9_VBAT1 | SET_V10_VBAT1 | SET_V11_VBAT1 | SET_V12_VBAT1;
+ }
+ API_I2C_Write(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_ADC_V1, 3, &adc2Queue[0]);
+}
+
+/**
+ * @brief EnableCbc
+ *
+ * Enable CBC function
+ *
+ * @para data address of SystemDataType
+ * @return _UPI_NULL_
+ */
+void EnableCbc(SystemDataType *data)
+{
+ _sys_u8_ tmp8;
+
+ API_I2C_SingleRead(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_INTR_CTRL_B, &tmp8);
+ tmp8 = tmp8 & (~(INTR_CTRL_B_CBC_32_EN | INTR_CTRL_B_CBC_21_EN));
+ tmp8 = tmp8 | (INTR_CTRL_B_ET_EN | INTR_CTRL_B_IT_EN | INTR_CTRL_B_RID_EN);
+ tmp8 = tmp8 | (data->ggbParameter->cbcEnable << 4);
+ API_I2C_SingleWrite(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_INTR_CTRL_B, tmp8);
+}
+
+/**
+ * @brief EnableICType
+ *
+ * Enable IC type
+ *
+ * @para data address of SystemDataType
+ * @return _UPI_NULL_
+ */
+void EnableICType(SystemDataType *data)
+{
+ _sys_u8_ tmp8;
+
+ API_I2C_SingleRead(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_CELL_EN, &tmp8);
+ tmp8 = tmp8 & (~CELL_EN_APPLICATION);
+ tmp8 = tmp8 | (data->ggbParameter->ICType << 2);
+ API_I2C_SingleWrite(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_CELL_EN, tmp8);
+}
+
+/**
+ * @brief ConfigGpioFunction
+ *
+ * Configure GPIO1/2 function
+ *
+ * @para setting GPIO1/2 setting
+ * @return register value
+ */
+_sys_u8_ ConfigGpioFunction(_sys_u8_ setting)
+{
+ _sys_u8_ gpioSelData = 0;
+
+ if(setting & FUN_GPIO)
+ {
+ gpioSelData = 0;
+ }
+ if(setting & FUN_ALARM) //select Alarm function
+ {
+ gpioSelData = 1;
+ }
+ if(setting & FUN_CBC_EN21) //cbc21 enable
+ {
+ gpioSelData = 2;
+ }if(setting & FUN_CBC_EN32) //cbc32 Enable
+ {
+ gpioSelData = 3;
+ }
+ if(setting & FUN_PWM) //PWM function, set PWM cycle
+ {
+ gpioSelData = 4;
+ }
+ return (gpioSelData);
+}
+
+/**
+ * @brief ConfigureGpio
+ *
+ * Configure GPIO function
+ *
+ * @para data SystemDataType
+ * @return _UPI_NULL_
+ */
+void ConfigureGpio(SystemDataType *data)
+{
+ _sys_u8_ tmp8;
+
+ API_I2C_SingleRead(SECURITY,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_INTR_CTRL_A,
+ &tmp8);
+ tmp8 = tmp8 | (ConfigGpioFunction(data->ggbParameter->gpio1) << 2);
+ tmp8 = tmp8 | (ConfigGpioFunction(data->ggbParameter->gpio2) << 5);
+ API_I2C_SingleWrite(SECURITY,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_INTR_CTRL_A,
+ tmp8);
+ API_I2C_SingleWrite(SECURITY,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_INTR_CTRL_D,
+ data->ggbParameter->gpio34);
+}
+
+#define ADC_FAIL_CRITERIA (10)
+
+/**
+ * @brief CheckAdcStatusFail
+ *
+ * Check ADC status is fail or not
+ *
+ * @para pUg31xx address of SystemDataType
+ * @return _UPI_TRUE_ if fail
+ */
+_upi_bool_ CheckAdcStatusFail(SystemDataType *data)
+{
+ API_I2C_Read(NORMAL,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_COUNTER_LOW,
+ REG_COUNTER_HIGH - REG_COUNTER_LOW + 1,
+ (unsigned char *)&data->adcCheckData.regCounter);
+
+ API_I2C_Read(NORMAL,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_AVE_VBAT1_LOW,
+ REG_AVE_VBAT1_HIGH - REG_AVE_VBAT1_LOW + 1,
+ (unsigned char *)&data->adcCheckData.regVbat1Ave);
+
+ /// [AT-PM] : Compare counter register ; 01/27/2013
+ if(data->adcCheckData.regCounter == data->adcCheckData.lastCounter)
+ {
+ data->adcCheckData.failCounterCurrent = data->adcCheckData.failCounterCurrent + 1;
+ UG31_LOGI("[%s]: Counter fixed (%d) ... %d\n", __func__,
+ data->adcCheckData.regCounter, data->adcCheckData.failCounterCurrent);
+ }
+ else
+ {
+ data->adcCheckData.failCounterCurrent = 0;
+ }
+ data->adcCheckData.lastCounter = data->adcCheckData.regCounter;
+
+ /// [AT-PM] : Compre VBat1 register ; 01/27/2013
+ if(data->adcCheckData.regVbat1Ave == data->adcCheckData.lastVBat1Ave)
+ {
+ data->adcCheckData.failCounterVoltage = data->adcCheckData.failCounterVoltage + 1;
+ UG31_LOGI("[%s]: VBat1 fixed (%d) ... %d\n", __func__,
+ data->adcCheckData.regVbat1Ave, data->adcCheckData.failCounterVoltage);
+ }
+ else
+ {
+ data->adcCheckData.failCounterVoltage = 0;
+ }
+ data->adcCheckData.lastVBat1Ave = data->adcCheckData.regVbat1Ave;
+
+ /// [AT-PM] : Check ADC fail criteria ; 01/27/2013
+ if(data->adcCheckData.failCounterCurrent > ADC_FAIL_CRITERIA)
+ {
+ data->adcCheckData.failCounterCurrent = 0;
+ return (_UPI_TRUE_);
+ }
+ if(data->adcCheckData.failCounterVoltage > ADC_FAIL_CRITERIA)
+ {
+ data->adcCheckData.failCounterVoltage = 0;
+ return (_UPI_TRUE_);
+ }
+ return (_UPI_FALSE_);
+}
+
+/**
+ * @brief DecimateRst
+ *
+ * Decimate reset filter of ADC
+ *
+ * @return _UPI_NULL_
+ */
+void DecimateRst(void)
+{
+ _sys_u8_ tmp8;
+
+ tmp8 = 0x00;
+ API_I2C_Read(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_ALARM_EN, 1, &tmp8);
+ tmp8 = tmp8 & (~ALARM_EN_DECIMATE_RST);
+ API_I2C_Write(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_ALARM_EN, 1, &tmp8);
+ tmp8 = tmp8 | ALARM_EN_DECIMATE_RST;
+ API_I2C_Write(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_ALARM_EN, 1, &tmp8);
+ UG31_LOGI("[%s]: DECIMATE_RST\n", __func__);
+}
+
+/**
+ * @brief AlarmEnable
+ *
+ * Enable alarm
+ *
+ * @para alarm REG_ALARM_EN bits
+ * @return NULL
+ */
+void AlarmEnable(_sys_u8_ alarm)
+{
+ _sys_u8_ tmp8;
+
+ API_I2C_Read(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_ALARM_EN, 1, &tmp8);
+ tmp8 = tmp8 | alarm;
+ API_I2C_Write(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_ALARM_EN, 1, &tmp8);
+}
+
+/**
+ * @brief AlarmDisable
+ *
+ * Disable alarm
+ *
+ * @para alarm REG_ALARM_EN bits
+ * @return NULL
+ */
+void AlarmDisable(_sys_u8_ alarm)
+{
+ _sys_u8_ tmp8;
+
+ API_I2C_Read(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_ALARM_EN, 1, &tmp8);
+ tmp8 = tmp8 & (~alarm);
+ API_I2C_Write(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_ALARM_EN, 1, &tmp8);
+}
+
+#define SYS_ALARM_STS_UV1 (ALARM2_STATUS_UV1_ALARM)
+#define SYS_ALARM_STS_OV1 (ALARM2_STATUS_OV1_ALARM)
+#define SYS_ALARM_STS_UV2 (ALARM2_STATUS_UV2_ALARM)
+#define SYS_ALARM_STS_OV2 (ALARM2_STATUS_OV2_ALARM)
+#define SYS_ALARM_STS_UV3 (ALARM2_STATUS_UV3_ALARM)
+#define SYS_ALARM_STS_OV3 (ALARM2_STATUS_OV3_ALARM)
+#define SYS_ALARM_STS_UET (ALARM1_STATUS_UET_ALARM<<8)
+#define SYS_ALARM_STS_OET (ALARM1_STATUS_OET_ALARM<<8)
+#define SYS_ALARM_STS_UIT (ALARM1_STATUS_UIT_ALARM<<8)
+#define SYS_ALARM_STS_OIT (ALARM1_STATUS_OIT_ALARM<<8)
+#define SYS_ALARM_STS_DOC (ALARM1_STATUS_DOC_ALARM<<8)
+#define SYS_ALARM_STS_COC (ALARM1_STATUS_COC_ALARM<<8)
+
+/**
+ * @brief ProcUVAlarm
+ *
+ * UV alarm function
+ *
+ * @para data address of SystemDataType
+ * @return NULL
+ */
+void ProcUVAlarm(SystemDataType *data)
+{
+ _sys_u8_ tmp8[4];
+
+ /// [AT-PM] : Check alarm is enable or not ; 04/08/2013
+ if(!(data->ggbParameter->alarmEnable & CELL_PARAMETER_ALARM_EN_UV))
+ {
+ /// [AT-PM] : Disable UV and OV alarm ; 04/08/2013
+ AlarmDisable(ALARM_EN_V1_ALARM_EN);
+ return;
+ }
+
+ if(data->uvAlarm.state == _UPI_TRUE_)
+ {
+ /// [AT-PM] : UV alarm has been set -> Wait for OV alarm ; 04/08/2013
+ if(data->alarmSts & SYS_ALARM_STS_OV1)
+ {
+ data->uvAlarm.state = _UPI_FALSE_;
+
+ /// [AT-PM] : Release UV alarm by disable ; 04/08/2013
+ AlarmDisable(ALARM_EN_V1_ALARM_EN);
+
+ /// [AT-PM] : UV release threshold reached -> set alarm threshold ; 04/08/2013
+ tmp8[0] = 0xff;
+ tmp8[1] = 0x7f;
+ tmp8[2] = (_sys_u8_)(data->uvAlarm.alarmThrd & 0x00ff);
+ tmp8[3] = (_sys_u8_)(data->uvAlarm.alarmThrd >> 8);
+ }
+ else
+ {
+ /// [AT-PM] : UV state -> set release threshold ; 04/08/2013
+ tmp8[0] = (_sys_u8_)(data->uvAlarm.releaseThrd & 0x00ff);
+ tmp8[1] = (_sys_u8_)(data->uvAlarm.releaseThrd >> 8);
+ tmp8[2] = 0x00;
+ tmp8[3] = 0x00;
+ }
+ }
+ else
+ {
+ /// [AT-PM] : Normal state ; 04/08/2013
+ if(data->alarmSts & SYS_ALARM_STS_UV1)
+ {
+ data->uvAlarm.state = _UPI_TRUE_;
+
+ /// [AT-PM] : Release UV alarm by disable ; 04/08/2013
+ AlarmDisable(ALARM_EN_V1_ALARM_EN);
+
+ /// [AT-PM] : UV alarm reached -> set release threshold ; 04/08/2013
+ tmp8[0] = (_sys_u8_)(data->uvAlarm.releaseThrd & 0x00ff);
+ tmp8[1] = (_sys_u8_)(data->uvAlarm.releaseThrd >> 8);
+ tmp8[2] = 0x00;
+ tmp8[3] = 0x00;
+ }
+ else
+ {
+ /// [AT-PM] : Normal state -> set alarm threshold ; 04/08/2013
+ tmp8[0] = 0xff;
+ tmp8[1] = 0x7f;
+ tmp8[2] = (_sys_u8_)(data->uvAlarm.alarmThrd & 0x00ff);
+ tmp8[3] = (_sys_u8_)(data->uvAlarm.alarmThrd >> 8);
+ }
+ }
+
+ /// [AT-PM] : Set alarm threshold ; 04/08/2013
+ API_I2C_Write(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_OV1_LOW, 4, &tmp8[0]);
+
+ /// [AT-PM] : Enable UV and OV alarm ; 04/08/2013
+ AlarmEnable(ALARM_EN_V1_ALARM_EN);
+}
+
+/**
+ * @brief ProcETAlarm
+ *
+ * UET and OET alarm function
+ *
+ * @para data address of SystemDataType
+ * @return NULL
+ */
+void ProcETAlarm(SystemDataType *data)
+{
+ _sys_u8_ tmp8[4];
+
+ /// [AT-PM] : Check alarm is enable or not ; 04/08/2013
+ if(!(data->ggbParameter->alarmEnable & (CELL_PARAMETER_ALARM_EN_UET | CELL_PARAMETER_ALARM_EN_OET)))
+ {
+ /// [AT-PM] : Disable UV and OV alarm ; 04/08/2013
+ AlarmDisable(ALARM_EN_ET_ALARM_EN);
+ return;
+ }
+
+ if(data->uetAlarm.state == _UPI_TRUE_)
+ {
+ /// [AT-PM] : UET alarm state -> wait for OET alarm ; 04/08/2013
+ if(data->alarmSts & SYS_ALARM_STS_OET)
+ {
+ data->uetAlarm.state = _UPI_FALSE_;
+
+ /// [AT-PM] : Release by disable ; 04/08/2013
+ AlarmDisable(ALARM_EN_ET_ALARM_EN);
+
+ /// [AT-PM] : UET release met -> set UET and OET alarm ; 04/08/2013
+ tmp8[0] = (_sys_u8_)(data->oetAlarm.alarmThrd & 0x00ff);
+ tmp8[1] = (_sys_u8_)(data->oetAlarm.alarmThrd >> 8);
+ tmp8[2] = (_sys_u8_)(data->uetAlarm.alarmThrd & 0x00ff);
+ tmp8[3] = (_sys_u8_)(data->uetAlarm.alarmThrd >> 8);
+ }
+ else
+ {
+ /// [AT-PM] : Wait OET alarm ; 04/08/2013
+ tmp8[0] = (_sys_u8_)(data->uetAlarm.releaseThrd & 0x00ff);
+ tmp8[1] = (_sys_u8_)(data->uetAlarm.releaseThrd >> 8);
+ tmp8[2] = 0x00;
+ tmp8[3] = 0x00;
+ }
+ }
+ else if(data->oetAlarm.state == _UPI_TRUE_)
+ {
+ /// [AT-PM] : OET alarm state -> wait for UET alarm ; 04/08/2013
+ if(data->alarmSts & SYS_ALARM_STS_UET)
+ {
+ data->oetAlarm.state = _UPI_FALSE_;
+
+ /// [AT-PM] : Release by disable ; 04/08/2013
+ AlarmDisable(ALARM_EN_ET_ALARM_EN);
+
+ /// [AT-PM] : OET release met -> set UET and OET alarm ; 04/08/2013
+ tmp8[0] = (_sys_u8_)(data->oetAlarm.alarmThrd & 0x00ff);
+ tmp8[1] = (_sys_u8_)(data->oetAlarm.alarmThrd >> 8);
+ tmp8[2] = (_sys_u8_)(data->uetAlarm.alarmThrd & 0x00ff);
+ tmp8[3] = (_sys_u8_)(data->uetAlarm.alarmThrd >> 8);
+ }
+ else
+ {
+ /// [AT-PM] : Wait UET alarm ; 04/08/2013
+ tmp8[0] = 0xff;
+ tmp8[1] = 0x7f;
+ tmp8[2] = (_sys_u8_)(data->oetAlarm.releaseThrd & 0x00ff);
+ tmp8[3] = (_sys_u8_)(data->oetAlarm.releaseThrd >> 8);
+ }
+ }
+ else
+ {
+ /// [AT-PM] : Normal state ; 04/08/2013
+ if((data->alarmSts & SYS_ALARM_STS_UET) &&
+ (data->ggbParameter->alarmEnable & CELL_PARAMETER_ALARM_EN_UET))
+ {
+ data->uetAlarm.state = _UPI_TRUE_;
+
+ /// [AT-PM] : Release by disable ; 04/08/2013
+ AlarmDisable(ALARM_EN_ET_ALARM_EN);
+
+ /// [AT-PM] : UET is set -> set UET release threshold ; 04/08/2013
+ tmp8[0] = (_sys_u8_)(data->uetAlarm.releaseThrd & 0x00ff);
+ tmp8[1] = (_sys_u8_)(data->uetAlarm.releaseThrd >> 8);
+ tmp8[2] = 0x00;
+ tmp8[3] = 0x00;
+ }
+ else if((data->alarmSts & SYS_ALARM_STS_OET) &&
+ (data->ggbParameter->alarmEnable & CELL_PARAMETER_ALARM_EN_OET))
+ {
+ data->oetAlarm.state = _UPI_TRUE_;
+
+ /// [AT-PM] : Release by disable ; 04/08/2013
+ AlarmDisable(ALARM_EN_ET_ALARM_EN);
+
+ /// [AT-PM] : OET is set -> set OET release threshold ; 04/08/2013
+ tmp8[0] = 0xff;
+ tmp8[1] = 0x7f;
+ tmp8[2] = (_sys_u8_)(data->oetAlarm.releaseThrd & 0x00ff);
+ tmp8[3] = (_sys_u8_)(data->oetAlarm.releaseThrd >> 8);
+ }
+ else
+ {
+ /// [AT-PM] : Set OET alarm threshold ; 04/08/2013
+ if(data->ggbParameter->alarmEnable & CELL_PARAMETER_ALARM_EN_OET)
+ {
+ tmp8[0] = (_sys_u8_)(data->oetAlarm.alarmThrd & 0x00ff);
+ tmp8[1] = (_sys_u8_)(data->oetAlarm.alarmThrd >> 8);
+ }
+ else
+ {
+ tmp8[0] = 0xff;
+ tmp8[1] = 0x7f;
+ }
+ /// [AT-PM] : Set UET alarm threshold ; 04/11/2013
+ if(data->ggbParameter->alarmEnable & CELL_PARAMETER_ALARM_EN_UET)
+ {
+ tmp8[2] = (_sys_u8_)(data->uetAlarm.alarmThrd & 0x00ff);
+ tmp8[3] = (_sys_u8_)(data->uetAlarm.alarmThrd >> 8);
+ }
+ else
+ {
+ tmp8[2] = 0x00;
+ tmp8[3] = 0x00;
+ }
+ }
+ }
+
+ /// [AT-PM] : Set alarm threshold ; 04/08/2013
+ API_I2C_Write(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_EXTR_OVER_TEMP_LOW, 4, &tmp8[0]);
+
+ /// [AT-PM] : Enable UV and OV alarm ; 04/08/2013
+ AlarmEnable(ALARM_EN_ET_ALARM_EN);
+}
+
+/**
+ * @brief EnableAlarm
+ *
+ * Set UV, UET, and OET alarm functions
+ *
+ * @para data address of SystemDataType
+ * @return NULL
+ */
+void EnableAlarm(SystemDataType *data)
+{
+ /// [AT-PM] : UV alarm ; 04/08/2013
+ ProcUVAlarm(data);
+
+ /// [AT-PM] : UET and OET alarm ; 04/08/2013
+ ProcETAlarm(data);
+}
+
+/// =============================================
+/// [AT-PM] : Extern function region
+/// =============================================
+
+/**
+ * @brief UpiInitSystemData
+ *
+ * Initialize system variables
+ *
+ * @para data address of SystemDataType
+ * @return SYSTEM_RTN_CODE
+ */
+SYSTEM_RTN_CODE UpiInitSystemData(SystemDataType *data)
+{
+ /// [AT-PM] : Initialize variables ; 01/30/2013
+ data->preITAve = 0;
+ data->cellNum = 0;
+
+ /// [AT-PM] : Load GGB file ; 01/30/2013
+ UG31_LOGI("[%s]: Read GGB\n", __func__);
+ #if defined(uG31xx_OS_ANDROID)
+ if(!ReadGGBXFileToCellDataAndInitSetting(data))
+ #else
+ if(!ReadGGBFileToCellDataAndInitSetting(data))
+ #endif
+ {
+ return (SYSTEM_RTN_READ_GGB_FAIL);
+ }
+
+ /// [AT-PM] : Set cell number ; 01/31/2013
+ GetCellNum(data);
+ return (SYSTEM_RTN_PASS);
+}
+
+/**
+ * @brief UpiCheckICActive
+ *
+ * Check IC is actived or not
+ *
+ * @return _UPI_TRUE_ if uG31xx is not actived
+ */
+_upi_bool_ UpiCheckICActive(void)
+{
+ _upi_u8_ tmp;
+
+ if(!API_I2C_Read(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_MODE, 1, &tmp))
+ {
+ UG31_LOGI("[%s]: Get GG_RUN fail.\n", __func__);
+ return (_UPI_TRUE_);
+ }
+
+ if((tmp & MODE_GG_RUN) == GG_RUN_OPERATION_MODE)
+ {
+ UG31_LOGI("[%s]: uG31xx is actived.\n", __func__);
+ return (_UPI_FALSE_);
+ }
+ UG31_LOGI("[%s]: uG31xx is NOT actived.\n", __func__);
+ return (_UPI_TRUE_);
+}
+
+/**
+ * @brief UpiActiveUg31xx
+ *
+ * Active uG31xx
+ *
+ * @return SYSTEM_RTN_CODE
+ */
+SYSTEM_RTN_CODE UpiActiveUg31xx(void)
+{
+ _sys_u8_ tmp8;
+
+ /// [AT-PM] : Reset uG31xx ; 01/31/2013
+ tmp8 = PORDET_W_SOFTRESET | IO1DATA_W_HIGH;
+ if(!API_I2C_Write(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_CTRL1, 1, &tmp8))
+ {
+ return (SYSTEM_RTN_I2C_FAIL);
+ }
+ tmp8 = IO1DATA_W_HIGH;
+ if(!API_I2C_Write(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_CTRL1, 1, &tmp8))
+ {
+ return (SYSTEM_RTN_I2C_FAIL);
+ }
+
+ /// [AT-PM] : Active uG31xx ; 01/31/2013
+ tmp8 = CTRL1_GG_RST | IO1DATA_W_HIGH;
+ if(!API_I2C_Write(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_CTRL1, 1, &tmp8))
+ {
+ return (SYSTEM_RTN_I2C_FAIL);
+ }
+ tmp8 = GG_RUN_OPERATION_MODE;
+ if(!API_I2C_Write(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_MODE, 1, &tmp8))
+ {
+ return (SYSTEM_RTN_I2C_FAIL);
+ }
+
+ /// [AT-PM] : Delay 255mS for system stable ; 01/31/2013
+ SleepMiniSecond(255); //2012/08/29/Jacky, need wait 255 ms
+ return (SYSTEM_RTN_PASS);
+}
+
+/**
+ * @brief UpiSetupAdc
+ *
+ * Setup ADC configurations
+ *
+ * @para data address of SystemDataType
+ * @return _UPI_NULL_
+ */
+void UpiSetupAdc(SystemDataType *data)
+{
+ _sys_u8_ tmp8;
+
+ /// [AT-PM] : Set ADC chop function ; 01/31/2013
+ SetupAdcChopFunction(data);
+
+ /// [AT-PM] : Set ADC1 queue ; 01/31/2013
+ SetupAdc1Queue(data);
+
+ /// [AT-PM] : Set ADC2 queue ; 01/31/2013
+ SetupAdc2Quene(data);
+
+ /// [AT-PM] : Enable ADC ; 01/31/2013
+ API_I2C_SingleRead(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_INTR_CTRL_A, &tmp8);
+ tmp8 = tmp8 | (INTR_CTRL_A_ADC2_EN | INTR_CTRL_A_ADC1_EN);
+ API_I2C_SingleWrite(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_INTR_CTRL_A, tmp8);
+
+ /// [AT-PM] : Decimate reset ; 01/31/2013
+ DecimateRst();
+
+ /// [AT-PM] : Enable CBC function ; 01/31/2013
+ EnableCbc(data);
+}
+
+/**
+ * @brief UpiSetupSystem
+ *
+ * Setup uG31xx system
+ *
+ * @para data address of SystemDataType
+ * @return _UPI_NULL_
+ */
+void UpiSetupSystem(SystemDataType *data)
+{
+ _sys_u8_ tmp8;
+
+ /// [AT-PM] : Enable IC type ; 01/31/2013
+ EnableICType(data);
+
+ /// [AT-PM] : Configure GPIO ; 01/31/2013
+ ConfigureGpio(data);
+
+ /// [AT-PM] : Enable cell ; 01/31/2013
+ API_I2C_SingleRead(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_CELL_EN, &tmp8);
+ tmp8 = tmp8 | (CELL_EN1 | CELL_EN0);
+ API_I2C_SingleWrite(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_CELL_EN, tmp8);
+}
+
+#define OSC_CNT_TARG 512 //oscCntTarg[9:0]
+
+/**
+ * @brief UpiCalibrationOsc
+ *
+ * OSC calibration
+ * oscCnt25[9:0] = oscCntTarg[9:0] + oscDeltaCode25[7:0]
+ * oscCnt80[9:0] = oscCntTarg[9:0] + oscDeltaCode80[7:0]
+ * oscCnt[9:0] = m*ITcode[15:8] + C[9:0]
+ * m = (oscCnt80[9:0]-oscCnt25[9:0])/(iTcode80[7:0]-iTcode25[7:0])
+ * c = oscCnt25[9:0] - m*ITcode25[7:0]
+ * write oscCnt[9:0] to register 0x97-98
+ *
+ * @para data address of SystemDataType
+ * @return _UPI_NULL_
+ */
+void UpiCalibrationOsc(SystemDataType *data)
+{
+ _sys_u16_ u16Temp;
+
+ _sys_u16_ oscCnt25;
+ _sys_u16_ oscCnt80; //10 bits
+ _sys_u16_ oscDeltaCode25;
+ _sys_u16_ oscDeltaCode80; //
+ _sys_u16_ targetOscCnt; //target osc
+
+ _sys_u16_ varM;
+ _sys_u16_ varC;
+
+ _sys_u16_ aveIT;
+
+ /// [AT-PM] : Calculate m & C ; 01/25/2013
+ oscDeltaCode25 = (_sys_u16_)data->otpData->oscDeltaCode25;
+ oscDeltaCode80 = (_sys_u16_)data->otpData->oscDeltaCode80;
+
+ oscCnt25 = OSC_CNT_TARG + oscDeltaCode25;
+ oscCnt80 = OSC_CNT_TARG + oscDeltaCode80;
+
+ varM = (oscCnt80 - oscCnt25)/(data->otpData->aveIT80 - data->otpData->aveIT25);
+ varC = oscCnt25 - varM*(data->otpData->aveIT25);
+
+ /// [AT-PM] : Read ITAve ; 01/27/2013
+ API_I2C_Read(NORMAL,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_AVE_IT_LOW,
+ REG_AVE_IT_HIGH - REG_AVE_IT_LOW + 1,
+ (_sys_u8_ *)&aveIT);
+
+ /// [AT-PM] : Calculate target OSC cnt ; 01/25/2013
+ targetOscCnt = varM*(aveIT/256) + varC;
+ if(targetOscCnt & 0x8000) //check +/-
+ {
+ u16Temp = (_sys_u16_)(targetOscCnt & 0x1fff);
+ u16Temp |= 0x0200; // minus
+ } else{
+ u16Temp = (_sys_u16_)targetOscCnt; //positive data
+ }
+
+ /// [AT-PM] : Write to register 0x97-98 ; 01/25/2013
+ API_I2C_SingleWrite(SECURITY,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ OSCTUNE_CNTB,
+ (_sys_u8_)(u16Temp >> 8));
+ API_I2C_SingleWrite(SECURITY,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ OSCTUNE_CNTA,
+ (_sys_u8_)u16Temp );
+}
+
+/**
+ * @brief UpiAdcStatus
+ *
+ * Check ADC status
+ *
+ * @para data address of SystemDataType
+ * @return _UPI_NULL_
+ */
+void UpiAdcStatus(SystemDataType *data)
+{
+ if(CheckAdcStatusFail(data) == _UPI_TRUE_) //check ADC Code frozen
+ {
+ DecimateRst();
+ }
+}
+
+#define BACKUP_TIME_BYTE3 (REG_COC_LOW)
+#define BACKUP_TIME_BYTE2 (REG_OTP_CTRL)
+#define BACKUP_NAC_HIGH (REG_CBC21_LOW)
+#define BACKUP_NAC_LOW (REG_CBC21_HIGH)
+#define BACKUP_LMD_HIGH (REG_CBC32_LOW)
+#define BACKUP_LMD_LOW (REG_CBC32_HIGH)
+#define BACKUP_TABLE_UPDATE_IDX (REG_COC_HIGH)
+#define BACKUP_DELTA_CAP_HIGH (REG_DOC_LOW)
+#define BACKUP_DELTA_CAP_LOW (REG_DOC_HIGH)
+#define BACKUP_ADC1_CONV_TIME (REG_COC_HIGH)
+
+/**
+ * @brief UpiLoadBatInfoFromIC
+ *
+ * Load battery information from uG31xx
+ *
+ * @para data address of SystemDataType
+ * @return _UPI_NULL_
+ */
+void UpiLoadBatInfoFromIC(SystemDataType *data)
+{
+ _sys_u8_ *u8Ptr;
+ _sys_u8_ u8Temp;
+ _sys_u8_ u8TempHigh;
+ _sys_u16_ u16Temp;
+
+ //Load the time tag
+ u8Ptr = (_sys_u8_ *)&data->timeTagFromIC;
+ *u8Ptr = 0;
+ *(u8Ptr + 1) = 0;
+ API_I2C_SingleRead(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_TIME_BYTE2, &u8Temp);
+ *(u8Ptr + 2) = u8Temp;
+ API_I2C_SingleRead(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_TIME_BYTE3, &u8Temp);
+ *(u8Ptr + 3) = u8Temp;
+
+ //Load the NAC
+ API_I2C_SingleRead(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_NAC_HIGH, &u8TempHigh);
+ API_I2C_SingleRead(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_NAC_LOW, &u8Temp);
+ data->rmFromIC = (_sys_u16_)u8TempHigh;
+ data->rmFromIC = data->rmFromIC*256 + u8Temp;
+
+ // Load LMD
+ API_I2C_SingleRead(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_LMD_HIGH, &u8TempHigh);
+ API_I2C_SingleRead(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_LMD_LOW, &u8Temp);
+ data->fccFromIC = (_sys_u16_)u8TempHigh;
+ data->fccFromIC = data->fccFromIC*256 + u8Temp;
+ UG31_LOGE("[%s]:timeTag =%u/%x ms,NAC = %d mAh,LMD = %dmAh\n",
+ __func__,
+ data->timeTagFromIC,
+ data->timeTagFromIC,
+ data->rmFromIC,
+ data->fccFromIC);
+
+ /// [AT-PM] : Load table update index ; 02/10/2013
+ API_I2C_SingleRead(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_TABLE_UPDATE_IDX, &u8Temp);
+ data->tableUpdateIdxFromIC = u8Temp & 0x07;
+ UG31_LOGI("[%s]: Table Update Index From IC = %d (0x%02x)\n", __func__, data->tableUpdateIdxFromIC, u8Temp);
+
+ /// [AT-PM] : Load delta capacity ; 02/10/2013
+ API_I2C_SingleRead(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_DELTA_CAP_HIGH, &u8TempHigh);
+ API_I2C_SingleRead(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_DELTA_CAP_LOW, &u8Temp);
+ data->deltaCapFromIC = (_sys_u16_)u8TempHigh;
+ data->deltaCapFromIC = data->deltaCapFromIC*256 + u8Temp;
+ UG31_LOGI("[%s]: Delta Capacity From IC = %d (0x%02x%02x)\n", __func__, data->deltaCapFromIC, u8TempHigh, u8Temp);
+
+ /// [AT-PM] : Load ADC1 conversion time ; 02/10/2013
+ API_I2C_SingleRead(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_ADC1_CONV_TIME, &u8Temp);
+ u16Temp = (_sys_u16_)(u8Temp & 0xf8);
+ data->adc1ConvTime = u16Temp*TIME_CONVERT_TIME_TO_MSEC;
+ UG31_LOGI("[%s]: ADC1 Conversion Time From IC = %d (0x%02x)\n", __func__, data->adc1ConvTime, u8Temp);
+
+ /// [AT-PM] : Get RSOC ; 01/31/2013
+ if(data->fccFromIC == 0)
+ {
+ data->rsocFromIC = 0;
+ }
+ else
+ {
+ data->rsocFromIC = CalculateRsoc(data->rmFromIC, data->fccFromIC);
+ }
+
+ data->rmFromICBackup = data->rmFromIC;
+ data->fccFromICBackup = data->fccFromIC;
+ data->rsocFromICBackup = data->rsocFromIC;
+}
+
+/**
+ * @brief UpiUpdateBatInfoFromIC
+ *
+ * Update battery information from uG31xx
+ *
+ * @para data address of SystemDataType
+ * @para deltaQ delta capacity from coulomb counter
+ * @return _UPI_NULL_
+ */
+void UpiUpdateBatInfoFromIC(SystemDataType *data, _sys_s16_ deltaQ)
+{
+ _sys_s32_ tmp32;
+ _sys_u16_ oldRM;
+
+ oldRM = data->rmFromIC;
+
+ tmp32 = (_sys_s32_)data->rmFromIC;
+ tmp32 = tmp32 + deltaQ;
+ if(tmp32 < 0)
+ {
+ tmp32 = 0;
+ }
+ if(tmp32 > data->fccFromIC)
+ {
+ tmp32 = (_sys_s32_)data->fccFromIC;
+ }
+ UG31_LOGI("[%s]: RM = %d + %d = %d\n", __func__,
+ data->rmFromIC, deltaQ, tmp32);
+ data->rmFromIC = (_sys_u16_)tmp32;
+
+ if(data->fccFromIC == 0)
+ {
+ data->rsocFromIC = 0;
+ }
+ else
+ {
+ data->rsocFromIC = CalculateRsoc(data->rmFromIC, data->fccFromIC);
+
+ if(oldRM != 0)
+ {
+ /// [AT-PM] : EDVF is not reached in last log data ; 02/13/2013
+ if(data->rsocFromIC == 0)
+ {
+ /// [AT-PM] : Check EDVF threshold ; 02/13/2013
+ if(data->voltage < data->ggbParameter->edv1Voltage)
+ {
+ /// [AT-PM] : Set capacity to 0 when EDVF reached ; 02/13/2013
+ data->rmFromIC = 0;
+ data->rsocFromIC = 0;
+ }
+ else
+ {
+ /// [AT-PM] : Capacity should not be 0 before EDVF ; 02/13/2013
+ tmp32 = (_sys_s32_)data->fccFromIC;
+ tmp32 = tmp32/CONST_PERCENTAGE;
+ data->rmFromIC = (_sys_u16_)tmp32;
+ data->rsocFromIC = 1;
+ }
+ }
+ else
+ {
+ /// [AT-PM] : Check EDVF threshold ; 02/13/2013
+ if(data->voltage < data->ggbParameter->edv1Voltage)
+ {
+ /// [AT-PM] : Set capacity to 1% when EDVF reached in initialization ; 02/13/2013
+ tmp32 = (_sys_s32_)data->fccFromIC;
+ tmp32 = tmp32/CONST_PERCENTAGE;
+ data->rmFromIC = (_sys_u16_)tmp32;
+ data->rsocFromIC = 1;
+ }
+ }
+ }
+ }
+}
+
+/**
+ * @brief UpiSaveBatInfoTOIC
+ *
+ * Save battery information from uG31xx
+ *
+ * @para data address of SystemDataType
+ * @return _UPI_NULL_
+ */
+void UpiSaveBatInfoTOIC(SystemDataType *data)
+{
+ _sys_u8_ *u8Ptr;
+ _sys_u8_ u8Temp;
+ _sys_u8_ u8Temp1;
+ _sys_u16_ u16Temp;
+
+ #if defined(uG31xx_OS_ANDROID)
+ data->timeTagFromIC = GetSysTickCount();
+ #else ///< else of defined(uG31xx_OS_ANDROID)
+ data->timeTagFromIC = GetTickCount();
+ #endif ///< end of defined(uG31xx_OS_ANDROID)
+ UG31_LOGE("[%s]:timeTag =%u/%x ms,NAC = %d maH,LMD = %d maH\n",
+ __func__,
+ data->timeTagFromIC,
+ data->timeTagFromIC,
+ data->rmFromIC,
+ data->fccFromIC);
+
+ //save the time tag
+ u8Ptr = (_sys_u8_ *)&data->timeTagFromIC;
+ u8Temp = (*(u8Ptr + 2)) & 0xf8;
+ API_I2C_SingleWrite(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_TIME_BYTE2, u8Temp);
+ API_I2C_SingleWrite(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_TIME_BYTE3, *(u8Ptr + 3));
+
+ //save the NAC
+ u8Temp = (_sys_u8_)((data->rmFromIC & 0xff00)/256);
+ API_I2C_SingleWrite(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_NAC_HIGH, u8Temp);
+ u8Temp = (_sys_u8_)(data->rmFromIC & 0x00ff);
+ API_I2C_SingleWrite(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_NAC_LOW, u8Temp);
+
+ // save LMD
+ u8Temp = (_sys_u8_)((data->fccFromIC & 0xff00)/256);
+ API_I2C_SingleWrite(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_LMD_HIGH, u8Temp);
+ u8Temp = (_sys_u8_)(data->fccFromIC & 0x00ff);
+ API_I2C_SingleWrite(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_LMD_LOW, u8Temp);
+
+ /// [AT-PM] : Save table update index ; 02/10/2013
+ API_I2C_SingleRead(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_TABLE_UPDATE_IDX, &u8Temp);
+ u8Temp = u8Temp & 0xf8;
+ u8Temp = u8Temp | (data->tableUpdateIdxFromIC & 0x07);
+ API_I2C_SingleWrite(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_TABLE_UPDATE_IDX, u8Temp);
+ UG31_LOGI("[%s]: Save Table Update Index = %d - 0x%02x\n", __func__, data->tableUpdateIdxFromIC, u8Temp);
+
+ /// [AT-PM] : Save delta capacity ; 02/10/2013
+ u16Temp = (_sys_u16_)data->deltaCapFromIC;
+ u8Temp = (_sys_u8_)(u16Temp >> 8);
+ API_I2C_SingleWrite(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_DELTA_CAP_HIGH, u8Temp);
+ u8Temp1 = (_sys_u8_)(u16Temp & 0x00ff);
+ API_I2C_SingleWrite(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_DELTA_CAP_LOW, u8Temp1);
+ UG31_LOGI("[%s]: Save Delta Capacity = %d - 0x%02x%02x\n", __func__, data->deltaCapFromIC, u8Temp, u8Temp1);
+
+ /// [AT-PM] : Save adc1 conversion time ; 02/10/2013
+ u16Temp = data->adc1ConvTime/TIME_CONVERT_TIME_TO_MSEC;
+ API_I2C_SingleRead(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_ADC1_CONV_TIME, &u8Temp);
+ u8Temp = u8Temp & 0x07;
+ u8Temp = u8Temp | (u16Temp & 0xf8);
+ API_I2C_SingleWrite(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_ADC1_CONV_TIME, u8Temp);
+ UG31_LOGI("[%s]: Save ADC1 Conversion Time = %d - 0x%02x\n", __func__, data->adc1ConvTime, u8Temp);
+}
+
+/**
+ * @brief UpiInitAlarm
+ *
+ * Initialize alarm function of uG3105
+ *
+ * @para data address of SystemDataType
+ * @return NULL
+ */
+void UpiInitAlarm(SystemDataType *data)
+{
+ /// [AT-PM] : Set GPIO as alarm pin ; 04/08/2013
+ ConfigureGpio(data);
+
+ /// [AT-PM] : Set delay time ; 04/08/2013
+ API_I2C_SingleWrite(SECURITY,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_TIMER,
+ data->ggbParameter->alarm_timer);
+ API_I2C_SingleWrite(SECURITY,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_CLK_DIVA,
+ data->ggbParameter->clkDivA);
+ API_I2C_SingleWrite(SECURITY,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_CLK_DIVB,
+ data->ggbParameter->clkDivB);
+
+ /// [AT-PM] : Enable alarm ; 04/08/2013
+ data->alarmSts = 0;
+ data->uvAlarm.state = _UPI_FALSE_;
+ data->uetAlarm.state = _UPI_FALSE_;
+ data->oetAlarm.state = _UPI_FALSE_;
+ EnableAlarm(data);
+}
+
+/**
+ * @brief UpiAlarmStatus
+ *
+ * Get alarm status
+ *
+ * @para data address of SystemDataType
+ * @return NULL
+ */
+_sys_u8_ UpiAlarmStatus(SystemDataType *data)
+{
+ _sys_u8_ sts;
+ _sys_u8_ tmp8[2];
+
+ sts = 0;
+
+ /// [AT-PM] : Read alarm status from uG3105 ; 04/08/2013
+ API_I2C_Read(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_ALARM1_STATUS, 2, &tmp8[0]);
+ data->alarmSts = (_sys_u16_)tmp8[0];
+ data->alarmSts = data->alarmSts*256 + tmp8[1];
+
+ /// [AT-PM] : Enable alarm ; 04/08/2013
+ EnableAlarm(data);
+
+ /// [AT-PM] : Update current alarm status ; 04/08/2013
+ tmp8[0] = data->uvAlarm.state == _UPI_TRUE_ ? ALARM_STATUS_UV : 0;
+ sts = sts | tmp8[0];
+ tmp8[0] = data->uetAlarm.state == _UPI_TRUE_ ? ALARM_STATUS_UET : 0;
+ sts = sts | tmp8[0];
+ tmp8[0] = data->oetAlarm.state == _UPI_TRUE_ ? ALARM_STATUS_OET : 0;
+ sts = sts | tmp8[0];
+ return (sts);
+}
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_System.h b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_System.h new file mode 100755 index 00000000..cb3ea775 --- /dev/null +++ b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_System.h @@ -0,0 +1,211 @@ +/**
+ * @filename uG31xx_API_System.h
+ *
+ * Interface of ug31xx system control
+ *
+ * @author AllenTeng <allen_teng@upi-semi.com>
+ */
+
+#define UG31XX_SYSTEM_VERSION (6)
+
+typedef unsigned char _sys_u8_;
+typedef signed char _sys_s8_;
+typedef unsigned short _sys_u16_;
+typedef signed short _sys_s16_;
+typedef unsigned long _sys_u32_;
+typedef signed long _sys_s32_;
+typedef char _sys_bool_;
+
+typedef enum _SYSTEM_RTN_CODE {
+ SYSTEM_RTN_PASS = 0,
+ SYSTEM_RTN_READ_GGB_FAIL,
+ SYSTEM_RTN_I2C_FAIL,
+} SYSTEM_RTN_CODE;
+
+typedef struct AlarmDataST {
+ _sys_u16_ alarmThrd;
+ _sys_u16_ releaseThrd;
+ _sys_bool_ state;
+#if defined(uG31xx_OS_ANDROID)
+ } __attribute__ ((packed)) AlarmDataType;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+ } AlarmDataType;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+typedef struct SystemDataST {
+
+ #if defined (uG31xx_OS_WINDOWS)
+ const wchar_t* ggbFilename;
+ const wchar_t* otpFileName;
+ const wchar_t* backupFileName;
+ #elif defined(uG31xx_OS_ANDROID)
+ GGBX_FILE_HEADER *ggbXBuf;
+ #endif
+
+ CELL_PARAMETER *ggbParameter;
+ CELL_TABLE *ggbCellTable;
+ OtpDataType *otpData;
+
+ ADC_CHECK adcCheckData; //add for adc error check 20121025/jacky
+
+ _sys_u16_ voltage;
+
+ _sys_u16_ preITAve;
+ _sys_u8_ cellNum;
+
+ _sys_u16_ rmFromIC;
+ _sys_u16_ fccFromIC;
+ _sys_u8_ rsocFromIC;
+ _sys_u32_ timeTagFromIC;
+ _sys_u8_ tableUpdateIdxFromIC;
+ _sys_s16_ deltaCapFromIC;
+ _sys_u16_ adc1ConvTime;
+
+ _sys_u16_ rmFromICBackup;
+ _sys_u16_ fccFromICBackup;
+ _sys_u8_ rsocFromICBackup;
+
+ AlarmDataType uvAlarm;
+ AlarmDataType oetAlarm;
+ AlarmDataType uetAlarm;
+ _sys_u16_ alarmSts;
+#if defined(uG31xx_OS_ANDROID)
+ } __attribute__ ((packed)) SystemDataType;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+ } SystemDataType;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+/**
+ * @brief UpiInitSystemData
+ *
+ * Initialize system data
+ *
+ * @para data address of BootDataType
+ * @return _UPI_NULL_
+ */
+extern SYSTEM_RTN_CODE UpiInitSystemData(SystemDataType *data);
+
+/**
+ * @brief UpiCheckICActive
+ *
+ * Check IC is actived or not
+ *
+ * @return _UPI_TRUE_ if uG31xx is not actived
+ */
+extern _upi_bool_ UpiCheckICActive(void);
+
+/**
+ * @brief UpiActiveUg31xx
+ *
+ * Active uG31xx
+ *
+ * @return SYSTEM_RTN_CODE
+ */
+extern SYSTEM_RTN_CODE UpiActiveUg31xx(void);
+
+/**
+ * @brief UpiSetupAdc
+ *
+ * Setup ADC configurations
+ *
+ * @para data address of SystemDataType
+ * @return _UPI_NULL_
+ */
+extern void UpiSetupAdc(SystemDataType *data);
+
+/**
+ * @brief UpiDecimateRst
+ *
+ * Decimate reset filter of ADC
+ *
+ * @return _UPI_NULL_
+ */
+extern void UpiDecimateRst(void);
+
+/**
+ * @brief UpiSetupSystem
+ *
+ * Setup uG31xx system
+ *
+ * @para data address of SystemDataType
+ * @return _UPI_NULL_
+ */
+extern void UpiSetupSystem(SystemDataType *data);
+
+/**
+ * @brief UpiCalibrationOsc
+ *
+ * OSC calibration
+ * oscCnt25[9:0] = oscCntTarg[9:0] + oscDeltaCode25[7:0]
+ * oscCnt80[9:0] = oscCntTarg[9:0] + oscDeltaCode80[7:0]
+ * oscCnt[9:0] = m*ITcode[15:8] + C[9:0]
+ * m = (oscCnt80[9:0]-oscCnt25[9:0])/(iTcode80[7:0]-iTcode25[7:0])
+ * c = oscCnt25[9:0] - m*ITcode25[7:0]
+ * write oscCnt[9:0] to register 0x97-98
+ *
+ * @para data address of SystemDataType
+ * @return _UPI_NULL_
+ */
+extern void UpiCalibrationOsc(SystemDataType *data);
+
+/**
+ * @brief UpiAdcStatus
+ *
+ * Check ADC status
+ *
+ * @para data address of SystemDataType
+ * @return _UPI_NULL_
+ */
+extern void UpiAdcStatus(SystemDataType *data);
+
+/**
+ * @brief UpiLoadBatInfoFromIC
+ *
+ * Load battery information from uG31xx
+ *
+ * @para data address of SystemDataType
+ * @return _UPI_NULL_
+ */
+extern void UpiLoadBatInfoFromIC(SystemDataType *data);
+
+/**
+ * @brief UpiUpdateBatInfoFromIC
+ *
+ * Update battery information from uG31xx
+ *
+ * @para data address of SystemDataType
+ * @para deltaQ delta capacity from coulomb counter
+ * @return _UPI_NULL_
+ */
+extern void UpiUpdateBatInfoFromIC(SystemDataType *data, _sys_s16_ deltaQ);
+
+/**
+ * @brief UpiSaveBatInfoTOIC
+ *
+ * Save battery information from uG31xx
+ *
+ * @para data address of SystemDataType
+ * @return _UPI_NULL_
+ */
+extern void UpiSaveBatInfoTOIC(SystemDataType *data);
+
+/**
+ * @brief UpiInitAlarm
+ *
+ * Initialize alarm function of uG3105
+ *
+ * @para data address of SystemDataType
+ * @return NULL
+ */
+extern void UpiInitAlarm(SystemDataType *data);
+
+/**
+ * @brief UpiAlarmStatus
+ *
+ * Get alarm status
+ *
+ * @para data address of SystemDataType
+ * @return NULL
+ */
+extern _sys_u8_ UpiAlarmStatus(SystemDataType *data);
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_Platform.h b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_Platform.h new file mode 100755 index 00000000..9c1e7325 --- /dev/null +++ b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_Platform.h @@ -0,0 +1,17 @@ +/**
+ * @filename uG31xx_Platform.h
+ *
+ * Define the platform for uG31xx driver
+ *
+ * @author AllenTeng <allen_teng@upi-semi.com>
+ */
+
+//#define uG31xx_OS_WINDOWS
+#define uG31xx_OS_ANDROID
+
+#ifdef uG31xx_OS_ANDROID
+
+ //#define uG31xx_BOOT_LOADER
+
+#endif ///< end of uG31xx_OS_ANDROID
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_Reg_Def.h b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_Reg_Def.h new file mode 100755 index 00000000..4de941ae --- /dev/null +++ b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_Reg_Def.h @@ -0,0 +1,667 @@ +/** + * @filename RegDef.h + * + * uG31xx register definition + * + * @author AllenTeng <allen.kuoliang.teng@gmail.com> + * @note register table release date 2012/11/05 + */ + +#define INTERNAL_REGISTER_GROUP_A (0x00) + #define REG_MODE (INTERNAL_REGISTER_GROUP_A + 0) ///< 0x00 + #define MODE_GG_RUN (1<<4) + #define GG_RUN_STANDBY_MODE (0<<4) + #define GG_RUN_OPERATION_MODE (1<<4) + #define REG_CTRL1 (INTERNAL_REGISTER_GROUP_A + 1) ///< 0x01 + #define CTRL1_PORDET (1<<4) + #define PORDET_R_NO_POR (0<<4) + #define PORDET_R_POR (1<<4) + #define PORDET_W_RELEASE (0<<4) + #define PORDET_W_SOFTRESET (1<<4) + #define CTRL1_VTM_EOC (1<<3) + #define CTRL1_GG_EOC (1<<2) + #define CTRL1_GG_RST (1<<1) + #define CTRL1_IO1DATA (1<<0) + #define IO1DATA_R_LOW (0<<0) + #define IO1DATA_R_HIGH (1<<0) + #define IO1DATA_W_LOW (0<<0) + #define IO1DATA_W_HIGH (1<<0) + +#define INTERNAL_REGISTER_GROUP_B (0x02) + #define REG_CHARGE_LOW (INTERNAL_REGISTER_GROUP_B + 0) ///< 0x02 + #define REG_CHARGE_HIGH (INTERNAL_REGISTER_GROUP_B + 1) ///< 0x03 + #define REG_COUNTER_LOW (INTERNAL_REGISTER_GROUP_B + 2) ///< 0x04 + #define REG_COUNTER_HIGH (INTERNAL_REGISTER_GROUP_B + 3) ///< 0x05 + #define REG_AVE_CURRENT_LOW (INTERNAL_REGISTER_GROUP_B + 4) ///< 0x06 + #define REG_AVE_CURRENT_HIGH (INTERNAL_REGISTER_GROUP_B + 5) ///< 0x07 + #define REG_AVE_VBAT1_LOW (INTERNAL_REGISTER_GROUP_B + 6) ///< 0x08 + #define REG_AVE_VBAT1_HIGH (INTERNAL_REGISTER_GROUP_B + 7) ///< 0x09 + #define REG_AVE_IT_LOW (INTERNAL_REGISTER_GROUP_B + 8) ///< 0x0A + #define REG_AVE_IT_HIGH (INTERNAL_REGISTER_GROUP_B + 9) ///< 0x0B + #define REG_AVE_OFFSET_CURRENT_LOW (INTERNAL_REGISTER_GROUP_B + 10) ///< 0x0C + #define REG_AVE_OFFSET_CURRENT_HIGH (INTERNAL_REGISTER_GROUP_B + 11) ///< 0x0D + #define REG_AVE_ET_LOW (INTERNAL_REGISTER_GROUP_B + 12) ///< 0x0E + #define REG_AVE_ET_HIGH (INTERNAL_REGISTER_GROUP_B + 13) ///< 0x0F + #define REG_AVE_RID_LOW (INTERNAL_REGISTER_GROUP_B + 14) ///< 0x10 + #define REG_AVE_RID_HIGH (INTERNAL_REGISTER_GROUP_B + 15) ///< 0x11 + #define REG_ALARM1_STATUS (INTERNAL_REGISTER_GROUP_B + 16) ///< 0x12 + #define ALARM1_STATUS_COC_ALARM (1<<5) + #define ALARM1_STATUS_DOC_ALARM (1<<4) + #define ALARM1_STATUS_OIT_ALARM (1<<3) + #define ALARM1_STATUS_UIT_ALARM (1<<2) + #define ALARM1_STATUS_OET_ALARM (1<<1) + #define ALARM1_STATUS_UET_ALARM (1<<0) + #define REG_ALARM2_STATUS (INTERNAL_REGISTER_GROUP_B + 17) ///< 0x13 + #define ALARM2_STATUS_OV3_ALARM (1<<5) + #define ALARM2_STATUS_UV3_ALARM (1<<4) + #define ALARM2_STATUS_OV2_ALARM (1<<3) + #define ALARM2_STATUS_UV2_ALARM (1<<2) + #define ALARM2_STATUS_OV1_ALARM (1<<1) + #define ALARM2_STATUS_UV1_ALARM (1<<0) + #define REG_INTR_STATUS (INTERNAL_REGISTER_GROUP_B + 18) ///< 0x14 + #define INTR_STATUS_CBC_STS32 (1<<7) + #define CBC_STS32_ENABLE (0<<7) + #define CBC_STS32_DISABLE (1<<7) + #define INTR_STATUS_CBC_STS21 (1<<6) + #define CBC_STS21_ENABLE (0<<6) + #define CBC_STS21_DISABLE (1<<6) + #define INTR_STATUS_STB_STS (1<<5) + #define INTR_STATUS_ET_STS (1<<4) + #define INTR_STATUS_IT_STS (1<<3) + #define INTR_STATUS_RID_STS (1<<2) + #define INTR_STATUS_LVD_STS (1<<1) + #define INTR_STATUS_AL_STS (1<<0) + #define REG_ALARM_EN (INTERNAL_REGISTER_GROUP_B + 19) ///< 0x15 + #define ALARM_EN_COC_ALARM_EN (1<<7) + #define ALARM_EN_DOC_ALARM_EN (1<<6) + #define ALARM_EN_IT_ALARM_EN (1<<5) + #define ALARM_EN_ET_ALARM_EN (1<<4) + #define ALARM_EN_DECIMATE_RST (1<<3) + #define ALARM_EN_V3_ALARM_EN (1<<2) + #define ALARM_EN_V2_ALARM_EN (1<<1) + #define ALARM_EN_V1_ALARM_EN (1<<0) + #define REG_CTRL2 (INTERNAL_REGISTER_GROUP_B + 20) ///< 0x16 + #define CTRL2_IO4DATA (1<<2) + #define IO4DATA_R_LOW (0<<2) + #define IO4DATA_R_HIGH (1<<2) + #define IO4DATA_W_LOW (0<<2) + #define IO4DATA_W_HIGH (1<<2) + #define CTRL2_IO3DATA (1<<1) + #define IO3DATA_R_LOW (0<<1) + #define IO3DATA_R_HIGH (1<<1) + #define IO3DATA_W_LOW (0<<1) + #define IO3DATA_W_HIGH (1<<1) + #define CTRL2_IO2DATA (1<<0) + #define IO2DATA_R_LOW (0<<0) + #define IO2DATA_R_HIGH (1<<0) + #define IO2DATA_W_LOW (0<<0) + #define IO2DATA_W_HIGH (1<<0) + +#define INTERNAL_REGISTER_GROUP_C (0x20) + #define REG_RAM0 (INTERNAL_REGISTER_GROUP_C + 0) ///< 0x20 + #define REG_RAM1 (INTERNAL_REGISTER_GROUP_C + 1) ///< 0x21 + #define REG_RAM2 (INTERNAL_REGISTER_GROUP_C + 2) ///< 0x22 + #define REG_RAM3 (INTERNAL_REGISTER_GROUP_C + 3) ///< 0x23 + #define REG_RAM4 (INTERNAL_REGISTER_GROUP_C + 4) ///< 0x24 + #define REG_RAM5 (INTERNAL_REGISTER_GROUP_C + 5) ///< 0x25 + #define REG_RAM6 (INTERNAL_REGISTER_GROUP_C + 6) ///< 0x26 + #define REG_RAM7 (INTERNAL_REGISTER_GROUP_C + 7) ///< 0x27 + #define REG_RAM8 (INTERNAL_REGISTER_GROUP_C + 8) ///< 0x28 + #define REG_RAM9 (INTERNAL_REGISTER_GROUP_C + 9) ///< 0x29 + #define REG_RAM10 (INTERNAL_REGISTER_GROUP_C + 10) ///< 0x2A + #define REG_RAM11 (INTERNAL_REGISTER_GROUP_C + 11) ///< 0x2B + #define REG_RAM12 (INTERNAL_REGISTER_GROUP_C + 12) ///< 0x2C + #define REG_RAM13 (INTERNAL_REGISTER_GROUP_C + 13) ///< 0x2D + #define REG_RAM14 (INTERNAL_REGISTER_GROUP_C + 14) ///< 0x2E + #define REG_RAM15 (INTERNAL_REGISTER_GROUP_C + 15) ///< 0x2F + #define REG_RAM16 (INTERNAL_REGISTER_GROUP_C + 16) ///< 0x30 + #define REG_RAM17 (INTERNAL_REGISTER_GROUP_C + 17) ///< 0x31 + #define REG_RAM18 (INTERNAL_REGISTER_GROUP_C + 18) ///< 0x32 + #define REG_RAM19 (INTERNAL_REGISTER_GROUP_C + 19) ///< 0x33 + #define REG_RAM20 (INTERNAL_REGISTER_GROUP_C + 20) ///< 0x34 + #define REG_RAM21 (INTERNAL_REGISTER_GROUP_C + 21) ///< 0x35 + #define REG_RAM22 (INTERNAL_REGISTER_GROUP_C + 22) ///< 0x36 + #define REG_RAM23 (INTERNAL_REGISTER_GROUP_C + 23) ///< 0x37 + #define REG_RAM24 (INTERNAL_REGISTER_GROUP_C + 24) ///< 0x38 + #define REG_RAM25 (INTERNAL_REGISTER_GROUP_C + 25) ///< 0x39 + #define REG_RAM26 (INTERNAL_REGISTER_GROUP_C + 26) ///< 0x3A + #define REG_RAM27 (INTERNAL_REGISTER_GROUP_C + 27) ///< 0x3B + #define REG_RAM28 (INTERNAL_REGISTER_GROUP_C + 28) ///< 0x3C + #define REG_RAM29 (INTERNAL_REGISTER_GROUP_C + 29) ///< 0x3D + #define REG_RAM30 (INTERNAL_REGISTER_GROUP_C + 30) ///< 0x3E + #define REG_RAM31 (INTERNAL_REGISTER_GROUP_C + 31) ///< 0x3F + +#define INTERNAL_REGISTER_GROUP_D (0x40) + #define REG_VBAT2_LOW (INTERNAL_REGISTER_GROUP_D + 0) ///< 0x40 + #define REG_VBAT2_HIGH (INTERNAL_REGISTER_GROUP_D + 1) ///< 0x41 + #define REG_VBAT3_LOW (INTERNAL_REGISTER_GROUP_D + 2) ///< 0x42 + #define REG_VBAT3_HIGH (INTERNAL_REGISTER_GROUP_D + 3) ///< 0x43 + #define REG_VBAT1_LOW (INTERNAL_REGISTER_GROUP_D + 4) ///< 0x44 + #define REG_VBAT1_HIGH (INTERNAL_REGISTER_GROUP_D + 5) ///< 0x45 + #define REG_VBAT2_AVE_LOW (INTERNAL_REGISTER_GROUP_D + 6) ///< 0x46 + #define REG_VBAT2_AVE_HIGH (INTERNAL_REGISTER_GROUP_D + 7) ///< 0x47 + #define REG_VBAT3_AVE_LOW (INTERNAL_REGISTER_GROUP_D + 8) ///< 0x48 + #define REG_VBAT3_AVE_HIGH (INTERNAL_REGISTER_GROUP_D + 9) ///< 0x49 + #define REG_V1_LOW (INTERNAL_REGISTER_GROUP_D + 10) ///< 0x4A + #define REG_V1_HIGH (INTERNAL_REGISTER_GROUP_D + 11) ///< 0x4B + #define REG_V2_LOW (INTERNAL_REGISTER_GROUP_D + 12) ///< 0x4C + #define REG_V2_HIGH (INTERNAL_REGISTER_GROUP_D + 13) ///< 0x4D + #define REG_V3_LOW (INTERNAL_REGISTER_GROUP_D + 14) ///< 0x4E + #define REG_V3_HIGH (INTERNAL_REGISTER_GROUP_D + 15) ///< 0x4F + #define REG_INTR_TEMPER_LOW (INTERNAL_REGISTER_GROUP_D + 16) ///< 0x50 + #define REG_INTR_TEMPER_HIGH (INTERNAL_REGISTER_GROUP_D + 17) ///< 0x51 + #define REG_EXTR_TEMPER_LOW (INTERNAL_REGISTER_GROUP_D + 18) ///< 0x52 + #define REG_EXTR_TEMPER_HIGH (INTERNAL_REGISTER_GROUP_D + 19) ///< 0x53 + #define REG_RID_LOW (INTERNAL_REGISTER_GROUP_D + 20) ///< 0x54 + #define REG_RID_HIGH (INTERNAL_REGISTER_GROUP_D + 21) ///< 0x55 + #define REG_CURRENT_LOW (INTERNAL_REGISTER_GROUP_D + 22) ///< 0x56 + #define REG_CURRENT_HIGH (INTERNAL_REGISTER_GROUP_D + 23) ///< 0x57 + #define REG_ADC1_OFFSET_LOW (INTERNAL_REGISTER_GROUP_D + 24) ///< 0x58 + #define REG_ADC1_OFFSET_HIGH (INTERNAL_REGISTER_GROUP_D + 25) ///< 0x59 + + #define OTP6_BYTE1 (INTERNAL_REGISTER_GROUP_D + 48) ///< 0x70 + #define AVE_IT25_7_3 (63<<3) + #define DELTA_ET_3_2 (3<<1) + #define DELTA_VREF_4 (1<<0) + #define OTP6_BYTE2 (INTERNAL_REGISTER_GROUP_D + 49) ///< 0x71 + #define AVE_IT25_15_8 (255<<0) + #define OTP6_BYTE3 (INTERNAL_REGISTER_GROUP_D + 50) ///< 0x72 + #define AVE_IT80_7_3 (63<<3) + #define INDEX_ADC1_200_25_4 (1<<2) + #define INDEX_ADC1_100_25_4 (1<<1) + #define INDEX_ADC2_200_25_4 (1<<0) + #define OTP6_BYTE4 (INTERNAL_REGISTER_GROUP_D + 51) ///< 0x73 + #define AVE_IT80_15_8 (255<<0) + +#define INTERNAL_REGISTER_GROUP_E (0x8F) + #define REG_TIMER (INTERNAL_REGISTER_GROUP_E + 0) ///< 0x8F + #define TIMER_TIMER_ITSET (3<<6) + #define TIMER_ITSET_4 (0<<6) + #define TIMER_ITSET_9 (1<<6) + #define TIMER_ITSET_14 (2<<6) + #define TIMER_ITSET_19 (3<<6) + #define TIMER_TIMER_ETSET (3<<4) + #define TIMER_ETSET_4 (0<<4) + #define TIMER_ETSET_9 (1<<4) + #define TIMER_ETSET_14 (2<<4) + #define TIMER_ETSET_19 (3<<4) + #define TIMER_TIMER_VSET (3<<2) + #define TIMER_VSET_4 (0<<2) + #define TIMER_VSET_9 (1<<2) + #define TIMER_VSET_14 (2<<2) + #define TIMER_VSET_19 (3<<2) + #define TIMER_TIMER_CSET (3<<0) + #define TIMER_CSET_4 (0<<0) + #define TIMER_CSET_9 (1<<0) + #define TIMER_CSET_14 (2<<0) + #define TIMER_CSET_19 (3<<0) + #define REG_CLK_DIVA (INTERNAL_REGISTER_GROUP_E + 1) ///< 0x90 + #define CLK_DIVA_FW_CLK_CDIV (15<<4) + #define FW_CLK_CDIV_31US (0<<4) + #define FW_CLK_CDIV_61US (1<<4) + #define FW_CLK_CDIV_122US (2<<4) + #define FW_CLK_CDIV_244US (3<<4) + #define FW_CLK_CDIV_488US (4<<4) + #define FW_CLK_CDIV_977US (5<<4) + #define FW_CLK_CDIV_2MS (6<<4) + #define FW_CLK_CDIV_4MS (7<<4) + #define FW_CLK_CDIV_8MS (8<<4) + #define FW_CLK_CDIV_16MS (9<<4) + #define FW_CLK_CDIV_31MS (10<<4) + #define FW_CLK_CDIV_62MS (11<<4) + #define FW_CLK_CDIV_125MS (12<<4) + #define FW_CLK_CDIV_250MS (13<<4) + #define FW_CLK_CDIV_500MS (14<<4) + #define FW_CLK_CDIV_1S (15<<4) + #define CLK_DIVA_FW_CLK_VDIV (15<<0) + #define FW_CLK_VDIV_31US (0<<0) + #define FW_CLK_VDIV_61US (1<<0) + #define FW_CLK_VDIV_122US (2<<0) + #define FW_CLK_VDIV_244US (3<<0) + #define FW_CLK_VDIV_488US (4<<0) + #define FW_CLK_VDIV_977US (5<<0) + #define FW_CLK_VDIV_2MS (6<<0) + #define FW_CLK_VDIV_4MS (7<<0) + #define FW_CLK_VDIV_8MS (8<<0) + #define FW_CLK_VDIV_16MS (9<<0) + #define FW_CLK_VDIV_31MS (10<<0) + #define FW_CLK_VDIV_62MS (11<<0) + #define FW_CLK_VDIV_125MS (12<<0) + #define FW_CLK_VDIV_250MS (13<<0) + #define FW_CLK_VDIV_500MS (14<<0) + #define FW_CLK_VDIV_1S (15<<0) + #define REG_CLK_DIVB (INTERNAL_REGISTER_GROUP_E + 2) ///< 0x91 + #define CLK_DIVB_FW_CLK_ETDIV (15<<4) + #define FW_CLK_ETDIV_31US (0<<4) + #define FW_CLK_ETDIV_61US (1<<4) + #define FW_CLK_ETDIV_122US (2<<4) + #define FW_CLK_ETDIV_244US (3<<4) + #define FW_CLK_ETDIV_488US (4<<4) + #define FW_CLK_ETDIV_977US (5<<4) + #define FW_CLK_ETDIV_2MS (6<<4) + #define FW_CLK_ETDIV_4MS (7<<4) + #define FW_CLK_ETDIV_8MS (8<<4) + #define FW_CLK_ETDIV_16MS (9<<4) + #define FW_CLK_ETDIV_31MS (10<<4) + #define FW_CLK_ETDIV_62MS (11<<4) + #define FW_CLK_ETDIV_125MS (12<<4) + #define FW_CLK_ETDIV_250MS (13<<4) + #define FW_CLK_ETDIV_500MS (14<<4) + #define FW_CLK_ETDIV_1S (15<<4) + #define CLK_DIVB_FW_CLK_ITDIV (15<<0) + #define FW_CLK_ITDIV_31US (0<<0) + #define FW_CLK_ITDIV_61US (1<<0) + #define FW_CLK_ITDIV_122US (2<<0) + #define FW_CLK_ITDIV_244US (3<<0) + #define FW_CLK_ITDIV_488US (4<<0) + #define FW_CLK_ITDIV_977US (5<<0) + #define FW_CLK_ITDIV_2MS (6<<0) + #define FW_CLK_ITDIV_4MS (7<<0) + #define FW_CLK_ITDIV_8MS (8<<0) + #define FW_CLK_ITDIV_16MS (9<<0) + #define FW_CLK_ITDIV_31MS (10<<0) + #define FW_CLK_ITDIV_62MS (11<<0) + #define FW_CLK_ITDIV_125MS (12<<0) + #define FW_CLK_ITDIV_250MS (13<<0) + #define FW_CLK_ITDIV_500MS (14<<0) + #define FW_CLK_ITDIV_1S (15<<0) + #define REG_INTR_CTRL_D (INTERNAL_REGISTER_GROUP_E + 3) ///< 0x92 + #define INTR_CTRL_D_GPIO4_SEL (7<<3) + #define GPIO4_SEL_GPIO1 (0<<3) + #define GPIO4_SEL_ALARM (1<<3) + #define GPIO4_SEL_CBC_EN21 (2<<3) + #define GPIO4_SEL_CBC_EN32 (3<<3) + #define GPIO4_SEL_PWM (4<<3) + #define GPIO4_SEL_ADC1_D (6<<3) + #define GPIO4_SEL_ADC2_D (7<<3) + #define INTR_CTRL_D_GPIO3_SEL (7<<0) + #define GPIO3_SEL_GPIO1 (0<<0) + #define GPIO3_SEL_ALARM (1<<0) + #define GPIO3_SEL_CBC_EN21 (2<<0) + #define GPIO3_SEL_CBC_EN32 (3<<0) + #define GPIO3_SEL_PWM (4<<0) + #define GPIO3_SEL_ADC1_D (6<<0) + #define GPIO3_SEL_ADC2_D (7<<0) + #define OSCTUNE_J1 (INTERNAL_REGISTER_GROUP_E + 4) ///< 0x93 + #define OSCTUNE_J_LOW (255<<0) + #define OSCTUNE_J2 (INTERNAL_REGISTER_GROUP_E + 5) ///< 0x94 + #define OSCTUNE_J_HIGH (3<<0) + #define OSCTUNE_K1 (INTERNAL_REGISTER_GROUP_E + 6) ///< 0x95 + #define OSCTUNE_K_LOW (255<<0) + #define OSCTUNE_K2 (INTERNAL_REGISTER_GROUP_E + 7) ///< 0x96 + #define OSCTUNE_K_HIGH (3<<0) + #define OSCTUNE_CNTA (INTERNAL_REGISTER_GROUP_E + 8) ///< 0x97 + #define OSCTUNE_CNT_LOW (255<<0) + #define OSCTUNE_CNTB (INTERNAL_REGISTER_GROUP_E + 9) ///< 0x98 + #define OSCTUNE_CNT_HIGH (3<<0) + #define ADC1_TARGET_A (INTERNAL_REGISTER_GROUP_E + 10) ///< 0x99 + #define TARGET_A_LOW (255<<0) + #define ADC1_TARGET_B (INTERNAL_REGISTER_GROUP_E + 11) ///< 0x9A + #define TARGET_A_HIGH (3<<0) + #define REG_INTR_CTRL_A (INTERNAL_REGISTER_GROUP_E + 12) ///< 0x9B + #define INTR_CTRL_A_GPIO2_SEL (7<<5) + #define GPIO2_SEL_GPIO1 (0<<5) + #define GPIO2_SEL_ALARM (1<<5) + #define GPIO2_SEL_CBC_EN21 (2<<5) + #define GPIO2_SEL_CBC_EN32 (3<<5) + #define GPIO2_SEL_PWM (4<<5) + #define INTR_CTRL_A_GPIO1_SEL (7<<2) + #define GPIO1_SEL_GPIO1 (0<<2) + #define GPIO1_SEL_ALARM (1<<2) + #define GPIO1_SEL_CBC_EN21 (2<<2) + #define GPIO1_SEL_CBC_EN32 (3<<2) + #define GPIO1_SEL_PWM (4<<2) + #define GPIO1_SEL_OSCOUT (7<<2) + #define INTR_CTRL_A_ADC2_EN (1<<1) + #define INTR_CTRL_A_ADC1_EN (1<<0) + #define REG_INTR_CTRL_B (INTERNAL_REGISTER_GROUP_E + 13) ///< 0x9C + #define INTR_CTRL_B_OSC_CNT_EN (1<<7) + #define INTR_CTRL_B_PWM_EN (1<<6) + #define INTR_CTRL_B_CBC_32_EN (1<<5) + #define INTR_CTRL_B_CBC_21_EN (1<<4) + #define INTR_CTRL_B_STB_EN (1<<3) + #define INTR_CTRL_B_ET_EN (1<<2) + #define INTR_CTRL_B_IT_EN (1<<1) + #define INTR_CTRL_B_RID_EN (1<<0) + #define REG_ITNR_CTRL_C (INTERNAL_REGISTER_GROUP_E + 14) ///< 0x9D + #define INTR_CTRL_C_BGRCAL_FINISH (1<<7) + #define INTR_CTRL_C_FW_V_DIVIDE (1<<6) + #define INTR_CTRL_C_BGRCAL_START (1<<5) + #define INTR_CTRL_C_BGR_CALEN (1<<4) + #define INTR_CTRL_C_PWM_SET (3<<2) + #define PWM_SET_32K (0<<2) + #define PWM_SET_16K (1<<2) + #define PWM_SET_8K (2<<2) + #define PWM_SET_4K (3<<2) + #define INTR_CTRL_C_TIMER_SET (3<<0) + #define TIMER_SET_OV_UV (0<<0) + #define TIMER_SET_OC_UC (1<<0) + #define TIMER_SET_OIT_UIT (2<<0) + #define TIMER_SET_OET_UET (3<<0) + #define REG_CELL_EN (INTERNAL_REGISTER_GROUP_E + 15) ///< 0x9E + #define CELL_EN_APPLICATION (7<<2) + #define APPLICATION_UG3100 (0<<2) + #define APPLICATION_UG3101 (1<<2) + #define APPLICATION_UG3102 (2<<2) + #define APPLICATION_UG3103_2 (4<<2) + #define APPLICATION_UG3103_3 (5<<2) + #define CELL_EN1 (1<<1) + #define CELL_EN0 (1<<0) + +#define INTERNAL_REGISTER_GROUP_F (0x9F) + #define REG_COC_LOW (INTERNAL_REGISTER_GROUP_F + 0) ///< 0x9F + #define REG_COC_HIGH (INTERNAL_REGISTER_GROUP_F + 1) ///< 0xA0 + #define REG_DOC_LOW (INTERNAL_REGISTER_GROUP_F + 2) ///< 0xA1 + #define REG_DOC_HIGH (INTERNAL_REGISTER_GROUP_F + 3) ///< 0xA2 + #define REG_UC_LOW (INTERNAL_REGISTER_GROUP_F + 4) ///< 0xA3 + #define REG_UC_HIGH (INTERNAL_REGISTER_GROUP_F + 5) ///< 0xA4 + #define REG_OV1_LOW (INTERNAL_REGISTER_GROUP_F + 6) ///< 0xA5 + #define REG_OV1_HIGH (INTERNAL_REGISTER_GROUP_F + 7) ///< 0xA6 + #define REG_UV1_LOW (INTERNAL_REGISTER_GROUP_F + 8) ///< 0xA7 + #define REG_UV1_HIGH (INTERNAL_REGISTER_GROUP_F + 9) ///< 0xA8 + #define REG_OV2_LOW (INTERNAL_REGISTER_GROUP_F + 10) ///< 0xA9 + #define REG_OV2_HIGH (INTERNAL_REGISTER_GROUP_F + 11) ///< 0xAA + #define REG_UV2_LOW (INTERNAL_REGISTER_GROUP_F + 12) ///< 0xAB + #define REG_UV2_HIGH (INTERNAL_REGISTER_GROUP_F + 13) ///< 0xAC + #define REG_OV3_LOW (INTERNAL_REGISTER_GROUP_F + 14) ///< 0xAD + #define REG_OV3_HIGH (INTERNAL_REGISTER_GROUP_F + 15) ///< 0xAE + #define REG_UV3_LOW (INTERNAL_REGISTER_GROUP_F + 16) ///< 0xAF + #define REG_UV3_HIGH (INTERNAL_REGISTER_GROUP_F + 17) ///< 0xB0 + #define REG_OVP_LOW (INTERNAL_REGISTER_GROUP_F + 18) ///< 0xB1 + #define REG_OVP_HIGH (INTERNAL_REGISTER_GROUP_F + 19) ///< 0xB2 + #define REG_UVP_LOW (INTERNAL_REGISTER_GROUP_F + 20) ///< 0xB3 + #define REG_UVP_HIGH (INTERNAL_REGISTER_GROUP_F + 21) ///< 0xB4 + #define REG_INTR_OVER_TEMP_LOW (INTERNAL_REGISTER_GROUP_F + 22) ///< 0xB5 + #define REG_INTR_OVER_TEMP_HIGH (INTERNAL_REGISTER_GROUP_F + 23) ///< 0xB6 + #define REG_INTR_UNDER_TEMP_LOW (INTERNAL_REGISTER_GROUP_F + 24) ///< 0xB7 + #define REG_INTR_UNDER_TEMP_HIGH (INTERNAL_REGISTER_GROUP_F + 25) ///< 0xB8 + #define REG_EXTR_OVER_TEMP_LOW (INTERNAL_REGISTER_GROUP_F + 26) ///< 0xB9 + #define REG_EXTR_OVER_TEMP_HIGH (INTERNAL_REGISTER_GROUP_F + 27) ///< 0xBA + #define REG_EXTR_UNDER_TEMP_LOW (INTERNAL_REGISTER_GROUP_F + 28) ///< 0xBB + #define REG_EXTR_UNDER_TEMP_HIGH (INTERNAL_REGISTER_GROUP_F + 29) ///< 0xBC + #define REG_CBC21_LOW (INTERNAL_REGISTER_GROUP_F + 30) ///< 0xBD + #define REG_CBC21_HIGH (INTERNAL_REGISTER_GROUP_F + 31) ///< 0xBE + #define REG_CBC32_LOW (INTERNAL_REGISTER_GROUP_F + 32) ///< 0xBF + #define REG_CBC32_HIGH (INTERNAL_REGISTER_GROUP_F + 33) ///< 0xC0 + +#define INTERNAL_REGISTER_GROUP_G (0xC1) + #define REG_FW_CTRL (INTERNAL_REGISTER_GROUP_G + 0) ///< 0xC1 + #define FW_CTRL_CHOP2_EN (1<<3) + #define FW_CTRL_CHOPPING2 (1<<2) + #define FW_CTRL_CHOP1_EN (1<<1) + #define FW_CTRL_CHOPPING1 (1<<0) + #define REG_OTP_CTRL (INTERNAL_REGISTER_GROUP_G + 1) ///< 0xC2 + #define OTP_CTRL_IT_CAL80 (1<<7) + #define OTP_CTRL_IT_CAL25 (1<<6) + #define OTP_CTRL_ADC2_200MV (1<<5) + #define OTP_CTRL_ADC2_100MV (1<<4) + #define OTP_CTRL_ADC1_200MV (1<<3) + #define OTP_CTRL_ADC1_100MV (1<<2) + #define OTP_CTRL_OTP_PTM (3<<0) + #define REG_OTP_PPROG_ON (INTERNAL_REGISTER_GROUP_G + 2) ///< 0xC3 + #define OTP_PPROG_ON_VALUE (0xDD) + #define REG_OTP_PPROG_OFF (INTERNAL_REGISTER_GROUP_G + 3) ///< 0xC4 + #define OTP_PPROG_OFF_VALUE (0xDE) + +#define INTERNAL_REGISTER_GROUP_H (0xC5) + #define REG_ADC_CTR_A (INTERNAL_REGISTER_GROUP_H + 0) ///< 0xC5 + #define ADC_CTR_A_SET_A (3<<6) + #define SET_A_CURRENT (0<<6) + #define SET_A_ET (1<<6) + #define SET_A_RID_IN (2<<6) + #define SET_A_IT (3<<6) + #define ADC_CTR_A_SET_B (3<<4) + #define SET_B_CURRENT (0<<4) + #define SET_B_ET (1<<4) + #define SET_B_RID_IN (2<<4) + #define SET_B_IT (3<<4) + #define ADC_CTR_A_SET_C (3<<2) + #define SET_C_CURRENT (0<<2) + #define SET_C_ET (1<<2) + #define SET_C_RID_IN (2<<2) + #define SET_C_IT (3<<2) + #define ADC_CTR_A_SET_D (3<<0) + #define SET_D_CURRENT (0<<0) + #define SET_D_ET (1<<0) + #define SET_D_RID_IN (2<<0) + #define SET_D_IT (3<<0) + #define REG_ADC_CTR_B (INTERNAL_REGISTER_GROUP_H + 1) ///< 0xC6 + #define ADC_CTR_B_SET_E (3<<6) + #define SET_E_CURRENT (0<<6) + #define SET_E_ET (1<<6) + #define SET_E_RID_IN (2<<6) + #define SET_E_IT (3<<6) + #define ADC_CTR_B_SET_F (3<<4) + #define SET_F_CURRENT (0<<4) + #define SET_F_ET (1<<4) + #define SET_F_RID_IN (2<<4) + #define SET_F_IT (3<<4) + #define ADC_CTR_B_SET_G (3<<2) + #define SET_G_CURRENT (0<<2) + #define SET_G_ET (1<<2) + #define SET_G_RID_IN (2<<2) + #define SET_G_IT (3<<2) + #define ADC_CTR_B_SET_H (3<<0) + #define SET_H_CURRENT (0<<0) + #define SET_H_ET (1<<0) + #define SET_H_RID_IN (2<<0) + #define SET_H_IT (3<<0) + #define REG_ADC_CTR_C (INTERNAL_REGISTER_GROUP_H + 2) ///< 0xC7 + #define ADC_CTR_C_SET_I (3<<6) + #define SET_I_CURRENT (0<<6) + #define SET_I_ET (1<<6) + #define SET_I_RID_IN (2<<6) + #define SET_I_IT (3<<6) + #define ADC_CTR_C_SET_J (3<<4) + #define SET_J_CURRENT (0<<4) + #define SET_J_ET (1<<4) + #define SET_J_RID_IN (2<<4) + #define SET_J_IT (3<<4) + #define ADC_CTR_C_SET_K (3<<2) + #define SET_K_CURRENT (0<<2) + #define SET_K_ET (1<<2) + #define SET_K_RID_IN (2<<2) + #define SET_K_IT (3<<2) + #define ADC_CTR_C_SET_L (3<<0) + #define SET_L_CURRENT (0<<0) + #define SET_L_ET (1<<0) + #define SET_L_RID_IN (2<<0) + #define SET_L_IT (3<<0) + #define REG_ADC_CTR_D (INTERNAL_REGISTER_GROUP_H + 3) ///< 0xC8 + #define ADC_CTR_D_SET_M (3<<6) + #define SET_M_CURRENT (0<<6) + #define SET_M_ET (1<<6) + #define SET_M_RID_IN (2<<6) + #define SET_M_IT (3<<6) + #define ADC_CTR_D_SET_N (3<<4) + #define SET_N_CURRENT (0<<4) + #define SET_N_ET (1<<4) + #define SET_N_RID_IN (2<<4) + #define SET_N_IT (3<<4) + #define ADC_CTR_D_SET_O (3<<2) + #define SET_O_CURRENT (0<<2) + #define SET_O_ET (1<<2) + #define SET_O_RID_IN (2<<2) + #define SET_O_IT (3<<2) + #define ADC_CTR_D_SET_P (3<<0) + #define SET_P_CURRENT (0<<0) + #define SET_P_ET (1<<0) + #define SET_P_RID_IN (2<<0) + #define SET_P_IT (3<<0) + #define REG_ADC_V1 (INTERNAL_REGISTER_GROUP_H + 4) ///< 0xC9 + #define ADC_CTR_V1_SET_V1 (3<<6) + #define SET_V1_GND (0<<6) + #define SET_V1_VBAT1 (1<<6) + #define SET_V1_VBAT2 (2<<6) + #define SET_V1_VBAT3 (3<<6) + #define ADC_CTR_V1_SET_V2 (3<<4) + #define SET_V2_GND (0<<4) + #define SET_V2_VBAT1 (1<<4) + #define SET_V2_VBAT2 (2<<4) + #define SET_V2_VBAT3 (3<<4) + #define ADC_CTR_V1_SET_V3 (3<<2) + #define SET_V3_GND (0<<2) + #define SET_V3_VBAT1 (1<<2) + #define SET_V3_VBAT2 (2<<2) + #define SET_V3_VBAT3 (3<<2) + #define ADC_CTR_V1_SET_V4 (3<<0) + #define SET_V4_GND (0<<0) + #define SET_V4_VBAT1 (1<<0) + #define SET_V4_VBAT2 (2<<0) + #define SET_V4_VBAT3 (3<<0) + #define REG_ADC_V2 (INTERNAL_REGISTER_GROUP_H + 5) ///< 0xCA + #define ADC_CTR_V2_SET_V5 (3<<6) + #define SET_V5_GND (0<<6) + #define SET_V5_VBAT1 (1<<6) + #define SET_V5_VBAT2 (2<<6) + #define SET_V5_VBAT3 (3<<6) + #define ADC_CTR_V2_SET_V6 (3<<4) + #define SET_V6_GND (0<<4) + #define SET_V6_VBAT1 (1<<4) + #define SET_V6_VBAT2 (2<<4) + #define SET_V6_VBAT3 (3<<4) + #define ADC_CTR_V2_SET_V7 (3<<2) + #define SET_V7_GND (0<<2) + #define SET_V7_VBAT1 (1<<2) + #define SET_V7_VBAT2 (2<<2) + #define SET_V7_VBAT3 (3<<2) + #define ADC_CTR_V2_SET_V8 (3<<0) + #define SET_V8_GND (0<<0) + #define SET_V8_VBAT1 (1<<0) + #define SET_V8_VBAT2 (2<<0) + #define SET_V8_VBAT3 (3<<0) + #define REG_ADC_V3 (INTERNAL_REGISTER_GROUP_H + 6) ///< 0xCB + #define ADC_CTR_V3_SET_V9 (3<<6) + #define SET_V9_GND (0<<6) + #define SET_V9_VBAT1 (1<<6) + #define SET_V9_VBAT2 (2<<6) + #define SET_V9_VBAT3 (3<<6) + #define ADC_CTR_V3_SET_V10 (3<<4) + #define SET_V10_GND (0<<4) + #define SET_V10_VBAT1 (1<<4) + #define SET_V10_VBAT2 (2<<4) + #define SET_V10_VBAT3 (3<<4) + #define ADC_CTR_V3_SET_V11 (3<<2) + #define SET_V11_GND (0<<2) + #define SET_V11_VBAT1 (1<<2) + #define SET_V11_VBAT2 (2<<2) + #define SET_V11_VBAT3 (3<<2) + #define ADC_CTR_V3_SET_V12 (3<<0) + #define SET_V12_GND (0<<0) + #define SET_V12_VBAT1 (1<<0) + #define SET_V12_VBAT2 (2<<0) + #define SET_V12_VBAT3 (3<<0) + +#define INTERNAL_REGISTER_GROUP_I (0xCC) + #define KCONFIG_D1 (INTERNAL_REGISTER_GROUP_I + 0) ///< 0xCC + #define KCONFIG_D_LOW (255<<0) + #define KCONFIG_D2 (INTERNAL_REGISTER_GROUP_I + 1) ///< 0xCD + #define KCONFIG_D_HIGH (255<<0) + +#define INTERNAL_REGISTER_GROUP_J (0xCE) + #define KCONFIG_A1 (INTERNAL_REGISTER_GROUP_J + 0) ///< 0xCE + #define KCONFIG_A1_KGG1_OSC (255<<0) + #define KCONFIG_A2 (INTERNAL_REGISTER_GROUP_J + 1) ///< 0xCF + #define KCONFIG_A2_KGG1_DSM2_L (255<<0) + #define KCONFIG_A3 (INTERNAL_REGISTER_GROUP_J + 2) ///< 0xD0 + #define KCONFIG_A3_KGG1_DSM2_M (255<<0) + #define KCONFIG_A4 (INTERNAL_REGISTER_GROUP_J + 3) ///< 0xD1 + #define KCONFIG_A4_KGG1_DSM2_H (255<<0) + #define KCONFIG_A5 (INTERNAL_REGISTER_GROUP_J + 4) ///< 0xD2 + #define KCONFIG_A5_KGG1_DSM1_L (255<<0) + #define KCONFIG_A6 (INTERNAL_REGISTER_GROUP_J + 5) ///< 0xD3 + #define KCONFIG_A6_KGG1_DSM1_M (255<<0) + #define KCONFIG_A7 (INTERNAL_REGISTER_GROUP_J + 6) ///< 0xD4 + #define KCONFIG_A7_KGG1_DSM1_H (255<<0) + #define KCONFIG_A8 (INTERNAL_REGISTER_GROUP_J + 7) ///< 0xD5 + #define KCONFIG_A8_KGG1_MBIAS_L (255<<0) + #define KCONFIG_A9 (INTERNAL_REGISTER_GROUP_J + 8) ///< 0xD6 + #define KCONFIG_A9_KGG1_MBIAS_H (3<<0) + + #define KCONFIG_H1 (INTERNAL_REGISTER_GROUP_J + 9) ///< 0xD7 + #define KCONFIG_H1_KGG1_IDO_LOW (15<<4) + #define KCONFIG_H1_KGG1_MBIAS (3<<2) + #define KCONFIG_H1_KGG1_OSC (3<<2) + #define KCONFIG_H2 (INTERNAL_REGISTER_GROUP_J + 10) ///< 0xD8 + #define KCONFIG_H2_KGG1_IDO_HIGH (255<<0) + #define KCONFIG_H3 (INTERNAL_REGISTER_GROUP_J + 11) ///< 0xD9 + #define KCONFIG_H3_KGG1_BGAP_LOW (63<<2) + #define KCONFIG_H3_KGG1_DSM2 (3<<0) + #define KCONFIG_H4 (INTERNAL_REGISTER_GROUP_J + 12) ///< 0xDA + #define KCONFIG_H3_KGG1_BGAP_HIGH (127<<0) + #define KCONFIG_H5 (INTERNAL_REGISTER_GROUP_J + 13) ///< 0xDB + #define KCONFIG_H5_WIRE_V_DIVIDE (1<<6) + #define KCONFIG_H5_KGG1_BGAP_CAL (63<<0) + +#define INTERNAL_REGISTER_GROUP_K (0xDC) + #define KCONFIG_CAL1 (INTERNAL_REGISTER_GROUP_K + 0) ///< 0xDC + #define KCONFIG_CAL1_KGG1_OSC_L (255<<0) + #define KCONFIG_CAL2 (INTERNAL_REGISTER_GROUP_K + 1) ///< 0xDD + #define KCONFIG_CAL2_KGG1_OSC_H (255<<0) + #define KCONFIG_CAL3 (INTERNAL_REGISTER_GROUP_K + 2) ///< 0xDE + #define KCONFIG_CAL3_KGG1_DSM2 (255<<0) + #define KCONFIG_CAL4 (INTERNAL_REGISTER_GROUP_K + 3) ///< 0xDF + #define KCONFIG_CAL4_KGG1_DSM1 (255<<0) + +#define INTERNAL_REGISTER_GROUP_L (0xE0) + #define OTP1_BYTE1 (INTERNAL_REGISTER_GROUP_L + 0) ///< 0xE0 + #define DELTA_VREF_3_0 (15<<4) + #define INDEX_ADC1_200_25_3_0 (15<<0) + #define OTP1_BYTE2 (INTERNAL_REGISTER_GROUP_L + 1) ///< 0xE1 + #define FT_IT_6_3 (15<<4) + #define INDEX_ADC1_100_25_3_0 (15<<0) + #define OTP1_BYTE3 (INTERNAL_REGISTER_GROUP_L + 2) ///< 0xE2 + #define FT_IT_10_7 (15<<4) + #define INDEX_ADC2_200_25_3_0 (15<<0) + #define OTP1_BYTE4 (INTERNAL_REGISTER_GROUP_L + 3) ///< 0xE3 + #define FT_IT_14_11 (15<<4) + #define INDEX_ADC2_100_25_3_0 (15<<0) + + #define OTP2_BYTE1 (INTERNAL_REGISTER_GROUP_L + 16) ///< 0xF0 + #define DELTA_ET_1 (1<<7) + #define INDEX_ADC2_100_25_4 (1<<6) + #define DELTA_ET_0 (1<<5) + #define PRODUCT_TYPE (3<<3) + #define ITDELTACODE25_10_8 (7<<0) + #define OTP2_BYTE2 (INTERNAL_REGISTER_GROUP_L + 17) ///< 0xF1 + #define ITDELTACODE25_7_0 (255<<0) + #define OTP2_BYTE3 (INTERNAL_REGISTER_GROUP_L + 18) ///< 0xF2 + #define OTP_CELL_EN (31<<3) + #define ITDELTACODE80_10_8 (7<<0) + #define OTP2_BYTE4 (INTERNAL_REGISTER_GROUP_L + 19) ///< 0xF3 + #define ITDELTACODE80_7_0 (255<<0) + + #define OTP3_BYTE1 (INTERNAL_REGISTER_GROUP_L + 20) ///< 0xF4 + #define DEVADDR_9_0 (63<<2) + #define ADC1DELTACODE25_200_9 (1<<1) + #define ADC1DELTACODE25_200_8 (1<<0) + #define OTP3_BYTE2 (INTERNAL_REGISTER_GROUP_L + 21) ///< 0xF5 + #define BGRTUNE_5_0 (63<<2) + #define ADC1DELTACODE80_200_9_8 (3<<0) + #define OTP3_BYTE3 (INTERNAL_REGISTER_GROUP_L + 22) ///< 0xF6 + #define OSCDELTACODE25 (255<<0) + #define OTP3_BYTE4 (INTERNAL_REGISTER_GROUP_L + 23) ///< 0xF7 + #define OSCDELTACODE80 (255<<0) + + #define OTP4_BYTE1 (INTERNAL_REGISTER_GROUP_L + 24) ///< 0xF8 + #define ADC1DELTACODE25_200_7_0 (255<<0) + #define OTP4_BYTE2 (INTERNAL_REGISTER_GROUP_L + 25) ///< 0xF9 + #define ADC1DELTACODE80_200_7_0 (255<<0) + #define OTP4_BYTE3 (INTERNAL_REGISTER_GROUP_L + 26) ///< 0xFA + #define ADC1DELTACODE25_100_7_0 (255<<0) + #define OTP4_BYTE4 (INTERNAL_REGISTER_GROUP_L + 27) ///< 0xFB + #define ADC1DELTACODE80_100_7_0 (255<<0) + + #define OTP5_BYTE1 (INTERNAL_REGISTER_GROUP_L + 28) ///< 0xFC + #define ADC2DELTACODE25_100_6 (1<<7) + #define ADC2DELTACODE25_100_5_0 (63<<1) + #define ADC1DELTACODE25_100_8 (1<<0) + #define OTP5_BYTE2 (INTERNAL_REGISTER_GROUP_L + 29) ///< 0xFD + #define ADC2DELTACODE80_100_6_0 (127<<1) + #define ADC1DELTACODE80_100_8 (1<<0) + #define OTP5_BYTE3 (INTERNAL_REGISTER_GROUP_L + 30) ///< 0xFE + #define ADC2DELTACODE25_200_7 (1<<7) + #define ADC2DELTACODE25_200_6_0 (127<<0) + #define OTP5_BYTE4 (INTERNAL_REGISTER_GROUP_L + 31) ///< 0xFF + #define ADC2DELTACODE80_200_7_0 (255<<0) + diff --git a/drivers/power/wmt_battery/gauge/ug31xx/ug31xx_gauge.c b/drivers/power/wmt_battery/gauge/ug31xx/ug31xx_gauge.c new file mode 100755 index 00000000..433ff15c --- /dev/null +++ b/drivers/power/wmt_battery/gauge/ug31xx/ug31xx_gauge.c @@ -0,0 +1,782 @@ +/* + * Copyright (c) 2012, uPI Semiconductor Corp. All Rights Reserved. + */ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/param.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/power_supply.h> +#include <linux/idr.h> +#include <linux/reboot.h> +#include <linux/notifier.h> +#include <linux/jiffies.h> +#include <linux/err.h> +#include <asm/unaligned.h> +#include <linux/wakelock.h> +#include <linux/version.h> + +#include <linux/power/wmt_battery.h> +#include <mach/wmt_env.h> + +#include "ug31xx_gauge.h" +#include "uG31xx_API.h" + +/* Functions Declaration */ +static int ug31xx_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val); +static int ug31xx_update_psp(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val); +static void ug31xx_external_power_changed(struct power_supply *psy); + +#define UG31XX_CHECK_FILE_CNT_THRD (20) + +/* Global Variables */ +static struct ug31xx_gauge *ug31 = NULL; +static char chk_backup_file_cnt = UG31XX_CHECK_FILE_CNT_THRD; +static char *pGGB = NULL; +static GG_DEVICE_INFO gauge_dev_info; +static GG_CAPACITY gauge_dev_capacity; +static struct workqueue_struct *ug31xx_gauge_wq = NULL; +static int charger_dc_in_before_suspend = 0; +static bool ug3105_in_suspend = false; +static bool ug3105_charger_enabled = true; +static unsigned char force_reset = 0; +static unsigned char enable_debug = 0; + +static __attribute__((unused)) char *chg_status[] = { + "Unknown", + "Charging", + "Discharging", + "Not charging", + "Full" +}; + +static drv_status_t drv_status = DRV_NOT_READY; + +#include "ggb/ug31xx_ggb_data_wms8309_wm8_20130820_110949.h" +#include "ggb/ug31xx_ggb_data_wms8309_c7_20130725_164935.h" +#include "ggb/ug31xx_ggb_data_wms8309_c7_20130910_130553.h" +#include "ggb/ug31xx_ggb_data_wms7320_20130718_200031.h" +#include "ggb/ug31xx_ggb_data_cw500_20130801_103638.h" +#include "ggb/ug31xx_ggb_data_mp718_20131004_070110.h" +#include "ggb/ug31xx_ggb_data_t73v_20131120_001204.h" + +enum { + UG31XX_ID_3105, + UG31XX_ID_3102, +}; + +struct ggb_info { + char *name; + uint8_t *data; +}; + +/* Extern Function */ +static struct ggb_info ggb_arrays[] = { + { + .name = "wms8309wm8", + .data = FactoryGGBXFile_wms8309_wm8, + }, { + .name = "wms7320", + .data = FactoryGGBXFile_wms7320, + }, { + .name = "wms8309c7_3900mAh", + .data = FactoryGGBXFile_wms8309_c7_3900mAh, + }, { + .name = "wms8309c7_3000mAh", + .data = FactoryGGBXFile_wms8309_c7_3000mAh, + }, { + .name = "cw500", + .data = FactoryGGBXFile_cw500, + }, { + .name = "mp718", + .data = FactoryGGBXFile_mp718, + }, { + .name = "t73v", + .data = FactoryGGBXFile_t73v, + }, +}; + +struct charger_param { + int id; + int i2c; + int et; + int temp_range[2]; + struct ggb_info *ggb; +}; + +static struct charger_param charger_param; + +static bool inline export_external_termperature(void) +{ + return !!charger_param.et; +} + +static int parse_battery_param(void) +{ + char env[] = "wmt.battery.param"; + char buf[64]; + size_t l = sizeof(buf); + int i; + + if (wmt_getsyspara(env, buf, &l)) + return -EINVAL; + + if (!prefixcmp(buf, "ug3105:")) { + charger_param.id = UG31XX_ID_3105; + } else if (!prefixcmp(buf, "ug3102:")) { + charger_param.id = UG31XX_ID_3102; + } else { + return -EINVAL; + } + + i = sscanf(buf + 7, "%d:%d:%d:%d", + &charger_param.i2c, &charger_param.et, + &charger_param.temp_range[0], + &charger_param.temp_range[1]); + if (i < 4) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(ggb_arrays); i++) { + if (strstr(buf, ggb_arrays[i].name)) { + charger_param.ggb = &ggb_arrays[i]; + break; + } + } + if (i == ARRAY_SIZE(ggb_arrays)) + charger_param.ggb = &ggb_arrays[0]; + + pr_info("%s i2c%d, use %s temperature, range [%d, %d], ggb %s\n", + charger_param.id == UG31XX_ID_3105 ? "ug3105" : "ug3102", + charger_param.i2c, + charger_param.et ? "External" : "Internal", + charger_param.temp_range[0], + charger_param.temp_range[1], + charger_param.ggb->name); + + return 0; +} + +/* + * WMT MCE: Use gpio3 on ug31xx as a switch to control charger. + */ +static void hw_charging_set(bool enable) +{ + if (charger_param.id == UG31XX_ID_3105) { + API_I2C_SingleWrite(0, 0, 0, 0x16, enable ? 0x2 : 0x0); + } else if (charger_param.id == UG31XX_ID_3102) { + u8 data = 0; + API_I2C_SingleRead(0, 0, 0 ,REG_CTRL1, &data); + if (enable) + data |= CTRL1_IO1DATA; + else + data &= ~CTRL1_IO1DATA; + API_I2C_SingleWrite(0, 0, 0, REG_CTRL1, data); + } + ug3105_charger_enabled = enable; +} + +static void inline hw_charging_disable(void) +{ + hw_charging_set(false); +} + +static void inline hw_charging_enable(void) +{ + hw_charging_set(true); +} + +static bool is_charging_full(void) +{ + return (ug3105_charger_enabled == true) && charger_is_full(); +} + +static enum power_supply_property ug31xx_batt_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + 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_CHARGE_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL, +}; + +static struct power_supply ug31xx_supply[] = { + { + .name = "battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = ug31xx_batt_props, + .num_properties = ARRAY_SIZE(ug31xx_batt_props), + .get_property = ug31xx_battery_get_property, + .external_power_changed = ug31xx_external_power_changed, + }, +}; + +static int ug31xx_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_HEALTH: + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 1; + break; + case POWER_SUPPLY_PROP_STATUS: + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + case POWER_SUPPLY_PROP_CURRENT_NOW: + case POWER_SUPPLY_PROP_CAPACITY: + case POWER_SUPPLY_PROP_TEMP: + case POWER_SUPPLY_PROP_CHARGE_NOW: + case POWER_SUPPLY_PROP_CHARGE_FULL: + if (ug31xx_update_psp(psy, psp, val)) + return -EINVAL; + break; + default: + return -EINVAL; + } + return ret; +} + +static int ug31xx_update_psp(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ +// int rtn; + + if (drv_status != DRV_INIT_OK) { + GAUGE_err("Gauge driver not init finish\n"); + return -EINVAL; + } + + if (psp == POWER_SUPPLY_PROP_TEMP) { + if (export_external_termperature()) + ug31->batt_temp = gauge_dev_info.ET; + else + ug31->batt_temp = gauge_dev_info.IT; + val->intval = ug31->batt_temp; + GAUGE_notice("I.Temperature=%d, E.T=%d\n", gauge_dev_info.IT, gauge_dev_info.ET); + } + + if (psp == POWER_SUPPLY_PROP_CAPACITY) { + if (ug31->batt_status == POWER_SUPPLY_STATUS_FULL) + ug31->batt_capacity = 100; + else + ug31->batt_capacity = gauge_dev_capacity.RSOC; + + val->intval = ug31->batt_capacity; + GAUGE_notice("Capacity=%d %%\n", val->intval); + } + + if (psp == POWER_SUPPLY_PROP_VOLTAGE_NOW) { + val->intval = ug31->batt_volt = gauge_dev_info.voltage_mV; + GAUGE_notice("Voltage=%d mV\n", val->intval); + } + + if (psp == POWER_SUPPLY_PROP_CURRENT_NOW) { + val->intval = ug31->batt_current = gauge_dev_info.AveCurrent_mA; + GAUGE_notice("Current=%d mA\n", val->intval); + } + + if (psp == POWER_SUPPLY_PROP_STATUS) { + int status = charger_get_status(); + if (status < 0) + return status; + + if (status == POWER_SUPPLY_STATUS_CHARGING && ug31->batt_capacity == 100) + status = POWER_SUPPLY_STATUS_FULL; + + ug31->batt_status = status; + val->intval = ug31->batt_status; + GAUGE_notice("Status=%s\n", chg_status[val->intval]); + } + + if (psp == POWER_SUPPLY_PROP_CHARGE_NOW) { + val->intval = ug31->batt_charge_now = gauge_dev_capacity.NAC; + GAUGE_notice("Charge_Now=%d mAh\n", val->intval); + } + + if (psp == POWER_SUPPLY_PROP_CHARGE_FULL) { + val->intval =ug31->batt_charge_full = gauge_dev_capacity.LMD; + GAUGE_notice("Charge_Full=%d mAh\n", val->intval); + } + +#if 0 + mutex_lock(&ug31->info_update_lock); + chk_backup_file_cnt = chk_backup_file_cnt + 1; + UG31_LOGI("chk_backup_file_cnt %d\n", chk_backup_file_cnt); + if (chk_backup_file_cnt > UG31XX_CHECK_FILE_CNT_THRD) { + rtn = upiGG_CheckBackupFile(pGGB); + if(rtn == UPI_CHECK_BACKUP_FILE_MISMATCH) + { + force_reset = 1; + UG31_LOGI("[%s]: Force reset due to version dismatched.\n", __func__); + } + else if(rtn == UPI_CHECK_BACKUP_FILE_EXIST) + { + chk_backup_file_cnt = 0; + UG31_LOGI("[%s]: Backup file existed -> no need to check frequently.\n", __func__) + } + else + { + chk_backup_file_cnt = UG31XX_CHECK_FILE_CNT_THRD; + UG31_LOGI("[%s]: Backup file not existed -> need to check frequently.\n", __func__) + } + UG31_LOGI("[%s]: Check backup file status = %d\n", __func__, rtn); + } + mutex_unlock(&ug31->info_update_lock); +#endif + return 0; +} + +static int ug31xx_powersupply_init(struct i2c_client *client) +{ + int i, ret; + for (i = 0; i < ARRAY_SIZE(ug31xx_supply); i++) { + ret = power_supply_register(&client->dev, &ug31xx_supply[i]); + if (ret) { + GAUGE_err("Failed to register power supply\n"); + while (i--) + power_supply_unregister(&ug31xx_supply[i]); + return ret; + } + } + return 0; +} + +static void ug31xx_external_power_changed(struct power_supply *psy) +{ + if (ug3105_in_suspend == false) + queue_delayed_work(ug31xx_gauge_wq, &ug31->ug31_gauge_info_work, 0*HZ); +} + +#define UG31XX_INITIAL_RETRY_CNT (3) +#define UG31XX_INITIAL_LOCK_ENABLE (1) +#define UG31XX_INITIAL_LOCK_DISABLE (0) +#define UG31XX_INITIAL_FIX_T_COUNT (30) + +static int ug31_gauge_info_reset(int enable_lock) +{ + int retry; + int gg_status; + + force_reset = 0; + + if(enable_lock == UG31XX_INITIAL_LOCK_ENABLE) + { + mutex_lock(&ug31->info_update_lock); + } + + retry = 0; + while(retry < UG31XX_INITIAL_RETRY_CNT) + { + gg_status = UG_SUCCESS; + if(pGGB != NULL) + { + gg_status = upiGG_UnInitial(&pGGB); + pGGB = NULL; + GAUGE_notice("Driver remove. gg_status=0x%02x\n", gg_status); + } + + if(gg_status == UG_SUCCESS) + { + gg_status = upiGG_Initial(&pGGB, + (GGBX_FILE_HEADER *)charger_param.ggb->data, + force_reset, UG31XX_INITIAL_FIX_T_COUNT); + + if(gg_status == UG_INIT_SUCCESS) + { + chk_backup_file_cnt = UG31XX_CHECK_FILE_CNT_THRD; + if(enable_lock == UG31XX_INITIAL_LOCK_ENABLE) + { + mutex_unlock(&ug31->info_update_lock); + } + return (0); + } + } + + retry = retry + 1; + GAUGE_err("GGB file read and init fail count = %d (%d)\n", retry, gg_status); + } + + GAUGE_err("Reset uG31xx fail.\n"); + if(pGGB != NULL) + { + gg_status = upiGG_UnInitial(&pGGB); + pGGB = NULL; + GAUGE_notice("Driver remove. gg_status=0x%02x\n", gg_status); + } + + if(enable_lock == UG31XX_INITIAL_LOCK_ENABLE) + { + mutex_unlock(&ug31->info_update_lock); + } + return (-1); +} + +static void ug31_gauge_info_work_func(struct work_struct *work) +{ + int gg_status = 0, retry = 3; + struct power_supply *psy = &ug31xx_supply[PWR_SUPPLY_BATTERY]; + int batt_status = ug31->batt_status; + struct ug31xx_gauge *ug31_dev; + + ug31_dev = container_of(work, struct ug31xx_gauge, ug31_gauge_info_work.work); + + if(enable_debug == 0) + { + upiGG_DebugSwitch(_UPI_FALSE_); + UG31_LOGI("Set upiGG_DebugSwitch to FALSE\n"); + } + else + { + upiGG_DebugSwitch(_UPI_TRUE_); + UG31_LOGI("Set upiGG_DebugSwitch to TRUE\n"); + } + + UG31_LOGI("Update gauge info!!\n"); + + mutex_lock(&ug31->info_update_lock); + while (retry-- > 0) { + gg_status = upiGG_ReadDeviceInfo(pGGB,&gauge_dev_info); + if (gg_status == UG_READ_DEVICE_INFO_SUCCESS) + goto read_dev_info_ok; + } + GAUGE_err("Read device info fail. gg_status=%d\n", gg_status); + if(gg_status == UG_MEAS_FAIL_BATTERY_REMOVED) + { + gauge_dev_capacity.NAC = 0; + gauge_dev_capacity.LMD = 0; + gauge_dev_capacity.RSOC = 0; + } + goto read_dev_info_fail; + +read_dev_info_ok: + if(gauge_dev_capacity.Ready != UG_CAP_DATA_READY) + { + if(gauge_dev_info.ET > UG31XX_MAX_TEMPERATURE_BEFORE_READY) + { + gauge_dev_info.ET = UG31XX_MAX_TEMPERATURE_BEFORE_READY; + } + if(gauge_dev_info.ET < UG31XX_MIN_TEMPERATURE_BEFORE_READY) + { + gauge_dev_info.ET = UG31XX_MIN_TEMPERATURE_BEFORE_READY; + } + } + + upiGG_ReadCapacity(pGGB,&gauge_dev_capacity); + UG31_LOGI("Gauge info updated !!\n"); + +read_dev_info_fail: + mutex_unlock(&ug31->info_update_lock); + + if(force_reset == 1) + { + UG31_LOGI("Reset whole uG31xx driver.\n"); + gg_status = ug31_gauge_info_reset(UG31XX_INITIAL_LOCK_ENABLE); + if(gg_status != 0) + { + goto set_polling_time; + } + gauge_dev_capacity.Ready = UG_CAP_DATA_READY; + } + + /* Disable charging by temperture */ + + if (charger_param.id == UG31XX_ID_3105) { + if (export_external_termperature()) { + if (gauge_dev_info.ET < charger_param.temp_range[0] || + gauge_dev_info.ET > charger_param.temp_range[1]) + hw_charging_disable(); + else + hw_charging_enable(); + } + } else if (charger_param.id == UG31XX_ID_3102) { + hw_charging_enable(); + } + + if (wmt_charger_is_dc_plugin() && power_supply_am_i_supplied(psy)) { + if (gauge_dev_capacity.RSOC == 100) + batt_status = POWER_SUPPLY_STATUS_FULL; + else if (is_charging_full() && ug31->batt_capacity >= 90) { + batt_status = POWER_SUPPLY_STATUS_FULL; + gauge_dev_capacity.RSOC = 100; + mutex_lock(&ug31->info_update_lock); + upiGG_ForceTaper(pGGB, 1, 1, 1); + mutex_unlock(&ug31->info_update_lock); + } else + batt_status = POWER_SUPPLY_STATUS_CHARGING; + } else + batt_status = POWER_SUPPLY_STATUS_DISCHARGING; + + /* Report uevent while capacity changed */ + if (ug31->batt_capacity != gauge_dev_capacity.RSOC || + ug31->batt_status != batt_status) { + GAUGE_notice("Capacity changed: %d -> %d\n", ug31->batt_capacity, gauge_dev_capacity.RSOC); + GAUGE_notice("Status changed: %d -> %d\n", ug31->batt_status, batt_status); + power_supply_changed(psy); + } + +set_polling_time: + if (gauge_dev_capacity.Ready == UG_CAP_DATA_READY) { + queue_delayed_work(ug31xx_gauge_wq, &ug31_dev->ug31_gauge_info_work, 5*HZ); + } else if(force_reset == 1) { + queue_delayed_work(ug31xx_gauge_wq, &ug31_dev->ug31_gauge_info_work, 0*HZ); + } else { + queue_delayed_work(ug31xx_gauge_wq, &ug31_dev->ug31_gauge_info_work, 1*HZ); + } +} + +static int __devinit ug31xx_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + int gg_status; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + return -EIO; + + ug31 = kzalloc(sizeof(*ug31), GFP_KERNEL); + if (!ug31) + return -ENOMEM; + + ug31->client = client; + ug31->dev = &client->dev; + + i2c_set_clientdata(client, ug31); + ug31xx_i2c_client_set(ug31->client); + + gauge_dev_capacity.RSOC = 50; + + /* get GGB file */ + GAUGE_notice("[UPI]: Force to reset uG3105 driver (%d)\n", force_reset); + gg_status = ug31_gauge_info_reset(UG31XX_INITIAL_LOCK_DISABLE); + if(gg_status != 0) + { + goto ggb_init_fail; + } + + mutex_init(&ug31->info_update_lock); + wake_lock_init(&ug31->cable_wake_lock, WAKE_LOCK_SUSPEND, "cable_state_changed"); + + ug31xx_gauge_wq = create_singlethread_workqueue("ug31xx_gauge_work_queue"); + INIT_DELAYED_WORK(&ug31->ug31_gauge_info_work, ug31_gauge_info_work_func); + queue_delayed_work(ug31xx_gauge_wq, &ug31->ug31_gauge_info_work, 0*HZ); + + /* power supply registration */ + if (ug31xx_powersupply_init(client)) + goto pwr_supply_fail; + + force_reset = 0; + drv_status = DRV_INIT_OK; + + GAUGE_notice(" Driver %s registered done\n", client->name); + + return 0; + +pwr_supply_fail: + kfree(ug31); +ggb_init_fail: + if(!pGGB) { + upiGG_UnInitial(&pGGB); + } + force_reset = 0; + return gg_status; +} + +static int __devexit ug31xx_i2c_remove(struct i2c_client *client) +{ + struct ug31xx_gauge *ug31_dev; + int i = 0, gg_status; + + for (i = 0; i < ARRAY_SIZE(ug31xx_supply); i++) { + power_supply_unregister(&ug31xx_supply[i]); + } + + cancel_delayed_work_sync(&ug31->ug31_gauge_info_work); + destroy_workqueue(ug31xx_gauge_wq); + wake_lock_destroy(&ug31->cable_wake_lock); + + gg_status = upiGG_UnInitial(&pGGB); + GAUGE_notice("Driver remove. gg_status=0x%02x\n", gg_status); + + ug31_dev = i2c_get_clientdata(client); + if (ug31_dev) { + kfree(ug31_dev); + } + return 0; +} + +static int ug31xx_i2c_suspend(struct i2c_client *client, pm_message_t mesg) +{ + int gg_status; + + GAUGE_notice("ug31xx_i2c_suspend() start\n"); + ug3105_in_suspend = true; + + cancel_delayed_work_sync(&ug31->ug31_gauge_info_work); + flush_workqueue(ug31xx_gauge_wq); + + mutex_lock(&ug31->info_update_lock); + gg_status = upiGG_PreSuspend(pGGB); + if (gg_status != UG_READ_DEVICE_INFO_SUCCESS) { + GAUGE_err("Fail in suspend. gg_status=0x%02x\n", gg_status); + } else + GAUGE_notice("Driver suspend. gg_status=0x%02x\n", gg_status); + mutex_unlock(&ug31->info_update_lock); + + charger_dc_in_before_suspend = wmt_charger_is_dc_plugin(); + + if (charger_param.id == UG31XX_ID_3102) { + hw_charging_enable(); + } + + GAUGE_notice("ug31xx_i2c_suspend() end\n"); + return 0; +} + +static int ug31xx_i2c_resume(struct i2c_client *client) +{ + int gg_status; + + GAUGE_notice("ug31xx_i2c_resume() start\n"); + + mutex_lock(&ug31->info_update_lock); + gg_status = upiGG_Wakeup(pGGB, charger_dc_in_before_suspend); + if (gg_status != UG_READ_DEVICE_INFO_SUCCESS) { + GAUGE_err("Fail in resume. gg_status=0x%02x\n", gg_status); + if(gg_status == UG_MEAS_FAIL_BATTERY_REMOVED) + { + gauge_dev_capacity.NAC = 0; + gauge_dev_capacity.LMD = 0; + gauge_dev_capacity.RSOC = 0; + } + } else { + GAUGE_notice("Driver resume. gg_status=0x%02x\n", gg_status); + } + mutex_unlock(&ug31->info_update_lock); + + /// [AT-PM] : Check charger status ; 08/14/2013 + mutex_lock(&ug31->info_update_lock); + upiGG_ForceTaper(pGGB, is_charging_full(), charger_dc_in_before_suspend, wmt_charger_is_dc_plugin()); + mutex_unlock(&ug31->info_update_lock); + + ug3105_in_suspend = false; + + cancel_delayed_work(&ug31->ug31_gauge_info_work); + queue_delayed_work(ug31xx_gauge_wq, &ug31->ug31_gauge_info_work, 0*HZ); + + GAUGE_notice("ug31xx_i2c_resume() end\n"); + return 0; +} + +void ug31xx_i2c_shutdown(struct i2c_client *client) +{ + int gg_status; + + cancel_delayed_work(&ug31->ug31_gauge_info_work); + mutex_lock(&ug31->info_update_lock); + gg_status = upiGG_PrePowerOff(pGGB); + mutex_unlock(&ug31->info_update_lock); + GAUGE_notice("Driver shutdown. gg_status=0x%02x\n", gg_status); +} + +static const struct i2c_device_id ug31xx_i2c_id[] = { + { UG31XX_DEV_NAME, 0 }, + { }, +}; + +MODULE_DEVICE_TABLE(i2c, ug31xx_i2c_id); + +static struct i2c_driver ug31xx_i2c_driver = { + .driver = { + .name = UG31XX_DEV_NAME, + .owner = THIS_MODULE, + }, + .probe = ug31xx_i2c_probe, + .remove = __devexit_p(ug31xx_i2c_remove), + .suspend = ug31xx_i2c_suspend, + .resume = ug31xx_i2c_resume, + .shutdown = ug31xx_i2c_shutdown, + .id_table = ug31xx_i2c_id, +}; + +static struct i2c_board_info ug31xx_i2c_board_info = { + .type = "ug31xx-gauge", + .flags = 0x00, + .addr = 0x70, + .platform_data = NULL, + .archdata = NULL, + .irq = -1, +}; + +static struct i2c_client *i2c_client; +static struct i2c_adapter *i2c_adap; + +static int __init ug31xx_i2c_init(void) +{ + int ret; + + ret = parse_battery_param(); + if (ret) + return ret; + + i2c_adap = i2c_get_adapter(charger_param.i2c); + if (!i2c_adap) { + pr_err("Cannot get i2c adapter %d\n", charger_param.i2c); + ret = -ENODEV; + goto err1; + } + + i2c_client = i2c_new_device(i2c_adap, &ug31xx_i2c_board_info); + if (!i2c_client) { + pr_err("Unable to add I2C device for 0x%x\n", + ug31xx_i2c_board_info.addr); + ret = -ENODEV; + goto err2; + } + + ret = i2c_add_driver(&ug31xx_i2c_driver); + if (ret) + goto err3; + + return 0; + +err3: i2c_unregister_device(i2c_client); +err2: i2c_put_adapter(i2c_adap); +err1: + return ret; +} +module_init(ug31xx_i2c_init); + +static void __exit ug31xx_i2c_exit(void) +{ + i2c_put_adapter(i2c_adap); + i2c_del_driver(&ug31xx_i2c_driver); + i2c_unregister_device(i2c_client); +} +module_exit(ug31xx_i2c_exit); + +MODULE_DESCRIPTION("ug31xx gauge driver"); +MODULE_LICENSE("GPL"); + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,12) +MODULE_PARM (force_reset, "b"); +MODULE_PARM (enable_debug, "b"); +#else +module_param (force_reset, byte, 0); +module_param (enable_debug, byte, 0644); +#endif +MODULE_PARM_DESC(force_reset, "Set 1 to force reset driver as first power on."); +MODULE_PARM_DESC(enable_debug, "Set 1 to enable dumping debug message."); diff --git a/drivers/power/wmt_battery/gauge/ug31xx/ug31xx_gauge.h b/drivers/power/wmt_battery/gauge/ug31xx/ug31xx_gauge.h new file mode 100755 index 00000000..212d7e30 --- /dev/null +++ b/drivers/power/wmt_battery/gauge/ug31xx/ug31xx_gauge.h @@ -0,0 +1,65 @@ +/*
+ * Copyright (c) 2012, ASUSTek, Inc. All Rights Reserved.
+ */
+
+#ifndef __UPI_ug31xx_GAUGE_H
+#define __UPI_ug31xx_GAUGE_H
+
+//#define UG31XX_DYNAMIC_POLLING
+
+#define UG31XX_DEV_NAME "ug31xx-gauge"
+
+#ifdef UG31XX_DEBUG_ENABLE
+#define GAUGE_notice(format, arg...) \
+ printk(KERN_DEBUG "GAUGE: [%s] " format , __func__ , ## arg);
+#else
+#define GAUGE_notice(format, arg...)
+#endif
+
+#define GAUGE_err(format, arg...) \
+ printk(KERN_ERR "GAUGE: [%s] " format , __FUNCTION__ , ## arg);
+
+#define UPI_DEBUG_STRING (320)
+
+typedef enum {
+ DRV_NOT_READY = 0,
+ DRV_INIT_OK,
+} drv_status_t;
+
+struct ug31xx_gauge {
+ struct i2c_client *client;
+ struct device *dev;
+ struct delayed_work batt_info_update_work;
+ struct delayed_work ug31_gauge_info_work;
+ struct wake_lock cable_wake_lock;
+ struct mutex info_update_lock;
+ u32 cable_status;
+ u32 polling_time;
+ u32 batt_volt;
+ u32 batt_capacity;
+ u32 batt_charge_now;
+ u32 batt_charge_full;
+ int batt_current;
+ int batt_temp;
+ int batt_status;
+
+ /// [AT-PM] : Add for version ; 01/30/2013
+ char gauge_debug[UPI_DEBUG_STRING];
+
+ /// [AT-PM] : Alarm status ; 04/19/2013
+ u8 alarmSts;
+};
+
+enum {
+ PWR_SUPPLY_BATTERY = 0,
+ PWR_SUPPLY_AC,
+ PWR_SUPPLY_USB
+};
+
+enum {
+ NO_CABLE = 0,
+ USB_PC_CABLE = 1,
+ AC_ADAPTER_CABLE = 3
+};
+
+#endif /*__UPI_ug31xx_GAUGE_H */
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/ug31xx_i2c.c b/drivers/power/wmt_battery/gauge/ug31xx/ug31xx_i2c.c new file mode 100755 index 00000000..6249df50 --- /dev/null +++ b/drivers/power/wmt_battery/gauge/ug31xx/ug31xx_i2c.c @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2012, ASUSTek, Inc. All Rights Reserved. + */ +#include <linux/module.h> +#include <linux/param.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <asm/unaligned.h> +#include <linux/interrupt.h> +#include <linux/idr.h> +#include <linux/proc_fs.h> +#include <linux/uaccess.h> +#include "ug31xx_i2c.h" + +static struct i2c_client *ug31xx_client = NULL; + +void ug31xx_i2c_client_set(struct i2c_client *client) +{ + ug31xx_client = client; + dev_info(&ug31xx_client->dev, "%s: Ug31xx i2c client saved.\n", __func__); +} + +int ug31xx_read_i2c(struct i2c_client *client, + u8 reg, int *rt_value, int b_single) +{ + struct i2c_msg msg[2]; + unsigned char data[4]; + int err; + + if (!client || !client->adapter) + return -ENODEV; + + if (!rt_value) return -EINVAL; + + data[0] = reg; + //err = i2c_transfer(client->adapter, msg, 1); + + msg[0].addr = client->addr; + msg[0].flags = 0 | I2C_M_NOSTART; + msg[0].len = 1; + if (reg >= 0x80) { + data[1] = SECURITY_KEY; + msg[0].len++; + } + msg[0].buf = (unsigned char *)data; + + msg[1].addr = client->addr; + msg[1].flags = (I2C_M_RD); + msg[1].len = b_single ? 1 : 2; + msg[1].buf = (unsigned char *)data; + + err = i2c_transfer(client->adapter, msg, sizeof(msg)/sizeof(struct i2c_msg)); + + if (err < 0) return err; + + if (b_single) + *rt_value = data[0]; + else + *rt_value = get_unaligned_le16(data); + + return 0; +} + +int ug31xx_write_i2c(struct i2c_client *client, + u8 reg, int rt_value, int b_single) +{ + struct i2c_msg msg[1]; + unsigned char data[4]; + int err; + int idx; + int tmp_buf=0; + + if (!client || !client->adapter) + return -ENODEV; + + idx = 0; + data[idx++] = reg; + if (reg >= 0x80) { + data[idx++] = SECURITY_KEY; + } + data[idx++] = rt_value & 0x0FF; + data[idx++] = (rt_value & 0x0FF00)>>8; + + msg[0].addr = client->addr; + msg[0].flags = 0 | I2C_M_NOSTART; + msg[0].len = b_single ? idx-1 : idx; + msg[0].buf = (unsigned char *)data; + + err = i2c_transfer(client->adapter, msg, sizeof(msg)/sizeof(struct i2c_msg)); + + if (err >= 0) + err = ug31xx_read_i2c(client, reg, &tmp_buf, b_single); + //dev_info(&client->dev, "%s:: 0x%02X, 0x%04X, 0x%04X. %s\n", + // __func__, reg, rt_value, tmp_buf, err < 0 ? "FAIL" : "SUCCESS"); + + return err < 0 ? err : 0; +} + +bool _API_I2C_Write(u16 writeAddress, u8 writeLength, u8 *PWriteData) +{ + int i, ret; + int byte_flag=0; + + if (!PWriteData) { + dev_err(&ug31xx_client->dev, "%s: Write buffer pointer error.\n", __func__); + return false; + } + + byte_flag = ONE_BYTE; + + for (i=0; i<writeLength; i++) { + int tmp_buf = PWriteData[i]; + + ret = ug31xx_write_i2c(ug31xx_client, writeAddress+i, tmp_buf, byte_flag); + if (ret) { + dev_err(&ug31xx_client->dev, "%s: Write data(0x%02X) fail. %d\n", + __func__, i, ret); + return false; + } + } + + return true; +} + +bool _API_I2C_Read(u16 readAddress, u8 readLength, u8 *pReadDataBuffer) +{ + int i, ret; + int byte_flag=0; + + if (!pReadDataBuffer) { + dev_err(&ug31xx_client->dev, "%s: Read buffer pointer error.\n", __func__); + return false; + } + + byte_flag = ONE_BYTE; + + for (i=0; i<readLength; i++) { + int tmp_buf=0; + + ret = ug31xx_read_i2c(ug31xx_client, readAddress+i, &tmp_buf, byte_flag); + if (ret) { + dev_err(&ug31xx_client->dev, "%s: read data(0x%02X) fail. %d\n", + __func__, i, ret); + return false; + } + pReadDataBuffer[i] = tmp_buf; + } + + return true; +} + diff --git a/drivers/power/wmt_battery/gauge/ug31xx/ug31xx_i2c.h b/drivers/power/wmt_battery/gauge/ug31xx/ug31xx_i2c.h new file mode 100755 index 00000000..630fedd6 --- /dev/null +++ b/drivers/power/wmt_battery/gauge/ug31xx/ug31xx_i2c.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2012, ASUSTek, Inc. All Rights Reserved. + */ +#include <linux/i2c.h> + +#define SECURITY_KEY 0x5A //i2c read/write +#define ONE_BYTE 0x1 +#define TWO_BYTE 0x0 + +void ug31xx_i2c_client_set(struct i2c_client *client); +int ug31xx_read_i2c(struct i2c_client *client, u8 reg, int *rt_value, int b_single); +int ug31xx_write_i2c(struct i2c_client *client, u8 reg, int rt_value, int b_single); + +bool _API_I2C_Write(u16 writeAddress, u8 writeLength, u8 *PWriteData); +bool _API_I2C_Read(u16 readAddress, u8 readLength, u8 *pReadDataBuffer); + +static inline bool API_I2C_Read(bool bSecurityMode, bool bHighSpeedMode, + bool bTenBitMode ,u16 readAddress, u8 readLength, u8 *pReadDataBuffer) +{ + return _API_I2C_Read(readAddress, readLength, pReadDataBuffer); +} + +static inline bool API_I2C_Write(bool bSecurityMode, bool bHighSpeedMode, bool bTenBitMode, + u16 writeAddress, u8 writeLength, u8 *PWriteData) +{ + return _API_I2C_Write(writeAddress, writeLength, PWriteData); +} + +static inline bool API_I2C_SingleRead(bool bSecurityMode,bool bHighSpeedMode, bool bTenBitMode , + u16 readAddress, u8 *ReadData) +{ + return API_I2C_Read(bSecurityMode, bHighSpeedMode, bTenBitMode, readAddress, 1, ReadData); +} + +static inline bool API_I2C_SingleWrite(bool bSecurityMode, bool bHighSpeedMode, bool bTenBitMode , + u16 writeAddress, u8 WriteData) +{ + return API_I2C_Write(bSecurityMode, bHighSpeedMode, bTenBitMode, writeAddress, 1, &WriteData); +} +static inline bool API_I2C_Init(u32 clockRate, u16 slaveAddr) +{ + return true; +} diff --git a/drivers/power/wmt_battery/gauge/vt1603/Makefile b/drivers/power/wmt_battery/gauge/vt1603/Makefile new file mode 100755 index 00000000..c30df937 --- /dev/null +++ b/drivers/power/wmt_battery/gauge/vt1603/Makefile @@ -0,0 +1,5 @@ + +s_wmt_batt_vt1603-objs += vt1603.o batt_leds.o + +obj-m += s_wmt_batt_vt1603.o + diff --git a/drivers/power/wmt_battery/gauge/vt1603/batt_leds.c b/drivers/power/wmt_battery/gauge/vt1603/batt_leds.c new file mode 100755 index 00000000..108f40f9 --- /dev/null +++ b/drivers/power/wmt_battery/gauge/vt1603/batt_leds.c @@ -0,0 +1,189 @@ +/* + * batt_leds.c - WonderMedia Battery LED Driver. + * + * Copyright (C) 2013 WonderMedia Technologies, 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. + * + * 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 <linux/leds.h> +#include <linux/rtc.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/gpio.h> +#include <mach/wmt_iomux.h> +#include <mach/wmt_env.h> + +enum { + LED_GREEN = 0, + LED_RED, +}; + +struct batt_led_trigger { + char *name; + struct led_trigger *trigger; +}; + +#define RTC_NAME "rtc0" +static struct rtc_device *rtc_dev = NULL; +static bool battery_led_registered = false; + +static struct batt_led_trigger batt_led_triggers[] = { + [LED_GREEN] = { + .name = "bat-green", + }, + [LED_RED] = { + .name = "bat-red", + } +}; + +static struct gpio_led batt_leds[] = { + [LED_GREEN] = { + .name = "bat-green", + .default_trigger = "bat-green", + .retain_state_suspended = 1, + .active_low = 0, + }, + [LED_RED] = { + .name = "bat-red", + .default_trigger = "bat-red", + .retain_state_suspended = 1, + .active_low = 0, + }, +}; + +static struct gpio_led_platform_data batt_leds_data = { + .leds = batt_leds, + .num_leds = ARRAY_SIZE(batt_leds), +}; + +static struct platform_device batt_leds_dev = { + .name = "leds-gpio", + .id = -1, + .dev = { + .platform_data = &batt_leds_data, + }, +}; + +static int parse_battery_led_param(void) +{ + char env[] = "wmt.charger.led"; + char s[64]; + size_t l = sizeof(s); + int id, r, g; + + if (wmt_getsyspara(env, s, &l)) + return -EINVAL; + + if (sscanf(s, "%d:%d:%d", &id, &r, &g) != 3) + return -EINVAL; + + if (id != 1 || !gpio_is_valid(r) || !gpio_is_valid(g)) + return -EINVAL; + + batt_leds[LED_GREEN].gpio = g; + batt_leds[LED_RED].gpio = r; + + pr_info("charger-led: charging (gpio%d), full (gpio%d)\n", r, g); + return 0; +} + +int batt_leds_update(int status) +{ + struct led_trigger *r = batt_led_triggers[LED_RED].trigger; + struct led_trigger *g = batt_led_triggers[LED_GREEN].trigger; + + if (battery_led_registered == false) + return 0; + + switch (status) { + case POWER_SUPPLY_STATUS_CHARGING: + led_trigger_event(r, LED_FULL); + led_trigger_event(g, LED_OFF); + gpio_direction_input(batt_leds[LED_GREEN].gpio); + break; + case POWER_SUPPLY_STATUS_FULL: + led_trigger_event(r, LED_OFF); + led_trigger_event(g, LED_FULL); + break; + case POWER_SUPPLY_STATUS_DISCHARGING: + default: + led_trigger_event(r, LED_OFF); + led_trigger_event(g, LED_OFF); + gpio_direction_input(batt_leds[LED_GREEN].gpio); + break; + } + return 0; +} + +void batt_leds_suspend_prepare(void) +{ + struct rtc_wkalrm tmp; + unsigned long time, now; + //unsigned long add = 30; /* seconds */ + unsigned long add = 120; /* seconds */ + + if (battery_led_registered == false) + return; + + if (!rtc_dev) { + rtc_dev = rtc_class_open(RTC_NAME); + if (IS_ERR_OR_NULL(rtc_dev)) { + rtc_dev = NULL; + pr_err("Cannot get RTC %s, %ld.\n", RTC_NAME, PTR_ERR(rtc_dev)); + return; + } + } + + tmp.enabled = 1; + rtc_read_time(rtc_dev, &tmp.time); + rtc_tm_to_time(&tmp.time, &now); + time = now + add; + + rtc_time_to_tm(time, &tmp.time); + rtc_set_alarm(rtc_dev, &tmp); + +} + +void batt_leds_resume_complete(void) +{ + if (rtc_dev) { + rtc_class_close(rtc_dev); + rtc_dev = NULL; + } +} + +int batt_leds_setup(void) +{ + int i; + + if (parse_battery_led_param()) + return -EINVAL; + + platform_device_register(&batt_leds_dev); + + for (i = 0; i < ARRAY_SIZE(batt_led_triggers); ++i) + led_trigger_register_simple(batt_led_triggers[i].name, + &batt_led_triggers[i].trigger); + battery_led_registered = true; + return 0; +} + +void batt_leds_cleanup(void) +{ + int i; + + if (battery_led_registered == true) { + platform_device_unregister(&batt_leds_dev); + for (i = 0; i < ARRAY_SIZE(batt_led_triggers); ++i) + led_trigger_unregister_simple(batt_led_triggers[i].trigger); + battery_led_registered = false; + } +} diff --git a/drivers/power/wmt_battery/gauge/vt1603/batt_leds.h b/drivers/power/wmt_battery/gauge/vt1603/batt_leds.h new file mode 100755 index 00000000..dbcaa657 --- /dev/null +++ b/drivers/power/wmt_battery/gauge/vt1603/batt_leds.h @@ -0,0 +1,12 @@ +#ifndef _BATT_LEDS_H_ +#define _BATT_LEDS_H_ + +extern int batt_leds_setup(void); +extern void batt_leds_cleanup(void); +extern void batt_leds_suspend_prepare(void); +extern void batt_leds_resume_complete(void); +extern int batt_leds_update(int status); + +#endif /* #ifndef _BATT_LEDS_H_ */ + + diff --git a/drivers/power/wmt_battery/gauge/vt1603/vt1603.c b/drivers/power/wmt_battery/gauge/vt1603/vt1603.c new file mode 100755 index 00000000..e75a99e8 --- /dev/null +++ b/drivers/power/wmt_battery/gauge/vt1603/vt1603.c @@ -0,0 +1,513 @@ +/* + * vt1603.c - WonderMedia VT1603 Adc Battery Driver. + * + * Copyright (C) 2013 WonderMedia Technologies, 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. + * + * 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 <linux/init.h> +#include <linux/module.h> +#include <linux/proc_fs.h> +#include <linux/power_supply.h> +#include <linux/platform_device.h> +#include <linux/power/wmt_battery.h> +#include <mach/wmt_env.h> + +#include "vt1603.h" +#include "batt_leds.h" + +enum { + COMPENSATION_VOLUME = 0, + COMPENSATION_BRIGHTNESS, + COMPENSATION_WIFI, + COMPENSATION_VIDEO, + COMPENSATION_USB, + COMPENSATION_HDMI, + COMPENSATION_COUNT +}; + +static const char *compensation_strings[] = { + "volume", + "brightness", + "wifi", + "video", + "usb", + "hdmi" +}; + +struct vt1603_device_info { + struct vt1603 *vt1603; + struct device *dev; + struct power_supply ps_bat; + struct mutex mutex; + int compensation[COMPENSATION_COUNT]; + + int capacity; + int sleeping; + int debug; +}; + +static struct vt1603_device_info *vt1603_dev_info = NULL; + +static int vt1603_set_reg8(struct vt1603 *vt1603, u8 reg, u8 val) +{ + int ret = vt1603->reg_write(vt1603, reg, val); + if (ret) + pr_err("vt1603 battery write error, errno%d\n", ret); + return ret; +} + +static u8 vt1603_get_reg8(struct vt1603 *vt1603, u8 reg) +{ + u8 val = 0; + int ret = 0; + + ret = vt1603->reg_read(vt1603, reg, &val); + if (ret < 0){ + pr_err("vt1603 battery read error, errno%d\n", ret); + return 0; + } + + return val; +} + +static int vt1603_read8(struct vt1603 *vt1603, u8 reg, u8 *data) +{ + int ret = vt1603->reg_read(vt1603, reg, data); + if (ret) + pr_err("vt1603 battery read error, errno%d\n", ret); + return ret; +} + +static void vt1603_setbits(struct vt1603 *vt1603, u8 reg, u8 mask) +{ + u8 tmp = vt1603_get_reg8(vt1603, reg) | mask; + vt1603_set_reg8(vt1603, reg, tmp); +} + +static void vt1603_clrbits(struct vt1603 *vt1603, u8 reg, u8 mask) +{ + u8 tmp = vt1603_get_reg8(vt1603, reg) & (~mask); + vt1603_set_reg8(vt1603, reg, tmp); +} + +#define ADC_DATA(low, high) ((((high) & 0x0F) << 8) + (low)) + +static int vt1603_get_bat_data(struct vt1603 *vt1603,int *data) +{ + int ret = 0; + u8 data_l, data_h; + + ret |= vt1603_read8(vt1603, VT1603_DATL_REG, &data_l); + ret |= vt1603_read8(vt1603, VT1603_DATH_REG, &data_h); + + *data = ADC_DATA(data_l, data_h); + return ret; +} + +static void vt1603_switch_to_bat_mode(struct vt1603 *vt1603) +{ + vt1603_set_reg8(vt1603, VT1603_CR_REG, 0x00); + vt1603_set_reg8(vt1603, 0xc6, 0x00); + vt1603_set_reg8(vt1603, VT1603_AMCR_REG, BIT0); +} + +static inline void vt1603_bat_pen_manual(struct vt1603 *vt1603) +{ + vt1603_setbits(vt1603, VT1603_INTCR_REG, BIT7); +} + +static void vt1603_bat_power_up(struct vt1603 *vt1603) +{ + if (vt1603_get_reg8(vt1603, VT1603_PWC_REG) != 0x08) + vt1603_set_reg8(vt1603, VT1603_PWC_REG, 0x08); +} + +static int vt1603_read_volt(struct vt1603 *vt1603) +{ + int timeout = 2000; + int ret = 0; + int value; + + // wait for interrupt that adc converted completed. + do { + if (vt1603_get_reg8(vt1603, VT1603_INTS_REG) & BIT0) + break; + } while (timeout--); + + if (!timeout) { + pr_err("wait adc end timeout ?!\n"); + return -ETIMEDOUT; + } + + ret = vt1603_get_bat_data(vt1603, &value); + if (ret < 0) { + pr_err("vt1603 get bat adc data Failed!\n"); + return ret; + } + + return value; +} + +static int vt1603_manual_read_adc(struct vt1603 *vt1603) +{ + int i; + int ret = 0; + uint32_t sum = 0; + + /* enable sar-adc power and clock */ + vt1603_bat_power_up(vt1603); + /* enable pen down/up to avoid miss irq */ + vt1603_bat_pen_manual(vt1603); + /* switch vt1603 to battery detect mode */ + vt1603_switch_to_bat_mode(vt1603); + /* do conversion use battery manual mode */ + vt1603_setbits(vt1603, VT1603_INTS_REG, BIT0); + vt1603_set_reg8(vt1603, VT1603_CR_REG, BIT4); + + for (i = 0; i < 4; i++) { + ret = vt1603_read_volt(vt1603); + if (ret < 0) + break; + sum += ret; + + vt1603_setbits(vt1603, VT1603_INTS_REG, BIT0); + vt1603_set_reg8(vt1603, VT1603_CR_REG, BIT4); //start manual ADC mode + } + vt1603_clrbits(vt1603, VT1603_INTCR_REG, BIT7); + vt1603_setbits(vt1603, VT1603_INTS_REG, BIT0 | BIT3); + vt1603_set_reg8(vt1603, VT1603_CR_REG, BIT1); + + return (ret < 0) ? ret : (sum >> 2); +} + +static inline int volt_reg_to_mV(int value) +{ + return ((value * 1047) / 1000); +} + +static int vt1603_bat_read_voltage(struct vt1603_device_info *di, int *intval) +{ + int ret = vt1603_manual_read_adc(di->vt1603); + if (ret < 0) + return ret; + + *intval = volt_reg_to_mV(ret); + return 0; +} + +static int vt1603_bat_read_status(struct vt1603_device_info *di, int *intval) +{ + int status; + + status = charger_get_status(); + if (status < 0) + return status; + + if (status == POWER_SUPPLY_STATUS_CHARGING && di->capacity == 100) + status = POWER_SUPPLY_STATUS_FULL; + + batt_leds_update(status); + *intval = status; + return 0; +} + +static int vt1603_proc_read(char *buf, char **start, off_t offset, int len, + int *eof, void *data) +{ + int l = 0, i; + int ret, status, dcin, voltage, full; + struct vt1603_device_info *di = vt1603_dev_info; + + mutex_lock(&di->mutex); + + ret = vt1603_bat_read_status(di, &status); + if (ret) { + pr_err("vt1603_bat_read_status failed\n"); + goto out; + } + voltage = vt1603_manual_read_adc(di->vt1603); + if (voltage < 0) { + pr_err("vt1603_manual_read_adc failed\n"); + goto out; + } + + dcin = power_supply_is_system_supplied(); + full = charger_is_full(); + + l += sprintf(buf + l, "status : %d\n", status); + l += sprintf(buf + l, "dcin : %d\n", dcin); + l += sprintf(buf + l, "voltage : %d\n", voltage); + l += sprintf(buf + l, "full : %d\n", full); + l += sprintf(buf + l, "sleeping : %d\n", di->sleeping); + l += sprintf(buf + l, "debug : %d\n", di->debug); + + for (i = 0; i < COMPENSATION_COUNT; i++) { + l += sprintf(buf +l, "compensation %10s : %d\n", + compensation_strings[i], di->compensation[i]); + } + + /* clear after read */ + di->sleeping = 0; +out: + mutex_unlock(&di->mutex); + return l; +} + +static int vt1603_proc_write(struct file *file, const char *buffer, + unsigned long count, void *data) +{ + int bm, usage; + struct vt1603_device_info *di = vt1603_dev_info; + int capacity; + + if (sscanf(buffer, "capacity=%d", &capacity)) { + di->capacity = capacity; + power_supply_changed(&di->ps_bat); + return count; + } + + if (sscanf(buffer, "debug=%d", &di->debug)) + return count; + + if (sscanf(buffer, "MODULE_CHANGE:%d-%d", &bm, &usage) < 2) + return 0; + + if (bm < 0 || bm >= COMPENSATION_COUNT) { + pr_err("bm %d error, [0, %d)\n", bm, COMPENSATION_COUNT); + return 0; + } + + if (usage > 100 || usage < 0) { + pr_err("usage %d error\n", usage); + return 0; + } + + mutex_lock(&di->mutex); + di->compensation[bm] = usage; + mutex_unlock(&di->mutex); + return count; +} + +#define VT1603_PROC_NAME "battery_calibration" + +static void vt1603_proc_init(void) +{ + struct proc_dir_entry *entry; + + entry = create_proc_entry(VT1603_PROC_NAME, 0666, NULL); + if (entry) { + entry->read_proc = vt1603_proc_read; + entry->write_proc = vt1603_proc_write; + } +} + +static void vt1603_proc_cleanup(void) +{ + remove_proc_entry(VT1603_PROC_NAME, NULL); +} + +#define to_vt1603_device_info(x) container_of((x), \ + struct vt1603_device_info, ps_bat); + +static int vt1603_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret = 0; + struct vt1603_device_info *di = to_vt1603_device_info(psy); + + mutex_lock(&di->mutex); + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + ret = vt1603_bat_read_status(di, &val->intval); + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = di->capacity; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = vt1603_bat_read_voltage(di, &val->intval); + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 1; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = 250; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = 1; + break; + default: + ret = -EINVAL; + break; + } + mutex_unlock(&di->mutex); + return ret; +} + +static void vt1603_external_power_changed(struct power_supply *psy) +{ + power_supply_changed(psy); +} + +static enum power_supply_property vt1603_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_TECHNOLOGY, +}; + +static int __devinit vt1603_batt_probe(struct platform_device *pdev) +{ + struct vt1603_device_info *di; + int ret; + + di = kzalloc(sizeof(*di), GFP_KERNEL); + if (!di) { + dev_err(&pdev->dev, "no memery\n"); + return -ENOMEM; + } + + di->dev = &pdev->dev; + di->vt1603 = dev_get_platdata(&pdev->dev); + di->capacity = 50; + + mutex_init(&di->mutex); + + di->ps_bat.name = "battery"; + di->ps_bat.type = POWER_SUPPLY_TYPE_BATTERY; + di->ps_bat.properties = vt1603_battery_props; + di->ps_bat.num_properties = ARRAY_SIZE(vt1603_battery_props); + di->ps_bat.get_property = vt1603_battery_get_property; + di->ps_bat.external_power_changed = vt1603_external_power_changed; + + ret = power_supply_register(di->dev, &di->ps_bat); + if (ret) { + dev_err(di->dev, "failed to register battery: %d\n", ret); + kfree(di); + return ret; + } + + platform_set_drvdata(pdev, di); + vt1603_dev_info = di; + + vt1603_proc_init(); + + pr_info("VT1603 Battery Driver Installed!\n"); + return 0; +} + +static int __devexit vt1603_batt_remove(struct platform_device *pdev) +{ + struct vt1603_device_info *di = platform_get_drvdata(pdev); + vt1603_proc_cleanup(); + power_supply_unregister(&di->ps_bat); + kfree(di); + vt1603_dev_info = NULL; + pr_info("VT1603 Battery Driver Removed!\n"); + return 0; +} + +static int vt1603_batt_suspend_prepare(struct device *dev) +{ + struct vt1603_device_info *di = dev_get_drvdata(dev); + int status, ret; + + ret = vt1603_bat_read_status(di, &status); + if (ret) { + pr_err("vt1603_bat_read_status failed\n"); + return 0; + } + + if (status == POWER_SUPPLY_STATUS_CHARGING) + batt_leds_suspend_prepare(); + + return 0; +} + +static int vt1603_batt_suspend(struct device *dev) +{ + return 0; +} + +static int vt1603_batt_resume(struct device *dev) +{ + struct vt1603_device_info *di = dev_get_drvdata(dev); + di->sleeping = 1; + return 0; +} + +static void vt1603_batt_resume_complete(struct device *dev) +{ + return batt_leds_resume_complete(); +} + +static const struct dev_pm_ops vt1603_batt_manager_pm = { + .prepare = vt1603_batt_suspend_prepare, + .suspend = vt1603_batt_suspend, + .resume = vt1603_batt_resume, + .complete = vt1603_batt_resume_complete, +}; + +static struct platform_driver vt1603_batt_driver = { + .driver = { + .name = "vt1603-batt", + .owner = THIS_MODULE, + .pm = &vt1603_batt_manager_pm, + }, + .probe = vt1603_batt_probe, + .remove = __devexit_p(vt1603_batt_remove), +}; + +static int parse_battery_param(void) +{ + char env[] = "wmt.battery.param"; + char buf[64]; + size_t l = sizeof(buf); + + if (wmt_getsyspara(env, buf, &l)) + return -EINVAL; + + return prefixcmp(buf, "vt1603") ? -ENODEV : 0; +} + +static int __init vt1603_batt_init(void) +{ + if (parse_battery_param()) + return -EINVAL; + + batt_leds_setup(); + return platform_driver_register(&vt1603_batt_driver); +} + +static void __exit vt1603_batt_exit(void) +{ + platform_driver_unregister(&vt1603_batt_driver); + batt_leds_cleanup(); +} + +module_init(vt1603_batt_init); +module_exit(vt1603_batt_exit); + +MODULE_AUTHOR("WonderMedia"); +MODULE_DESCRIPTION("WonderMedia VT1603 Adc Battery Driver"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/power/wmt_battery/gauge/vt1603/vt1603.h b/drivers/power/wmt_battery/gauge/vt1603/vt1603.h new file mode 100755 index 00000000..24471226 --- /dev/null +++ b/drivers/power/wmt_battery/gauge/vt1603/vt1603.h @@ -0,0 +1,124 @@ +/*++ + Copyright (c) 2008 WonderMedia Technologies, 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. + + 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 <http://www.gnu.org/licenses/>. + + WonderMedia Technologies, Inc. + 10F, 529, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C. +--*/ + +#ifndef __VT1603_BAT_H__ +#define __VT1603_BAT_H__ +#include <linux/mfd/vt1603/core.h> + +#define BIT0 0x00000001 +#define BIT1 0x00000002 +#define BIT2 0x00000004 +#define BIT3 0x00000008 +#define BIT4 0x00000010 +#define BIT5 0x00000020 +#define BIT6 0x00000040 +#define BIT7 0x00000080 +#define BIT8 0x00000100 + +/* VT1603 touch panel state */ +#define TS_PENDOWN_STATE 0x00 +#define TS_PENUP_STATE 0x01 + +/* vt1603 bus type config */ +#define VT1603_MAX_SPI_CLK (20*1000*1000) +#define SPI_DEFAULT_CLK (12*1000*1000) +#define IDLE_DATA_NUM 5 +#define VT1603_SPI_FIX_CS 0x00 +#define VT1603_SPI_FAKE_CS 3 //(0x7F - 1) +#define VT1603_SPI_BUS_0 0x00 +#define VT1603_SPI_BUS_1 0x01 +#define VT1603_REG_OP_R 0x00 +#define VT1603_REG_OP_W 0x01 + + +#define VT1603_I2C_FIX_ADDR 0x1A +#define VT1603_I2C_FAKE_ADDR 0xEE +#define VT1603_I2C_WCMD 0x00 +#define VT1603_I2C_RCMD 0x01 +#define VT1603_I2C_RWCMD 0x02 +#define VT1603_I2C_BUS_0 0x00 +#define VT1603_I2C_BUS_1 0x01 + + +#define BA_WAKEUP_SRC_0 BIT0 +#define BA_WAKEUP_SRC_1 BIT1 +#define BA_WAKEUP_SRC_2 BIT2 +#define BA_WAKEUP_SRC_3 BIT3 + +#define I2C_BUS 0 +#define SPI_BUS 1 +#define VT1603_FIFO_LEN 3 + +/* VT1603 Register address */ +#define VT1603_BTHD_REG 0x78 +#define VT1603_BCLK_REG 0x88 +#define VT1603_BAEN_REG 0x04 + +#define VT1603_PWC_REG 0xC0 +#define VT1603_CR_REG 0xC1 +#define VT1603_CCCR_REG 0xC2 +#define VT1603_CDPR_REG 0xC3 +#define VT1603_TSPC_REG 0xC4 +#define VT1603_AMCR_REG 0xC7 +#define VT1603_INTCR_REG 0xC8 +#define VT1603_INTEN_REG 0xC9 +#define VT1603_INTS_REG 0xCA +#define VT1603_DCR_REG 0xCB + +#define VT1603_TODCL_REG 0xCC +#define VT1603_TODCH_REG 0xCD + +#define VT1603_DATL_REG 0xCE +#define VT1603_DATH_REG 0xCF + +#define VT1603_XPL_REG 0xD0 +#define VT1603_XPH_REG 0xD1 +#define VT1603_YPL_REG 0xD2 +#define VT1603_YPH_REG 0xD3 + +#define VT1603_BATL_REG 0xD4 +#define VT1603_BATH_REG 0xD5 + +#define VT1603_TEMPL_REG 0xD6 +#define VT1603_TEMPH_REG 0xD7 + +#define VT1603_ERR8_REG 0xD8 +#define VT1603_ERR7_REG 0xD9 +#define VT1603_ERR6_REG 0xDA +#define VT1603_ERR5_REG 0xDB +#define VT1603_ERR4_REG 0xDC +#define VT1603_ERR3_REG 0xDD +#define VT1603_ERR2_REG 0xDE +#define VT1603_ERR1_REG 0xDF + +#define VT1603_DBG8_REG 0xE0 +#define VT1603_DBG7_REG 0xE1 +#define VT1603_DBG6_REG 0xE2 +#define VT1603_DBG5_REG 0xE3 +#define VT1603_DBG4_REG 0xE4 +#define VT1603_DBG3_REG 0xE5 +#define VT1603_DBG2_REG 0xE6 +#define VT1603_DBG1_REG 0xE7 + +/* for VT1603 GPIO1 interrupt setting */ +#define VT1603_IMASK_REG27 27 +#define VT1603_IMASK_REG28 28 +#define VT1603_IMASK_REG29 29 +#define VT1603_IPOL_REG33 33 +#define VT1603_ISEL_REG36 36 + +#endif /* __VT1603_TS_H__ */ diff --git a/drivers/power/wmt_battery/wmt_battery.c b/drivers/power/wmt_battery/wmt_battery.c new file mode 100755 index 00000000..23692ee9 --- /dev/null +++ b/drivers/power/wmt_battery/wmt_battery.c @@ -0,0 +1,66 @@ +/* + * mp2625_charger.c - WonderMedia Charger Driver. + * + * Copyright (C) 2013 WonderMedia Technologies, 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. + * + * 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 <linux/gpio.h> +#include <mach/wmt_iomux.h> +#include <mach/wmt_env.h> +#include <linux/power/wmt_battery.h> + +extern void mp2625_pc_connected(void); +extern void g2214_pc_connected(void); + +void wmt_do_pc_connected(void) +{ + mp2625_pc_connected(); + g2214_pc_connected(); +} + +static struct { + int led_power; + int led_gpio_level; +} charger_led; + +void led_power_enable(int enable) +{ + if (gpio_is_valid(charger_led.led_power)) { + if (enable) + gpio_direction_output(charger_led.led_power, + charger_led.led_gpio_level); + else + gpio_direction_output(charger_led.led_power, + !charger_led.led_gpio_level); + } +} + +int parse_charger_led(void) +{ + static const char uboot_env[] = "wmt.charger.led"; + char buf[64]; + size_t l = sizeof(buf); + int id; + + if (wmt_getsyspara((char *)uboot_env, buf, &l) || + (sscanf(buf, "%d:%d:%d", &id, + &charger_led.led_power, &charger_led.led_gpio_level) != 3) || + id != 0 || !gpio_is_valid(charger_led.led_power) || + gpio_request(charger_led.led_power, "led power")) { + charger_led.led_power = -1; + return -EINVAL; + } + + led_power_enable(0); + return 0; +} + diff --git a/drivers/power/z2_battery.c b/drivers/power/z2_battery.c new file mode 100644 index 00000000..8c9a607e --- /dev/null +++ b/drivers/power/z2_battery.c @@ -0,0 +1,323 @@ +/* + * Battery measurement code for Zipit Z2 + * + * Copyright (C) 2009 Peter Edwards <sweetlilmre@gmail.com> + * + * 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 <linux/module.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/power_supply.h> +#include <linux/slab.h> +#include <linux/z2_battery.h> + +#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, 0, + "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 = __devexit_p(z2_batt_remove), + .id_table = z2_batt_id, +}; +module_i2c_driver(z2_batt_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Peter Edwards <sweetlilmre@gmail.com>"); +MODULE_DESCRIPTION("Zipit Z2 battery driver"); |