From f42c2b45b139b7b21c0c2eca70c82ce8d4104815 Mon Sep 17 00:00:00 2001 From: Dirk Brandewie Date: Tue, 24 Jan 2012 09:26:04 -0800 Subject: max17042: Align register definitions with data sheet and init appnote align the register names with max17042 data sheet removing registers that are marked reserved that are not used. Add register definitions defined in the maxim initialization appnote Signed-off-by: Dirk Brandewie Acked-by: MyungJoo Ham Signed-off-by: Anton Vorontsov --- include/linux/power/max17042_battery.h | 37 +++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) (limited to 'include') diff --git a/include/linux/power/max17042_battery.h b/include/linux/power/max17042_battery.h index fe99211fb2b8..67eeada71073 100644 --- a/include/linux/power/max17042_battery.h +++ b/include/linux/power/max17042_battery.h @@ -40,11 +40,11 @@ enum max17042_register { MAX17042_VCELL = 0x09, MAX17042_Current = 0x0A, MAX17042_AvgCurrent = 0x0B, - MAX17042_Qresidual = 0x0C, + MAX17042_SOC = 0x0D, MAX17042_AvSOC = 0x0E, MAX17042_RemCap = 0x0F, - MAX17402_FullCAP = 0x10, + MAX17042_FullCAP = 0x10, MAX17042_TTE = 0x11, MAX17042_V_empty = 0x12, @@ -62,14 +62,14 @@ enum max17042_register { MAX17042_AvCap = 0x1F, MAX17042_ManName = 0x20, MAX17042_DevName = 0x21, - MAX17042_DevChem = 0x22, + MAX17042_FullCAPNom = 0x23, MAX17042_TempNom = 0x24, - MAX17042_TempCold = 0x25, + MAX17042_TempLim = 0x25, MAX17042_TempHot = 0x26, MAX17042_AIN = 0x27, MAX17042_LearnCFG = 0x28, - MAX17042_SHFTCFG = 0x29, + MAX17042_FilterCFG = 0x29, MAX17042_RelaxCFG = 0x2A, MAX17042_MiscCFG = 0x2B, MAX17042_TGAIN = 0x2C, @@ -77,22 +77,41 @@ enum max17042_register { MAX17042_CGAIN = 0x2E, MAX17042_COFF = 0x2F, - MAX17042_Q_empty = 0x33, + MAX17042_MaskSOC = 0x32, + MAX17042_SOC_empty = 0x33, MAX17042_T_empty = 0x34, + MAX17042_FullCAP0 = 0x35, + MAX17042_LAvg_empty = 0x36, + MAX17042_FCTC = 0x37, MAX17042_RCOMP0 = 0x38, MAX17042_TempCo = 0x39, - MAX17042_Rx = 0x3A, - MAX17042_T_empty0 = 0x3B, + MAX17042_EmptyTempCo = 0x3A, + MAX17042_K_empty0 = 0x3B, MAX17042_TaskPeriod = 0x3C, MAX17042_FSTAT = 0x3D, MAX17042_SHDNTIMER = 0x3F, - MAX17042_VFRemCap = 0x4A, + MAX17042_dQacc = 0x45, + MAX17042_dPacc = 0x46, + + MAX17042_VFSOC0 = 0x48, MAX17042_QH = 0x4D, MAX17042_QL = 0x4E, + + MAX17042_VFSOC0Enable = 0x60, + MAX17042_MLOCKReg1 = 0x62, + MAX17042_MLOCKReg2 = 0x63, + + MAX17042_MODELChrTbl = 0x80, + + MAX17042_OCV = 0xEE, + + MAX17042_OCVInternal = 0xFB, + + MAX17042_VFSOC = 0xFF, }; /* -- cgit v1.2.3-58-ga151 From f3a71a6eb13b71cc8a3dc5b6e5692e3db66b92f0 Mon Sep 17 00:00:00 2001 From: Ramakrishna Pallala Date: Tue, 13 Mar 2012 22:03:52 +0400 Subject: max17042: Add POR init procedure from Maxim appnote Add power on reset (POR) init procedure defined by the maxim appnote. Using this procedure ensures that the part is configured/initialized correctly at POR and improves early accuracy of the fuel gauge and informs the fuel gauge with the battery characterization parameters. The battery characterization parameters come from the maxim characterization procedure. Signed-off-by: Ramakrishna Pallala Signed-off-by: Dirk Brandewie Signed-off-by: Anton Vorontsov --- drivers/power/max17042_battery.c | 386 ++++++++++++++++++++++++++++++++- include/linux/power/max17042_battery.h | 56 +++++ 2 files changed, 432 insertions(+), 10 deletions(-) (limited to 'include') diff --git a/drivers/power/max17042_battery.c b/drivers/power/max17042_battery.c index 86acee2f9889..fde7ccd62953 100644 --- a/drivers/power/max17042_battery.c +++ b/drivers/power/max17042_battery.c @@ -26,14 +26,40 @@ #include #include #include +#include #include #include #include +/* 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) + +#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) @@ -87,6 +113,9 @@ static int max17042_get_property(struct power_supply *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); @@ -210,12 +239,343 @@ static int max17042_get_property(struct power_supply *psy, 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_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; +} + 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; @@ -243,17 +603,9 @@ static int __devinit max17042_probe(struct i2c_client *client, if (chip->pdata->r_sns == 0) chip->pdata->r_sns = MAX17042_DEFAULT_SNS_RESISTOR; - ret = power_supply_register(&client->dev, &chip->battery); - if (ret) { - dev_err(&client->dev, "failed: power supply register\n"); - kfree(chip); - return ret; - } - - /* Initialize registers according to values from the platform data */ if (chip->pdata->init_data) max17042_set_reg(client, chip->pdata->init_data, - chip->pdata->num_init_data); + chip->pdata->num_init_data); if (!chip->pdata->enable_current_sense) { max17042_write_reg(client, MAX17042_CGAIN, 0x0000); @@ -261,7 +613,21 @@ static int __devinit max17042_probe(struct i2c_client *client, max17042_write_reg(client, MAX17042_LearnCFG, 0x0007); } - return 0; + 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"); + kfree(chip); + } + return ret; } static int __devexit max17042_remove(struct i2c_client *client) diff --git a/include/linux/power/max17042_battery.h b/include/linux/power/max17042_battery.h index 67eeada71073..e01b167e66f0 100644 --- a/include/linux/power/max17042_battery.h +++ b/include/linux/power/max17042_battery.h @@ -27,6 +27,8 @@ #define MAX17042_BATTERY_FULL (100) #define MAX17042_DEFAULT_SNS_RESISTOR (10000) +#define MAX17042_CHARACTERIZATION_DATA_SIZE 48 + enum max17042_register { MAX17042_STATUS = 0x00, MAX17042_VALRT_Th = 0x01, @@ -124,10 +126,64 @@ struct max17042_reg_data { u16 data; }; +struct max17042_config_data { + /* External current sense resistor value in milli-ohms */ + u32 cur_sense_val; + + /* A/D measurement */ + u16 tgain; /* 0x2C */ + u16 toff; /* 0x2D */ + u16 cgain; /* 0x2E */ + u16 coff; /* 0x2F */ + + /* Alert / Status */ + u16 valrt_thresh; /* 0x01 */ + u16 talrt_thresh; /* 0x02 */ + u16 soc_alrt_thresh; /* 0x03 */ + u16 config; /* 0x01D */ + u16 shdntimer; /* 0x03F */ + + /* App data */ + u16 design_cap; /* 0x18 */ + u16 ichgt_term; /* 0x1E */ + + /* MG3 config */ + u16 at_rate; /* 0x04 */ + u16 learn_cfg; /* 0x28 */ + u16 filter_cfg; /* 0x29 */ + u16 relax_cfg; /* 0x2A */ + u16 misc_cfg; /* 0x2B */ + u16 masksoc; /* 0x32 */ + + /* MG3 save and restore */ + u16 fullcap; /* 0x10 */ + u16 fullcapnom; /* 0x23 */ + u16 socempty; /* 0x33 */ + u16 lavg_empty; /* 0x36 */ + u16 dqacc; /* 0x45 */ + u16 dpacc; /* 0x46 */ + + /* Cell technology from power_supply.h */ + u16 cell_technology; + + /* Cell Data */ + u16 vempty; /* 0x12 */ + u16 temp_nom; /* 0x24 */ + u16 temp_lim; /* 0x25 */ + u16 fctc; /* 0x37 */ + u16 rcomp0; /* 0x38 */ + u16 tcompc0; /* 0x39 */ + u16 empty_tempco; /* 0x3A */ + u16 kempty0; /* 0x3B */ + u16 cell_char_tbl[MAX17042_CHARACTERIZATION_DATA_SIZE]; +} __packed; + struct max17042_platform_data { struct max17042_reg_data *init_data; + struct max17042_config_data *config_data; int num_init_data; /* Number of enties in init_data array */ bool enable_current_sense; + bool enable_por_init; /* Use POR init from Maxim appnote */ /* * R_sns in micro-ohms. -- cgit v1.2.3-58-ga151 From e39b828f5355e41a8fd24f413fb9dfb81d808397 Mon Sep 17 00:00:00 2001 From: "Kim, Milo" Date: Sun, 29 Jan 2012 17:28:18 -0800 Subject: lp8727_charger: Add company name and description Add 'Texas Instruments' because TI acquired National semiconductor at 2011. And the driver information is added in the header file. Signed-off-by: Milo(Woogyom) Kim Signed-off-by: Anton Vorontsov --- drivers/power/Kconfig | 2 +- drivers/power/lp8727_charger.c | 3 ++- include/linux/lp8727.h | 3 +++ 3 files changed, 6 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 3a8daf858742..97680d8fa238 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -235,7 +235,7 @@ config CHARGER_TWL4030 Say Y here to enable support for TWL4030 Battery Charge Interface. config CHARGER_LP8727 - tristate "National Semiconductor LP8727 charger driver" + tristate "TI/National Semiconductor LP8727 charger driver" depends on I2C help Say Y here to enable support for LP8727 Charger Driver. diff --git a/drivers/power/lp8727_charger.c b/drivers/power/lp8727_charger.c index c53dd1292f81..d23a3bcc74d9 100644 --- a/drivers/power/lp8727_charger.c +++ b/drivers/power/lp8727_charger.c @@ -1,6 +1,7 @@ /* * Driver for LP8727 Micro/Mini USB IC with intergrated charger * + * Copyright (C) 2011 Texas Instruments * Copyright (C) 2011 National Semiconductor * * This program is free software; you can redistribute it and/or modify @@ -489,7 +490,7 @@ static void __exit lp8727_exit(void) module_init(lp8727_init); module_exit(lp8727_exit); -MODULE_DESCRIPTION("National Semiconductor LP8727 charger driver"); +MODULE_DESCRIPTION("TI/National Semiconductor LP8727 charger driver"); MODULE_AUTHOR ("Woogyom Kim , Daniel Jeong "); MODULE_LICENSE("GPL"); diff --git a/include/linux/lp8727.h b/include/linux/lp8727.h index d21fa2865bf4..95371c46dc3a 100644 --- a/include/linux/lp8727.h +++ b/include/linux/lp8727.h @@ -1,4 +1,7 @@ /* + * LP8727 Micro/Mini USB IC with intergrated charger + * + * Copyright (C) 2011 Texas Instruments * Copyright (C) 2011 National Semiconductor * * This program is free software; you can redistribute it and/or modify -- cgit v1.2.3-58-ga151 From 9b11adc773b45845cc384bf01a623bba628f9f99 Mon Sep 17 00:00:00 2001 From: "Kim, Milo" Date: Thu, 26 Jan 2012 22:59:00 -0800 Subject: lp8727_charger: Add description of platform data Add brief description of lp8727_platform_data and lp8727_chg_param. Signed-off-by: Milo(Woogyom) Kim Signed-off-by: Anton Vorontsov --- include/linux/lp8727.h | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/linux/lp8727.h b/include/linux/lp8727.h index 95371c46dc3a..a508b4555e60 100644 --- a/include/linux/lp8727.h +++ b/include/linux/lp8727.h @@ -35,13 +35,24 @@ enum lp8727_ichg { ICHG_1000mA, }; +/** + * struct lp8727_chg_param + * @eoc_level : end of charge level setting + * @ichg : charging current + */ struct lp8727_chg_param { - /* end of charge level setting */ enum lp8727_eoc_level eoc_level; - /* charging current */ enum lp8727_ichg ichg; }; +/** + * struct lp8727_platform_data + * @get_batt_present : check battery status - exists or not + * @get_batt_level : get battery voltage (mV) + * @get_batt_capacity : get battery capacity (%) + * @get_batt_temp : get battery temperature + * @ac, @usb : charging parameters each charger type + */ struct lp8727_platform_data { u8 (*get_batt_present)(void); u16 (*get_batt_level)(void); -- cgit v1.2.3-58-ga151 From f7bae49aa1247a195a3fe4235edf6811c175bb7b Mon Sep 17 00:00:00 2001 From: "Kim, Milo" Date: Thu, 26 Jan 2012 22:59:08 -0800 Subject: lp8727_charger: Correct typos on the comment intergrated charger -> integrated charger ^ Signed-off-by: Milo(Woogyom) Kim Signed-off-by: Anton Vorontsov --- drivers/power/lp8727_charger.c | 2 +- include/linux/lp8727.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/drivers/power/lp8727_charger.c b/drivers/power/lp8727_charger.c index d5aab54eda11..815dba37e3eb 100644 --- a/drivers/power/lp8727_charger.c +++ b/drivers/power/lp8727_charger.c @@ -1,5 +1,5 @@ /* - * Driver for LP8727 Micro/Mini USB IC with intergrated charger + * Driver for LP8727 Micro/Mini USB IC with integrated charger * * Copyright (C) 2011 Texas Instruments * Copyright (C) 2011 National Semiconductor diff --git a/include/linux/lp8727.h b/include/linux/lp8727.h index a508b4555e60..ea98c6133d32 100644 --- a/include/linux/lp8727.h +++ b/include/linux/lp8727.h @@ -1,5 +1,5 @@ /* - * LP8727 Micro/Mini USB IC with intergrated charger + * LP8727 Micro/Mini USB IC with integrated charger * * Copyright (C) 2011 Texas Instruments * Copyright (C) 2011 National Semiconductor -- cgit v1.2.3-58-ga151 From ed1a230f96eb4610f1f4296b8c3c067389ddf540 Mon Sep 17 00:00:00 2001 From: "Bruce E. Robertson" Date: Mon, 6 Feb 2012 15:59:01 +0000 Subject: Add I2C driver for Summit Microelectronics SMB347 Battery Charger. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Driver support for the Summit I²C battery charger. This is used in some Intel devices. Signed-off-by: Bruce E. Robertson Signed-off-by: Alan Cox Signed-off-by: Anton Vorontsov --- drivers/power/Kconfig | 7 + drivers/power/Makefile | 1 + drivers/power/smb347-charger.c | 1294 ++++++++++++++++++++++++++++++++++ include/linux/power/smb347-charger.h | 117 +++ 4 files changed, 1419 insertions(+) create mode 100644 drivers/power/smb347-charger.c create mode 100644 include/linux/power/smb347-charger.h (limited to 'include') diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 97680d8fa238..c728d26dca6c 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -274,4 +274,11 @@ config CHARGER_MAX8998 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. + endif # POWER_SUPPLY diff --git a/drivers/power/Makefile b/drivers/power/Makefile index e429008eaf10..894710e336f0 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -41,3 +41,4 @@ 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 diff --git a/drivers/power/smb347-charger.c b/drivers/power/smb347-charger.c new file mode 100644 index 000000000000..ce1694d1a365 --- /dev/null +++ b/drivers/power/smb347-charger.c @@ -0,0 +1,1294 @@ +/* + * Summit Microelectronics SMB347 Battery Charger Driver + * + * Copyright (C) 2011, Intel Corporation + * + * Authors: Bruce E. Robertson + * Mika Westerberg + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * 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_FLOAT_VOLTAGE 0x03 +#define CFG_FLOAT_VOLTAGE_THRESHOLD_MASK 0xc0 +#define CFG_FLOAT_VOLTAGE_THRESHOLD_SHIFT 6 +#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_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_ALLOW_WRITE BIT(7) +#define CMD_B 0x31 +#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_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; + 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 (smb->pdata->enable_control != SMB347_CHG_ENABLE_SW) { + dev_dbg(&smb->client->dev, + "charging enable/disable in SW disabled\n"); + 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->pdata->mains_current_limit) { + val = current_to_hw(icl_tbl, ARRAY_SIZE(icl_tbl), + smb->pdata->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 |= 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_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; + + ret = smb347_set_temp_limits(smb); + if (ret < 0) + goto fail; + + /* 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; + + 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; + + 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; + } + + /* 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); + +fail: + smb347_set_writable(smb, false); + return ret; +} + +static irqreturn_t smb347_interrupt(int irq, void *data) +{ + struct smb347_charger *smb = data; + int stat_c, irqstat_e, irqstat_c; + irqreturn_t ret = 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; + } + + irqstat_c = smb347_read(smb, IRQSTAT_C); + if (irqstat_c < 0) { + dev_warn(&smb->client->dev, "reading IRQSTAT_C failed\n"); + return IRQ_NONE; + } + + irqstat_e = smb347_read(smb, IRQSTAT_E); + if (irqstat_e < 0) { + dev_warn(&smb->client->dev, "reading IRQSTAT_E failed\n"); + return IRQ_NONE; + } + + /* + * 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; + } + + /* + * If we reached the termination current the battery is charged and + * we can update the status now. Charging is automatically + * disabled by the hardware. + */ + if (irqstat_c & (IRQSTAT_C_TERMINATION_IRQ | IRQSTAT_C_TAPER_IRQ)) { + if (irqstat_c & IRQSTAT_C_TERMINATION_STAT) + power_supply_changed(&smb->battery); + ret = IRQ_HANDLED; + } + + /* + * If we got an under voltage interrupt it means that AC/USB input + * was connected or disconnected. + */ + if (irqstat_e & (IRQSTAT_E_USBIN_UV_IRQ | IRQSTAT_E_DCIN_UV_IRQ)) { + 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_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 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, + IRQF_TRIGGER_FALLING, smb->client->name, + smb); + if (ret < 0) + goto fail_gpio; + + ret = smb347_set_writable(smb, true); + if (ret < 0) + goto fail_irq; + + /* + * 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_readonly; + + ret &= ~CFG_STAT_ACTIVE_HIGH; + ret |= CFG_STAT_DISABLED; + + ret = smb347_write(smb, CFG_STAT, ret); + if (ret < 0) + goto fail_readonly; + + ret = smb347_irq_enable(smb); + if (ret < 0) + goto fail_readonly; + + smb347_set_writable(smb, false); + smb->client->irq = irq; + return 0; + +fail_readonly: + smb347_set_writable(smb, false); +fail_irq: + free_irq(irq, smb); +fail_gpio: + gpio_free(pdata->irq_gpio); +fail: + smb->client->irq = 0; + 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); + + if (prop == POWER_SUPPLY_PROP_ONLINE) { + val->intval = smb->mains_online; + return 0; + } + return -EINVAL; +} + +static enum power_supply_property smb347_mains_properties[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +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); + + if (prop == POWER_SUPPLY_PROP_ONLINE) { + val->intval = smb->usb_online; + return 0; + } + return -EINVAL; +} + +static enum power_supply_property smb347_usb_properties[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +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; + + switch (prop) { + case POWER_SUPPLY_PROP_STATUS: + if (!smb347_is_online(smb)) { + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + break; + } + if (smb347_charging_status(smb)) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else + val->intval = POWER_SUPPLY_STATUS_FULL; + 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_MODEL_NAME: + val->strval = pdata->battery_info.name; + break; + + default: + return -EINVAL; + } + + 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_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; + + 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.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.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.properties = smb347_battery_properties; + smb->battery.num_properties = ARRAY_SIZE(smb347_battery_properties); + + 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); + 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 const struct i2c_device_id smb347_id[] = { + { "smb347", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, smb347_id); + +static struct i2c_driver smb347_driver = { + .driver = { + .name = "smb347", + }, + .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 "); +MODULE_AUTHOR("Mika Westerberg "); +MODULE_DESCRIPTION("SMB347 battery charger driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("i2c:smb347"); diff --git a/include/linux/power/smb347-charger.h b/include/linux/power/smb347-charger.h new file mode 100644 index 000000000000..b3cb20dab55f --- /dev/null +++ b/include/linux/power/smb347-charger.h @@ -0,0 +1,117 @@ +/* + * Summit Microelectronics SMB347 Battery Charger Driver + * + * Copyright (C) 2011, Intel Corporation + * + * Authors: Bruce E. Robertson + * Mika Westerberg + * + * 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. + */ + +#ifndef SMB347_CHARGER_H +#define SMB347_CHARGER_H + +#include +#include + +enum { + /* use the default compensation method */ + SMB347_SOFT_TEMP_COMPENSATE_DEFAULT = -1, + + SMB347_SOFT_TEMP_COMPENSATE_NONE, + SMB347_SOFT_TEMP_COMPENSATE_CURRENT, + SMB347_SOFT_TEMP_COMPENSATE_VOLTAGE, +}; + +/* Use default factory programmed value for hard/soft temperature limit */ +#define SMB347_TEMP_USE_DEFAULT -273 + +/* + * Charging enable can be controlled by software (via i2c) by + * smb347-charger driver or by EN pin (active low/high). + */ +enum smb347_chg_enable { + SMB347_CHG_ENABLE_SW, + SMB347_CHG_ENABLE_PIN_ACTIVE_LOW, + SMB347_CHG_ENABLE_PIN_ACTIVE_HIGH, +}; + +/** + * struct smb347_charger_platform_data - platform data for SMB347 charger + * @battery_info: Information about the battery + * @max_charge_current: maximum current (in uA) the battery can be charged + * @max_charge_voltage: maximum voltage (in uV) the battery can be charged + * @pre_charge_current: current (in uA) to use in pre-charging phase + * @termination_current: current (in uA) used to determine when the + * charging cycle terminates + * @pre_to_fast_voltage: voltage (in uV) treshold used for transitioning to + * pre-charge to fast charge mode + * @mains_current_limit: maximum input current drawn from AC/DC input (in uA) + * @usb_hc_current_limit: maximum input high current (in uA) drawn from USB + * input + * @chip_temp_threshold: die temperature where device starts limiting charge + * current [%100 - %130] (in degree C) + * @soft_cold_temp_limit: soft cold temperature limit [%0 - %15] (in degree C), + * granularity is 5 deg C. + * @soft_hot_temp_limit: soft hot temperature limit [%40 - %55] (in degree C), + * granularity is 5 deg C. + * @hard_cold_temp_limit: hard cold temperature limit [%-5 - %10] (in degree C), + * granularity is 5 deg C. + * @hard_hot_temp_limit: hard hot temperature limit [%50 - %65] (in degree C), + * granularity is 5 deg C. + * @suspend_on_hard_temp_limit: suspend charging when hard limit is hit + * @soft_temp_limit_compensation: compensation method when soft temperature + * limit is hit + * @charge_current_compensation: current (in uA) for charging compensation + * current when temperature hits soft limits + * @use_mains: AC/DC input can be used + * @use_usb: USB input can be used + * @use_usb_otg: USB OTG output can be used (not implemented yet) + * @irq_gpio: GPIO number used for interrupts (%-1 if not used) + * @enable_control: how charging enable/disable is controlled + * (driver/pin controls) + * + * @use_main, @use_usb, and @use_usb_otg are means to enable/disable + * hardware support for these. This is useful when we want to have for + * example OTG charging controlled via OTG transceiver driver and not by + * the SMB347 hardware. + * + * Hard and soft temperature limit values are given as described in the + * device data sheet and assuming NTC beta value is %3750. Even if this is + * not the case, these values should be used. They can be mapped to the + * corresponding NTC beta values with the help of table %2 in the data + * sheet. So for example if NTC beta is %3375 and we want to program hard + * hot limit to be %53 deg C, @hard_hot_temp_limit should be set to %50. + * + * If zero value is given in any of the current and voltage values, the + * factory programmed default will be used. For soft/hard temperature + * values, pass in %SMB347_TEMP_USE_DEFAULT instead. + */ +struct smb347_charger_platform_data { + struct power_supply_info battery_info; + unsigned int max_charge_current; + unsigned int max_charge_voltage; + unsigned int pre_charge_current; + unsigned int termination_current; + unsigned int pre_to_fast_voltage; + unsigned int mains_current_limit; + unsigned int usb_hc_current_limit; + unsigned int chip_temp_threshold; + int soft_cold_temp_limit; + int soft_hot_temp_limit; + int hard_cold_temp_limit; + int hard_hot_temp_limit; + bool suspend_on_hard_temp_limit; + unsigned int soft_temp_limit_compensation; + unsigned int charge_current_compensation; + bool use_mains; + bool use_usb; + bool use_usb_otg; + int irq_gpio; + enum smb347_chg_enable enable_control; +}; + +#endif /* SMB347_CHARGER_H */ -- cgit v1.2.3-58-ga151 From 1668f81159fb72eda2114a9c73a64ffee045cb01 Mon Sep 17 00:00:00 2001 From: Arun Murthy Date: Wed, 29 Feb 2012 21:54:25 +0530 Subject: abx500-chargalg: Add abx500 charging algorithm This is a charging algorithm driver for abx500 variants. It is the central entity for battery driver and is responsible for charging and monitoring the battery driver. It is a hardware independant driver and also monitors other abx500 power supply devices. Signed-off-by: Arun Murthy Acked-by: Linus Walleij Signed-off-by: Anton Vorontsov --- drivers/power/abx500_chargalg.c | 1921 +++++++++++++++++++++++++++++ include/linux/mfd/abx500.h | 273 ++++ include/linux/mfd/abx500/ux500_chargalg.h | 38 + 3 files changed, 2232 insertions(+) create mode 100644 drivers/power/abx500_chargalg.c create mode 100644 include/linux/mfd/abx500/ux500_chargalg.h (limited to 'include') diff --git a/drivers/power/abx500_chargalg.c b/drivers/power/abx500_chargalg.c new file mode 100644 index 000000000000..fbb6a1fe97e4 --- /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 + * Karl Komierowski + * Arun R Murthy + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 +}; + +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 <> */ + +#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/include/linux/mfd/abx500.h b/include/linux/mfd/abx500.h index 9970337ff041..8344196b0004 100644 --- a/include/linux/mfd/abx500.h +++ b/include/linux/mfd/abx500.h @@ -152,6 +152,279 @@ struct abx500_init_settings { u8 setting; }; +/* Battery driver related data */ +/* + * ADC for the battery thermistor. + * When using the ABx500_ADC_THERM_BATCTRL the battery ID resistor is combined + * with a NTC resistor to both identify the battery and to measure its + * temperature. Different phone manufactures uses different techniques to both + * identify the battery and to read its temperature. + */ +enum abx500_adc_therm { + ABx500_ADC_THERM_BATCTRL, + ABx500_ADC_THERM_BATTEMP, +}; + +/** + * struct abx500_res_to_temp - defines one point in a temp to res curve. To + * be used in battery packs that combines the identification resistor with a + * NTC resistor. + * @temp: battery pack temperature in Celcius + * @resist: NTC resistor net total resistance + */ +struct abx500_res_to_temp { + int temp; + int resist; +}; + +/** + * struct abx500_v_to_cap - Table for translating voltage to capacity + * @voltage: Voltage in mV + * @capacity: Capacity in percent + */ +struct abx500_v_to_cap { + int voltage; + int capacity; +}; + +/* Forward declaration */ +struct abx500_fg; + +/** + * struct abx500_fg_parameters - Fuel gauge algorithm parameters, in seconds + * if not specified + * @recovery_sleep_timer: Time between measurements while recovering + * @recovery_total_time: Total recovery time + * @init_timer: Measurement interval during startup + * @init_discard_time: Time we discard voltage measurement at startup + * @init_total_time: Total init time during startup + * @high_curr_time: Time current has to be high to go to recovery + * @accu_charging: FG accumulation time while charging + * @accu_high_curr: FG accumulation time in high current mode + * @high_curr_threshold: High current threshold, in mA + * @lowbat_threshold: Low battery threshold, in mV + * @overbat_threshold: Over battery threshold, in mV + * @battok_falling_th_sel0 Threshold in mV for battOk signal sel0 + * Resolution in 50 mV step. + * @battok_raising_th_sel1 Threshold in mV for battOk signal sel1 + * Resolution in 50 mV step. + * @user_cap_limit Capacity reported from user must be within this + * limit to be considered as sane, in percentage + * points. + * @maint_thres This is the threshold where we stop reporting + * battery full while in maintenance, in per cent + */ +struct abx500_fg_parameters { + int recovery_sleep_timer; + int recovery_total_time; + int init_timer; + int init_discard_time; + int init_total_time; + int high_curr_time; + int accu_charging; + int accu_high_curr; + int high_curr_threshold; + int lowbat_threshold; + int overbat_threshold; + int battok_falling_th_sel0; + int battok_raising_th_sel1; + int user_cap_limit; + int maint_thres; +}; + +/** + * struct abx500_charger_maximization - struct used by the board config. + * @use_maxi: Enable maximization for this battery type + * @maxi_chg_curr: Maximum charger current allowed + * @maxi_wait_cycles: cycles to wait before setting charger current + * @charger_curr_step delta between two charger current settings (mA) + */ +struct abx500_maxim_parameters { + bool ena_maxi; + int chg_curr; + int wait_cycles; + int charger_curr_step; +}; + +/** + * struct abx500_battery_type - different batteries supported + * @name: battery technology + * @resis_high: battery upper resistance limit + * @resis_low: battery lower resistance limit + * @charge_full_design: Maximum battery capacity in mAh + * @nominal_voltage: Nominal voltage of the battery in mV + * @termination_vol: max voltage upto which battery can be charged + * @termination_curr battery charging termination current in mA + * @recharge_vol battery voltage limit that will trigger a new + * full charging cycle in the case where maintenan- + * -ce charging has been disabled + * @normal_cur_lvl: charger current in normal state in mA + * @normal_vol_lvl: charger voltage in normal state in mV + * @maint_a_cur_lvl: charger current in maintenance A state in mA + * @maint_a_vol_lvl: charger voltage in maintenance A state in mV + * @maint_a_chg_timer_h: charge time in maintenance A state + * @maint_b_cur_lvl: charger current in maintenance B state in mA + * @maint_b_vol_lvl: charger voltage in maintenance B state in mV + * @maint_b_chg_timer_h: charge time in maintenance B state + * @low_high_cur_lvl: charger current in temp low/high state in mA + * @low_high_vol_lvl: charger voltage in temp low/high state in mV' + * @battery_resistance: battery inner resistance in mOhm. + * @n_r_t_tbl_elements: number of elements in r_to_t_tbl + * @r_to_t_tbl: table containing resistance to temp points + * @n_v_cap_tbl_elements: number of elements in v_to_cap_tbl + * @v_to_cap_tbl: Voltage to capacity (in %) table + * @n_batres_tbl_elements number of elements in the batres_tbl + * @batres_tbl battery internal resistance vs temperature table + */ +struct abx500_battery_type { + int name; + int resis_high; + int resis_low; + int charge_full_design; + int nominal_voltage; + int termination_vol; + int termination_curr; + int recharge_vol; + int normal_cur_lvl; + int normal_vol_lvl; + int maint_a_cur_lvl; + int maint_a_vol_lvl; + int maint_a_chg_timer_h; + int maint_b_cur_lvl; + int maint_b_vol_lvl; + int maint_b_chg_timer_h; + int low_high_cur_lvl; + int low_high_vol_lvl; + int battery_resistance; + int n_temp_tbl_elements; + struct abx500_res_to_temp *r_to_t_tbl; + int n_v_cap_tbl_elements; + struct abx500_v_to_cap *v_to_cap_tbl; + int n_batres_tbl_elements; + struct batres_vs_temp *batres_tbl; +}; + +/** + * struct abx500_bm_capacity_levels - abx500 capacity level data + * @critical: critical capacity level in percent + * @low: low capacity level in percent + * @normal: normal capacity level in percent + * @high: high capacity level in percent + * @full: full capacity level in percent + */ +struct abx500_bm_capacity_levels { + int critical; + int low; + int normal; + int high; + int full; +}; + +/** + * struct abx500_bm_charger_parameters - Charger specific parameters + * @usb_volt_max: maximum allowed USB charger voltage in mV + * @usb_curr_max: maximum allowed USB charger current in mA + * @ac_volt_max: maximum allowed AC charger voltage in mV + * @ac_curr_max: maximum allowed AC charger current in mA + */ +struct abx500_bm_charger_parameters { + int usb_volt_max; + int usb_curr_max; + int ac_volt_max; + int ac_curr_max; +}; + +/** + * struct abx500_bm_data - abx500 battery management data + * @temp_under under this temp, charging is stopped + * @temp_low between this temp and temp_under charging is reduced + * @temp_high between this temp and temp_over charging is reduced + * @temp_over over this temp, charging is stopped + * @temp_now present battery temperature + * @temp_interval_chg temperature measurement interval in s when charging + * @temp_interval_nochg temperature measurement interval in s when not charging + * @main_safety_tmr_h safety timer for main charger + * @usb_safety_tmr_h safety timer for usb charger + * @bkup_bat_v voltage which we charge the backup battery with + * @bkup_bat_i current which we charge the backup battery with + * @no_maintenance indicates that maintenance charging is disabled + * @abx500_adc_therm placement of thermistor, batctrl or battemp adc + * @chg_unknown_bat flag to enable charging of unknown batteries + * @enable_overshoot flag to enable VBAT overshoot control + * @auto_trig flag to enable auto adc trigger + * @fg_res resistance of FG resistor in 0.1mOhm + * @n_btypes number of elements in array bat_type + * @batt_id index of the identified battery in array bat_type + * @interval_charging charge alg cycle period time when charging (sec) + * @interval_not_charging charge alg cycle period time when not charging (sec) + * @temp_hysteresis temperature hysteresis + * @gnd_lift_resistance Battery ground to phone ground resistance (mOhm) + * @maxi: maximization parameters + * @cap_levels capacity in percent for the different capacity levels + * @bat_type table of supported battery types + * @chg_params charger parameters + * @fg_params fuel gauge parameters + */ +struct abx500_bm_data { + int temp_under; + int temp_low; + int temp_high; + int temp_over; + int temp_now; + int temp_interval_chg; + int temp_interval_nochg; + int main_safety_tmr_h; + int usb_safety_tmr_h; + int bkup_bat_v; + int bkup_bat_i; + bool no_maintenance; + bool chg_unknown_bat; + bool enable_overshoot; + bool auto_trig; + enum abx500_adc_therm adc_therm; + int fg_res; + int n_btypes; + int batt_id; + int interval_charging; + int interval_not_charging; + int temp_hysteresis; + int gnd_lift_resistance; + const struct abx500_maxim_parameters *maxi; + const struct abx500_bm_capacity_levels *cap_levels; + const struct abx500_battery_type *bat_type; + const struct abx500_bm_charger_parameters *chg_params; + const struct abx500_fg_parameters *fg_params; +}; + +struct abx500_chargalg_platform_data { + char **supplied_to; + size_t num_supplicants; +}; + +struct abx500_charger_platform_data { + char **supplied_to; + size_t num_supplicants; + bool autopower_cfg; +}; + +struct abx500_btemp_platform_data { + char **supplied_to; + size_t num_supplicants; +}; + +struct abx500_fg_platform_data { + char **supplied_to; + size_t num_supplicants; +}; + +struct abx500_bm_plat_data { + struct abx500_bm_data *battery; + struct abx500_charger_platform_data *charger; + struct abx500_btemp_platform_data *btemp; + struct abx500_fg_platform_data *fg; + struct abx500_chargalg_platform_data *chargalg; +}; + int abx500_set_register_interruptible(struct device *dev, u8 bank, u8 reg, u8 value); int abx500_get_register_interruptible(struct device *dev, u8 bank, u8 reg, diff --git a/include/linux/mfd/abx500/ux500_chargalg.h b/include/linux/mfd/abx500/ux500_chargalg.h new file mode 100644 index 000000000000..9b07725750c9 --- /dev/null +++ b/include/linux/mfd/abx500/ux500_chargalg.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) ST-Ericsson SA 2012 + * Author: Johan Gardsmark for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _UX500_CHARGALG_H +#define _UX500_CHARGALG_H + +#include + +#define psy_to_ux500_charger(x) container_of((x), \ + struct ux500_charger, psy) + +/* Forward declaration */ +struct ux500_charger; + +struct ux500_charger_ops { + int (*enable) (struct ux500_charger *, int, int, int); + int (*kick_wd) (struct ux500_charger *); + int (*update_curr) (struct ux500_charger *, int); +}; + +/** + * struct ux500_charger - power supply ux500 charger sub class + * @psy power supply base class + * @ops ux500 charger operations + * @max_out_volt maximum output charger voltage in mV + * @max_out_curr maximum output charger current in mA + */ +struct ux500_charger { + struct power_supply psy; + struct ux500_charger_ops ops; + int max_out_volt; + int max_out_curr; +}; + +#endif -- cgit v1.2.3-58-ga151 From 84edbeeab67c1575067335179513150115da367b Mon Sep 17 00:00:00 2001 From: Arun Murthy Date: Wed, 29 Feb 2012 21:54:26 +0530 Subject: ab8500-charger: AB8500 charger driver This driver is responsible for detecting the ac/usb plugin and also includes function to enable ac/usb charging and re-kick the watchdog. It registers with the power supply class and provides information to the user space. The information include status of ac/usb charger device. This information in turn will be used by the abx500 charging algorithm driver to enable/disable and monitor charging. Signed-off-by: Arun Murthy Acked-by: Linus Walleij Signed-off-by: Anton Vorontsov --- drivers/power/ab8500_charger.c | 2789 ++++++++++++++++++++++++++++++++++ include/linux/mfd/abx500/ab8500-bm.h | 554 +++++++ 2 files changed, 3343 insertions(+) create mode 100644 drivers/power/ab8500_charger.c create mode 100644 include/linux/mfd/abx500/ab8500-bm.h (limited to 'include') diff --git a/drivers/power/ab8500_charger.c b/drivers/power/ab8500_charger.c new file mode 100644 index 000000000000..bbc541a59a38 --- /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 + * Karl Komierowski + * Arun R Murthy + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 otg_transceiver *otg; + 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 + */ +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__); + + otg_unregister_notifier(di->otg, &di->nb); + otg_put_transceiver(di->otg); + + /* 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->otg = otg_get_transceiver(); + if (!di->otg) { + dev_err(di->dev, "failed to get otg transceiver\n"); + ret = -EINVAL; + goto free_usb; + } + di->nb.notifier_call = ab8500_charger_usb_notifier_call; + ret = otg_register_notifier(di->otg, &di->nb); + if (ret) { + dev_err(di->dev, "failed to register otg notifier\n"); + goto put_otg_transceiver; + } + + /* 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: + otg_unregister_notifier(di->otg, &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_otg_transceiver: + otg_put_transceiver(di->otg); +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/include/linux/mfd/abx500/ab8500-bm.h b/include/linux/mfd/abx500/ab8500-bm.h new file mode 100644 index 000000000000..4b7342c54b41 --- /dev/null +++ b/include/linux/mfd/abx500/ab8500-bm.h @@ -0,0 +1,554 @@ +/* + * Copyright ST-Ericsson 2012. + * + * Author: Arun Murthy + * Licensed under GPLv2. + */ + +#ifndef _AB8500_BM_H +#define _AB8500_BM_H + +#include + +/* + * System control 2 register offsets. + * bank = 0x02 + */ +#define AB8500_MAIN_WDOG_CTRL_REG 0x01 +#define AB8500_LOW_BAT_REG 0x03 +#define AB8500_BATT_OK_REG 0x04 +/* + * USB/ULPI register offsets + * Bank : 0x5 + */ +#define AB8500_USB_LINE_STAT_REG 0x80 + +/* + * Charger / status register offfsets + * Bank : 0x0B + */ +#define AB8500_CH_STATUS1_REG 0x00 +#define AB8500_CH_STATUS2_REG 0x01 +#define AB8500_CH_USBCH_STAT1_REG 0x02 +#define AB8500_CH_USBCH_STAT2_REG 0x03 +#define AB8500_CH_FSM_STAT_REG 0x04 +#define AB8500_CH_STAT_REG 0x05 + +/* + * Charger / control register offfsets + * Bank : 0x0B + */ +#define AB8500_CH_VOLT_LVL_REG 0x40 +#define AB8500_CH_VOLT_LVL_MAX_REG 0x41 /*Only in Cut2.0*/ +#define AB8500_CH_OPT_CRNTLVL_REG 0x42 +#define AB8500_CH_OPT_CRNTLVL_MAX_REG 0x43 /*Only in Cut2.0*/ +#define AB8500_CH_WD_TIMER_REG 0x50 +#define AB8500_CHARG_WD_CTRL 0x51 +#define AB8500_BTEMP_HIGH_TH 0x52 +#define AB8500_LED_INDICATOR_PWM_CTRL 0x53 +#define AB8500_LED_INDICATOR_PWM_DUTY 0x54 +#define AB8500_BATT_OVV 0x55 +#define AB8500_CHARGER_CTRL 0x56 +#define AB8500_BAT_CTRL_CURRENT_SOURCE 0x60 /*Only in Cut2.0*/ + +/* + * Charger / main control register offsets + * Bank : 0x0B + */ +#define AB8500_MCH_CTRL1 0x80 +#define AB8500_MCH_CTRL2 0x81 +#define AB8500_MCH_IPT_CURLVL_REG 0x82 +#define AB8500_CH_WD_REG 0x83 + +/* + * Charger / USB control register offsets + * Bank : 0x0B + */ +#define AB8500_USBCH_CTRL1_REG 0xC0 +#define AB8500_USBCH_CTRL2_REG 0xC1 +#define AB8500_USBCH_IPT_CRNTLVL_REG 0xC2 + +/* + * Gas Gauge register offsets + * Bank : 0x0C + */ +#define AB8500_GASG_CC_CTRL_REG 0x00 +#define AB8500_GASG_CC_ACCU1_REG 0x01 +#define AB8500_GASG_CC_ACCU2_REG 0x02 +#define AB8500_GASG_CC_ACCU3_REG 0x03 +#define AB8500_GASG_CC_ACCU4_REG 0x04 +#define AB8500_GASG_CC_SMPL_CNTRL_REG 0x05 +#define AB8500_GASG_CC_SMPL_CNTRH_REG 0x06 +#define AB8500_GASG_CC_SMPL_CNVL_REG 0x07 +#define AB8500_GASG_CC_SMPL_CNVH_REG 0x08 +#define AB8500_GASG_CC_CNTR_AVGOFF_REG 0x09 +#define AB8500_GASG_CC_OFFSET_REG 0x0A +#define AB8500_GASG_CC_NCOV_ACCU 0x10 +#define AB8500_GASG_CC_NCOV_ACCU_CTRL 0x11 +#define AB8500_GASG_CC_NCOV_ACCU_LOW 0x12 +#define AB8500_GASG_CC_NCOV_ACCU_MED 0x13 +#define AB8500_GASG_CC_NCOV_ACCU_HIGH 0x14 + +/* + * Interrupt register offsets + * Bank : 0x0E + */ +#define AB8500_IT_SOURCE2_REG 0x01 +#define AB8500_IT_SOURCE21_REG 0x14 + +/* + * RTC register offsets + * Bank: 0x0F + */ +#define AB8500_RTC_BACKUP_CHG_REG 0x0C +#define AB8500_RTC_CC_CONF_REG 0x01 +#define AB8500_RTC_CTRL_REG 0x0B + +/* + * OTP register offsets + * Bank : 0x15 + */ +#define AB8500_OTP_CONF_15 0x0E + +/* GPADC constants from AB8500 spec, UM0836 */ +#define ADC_RESOLUTION 1024 +#define ADC_CH_MAIN_MIN 0 +#define ADC_CH_MAIN_MAX 20030 +#define ADC_CH_VBUS_MIN 0 +#define ADC_CH_VBUS_MAX 20030 +#define ADC_CH_VBAT_MIN 2300 +#define ADC_CH_VBAT_MAX 4800 +#define ADC_CH_BKBAT_MIN 0 +#define ADC_CH_BKBAT_MAX 3200 + +/* Main charge i/p current */ +#define MAIN_CH_IP_CUR_0P9A 0x80 +#define MAIN_CH_IP_CUR_1P0A 0x90 +#define MAIN_CH_IP_CUR_1P1A 0xA0 +#define MAIN_CH_IP_CUR_1P2A 0xB0 +#define MAIN_CH_IP_CUR_1P3A 0xC0 +#define MAIN_CH_IP_CUR_1P4A 0xD0 +#define MAIN_CH_IP_CUR_1P5A 0xE0 + +/* ChVoltLevel */ +#define CH_VOL_LVL_3P5 0x00 +#define CH_VOL_LVL_4P0 0x14 +#define CH_VOL_LVL_4P05 0x16 +#define CH_VOL_LVL_4P1 0x1B +#define CH_VOL_LVL_4P15 0x20 +#define CH_VOL_LVL_4P2 0x25 +#define CH_VOL_LVL_4P6 0x4D + +/* ChOutputCurrentLevel */ +#define CH_OP_CUR_LVL_0P1 0x00 +#define CH_OP_CUR_LVL_0P2 0x01 +#define CH_OP_CUR_LVL_0P3 0x02 +#define CH_OP_CUR_LVL_0P4 0x03 +#define CH_OP_CUR_LVL_0P5 0x04 +#define CH_OP_CUR_LVL_0P6 0x05 +#define CH_OP_CUR_LVL_0P7 0x06 +#define CH_OP_CUR_LVL_0P8 0x07 +#define CH_OP_CUR_LVL_0P9 0x08 +#define CH_OP_CUR_LVL_1P4 0x0D +#define CH_OP_CUR_LVL_1P5 0x0E +#define CH_OP_CUR_LVL_1P6 0x0F + +/* BTEMP High thermal limits */ +#define BTEMP_HIGH_TH_57_0 0x00 +#define BTEMP_HIGH_TH_52 0x01 +#define BTEMP_HIGH_TH_57_1 0x02 +#define BTEMP_HIGH_TH_62 0x03 + +/* current is mA */ +#define USB_0P1A 100 +#define USB_0P2A 200 +#define USB_0P3A 300 +#define USB_0P4A 400 +#define USB_0P5A 500 + +#define LOW_BAT_3P1V 0x20 +#define LOW_BAT_2P3V 0x00 +#define LOW_BAT_RESET 0x01 +#define LOW_BAT_ENABLE 0x01 + +/* Backup battery constants */ +#define BUP_ICH_SEL_50UA 0x00 +#define BUP_ICH_SEL_150UA 0x04 +#define BUP_ICH_SEL_300UA 0x08 +#define BUP_ICH_SEL_700UA 0x0C + +#define BUP_VCH_SEL_2P5V 0x00 +#define BUP_VCH_SEL_2P6V 0x01 +#define BUP_VCH_SEL_2P8V 0x02 +#define BUP_VCH_SEL_3P1V 0x03 + +/* Battery OVV constants */ +#define BATT_OVV_ENA 0x02 +#define BATT_OVV_TH_3P7 0x00 +#define BATT_OVV_TH_4P75 0x01 + +/* A value to indicate over voltage */ +#define BATT_OVV_VALUE 4750 + +/* VBUS OVV constants */ +#define VBUS_OVV_SELECT_MASK 0x78 +#define VBUS_OVV_SELECT_5P6V 0x00 +#define VBUS_OVV_SELECT_5P7V 0x08 +#define VBUS_OVV_SELECT_5P8V 0x10 +#define VBUS_OVV_SELECT_5P9V 0x18 +#define VBUS_OVV_SELECT_6P0V 0x20 +#define VBUS_OVV_SELECT_6P1V 0x28 +#define VBUS_OVV_SELECT_6P2V 0x30 +#define VBUS_OVV_SELECT_6P3V 0x38 + +#define VBUS_AUTO_IN_CURR_LIM_ENA 0x04 + +/* Fuel Gauge constants */ +#define RESET_ACCU 0x02 +#define READ_REQ 0x01 +#define CC_DEEP_SLEEP_ENA 0x02 +#define CC_PWR_UP_ENA 0x01 +#define CC_SAMPLES_40 0x28 +#define RD_NCONV_ACCU_REQ 0x01 +#define CC_CALIB 0x08 +#define CC_INTAVGOFFSET_ENA 0x10 +#define CC_MUXOFFSET 0x80 +#define CC_INT_CAL_N_AVG_MASK 0x60 +#define CC_INT_CAL_SAMPLES_16 0x40 +#define CC_INT_CAL_SAMPLES_8 0x20 +#define CC_INT_CAL_SAMPLES_4 0x00 + +/* RTC constants */ +#define RTC_BUP_CH_ENA 0x10 + +/* BatCtrl Current Source Constants */ +#define BAT_CTRL_7U_ENA 0x01 +#define BAT_CTRL_20U_ENA 0x02 +#define BAT_CTRL_CMP_ENA 0x04 +#define FORCE_BAT_CTRL_CMP_HIGH 0x08 +#define BAT_CTRL_PULL_UP_ENA 0x10 + +/* Battery type */ +#define BATTERY_UNKNOWN 00 + +/* + * ADC for the battery thermistor. + * When using the ADC_THERM_BATCTRL the battery ID resistor is combined with + * a NTC resistor to both identify the battery and to measure its temperature. + * Different phone manufactures uses different techniques to both identify the + * battery and to read its temperature. + */ +enum adc_therm { + ADC_THERM_BATCTRL, + ADC_THERM_BATTEMP, +}; + +/** + * struct res_to_temp - defines one point in a temp to res curve. To + * be used in battery packs that combines the identification resistor with a + * NTC resistor. + * @temp: battery pack temperature in Celcius + * @resist: NTC resistor net total resistance + */ +struct res_to_temp { + int temp; + int resist; +}; + +/** + * struct batres_vs_temp - defines one point in a temp vs battery internal + * resistance curve. + * @temp: battery pack temperature in Celcius + * @resist: battery internal reistance in mOhm + */ +struct batres_vs_temp { + int temp; + int resist; +}; + +/** + * struct v_to_cap - Table for translating voltage to capacity + * @voltage: Voltage in mV + * @capacity: Capacity in percent + */ +struct v_to_cap { + int voltage; + int capacity; +}; + +/* Forward declaration */ +struct ab8500_fg; + +/** + * struct ab8500_fg_parameters - Fuel gauge algorithm parameters, in seconds + * if not specified + * @recovery_sleep_timer: Time between measurements while recovering + * @recovery_total_time: Total recovery time + * @init_timer: Measurement interval during startup + * @init_discard_time: Time we discard voltage measurement at startup + * @init_total_time: Total init time during startup + * @high_curr_time: Time current has to be high to go to recovery + * @accu_charging: FG accumulation time while charging + * @accu_high_curr: FG accumulation time in high current mode + * @high_curr_threshold: High current threshold, in mA + * @lowbat_threshold: Low battery threshold, in mV + * @battok_falling_th_sel0 Threshold in mV for battOk signal sel0 + * Resolution in 50 mV step. + * @battok_raising_th_sel1 Threshold in mV for battOk signal sel1 + * Resolution in 50 mV step. + * @user_cap_limit Capacity reported from user must be within this + * limit to be considered as sane, in percentage + * points. + * @maint_thres This is the threshold where we stop reporting + * battery full while in maintenance, in per cent + */ +struct ab8500_fg_parameters { + int recovery_sleep_timer; + int recovery_total_time; + int init_timer; + int init_discard_time; + int init_total_time; + int high_curr_time; + int accu_charging; + int accu_high_curr; + int high_curr_threshold; + int lowbat_threshold; + int battok_falling_th_sel0; + int battok_raising_th_sel1; + int user_cap_limit; + int maint_thres; +}; + +/** + * struct ab8500_charger_maximization - struct used by the board config. + * @use_maxi: Enable maximization for this battery type + * @maxi_chg_curr: Maximum charger current allowed + * @maxi_wait_cycles: cycles to wait before setting charger current + * @charger_curr_step delta between two charger current settings (mA) + */ +struct ab8500_maxim_parameters { + bool ena_maxi; + int chg_curr; + int wait_cycles; + int charger_curr_step; +}; + +/** + * struct battery_type - different batteries supported + * @name: battery technology + * @resis_high: battery upper resistance limit + * @resis_low: battery lower resistance limit + * @charge_full_design: Maximum battery capacity in mAh + * @nominal_voltage: Nominal voltage of the battery in mV + * @termination_vol: max voltage upto which battery can be charged + * @termination_curr battery charging termination current in mA + * @recharge_vol battery voltage limit that will trigger a new + * full charging cycle in the case where maintenan- + * -ce charging has been disabled + * @normal_cur_lvl: charger current in normal state in mA + * @normal_vol_lvl: charger voltage in normal state in mV + * @maint_a_cur_lvl: charger current in maintenance A state in mA + * @maint_a_vol_lvl: charger voltage in maintenance A state in mV + * @maint_a_chg_timer_h: charge time in maintenance A state + * @maint_b_cur_lvl: charger current in maintenance B state in mA + * @maint_b_vol_lvl: charger voltage in maintenance B state in mV + * @maint_b_chg_timer_h: charge time in maintenance B state + * @low_high_cur_lvl: charger current in temp low/high state in mA + * @low_high_vol_lvl: charger voltage in temp low/high state in mV' + * @battery_resistance: battery inner resistance in mOhm. + * @n_r_t_tbl_elements: number of elements in r_to_t_tbl + * @r_to_t_tbl: table containing resistance to temp points + * @n_v_cap_tbl_elements: number of elements in v_to_cap_tbl + * @v_to_cap_tbl: Voltage to capacity (in %) table + * @n_batres_tbl_elements number of elements in the batres_tbl + * @batres_tbl battery internal resistance vs temperature table + */ +struct battery_type { + int name; + int resis_high; + int resis_low; + int charge_full_design; + int nominal_voltage; + int termination_vol; + int termination_curr; + int recharge_vol; + int normal_cur_lvl; + int normal_vol_lvl; + int maint_a_cur_lvl; + int maint_a_vol_lvl; + int maint_a_chg_timer_h; + int maint_b_cur_lvl; + int maint_b_vol_lvl; + int maint_b_chg_timer_h; + int low_high_cur_lvl; + int low_high_vol_lvl; + int battery_resistance; + int n_temp_tbl_elements; + struct res_to_temp *r_to_t_tbl; + int n_v_cap_tbl_elements; + struct v_to_cap *v_to_cap_tbl; + int n_batres_tbl_elements; + struct batres_vs_temp *batres_tbl; +}; + +/** + * struct ab8500_bm_capacity_levels - ab8500 capacity level data + * @critical: critical capacity level in percent + * @low: low capacity level in percent + * @normal: normal capacity level in percent + * @high: high capacity level in percent + * @full: full capacity level in percent + */ +struct ab8500_bm_capacity_levels { + int critical; + int low; + int normal; + int high; + int full; +}; + +/** + * struct ab8500_bm_charger_parameters - Charger specific parameters + * @usb_volt_max: maximum allowed USB charger voltage in mV + * @usb_curr_max: maximum allowed USB charger current in mA + * @ac_volt_max: maximum allowed AC charger voltage in mV + * @ac_curr_max: maximum allowed AC charger current in mA + */ +struct ab8500_bm_charger_parameters { + int usb_volt_max; + int usb_curr_max; + int ac_volt_max; + int ac_curr_max; +}; + +/** + * struct ab8500_bm_data - ab8500 battery management data + * @temp_under under this temp, charging is stopped + * @temp_low between this temp and temp_under charging is reduced + * @temp_high between this temp and temp_over charging is reduced + * @temp_over over this temp, charging is stopped + * @temp_interval_chg temperature measurement interval in s when charging + * @temp_interval_nochg temperature measurement interval in s when not charging + * @main_safety_tmr_h safety timer for main charger + * @usb_safety_tmr_h safety timer for usb charger + * @bkup_bat_v voltage which we charge the backup battery with + * @bkup_bat_i current which we charge the backup battery with + * @no_maintenance indicates that maintenance charging is disabled + * @adc_therm placement of thermistor, batctrl or battemp adc + * @chg_unknown_bat flag to enable charging of unknown batteries + * @enable_overshoot flag to enable VBAT overshoot control + * @fg_res resistance of FG resistor in 0.1mOhm + * @n_btypes number of elements in array bat_type + * @batt_id index of the identified battery in array bat_type + * @interval_charging charge alg cycle period time when charging (sec) + * @interval_not_charging charge alg cycle period time when not charging (sec) + * @temp_hysteresis temperature hysteresis + * @gnd_lift_resistance Battery ground to phone ground resistance (mOhm) + * @maxi: maximization parameters + * @cap_levels capacity in percent for the different capacity levels + * @bat_type table of supported battery types + * @chg_params charger parameters + * @fg_params fuel gauge parameters + */ +struct ab8500_bm_data { + int temp_under; + int temp_low; + int temp_high; + int temp_over; + int temp_interval_chg; + int temp_interval_nochg; + int main_safety_tmr_h; + int usb_safety_tmr_h; + int bkup_bat_v; + int bkup_bat_i; + bool no_maintenance; + bool chg_unknown_bat; + bool enable_overshoot; + enum adc_therm adc_therm; + int fg_res; + int n_btypes; + int batt_id; + int interval_charging; + int interval_not_charging; + int temp_hysteresis; + int gnd_lift_resistance; + const struct ab8500_maxim_parameters *maxi; + const struct ab8500_bm_capacity_levels *cap_levels; + const struct battery_type *bat_type; + const struct ab8500_bm_charger_parameters *chg_params; + const struct ab8500_fg_parameters *fg_params; +}; + +struct ab8500_charger_platform_data { + char **supplied_to; + size_t num_supplicants; + bool autopower_cfg; +}; + +struct ab8500_btemp_platform_data { + char **supplied_to; + size_t num_supplicants; +}; + +struct ab8500_fg_platform_data { + char **supplied_to; + size_t num_supplicants; +}; + +struct ab8500_chargalg_platform_data { + char **supplied_to; + size_t num_supplicants; +}; +struct ab8500_btemp; +struct ab8500_gpadc; +struct ab8500_fg; +#ifdef CONFIG_AB8500_BM +void ab8500_fg_reinit(void); +void ab8500_charger_usb_state_changed(u8 bm_usb_state, u16 mA); +struct ab8500_btemp *ab8500_btemp_get(void); +int ab8500_btemp_get_batctrl_temp(struct ab8500_btemp *btemp); +struct ab8500_fg *ab8500_fg_get(void); +int ab8500_fg_inst_curr_blocking(struct ab8500_fg *dev); +int ab8500_fg_inst_curr_start(struct ab8500_fg *di); +int ab8500_fg_inst_curr_finalize(struct ab8500_fg *di, int *res); +int ab8500_fg_inst_curr_done(struct ab8500_fg *di); + +#else +int ab8500_fg_inst_curr_done(struct ab8500_fg *di) +{ +} +static void ab8500_fg_reinit(void) +{ +} +static void ab8500_charger_usb_state_changed(u8 bm_usb_state, u16 mA) +{ +} +static struct ab8500_btemp *ab8500_btemp_get(void) +{ + return NULL; +} +static int ab8500_btemp_get_batctrl_temp(struct ab8500_btemp *btemp) +{ + return 0; +} +struct ab8500_fg *ab8500_fg_get(void) +{ + return NULL; +} +static int ab8500_fg_inst_curr_blocking(struct ab8500_fg *dev) +{ + return -ENODEV; +} + +static inline int ab8500_fg_inst_curr_start(struct ab8500_fg *di) +{ + return -ENODEV; +} + +static inline int ab8500_fg_inst_curr_finalize(struct ab8500_fg *di, int *res) +{ + return -ENODEV; +} + +#endif +#endif /* _AB8500_BM_H */ -- cgit v1.2.3-58-ga151 From d329129e9e10e3089550fd9bd692f67687503136 Mon Sep 17 00:00:00 2001 From: Anton Vorontsov Date: Wed, 14 Mar 2012 04:27:20 +0400 Subject: ab8500_btemp: Get rid of 'enum adc_therm' This is the same as abx500_adc_therm, but when the former is used, the following warning flood pops up: drivers/power/ab8500_btemp.c: In function 'ab8500_btemp_batctrl_volt_to_res': ab8500_btemp.c:150:25: warning: comparison between 'enum abx500_adc_therm' and 'enum adc_therm' [-Wenum-compare] ab8500_btemp.c: In function 'ab8500_btemp_curr_source_enable': ab8500_btemp.c:212:25: warning: comparison between 'enum abx500_adc_therm' and 'enum adc_therm' [-Wenum-compare] ab8500_btemp.c:244:32: warning: comparison between 'enum abx500_adc_therm' and 'enum adc_therm' [-Wenum-compare] ab8500_btemp.c: In function 'ab8500_btemp_measure_temp': ab8500_btemp.c:462:25: warning: comparison between 'enum abx500_adc_therm' and 'enum adc_therm' [-Wenum-compare] ab8500_btemp.c: In function 'ab8500_btemp_id': ab8500_btemp.c:528:121: warning: comparison between 'enum abx500_adc_therm' and 'enum adc_therm' [-Wenum-compare] ab8500_btemp.c:551:25: warning: comparison between 'enum abx500_adc_therm' and 'enum adc_therm' [-Wenum-compare] This patch fixes the issue by switching the driver to use more namespace-friendly enum. Signed-off-by: Anton Vorontsov --- drivers/power/ab8500_btemp.c | 13 +++++++------ include/linux/mfd/abx500/ab8500-bm.h | 15 ++------------- 2 files changed, 9 insertions(+), 19 deletions(-) (limited to 'include') diff --git a/drivers/power/ab8500_btemp.c b/drivers/power/ab8500_btemp.c index 443bc7db30a0..d8bb99394ac0 100644 --- a/drivers/power/ab8500_btemp.c +++ b/drivers/power/ab8500_btemp.c @@ -147,7 +147,7 @@ static int ab8500_btemp_batctrl_volt_to_res(struct ab8500_btemp *di, return (450000 * (v_batctrl)) / (1800 - v_batctrl); } - if (di->bat->adc_therm == ADC_THERM_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 @@ -209,7 +209,7 @@ static int ab8500_btemp_curr_source_enable(struct ab8500_btemp *di, return 0; /* Only do this for batteries with internal NTC */ - if (di->bat->adc_therm == ADC_THERM_BATCTRL && enable) { + 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 @@ -241,7 +241,7 @@ static int ab8500_btemp_curr_source_enable(struct ab8500_btemp *di, __func__); goto disable_curr_source; } - } else if (di->bat->adc_therm == ADC_THERM_BATCTRL && !enable) { + } 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 */ @@ -459,7 +459,7 @@ static int ab8500_btemp_measure_temp(struct ab8500_btemp *di) id = di->bat->batt_id; - if (di->bat->adc_therm == ADC_THERM_BATCTRL && + if (di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL && id != BATTERY_UNKNOWN) { rbat = ab8500_btemp_get_batctrl_res(di); @@ -528,7 +528,7 @@ static int ab8500_btemp_id(struct ab8500_btemp *di) dev_dbg(di->dev, "Battery detected on %s" " low %d < res %d < high: %d" " index: %d\n", - di->bat->adc_therm == ADC_THERM_BATCTRL ? + 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); @@ -548,7 +548,8 @@ static int ab8500_btemp_id(struct ab8500_btemp *di) * 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 == ADC_THERM_BATCTRL && di->bat->batt_id == 1) { + 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; } diff --git a/include/linux/mfd/abx500/ab8500-bm.h b/include/linux/mfd/abx500/ab8500-bm.h index 4b7342c54b41..f61b7b981ce2 100644 --- a/include/linux/mfd/abx500/ab8500-bm.h +++ b/include/linux/mfd/abx500/ab8500-bm.h @@ -9,6 +9,7 @@ #define _AB8500_BM_H #include +#include /* * System control 2 register offsets. @@ -231,18 +232,6 @@ /* Battery type */ #define BATTERY_UNKNOWN 00 -/* - * ADC for the battery thermistor. - * When using the ADC_THERM_BATCTRL the battery ID resistor is combined with - * a NTC resistor to both identify the battery and to measure its temperature. - * Different phone manufactures uses different techniques to both identify the - * battery and to read its temperature. - */ -enum adc_therm { - ADC_THERM_BATCTRL, - ADC_THERM_BATTEMP, -}; - /** * struct res_to_temp - defines one point in a temp to res curve. To * be used in battery packs that combines the identification resistor with a @@ -464,7 +453,7 @@ struct ab8500_bm_data { bool no_maintenance; bool chg_unknown_bat; bool enable_overshoot; - enum adc_therm adc_therm; + enum abx500_adc_therm adc_therm; int fg_res; int n_btypes; int batt_id; -- cgit v1.2.3-58-ga151 From 450ceb2b23ed0feba8c1238f52a1d3feacd5379d Mon Sep 17 00:00:00 2001 From: Anton Vorontsov Date: Wed, 14 Mar 2012 04:38:32 +0400 Subject: ab8500_fg: Get rid of 'struct v_to_cap' The struct is duplicated, plus when used it causes the following warnings: CHECK drivers/power/ab8500_fg.c ab8500_fg.c:818:13: warning: incorrect type in assignment (different base types) ab8500_fg.c:818:13: expected struct v_to_cap *tbl ab8500_fg.c:818:13: got struct abx500_v_to_cap *const v_to_cap_tbl CC drivers/power/ab8500_fg.o ab8500_fg.c: In function 'ab8500_fg_volt_to_capacity': ab8500_fg.c:818:6: warning: assignment from incompatible pointer type [enabled by default] Signed-off-by: Anton Vorontsov --- drivers/power/ab8500_fg.c | 2 +- include/linux/mfd/abx500/ab8500-bm.h | 10 ---------- 2 files changed, 1 insertion(+), 11 deletions(-) (limited to 'include') diff --git a/drivers/power/ab8500_fg.c b/drivers/power/ab8500_fg.c index 41ccb70d4018..180c21ad6800 100644 --- a/drivers/power/ab8500_fg.c +++ b/drivers/power/ab8500_fg.c @@ -812,7 +812,7 @@ static int ab8500_fg_bat_voltage(struct ab8500_fg *di) static int ab8500_fg_volt_to_capacity(struct ab8500_fg *di, int voltage) { int i, tbl_size; - struct v_to_cap *tbl; + struct abx500_v_to_cap *tbl; int cap = 0; tbl = di->bat->bat_type[di->bat->batt_id].v_to_cap_tbl, diff --git a/include/linux/mfd/abx500/ab8500-bm.h b/include/linux/mfd/abx500/ab8500-bm.h index f61b7b981ce2..44d86206a253 100644 --- a/include/linux/mfd/abx500/ab8500-bm.h +++ b/include/linux/mfd/abx500/ab8500-bm.h @@ -255,16 +255,6 @@ struct batres_vs_temp { int resist; }; -/** - * struct v_to_cap - Table for translating voltage to capacity - * @voltage: Voltage in mV - * @capacity: Capacity in percent - */ -struct v_to_cap { - int voltage; - int capacity; -}; - /* Forward declaration */ struct ab8500_fg; -- cgit v1.2.3-58-ga151 From c34a61b4e7a9966edc0e87d7b0a12fbb8cc58168 Mon Sep 17 00:00:00 2001 From: Anton Vorontsov Date: Wed, 14 Mar 2012 04:39:01 +0400 Subject: ab8500_fg: Get rid of 'struct battery_type' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The struct is duplicated, plus causes the following flood: CC drivers/power/ab8500_fg.o ab8500_fg.c: In function ‘ab8500_fg_get_ext_psy_data’: b8500_fg.c:2081:8: warning: assignment from incompatible pointer type [enabled by default] Signed-off-by: Anton Vorontsov --- drivers/power/ab8500_fg.c | 3 +- include/linux/mfd/abx500/ab8500-bm.h | 59 ------------------------------------ 2 files changed, 2 insertions(+), 60 deletions(-) (limited to 'include') diff --git a/drivers/power/ab8500_fg.c b/drivers/power/ab8500_fg.c index 180c21ad6800..b7e12c8af8cd 100644 --- a/drivers/power/ab8500_fg.c +++ b/drivers/power/ab8500_fg.c @@ -2077,7 +2077,8 @@ static int ab8500_fg_get_ext_psy_data(struct device *dev, void *data) switch (ext->type) { case POWER_SUPPLY_TYPE_BATTERY: if (!di->flags.batt_id_received) { - const struct battery_type *b; + const struct abx500_battery_type *b; + b = &(di->bat->bat_type[di->bat->batt_id]); di->flags.batt_id_received = true; diff --git a/include/linux/mfd/abx500/ab8500-bm.h b/include/linux/mfd/abx500/ab8500-bm.h index 44d86206a253..44310c98ee6e 100644 --- a/include/linux/mfd/abx500/ab8500-bm.h +++ b/include/linux/mfd/abx500/ab8500-bm.h @@ -312,64 +312,6 @@ struct ab8500_maxim_parameters { int charger_curr_step; }; -/** - * struct battery_type - different batteries supported - * @name: battery technology - * @resis_high: battery upper resistance limit - * @resis_low: battery lower resistance limit - * @charge_full_design: Maximum battery capacity in mAh - * @nominal_voltage: Nominal voltage of the battery in mV - * @termination_vol: max voltage upto which battery can be charged - * @termination_curr battery charging termination current in mA - * @recharge_vol battery voltage limit that will trigger a new - * full charging cycle in the case where maintenan- - * -ce charging has been disabled - * @normal_cur_lvl: charger current in normal state in mA - * @normal_vol_lvl: charger voltage in normal state in mV - * @maint_a_cur_lvl: charger current in maintenance A state in mA - * @maint_a_vol_lvl: charger voltage in maintenance A state in mV - * @maint_a_chg_timer_h: charge time in maintenance A state - * @maint_b_cur_lvl: charger current in maintenance B state in mA - * @maint_b_vol_lvl: charger voltage in maintenance B state in mV - * @maint_b_chg_timer_h: charge time in maintenance B state - * @low_high_cur_lvl: charger current in temp low/high state in mA - * @low_high_vol_lvl: charger voltage in temp low/high state in mV' - * @battery_resistance: battery inner resistance in mOhm. - * @n_r_t_tbl_elements: number of elements in r_to_t_tbl - * @r_to_t_tbl: table containing resistance to temp points - * @n_v_cap_tbl_elements: number of elements in v_to_cap_tbl - * @v_to_cap_tbl: Voltage to capacity (in %) table - * @n_batres_tbl_elements number of elements in the batres_tbl - * @batres_tbl battery internal resistance vs temperature table - */ -struct battery_type { - int name; - int resis_high; - int resis_low; - int charge_full_design; - int nominal_voltage; - int termination_vol; - int termination_curr; - int recharge_vol; - int normal_cur_lvl; - int normal_vol_lvl; - int maint_a_cur_lvl; - int maint_a_vol_lvl; - int maint_a_chg_timer_h; - int maint_b_cur_lvl; - int maint_b_vol_lvl; - int maint_b_chg_timer_h; - int low_high_cur_lvl; - int low_high_vol_lvl; - int battery_resistance; - int n_temp_tbl_elements; - struct res_to_temp *r_to_t_tbl; - int n_v_cap_tbl_elements; - struct v_to_cap *v_to_cap_tbl; - int n_batres_tbl_elements; - struct batres_vs_temp *batres_tbl; -}; - /** * struct ab8500_bm_capacity_levels - ab8500 capacity level data * @critical: critical capacity level in percent @@ -453,7 +395,6 @@ struct ab8500_bm_data { int gnd_lift_resistance; const struct ab8500_maxim_parameters *maxi; const struct ab8500_bm_capacity_levels *cap_levels; - const struct battery_type *bat_type; const struct ab8500_bm_charger_parameters *chg_params; const struct ab8500_fg_parameters *fg_params; }; -- cgit v1.2.3-58-ga151