diff options
Diffstat (limited to 'drivers/power/max14577_charger.c')
-rw-r--r-- | drivers/power/max14577_charger.c | 370 |
1 files changed, 336 insertions, 34 deletions
diff --git a/drivers/power/max14577_charger.c b/drivers/power/max14577_charger.c index fad2a75b3604..0a2bc7277026 100644 --- a/drivers/power/max14577_charger.c +++ b/drivers/power/max14577_charger.c @@ -1,7 +1,7 @@ /* - * Battery charger driver for the Maxim 14577 + * max14577_charger.c - Battery charger driver for the Maxim 14577/77836 * - * Copyright (C) 2013 Samsung Electronics + * Copyright (C) 2013,2014 Samsung Electronics * Krzysztof Kozlowski <k.kozlowski@samsung.com> * * This program is free software; you can redistribute it and/or modify @@ -19,16 +19,44 @@ #include <linux/platform_device.h> #include <linux/power_supply.h> #include <linux/mfd/max14577-private.h> +#include <linux/mfd/max14577.h> struct max14577_charger { struct device *dev; struct max14577 *max14577; struct power_supply charger; - unsigned int charging_state; - unsigned int battery_state; + unsigned int charging_state; + unsigned int battery_state; + + struct max14577_charger_platform_data *pdata; }; +/* + * Helper function for mapping values of STATUS2/CHGTYP register on max14577 + * and max77836 chipsets to enum maxim_muic_charger_type. + */ +static enum max14577_muic_charger_type maxim_get_charger_type( + enum maxim_device_type dev_type, u8 val) { + switch (val) { + case MAX14577_CHARGER_TYPE_NONE: + case MAX14577_CHARGER_TYPE_USB: + case MAX14577_CHARGER_TYPE_DOWNSTREAM_PORT: + case MAX14577_CHARGER_TYPE_DEDICATED_CHG: + case MAX14577_CHARGER_TYPE_SPECIAL_500MA: + case MAX14577_CHARGER_TYPE_SPECIAL_1A: + return val; + case MAX14577_CHARGER_TYPE_DEAD_BATTERY: + case MAX14577_CHARGER_TYPE_RESERVED: + if (dev_type == MAXIM_DEVICE_TYPE_MAX77836) + val |= 0x8; + return val; + default: + WARN_ONCE(1, "max14577: Unsupported chgtyp register value 0x%02x", val); + return val; + } +} + static int max14577_get_charger_state(struct max14577_charger *chg) { struct regmap *rmap = chg->max14577->regmap; @@ -89,19 +117,23 @@ static int max14577_get_online(struct max14577_charger *chg) { struct regmap *rmap = chg->max14577->regmap; u8 reg_data; + enum max14577_muic_charger_type chg_type; max14577_read_reg(rmap, MAX14577_MUIC_REG_STATUS2, ®_data); reg_data = ((reg_data & STATUS2_CHGTYP_MASK) >> STATUS2_CHGTYP_SHIFT); - switch (reg_data) { + chg_type = maxim_get_charger_type(chg->max14577->dev_type, reg_data); + switch (chg_type) { case MAX14577_CHARGER_TYPE_USB: case MAX14577_CHARGER_TYPE_DEDICATED_CHG: case MAX14577_CHARGER_TYPE_SPECIAL_500MA: case MAX14577_CHARGER_TYPE_SPECIAL_1A: case MAX14577_CHARGER_TYPE_DEAD_BATTERY: + case MAX77836_CHARGER_TYPE_SPECIAL_BIAS: return 1; case MAX14577_CHARGER_TYPE_NONE: case MAX14577_CHARGER_TYPE_DOWNSTREAM_PORT: case MAX14577_CHARGER_TYPE_RESERVED: + case MAX77836_CHARGER_TYPE_RESERVED: default: return 0; } @@ -118,10 +150,12 @@ static int max14577_get_battery_health(struct max14577_charger *chg) struct regmap *rmap = chg->max14577->regmap; int state = POWER_SUPPLY_HEALTH_GOOD; u8 reg_data; + enum max14577_muic_charger_type chg_type; max14577_read_reg(rmap, MAX14577_MUIC_REG_STATUS2, ®_data); reg_data = ((reg_data & STATUS2_CHGTYP_MASK) >> STATUS2_CHGTYP_SHIFT); - if (reg_data == MAX14577_CHARGER_TYPE_DEAD_BATTERY) { + chg_type = maxim_get_charger_type(chg->max14577->dev_type, reg_data); + if (chg_type == MAX14577_CHARGER_TYPE_DEAD_BATTERY) { state = POWER_SUPPLY_HEALTH_DEAD; goto state_set; } @@ -147,15 +181,131 @@ static int max14577_get_present(struct max14577_charger *chg) return 1; } +static int max14577_set_fast_charge_timer(struct max14577_charger *chg, + unsigned long hours) +{ + u8 reg_data; + + switch (hours) { + case 5 ... 7: + reg_data = hours - 3; + break; + case 0: + /* Disable */ + reg_data = 0x7; + break; + default: + dev_err(chg->dev, "Wrong value for Fast-Charge Timer: %lu\n", + hours); + return -EINVAL; + } + reg_data <<= CHGCTRL1_TCHW_SHIFT; + + return max14577_update_reg(chg->max14577->regmap, + MAX14577_REG_CHGCTRL1, CHGCTRL1_TCHW_MASK, reg_data); +} + +static int max14577_init_constant_voltage(struct max14577_charger *chg, + unsigned int uvolt) +{ + u8 reg_data; + + if (uvolt < MAXIM_CHARGER_CONSTANT_VOLTAGE_MIN || + uvolt > MAXIM_CHARGER_CONSTANT_VOLTAGE_MAX) + return -EINVAL; + + if (uvolt == 4200000) + reg_data = 0x0; + else if (uvolt == MAXIM_CHARGER_CONSTANT_VOLTAGE_MAX) + reg_data = 0x1f; + else if (uvolt <= 4280000) { + unsigned int val = uvolt; + + val -= MAXIM_CHARGER_CONSTANT_VOLTAGE_MIN; + val /= MAXIM_CHARGER_CONSTANT_VOLTAGE_STEP; + if (uvolt <= 4180000) + reg_data = 0x1 + val; + else + reg_data = val; /* Fix for gap between 4.18V and 4.22V */ + } else + return -EINVAL; + + reg_data <<= CHGCTRL3_MBCCVWRC_SHIFT; + + return max14577_write_reg(chg->max14577->regmap, + MAX14577_CHG_REG_CHG_CTRL3, reg_data); +} + +static int max14577_init_eoc(struct max14577_charger *chg, + unsigned int uamp) +{ + unsigned int current_bits = 0xf; + u8 reg_data; + + switch (chg->max14577->dev_type) { + case MAXIM_DEVICE_TYPE_MAX77836: + if (uamp < 5000) + return -EINVAL; /* Requested current is too low */ + + if (uamp >= 7500 && uamp < 10000) + current_bits = 0x0; + else if (uamp <= 50000) { + /* <5000, 7499> and <10000, 50000> */ + current_bits = uamp / 5000; + } else { + uamp = min(uamp, 100000U) - 50000U; + current_bits = 0xa + uamp / 10000; + } + break; + + case MAXIM_DEVICE_TYPE_MAX14577: + default: + if (uamp < MAX14577_CHARGER_EOC_CURRENT_LIMIT_MIN) + return -EINVAL; /* Requested current is too low */ + + uamp = min(uamp, MAX14577_CHARGER_EOC_CURRENT_LIMIT_MAX); + uamp -= MAX14577_CHARGER_EOC_CURRENT_LIMIT_MIN; + current_bits = uamp / MAX14577_CHARGER_EOC_CURRENT_LIMIT_STEP; + break; + } + + reg_data = current_bits << CHGCTRL5_EOCS_SHIFT; + + return max14577_update_reg(chg->max14577->regmap, + MAX14577_CHG_REG_CHG_CTRL5, CHGCTRL5_EOCS_MASK, + reg_data); +} + +static int max14577_init_fast_charge(struct max14577_charger *chg, + unsigned int uamp) +{ + u8 reg_data; + int ret; + const struct maxim_charger_current *limits = + &maxim_charger_currents[chg->max14577->dev_type]; + + ret = maxim_charger_calc_reg_current(limits, uamp, uamp, ®_data); + if (ret) { + dev_err(chg->dev, "Wrong value for fast charge: %u\n", uamp); + return ret; + } + + return max14577_update_reg(chg->max14577->regmap, + MAX14577_CHG_REG_CHG_CTRL4, + CHGCTRL4_MBCICHWRCL_MASK | CHGCTRL4_MBCICHWRCH_MASK, + reg_data); +} + /* * Sets charger registers to proper and safe default values. * Some of these values are equal to defaults in MAX14577E * data sheet but there are minor differences. */ -static void max14577_charger_reg_init(struct max14577_charger *chg) +static int max14577_charger_reg_init(struct max14577_charger *chg) { struct regmap *rmap = chg->max14577->regmap; u8 reg_data; + int ret; /* * Charger-Type Manual Detection, default off (set CHGTYPMAN to 0) @@ -167,10 +317,6 @@ static void max14577_charger_reg_init(struct max14577_charger *chg) CDETCTRL1_CHGDETEN_MASK | CDETCTRL1_CHGTYPMAN_MASK, reg_data); - /* Battery Fast-Charge Timer, from SM-V700: 6hrs */ - reg_data = 0x3 << CHGCTRL1_TCHW_SHIFT; - max14577_write_reg(rmap, MAX14577_REG_CHGCTRL1, reg_data); - /* * Wall-Adapter Rapid Charge, default on * Battery-Charger, default on @@ -179,29 +325,46 @@ static void max14577_charger_reg_init(struct max14577_charger *chg) reg_data |= 0x1 << CHGCTRL2_MBCHOSTEN_SHIFT; max14577_write_reg(rmap, MAX14577_REG_CHGCTRL2, reg_data); - /* Battery-Charger Constant Voltage (CV) Mode, from SM-V700: 4.35V */ - reg_data = 0xf << CHGCTRL3_MBCCVWRC_SHIFT; - max14577_write_reg(rmap, MAX14577_REG_CHGCTRL3, reg_data); - - /* - * Fast Battery-Charge Current Low, default 200-950mA - * Fast Battery-Charge Current High, from SM-V700: 450mA - */ - reg_data = 0x1 << CHGCTRL4_MBCICHWRCL_SHIFT; - reg_data |= 0x5 << CHGCTRL4_MBCICHWRCH_SHIFT; - max14577_write_reg(rmap, MAX14577_REG_CHGCTRL4, reg_data); - - /* End-of-Charge Current, from SM-V700: 50mA */ - reg_data = 0x0 << CHGCTRL5_EOCS_SHIFT; - max14577_write_reg(rmap, MAX14577_REG_CHGCTRL5, reg_data); - /* Auto Charging Stop, default off */ reg_data = 0x0 << CHGCTRL6_AUTOSTOP_SHIFT; max14577_write_reg(rmap, MAX14577_REG_CHGCTRL6, reg_data); - /* Overvoltage-Protection Threshold, from SM-V700: 6.5V */ - reg_data = 0x2 << CHGCTRL7_OTPCGHCVS_SHIFT; + ret = max14577_init_constant_voltage(chg, chg->pdata->constant_uvolt); + if (ret) + return ret; + + ret = max14577_init_eoc(chg, chg->pdata->eoc_uamp); + if (ret) + return ret; + + ret = max14577_init_fast_charge(chg, chg->pdata->fast_charge_uamp); + if (ret) + return ret; + + ret = max14577_set_fast_charge_timer(chg, + MAXIM_CHARGER_FAST_CHARGE_TIMER_DEFAULT); + if (ret) + return ret; + + /* Initialize Overvoltage-Protection Threshold */ + switch (chg->pdata->ovp_uvolt) { + case 7500000: + reg_data = 0x0; + break; + case 6000000: + case 6500000: + case 7000000: + reg_data = 0x1 + (chg->pdata->ovp_uvolt - 6000000) / 500000; + break; + default: + dev_err(chg->dev, "Wrong value for OVP: %u\n", + chg->pdata->ovp_uvolt); + return -EINVAL; + } + reg_data <<= CHGCTRL7_OTPCGHCVS_SHIFT; max14577_write_reg(rmap, MAX14577_REG_CHGCTRL7, reg_data); + + return 0; } /* Support property from charger */ @@ -215,7 +378,11 @@ static enum power_supply_property max14577_charger_props[] = { POWER_SUPPLY_PROP_MANUFACTURER, }; -static const char *model_name = "MAX14577"; +static const char * const model_names[] = { + [MAXIM_DEVICE_TYPE_UNKNOWN] = "MAX14577-like", + [MAXIM_DEVICE_TYPE_MAX14577] = "MAX14577", + [MAXIM_DEVICE_TYPE_MAX77836] = "MAX77836", +}; static const char *manufacturer = "Maxim Integrated"; static int max14577_charger_get_property(struct power_supply *psy, @@ -244,7 +411,8 @@ static int max14577_charger_get_property(struct power_supply *psy, val->intval = max14577_get_online(chg); break; case POWER_SUPPLY_PROP_MODEL_NAME: - val->strval = model_name; + BUILD_BUG_ON(ARRAY_SIZE(model_names) != MAXIM_DEVICE_TYPE_NUM); + val->strval = model_names[chg->max14577->dev_type]; break; case POWER_SUPPLY_PROP_MANUFACTURER: val->strval = manufacturer; @@ -256,6 +424,110 @@ static int max14577_charger_get_property(struct power_supply *psy, return ret; } +#ifdef CONFIG_OF +static struct max14577_charger_platform_data *max14577_charger_dt_init( + struct platform_device *pdev) +{ + struct max14577_charger_platform_data *pdata; + struct device_node *np = pdev->dev.of_node; + int ret; + + if (!np) { + dev_err(&pdev->dev, "No charger OF node\n"); + return ERR_PTR(-EINVAL); + } + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + ret = of_property_read_u32(np, "maxim,constant-uvolt", + &pdata->constant_uvolt); + if (ret) { + dev_err(&pdev->dev, "Cannot parse maxim,constant-uvolt field from DT\n"); + return ERR_PTR(ret); + } + + ret = of_property_read_u32(np, "maxim,fast-charge-uamp", + &pdata->fast_charge_uamp); + if (ret) { + dev_err(&pdev->dev, "Cannot parse maxim,fast-charge-uamp field from DT\n"); + return ERR_PTR(ret); + } + + ret = of_property_read_u32(np, "maxim,eoc-uamp", &pdata->eoc_uamp); + if (ret) { + dev_err(&pdev->dev, "Cannot parse maxim,eoc-uamp field from DT\n"); + return ERR_PTR(ret); + } + + ret = of_property_read_u32(np, "maxim,ovp-uvolt", &pdata->ovp_uvolt); + if (ret) { + dev_err(&pdev->dev, "Cannot parse maxim,ovp-uvolt field from DT\n"); + return ERR_PTR(ret); + } + + return pdata; +} +#else /* CONFIG_OF */ +static struct max14577_charger_platform_data *max14577_charger_dt_init( + struct platform_device *pdev) +{ + return NULL; +} +#endif /* CONFIG_OF */ + +static ssize_t show_fast_charge_timer(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct max14577_charger *chg = dev_get_drvdata(dev); + u8 reg_data; + int ret; + unsigned int val; + + ret = max14577_read_reg(chg->max14577->regmap, MAX14577_REG_CHGCTRL1, + ®_data); + if (ret) + return ret; + + reg_data &= CHGCTRL1_TCHW_MASK; + reg_data >>= CHGCTRL1_TCHW_SHIFT; + switch (reg_data) { + case 0x2 ... 0x4: + val = reg_data + 3; + break; + case 0x7: + val = 0; + break; + default: + val = 5; + break; + } + + return scnprintf(buf, PAGE_SIZE, "%u\n", val); +} + +static ssize_t store_fast_charge_timer(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct max14577_charger *chg = dev_get_drvdata(dev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + ret = max14577_set_fast_charge_timer(chg, val); + if (ret) + return ret; + + return count; +} + +static DEVICE_ATTR(fast_charge_timer, S_IRUGO | S_IWUSR, + show_fast_charge_timer, store_fast_charge_timer); + static int max14577_charger_probe(struct platform_device *pdev) { struct max14577_charger *chg; @@ -270,7 +542,13 @@ static int max14577_charger_probe(struct platform_device *pdev) chg->dev = &pdev->dev; chg->max14577 = max14577; - max14577_charger_reg_init(chg); + chg->pdata = max14577_charger_dt_init(pdev); + if (IS_ERR_OR_NULL(chg->pdata)) + return PTR_ERR(chg->pdata); + + ret = max14577_charger_reg_init(chg); + if (ret) + return ret; chg->charger.name = "max14577-charger", chg->charger.type = POWER_SUPPLY_TYPE_BATTERY, @@ -278,24 +556,47 @@ static int max14577_charger_probe(struct platform_device *pdev) chg->charger.num_properties = ARRAY_SIZE(max14577_charger_props), chg->charger.get_property = max14577_charger_get_property, + ret = device_create_file(&pdev->dev, &dev_attr_fast_charge_timer); + if (ret) { + dev_err(&pdev->dev, "failed: create sysfs entry\n"); + return ret; + } + ret = power_supply_register(&pdev->dev, &chg->charger); if (ret) { dev_err(&pdev->dev, "failed: power supply register\n"); - return ret; + goto err; } + /* Check for valid values for charger */ + BUILD_BUG_ON(MAX14577_CHARGER_EOC_CURRENT_LIMIT_MIN + + MAX14577_CHARGER_EOC_CURRENT_LIMIT_STEP * 0xf != + MAX14577_CHARGER_EOC_CURRENT_LIMIT_MAX); return 0; + +err: + device_remove_file(&pdev->dev, &dev_attr_fast_charge_timer); + + return ret; } static int max14577_charger_remove(struct platform_device *pdev) { struct max14577_charger *chg = platform_get_drvdata(pdev); + device_remove_file(&pdev->dev, &dev_attr_fast_charge_timer); power_supply_unregister(&chg->charger); return 0; } +static const struct platform_device_id max14577_charger_id[] = { + { "max14577-charger", MAXIM_DEVICE_TYPE_MAX14577, }, + { "max77836-charger", MAXIM_DEVICE_TYPE_MAX77836, }, + { } +}; +MODULE_DEVICE_TABLE(platform, max14577_charger_id); + static struct platform_driver max14577_charger_driver = { .driver = { .owner = THIS_MODULE, @@ -303,9 +604,10 @@ static struct platform_driver max14577_charger_driver = { }, .probe = max14577_charger_probe, .remove = max14577_charger_remove, + .id_table = max14577_charger_id, }; module_platform_driver(max14577_charger_driver); MODULE_AUTHOR("Krzysztof Kozlowski <k.kozlowski@samsung.com>"); -MODULE_DESCRIPTION("MAXIM 14577 charger driver"); +MODULE_DESCRIPTION("Maxim 14577/77836 charger driver"); MODULE_LICENSE("GPL"); |