From 69fb4dcada7704beeba86e3f401a66c726aa8504 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 1 Aug 2015 10:39:38 +0200 Subject: power: Add an axp20x-usb-power driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds a driver for the usb power_supply bits of the axp20x PMICs. I initially started writing my own driver, before coming aware of Bruno Prémont's excellent earlier RFC with a driver for this. My driver was lacking CURRENT_MAX and VOLTAGE_MIN support Bruno's drvier has, so I've copied the code for those from his driver. Note that the AC-power-supply and battery charger bits will need separate drivers. Each one needs its own devictree child-node so that other devicetree nodes can reference the right power-supply, and thus each one will get its own mfd-cell / platform_device and platform-driver. Cc: Bruno Prémont Acked-by: Lee Jones Signed-off-by: Bruno Prémont Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/Kconfig | 7 ++ drivers/power/Makefile | 1 + drivers/power/axp20x_usb_power.c | 248 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 256 insertions(+) create mode 100644 drivers/power/axp20x_usb_power.c (limited to 'drivers') diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index f8758d6febf8..914167e14d2e 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -472,6 +472,13 @@ config CHARGER_RT9455 help Say Y to enable support for Richtek RT9455 battery charger. +config AXP20X_POWER + tristate "AXP20x power supply driver" + depends on MFD_AXP20X + help + This driver provides support for the power supply features of + AXP20x PMIC. + source "drivers/power/reset/Kconfig" endif # POWER_SUPPLY diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 5752ce818f51..fb4413e5dd37 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_GENERIC_ADC_BATTERY) += generic-adc-battery.o obj-$(CONFIG_PDA_POWER) += pda_power.o obj-$(CONFIG_APM_POWER) += apm_power.o +obj-$(CONFIG_AXP20X_POWER) += axp20x_usb_power.o obj-$(CONFIG_MAX8925_POWER) += max8925_power.o obj-$(CONFIG_WM831X_BACKUP) += wm831x_backup.o obj-$(CONFIG_WM831X_POWER) += wm831x_power.o diff --git a/drivers/power/axp20x_usb_power.c b/drivers/power/axp20x_usb_power.c new file mode 100644 index 000000000000..421a90b83567 --- /dev/null +++ b/drivers/power/axp20x_usb_power.c @@ -0,0 +1,248 @@ +/* + * AXP20x PMIC USB power supply status driver + * + * Copyright (C) 2015 Hans de Goede + * Copyright (C) 2014 Bruno Prémont + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRVNAME "axp20x-usb-power-supply" + +#define AXP20X_PWR_STATUS_VBUS_PRESENT BIT(5) +#define AXP20X_PWR_STATUS_VBUS_USED BIT(4) + +#define AXP20X_USB_STATUS_VBUS_VALID BIT(2) + +#define AXP20X_VBUS_VHOLD_uV(b) (4000000 + (((b) >> 3) & 7) * 100000) +#define AXP20X_VBUS_CLIMIT_MASK 3 +#define AXP20X_VBUC_CLIMIT_900mA 0 +#define AXP20X_VBUC_CLIMIT_500mA 1 +#define AXP20X_VBUC_CLIMIT_100mA 2 +#define AXP20X_VBUC_CLIMIT_NONE 3 + +#define AXP20X_ADC_EN1_VBUS_CURR BIT(2) +#define AXP20X_ADC_EN1_VBUS_VOLT BIT(3) + +#define AXP20X_VBUS_MON_VBUS_VALID BIT(3) + +struct axp20x_usb_power { + struct regmap *regmap; + struct power_supply *supply; +}; + +static irqreturn_t axp20x_usb_power_irq(int irq, void *devid) +{ + struct axp20x_usb_power *power = devid; + + power_supply_changed(power->supply); + + return IRQ_HANDLED; +} + +static int axp20x_usb_power_get_property(struct power_supply *psy, + enum power_supply_property psp, union power_supply_propval *val) +{ + struct axp20x_usb_power *power = power_supply_get_drvdata(psy); + unsigned int input, v; + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + ret = regmap_read(power->regmap, AXP20X_VBUS_IPSOUT_MGMT, &v); + if (ret) + return ret; + + val->intval = AXP20X_VBUS_VHOLD_uV(v); + return 0; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = axp20x_read_variable_width(power->regmap, + AXP20X_VBUS_V_ADC_H, 12); + if (ret < 0) + return ret; + + val->intval = ret * 1700; /* 1 step = 1.7 mV */ + return 0; + case POWER_SUPPLY_PROP_CURRENT_MAX: + ret = regmap_read(power->regmap, AXP20X_VBUS_IPSOUT_MGMT, &v); + if (ret) + return ret; + + switch (v & AXP20X_VBUS_CLIMIT_MASK) { + case AXP20X_VBUC_CLIMIT_100mA: + val->intval = 100000; + break; + case AXP20X_VBUC_CLIMIT_500mA: + val->intval = 500000; + break; + case AXP20X_VBUC_CLIMIT_900mA: + val->intval = 900000; + break; + case AXP20X_VBUC_CLIMIT_NONE: + val->intval = -1; + break; + } + return 0; + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = axp20x_read_variable_width(power->regmap, + AXP20X_VBUS_I_ADC_H, 12); + if (ret < 0) + return ret; + + val->intval = ret * 375; /* 1 step = 0.375 mA */ + return 0; + default: + break; + } + + /* All the properties below need the input-status reg value */ + ret = regmap_read(power->regmap, AXP20X_PWR_INPUT_STATUS, &input); + if (ret) + return ret; + + switch (psp) { + case POWER_SUPPLY_PROP_HEALTH: + if (!(input & AXP20X_PWR_STATUS_VBUS_PRESENT)) { + val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; + break; + } + + ret = regmap_read(power->regmap, AXP20X_USB_OTG_STATUS, &v); + if (ret) + return ret; + + if (!(v & AXP20X_USB_STATUS_VBUS_VALID)) { + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + break; + } + + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = !!(input & AXP20X_PWR_STATUS_VBUS_PRESENT); + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = !!(input & AXP20X_PWR_STATUS_VBUS_USED); + break; + default: + return -EINVAL; + } + + return 0; +} + +static enum power_supply_property axp20x_usb_power_properties[] = { + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_MIN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_CURRENT_NOW, +}; + +static const struct power_supply_desc axp20x_usb_power_desc = { + .name = "axp20x-usb", + .type = POWER_SUPPLY_TYPE_USB, + .properties = axp20x_usb_power_properties, + .num_properties = ARRAY_SIZE(axp20x_usb_power_properties), + .get_property = axp20x_usb_power_get_property, +}; + +static int axp20x_usb_power_probe(struct platform_device *pdev) +{ + struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent); + struct power_supply_config psy_cfg = {}; + struct axp20x_usb_power *power; + static const char * const irq_names[] = { "VBUS_PLUGIN", + "VBUS_REMOVAL", "VBUS_VALID", "VBUS_NOT_VALID" }; + int i, irq, ret; + + if (!of_device_is_available(pdev->dev.of_node)) + return -ENODEV; + + if (!axp20x) { + dev_err(&pdev->dev, "Parent drvdata not set\n"); + return -EINVAL; + } + + power = devm_kzalloc(&pdev->dev, sizeof(*power), GFP_KERNEL); + if (!power) + return -ENOMEM; + + power->regmap = axp20x->regmap; + + /* Enable vbus valid checking */ + ret = regmap_update_bits(power->regmap, AXP20X_VBUS_MON, + AXP20X_VBUS_MON_VBUS_VALID, AXP20X_VBUS_MON_VBUS_VALID); + if (ret) + return ret; + + /* Enable vbus voltage and current measurement */ + ret = regmap_update_bits(power->regmap, AXP20X_ADC_EN1, + AXP20X_ADC_EN1_VBUS_CURR | AXP20X_ADC_EN1_VBUS_VOLT, + AXP20X_ADC_EN1_VBUS_CURR | AXP20X_ADC_EN1_VBUS_VOLT); + if (ret) + return ret; + + psy_cfg.of_node = pdev->dev.of_node; + psy_cfg.drv_data = power; + + power->supply = devm_power_supply_register(&pdev->dev, + &axp20x_usb_power_desc, &psy_cfg); + if (IS_ERR(power->supply)) + return PTR_ERR(power->supply); + + /* Request irqs after registering, as irqs may trigger immediately */ + for (i = 0; i < ARRAY_SIZE(irq_names); i++) { + irq = platform_get_irq_byname(pdev, irq_names[i]); + if (irq < 0) { + dev_warn(&pdev->dev, "No IRQ for %s: %d\n", + irq_names[i], irq); + continue; + } + irq = regmap_irq_get_virq(axp20x->regmap_irqc, irq); + ret = devm_request_any_context_irq(&pdev->dev, irq, + axp20x_usb_power_irq, 0, DRVNAME, power); + if (ret < 0) + dev_warn(&pdev->dev, "Error requesting %s IRQ: %d\n", + irq_names[i], ret); + } + + return 0; +} + +static const struct of_device_id axp20x_usb_power_match[] = { + { .compatible = "x-powers,axp202-usb-power-supply" }, + { } +}; +MODULE_DEVICE_TABLE(of, axp20x_usb_power_match); + +static struct platform_driver axp20x_usb_power_driver = { + .probe = axp20x_usb_power_probe, + .driver = { + .name = DRVNAME, + .of_match_table = axp20x_usb_power_match, + }, +}; + +module_platform_driver(axp20x_usb_power_driver); + +MODULE_AUTHOR("Hans de Goede "); +MODULE_DESCRIPTION("AXP20x PMIC USB power supply status driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From c01576c8f3445208036eac72ea6703b452d42612 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 20 Aug 2015 15:12:43 -0700 Subject: power: wm831x_power: Convert to devm_kzalloc() Signed-off-by: Mark Brown Signed-off-by: Sebastian Reichel --- drivers/power/wm831x_power.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/power/wm831x_power.c b/drivers/power/wm831x_power.c index db11ae6599f3..c826c83fdf98 100644 --- a/drivers/power/wm831x_power.c +++ b/drivers/power/wm831x_power.c @@ -499,7 +499,8 @@ static int wm831x_power_probe(struct platform_device *pdev) struct wm831x_power *power; int ret, irq, i; - power = kzalloc(sizeof(struct wm831x_power), GFP_KERNEL); + power = devm_kzalloc(&pdev->dev, sizeof(struct wm831x_power), + GFP_KERNEL); if (power == NULL) return -ENOMEM; @@ -536,7 +537,7 @@ static int wm831x_power_probe(struct platform_device *pdev) NULL); if (IS_ERR(power->wall)) { ret = PTR_ERR(power->wall); - goto err_kmalloc; + goto err; } power->usb_desc.name = power->usb_name, @@ -626,8 +627,7 @@ err_usb: power_supply_unregister(power->usb); err_wall: power_supply_unregister(power->wall); -err_kmalloc: - kfree(power); +err: return ret; } @@ -654,7 +654,6 @@ static int wm831x_power_remove(struct platform_device *pdev) power_supply_unregister(wm831x_power->battery); power_supply_unregister(wm831x_power->wall); power_supply_unregister(wm831x_power->usb); - kfree(wm831x_power); return 0; } -- cgit v1.2.3 From 87d931d56970d014a7074ca57f11baadc4e6d834 Mon Sep 17 00:00:00 2001 From: Milo Kim Date: Tue, 25 Aug 2015 15:07:07 +0900 Subject: power:lp8727_charger: use the private data instead of updating I2C device platform data Currently, lp8727 charger driver parses the DT and copies values into the 'cl->dev.platform_data' if 'of_node' exists. This may have architectural issue. Platform data is configurable through the DT or I2C board info inside the platform area. However, lp8727 driver changes this configuration when it is loaded. The driver should get data from the platform side and use the private data, 'lp8727_chg->pdata' instead of changing the original platform data. _probe() procedure is changed as follows. 1. lp8727_parse_dt() returns the pointer of lp8727_platform_data. The driver uses this allocated platform data. So it should keep original platform data, 'dev->platform_data'. 2. In _probe(), check the return value of lp8727_parse_dt(). If an error is found, then exit as PTR_ERR(pdata). 3. If 'of_node' is not found, then the driver just gets the platform data from the I2C device structure. 4. Map the platform data to private data structure. Cc: Dmitry Eremin-Solenikov Cc: linux-pm@vger.kernel.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Milo Kim Signed-off-by: Sebastian Reichel --- drivers/power/lp8727_charger.c | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/power/lp8727_charger.c b/drivers/power/lp8727_charger.c index 7e741f1d3cd5..30dc265435ee 100644 --- a/drivers/power/lp8727_charger.c +++ b/drivers/power/lp8727_charger.c @@ -508,7 +508,7 @@ out: return param; } -static int lp8727_parse_dt(struct device *dev) +static struct lp8727_platform_data *lp8727_parse_dt(struct device *dev) { struct device_node *np = dev->of_node; struct device_node *child; @@ -517,11 +517,11 @@ static int lp8727_parse_dt(struct device *dev) /* If charging parameter is not defined, just skip parsing the dt */ if (of_get_child_count(np) == 0) - goto out; + return NULL; pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) - return -ENOMEM; + return ERR_PTR(-ENOMEM); of_property_read_u32(np, "debounce-ms", &pdata->debounce_msec); @@ -535,29 +535,30 @@ static int lp8727_parse_dt(struct device *dev) pdata->usb = lp8727_parse_charge_pdata(dev, child); } - dev->platform_data = pdata; -out: - return 0; + return pdata; } #else -static int lp8727_parse_dt(struct device *dev) +static struct lp8727_platform_data *lp8727_parse_dt(struct device *dev) { - return 0; + return NULL; } #endif static int lp8727_probe(struct i2c_client *cl, const struct i2c_device_id *id) { struct lp8727_chg *pchg; + struct lp8727_platform_data *pdata; int ret; if (!i2c_check_functionality(cl->adapter, I2C_FUNC_SMBUS_I2C_BLOCK)) return -EIO; if (cl->dev.of_node) { - ret = lp8727_parse_dt(&cl->dev); - if (ret) - return ret; + pdata = lp8727_parse_dt(&cl->dev); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + } else { + pdata = dev_get_platdata(&cl->dev); } pchg = devm_kzalloc(&cl->dev, sizeof(*pchg), GFP_KERNEL); @@ -566,7 +567,7 @@ static int lp8727_probe(struct i2c_client *cl, const struct i2c_device_id *id) pchg->client = cl; pchg->dev = &cl->dev; - pchg->pdata = cl->dev.platform_data; + pchg->pdata = pdata; i2c_set_clientdata(cl, pchg); mutex_init(&pchg->xfer_lock); -- cgit v1.2.3 From 9615a29932e8a2c719142ac81481ad7d0e271924 Mon Sep 17 00:00:00 2001 From: Milo Kim Date: Tue, 25 Aug 2015 15:07:08 +0900 Subject: power:lp8727_charger: parsing child node after getting debounce-ms According to lp8727 bindings[*], charging parameter is optional. So parsing can be skipped in case those properties are undefined. However, 'debounce-ms' should be read prior to checking the properties. Otherwise, 'debounce-ms' property will be ignored even it is configured inside the DT. So, counting child is processed after updating 'debounce-ms'. [*] Documentation/devicetree/bindings/power_supply/lp8727_charger.txt Cc: Dmitry Eremin-Solenikov Cc: linux-pm@vger.kernel.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Milo Kim Signed-off-by: Sebastian Reichel --- drivers/power/lp8727_charger.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/power/lp8727_charger.c b/drivers/power/lp8727_charger.c index 30dc265435ee..042fb3dacb46 100644 --- a/drivers/power/lp8727_charger.c +++ b/drivers/power/lp8727_charger.c @@ -515,16 +515,16 @@ static struct lp8727_platform_data *lp8727_parse_dt(struct device *dev) struct lp8727_platform_data *pdata; const char *type; - /* If charging parameter is not defined, just skip parsing the dt */ - if (of_get_child_count(np) == 0) - return NULL; - pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) return ERR_PTR(-ENOMEM); of_property_read_u32(np, "debounce-ms", &pdata->debounce_msec); + /* If charging parameter is not defined, just skip parsing the dt */ + if (of_get_child_count(np) == 0) + return pdata; + for_each_child_of_node(np, child) { of_property_read_string(child, "charger-type", &type); -- cgit v1.2.3 From da42bbd99d8a74ef15f53c4c1782ee2ea627ed81 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Mon, 31 Aug 2015 10:52:00 +0200 Subject: power: Remove unnecessary MODULE_ALIAS() for I2C drivers These drivers already have an I2C device id table that is used to create module aliases and the used MODULE_ALIAS() was either already in the I2C table so it was redundant or wasn't a valid I2C id so it was never used. Signed-off-by: Javier Martinez Canillas Signed-off-by: Sebastian Reichel --- drivers/power/bq24190_charger.c | 1 - drivers/power/pm2301_charger.c | 1 - drivers/power/rt9455_charger.c | 1 - drivers/power/smb347-charger.c | 1 - 4 files changed, 4 deletions(-) (limited to 'drivers') diff --git a/drivers/power/bq24190_charger.c b/drivers/power/bq24190_charger.c index 469a452cbe10..f5746b9f4e83 100644 --- a/drivers/power/bq24190_charger.c +++ b/drivers/power/bq24190_charger.c @@ -1543,5 +1543,4 @@ module_i2c_driver(bq24190_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Mark A. Greer "); -MODULE_ALIAS("i2c:bq24190-charger"); MODULE_DESCRIPTION("TI BQ24190 Charger Driver"); diff --git a/drivers/power/pm2301_charger.c b/drivers/power/pm2301_charger.c index 3a45cc0c4dce..8f9bd1d0eeb6 100644 --- a/drivers/power/pm2301_charger.c +++ b/drivers/power/pm2301_charger.c @@ -1264,5 +1264,4 @@ module_exit(pm2xxx_charger_exit); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Rajkumar kasirajan, Olivier Launay"); -MODULE_ALIAS("i2c:pm2xxx-charger"); MODULE_DESCRIPTION("PM2xxx charger management driver"); diff --git a/drivers/power/rt9455_charger.c b/drivers/power/rt9455_charger.c index a49a9d44bdda..cfdbde9daf94 100644 --- a/drivers/power/rt9455_charger.c +++ b/drivers/power/rt9455_charger.c @@ -1760,5 +1760,4 @@ module_i2c_driver(rt9455_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Anda-Maria Nicolae "); -MODULE_ALIAS("i2c:rt9455-charger"); MODULE_DESCRIPTION("Richtek RT9455 Charger Driver"); diff --git a/drivers/power/smb347-charger.c b/drivers/power/smb347-charger.c index 0b60a0b5878b..072c5189bd6d 100644 --- a/drivers/power/smb347-charger.c +++ b/drivers/power/smb347-charger.c @@ -1332,4 +1332,3 @@ MODULE_AUTHOR("Bruce E. Robertson "); MODULE_AUTHOR("Mika Westerberg "); MODULE_DESCRIPTION("SMB347 battery charger driver"); MODULE_LICENSE("GPL"); -MODULE_ALIAS("i2c:smb347"); -- cgit v1.2.3 From 56d7df8716b2e7a138af89bffe4923e15cf9bf68 Mon Sep 17 00:00:00 2001 From: Courtney Cavin Date: Thu, 30 Jul 2015 10:53:56 -0700 Subject: power: Add Qualcomm SMBB driver Add the Qualcomm Switch-Mode Battery Charger and Boost driver, found in pm8941. Signed-off-by: Courtney Cavin Signed-off-by: Bjorn Andersson Signed-off-by: Sebastian Reichel --- drivers/power/Kconfig | 12 + drivers/power/Makefile | 1 + drivers/power/qcom_smbb.c | 951 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 964 insertions(+) create mode 100644 drivers/power/qcom_smbb.c (limited to 'drivers') diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 914167e14d2e..2c0520db5025 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -379,6 +379,18 @@ config CHARGER_MAX8998 Say Y to enable support for the battery charger control sysfs and platform data of MAX8998/LP3974 PMICs. +config CHARGER_QCOM_SMBB + tristate "Qualcomm Switch-Mode Battery Charger and Boost" + depends on MFD_SPMI_PMIC || COMPILE_TEST + depends on OF + help + Say Y to include support for the Switch-Mode Battery Charger and + Boost (SMBB) hardware found in Qualcomm PM8941 PMICs. The charger + is an integrated, single-cell lithium-ion battery charger. DT + configuration is required for loading, see the devicetree + documentation for more detail. The base name for this driver is + 'pm8941_charger'. + config CHARGER_BQ2415X tristate "TI BQ2415x battery charger driver" depends on I2C diff --git a/drivers/power/Makefile b/drivers/power/Makefile index fb4413e5dd37..81109baff598 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -58,6 +58,7 @@ obj-$(CONFIG_CHARGER_MAX14577) += max14577_charger.o obj-$(CONFIG_CHARGER_MAX77693) += max77693_charger.o obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o +obj-$(CONFIG_CHARGER_QCOM_SMBB) += qcom_smbb.o obj-$(CONFIG_CHARGER_BQ2415X) += bq2415x_charger.o obj-$(CONFIG_CHARGER_BQ24190) += bq24190_charger.o obj-$(CONFIG_CHARGER_BQ24257) += bq24257_charger.o diff --git a/drivers/power/qcom_smbb.c b/drivers/power/qcom_smbb.c new file mode 100644 index 000000000000..0dabfe8cceb2 --- /dev/null +++ b/drivers/power/qcom_smbb.c @@ -0,0 +1,951 @@ +/* Copyright (c) 2014, Sony Mobile Communications Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This driver is for the multi-block Switch-Mode Battery Charger and Boost + * (SMBB) hardware, found in Qualcomm PM8941 PMICs. The charger is an + * integrated, single-cell lithium-ion battery charger. + * + * Sub-components: + * - Charger core + * - Buck + * - DC charge-path + * - USB charge-path + * - Battery interface + * - Boost (not implemented) + * - Misc + * - HF-Buck + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SMBB_CHG_VMAX 0x040 +#define SMBB_CHG_VSAFE 0x041 +#define SMBB_CHG_CFG 0x043 +#define SMBB_CHG_IMAX 0x044 +#define SMBB_CHG_ISAFE 0x045 +#define SMBB_CHG_VIN_MIN 0x047 +#define SMBB_CHG_CTRL 0x049 +#define CTRL_EN BIT(7) +#define SMBB_CHG_VBAT_WEAK 0x052 +#define SMBB_CHG_IBAT_TERM_CHG 0x05b +#define IBAT_TERM_CHG_IEOC BIT(7) +#define IBAT_TERM_CHG_IEOC_BMS BIT(7) +#define IBAT_TERM_CHG_IEOC_CHG 0 +#define SMBB_CHG_VBAT_DET 0x05d +#define SMBB_CHG_TCHG_MAX_EN 0x060 +#define TCHG_MAX_EN BIT(7) +#define SMBB_CHG_WDOG_TIME 0x062 +#define SMBB_CHG_WDOG_EN 0x065 +#define WDOG_EN BIT(7) + +#define SMBB_BUCK_REG_MODE 0x174 +#define BUCK_REG_MODE BIT(0) +#define BUCK_REG_MODE_VBAT BIT(0) +#define BUCK_REG_MODE_VSYS 0 + +#define SMBB_BAT_PRES_STATUS 0x208 +#define PRES_STATUS_BAT_PRES BIT(7) +#define SMBB_BAT_TEMP_STATUS 0x209 +#define TEMP_STATUS_OK BIT(7) +#define TEMP_STATUS_HOT BIT(6) +#define SMBB_BAT_BTC_CTRL 0x249 +#define BTC_CTRL_COMP_EN BIT(7) +#define BTC_CTRL_COLD_EXT BIT(1) +#define BTC_CTRL_HOT_EXT_N BIT(0) + +#define SMBB_USB_IMAX 0x344 +#define SMBB_USB_ENUM_TIMER_STOP 0x34e +#define ENUM_TIMER_STOP BIT(0) +#define SMBB_USB_SEC_ACCESS 0x3d0 +#define SEC_ACCESS_MAGIC 0xa5 +#define SMBB_USB_REV_BST 0x3ed +#define REV_BST_CHG_GONE BIT(7) + +#define SMBB_DC_IMAX 0x444 + +#define SMBB_MISC_REV2 0x601 +#define SMBB_MISC_BOOT_DONE 0x642 +#define BOOT_DONE BIT(7) + +#define STATUS_USBIN_VALID BIT(0) /* USB connection is valid */ +#define STATUS_DCIN_VALID BIT(1) /* DC connection is valid */ +#define STATUS_BAT_HOT BIT(2) /* Battery temp 1=Hot, 0=Cold */ +#define STATUS_BAT_OK BIT(3) /* Battery temp OK */ +#define STATUS_BAT_PRESENT BIT(4) /* Battery is present */ +#define STATUS_CHG_DONE BIT(5) /* Charge cycle is complete */ +#define STATUS_CHG_TRKL BIT(6) /* Trickle charging */ +#define STATUS_CHG_FAST BIT(7) /* Fast charging */ +#define STATUS_CHG_GONE BIT(8) /* No charger is connected */ + +enum smbb_attr { + ATTR_BAT_ISAFE, + ATTR_BAT_IMAX, + ATTR_USBIN_IMAX, + ATTR_DCIN_IMAX, + ATTR_BAT_VSAFE, + ATTR_BAT_VMAX, + ATTR_BAT_VMIN, + ATTR_CHG_VDET, + ATTR_VIN_MIN, + _ATTR_CNT, +}; + +struct smbb_charger { + unsigned int revision; + unsigned int addr; + struct device *dev; + + bool dc_disabled; + bool jeita_ext_temp; + unsigned long status; + struct mutex statlock; + + unsigned int attr[_ATTR_CNT]; + + struct power_supply *usb_psy; + struct power_supply *dc_psy; + struct power_supply *bat_psy; + struct regmap *regmap; +}; + +static int smbb_vbat_weak_fn(unsigned int index) +{ + return 2100000 + index * 100000; +} + +static int smbb_vin_fn(unsigned int index) +{ + if (index > 42) + return 5600000 + (index - 43) * 200000; + return 3400000 + index * 50000; +} + +static int smbb_vmax_fn(unsigned int index) +{ + return 3240000 + index * 10000; +} + +static int smbb_vbat_det_fn(unsigned int index) +{ + return 3240000 + index * 20000; +} + +static int smbb_imax_fn(unsigned int index) +{ + if (index < 2) + return 100000 + index * 50000; + return index * 100000; +} + +static int smbb_bat_imax_fn(unsigned int index) +{ + return index * 50000; +} + +static unsigned int smbb_hw_lookup(unsigned int val, int (*fn)(unsigned int)) +{ + unsigned int widx; + unsigned int sel; + + for (widx = sel = 0; (*fn)(widx) <= val; ++widx) + sel = widx; + + return sel; +} + +static const struct smbb_charger_attr { + const char *name; + unsigned int reg; + unsigned int safe_reg; + unsigned int max; + unsigned int min; + unsigned int fail_ok; + int (*hw_fn)(unsigned int); +} smbb_charger_attrs[] = { + [ATTR_BAT_ISAFE] = { + .name = "qcom,fast-charge-safe-current", + .reg = SMBB_CHG_ISAFE, + .max = 3000000, + .min = 200000, + .hw_fn = smbb_bat_imax_fn, + .fail_ok = 1, + }, + [ATTR_BAT_IMAX] = { + .name = "qcom,fast-charge-current-limit", + .reg = SMBB_CHG_IMAX, + .safe_reg = SMBB_CHG_ISAFE, + .max = 3000000, + .min = 200000, + .hw_fn = smbb_bat_imax_fn, + }, + [ATTR_DCIN_IMAX] = { + .name = "qcom,dc-current-limit", + .reg = SMBB_DC_IMAX, + .max = 2500000, + .min = 100000, + .hw_fn = smbb_imax_fn, + }, + [ATTR_BAT_VSAFE] = { + .name = "qcom,fast-charge-safe-voltage", + .reg = SMBB_CHG_VSAFE, + .max = 5000000, + .min = 3240000, + .hw_fn = smbb_vmax_fn, + .fail_ok = 1, + }, + [ATTR_BAT_VMAX] = { + .name = "qcom,fast-charge-high-threshold-voltage", + .reg = SMBB_CHG_VMAX, + .safe_reg = SMBB_CHG_VSAFE, + .max = 5000000, + .min = 3240000, + .hw_fn = smbb_vmax_fn, + }, + [ATTR_BAT_VMIN] = { + .name = "qcom,fast-charge-low-threshold-voltage", + .reg = SMBB_CHG_VBAT_WEAK, + .max = 3600000, + .min = 2100000, + .hw_fn = smbb_vbat_weak_fn, + }, + [ATTR_CHG_VDET] = { + .name = "qcom,auto-recharge-threshold-voltage", + .reg = SMBB_CHG_VBAT_DET, + .max = 5000000, + .min = 3240000, + .hw_fn = smbb_vbat_det_fn, + }, + [ATTR_VIN_MIN] = { + .name = "qcom,minimum-input-voltage", + .reg = SMBB_CHG_VIN_MIN, + .max = 9600000, + .min = 4200000, + .hw_fn = smbb_vin_fn, + }, + [ATTR_USBIN_IMAX] = { + .name = "usb-charge-current-limit", + .reg = SMBB_USB_IMAX, + .max = 2500000, + .min = 100000, + .hw_fn = smbb_imax_fn, + }, +}; + +static int smbb_charger_attr_write(struct smbb_charger *chg, + enum smbb_attr which, unsigned int val) +{ + const struct smbb_charger_attr *prop; + unsigned int wval; + unsigned int out; + int rc; + + prop = &smbb_charger_attrs[which]; + + if (val > prop->max || val < prop->min) { + dev_err(chg->dev, "value out of range for %s [%u:%u]\n", + prop->name, prop->min, prop->max); + return -EINVAL; + } + + if (prop->safe_reg) { + rc = regmap_read(chg->regmap, + chg->addr + prop->safe_reg, &wval); + if (rc) { + dev_err(chg->dev, + "unable to read safe value for '%s'\n", + prop->name); + return rc; + } + + wval = prop->hw_fn(wval); + + if (val > wval) { + dev_warn(chg->dev, + "%s above safe value, clamping at %u\n", + prop->name, wval); + val = wval; + } + } + + wval = smbb_hw_lookup(val, prop->hw_fn); + + rc = regmap_write(chg->regmap, chg->addr + prop->reg, wval); + if (rc) { + dev_err(chg->dev, "unable to update %s", prop->name); + return rc; + } + out = prop->hw_fn(wval); + if (out != val) { + dev_warn(chg->dev, + "%s inaccurate, rounded to %u\n", + prop->name, out); + } + + dev_dbg(chg->dev, "%s <= %d\n", prop->name, out); + + chg->attr[which] = out; + + return 0; +} + +static int smbb_charger_attr_read(struct smbb_charger *chg, + enum smbb_attr which) +{ + const struct smbb_charger_attr *prop; + unsigned int val; + int rc; + + prop = &smbb_charger_attrs[which]; + + rc = regmap_read(chg->regmap, chg->addr + prop->reg, &val); + if (rc) { + dev_err(chg->dev, "failed to read %s\n", prop->name); + return rc; + } + val = prop->hw_fn(val); + dev_dbg(chg->dev, "%s => %d\n", prop->name, val); + + chg->attr[which] = val; + + return 0; +} + +static int smbb_charger_attr_parse(struct smbb_charger *chg, + enum smbb_attr which) +{ + const struct smbb_charger_attr *prop; + unsigned int val; + int rc; + + prop = &smbb_charger_attrs[which]; + + rc = of_property_read_u32(chg->dev->of_node, prop->name, &val); + if (rc == 0) { + rc = smbb_charger_attr_write(chg, which, val); + if (!rc || !prop->fail_ok) + return rc; + } + return smbb_charger_attr_read(chg, which); +} + +static void smbb_set_line_flag(struct smbb_charger *chg, int irq, int flag) +{ + bool state; + int ret; + + ret = irq_get_irqchip_state(irq, IRQCHIP_STATE_LINE_LEVEL, &state); + if (state < 0) { + dev_err(chg->dev, "failed to read irq line\n"); + return; + } + + mutex_lock(&chg->statlock); + if (state) + chg->status |= flag; + else + chg->status &= ~flag; + mutex_unlock(&chg->statlock); + + dev_dbg(chg->dev, "status = %03lx\n", chg->status); +} + +static irqreturn_t smbb_usb_valid_handler(int irq, void *_data) +{ + struct smbb_charger *chg = _data; + + smbb_set_line_flag(chg, irq, STATUS_USBIN_VALID); + power_supply_changed(chg->usb_psy); + + return IRQ_HANDLED; +} + +static irqreturn_t smbb_dc_valid_handler(int irq, void *_data) +{ + struct smbb_charger *chg = _data; + + smbb_set_line_flag(chg, irq, STATUS_DCIN_VALID); + if (!chg->dc_disabled) + power_supply_changed(chg->dc_psy); + + return IRQ_HANDLED; +} + +static irqreturn_t smbb_bat_temp_handler(int irq, void *_data) +{ + struct smbb_charger *chg = _data; + unsigned int val; + int rc; + + rc = regmap_read(chg->regmap, chg->addr + SMBB_BAT_TEMP_STATUS, &val); + if (rc) + return IRQ_HANDLED; + + mutex_lock(&chg->statlock); + if (val & TEMP_STATUS_OK) { + chg->status |= STATUS_BAT_OK; + } else { + chg->status &= ~STATUS_BAT_OK; + if (val & TEMP_STATUS_HOT) + chg->status |= STATUS_BAT_HOT; + } + mutex_unlock(&chg->statlock); + + power_supply_changed(chg->bat_psy); + return IRQ_HANDLED; +} + +static irqreturn_t smbb_bat_present_handler(int irq, void *_data) +{ + struct smbb_charger *chg = _data; + + smbb_set_line_flag(chg, irq, STATUS_BAT_PRESENT); + power_supply_changed(chg->bat_psy); + + return IRQ_HANDLED; +} + +static irqreturn_t smbb_chg_done_handler(int irq, void *_data) +{ + struct smbb_charger *chg = _data; + + smbb_set_line_flag(chg, irq, STATUS_CHG_DONE); + power_supply_changed(chg->bat_psy); + + return IRQ_HANDLED; +} + +static irqreturn_t smbb_chg_gone_handler(int irq, void *_data) +{ + struct smbb_charger *chg = _data; + + smbb_set_line_flag(chg, irq, STATUS_CHG_GONE); + power_supply_changed(chg->bat_psy); + power_supply_changed(chg->usb_psy); + if (!chg->dc_disabled) + power_supply_changed(chg->dc_psy); + + return IRQ_HANDLED; +} + +static irqreturn_t smbb_chg_fast_handler(int irq, void *_data) +{ + struct smbb_charger *chg = _data; + + smbb_set_line_flag(chg, irq, STATUS_CHG_FAST); + power_supply_changed(chg->bat_psy); + + return IRQ_HANDLED; +} + +static irqreturn_t smbb_chg_trkl_handler(int irq, void *_data) +{ + struct smbb_charger *chg = _data; + + smbb_set_line_flag(chg, irq, STATUS_CHG_TRKL); + power_supply_changed(chg->bat_psy); + + return IRQ_HANDLED; +} + +static const struct smbb_irq { + const char *name; + irqreturn_t (*handler)(int, void *); +} smbb_charger_irqs[] = { + { "chg-done", smbb_chg_done_handler }, + { "chg-fast", smbb_chg_fast_handler }, + { "chg-trkl", smbb_chg_trkl_handler }, + { "bat-temp-ok", smbb_bat_temp_handler }, + { "bat-present", smbb_bat_present_handler }, + { "chg-gone", smbb_chg_gone_handler }, + { "usb-valid", smbb_usb_valid_handler }, + { "dc-valid", smbb_dc_valid_handler }, +}; + +static int smbb_usbin_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct smbb_charger *chg = power_supply_get_drvdata(psy); + int rc = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + mutex_lock(&chg->statlock); + val->intval = !(chg->status & STATUS_CHG_GONE) && + (chg->status & STATUS_USBIN_VALID); + mutex_unlock(&chg->statlock); + break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + val->intval = chg->attr[ATTR_USBIN_IMAX]; + break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX: + val->intval = 2500000; + break; + default: + rc = -EINVAL; + break; + } + + return rc; +} + +static int smbb_usbin_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct smbb_charger *chg = power_supply_get_drvdata(psy); + int rc; + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + rc = smbb_charger_attr_write(chg, ATTR_USBIN_IMAX, + val->intval); + break; + default: + rc = -EINVAL; + break; + } + + return rc; +} + +static int smbb_dcin_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct smbb_charger *chg = power_supply_get_drvdata(psy); + int rc = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + mutex_lock(&chg->statlock); + val->intval = !(chg->status & STATUS_CHG_GONE) && + (chg->status & STATUS_DCIN_VALID); + mutex_unlock(&chg->statlock); + break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + val->intval = chg->attr[ATTR_DCIN_IMAX]; + break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX: + val->intval = 2500000; + break; + default: + rc = -EINVAL; + break; + } + + return rc; +} + +static int smbb_dcin_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct smbb_charger *chg = power_supply_get_drvdata(psy); + int rc; + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + rc = smbb_charger_attr_write(chg, ATTR_DCIN_IMAX, + val->intval); + break; + default: + rc = -EINVAL; + break; + } + + return rc; +} + +static int smbb_charger_writable_property(struct power_supply *psy, + enum power_supply_property psp) +{ + return psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT; +} + +static int smbb_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct smbb_charger *chg = power_supply_get_drvdata(psy); + unsigned long status; + int rc = 0; + + mutex_lock(&chg->statlock); + status = chg->status; + mutex_unlock(&chg->statlock); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (status & STATUS_CHG_GONE) + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + else if (!(status & (STATUS_DCIN_VALID | STATUS_USBIN_VALID))) + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + else if (status & STATUS_CHG_DONE) + val->intval = POWER_SUPPLY_STATUS_FULL; + else if (!(status & STATUS_BAT_OK)) + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + else if (status & (STATUS_CHG_FAST | STATUS_CHG_TRKL)) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else /* everything is ok for charging, but we are not... */ + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case POWER_SUPPLY_PROP_HEALTH: + if (status & STATUS_BAT_OK) + val->intval = POWER_SUPPLY_HEALTH_GOOD; + else if (status & STATUS_BAT_HOT) + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else + val->intval = POWER_SUPPLY_HEALTH_COLD; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + if (status & STATUS_CHG_FAST) + val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; + else if (status & STATUS_CHG_TRKL) + val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + else + val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = !!(status & STATUS_BAT_PRESENT); + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + val->intval = chg->attr[ATTR_BAT_IMAX]; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + val->intval = chg->attr[ATTR_BAT_VMAX]; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + /* this charger is a single-cell lithium-ion battery charger + * only. If you hook up some other technology, there will be + * fireworks. + */ + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = 3000000; /* single-cell li-ion low end */ + break; + default: + rc = -EINVAL; + break; + } + + return rc; +} + +static int smbb_battery_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct smbb_charger *chg = power_supply_get_drvdata(psy); + int rc; + + switch (psp) { + case POWER_SUPPLY_PROP_CURRENT_MAX: + rc = smbb_charger_attr_write(chg, ATTR_BAT_IMAX, val->intval); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + rc = smbb_charger_attr_write(chg, ATTR_BAT_VMAX, val->intval); + break; + default: + rc = -EINVAL; + break; + } + + return rc; +} + +static int smbb_battery_writable_property(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CURRENT_MAX: + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + return 1; + default: + return 0; + } +} + +static enum power_supply_property smbb_charger_properties[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, + POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX, +}; + +static enum power_supply_property smbb_battery_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_TECHNOLOGY, +}; + +static const struct reg_off_mask_default { + unsigned int offset; + unsigned int mask; + unsigned int value; + unsigned int rev_mask; +} smbb_charger_setup[] = { + /* The bootloader is supposed to set this... make sure anyway. */ + { SMBB_MISC_BOOT_DONE, BOOT_DONE, BOOT_DONE }, + + /* Disable software timer */ + { SMBB_CHG_TCHG_MAX_EN, TCHG_MAX_EN, 0 }, + + /* Clear and disable watchdog */ + { SMBB_CHG_WDOG_TIME, 0xff, 160 }, + { SMBB_CHG_WDOG_EN, WDOG_EN, 0 }, + + /* Use charger based EoC detection */ + { SMBB_CHG_IBAT_TERM_CHG, IBAT_TERM_CHG_IEOC, IBAT_TERM_CHG_IEOC_CHG }, + + /* Disable GSM PA load adjustment. + * The PA signal is incorrectly connected on v2. + */ + { SMBB_CHG_CFG, 0xff, 0x00, BIT(3) }, + + /* Use VBAT (not VSYS) to compensate for IR drop during fast charging */ + { SMBB_BUCK_REG_MODE, BUCK_REG_MODE, BUCK_REG_MODE_VBAT }, + + /* Enable battery temperature comparators */ + { SMBB_BAT_BTC_CTRL, BTC_CTRL_COMP_EN, BTC_CTRL_COMP_EN }, + + /* Stop USB enumeration timer */ + { SMBB_USB_ENUM_TIMER_STOP, ENUM_TIMER_STOP, ENUM_TIMER_STOP }, + +#if 0 /* FIXME supposedly only to disable hardware ARB termination */ + { SMBB_USB_SEC_ACCESS, SEC_ACCESS_MAGIC }, + { SMBB_USB_REV_BST, 0xff, REV_BST_CHG_GONE }, +#endif + + /* Stop USB enumeration timer, again */ + { SMBB_USB_ENUM_TIMER_STOP, ENUM_TIMER_STOP, ENUM_TIMER_STOP }, + + /* Enable charging */ + { SMBB_CHG_CTRL, CTRL_EN, CTRL_EN }, +}; + +static char *smbb_bif[] = { "smbb-bif" }; + +static const struct power_supply_desc bat_psy_desc = { + .name = "smbb-bif", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = smbb_battery_properties, + .num_properties = ARRAY_SIZE(smbb_battery_properties), + .get_property = smbb_battery_get_property, + .set_property = smbb_battery_set_property, + .property_is_writeable = smbb_battery_writable_property, +}; + +static const struct power_supply_desc usb_psy_desc = { + .name = "smbb-usbin", + .type = POWER_SUPPLY_TYPE_USB, + .properties = smbb_charger_properties, + .num_properties = ARRAY_SIZE(smbb_charger_properties), + .get_property = smbb_usbin_get_property, + .set_property = smbb_usbin_set_property, + .property_is_writeable = smbb_charger_writable_property, +}; + +static const struct power_supply_desc dc_psy_desc = { + .name = "smbb-dcin", + .type = POWER_SUPPLY_TYPE_MAINS, + .properties = smbb_charger_properties, + .num_properties = ARRAY_SIZE(smbb_charger_properties), + .get_property = smbb_dcin_get_property, + .set_property = smbb_dcin_set_property, + .property_is_writeable = smbb_charger_writable_property, +}; + +static int smbb_charger_probe(struct platform_device *pdev) +{ + struct power_supply_config bat_cfg = {}; + struct power_supply_config usb_cfg = {}; + struct power_supply_config dc_cfg = {}; + struct smbb_charger *chg; + int rc, i; + + chg = devm_kzalloc(&pdev->dev, sizeof(*chg), GFP_KERNEL); + if (!chg) + return -ENOMEM; + + chg->dev = &pdev->dev; + mutex_init(&chg->statlock); + + chg->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!chg->regmap) { + dev_err(&pdev->dev, "failed to locate regmap\n"); + return -ENODEV; + } + + rc = of_property_read_u32(pdev->dev.of_node, "reg", &chg->addr); + if (rc) { + dev_err(&pdev->dev, "missing or invalid 'reg' property\n"); + return rc; + } + + rc = regmap_read(chg->regmap, chg->addr + SMBB_MISC_REV2, &chg->revision); + if (rc) { + dev_err(&pdev->dev, "unable to read revision\n"); + return rc; + } + + chg->revision += 1; + if (chg->revision != 2 && chg->revision != 3) { + dev_err(&pdev->dev, "v1 hardware not supported\n"); + return -ENODEV; + } + dev_info(&pdev->dev, "Initializing SMBB rev %u", chg->revision); + + chg->dc_disabled = of_property_read_bool(pdev->dev.of_node, "qcom,disable-dc"); + + for (i = 0; i < _ATTR_CNT; ++i) { + rc = smbb_charger_attr_parse(chg, i); + if (rc) { + dev_err(&pdev->dev, "failed to parse/apply settings\n"); + return rc; + } + } + + bat_cfg.drv_data = chg; + bat_cfg.of_node = pdev->dev.of_node; + chg->bat_psy = devm_power_supply_register(&pdev->dev, + &bat_psy_desc, + &bat_cfg); + if (IS_ERR(chg->bat_psy)) { + dev_err(&pdev->dev, "failed to register battery\n"); + return PTR_ERR(chg->bat_psy); + } + + usb_cfg.drv_data = chg; + usb_cfg.supplied_to = smbb_bif; + usb_cfg.num_supplicants = ARRAY_SIZE(smbb_bif); + chg->usb_psy = devm_power_supply_register(&pdev->dev, + &usb_psy_desc, + &usb_cfg); + if (IS_ERR(chg->usb_psy)) { + dev_err(&pdev->dev, "failed to register USB power supply\n"); + return PTR_ERR(chg->usb_psy); + } + + if (!chg->dc_disabled) { + dc_cfg.drv_data = chg; + dc_cfg.supplied_to = smbb_bif; + dc_cfg.num_supplicants = ARRAY_SIZE(smbb_bif); + chg->dc_psy = devm_power_supply_register(&pdev->dev, + &dc_psy_desc, + &dc_cfg); + if (IS_ERR(chg->dc_psy)) { + dev_err(&pdev->dev, "failed to register DC power supply\n"); + return PTR_ERR(chg->dc_psy); + } + } + + for (i = 0; i < ARRAY_SIZE(smbb_charger_irqs); ++i) { + int irq; + + irq = platform_get_irq_byname(pdev, smbb_charger_irqs[i].name); + if (irq < 0) { + dev_err(&pdev->dev, "failed to get irq '%s'\n", + smbb_charger_irqs[i].name); + return irq; + } + + smbb_charger_irqs[i].handler(irq, chg); + + rc = devm_request_threaded_irq(&pdev->dev, irq, NULL, + smbb_charger_irqs[i].handler, IRQF_ONESHOT, + smbb_charger_irqs[i].name, chg); + if (rc) { + dev_err(&pdev->dev, "failed to request irq '%s'\n", + smbb_charger_irqs[i].name); + return rc; + } + } + + chg->jeita_ext_temp = of_property_read_bool(pdev->dev.of_node, + "qcom,jeita-extended-temp-range"); + + /* Set temperature range to [35%:70%] or [25%:80%] accordingly */ + rc = regmap_update_bits(chg->regmap, chg->addr + SMBB_BAT_BTC_CTRL, + BTC_CTRL_COLD_EXT | BTC_CTRL_HOT_EXT_N, + chg->jeita_ext_temp ? + BTC_CTRL_COLD_EXT : + BTC_CTRL_HOT_EXT_N); + if (rc) { + dev_err(&pdev->dev, + "unable to set %s temperature range\n", + chg->jeita_ext_temp ? "JEITA extended" : "normal"); + return rc; + } + + for (i = 0; i < ARRAY_SIZE(smbb_charger_setup); ++i) { + const struct reg_off_mask_default *r = &smbb_charger_setup[i]; + + if (r->rev_mask & BIT(chg->revision)) + continue; + + rc = regmap_update_bits(chg->regmap, chg->addr + r->offset, + r->mask, r->value); + if (rc) { + dev_err(&pdev->dev, + "unable to initializing charging, bailing\n"); + return rc; + } + } + + platform_set_drvdata(pdev, chg); + + return 0; +} + +static int smbb_charger_remove(struct platform_device *pdev) +{ + struct smbb_charger *chg; + + chg = platform_get_drvdata(pdev); + + regmap_update_bits(chg->regmap, chg->addr + SMBB_CHG_CTRL, CTRL_EN, 0); + + return 0; +} + +static const struct of_device_id smbb_charger_id_table[] = { + { .compatible = "qcom,pm8941-charger" }, + { } +}; +MODULE_DEVICE_TABLE(of, smbb_charger_id_table); + +static struct platform_driver smbb_charger_driver = { + .probe = smbb_charger_probe, + .remove = smbb_charger_remove, + .driver = { + .name = "qcom-smbb", + .of_match_table = smbb_charger_id_table, + }, +}; +module_platform_driver(smbb_charger_driver); + +MODULE_DESCRIPTION("Qualcomm Switch-Mode Battery Charger and Boost driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 2a9123f185ca07dcd76f36242ee03bed3fe2ab5c Mon Sep 17 00:00:00 2001 From: Vaishali Thakkar Date: Thu, 17 Sep 2015 18:03:40 +0530 Subject: 88pm860x_battery: Convert to using managed resources Use managed resource functions devm_request_threaded_irq and devm_power_supply_register to simplify error handling. To be compatible with the change, various gotos are replaced with direct returns and unneeded labels are dropped. Also, remove pm860x_battery_remove as it is now redundant. Signed-off-by: Vaishali Thakkar Signed-off-by: Sebastian Reichel --- drivers/power/88pm860x_battery.c | 37 +++++++++++-------------------------- 1 file changed, 11 insertions(+), 26 deletions(-) (limited to 'drivers') diff --git a/drivers/power/88pm860x_battery.c b/drivers/power/88pm860x_battery.c index d49579b227ec..63c57dc82ac1 100644 --- a/drivers/power/88pm860x_battery.c +++ b/drivers/power/88pm860x_battery.c @@ -954,46 +954,32 @@ static int pm860x_battery_probe(struct platform_device *pdev) else info->resistor = 300; /* set default internal resistor */ - info->battery = power_supply_register(&pdev->dev, &pm860x_battery_desc, - NULL); + info->battery = devm_power_supply_register(&pdev->dev, + &pm860x_battery_desc, + NULL); if (IS_ERR(info->battery)) return PTR_ERR(info->battery); info->battery->dev.parent = &pdev->dev; - ret = request_threaded_irq(info->irq_cc, NULL, - pm860x_coulomb_handler, IRQF_ONESHOT, - "coulomb", info); + ret = devm_request_threaded_irq(chip->dev, info->irq_cc, NULL, + pm860x_coulomb_handler, IRQF_ONESHOT, + "coulomb", info); if (ret < 0) { dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n", info->irq_cc, ret); - goto out_reg; + return ret; } - ret = request_threaded_irq(info->irq_batt, NULL, pm860x_batt_handler, - IRQF_ONESHOT, "battery", info); + ret = devm_request_threaded_irq(chip->dev, info->irq_batt, NULL, + pm860x_batt_handler, + IRQF_ONESHOT, "battery", info); if (ret < 0) { dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n", info->irq_batt, ret); - goto out_coulomb; + return ret; } - return 0; - -out_coulomb: - free_irq(info->irq_cc, info); -out_reg: - power_supply_unregister(info->battery); - return ret; -} - -static int pm860x_battery_remove(struct platform_device *pdev) -{ - struct pm860x_battery_info *info = platform_get_drvdata(pdev); - - free_irq(info->irq_batt, info); - free_irq(info->irq_cc, info); - power_supply_unregister(info->battery); return 0; } @@ -1028,7 +1014,6 @@ static struct platform_driver pm860x_battery_driver = { .pm = &pm860x_battery_pm_ops, }, .probe = pm860x_battery_probe, - .remove = pm860x_battery_remove, }; module_platform_driver(pm860x_battery_driver); -- cgit v1.2.3 From eacd8d09db7f4504eea6b5fdc55a8fe5eaf62bed Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Tue, 11 Aug 2015 11:12:46 +0200 Subject: power/reset: at91-reset: remove useless at91_reset_platform_probe() Since all the at91 platforms are now DT only, at91_reset_platform_probe() is now useless, remove it. Signed-off-by: Alexandre Belloni Signed-off-by: Sebastian Reichel --- drivers/power/reset/at91-reset.c | 47 +++------------------------------------- 1 file changed, 3 insertions(+), 44 deletions(-) (limited to 'drivers') diff --git a/drivers/power/reset/at91-reset.c b/drivers/power/reset/at91-reset.c index c378d4ec826f..16e12bd507a1 100644 --- a/drivers/power/reset/at91-reset.c +++ b/drivers/power/reset/at91-reset.c @@ -178,11 +178,11 @@ static struct notifier_block at91_restart_nb = { .priority = 192, }; -static int at91_reset_of_probe(struct platform_device *pdev) +static int at91_reset_probe(struct platform_device *pdev) { const struct of_device_id *match; struct device_node *np; - int idx = 0; + int ret, idx = 0; at91_rstc_base = of_iomap(pdev->dev.of_node, 0); if (!at91_rstc_base) { @@ -204,49 +204,8 @@ static int at91_reset_of_probe(struct platform_device *pdev) match = of_match_node(at91_reset_of_match, pdev->dev.of_node); at91_restart_nb.notifier_call = match->data; - return register_restart_handler(&at91_restart_nb); -} - -static int at91_reset_platform_probe(struct platform_device *pdev) -{ - const struct platform_device_id *match; - struct resource *res; - int idx = 0; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - at91_rstc_base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(at91_rstc_base)) { - dev_err(&pdev->dev, "Could not map reset controller address\n"); - return PTR_ERR(at91_rstc_base); - } - - for (idx = 0; idx < 2; idx++) { - res = platform_get_resource(pdev, IORESOURCE_MEM, idx + 1 ); - at91_ramc_base[idx] = devm_ioremap(&pdev->dev, res->start, - resource_size(res)); - if (!at91_ramc_base[idx]) { - dev_err(&pdev->dev, "Could not map ram controller address\n"); - return -ENOMEM; - } - } - - match = platform_get_device_id(pdev); - at91_restart_nb.notifier_call = - (int (*)(struct notifier_block *, - unsigned long, void *)) match->driver_data; - - return register_restart_handler(&at91_restart_nb); -} - -static int at91_reset_probe(struct platform_device *pdev) -{ - int ret; - - if (pdev->dev.of_node) - ret = at91_reset_of_probe(pdev); - else - ret = at91_reset_platform_probe(pdev); + ret = register_restart_handler(&at91_restart_nb); if (ret) return ret; -- cgit v1.2.3 From 6e64180a7c0219d769e05b6dbe5af4a073c1eb92 Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Tue, 11 Aug 2015 11:12:47 +0200 Subject: power/reset: at91-reset: allow compiling as a module It was not possible to compile at91-reset as a module. Implement .remove() to allow it. Also switch to module_platform_driver_probe() as it is not hotpluggable. Signed-off-by: Alexandre Belloni Signed-off-by: Sebastian Reichel --- drivers/power/reset/Kconfig | 2 +- drivers/power/reset/at91-reset.c | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig index 5a0189bf19bb..5cec1d9fdd47 100644 --- a/drivers/power/reset/Kconfig +++ b/drivers/power/reset/Kconfig @@ -23,7 +23,7 @@ config POWER_RESET_AT91_POWEROFF SoCs config POWER_RESET_AT91_RESET - bool "Atmel AT91 reset driver" + tristate "Atmel AT91 reset driver" depends on ARCH_AT91 default SOC_AT91SAM9 || SOC_SAMA5 help diff --git a/drivers/power/reset/at91-reset.c b/drivers/power/reset/at91-reset.c index 16e12bd507a1..6374f5c9ee91 100644 --- a/drivers/power/reset/at91-reset.c +++ b/drivers/power/reset/at91-reset.c @@ -178,7 +178,7 @@ static struct notifier_block at91_restart_nb = { .priority = 192, }; -static int at91_reset_probe(struct platform_device *pdev) +static int __init at91_reset_probe(struct platform_device *pdev) { const struct of_device_id *match; struct device_node *np; @@ -214,6 +214,13 @@ static int at91_reset_probe(struct platform_device *pdev) return 0; } +static int __exit at91_reset_remove(struct platform_device *pdev) +{ + unregister_restart_handler(&at91_restart_nb); + + return 0; +} + static const struct platform_device_id at91_reset_plat_match[] = { { "at91-sam9260-reset", (unsigned long)at91sam9260_restart }, { "at91-sam9g45-reset", (unsigned long)at91sam9g45_restart }, @@ -221,11 +228,15 @@ static const struct platform_device_id at91_reset_plat_match[] = { }; static struct platform_driver at91_reset_driver = { - .probe = at91_reset_probe, + .remove = __exit_p(at91_reset_remove), .driver = { .name = "at91-reset", .of_match_table = at91_reset_of_match, }, .id_table = at91_reset_plat_match, }; -module_platform_driver(at91_reset_driver); +module_platform_driver_probe(at91_reset_driver, at91_reset_probe); + +MODULE_AUTHOR("Atmel Corporation"); +MODULE_DESCRIPTION("Reset driver for Atmel SoCs"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 2b2c6148fe851042cefbf272146b77634a4da39d Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Tue, 11 Aug 2015 11:12:48 +0200 Subject: power/reset: at91-reset: get and use slow clock Commit dca1a4b5ff6e ("clk: at91: keep slow clk enabled to prevent system hang") added a workaround for the slow clock as it is not properly handled by its users. Get and use the slow clock as it is necessary for the at91 reset controller. Signed-off-by: Alexandre Belloni Signed-off-by: Sebastian Reichel --- drivers/power/reset/at91-reset.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/power/reset/at91-reset.c b/drivers/power/reset/at91-reset.c index 6374f5c9ee91..3d9c43a66806 100644 --- a/drivers/power/reset/at91-reset.c +++ b/drivers/power/reset/at91-reset.c @@ -11,6 +11,7 @@ * warranty of any kind, whether express or implied. */ +#include #include #include #include @@ -46,6 +47,7 @@ enum reset_type { }; static void __iomem *at91_ramc_base[2], *at91_rstc_base; +static struct clk *sclk; /* * unless the SDRAM is cleanly shutdown before we hit the @@ -205,9 +207,21 @@ static int __init at91_reset_probe(struct platform_device *pdev) match = of_match_node(at91_reset_of_match, pdev->dev.of_node); at91_restart_nb.notifier_call = match->data; + sclk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(sclk)) + return PTR_ERR(sclk); + + ret = clk_prepare_enable(sclk); + if (ret) { + dev_err(&pdev->dev, "Could not enable slow clock\n"); + return ret; + } + ret = register_restart_handler(&at91_restart_nb); - if (ret) + if (ret) { + clk_disable_unprepare(sclk); return ret; + } at91_reset_status(pdev); @@ -217,6 +231,7 @@ static int __init at91_reset_probe(struct platform_device *pdev) static int __exit at91_reset_remove(struct platform_device *pdev) { unregister_restart_handler(&at91_restart_nb); + clk_disable_unprepare(sclk); return 0; } -- cgit v1.2.3 From 6dd1ad1f236e7e683e92efd6b5b3953615900701 Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Tue, 11 Aug 2015 11:12:49 +0200 Subject: power/reset: at91-poweroff: allow compiling as a module It was not possible to compile at91-poweroff as a module. Implement .remove() to allow it. Also switch to module_platform_driver_probe() as it is not hotpluggable. Signed-off-by: Alexandre Belloni Signed-off-by: Sebastian Reichel --- drivers/power/reset/Kconfig | 2 +- drivers/power/reset/at91-poweroff.c | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig index 5cec1d9fdd47..1131cf75acc6 100644 --- a/drivers/power/reset/Kconfig +++ b/drivers/power/reset/Kconfig @@ -15,7 +15,7 @@ config POWER_RESET_AS3722 This driver supports turning off board via a ams AS3722 power-off. config POWER_RESET_AT91_POWEROFF - bool "Atmel AT91 poweroff driver" + tristate "Atmel AT91 poweroff driver" depends on ARCH_AT91 default SOC_AT91SAM9 || SOC_SAMA5 help diff --git a/drivers/power/reset/at91-poweroff.c b/drivers/power/reset/at91-poweroff.c index 9847cfb7e23d..3e698d916c4f 100644 --- a/drivers/power/reset/at91-poweroff.c +++ b/drivers/power/reset/at91-poweroff.c @@ -119,7 +119,7 @@ static void at91_poweroff_dt_set_wakeup_mode(struct platform_device *pdev) writel(wakeup_mode | mode, at91_shdwc_base + AT91_SHDW_MR); } -static int at91_poweroff_probe(struct platform_device *pdev) +static int __init at91_poweroff_probe(struct platform_device *pdev) { struct resource *res; @@ -140,6 +140,14 @@ static int at91_poweroff_probe(struct platform_device *pdev) return 0; } +static int __exit at91_poweroff_remove(struct platform_device *pdev) +{ + if (pm_power_off == at91_poweroff) + pm_power_off = NULL; + + return 0; +} + static const struct of_device_id at91_poweroff_of_match[] = { { .compatible = "atmel,at91sam9260-shdwc", }, { .compatible = "atmel,at91sam9rl-shdwc", }, @@ -148,10 +156,14 @@ static const struct of_device_id at91_poweroff_of_match[] = { }; static struct platform_driver at91_poweroff_driver = { - .probe = at91_poweroff_probe, + .remove = __exit_p(at91_poweroff_remove), .driver = { .name = "at91-poweroff", .of_match_table = at91_poweroff_of_match, }, }; -module_platform_driver(at91_poweroff_driver); +module_platform_driver_probe(at91_poweroff_driver, at91_poweroff_probe); + +MODULE_AUTHOR("Atmel Corporation"); +MODULE_DESCRIPTION("Shutdown driver for Atmel SoCs"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 064380a12e12412f25db049a503126228babbae5 Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Tue, 11 Aug 2015 11:12:50 +0200 Subject: power/reset: at91-poweroff: get and use slow clock Commit dca1a4b5ff6e ("clk: at91: keep slow clk enabled to prevent system hang") added a workaround for the slow clock as it is not properly handled by its users. Get and use the slow clock as it is necessary for the at91 shutdown controller. Signed-off-by: Alexandre Belloni Signed-off-by: Sebastian Reichel --- drivers/power/reset/at91-poweroff.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'drivers') diff --git a/drivers/power/reset/at91-poweroff.c b/drivers/power/reset/at91-poweroff.c index 3e698d916c4f..e9e24df35f26 100644 --- a/drivers/power/reset/at91-poweroff.c +++ b/drivers/power/reset/at91-poweroff.c @@ -10,6 +10,7 @@ * warranty of any kind, whether express or implied. */ +#include #include #include #include @@ -48,6 +49,7 @@ static const char *shdwc_wakeup_modes[] = { }; static void __iomem *at91_shdwc_base; +static struct clk *sclk; static void __init at91_wakeup_status(void) { @@ -122,6 +124,7 @@ static void at91_poweroff_dt_set_wakeup_mode(struct platform_device *pdev) static int __init at91_poweroff_probe(struct platform_device *pdev) { struct resource *res; + int ret; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); at91_shdwc_base = devm_ioremap_resource(&pdev->dev, res); @@ -130,6 +133,16 @@ static int __init at91_poweroff_probe(struct platform_device *pdev) return PTR_ERR(at91_shdwc_base); } + sclk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(sclk)) + return PTR_ERR(sclk); + + ret = clk_prepare_enable(sclk); + if (ret) { + dev_err(&pdev->dev, "Could not enable slow clock\n"); + return ret; + } + at91_wakeup_status(); if (pdev->dev.of_node) @@ -145,6 +158,8 @@ static int __exit at91_poweroff_remove(struct platform_device *pdev) if (pm_power_off == at91_poweroff) pm_power_off = NULL; + clk_disable_unprepare(sclk); + return 0; } -- cgit v1.2.3 From bc312cbdfa4c4eaa074bae29adbff231c743619a Mon Sep 17 00:00:00 2001 From: Nicolas Ferre Date: Thu, 10 Sep 2015 12:35:13 +0200 Subject: power: reset: at91-reset/trivial: driver applies to SAMA5 family as well This diver doesn't applies only on SAM9 SoC families but on SAMA5 families as well. Signed-off-by: Nicolas Ferre Signed-off-by: Sebastian Reichel --- drivers/power/reset/at91-reset.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/power/reset/at91-reset.c b/drivers/power/reset/at91-reset.c index 3d9c43a66806..3f6b5dd7c3d4 100644 --- a/drivers/power/reset/at91-reset.c +++ b/drivers/power/reset/at91-reset.c @@ -1,5 +1,5 @@ /* - * Atmel AT91 SAM9 SoCs reset code + * Atmel AT91 SAM9 & SAMA5 SoCs reset code * * Copyright (C) 2007 Atmel Corporation. * Copyright (C) BitBox Ltd 2010 -- cgit v1.2.3 From 50bddb99c10d6ed861ce384bfe95c78eab5e3ca7 Mon Sep 17 00:00:00 2001 From: Vaishali Thakkar Date: Thu, 17 Sep 2015 18:27:52 +0530 Subject: power: max17042_battery: Convert to using managed resources Use managed resource functions devm_request_threaded_irq and devm_power_supply_register to simplify error handling. Also, remove max17042_remove as it is now redundant. Signed-off-by: Vaishali Thakkar Signed-off-by: Sebastian Reichel --- drivers/power/max17042_battery.c | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) (limited to 'drivers') diff --git a/drivers/power/max17042_battery.c b/drivers/power/max17042_battery.c index e89255764745..9c65f134d447 100644 --- a/drivers/power/max17042_battery.c +++ b/drivers/power/max17042_battery.c @@ -909,18 +909,21 @@ static int max17042_probe(struct i2c_client *client, regmap_write(chip->regmap, MAX17042_LearnCFG, 0x0007); } - chip->battery = power_supply_register(&client->dev, max17042_desc, - &psy_cfg); + chip->battery = devm_power_supply_register(&client->dev, max17042_desc, + &psy_cfg); if (IS_ERR(chip->battery)) { dev_err(&client->dev, "failed: power supply register\n"); return PTR_ERR(chip->battery); } if (client->irq) { - ret = request_threaded_irq(client->irq, NULL, - max17042_thread_handler, - IRQF_TRIGGER_FALLING | IRQF_ONESHOT, - chip->battery->desc->name, chip); + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, + max17042_thread_handler, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + chip->battery->desc->name, + chip); if (!ret) { regmap_update_bits(chip->regmap, MAX17042_CONFIG, CONFIG_ALRT_BIT_ENBL, @@ -944,16 +947,6 @@ static int max17042_probe(struct i2c_client *client, return 0; } -static int max17042_remove(struct i2c_client *client) -{ - struct max17042_chip *chip = i2c_get_clientdata(client); - - if (client->irq) - free_irq(client->irq, chip); - power_supply_unregister(chip->battery); - return 0; -} - #ifdef CONFIG_PM_SLEEP static int max17042_suspend(struct device *dev) { @@ -1014,7 +1007,6 @@ static struct i2c_driver max17042_i2c_driver = { .pm = &max17042_pm_ops, }, .probe = max17042_probe, - .remove = max17042_remove, .id_table = max17042_id, }; module_i2c_driver(max17042_i2c_driver); -- cgit v1.2.3 From 0df2deeab462bde009c7f1a29c6d2d395bac39d8 Mon Sep 17 00:00:00 2001 From: Vaishali Thakkar Date: Thu, 17 Sep 2015 18:55:00 +0530 Subject: max8903_charger: Convert to using managed resources Use managed resource functions devm_request_threaded_irq and devm_power_supply_register to simplify error handling. To be compatible with the change, various gotos are replaced with direct returns and unneeded labels are dropped. Also, remove max8903_remove as it is now redundant. Signed-off-by: Vaishali Thakkar Signed-off-by: Sebastian Reichel --- drivers/power/max8903_charger.c | 93 +++++++++++++---------------------------- 1 file changed, 28 insertions(+), 65 deletions(-) (limited to 'drivers') diff --git a/drivers/power/max8903_charger.c b/drivers/power/max8903_charger.c index bf2b4b3a7cae..6d39d52040d4 100644 --- a/drivers/power/max8903_charger.c +++ b/drivers/power/max8903_charger.c @@ -201,8 +201,7 @@ static int max8903_probe(struct platform_device *pdev) if (pdata->dc_valid == false && pdata->usb_valid == false) { dev_err(dev, "No valid power sources.\n"); - ret = -EINVAL; - goto err; + return -EINVAL; } if (pdata->dc_valid) { @@ -216,8 +215,7 @@ static int max8903_probe(struct platform_device *pdev) } else { dev_err(dev, "When DC is wired, DOK and DCM should" " be wired as well.\n"); - ret = -EINVAL; - goto err; + return -EINVAL; } } else { if (pdata->dcm) { @@ -225,8 +223,7 @@ static int max8903_probe(struct platform_device *pdev) gpio_set_value(pdata->dcm, 0); else { dev_err(dev, "Invalid pin: dcm.\n"); - ret = -EINVAL; - goto err; + return -EINVAL; } } } @@ -238,8 +235,7 @@ static int max8903_probe(struct platform_device *pdev) } else { dev_err(dev, "When USB is wired, UOK should be wired." "as well.\n"); - ret = -EINVAL; - goto err; + return -EINVAL; } } @@ -248,32 +244,28 @@ static int max8903_probe(struct platform_device *pdev) gpio_set_value(pdata->cen, (ta_in || usb_in) ? 0 : 1); } else { dev_err(dev, "Invalid pin: cen.\n"); - ret = -EINVAL; - goto err; + return -EINVAL; } } if (pdata->chg) { if (!gpio_is_valid(pdata->chg)) { dev_err(dev, "Invalid pin: chg.\n"); - ret = -EINVAL; - goto err; + return -EINVAL; } } if (pdata->flt) { if (!gpio_is_valid(pdata->flt)) { dev_err(dev, "Invalid pin: flt.\n"); - ret = -EINVAL; - goto err; + return -EINVAL; } } if (pdata->usus) { if (!gpio_is_valid(pdata->usus)) { dev_err(dev, "Invalid pin: usus.\n"); - ret = -EINVAL; - goto err; + return -EINVAL; } } @@ -291,85 +283,56 @@ static int max8903_probe(struct platform_device *pdev) psy_cfg.drv_data = data; - data->psy = power_supply_register(dev, &data->psy_desc, &psy_cfg); + data->psy = devm_power_supply_register(dev, &data->psy_desc, &psy_cfg); if (IS_ERR(data->psy)) { dev_err(dev, "failed: power supply register.\n"); - ret = PTR_ERR(data->psy); - goto err; + return PTR_ERR(data->psy); } if (pdata->dc_valid) { - ret = request_threaded_irq(gpio_to_irq(pdata->dok), - NULL, max8903_dcin, - IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, - "MAX8903 DC IN", data); + ret = devm_request_threaded_irq(dev, gpio_to_irq(pdata->dok), + NULL, max8903_dcin, + IRQF_TRIGGER_FALLING | + IRQF_TRIGGER_RISING, + "MAX8903 DC IN", data); if (ret) { dev_err(dev, "Cannot request irq %d for DC (%d)\n", gpio_to_irq(pdata->dok), ret); - goto err_psy; + return ret; } } if (pdata->usb_valid) { - ret = request_threaded_irq(gpio_to_irq(pdata->uok), - NULL, max8903_usbin, - IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, - "MAX8903 USB IN", data); + ret = devm_request_threaded_irq(dev, gpio_to_irq(pdata->uok), + NULL, max8903_usbin, + IRQF_TRIGGER_FALLING | + IRQF_TRIGGER_RISING, + "MAX8903 USB IN", data); if (ret) { dev_err(dev, "Cannot request irq %d for USB (%d)\n", gpio_to_irq(pdata->uok), ret); - goto err_dc_irq; + return ret; } } if (pdata->flt) { - ret = request_threaded_irq(gpio_to_irq(pdata->flt), - NULL, max8903_fault, - IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, - "MAX8903 Fault", data); + ret = devm_request_threaded_irq(dev, gpio_to_irq(pdata->flt), + NULL, max8903_fault, + IRQF_TRIGGER_FALLING | + IRQF_TRIGGER_RISING, + "MAX8903 Fault", data); if (ret) { dev_err(dev, "Cannot request irq %d for Fault (%d)\n", gpio_to_irq(pdata->flt), ret); - goto err_usb_irq; + return ret; } } - return 0; - -err_usb_irq: - if (pdata->usb_valid) - free_irq(gpio_to_irq(pdata->uok), data); -err_dc_irq: - if (pdata->dc_valid) - free_irq(gpio_to_irq(pdata->dok), data); -err_psy: - power_supply_unregister(data->psy); -err: - return ret; -} - -static int max8903_remove(struct platform_device *pdev) -{ - struct max8903_data *data = platform_get_drvdata(pdev); - - if (data) { - struct max8903_pdata *pdata = &data->pdata; - - if (pdata->flt) - free_irq(gpio_to_irq(pdata->flt), data); - if (pdata->usb_valid) - free_irq(gpio_to_irq(pdata->uok), data); - if (pdata->dc_valid) - free_irq(gpio_to_irq(pdata->dok), data); - power_supply_unregister(data->psy); - } - return 0; } static struct platform_driver max8903_driver = { .probe = max8903_probe, - .remove = max8903_remove, .driver = { .name = "max8903-charger", }, -- cgit v1.2.3 From c72b7bf82cc681c350c051e5f077b54dac531d2f Mon Sep 17 00:00:00 2001 From: Luis de Bethencourt Date: Fri, 18 Sep 2015 18:54:07 +0200 Subject: tps65090-charger: Fix module autoload for OF platform driver This platform driver has a OF device ID table but the OF module alias information is not created so module autoloading won't work. Signed-off-by: Luis de Bethencourt Signed-off-by: Sebastian Reichel --- drivers/power/tps65090-charger.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/power/tps65090-charger.c b/drivers/power/tps65090-charger.c index 7e8fbd29c30e..1b4b5e09538e 100644 --- a/drivers/power/tps65090-charger.c +++ b/drivers/power/tps65090-charger.c @@ -353,6 +353,7 @@ static const struct of_device_id of_tps65090_charger_match[] = { { .compatible = "ti,tps65090-charger", }, { /* end */ } }; +MODULE_DEVICE_TABLE(of, of_tps65090_charger_match); static struct platform_driver tps65090_charger_driver = { .driver = { -- cgit v1.2.3 From 5e5822f670c2c61953b5d4d18b4cc3e66744865c Mon Sep 17 00:00:00 2001 From: Vaishali Thakkar Date: Thu, 13 Aug 2015 10:24:41 +0530 Subject: power_supply: max8998: Use devm_power_supply_register Use managed resource function devm_power_supply_register instead of power_supply_register to simplify the error path by allowing unregister to happen automatically on error. To be compatible with the change, replace various gotos by direct returns and remove unneeded label err. Also, remove max8998_battery_remove as it is now redundant. Signed-off-by: Vaishali Thakkar Signed-off-by: Sebastian Reichel --- drivers/power/max8998_charger.c | 29 +++++++---------------------- 1 file changed, 7 insertions(+), 22 deletions(-) (limited to 'drivers') diff --git a/drivers/power/max8998_charger.c b/drivers/power/max8998_charger.c index 47448d4bc6cd..b64cf0f14142 100644 --- a/drivers/power/max8998_charger.c +++ b/drivers/power/max8998_charger.c @@ -117,8 +117,7 @@ static int max8998_battery_probe(struct platform_device *pdev) "EOC value not set: leave it unchanged.\n"); } else { dev_err(max8998->dev, "Invalid EOC value\n"); - ret = -EINVAL; - goto err; + return -EINVAL; } /* Setup Charge Restart Level */ @@ -141,8 +140,7 @@ static int max8998_battery_probe(struct platform_device *pdev) break; default: dev_err(max8998->dev, "Invalid Restart Level\n"); - ret = -EINVAL; - goto err; + return -EINVAL; } /* Setup Charge Full Timeout */ @@ -165,33 +163,21 @@ static int max8998_battery_probe(struct platform_device *pdev) break; default: dev_err(max8998->dev, "Invalid Full Timeout value\n"); - ret = -EINVAL; - goto err; + return -EINVAL; } psy_cfg.drv_data = max8998; - max8998->battery = power_supply_register(max8998->dev, - &max8998_battery_desc, - &psy_cfg); + max8998->battery = devm_power_supply_register(max8998->dev, + &max8998_battery_desc, + &psy_cfg); if (IS_ERR(max8998->battery)) { ret = PTR_ERR(max8998->battery); dev_err(max8998->dev, "failed: power supply register: %d\n", ret); - goto err; + return ret; } - return 0; -err: - return ret; -} - -static int max8998_battery_remove(struct platform_device *pdev) -{ - struct max8998_battery_data *max8998 = platform_get_drvdata(pdev); - - power_supply_unregister(max8998->battery); - return 0; } @@ -205,7 +191,6 @@ static struct platform_driver max8998_battery_driver = { .name = "max8998-battery", }, .probe = max8998_battery_probe, - .remove = max8998_battery_remove, .id_table = max8998_battery_id, }; -- cgit v1.2.3 From 95b8aff2a6e5616639db06dd37efb36fba188590 Mon Sep 17 00:00:00 2001 From: "Andrew F. Davis" Date: Mon, 14 Sep 2015 16:26:07 -0500 Subject: power: bq27x00_battery: Remove unneeded i2c MODULE_ALIAS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The MODULE_DEVICE_TABLE macro automatically adds all needed i2c MODULE_ALIASes so remove the extra MODULE_ALIAS. Signed-off-by: Andrew F. Davis Acked-by: Pali Rohár Signed-off-by: Sebastian Reichel --- drivers/power/bq27x00_battery.c | 4 ---- 1 file changed, 4 deletions(-) (limited to 'drivers') diff --git a/drivers/power/bq27x00_battery.c b/drivers/power/bq27x00_battery.c index 8287261fd978..d0b2f3b47b8f 100644 --- a/drivers/power/bq27x00_battery.c +++ b/drivers/power/bq27x00_battery.c @@ -1120,10 +1120,6 @@ module_exit(bq27x00_battery_exit); MODULE_ALIAS("platform:bq27000-battery"); #endif -#ifdef CONFIG_BATTERY_BQ27X00_I2C -MODULE_ALIAS("i2c:bq27000-battery"); -#endif - MODULE_AUTHOR("Rodolfo Giometti "); MODULE_DESCRIPTION("BQ27x00 battery monitor driver"); MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 081bab217db769526c1202c87099ff69737126ae Mon Sep 17 00:00:00 2001 From: "Andrew F. Davis" Date: Tue, 22 Sep 2015 14:35:06 -0500 Subject: power: bq27x00_battery: Renaming for consistency Rename functions that are used by multiple devices. New devices have been added and the function names and driver name are no longer general enough for the functionality they provide. Signed-off-by: Andrew F. Davis Acked-by: Tony Lindgren Acked-by: GUAN Xuetao Signed-off-by: Sebastian Reichel --- drivers/power/Kconfig | 22 +- drivers/power/Makefile | 2 +- drivers/power/bq27x00_battery.c | 1125 -------------------------------------- drivers/power/bq27xxx_battery.c | 1126 +++++++++++++++++++++++++++++++++++++++ drivers/w1/slaves/w1_bq27000.c | 4 +- 5 files changed, 1140 insertions(+), 1139 deletions(-) delete mode 100644 drivers/power/bq27x00_battery.c create mode 100644 drivers/power/bq27xxx_battery.c (limited to 'drivers') diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 2c0520db5025..d3cd2ea54440 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -157,26 +157,26 @@ config BATTERY_SBS Say Y to include support for SBS battery driver for SBS-compliant gas gauges. -config BATTERY_BQ27x00 - tristate "BQ27x00 battery driver" +config BATTERY_BQ27XXX + tristate "BQ27xxx battery driver" depends on I2C || I2C=n help - Say Y here to enable support for batteries with BQ27x00 (I2C/HDQ) chips. + Say Y here to enable support for batteries with BQ27xxx (I2C/HDQ) chips. -config BATTERY_BQ27X00_I2C - bool "BQ27200/BQ27500 support" - depends on BATTERY_BQ27x00 +config BATTERY_BQ27XXX_I2C + bool "BQ27xxx I2C support" + depends on BATTERY_BQ27XXX depends on I2C default y help - Say Y here to enable support for batteries with BQ27x00 (I2C) chips. + Say Y here to enable support for batteries with BQ27xxx (I2C) chips. -config BATTERY_BQ27X00_PLATFORM - bool "BQ27000 support" - depends on BATTERY_BQ27x00 +config BATTERY_BQ27XXX_PLATFORM + bool "BQ27xxx HDQ support" + depends on BATTERY_BQ27XXX default y help - Say Y here to enable support for batteries with BQ27000 (HDQ) chips. + Say Y here to enable support for batteries with BQ27xxx (HDQ) chips. config BATTERY_DA9030 tristate "DA9030 battery driver" diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 81109baff598..8eb30a515d5c 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -30,7 +30,7 @@ obj-$(CONFIG_BATTERY_COLLIE) += collie_battery.o obj-$(CONFIG_BATTERY_IPAQ_MICRO) += ipaq_micro_battery.o obj-$(CONFIG_BATTERY_WM97XX) += wm97xx_battery.o obj-$(CONFIG_BATTERY_SBS) += sbs-battery.o -obj-$(CONFIG_BATTERY_BQ27x00) += bq27x00_battery.o +obj-$(CONFIG_BATTERY_BQ27XXX) += bq27xxx_battery.o obj-$(CONFIG_BATTERY_DA9030) += da9030_battery.o obj-$(CONFIG_BATTERY_DA9052) += da9052-battery.o obj-$(CONFIG_CHARGER_DA9150) += da9150-charger.o diff --git a/drivers/power/bq27x00_battery.c b/drivers/power/bq27x00_battery.c deleted file mode 100644 index d0b2f3b47b8f..000000000000 --- a/drivers/power/bq27x00_battery.c +++ /dev/null @@ -1,1125 +0,0 @@ -/* - * BQ27x00 battery driver - * - * Copyright (C) 2008 Rodolfo Giometti - * Copyright (C) 2008 Eurotech S.p.A. - * Copyright (C) 2010-2011 Lars-Peter Clausen - * Copyright (C) 2011 Pali Rohár - * - * Based on a previous work by Copyright (C) 2008 Texas Instruments, Inc. - * - * This package is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED - * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. - * - * Datasheets: - * http://focus.ti.com/docs/prod/folders/print/bq27000.html - * http://focus.ti.com/docs/prod/folders/print/bq27500.html - * http://www.ti.com/product/bq27425-g1 - * http://www.ti.com/product/BQ27742-G1 - * http://www.ti.com/product/BQ27510-G3 - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#define DRIVER_VERSION "1.2.0" - -#define BQ27XXX_MANUFACTURER "Texas Instruments" - -#define BQ27x00_REG_TEMP 0x06 -#define BQ27x00_REG_VOLT 0x08 -#define BQ27x00_REG_AI 0x14 -#define BQ27x00_REG_FLAGS 0x0A -#define BQ27x00_REG_TTE 0x16 -#define BQ27x00_REG_TTF 0x18 -#define BQ27x00_REG_TTECP 0x26 -#define BQ27x00_REG_NAC 0x0C /* Nominal available capacity */ -#define BQ27x00_REG_LMD 0x12 /* Last measured discharge */ -#define BQ27x00_REG_CYCT 0x2A /* Cycle count total */ -#define BQ27x00_REG_AE 0x22 /* Available energy */ -#define BQ27x00_POWER_AVG 0x24 - -#define BQ27000_REG_RSOC 0x0B /* Relative State-of-Charge */ -#define BQ27000_REG_ILMD 0x76 /* Initial last measured discharge */ -#define BQ27000_FLAG_EDVF BIT(0) /* Final End-of-Discharge-Voltage flag */ -#define BQ27000_FLAG_EDV1 BIT(1) /* First End-of-Discharge-Voltage flag */ -#define BQ27000_FLAG_CI BIT(4) /* Capacity Inaccurate flag */ -#define BQ27000_FLAG_FC BIT(5) -#define BQ27000_FLAG_CHGS BIT(7) /* Charge state flag */ - -#define BQ27500_REG_SOC 0x2C -#define BQ27500_REG_DCAP 0x3C /* Design capacity */ -#define BQ27500_FLAG_DSC BIT(0) -#define BQ27500_FLAG_SOCF BIT(1) /* State-of-Charge threshold final */ -#define BQ27500_FLAG_SOC1 BIT(2) /* State-of-Charge threshold 1 */ -#define BQ27500_FLAG_FC BIT(9) -#define BQ27500_FLAG_OTC BIT(15) - -#define BQ27742_POWER_AVG 0x76 - -#define BQ27510_REG_SOC 0x20 -#define BQ27510_REG_DCAP 0x2E /* Design capacity */ -#define BQ27510_REG_CYCT 0x1E /* Cycle count total */ - -/* bq27425 register addresses are same as bq27x00 addresses minus 4 */ -#define BQ27425_REG_OFFSET 0x04 -#define BQ27425_REG_SOC (0x1C + BQ27425_REG_OFFSET) -#define BQ27425_REG_DCAP (0x3C + BQ27425_REG_OFFSET) - -#define BQ27000_RS 20 /* Resistor sense */ -#define BQ27x00_POWER_CONSTANT (256 * 29200 / 1000) - -struct bq27x00_device_info; -struct bq27x00_access_methods { - int (*read)(struct bq27x00_device_info *di, u8 reg, bool single); -}; - -enum bq27x00_chip { BQ27000, BQ27500, BQ27425, BQ27742, BQ27510}; - -struct bq27x00_reg_cache { - int temperature; - int time_to_empty; - int time_to_empty_avg; - int time_to_full; - int charge_full; - int cycle_count; - int capacity; - int energy; - int flags; - int power_avg; - int health; -}; - -struct bq27x00_device_info { - struct device *dev; - int id; - enum bq27x00_chip chip; - - struct bq27x00_reg_cache cache; - int charge_design_full; - - unsigned long last_update; - struct delayed_work work; - - struct power_supply *bat; - - struct bq27x00_access_methods bus; - - struct mutex lock; -}; - -static enum power_supply_property bq27x00_battery_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_CURRENT_NOW, - POWER_SUPPLY_PROP_CAPACITY, - POWER_SUPPLY_PROP_CAPACITY_LEVEL, - POWER_SUPPLY_PROP_TEMP, - POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, - POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, - POWER_SUPPLY_PROP_TIME_TO_FULL_NOW, - POWER_SUPPLY_PROP_TECHNOLOGY, - POWER_SUPPLY_PROP_CHARGE_FULL, - POWER_SUPPLY_PROP_CHARGE_NOW, - POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, - POWER_SUPPLY_PROP_CYCLE_COUNT, - POWER_SUPPLY_PROP_ENERGY_NOW, - POWER_SUPPLY_PROP_POWER_AVG, - POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_MANUFACTURER, -}; - -static enum power_supply_property bq27425_battery_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_CURRENT_NOW, - POWER_SUPPLY_PROP_CAPACITY, - POWER_SUPPLY_PROP_CAPACITY_LEVEL, - POWER_SUPPLY_PROP_TEMP, - POWER_SUPPLY_PROP_TECHNOLOGY, - POWER_SUPPLY_PROP_CHARGE_FULL, - POWER_SUPPLY_PROP_CHARGE_NOW, - POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, - POWER_SUPPLY_PROP_MANUFACTURER, -}; - -static enum power_supply_property bq27742_battery_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_CURRENT_NOW, - POWER_SUPPLY_PROP_CAPACITY, - POWER_SUPPLY_PROP_CAPACITY_LEVEL, - POWER_SUPPLY_PROP_TEMP, - POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, - POWER_SUPPLY_PROP_TECHNOLOGY, - POWER_SUPPLY_PROP_CHARGE_FULL, - POWER_SUPPLY_PROP_CHARGE_NOW, - POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, - POWER_SUPPLY_PROP_CYCLE_COUNT, - POWER_SUPPLY_PROP_POWER_AVG, - POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_MANUFACTURER, -}; - -static enum power_supply_property bq27510_battery_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_CURRENT_NOW, - POWER_SUPPLY_PROP_CAPACITY, - POWER_SUPPLY_PROP_CAPACITY_LEVEL, - POWER_SUPPLY_PROP_TEMP, - POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, - POWER_SUPPLY_PROP_TECHNOLOGY, - POWER_SUPPLY_PROP_CHARGE_FULL, - POWER_SUPPLY_PROP_CHARGE_NOW, - POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, - POWER_SUPPLY_PROP_CYCLE_COUNT, - POWER_SUPPLY_PROP_POWER_AVG, - POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_MANUFACTURER, -}; - -static unsigned int poll_interval = 360; -module_param(poll_interval, uint, 0644); -MODULE_PARM_DESC(poll_interval, - "battery poll interval in seconds - 0 disables polling"); - -/* - * Common code for BQ27x00 devices - */ - -static inline int bq27x00_read(struct bq27x00_device_info *di, u8 reg, - bool single) -{ - if (di->chip == BQ27425) - return di->bus.read(di, reg - BQ27425_REG_OFFSET, single); - return di->bus.read(di, reg, single); -} - -/* - * Higher versions of the chip like BQ27425 and BQ27500 - * differ from BQ27000 and BQ27200 in calculation of certain - * parameters. Hence we need to check for the chip type. - */ -static bool bq27xxx_is_chip_version_higher(struct bq27x00_device_info *di) -{ - if (di->chip == BQ27425 || di->chip == BQ27500 || di->chip == BQ27742 - || di->chip == BQ27510) - return true; - return false; -} - -/* - * Return the battery Relative State-of-Charge - * Or < 0 if something fails. - */ -static int bq27x00_battery_read_rsoc(struct bq27x00_device_info *di) -{ - int rsoc; - - if (di->chip == BQ27500 || di->chip == BQ27742) - rsoc = bq27x00_read(di, BQ27500_REG_SOC, false); - else if (di->chip == BQ27510) - rsoc = bq27x00_read(di, BQ27510_REG_SOC, false); - else if (di->chip == BQ27425) - rsoc = bq27x00_read(di, BQ27425_REG_SOC, false); - else - rsoc = bq27x00_read(di, BQ27000_REG_RSOC, true); - - if (rsoc < 0) - dev_dbg(di->dev, "error reading relative State-of-Charge\n"); - - return rsoc; -} - -/* - * Return a battery charge value in µAh - * Or < 0 if something fails. - */ -static int bq27x00_battery_read_charge(struct bq27x00_device_info *di, u8 reg) -{ - int charge; - - charge = bq27x00_read(di, reg, false); - if (charge < 0) { - dev_dbg(di->dev, "error reading charge register %02x: %d\n", - reg, charge); - return charge; - } - - if (bq27xxx_is_chip_version_higher(di)) - charge *= 1000; - else - charge = charge * 3570 / BQ27000_RS; - - return charge; -} - -/* - * Return the battery Nominal available capaciy in µAh - * Or < 0 if something fails. - */ -static inline int bq27x00_battery_read_nac(struct bq27x00_device_info *di) -{ - int flags; - bool is_bq27500 = di->chip == BQ27500; - bool is_bq27742 = di->chip == BQ27742; - bool is_higher = bq27xxx_is_chip_version_higher(di); - bool flags_1b = !(is_bq27500 || is_bq27742); - - flags = bq27x00_read(di, BQ27x00_REG_FLAGS, flags_1b); - if (flags >= 0 && !is_higher && (flags & BQ27000_FLAG_CI)) - return -ENODATA; - - return bq27x00_battery_read_charge(di, BQ27x00_REG_NAC); -} - -/* - * Return the battery Last measured discharge in µAh - * Or < 0 if something fails. - */ -static inline int bq27x00_battery_read_lmd(struct bq27x00_device_info *di) -{ - return bq27x00_battery_read_charge(di, BQ27x00_REG_LMD); -} - -/* - * Return the battery Initial last measured discharge in µAh - * Or < 0 if something fails. - */ -static int bq27x00_battery_read_ilmd(struct bq27x00_device_info *di) -{ - int ilmd; - - if (bq27xxx_is_chip_version_higher(di)) { - if (di->chip == BQ27425) - ilmd = bq27x00_read(di, BQ27425_REG_DCAP, false); - else if (di->chip == BQ27510) - ilmd = bq27x00_read(di, BQ27510_REG_DCAP, false); - else - ilmd = bq27x00_read(di, BQ27500_REG_DCAP, false); - } else { - ilmd = bq27x00_read(di, BQ27000_REG_ILMD, true); - } - - if (ilmd < 0) { - dev_dbg(di->dev, "error reading initial last measured discharge\n"); - return ilmd; - } - - if (bq27xxx_is_chip_version_higher(di)) - ilmd *= 1000; - else - ilmd = ilmd * 256 * 3570 / BQ27000_RS; - - return ilmd; -} - -/* - * Return the battery Available energy in µWh - * Or < 0 if something fails. - */ -static int bq27x00_battery_read_energy(struct bq27x00_device_info *di) -{ - int ae; - - ae = bq27x00_read(di, BQ27x00_REG_AE, false); - if (ae < 0) { - dev_dbg(di->dev, "error reading available energy\n"); - return ae; - } - - if (di->chip == BQ27500) - ae *= 1000; - else - ae = ae * 29200 / BQ27000_RS; - - return ae; -} - -/* - * Return the battery temperature in tenths of degree Kelvin - * Or < 0 if something fails. - */ -static int bq27x00_battery_read_temperature(struct bq27x00_device_info *di) -{ - int temp; - - temp = bq27x00_read(di, BQ27x00_REG_TEMP, false); - if (temp < 0) { - dev_err(di->dev, "error reading temperature\n"); - return temp; - } - - if (!bq27xxx_is_chip_version_higher(di)) - temp = 5 * temp / 2; - - return temp; -} - -/* - * Return the battery Cycle count total - * Or < 0 if something fails. - */ -static int bq27x00_battery_read_cyct(struct bq27x00_device_info *di) -{ - int cyct; - - if (di->chip == BQ27510) - cyct = bq27x00_read(di, BQ27510_REG_CYCT, false); - else - cyct = bq27x00_read(di, BQ27x00_REG_CYCT, false); - if (cyct < 0) - dev_err(di->dev, "error reading cycle count total\n"); - - return cyct; -} - -/* - * Read a time register. - * Return < 0 if something fails. - */ -static int bq27x00_battery_read_time(struct bq27x00_device_info *di, u8 reg) -{ - int tval; - - tval = bq27x00_read(di, reg, false); - if (tval < 0) { - dev_dbg(di->dev, "error reading time register %02x: %d\n", - reg, tval); - return tval; - } - - if (tval == 65535) - return -ENODATA; - - return tval * 60; -} - -/* - * Read a power avg register. - * Return < 0 if something fails. - */ -static int bq27x00_battery_read_pwr_avg(struct bq27x00_device_info *di, u8 reg) -{ - int tval; - - tval = bq27x00_read(di, reg, false); - if (tval < 0) { - dev_err(di->dev, "error reading power avg rgister %02x: %d\n", - reg, tval); - return tval; - } - - if (di->chip == BQ27500) - return tval; - else - return (tval * BQ27x00_POWER_CONSTANT) / BQ27000_RS; -} - -/* - * Read flag register. - * Return < 0 if something fails. - */ -static int bq27x00_battery_read_health(struct bq27x00_device_info *di) -{ - int tval; - - tval = bq27x00_read(di, BQ27x00_REG_FLAGS, false); - if (tval < 0) { - dev_err(di->dev, "error reading flag register:%d\n", tval); - return tval; - } - - if (di->chip == BQ27500) { - if (tval & BQ27500_FLAG_SOCF) - tval = POWER_SUPPLY_HEALTH_DEAD; - else if (tval & BQ27500_FLAG_OTC) - tval = POWER_SUPPLY_HEALTH_OVERHEAT; - else - tval = POWER_SUPPLY_HEALTH_GOOD; - return tval; - } else if (di->chip == BQ27510) { - if (tval & BQ27500_FLAG_OTC) - return POWER_SUPPLY_HEALTH_OVERHEAT; - return POWER_SUPPLY_HEALTH_GOOD; - } else { - if (tval & BQ27000_FLAG_EDV1) - tval = POWER_SUPPLY_HEALTH_DEAD; - else - tval = POWER_SUPPLY_HEALTH_GOOD; - return tval; - } - - return -1; -} - -static void bq27x00_update(struct bq27x00_device_info *di) -{ - struct bq27x00_reg_cache cache = {0, }; - bool is_bq27500 = di->chip == BQ27500; - bool is_bq27510 = di->chip == BQ27510; - bool is_bq27425 = di->chip == BQ27425; - bool is_bq27742 = di->chip == BQ27742; - bool flags_1b = !(is_bq27500 || is_bq27742); - - cache.flags = bq27x00_read(di, BQ27x00_REG_FLAGS, flags_1b); - if ((cache.flags & 0xff) == 0xff) - /* read error */ - cache.flags = -1; - if (cache.flags >= 0) { - if (!is_bq27500 && !is_bq27425 && !is_bq27742 && !is_bq27510 - && (cache.flags & BQ27000_FLAG_CI)) { - dev_info(di->dev, "battery is not calibrated! ignoring capacity values\n"); - cache.capacity = -ENODATA; - cache.energy = -ENODATA; - cache.time_to_empty = -ENODATA; - cache.time_to_empty_avg = -ENODATA; - cache.time_to_full = -ENODATA; - cache.charge_full = -ENODATA; - cache.health = -ENODATA; - } else { - cache.capacity = bq27x00_battery_read_rsoc(di); - if (is_bq27742 || is_bq27510) - cache.time_to_empty = - bq27x00_battery_read_time(di, - BQ27x00_REG_TTE); - else if (!is_bq27425) { - cache.energy = bq27x00_battery_read_energy(di); - cache.time_to_empty = - bq27x00_battery_read_time(di, - BQ27x00_REG_TTE); - cache.time_to_empty_avg = - bq27x00_battery_read_time(di, - BQ27x00_REG_TTECP); - cache.time_to_full = - bq27x00_battery_read_time(di, - BQ27x00_REG_TTF); - } - cache.charge_full = bq27x00_battery_read_lmd(di); - cache.health = bq27x00_battery_read_health(di); - } - cache.temperature = bq27x00_battery_read_temperature(di); - if (!is_bq27425) - cache.cycle_count = bq27x00_battery_read_cyct(di); - if (is_bq27742) - cache.power_avg = - bq27x00_battery_read_pwr_avg(di, - BQ27742_POWER_AVG); - else - cache.power_avg = - bq27x00_battery_read_pwr_avg(di, - BQ27x00_POWER_AVG); - - /* We only have to read charge design full once */ - if (di->charge_design_full <= 0) - di->charge_design_full = bq27x00_battery_read_ilmd(di); - } - - if (di->cache.capacity != cache.capacity) - power_supply_changed(di->bat); - - if (memcmp(&di->cache, &cache, sizeof(cache)) != 0) - di->cache = cache; - - di->last_update = jiffies; -} - -static void bq27x00_battery_poll(struct work_struct *work) -{ - struct bq27x00_device_info *di = - container_of(work, struct bq27x00_device_info, work.work); - - bq27x00_update(di); - - if (poll_interval > 0) { - /* The timer does not have to be accurate. */ - set_timer_slack(&di->work.timer, poll_interval * HZ / 4); - schedule_delayed_work(&di->work, poll_interval * HZ); - } -} - -/* - * Return the battery average current in µA - * Note that current can be negative signed as well - * Or 0 if something fails. - */ -static int bq27x00_battery_current(struct bq27x00_device_info *di, - union power_supply_propval *val) -{ - int curr; - int flags; - - curr = bq27x00_read(di, BQ27x00_REG_AI, false); - if (curr < 0) { - dev_err(di->dev, "error reading current\n"); - return curr; - } - - if (bq27xxx_is_chip_version_higher(di)) { - /* bq27500 returns signed value */ - val->intval = (int)((s16)curr) * 1000; - } else { - flags = bq27x00_read(di, BQ27x00_REG_FLAGS, false); - if (flags & BQ27000_FLAG_CHGS) { - dev_dbg(di->dev, "negative current!\n"); - curr = -curr; - } - - val->intval = curr * 3570 / BQ27000_RS; - } - - return 0; -} - -static int bq27x00_battery_status(struct bq27x00_device_info *di, - union power_supply_propval *val) -{ - int status; - - if (bq27xxx_is_chip_version_higher(di)) { - if (di->cache.flags & BQ27500_FLAG_FC) - status = POWER_SUPPLY_STATUS_FULL; - else if (di->cache.flags & BQ27500_FLAG_DSC) - status = POWER_SUPPLY_STATUS_DISCHARGING; - else - status = POWER_SUPPLY_STATUS_CHARGING; - } else { - if (di->cache.flags & BQ27000_FLAG_FC) - status = POWER_SUPPLY_STATUS_FULL; - else if (di->cache.flags & BQ27000_FLAG_CHGS) - status = POWER_SUPPLY_STATUS_CHARGING; - else if (power_supply_am_i_supplied(di->bat)) - status = POWER_SUPPLY_STATUS_NOT_CHARGING; - else - status = POWER_SUPPLY_STATUS_DISCHARGING; - } - - val->intval = status; - - return 0; -} - -static int bq27x00_battery_capacity_level(struct bq27x00_device_info *di, - union power_supply_propval *val) -{ - int level; - - if (bq27xxx_is_chip_version_higher(di)) { - if (di->cache.flags & BQ27500_FLAG_FC) - level = POWER_SUPPLY_CAPACITY_LEVEL_FULL; - else if (di->cache.flags & BQ27500_FLAG_SOC1) - level = POWER_SUPPLY_CAPACITY_LEVEL_LOW; - else if (di->cache.flags & BQ27500_FLAG_SOCF) - level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; - else - level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; - } else { - if (di->cache.flags & BQ27000_FLAG_FC) - level = POWER_SUPPLY_CAPACITY_LEVEL_FULL; - else if (di->cache.flags & BQ27000_FLAG_EDV1) - level = POWER_SUPPLY_CAPACITY_LEVEL_LOW; - else if (di->cache.flags & BQ27000_FLAG_EDVF) - level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; - else - level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; - } - - val->intval = level; - - return 0; -} - -/* - * Return the battery Voltage in millivolts - * Or < 0 if something fails. - */ -static int bq27x00_battery_voltage(struct bq27x00_device_info *di, - union power_supply_propval *val) -{ - int volt; - - volt = bq27x00_read(di, BQ27x00_REG_VOLT, false); - if (volt < 0) { - dev_err(di->dev, "error reading voltage\n"); - return volt; - } - - val->intval = volt * 1000; - - return 0; -} - -static int bq27x00_simple_value(int value, - union power_supply_propval *val) -{ - if (value < 0) - return value; - - val->intval = value; - - return 0; -} - -static int bq27x00_battery_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - int ret = 0; - struct bq27x00_device_info *di = power_supply_get_drvdata(psy); - - mutex_lock(&di->lock); - if (time_is_before_jiffies(di->last_update + 5 * HZ)) { - cancel_delayed_work_sync(&di->work); - bq27x00_battery_poll(&di->work.work); - } - mutex_unlock(&di->lock); - - if (psp != POWER_SUPPLY_PROP_PRESENT && di->cache.flags < 0) - return -ENODEV; - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - ret = bq27x00_battery_status(di, val); - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - ret = bq27x00_battery_voltage(di, val); - break; - case POWER_SUPPLY_PROP_PRESENT: - val->intval = di->cache.flags < 0 ? 0 : 1; - break; - case POWER_SUPPLY_PROP_CURRENT_NOW: - ret = bq27x00_battery_current(di, val); - break; - case POWER_SUPPLY_PROP_CAPACITY: - ret = bq27x00_simple_value(di->cache.capacity, val); - break; - case POWER_SUPPLY_PROP_CAPACITY_LEVEL: - ret = bq27x00_battery_capacity_level(di, val); - break; - case POWER_SUPPLY_PROP_TEMP: - ret = bq27x00_simple_value(di->cache.temperature, val); - if (ret == 0) - val->intval -= 2731; - break; - case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: - ret = bq27x00_simple_value(di->cache.time_to_empty, val); - break; - case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: - ret = bq27x00_simple_value(di->cache.time_to_empty_avg, val); - break; - case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW: - ret = bq27x00_simple_value(di->cache.time_to_full, val); - break; - case POWER_SUPPLY_PROP_TECHNOLOGY: - val->intval = POWER_SUPPLY_TECHNOLOGY_LION; - break; - case POWER_SUPPLY_PROP_CHARGE_NOW: - ret = bq27x00_simple_value(bq27x00_battery_read_nac(di), val); - break; - case POWER_SUPPLY_PROP_CHARGE_FULL: - ret = bq27x00_simple_value(di->cache.charge_full, val); - break; - case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: - ret = bq27x00_simple_value(di->charge_design_full, val); - break; - case POWER_SUPPLY_PROP_CYCLE_COUNT: - ret = bq27x00_simple_value(di->cache.cycle_count, val); - break; - case POWER_SUPPLY_PROP_ENERGY_NOW: - ret = bq27x00_simple_value(di->cache.energy, val); - break; - case POWER_SUPPLY_PROP_POWER_AVG: - ret = bq27x00_simple_value(di->cache.power_avg, val); - break; - case POWER_SUPPLY_PROP_HEALTH: - ret = bq27x00_simple_value(di->cache.health, val); - break; - case POWER_SUPPLY_PROP_MANUFACTURER: - val->strval = BQ27XXX_MANUFACTURER; - break; - default: - return -EINVAL; - } - - return ret; -} - -static void bq27x00_external_power_changed(struct power_supply *psy) -{ - struct bq27x00_device_info *di = power_supply_get_drvdata(psy); - - cancel_delayed_work_sync(&di->work); - schedule_delayed_work(&di->work, 0); -} - -static int bq27x00_powersupply_init(struct bq27x00_device_info *di, - const char *name) -{ - int ret; - struct power_supply_desc *psy_desc; - struct power_supply_config psy_cfg = { .drv_data = di, }; - - psy_desc = devm_kzalloc(di->dev, sizeof(*psy_desc), GFP_KERNEL); - if (!psy_desc) - return -ENOMEM; - - psy_desc->name = name; - psy_desc->type = POWER_SUPPLY_TYPE_BATTERY; - if (di->chip == BQ27425) { - psy_desc->properties = bq27425_battery_props; - psy_desc->num_properties = ARRAY_SIZE(bq27425_battery_props); - } else if (di->chip == BQ27742) { - psy_desc->properties = bq27742_battery_props; - psy_desc->num_properties = ARRAY_SIZE(bq27742_battery_props); - } else if (di->chip == BQ27510) { - psy_desc->properties = bq27510_battery_props; - psy_desc->num_properties = ARRAY_SIZE(bq27510_battery_props); - } else { - psy_desc->properties = bq27x00_battery_props; - psy_desc->num_properties = ARRAY_SIZE(bq27x00_battery_props); - } - psy_desc->get_property = bq27x00_battery_get_property; - psy_desc->external_power_changed = bq27x00_external_power_changed; - - INIT_DELAYED_WORK(&di->work, bq27x00_battery_poll); - mutex_init(&di->lock); - - di->bat = power_supply_register_no_ws(di->dev, psy_desc, &psy_cfg); - if (IS_ERR(di->bat)) { - ret = PTR_ERR(di->bat); - dev_err(di->dev, "failed to register battery: %d\n", ret); - return ret; - } - - dev_info(di->dev, "support ver. %s enabled\n", DRIVER_VERSION); - - bq27x00_update(di); - - return 0; -} - -static void bq27x00_powersupply_unregister(struct bq27x00_device_info *di) -{ - /* - * power_supply_unregister call bq27x00_battery_get_property which - * call bq27x00_battery_poll. - * Make sure that bq27x00_battery_poll will not call - * schedule_delayed_work again after unregister (which cause OOPS). - */ - poll_interval = 0; - - cancel_delayed_work_sync(&di->work); - - power_supply_unregister(di->bat); - - mutex_destroy(&di->lock); -} - -/* i2c specific code */ -#ifdef CONFIG_BATTERY_BQ27X00_I2C - -/* If the system has several batteries we need a different name for each - * of them... - */ -static DEFINE_IDR(battery_id); -static DEFINE_MUTEX(battery_mutex); - -static int bq27x00_read_i2c(struct bq27x00_device_info *di, u8 reg, bool single) -{ - struct i2c_client *client = to_i2c_client(di->dev); - struct i2c_msg msg[2]; - unsigned char data[2]; - int ret; - - if (!client->adapter) - return -ENODEV; - - msg[0].addr = client->addr; - msg[0].flags = 0; - msg[0].buf = ® - msg[0].len = sizeof(reg); - msg[1].addr = client->addr; - msg[1].flags = I2C_M_RD; - msg[1].buf = data; - if (single) - msg[1].len = 1; - else - msg[1].len = 2; - - ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); - if (ret < 0) - return ret; - - if (!single) - ret = get_unaligned_le16(data); - else - ret = data[0]; - - return ret; -} - -static int bq27x00_battery_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - char *name; - struct bq27x00_device_info *di; - int num; - int retval = 0; - - /* Get new ID for the new battery device */ - mutex_lock(&battery_mutex); - num = idr_alloc(&battery_id, client, 0, 0, GFP_KERNEL); - mutex_unlock(&battery_mutex); - if (num < 0) - return num; - - name = devm_kasprintf(&client->dev, GFP_KERNEL, "%s-%d", id->name, num); - if (!name) { - retval = -ENOMEM; - goto batt_failed; - } - - di = devm_kzalloc(&client->dev, sizeof(*di), GFP_KERNEL); - if (!di) { - retval = -ENOMEM; - goto batt_failed; - } - - di->id = num; - di->dev = &client->dev; - di->chip = id->driver_data; - di->bus.read = &bq27x00_read_i2c; - - retval = bq27x00_powersupply_init(di, name); - if (retval) - goto batt_failed; - - i2c_set_clientdata(client, di); - - return 0; - -batt_failed: - mutex_lock(&battery_mutex); - idr_remove(&battery_id, num); - mutex_unlock(&battery_mutex); - - return retval; -} - -static int bq27x00_battery_remove(struct i2c_client *client) -{ - struct bq27x00_device_info *di = i2c_get_clientdata(client); - - bq27x00_powersupply_unregister(di); - - mutex_lock(&battery_mutex); - idr_remove(&battery_id, di->id); - mutex_unlock(&battery_mutex); - - return 0; -} - -static const struct i2c_device_id bq27x00_id[] = { - { "bq27200", BQ27000 }, /* bq27200 is same as bq27000, but with i2c */ - { "bq27500", BQ27500 }, - { "bq27425", BQ27425 }, - { "bq27742", BQ27742 }, - { "bq27510", BQ27510 }, - {}, -}; -MODULE_DEVICE_TABLE(i2c, bq27x00_id); - -static struct i2c_driver bq27x00_battery_driver = { - .driver = { - .name = "bq27x00-battery", - }, - .probe = bq27x00_battery_probe, - .remove = bq27x00_battery_remove, - .id_table = bq27x00_id, -}; - -static inline int bq27x00_battery_i2c_init(void) -{ - int ret = i2c_add_driver(&bq27x00_battery_driver); - - if (ret) - pr_err("Unable to register BQ27x00 i2c driver\n"); - - return ret; -} - -static inline void bq27x00_battery_i2c_exit(void) -{ - i2c_del_driver(&bq27x00_battery_driver); -} - -#else - -static inline int bq27x00_battery_i2c_init(void) { return 0; } -static inline void bq27x00_battery_i2c_exit(void) {}; - -#endif - -/* platform specific code */ -#ifdef CONFIG_BATTERY_BQ27X00_PLATFORM - -static int bq27000_read_platform(struct bq27x00_device_info *di, u8 reg, - bool single) -{ - struct device *dev = di->dev; - struct bq27000_platform_data *pdata = dev->platform_data; - unsigned int timeout = 3; - int upper, lower; - int temp; - - if (!single) { - /* Make sure the value has not changed in between reading the - * lower and the upper part */ - upper = pdata->read(dev, reg + 1); - do { - temp = upper; - if (upper < 0) - return upper; - - lower = pdata->read(dev, reg); - if (lower < 0) - return lower; - - upper = pdata->read(dev, reg + 1); - } while (temp != upper && --timeout); - - if (timeout == 0) - return -EIO; - - return (upper << 8) | lower; - } - - return pdata->read(dev, reg); -} - -static int bq27000_battery_probe(struct platform_device *pdev) -{ - struct bq27x00_device_info *di; - struct bq27000_platform_data *pdata = pdev->dev.platform_data; - const char *name; - - if (!pdata) { - dev_err(&pdev->dev, "no platform_data supplied\n"); - return -EINVAL; - } - - if (!pdata->read) { - dev_err(&pdev->dev, "no hdq read callback supplied\n"); - return -EINVAL; - } - - di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL); - if (!di) - return -ENOMEM; - - platform_set_drvdata(pdev, di); - - di->dev = &pdev->dev; - di->chip = BQ27000; - - name = pdata->name ?: dev_name(&pdev->dev); - di->bus.read = &bq27000_read_platform; - - return bq27x00_powersupply_init(di, name); -} - -static int bq27000_battery_remove(struct platform_device *pdev) -{ - struct bq27x00_device_info *di = platform_get_drvdata(pdev); - - bq27x00_powersupply_unregister(di); - - return 0; -} - -static struct platform_driver bq27000_battery_driver = { - .probe = bq27000_battery_probe, - .remove = bq27000_battery_remove, - .driver = { - .name = "bq27000-battery", - }, -}; - -static inline int bq27x00_battery_platform_init(void) -{ - int ret = platform_driver_register(&bq27000_battery_driver); - - if (ret) - pr_err("Unable to register BQ27000 platform driver\n"); - - return ret; -} - -static inline void bq27x00_battery_platform_exit(void) -{ - platform_driver_unregister(&bq27000_battery_driver); -} - -#else - -static inline int bq27x00_battery_platform_init(void) { return 0; } -static inline void bq27x00_battery_platform_exit(void) {}; - -#endif - -/* - * Module stuff - */ - -static int __init bq27x00_battery_init(void) -{ - int ret; - - ret = bq27x00_battery_i2c_init(); - if (ret) - return ret; - - ret = bq27x00_battery_platform_init(); - if (ret) - bq27x00_battery_i2c_exit(); - - return ret; -} -module_init(bq27x00_battery_init); - -static void __exit bq27x00_battery_exit(void) -{ - bq27x00_battery_platform_exit(); - bq27x00_battery_i2c_exit(); -} -module_exit(bq27x00_battery_exit); - -#ifdef CONFIG_BATTERY_BQ27X00_PLATFORM -MODULE_ALIAS("platform:bq27000-battery"); -#endif - -MODULE_AUTHOR("Rodolfo Giometti "); -MODULE_DESCRIPTION("BQ27x00 battery monitor driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/bq27xxx_battery.c b/drivers/power/bq27xxx_battery.c new file mode 100644 index 000000000000..e72055ce7b3c --- /dev/null +++ b/drivers/power/bq27xxx_battery.c @@ -0,0 +1,1126 @@ +/* + * BQ27xxx battery driver + * + * Copyright (C) 2008 Rodolfo Giometti + * Copyright (C) 2008 Eurotech S.p.A. + * Copyright (C) 2010-2011 Lars-Peter Clausen + * Copyright (C) 2011 Pali Rohár + * + * Based on a previous work by Copyright (C) 2008 Texas Instruments, Inc. + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Datasheets: + * http://focus.ti.com/docs/prod/folders/print/bq27000.html + * http://focus.ti.com/docs/prod/folders/print/bq27500.html + * http://www.ti.com/product/bq27425-g1 + * http://www.ti.com/product/BQ27742-G1 + * http://www.ti.com/product/BQ27510-G3 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define DRIVER_VERSION "1.2.0" + +#define BQ27XXX_MANUFACTURER "Texas Instruments" + +#define BQ27x00_REG_TEMP 0x06 +#define BQ27x00_REG_VOLT 0x08 +#define BQ27x00_REG_AI 0x14 +#define BQ27x00_REG_FLAGS 0x0A +#define BQ27x00_REG_TTE 0x16 +#define BQ27x00_REG_TTF 0x18 +#define BQ27x00_REG_TTECP 0x26 +#define BQ27x00_REG_NAC 0x0C /* Nominal available capacity */ +#define BQ27x00_REG_LMD 0x12 /* Last measured discharge */ +#define BQ27x00_REG_CYCT 0x2A /* Cycle count total */ +#define BQ27x00_REG_AE 0x22 /* Available energy */ +#define BQ27x00_POWER_AVG 0x24 + +#define BQ27000_REG_RSOC 0x0B /* Relative State-of-Charge */ +#define BQ27000_REG_ILMD 0x76 /* Initial last measured discharge */ +#define BQ27000_FLAG_EDVF BIT(0) /* Final End-of-Discharge-Voltage flag */ +#define BQ27000_FLAG_EDV1 BIT(1) /* First End-of-Discharge-Voltage flag */ +#define BQ27000_FLAG_CI BIT(4) /* Capacity Inaccurate flag */ +#define BQ27000_FLAG_FC BIT(5) +#define BQ27000_FLAG_CHGS BIT(7) /* Charge state flag */ + +#define BQ27500_REG_SOC 0x2C +#define BQ27500_REG_DCAP 0x3C /* Design capacity */ +#define BQ27500_FLAG_DSC BIT(0) +#define BQ27500_FLAG_SOCF BIT(1) /* State-of-Charge threshold final */ +#define BQ27500_FLAG_SOC1 BIT(2) /* State-of-Charge threshold 1 */ +#define BQ27500_FLAG_FC BIT(9) +#define BQ27500_FLAG_OTC BIT(15) + +#define BQ27742_POWER_AVG 0x76 + +#define BQ27510_REG_SOC 0x20 +#define BQ27510_REG_DCAP 0x2E /* Design capacity */ +#define BQ27510_REG_CYCT 0x1E /* Cycle count total */ + +/* bq27425 register addresses are same as bq27x00 addresses minus 4 */ +#define BQ27425_REG_OFFSET 0x04 +#define BQ27425_REG_SOC (0x1C + BQ27425_REG_OFFSET) +#define BQ27425_REG_DCAP (0x3C + BQ27425_REG_OFFSET) + +#define BQ27XXX_RS 20 /* Resistor sense */ +#define BQ27XXX_POWER_CONSTANT (256 * 29200 / 1000) + +struct bq27xxx_device_info; +struct bq27xxx_access_methods { + int (*read)(struct bq27xxx_device_info *di, u8 reg, bool single); +}; + +enum bq27xxx_chip { BQ27000, BQ27500, BQ27425, BQ27742, BQ27510}; + +struct bq27xxx_reg_cache { + int temperature; + int time_to_empty; + int time_to_empty_avg; + int time_to_full; + int charge_full; + int cycle_count; + int capacity; + int energy; + int flags; + int power_avg; + int health; +}; + +struct bq27xxx_device_info { + struct device *dev; + int id; + enum bq27xxx_chip chip; + + struct bq27xxx_reg_cache cache; + int charge_design_full; + + unsigned long last_update; + struct delayed_work work; + + struct power_supply *bat; + + struct bq27xxx_access_methods bus; + + struct mutex lock; +}; + +static enum power_supply_property bq27x00_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, + POWER_SUPPLY_PROP_TIME_TO_FULL_NOW, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_ENERGY_NOW, + POWER_SUPPLY_PROP_POWER_AVG, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static enum power_supply_property bq27425_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static enum power_supply_property bq27742_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_POWER_AVG, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static enum power_supply_property bq27510_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_POWER_AVG, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static unsigned int poll_interval = 360; +module_param(poll_interval, uint, 0644); +MODULE_PARM_DESC(poll_interval, + "battery poll interval in seconds - 0 disables polling"); + +/* + * Common code for BQ27xxx devices + */ + +static inline int bq27xxx_read(struct bq27xxx_device_info *di, u8 reg, + bool single) +{ + if (di->chip == BQ27425) + return di->bus.read(di, reg - BQ27425_REG_OFFSET, single); + return di->bus.read(di, reg, single); +} + +/* + * Higher versions of the chip like BQ27425 and BQ27500 + * differ from BQ27000 and BQ27200 in calculation of certain + * parameters. Hence we need to check for the chip type. + */ +static bool bq27xxx_is_chip_version_higher(struct bq27xxx_device_info *di) +{ + if (di->chip == BQ27425 || di->chip == BQ27500 || di->chip == BQ27742 + || di->chip == BQ27510) + return true; + return false; +} + +/* + * Return the battery Relative State-of-Charge + * Or < 0 if something fails. + */ +static int bq27xxx_battery_read_rsoc(struct bq27xxx_device_info *di) +{ + int rsoc; + + if (di->chip == BQ27500 || di->chip == BQ27742) + rsoc = bq27xxx_read(di, BQ27500_REG_SOC, false); + else if (di->chip == BQ27510) + rsoc = bq27xxx_read(di, BQ27510_REG_SOC, false); + else if (di->chip == BQ27425) + rsoc = bq27xxx_read(di, BQ27425_REG_SOC, false); + else + rsoc = bq27xxx_read(di, BQ27000_REG_RSOC, true); + + if (rsoc < 0) + dev_dbg(di->dev, "error reading relative State-of-Charge\n"); + + return rsoc; +} + +/* + * Return a battery charge value in µAh + * Or < 0 if something fails. + */ +static int bq27xxx_battery_read_charge(struct bq27xxx_device_info *di, u8 reg) +{ + int charge; + + charge = bq27xxx_read(di, reg, false); + if (charge < 0) { + dev_dbg(di->dev, "error reading charge register %02x: %d\n", + reg, charge); + return charge; + } + + if (bq27xxx_is_chip_version_higher(di)) + charge *= 1000; + else + charge = charge * 3570 / BQ27XXX_RS; + + return charge; +} + +/* + * Return the battery Nominal available capaciy in µAh + * Or < 0 if something fails. + */ +static inline int bq27xxx_battery_read_nac(struct bq27xxx_device_info *di) +{ + int flags; + bool is_bq27500 = di->chip == BQ27500; + bool is_bq27742 = di->chip == BQ27742; + bool is_higher = bq27xxx_is_chip_version_higher(di); + bool flags_1b = !(is_bq27500 || is_bq27742); + + flags = bq27xxx_read(di, BQ27x00_REG_FLAGS, flags_1b); + if (flags >= 0 && !is_higher && (flags & BQ27000_FLAG_CI)) + return -ENODATA; + + return bq27xxx_battery_read_charge(di, BQ27x00_REG_NAC); +} + +/* + * Return the battery Last measured discharge in µAh + * Or < 0 if something fails. + */ +static inline int bq27xxx_battery_read_lmd(struct bq27xxx_device_info *di) +{ + return bq27xxx_battery_read_charge(di, BQ27x00_REG_LMD); +} + +/* + * Return the battery Initial last measured discharge in µAh + * Or < 0 if something fails. + */ +static int bq27xxx_battery_read_ilmd(struct bq27xxx_device_info *di) +{ + int ilmd; + + if (bq27xxx_is_chip_version_higher(di)) { + if (di->chip == BQ27425) + ilmd = bq27xxx_read(di, BQ27425_REG_DCAP, false); + else if (di->chip == BQ27510) + ilmd = bq27xxx_read(di, BQ27510_REG_DCAP, false); + else + ilmd = bq27xxx_read(di, BQ27500_REG_DCAP, false); + } else { + ilmd = bq27xxx_read(di, BQ27000_REG_ILMD, true); + } + + if (ilmd < 0) { + dev_dbg(di->dev, "error reading initial last measured discharge\n"); + return ilmd; + } + + if (bq27xxx_is_chip_version_higher(di)) + ilmd *= 1000; + else + ilmd = ilmd * 256 * 3570 / BQ27XXX_RS; + + return ilmd; +} + +/* + * Return the battery Available energy in µWh + * Or < 0 if something fails. + */ +static int bq27xxx_battery_read_energy(struct bq27xxx_device_info *di) +{ + int ae; + + ae = bq27xxx_read(di, BQ27x00_REG_AE, false); + if (ae < 0) { + dev_dbg(di->dev, "error reading available energy\n"); + return ae; + } + + if (di->chip == BQ27500) + ae *= 1000; + else + ae = ae * 29200 / BQ27XXX_RS; + + return ae; +} + +/* + * Return the battery temperature in tenths of degree Kelvin + * Or < 0 if something fails. + */ +static int bq27xxx_battery_read_temperature(struct bq27xxx_device_info *di) +{ + int temp; + + temp = bq27xxx_read(di, BQ27x00_REG_TEMP, false); + if (temp < 0) { + dev_err(di->dev, "error reading temperature\n"); + return temp; + } + + if (!bq27xxx_is_chip_version_higher(di)) + temp = 5 * temp / 2; + + return temp; +} + +/* + * Return the battery Cycle count total + * Or < 0 if something fails. + */ +static int bq27xxx_battery_read_cyct(struct bq27xxx_device_info *di) +{ + int cyct; + + if (di->chip == BQ27510) + cyct = bq27xxx_read(di, BQ27510_REG_CYCT, false); + else + cyct = bq27xxx_read(di, BQ27x00_REG_CYCT, false); + if (cyct < 0) + dev_err(di->dev, "error reading cycle count total\n"); + + return cyct; +} + +/* + * Read a time register. + * Return < 0 if something fails. + */ +static int bq27xxx_battery_read_time(struct bq27xxx_device_info *di, u8 reg) +{ + int tval; + + tval = bq27xxx_read(di, reg, false); + if (tval < 0) { + dev_dbg(di->dev, "error reading time register %02x: %d\n", + reg, tval); + return tval; + } + + if (tval == 65535) + return -ENODATA; + + return tval * 60; +} + +/* + * Read a power avg register. + * Return < 0 if something fails. + */ +static int bq27xxx_battery_read_pwr_avg(struct bq27xxx_device_info *di, u8 reg) +{ + int tval; + + tval = bq27xxx_read(di, reg, false); + if (tval < 0) { + dev_err(di->dev, "error reading power avg rgister %02x: %d\n", + reg, tval); + return tval; + } + + if (di->chip == BQ27500) + return tval; + else + return (tval * BQ27XXX_POWER_CONSTANT) / BQ27XXX_RS; +} + +/* + * Read flag register. + * Return < 0 if something fails. + */ +static int bq27xxx_battery_read_health(struct bq27xxx_device_info *di) +{ + int tval; + + tval = bq27xxx_read(di, BQ27x00_REG_FLAGS, false); + if (tval < 0) { + dev_err(di->dev, "error reading flag register:%d\n", tval); + return tval; + } + + if (di->chip == BQ27500) { + if (tval & BQ27500_FLAG_SOCF) + tval = POWER_SUPPLY_HEALTH_DEAD; + else if (tval & BQ27500_FLAG_OTC) + tval = POWER_SUPPLY_HEALTH_OVERHEAT; + else + tval = POWER_SUPPLY_HEALTH_GOOD; + return tval; + } else if (di->chip == BQ27510) { + if (tval & BQ27500_FLAG_OTC) + return POWER_SUPPLY_HEALTH_OVERHEAT; + return POWER_SUPPLY_HEALTH_GOOD; + } else { + if (tval & BQ27000_FLAG_EDV1) + tval = POWER_SUPPLY_HEALTH_DEAD; + else + tval = POWER_SUPPLY_HEALTH_GOOD; + return tval; + } + + return -1; +} + +static void bq27xxx_battery_update(struct bq27xxx_device_info *di) +{ + struct bq27xxx_reg_cache cache = {0, }; + bool is_bq27500 = di->chip == BQ27500; + bool is_bq27510 = di->chip == BQ27510; + bool is_bq27425 = di->chip == BQ27425; + bool is_bq27742 = di->chip == BQ27742; + bool flags_1b = !(is_bq27500 || is_bq27742); + + cache.flags = bq27xxx_read(di, BQ27x00_REG_FLAGS, flags_1b); + if ((cache.flags & 0xff) == 0xff) + /* read error */ + cache.flags = -1; + if (cache.flags >= 0) { + if (!is_bq27500 && !is_bq27425 && !is_bq27742 && !is_bq27510 + && (cache.flags & BQ27000_FLAG_CI)) { + dev_info(di->dev, "battery is not calibrated! ignoring capacity values\n"); + cache.capacity = -ENODATA; + cache.energy = -ENODATA; + cache.time_to_empty = -ENODATA; + cache.time_to_empty_avg = -ENODATA; + cache.time_to_full = -ENODATA; + cache.charge_full = -ENODATA; + cache.health = -ENODATA; + } else { + cache.capacity = bq27xxx_battery_read_rsoc(di); + if (is_bq27742 || is_bq27510) + cache.time_to_empty = + bq27xxx_battery_read_time(di, + BQ27x00_REG_TTE); + else if (!is_bq27425) { + cache.energy = bq27xxx_battery_read_energy(di); + cache.time_to_empty = + bq27xxx_battery_read_time(di, + BQ27x00_REG_TTE); + cache.time_to_empty_avg = + bq27xxx_battery_read_time(di, + BQ27x00_REG_TTECP); + cache.time_to_full = + bq27xxx_battery_read_time(di, + BQ27x00_REG_TTF); + } + cache.charge_full = bq27xxx_battery_read_lmd(di); + cache.health = bq27xxx_battery_read_health(di); + } + cache.temperature = bq27xxx_battery_read_temperature(di); + if (!is_bq27425) + cache.cycle_count = bq27xxx_battery_read_cyct(di); + if (is_bq27742) + cache.power_avg = + bq27xxx_battery_read_pwr_avg(di, + BQ27742_POWER_AVG); + else + cache.power_avg = + bq27xxx_battery_read_pwr_avg(di, + BQ27x00_POWER_AVG); + + /* We only have to read charge design full once */ + if (di->charge_design_full <= 0) + di->charge_design_full = bq27xxx_battery_read_ilmd(di); + } + + if (di->cache.capacity != cache.capacity) + power_supply_changed(di->bat); + + if (memcmp(&di->cache, &cache, sizeof(cache)) != 0) + di->cache = cache; + + di->last_update = jiffies; +} + +static void bq27xxx_battery_poll(struct work_struct *work) +{ + struct bq27xxx_device_info *di = + container_of(work, struct bq27xxx_device_info, work.work); + + bq27xxx_battery_update(di); + + if (poll_interval > 0) { + /* The timer does not have to be accurate. */ + set_timer_slack(&di->work.timer, poll_interval * HZ / 4); + schedule_delayed_work(&di->work, poll_interval * HZ); + } +} + +/* + * Return the battery average current in µA + * Note that current can be negative signed as well + * Or 0 if something fails. + */ +static int bq27xxx_battery_current(struct bq27xxx_device_info *di, + union power_supply_propval *val) +{ + int curr; + int flags; + + curr = bq27xxx_read(di, BQ27x00_REG_AI, false); + if (curr < 0) { + dev_err(di->dev, "error reading current\n"); + return curr; + } + + if (bq27xxx_is_chip_version_higher(di)) { + /* bq27500 returns signed value */ + val->intval = (int)((s16)curr) * 1000; + } else { + flags = bq27xxx_read(di, BQ27x00_REG_FLAGS, false); + if (flags & BQ27000_FLAG_CHGS) { + dev_dbg(di->dev, "negative current!\n"); + curr = -curr; + } + + val->intval = curr * 3570 / BQ27XXX_RS; + } + + return 0; +} + +static int bq27xxx_battery_status(struct bq27xxx_device_info *di, + union power_supply_propval *val) +{ + int status; + + if (bq27xxx_is_chip_version_higher(di)) { + if (di->cache.flags & BQ27500_FLAG_FC) + status = POWER_SUPPLY_STATUS_FULL; + else if (di->cache.flags & BQ27500_FLAG_DSC) + status = POWER_SUPPLY_STATUS_DISCHARGING; + else + status = POWER_SUPPLY_STATUS_CHARGING; + } else { + if (di->cache.flags & BQ27000_FLAG_FC) + status = POWER_SUPPLY_STATUS_FULL; + else if (di->cache.flags & BQ27000_FLAG_CHGS) + status = POWER_SUPPLY_STATUS_CHARGING; + else if (power_supply_am_i_supplied(di->bat)) + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + else + status = POWER_SUPPLY_STATUS_DISCHARGING; + } + + val->intval = status; + + return 0; +} + +static int bq27xxx_battery_capacity_level(struct bq27xxx_device_info *di, + union power_supply_propval *val) +{ + int level; + + if (bq27xxx_is_chip_version_higher(di)) { + if (di->cache.flags & BQ27500_FLAG_FC) + level = POWER_SUPPLY_CAPACITY_LEVEL_FULL; + else if (di->cache.flags & BQ27500_FLAG_SOC1) + level = POWER_SUPPLY_CAPACITY_LEVEL_LOW; + else if (di->cache.flags & BQ27500_FLAG_SOCF) + level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; + else + level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; + } else { + if (di->cache.flags & BQ27000_FLAG_FC) + level = POWER_SUPPLY_CAPACITY_LEVEL_FULL; + else if (di->cache.flags & BQ27000_FLAG_EDV1) + level = POWER_SUPPLY_CAPACITY_LEVEL_LOW; + else if (di->cache.flags & BQ27000_FLAG_EDVF) + level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; + else + level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; + } + + val->intval = level; + + return 0; +} + +/* + * Return the battery Voltage in millivolts + * Or < 0 if something fails. + */ +static int bq27xxx_battery_voltage(struct bq27xxx_device_info *di, + union power_supply_propval *val) +{ + int volt; + + volt = bq27xxx_read(di, BQ27x00_REG_VOLT, false); + if (volt < 0) { + dev_err(di->dev, "error reading voltage\n"); + return volt; + } + + val->intval = volt * 1000; + + return 0; +} + +static int bq27xxx_simple_value(int value, + union power_supply_propval *val) +{ + if (value < 0) + return value; + + val->intval = value; + + return 0; +} + +static int bq27xxx_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret = 0; + struct bq27xxx_device_info *di = power_supply_get_drvdata(psy); + + mutex_lock(&di->lock); + if (time_is_before_jiffies(di->last_update + 5 * HZ)) { + cancel_delayed_work_sync(&di->work); + bq27xxx_battery_poll(&di->work.work); + } + mutex_unlock(&di->lock); + + if (psp != POWER_SUPPLY_PROP_PRESENT && di->cache.flags < 0) + return -ENODEV; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + ret = bq27xxx_battery_status(di, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = bq27xxx_battery_voltage(di, val); + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = di->cache.flags < 0 ? 0 : 1; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = bq27xxx_battery_current(di, val); + break; + case POWER_SUPPLY_PROP_CAPACITY: + ret = bq27xxx_simple_value(di->cache.capacity, val); + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + ret = bq27xxx_battery_capacity_level(di, val); + break; + case POWER_SUPPLY_PROP_TEMP: + ret = bq27xxx_simple_value(di->cache.temperature, val); + if (ret == 0) + val->intval -= 2731; + break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: + ret = bq27xxx_simple_value(di->cache.time_to_empty, val); + break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: + ret = bq27xxx_simple_value(di->cache.time_to_empty_avg, val); + break; + case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW: + ret = bq27xxx_simple_value(di->cache.time_to_full, val); + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + ret = bq27xxx_simple_value(bq27xxx_battery_read_nac(di), val); + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + ret = bq27xxx_simple_value(di->cache.charge_full, val); + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + ret = bq27xxx_simple_value(di->charge_design_full, val); + break; + case POWER_SUPPLY_PROP_CYCLE_COUNT: + ret = bq27xxx_simple_value(di->cache.cycle_count, val); + break; + case POWER_SUPPLY_PROP_ENERGY_NOW: + ret = bq27xxx_simple_value(di->cache.energy, val); + break; + case POWER_SUPPLY_PROP_POWER_AVG: + ret = bq27xxx_simple_value(di->cache.power_avg, val); + break; + case POWER_SUPPLY_PROP_HEALTH: + ret = bq27xxx_simple_value(di->cache.health, val); + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = BQ27XXX_MANUFACTURER; + break; + default: + return -EINVAL; + } + + return ret; +} + +static void bq27xxx_external_power_changed(struct power_supply *psy) +{ + struct bq27xxx_device_info *di = power_supply_get_drvdata(psy); + + cancel_delayed_work_sync(&di->work); + schedule_delayed_work(&di->work, 0); +} + +static int bq27xxx_powersupply_init(struct bq27xxx_device_info *di, + const char *name) +{ + int ret; + struct power_supply_desc *psy_desc; + struct power_supply_config psy_cfg = { .drv_data = di, }; + + psy_desc = devm_kzalloc(di->dev, sizeof(*psy_desc), GFP_KERNEL); + if (!psy_desc) + return -ENOMEM; + + psy_desc->name = name; + psy_desc->type = POWER_SUPPLY_TYPE_BATTERY; + if (di->chip == BQ27425) { + psy_desc->properties = bq27425_battery_props; + psy_desc->num_properties = ARRAY_SIZE(bq27425_battery_props); + } else if (di->chip == BQ27742) { + psy_desc->properties = bq27742_battery_props; + psy_desc->num_properties = ARRAY_SIZE(bq27742_battery_props); + } else if (di->chip == BQ27510) { + psy_desc->properties = bq27510_battery_props; + psy_desc->num_properties = ARRAY_SIZE(bq27510_battery_props); + } else { + psy_desc->properties = bq27x00_battery_props; + psy_desc->num_properties = ARRAY_SIZE(bq27x00_battery_props); + } + psy_desc->get_property = bq27xxx_battery_get_property; + psy_desc->external_power_changed = bq27xxx_external_power_changed; + + INIT_DELAYED_WORK(&di->work, bq27xxx_battery_poll); + mutex_init(&di->lock); + + di->bat = power_supply_register_no_ws(di->dev, psy_desc, &psy_cfg); + if (IS_ERR(di->bat)) { + ret = PTR_ERR(di->bat); + dev_err(di->dev, "failed to register battery: %d\n", ret); + return ret; + } + + dev_info(di->dev, "support ver. %s enabled\n", DRIVER_VERSION); + + bq27xxx_battery_update(di); + + return 0; +} + +static void bq27xxx_powersupply_unregister(struct bq27xxx_device_info *di) +{ + /* + * power_supply_unregister call bq27xxx_battery_get_property which + * call bq27xxx_battery_poll. + * Make sure that bq27xxx_battery_poll will not call + * schedule_delayed_work again after unregister (which cause OOPS). + */ + poll_interval = 0; + + cancel_delayed_work_sync(&di->work); + + power_supply_unregister(di->bat); + + mutex_destroy(&di->lock); +} + +/* i2c specific code */ +#ifdef CONFIG_BATTERY_BQ27XXX_I2C + +/* If the system has several batteries we need a different name for each + * of them... + */ +static DEFINE_IDR(battery_id); +static DEFINE_MUTEX(battery_mutex); + +static int bq27xxx_battery_i2c_read(struct bq27xxx_device_info *di, u8 reg, + bool single) +{ + struct i2c_client *client = to_i2c_client(di->dev); + struct i2c_msg msg[2]; + unsigned char data[2]; + int ret; + + if (!client->adapter) + return -ENODEV; + + msg[0].addr = client->addr; + msg[0].flags = 0; + msg[0].buf = ® + msg[0].len = sizeof(reg); + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].buf = data; + if (single) + msg[1].len = 1; + else + msg[1].len = 2; + + ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + if (ret < 0) + return ret; + + if (!single) + ret = get_unaligned_le16(data); + else + ret = data[0]; + + return ret; +} + +static int bq27xxx_battery_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + char *name; + struct bq27xxx_device_info *di; + int num; + int retval = 0; + + /* Get new ID for the new battery device */ + mutex_lock(&battery_mutex); + num = idr_alloc(&battery_id, client, 0, 0, GFP_KERNEL); + mutex_unlock(&battery_mutex); + if (num < 0) + return num; + + name = devm_kasprintf(&client->dev, GFP_KERNEL, "%s-%d", id->name, num); + if (!name) { + retval = -ENOMEM; + goto batt_failed; + } + + di = devm_kzalloc(&client->dev, sizeof(*di), GFP_KERNEL); + if (!di) { + retval = -ENOMEM; + goto batt_failed; + } + + di->id = num; + di->dev = &client->dev; + di->chip = id->driver_data; + di->bus.read = &bq27xxx_battery_i2c_read; + + retval = bq27xxx_powersupply_init(di, name); + if (retval) + goto batt_failed; + + i2c_set_clientdata(client, di); + + return 0; + +batt_failed: + mutex_lock(&battery_mutex); + idr_remove(&battery_id, num); + mutex_unlock(&battery_mutex); + + return retval; +} + +static int bq27xxx_battery_i2c_remove(struct i2c_client *client) +{ + struct bq27xxx_device_info *di = i2c_get_clientdata(client); + + bq27xxx_powersupply_unregister(di); + + mutex_lock(&battery_mutex); + idr_remove(&battery_id, di->id); + mutex_unlock(&battery_mutex); + + return 0; +} + +static const struct i2c_device_id bq27xxx_id[] = { + { "bq27200", BQ27000 }, /* bq27200 is same as bq27000, but with i2c */ + { "bq27500", BQ27500 }, + { "bq27425", BQ27425 }, + { "bq27742", BQ27742 }, + { "bq27510", BQ27510 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, bq27xxx_id); + +static struct i2c_driver bq27xxx_battery_i2c_driver = { + .driver = { + .name = "bq27xxx-battery", + }, + .probe = bq27xxx_battery_i2c_probe, + .remove = bq27xxx_battery_i2c_remove, + .id_table = bq27xxx_id, +}; + +static inline int bq27xxx_battery_i2c_init(void) +{ + int ret = i2c_add_driver(&bq27xxx_battery_i2c_driver); + + if (ret) + pr_err("Unable to register BQ27xxx i2c driver\n"); + + return ret; +} + +static inline void bq27xxx_battery_i2c_exit(void) +{ + i2c_del_driver(&bq27xxx_battery_i2c_driver); +} + +#else + +static inline int bq27xxx_battery_i2c_init(void) { return 0; } +static inline void bq27xxx_battery_i2c_exit(void) {}; + +#endif + +/* platform specific code */ +#ifdef CONFIG_BATTERY_BQ27XXX_PLATFORM + +static int bq27xxx_battery_platform_read(struct bq27xxx_device_info *di, u8 reg, + bool single) +{ + struct device *dev = di->dev; + struct bq27xxx_platform_data *pdata = dev->platform_data; + unsigned int timeout = 3; + int upper, lower; + int temp; + + if (!single) { + /* Make sure the value has not changed in between reading the + * lower and the upper part */ + upper = pdata->read(dev, reg + 1); + do { + temp = upper; + if (upper < 0) + return upper; + + lower = pdata->read(dev, reg); + if (lower < 0) + return lower; + + upper = pdata->read(dev, reg + 1); + } while (temp != upper && --timeout); + + if (timeout == 0) + return -EIO; + + return (upper << 8) | lower; + } + + return pdata->read(dev, reg); +} + +static int bq27xxx_battery_platform_probe(struct platform_device *pdev) +{ + struct bq27xxx_device_info *di; + struct bq27xxx_platform_data *pdata = pdev->dev.platform_data; + const char *name; + + if (!pdata) { + dev_err(&pdev->dev, "no platform_data supplied\n"); + return -EINVAL; + } + + if (!pdata->read) { + dev_err(&pdev->dev, "no hdq read callback supplied\n"); + return -EINVAL; + } + + di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL); + if (!di) + return -ENOMEM; + + platform_set_drvdata(pdev, di); + + di->dev = &pdev->dev; + di->chip = BQ27000; + + name = pdata->name ?: dev_name(&pdev->dev); + di->bus.read = &bq27xxx_battery_platform_read; + + return bq27xxx_powersupply_init(di, name); +} + +static int bq27xxx_battery_platform_remove(struct platform_device *pdev) +{ + struct bq27xxx_device_info *di = platform_get_drvdata(pdev); + + bq27xxx_powersupply_unregister(di); + + return 0; +} + +static struct platform_driver bq27xxx_battery_platform_driver = { + .probe = bq27xxx_battery_platform_probe, + .remove = bq27xxx_battery_platform_remove, + .driver = { + .name = "bq27000-battery", + }, +}; + +static inline int bq27xxx_battery_platform_init(void) +{ + int ret = platform_driver_register(&bq27xxx_battery_platform_driver); + + if (ret) + pr_err("Unable to register BQ27xxx platform driver\n"); + + return ret; +} + +static inline void bq27xxx_battery_platform_exit(void) +{ + platform_driver_unregister(&bq27xxx_battery_platform_driver); +} + +#else + +static inline int bq27xxx_battery_platform_init(void) { return 0; } +static inline void bq27xxx_battery_platform_exit(void) {}; + +#endif + +/* + * Module stuff + */ + +static int __init bq27xxx_battery_init(void) +{ + int ret; + + ret = bq27xxx_battery_i2c_init(); + if (ret) + return ret; + + ret = bq27xxx_battery_platform_init(); + if (ret) + bq27xxx_battery_i2c_exit(); + + return ret; +} +module_init(bq27xxx_battery_init); + +static void __exit bq27xxx_battery_exit(void) +{ + bq27xxx_battery_platform_exit(); + bq27xxx_battery_i2c_exit(); +} +module_exit(bq27xxx_battery_exit); + +#ifdef CONFIG_BATTERY_BQ27XXX_PLATFORM +MODULE_ALIAS("platform:bq27000-battery"); +#endif + +MODULE_AUTHOR("Rodolfo Giometti "); +MODULE_DESCRIPTION("BQ27xxx battery monitor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/w1/slaves/w1_bq27000.c b/drivers/w1/slaves/w1_bq27000.c index caafb1722783..84805317a5ce 100644 --- a/drivers/w1/slaves/w1_bq27000.c +++ b/drivers/w1/slaves/w1_bq27000.c @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include "../w1.h" #include "../w1_int.h" @@ -39,7 +39,7 @@ static int w1_bq27000_read(struct device *dev, unsigned int reg) return val; } -static struct bq27000_platform_data bq27000_battery_info = { +static struct bq27xxx_platform_data bq27000_battery_info = { .read = w1_bq27000_read, .name = "bq27000-battery", }; -- cgit v1.2.3 From 424cfde49acaf1426e90837961e8aead0238b11b Mon Sep 17 00:00:00 2001 From: "Andrew F. Davis" Date: Tue, 22 Sep 2015 14:35:07 -0500 Subject: power: bq27xxx_battery: Platform initialization must declare a device When initialized as a platform device the initializer must now specify a device. An empty device name is no longer valid. Signed-off-by: Andrew F. Davis Signed-off-by: Sebastian Reichel --- drivers/power/bq27xxx_battery.c | 9 ++++++--- drivers/w1/slaves/w1_bq27000.c | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/power/bq27xxx_battery.c b/drivers/power/bq27xxx_battery.c index e72055ce7b3c..073441383df1 100644 --- a/drivers/power/bq27xxx_battery.c +++ b/drivers/power/bq27xxx_battery.c @@ -91,8 +91,6 @@ struct bq27xxx_access_methods { int (*read)(struct bq27xxx_device_info *di, u8 reg, bool single); }; -enum bq27xxx_chip { BQ27000, BQ27500, BQ27425, BQ27742, BQ27510}; - struct bq27xxx_reg_cache { int temperature; int time_to_empty; @@ -1036,6 +1034,11 @@ static int bq27xxx_battery_platform_probe(struct platform_device *pdev) return -EINVAL; } + if (!pdata->chip) { + dev_err(&pdev->dev, "no device supplied\n"); + return -EINVAL; + } + di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL); if (!di) return -ENOMEM; @@ -1043,7 +1046,7 @@ static int bq27xxx_battery_platform_probe(struct platform_device *pdev) platform_set_drvdata(pdev, di); di->dev = &pdev->dev; - di->chip = BQ27000; + di->chip = pdata->chip; name = pdata->name ?: dev_name(&pdev->dev); di->bus.read = &bq27xxx_battery_platform_read; diff --git a/drivers/w1/slaves/w1_bq27000.c b/drivers/w1/slaves/w1_bq27000.c index 84805317a5ce..9f4a86b754ba 100644 --- a/drivers/w1/slaves/w1_bq27000.c +++ b/drivers/w1/slaves/w1_bq27000.c @@ -42,6 +42,7 @@ static int w1_bq27000_read(struct device *dev, unsigned int reg) static struct bq27xxx_platform_data bq27000_battery_info = { .read = w1_bq27000_read, .name = "bq27000-battery", + .chip = BQ27000, }; static int w1_bq27000_add_slave(struct w1_slave *sl) -- cgit v1.2.3 From c570903290d7cb542c06b8985b7326a8dd4cdf86 Mon Sep 17 00:00:00 2001 From: "Andrew F. Davis" Date: Tue, 22 Sep 2015 14:35:08 -0500 Subject: power: bq27xxx_battery: Fix typos and change naming for state of charge functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix typos and change "relative state of charge" to "state of charge" as not all supported devices use relative state of charge. Signed-off-by: Andrew F. Davis Acked-by: Pali Rohár Signed-off-by: Sebastian Reichel --- drivers/power/bq27xxx_battery.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) (limited to 'drivers') diff --git a/drivers/power/bq27xxx_battery.c b/drivers/power/bq27xxx_battery.c index 073441383df1..1ff88ade97fd 100644 --- a/drivers/power/bq27xxx_battery.c +++ b/drivers/power/bq27xxx_battery.c @@ -229,26 +229,26 @@ static bool bq27xxx_is_chip_version_higher(struct bq27xxx_device_info *di) } /* - * Return the battery Relative State-of-Charge + * Return the battery State-of-Charge * Or < 0 if something fails. */ -static int bq27xxx_battery_read_rsoc(struct bq27xxx_device_info *di) +static int bq27xxx_battery_read_soc(struct bq27xxx_device_info *di) { - int rsoc; + int soc; if (di->chip == BQ27500 || di->chip == BQ27742) - rsoc = bq27xxx_read(di, BQ27500_REG_SOC, false); + soc = bq27xxx_read(di, BQ27500_REG_SOC, false); else if (di->chip == BQ27510) - rsoc = bq27xxx_read(di, BQ27510_REG_SOC, false); + soc = bq27xxx_read(di, BQ27510_REG_SOC, false); else if (di->chip == BQ27425) - rsoc = bq27xxx_read(di, BQ27425_REG_SOC, false); - else - rsoc = bq27xxx_read(di, BQ27000_REG_RSOC, true); + soc = bq27xxx_read(di, BQ27425_REG_SOC, false); + else /* for the bq27000 we read the "relative" SoC register */ + soc = bq27xxx_read(di, BQ27000_REG_RSOC, true); - if (rsoc < 0) - dev_dbg(di->dev, "error reading relative State-of-Charge\n"); + if (soc < 0) + dev_dbg(di->dev, "error reading State-of-Charge\n"); - return rsoc; + return soc; } /* @@ -275,7 +275,7 @@ static int bq27xxx_battery_read_charge(struct bq27xxx_device_info *di, u8 reg) } /* - * Return the battery Nominal available capaciy in µAh + * Return the battery Nominal available capacity in µAh * Or < 0 if something fails. */ static inline int bq27xxx_battery_read_nac(struct bq27xxx_device_info *di) @@ -416,7 +416,7 @@ static int bq27xxx_battery_read_time(struct bq27xxx_device_info *di, u8 reg) } /* - * Read a power avg register. + * Read an average power register. * Return < 0 if something fails. */ static int bq27xxx_battery_read_pwr_avg(struct bq27xxx_device_info *di, u8 reg) @@ -498,7 +498,7 @@ static void bq27xxx_battery_update(struct bq27xxx_device_info *di) cache.charge_full = -ENODATA; cache.health = -ENODATA; } else { - cache.capacity = bq27xxx_battery_read_rsoc(di); + cache.capacity = bq27xxx_battery_read_soc(di); if (is_bq27742 || is_bq27510) cache.time_to_empty = bq27xxx_battery_read_time(di, -- cgit v1.2.3 From d74534c27775857cb09abd0f92ed9539dc8d0a93 Mon Sep 17 00:00:00 2001 From: "Andrew F. Davis" Date: Tue, 22 Sep 2015 14:35:09 -0500 Subject: power: bq27xxx_battery: Add support for additional bq27xxx family devices Add support for additional devices and register equivalent family devices including the bq27010, bq27210, bq27500, bq27510, bq27520, bq27530, bq27531, bq27541, bq27542, bq27546, bq27545, bq27441, bq27421, and the bq27641. To facilitate this process the register mapings have been moved to tables and other small cleanups have been made. Signed-off-by: Andrew F. Davis Signed-off-by: Sebastian Reichel --- drivers/power/bq27xxx_battery.c | 622 ++++++++++++++++++++++++++-------------- 1 file changed, 412 insertions(+), 210 deletions(-) (limited to 'drivers') diff --git a/drivers/power/bq27xxx_battery.c b/drivers/power/bq27xxx_battery.c index 1ff88ade97fd..236c5888aa9d 100644 --- a/drivers/power/bq27xxx_battery.c +++ b/drivers/power/bq27xxx_battery.c @@ -17,11 +17,24 @@ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. * * Datasheets: - * http://focus.ti.com/docs/prod/folders/print/bq27000.html - * http://focus.ti.com/docs/prod/folders/print/bq27500.html + * http://www.ti.com/product/bq27000 + * http://www.ti.com/product/bq27200 + * http://www.ti.com/product/bq27010 + * http://www.ti.com/product/bq27210 + * http://www.ti.com/product/bq27500 + * http://www.ti.com/product/bq27510-g3 + * http://www.ti.com/product/bq27520-g4 + * http://www.ti.com/product/bq27530-g1 + * http://www.ti.com/product/bq27531-g1 + * http://www.ti.com/product/bq27541-g1 + * http://www.ti.com/product/bq27542-g1 + * http://www.ti.com/product/bq27546-g1 + * http://www.ti.com/product/bq27742-g1 + * http://www.ti.com/product/bq27545-g1 + * http://www.ti.com/product/bq27421-g1 * http://www.ti.com/product/bq27425-g1 - * http://www.ti.com/product/BQ27742-G1 - * http://www.ti.com/product/BQ27510-G3 + * http://www.ti.com/product/bq27411-g1 + * http://www.ti.com/product/bq27621-g1 */ #include @@ -43,54 +56,57 @@ #define BQ27XXX_MANUFACTURER "Texas Instruments" -#define BQ27x00_REG_TEMP 0x06 -#define BQ27x00_REG_VOLT 0x08 -#define BQ27x00_REG_AI 0x14 -#define BQ27x00_REG_FLAGS 0x0A -#define BQ27x00_REG_TTE 0x16 -#define BQ27x00_REG_TTF 0x18 -#define BQ27x00_REG_TTECP 0x26 -#define BQ27x00_REG_NAC 0x0C /* Nominal available capacity */ -#define BQ27x00_REG_LMD 0x12 /* Last measured discharge */ -#define BQ27x00_REG_CYCT 0x2A /* Cycle count total */ -#define BQ27x00_REG_AE 0x22 /* Available energy */ -#define BQ27x00_POWER_AVG 0x24 - -#define BQ27000_REG_RSOC 0x0B /* Relative State-of-Charge */ -#define BQ27000_REG_ILMD 0x76 /* Initial last measured discharge */ +/* BQ27XXX Flags */ +#define BQ27XXX_FLAG_DSC BIT(0) +#define BQ27XXX_FLAG_SOCF BIT(1) /* State-of-Charge threshold final */ +#define BQ27XXX_FLAG_SOC1 BIT(2) /* State-of-Charge threshold 1 */ +#define BQ27XXX_FLAG_FC BIT(9) +#define BQ27XXX_FLAG_OTD BIT(14) +#define BQ27XXX_FLAG_OTC BIT(15) + +/* BQ27000 has different layout for Flags register */ #define BQ27000_FLAG_EDVF BIT(0) /* Final End-of-Discharge-Voltage flag */ #define BQ27000_FLAG_EDV1 BIT(1) /* First End-of-Discharge-Voltage flag */ #define BQ27000_FLAG_CI BIT(4) /* Capacity Inaccurate flag */ #define BQ27000_FLAG_FC BIT(5) #define BQ27000_FLAG_CHGS BIT(7) /* Charge state flag */ -#define BQ27500_REG_SOC 0x2C -#define BQ27500_REG_DCAP 0x3C /* Design capacity */ -#define BQ27500_FLAG_DSC BIT(0) -#define BQ27500_FLAG_SOCF BIT(1) /* State-of-Charge threshold final */ -#define BQ27500_FLAG_SOC1 BIT(2) /* State-of-Charge threshold 1 */ -#define BQ27500_FLAG_FC BIT(9) -#define BQ27500_FLAG_OTC BIT(15) - -#define BQ27742_POWER_AVG 0x76 - -#define BQ27510_REG_SOC 0x20 -#define BQ27510_REG_DCAP 0x2E /* Design capacity */ -#define BQ27510_REG_CYCT 0x1E /* Cycle count total */ - -/* bq27425 register addresses are same as bq27x00 addresses minus 4 */ -#define BQ27425_REG_OFFSET 0x04 -#define BQ27425_REG_SOC (0x1C + BQ27425_REG_OFFSET) -#define BQ27425_REG_DCAP (0x3C + BQ27425_REG_OFFSET) - -#define BQ27XXX_RS 20 /* Resistor sense */ -#define BQ27XXX_POWER_CONSTANT (256 * 29200 / 1000) +#define BQ27XXX_RS (20) /* Resistor sense mOhm */ +#define BQ27XXX_POWER_CONSTANT (29200) /* 29.2 µV^2 * 1000 */ +#define BQ27XXX_CURRENT_CONSTANT (3570) /* 3.57 µV * 1000 */ struct bq27xxx_device_info; struct bq27xxx_access_methods { int (*read)(struct bq27xxx_device_info *di, u8 reg, bool single); }; +#define INVALID_REG_ADDR 0xff + +/* + * bq27xxx_reg_index - Register names + * + * These are indexes into a device's register mapping array. + */ +enum bq27xxx_reg_index { + BQ27XXX_REG_CTRL = 0, /* Control */ + BQ27XXX_REG_TEMP, /* Temperature */ + BQ27XXX_REG_INT_TEMP, /* Internal Temperature */ + BQ27XXX_REG_VOLT, /* Voltage */ + BQ27XXX_REG_AI, /* Average Current */ + BQ27XXX_REG_FLAGS, /* Flags */ + BQ27XXX_REG_TTE, /* Time-to-Empty */ + BQ27XXX_REG_TTF, /* Time-to-Full */ + BQ27XXX_REG_TTES, /* Time-to-Empty Standby */ + BQ27XXX_REG_TTECP, /* Time-to-Empty at Constant Power */ + BQ27XXX_REG_NAC, /* Nominal Available Capacity */ + BQ27XXX_REG_FCC, /* Full Charge Capacity */ + BQ27XXX_REG_CYCT, /* Cycle Count */ + BQ27XXX_REG_AE, /* Available Energy */ + BQ27XXX_REG_SOC, /* State-of-Charge */ + BQ27XXX_REG_DCAP, /* Design Capacity */ + BQ27XXX_REG_AP, /* Average Power */ +}; + struct bq27xxx_reg_cache { int temperature; int time_to_empty; @@ -121,9 +137,162 @@ struct bq27xxx_device_info { struct bq27xxx_access_methods bus; struct mutex lock; + + u8 *regs; +}; + +/* Register mappings */ +static u8 bq27000_regs[] = { + 0x00, /* CONTROL */ + 0x06, /* TEMP */ + INVALID_REG_ADDR, /* INT TEMP - NA*/ + 0x08, /* VOLT */ + 0x14, /* AVG CURR */ + 0x0a, /* FLAGS */ + 0x16, /* TTE */ + 0x18, /* TTF */ + 0x1c, /* TTES */ + 0x26, /* TTECP */ + 0x0c, /* NAC */ + 0x12, /* LMD(FCC) */ + 0x2a, /* CYCT */ + 0x22, /* AE */ + 0x0b, /* SOC(RSOC) */ + 0x76, /* DCAP(ILMD) */ + 0x24, /* AP */ +}; + +static u8 bq27010_regs[] = { + 0x00, /* CONTROL */ + 0x06, /* TEMP */ + INVALID_REG_ADDR, /* INT TEMP - NA*/ + 0x08, /* VOLT */ + 0x14, /* AVG CURR */ + 0x0a, /* FLAGS */ + 0x16, /* TTE */ + 0x18, /* TTF */ + 0x1c, /* TTES */ + 0x26, /* TTECP */ + 0x0c, /* NAC */ + 0x12, /* LMD(FCC) */ + 0x2a, /* CYCT */ + INVALID_REG_ADDR, /* AE - NA */ + 0x0b, /* SOC(RSOC) */ + 0x76, /* DCAP(ILMD) */ + INVALID_REG_ADDR, /* AP - NA */ +}; + +static u8 bq27500_regs[] = { + 0x00, /* CONTROL */ + 0x06, /* TEMP */ + 0x28, /* INT TEMP */ + 0x08, /* VOLT */ + 0x14, /* AVG CURR */ + 0x0a, /* FLAGS */ + 0x16, /* TTE */ + INVALID_REG_ADDR, /* TTF - NA */ + 0x1a, /* TTES */ + INVALID_REG_ADDR, /* TTECP - NA */ + 0x0c, /* NAC */ + 0x12, /* LMD(FCC) */ + 0x1e, /* CYCT */ + INVALID_REG_ADDR, /* AE - NA */ + 0x20, /* SOC(RSOC) */ + 0x2e, /* DCAP(ILMD) */ + INVALID_REG_ADDR, /* AP - NA */ }; -static enum power_supply_property bq27x00_battery_props[] = { +static u8 bq27530_regs[] = { + 0x00, /* CONTROL */ + 0x06, /* TEMP */ + 0x32, /* INT TEMP */ + 0x08, /* VOLT */ + 0x14, /* AVG CURR */ + 0x0a, /* FLAGS */ + 0x16, /* TTE */ + INVALID_REG_ADDR, /* TTF - NA */ + INVALID_REG_ADDR, /* TTES - NA */ + INVALID_REG_ADDR, /* TTECP - NA */ + 0x0c, /* NAC */ + 0x12, /* LMD(FCC) */ + 0x2a, /* CYCT */ + INVALID_REG_ADDR, /* AE - NA */ + 0x2c, /* SOC(RSOC) */ + INVALID_REG_ADDR, /* DCAP - NA */ + 0x24, /* AP */ +}; + +static u8 bq27541_regs[] = { + 0x00, /* CONTROL */ + 0x06, /* TEMP */ + 0x28, /* INT TEMP */ + 0x08, /* VOLT */ + 0x14, /* AVG CURR */ + 0x0a, /* FLAGS */ + 0x16, /* TTE */ + INVALID_REG_ADDR, /* TTF - NA */ + INVALID_REG_ADDR, /* TTES - NA */ + INVALID_REG_ADDR, /* TTECP - NA */ + 0x0c, /* NAC */ + 0x12, /* LMD(FCC) */ + 0x2a, /* CYCT */ + INVALID_REG_ADDR, /* AE - NA */ + 0x2c, /* SOC(RSOC) */ + 0x3c, /* DCAP */ + 0x76, /* AP */ +}; + +static u8 bq27545_regs[] = { + 0x00, /* CONTROL */ + 0x06, /* TEMP */ + 0x28, /* INT TEMP */ + 0x08, /* VOLT */ + 0x14, /* AVG CURR */ + 0x0a, /* FLAGS */ + 0x16, /* TTE */ + INVALID_REG_ADDR, /* TTF - NA */ + INVALID_REG_ADDR, /* TTES - NA */ + INVALID_REG_ADDR, /* TTECP - NA */ + 0x0c, /* NAC */ + 0x12, /* LMD(FCC) */ + 0x2a, /* CYCT */ + INVALID_REG_ADDR, /* AE - NA */ + 0x2c, /* SOC(RSOC) */ + INVALID_REG_ADDR, /* DCAP - NA */ + 0x24, /* AP */ +}; + +static u8 bq27421_regs[] = { + 0x00, /* CONTROL */ + 0x02, /* TEMP */ + 0x1e, /* INT TEMP */ + 0x04, /* VOLT */ + 0x10, /* AVG CURR */ + 0x06, /* FLAGS */ + INVALID_REG_ADDR, /* TTE - NA */ + INVALID_REG_ADDR, /* TTF - NA */ + INVALID_REG_ADDR, /* TTES - NA */ + INVALID_REG_ADDR, /* TTECP - NA */ + 0x08, /* NAC */ + 0x0e, /* FCC */ + INVALID_REG_ADDR, /* CYCT - NA */ + INVALID_REG_ADDR, /* AE - NA */ + 0x1c, /* SOC */ + 0x3c, /* DCAP */ + 0x18, /* AP */ +}; + +static u8 *bq27xxx_regs[] = { + [BQ27000] = bq27000_regs, + [BQ27010] = bq27010_regs, + [BQ27500] = bq27500_regs, + [BQ27530] = bq27530_regs, + [BQ27541] = bq27541_regs, + [BQ27545] = bq27545_regs, + [BQ27421] = bq27421_regs, +}; + +static enum power_supply_property bq27000_battery_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_VOLTAGE_NOW, @@ -145,7 +314,7 @@ static enum power_supply_property bq27x00_battery_props[] = { POWER_SUPPLY_PROP_MANUFACTURER, }; -static enum power_supply_property bq27425_battery_props[] = { +static enum power_supply_property bq27010_battery_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_VOLTAGE_NOW, @@ -153,14 +322,19 @@ static enum power_supply_property bq27425_battery_props[] = { POWER_SUPPLY_PROP_CAPACITY, POWER_SUPPLY_PROP_CAPACITY_LEVEL, POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, + POWER_SUPPLY_PROP_TIME_TO_FULL_NOW, POWER_SUPPLY_PROP_TECHNOLOGY, POWER_SUPPLY_PROP_CHARGE_FULL, POWER_SUPPLY_PROP_CHARGE_NOW, POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_MANUFACTURER, }; -static enum power_supply_property bq27742_battery_props[] = { +static enum power_supply_property bq27500_battery_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_VOLTAGE_NOW, @@ -174,12 +348,29 @@ static enum power_supply_property bq27742_battery_props[] = { POWER_SUPPLY_PROP_CHARGE_NOW, POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static enum power_supply_property bq27530_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, POWER_SUPPLY_PROP_POWER_AVG, POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_CYCLE_COUNT, POWER_SUPPLY_PROP_MANUFACTURER, }; -static enum power_supply_property bq27510_battery_props[] = { +static enum power_supply_property bq27541_battery_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_VOLTAGE_NOW, @@ -198,6 +389,58 @@ static enum power_supply_property bq27510_battery_props[] = { POWER_SUPPLY_PROP_MANUFACTURER, }; +static enum power_supply_property bq27545_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_POWER_AVG, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static enum power_supply_property bq27421_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +#define BQ27XXX_PROP(_id, _prop) \ + [_id] = { \ + .props = _prop, \ + .size = ARRAY_SIZE(_prop), \ + } + +static struct { + enum power_supply_property *props; + size_t size; +} bq27xxx_battery_props[] = { + BQ27XXX_PROP(BQ27000, bq27000_battery_props), + BQ27XXX_PROP(BQ27010, bq27010_battery_props), + BQ27XXX_PROP(BQ27500, bq27500_battery_props), + BQ27XXX_PROP(BQ27530, bq27530_battery_props), + BQ27XXX_PROP(BQ27541, bq27541_battery_props), + BQ27XXX_PROP(BQ27545, bq27545_battery_props), + BQ27XXX_PROP(BQ27421, bq27421_battery_props), +}; + static unsigned int poll_interval = 360; module_param(poll_interval, uint, 0644); MODULE_PARM_DESC(poll_interval, @@ -207,25 +450,14 @@ MODULE_PARM_DESC(poll_interval, * Common code for BQ27xxx devices */ -static inline int bq27xxx_read(struct bq27xxx_device_info *di, u8 reg, +static inline int bq27xxx_read(struct bq27xxx_device_info *di, int reg_index, bool single) { - if (di->chip == BQ27425) - return di->bus.read(di, reg - BQ27425_REG_OFFSET, single); - return di->bus.read(di, reg, single); -} + /* Reports EINVAL for invalid/missing registers */ + if (!di || di->regs[reg_index] == INVALID_REG_ADDR) + return -EINVAL; -/* - * Higher versions of the chip like BQ27425 and BQ27500 - * differ from BQ27000 and BQ27200 in calculation of certain - * parameters. Hence we need to check for the chip type. - */ -static bool bq27xxx_is_chip_version_higher(struct bq27xxx_device_info *di) -{ - if (di->chip == BQ27425 || di->chip == BQ27500 || di->chip == BQ27742 - || di->chip == BQ27510) - return true; - return false; + return di->bus.read(di, di->regs[reg_index], single); } /* @@ -236,14 +468,7 @@ static int bq27xxx_battery_read_soc(struct bq27xxx_device_info *di) { int soc; - if (di->chip == BQ27500 || di->chip == BQ27742) - soc = bq27xxx_read(di, BQ27500_REG_SOC, false); - else if (di->chip == BQ27510) - soc = bq27xxx_read(di, BQ27510_REG_SOC, false); - else if (di->chip == BQ27425) - soc = bq27xxx_read(di, BQ27425_REG_SOC, false); - else /* for the bq27000 we read the "relative" SoC register */ - soc = bq27xxx_read(di, BQ27000_REG_RSOC, true); + soc = bq27xxx_read(di, BQ27XXX_REG_SOC, false); if (soc < 0) dev_dbg(di->dev, "error reading State-of-Charge\n"); @@ -266,10 +491,10 @@ static int bq27xxx_battery_read_charge(struct bq27xxx_device_info *di, u8 reg) return charge; } - if (bq27xxx_is_chip_version_higher(di)) - charge *= 1000; + if (di->chip == BQ27000 || di->chip == BQ27010) + charge *= BQ27XXX_CURRENT_CONSTANT / BQ27XXX_RS; else - charge = charge * 3570 / BQ27XXX_RS; + charge *= 1000; return charge; } @@ -281,57 +506,46 @@ static int bq27xxx_battery_read_charge(struct bq27xxx_device_info *di, u8 reg) static inline int bq27xxx_battery_read_nac(struct bq27xxx_device_info *di) { int flags; - bool is_bq27500 = di->chip == BQ27500; - bool is_bq27742 = di->chip == BQ27742; - bool is_higher = bq27xxx_is_chip_version_higher(di); - bool flags_1b = !(is_bq27500 || is_bq27742); - flags = bq27xxx_read(di, BQ27x00_REG_FLAGS, flags_1b); - if (flags >= 0 && !is_higher && (flags & BQ27000_FLAG_CI)) - return -ENODATA; + if (di->chip == BQ27000 || di->chip == BQ27010) { + flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, true); + if (flags >= 0 && (flags & BQ27000_FLAG_CI)) + return -ENODATA; + } - return bq27xxx_battery_read_charge(di, BQ27x00_REG_NAC); + return bq27xxx_battery_read_charge(di, BQ27XXX_REG_NAC); } /* - * Return the battery Last measured discharge in µAh + * Return the battery Full Charge Capacity in µAh * Or < 0 if something fails. */ -static inline int bq27xxx_battery_read_lmd(struct bq27xxx_device_info *di) +static inline int bq27xxx_battery_read_fcc(struct bq27xxx_device_info *di) { - return bq27xxx_battery_read_charge(di, BQ27x00_REG_LMD); + return bq27xxx_battery_read_charge(di, BQ27XXX_REG_FCC); } /* - * Return the battery Initial last measured discharge in µAh + * Return the Design Capacity in µAh * Or < 0 if something fails. */ -static int bq27xxx_battery_read_ilmd(struct bq27xxx_device_info *di) +static int bq27xxx_battery_read_dcap(struct bq27xxx_device_info *di) { - int ilmd; + int dcap; - if (bq27xxx_is_chip_version_higher(di)) { - if (di->chip == BQ27425) - ilmd = bq27xxx_read(di, BQ27425_REG_DCAP, false); - else if (di->chip == BQ27510) - ilmd = bq27xxx_read(di, BQ27510_REG_DCAP, false); - else - ilmd = bq27xxx_read(di, BQ27500_REG_DCAP, false); - } else { - ilmd = bq27xxx_read(di, BQ27000_REG_ILMD, true); - } + dcap = bq27xxx_read(di, BQ27XXX_REG_DCAP, false); - if (ilmd < 0) { + if (dcap < 0) { dev_dbg(di->dev, "error reading initial last measured discharge\n"); - return ilmd; + return dcap; } - if (bq27xxx_is_chip_version_higher(di)) - ilmd *= 1000; + if (di->chip == BQ27000 || di->chip == BQ27010) + dcap *= BQ27XXX_CURRENT_CONSTANT / BQ27XXX_RS; else - ilmd = ilmd * 256 * 3570 / BQ27XXX_RS; + dcap *= 1000; - return ilmd; + return dcap; } /* @@ -342,16 +556,16 @@ static int bq27xxx_battery_read_energy(struct bq27xxx_device_info *di) { int ae; - ae = bq27xxx_read(di, BQ27x00_REG_AE, false); + ae = bq27xxx_read(di, BQ27XXX_REG_AE, false); if (ae < 0) { dev_dbg(di->dev, "error reading available energy\n"); return ae; } - if (di->chip == BQ27500) - ae *= 1000; + if (di->chip == BQ27000 || di->chip == BQ27010) + ae *= BQ27XXX_POWER_CONSTANT / BQ27XXX_RS; else - ae = ae * 29200 / BQ27XXX_RS; + ae *= 1000; return ae; } @@ -364,13 +578,13 @@ static int bq27xxx_battery_read_temperature(struct bq27xxx_device_info *di) { int temp; - temp = bq27xxx_read(di, BQ27x00_REG_TEMP, false); + temp = bq27xxx_read(di, BQ27XXX_REG_TEMP, false); if (temp < 0) { dev_err(di->dev, "error reading temperature\n"); return temp; } - if (!bq27xxx_is_chip_version_higher(di)) + if (di->chip == BQ27000 || di->chip == BQ27010) temp = 5 * temp / 2; return temp; @@ -384,10 +598,7 @@ static int bq27xxx_battery_read_cyct(struct bq27xxx_device_info *di) { int cyct; - if (di->chip == BQ27510) - cyct = bq27xxx_read(di, BQ27510_REG_CYCT, false); - else - cyct = bq27xxx_read(di, BQ27x00_REG_CYCT, false); + cyct = bq27xxx_read(di, BQ27XXX_REG_CYCT, false); if (cyct < 0) dev_err(di->dev, "error reading cycle count total\n"); @@ -419,21 +630,32 @@ static int bq27xxx_battery_read_time(struct bq27xxx_device_info *di, u8 reg) * Read an average power register. * Return < 0 if something fails. */ -static int bq27xxx_battery_read_pwr_avg(struct bq27xxx_device_info *di, u8 reg) +static int bq27xxx_battery_read_pwr_avg(struct bq27xxx_device_info *di) { int tval; - tval = bq27xxx_read(di, reg, false); + tval = bq27xxx_read(di, BQ27XXX_REG_AP, false); if (tval < 0) { - dev_err(di->dev, "error reading power avg rgister %02x: %d\n", - reg, tval); + dev_err(di->dev, "error reading average power register %02x: %d\n", + BQ27XXX_REG_AP, tval); return tval; } - if (di->chip == BQ27500) + if (di->chip == BQ27000 || di->chip == BQ27010) + return (tval * BQ27XXX_POWER_CONSTANT) / BQ27XXX_RS; + else return tval; +} + +/* + * Returns true if a battery over temperature condition is detected + */ +static int bq27xxx_battery_overtemp(struct bq27xxx_device_info *di, u16 flags) +{ + if (di->chip == BQ27500 || di->chip == BQ27541) + return flags & (BQ27XXX_FLAG_OTC | BQ27XXX_FLAG_OTD); else - return (tval * BQ27XXX_POWER_CONSTANT) / BQ27XXX_RS; + return flags & BQ27XXX_FLAG_OTC; } /* @@ -442,53 +664,43 @@ static int bq27xxx_battery_read_pwr_avg(struct bq27xxx_device_info *di, u8 reg) */ static int bq27xxx_battery_read_health(struct bq27xxx_device_info *di) { - int tval; + u16 tval; - tval = bq27xxx_read(di, BQ27x00_REG_FLAGS, false); + tval = bq27xxx_read(di, BQ27XXX_REG_FLAGS, false); if (tval < 0) { dev_err(di->dev, "error reading flag register:%d\n", tval); return tval; } - if (di->chip == BQ27500) { - if (tval & BQ27500_FLAG_SOCF) + if (di->chip == BQ27000 || di->chip == BQ27010) { + if (tval & BQ27000_FLAG_EDV1) tval = POWER_SUPPLY_HEALTH_DEAD; - else if (tval & BQ27500_FLAG_OTC) - tval = POWER_SUPPLY_HEALTH_OVERHEAT; else tval = POWER_SUPPLY_HEALTH_GOOD; - return tval; - } else if (di->chip == BQ27510) { - if (tval & BQ27500_FLAG_OTC) - return POWER_SUPPLY_HEALTH_OVERHEAT; - return POWER_SUPPLY_HEALTH_GOOD; } else { - if (tval & BQ27000_FLAG_EDV1) + if (tval & BQ27XXX_FLAG_SOCF) tval = POWER_SUPPLY_HEALTH_DEAD; + else if (bq27xxx_battery_overtemp(di, tval)) + tval = POWER_SUPPLY_HEALTH_OVERHEAT; else tval = POWER_SUPPLY_HEALTH_GOOD; - return tval; } - return -1; + return tval; } static void bq27xxx_battery_update(struct bq27xxx_device_info *di) { struct bq27xxx_reg_cache cache = {0, }; - bool is_bq27500 = di->chip == BQ27500; - bool is_bq27510 = di->chip == BQ27510; - bool is_bq27425 = di->chip == BQ27425; - bool is_bq27742 = di->chip == BQ27742; - bool flags_1b = !(is_bq27500 || is_bq27742); + bool has_ci_flag = di->chip == BQ27000 || di->chip == BQ27010; + bool has_singe_flag = di->chip == BQ27000 || di->chip == BQ27010; - cache.flags = bq27xxx_read(di, BQ27x00_REG_FLAGS, flags_1b); + cache.flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, has_singe_flag); if ((cache.flags & 0xff) == 0xff) - /* read error */ - cache.flags = -1; + cache.flags = -1; /* read error */ if (cache.flags >= 0) { - if (!is_bq27500 && !is_bq27425 && !is_bq27742 && !is_bq27510 - && (cache.flags & BQ27000_FLAG_CI)) { + cache.temperature = bq27xxx_battery_read_temperature(di); + if (has_ci_flag && (cache.flags & BQ27000_FLAG_CI)) { dev_info(di->dev, "battery is not calibrated! ignoring capacity values\n"); cache.capacity = -ENODATA; cache.energy = -ENODATA; @@ -498,41 +710,26 @@ static void bq27xxx_battery_update(struct bq27xxx_device_info *di) cache.charge_full = -ENODATA; cache.health = -ENODATA; } else { + if (di->regs[BQ27XXX_REG_TTE] != INVALID_REG_ADDR) + cache.time_to_empty = bq27xxx_battery_read_time(di, BQ27XXX_REG_TTE); + if (di->regs[BQ27XXX_REG_TTECP] != INVALID_REG_ADDR) + cache.time_to_empty_avg = bq27xxx_battery_read_time(di, BQ27XXX_REG_TTECP); + if (di->regs[BQ27XXX_REG_TTF] != INVALID_REG_ADDR) + cache.time_to_full = bq27xxx_battery_read_time(di, BQ27XXX_REG_TTF); + cache.charge_full = bq27xxx_battery_read_fcc(di); cache.capacity = bq27xxx_battery_read_soc(di); - if (is_bq27742 || is_bq27510) - cache.time_to_empty = - bq27xxx_battery_read_time(di, - BQ27x00_REG_TTE); - else if (!is_bq27425) { + if (di->regs[BQ27XXX_REG_AE] != INVALID_REG_ADDR) cache.energy = bq27xxx_battery_read_energy(di); - cache.time_to_empty = - bq27xxx_battery_read_time(di, - BQ27x00_REG_TTE); - cache.time_to_empty_avg = - bq27xxx_battery_read_time(di, - BQ27x00_REG_TTECP); - cache.time_to_full = - bq27xxx_battery_read_time(di, - BQ27x00_REG_TTF); - } - cache.charge_full = bq27xxx_battery_read_lmd(di); cache.health = bq27xxx_battery_read_health(di); } - cache.temperature = bq27xxx_battery_read_temperature(di); - if (!is_bq27425) + if (di->regs[BQ27XXX_REG_CYCT] != INVALID_REG_ADDR) cache.cycle_count = bq27xxx_battery_read_cyct(di); - if (is_bq27742) - cache.power_avg = - bq27xxx_battery_read_pwr_avg(di, - BQ27742_POWER_AVG); - else - cache.power_avg = - bq27xxx_battery_read_pwr_avg(di, - BQ27x00_POWER_AVG); + if (di->regs[BQ27XXX_REG_AP] != INVALID_REG_ADDR) + cache.power_avg = bq27xxx_battery_read_pwr_avg(di); /* We only have to read charge design full once */ if (di->charge_design_full <= 0) - di->charge_design_full = bq27xxx_battery_read_ilmd(di); + di->charge_design_full = bq27xxx_battery_read_dcap(di); } if (di->cache.capacity != cache.capacity) @@ -547,7 +744,8 @@ static void bq27xxx_battery_update(struct bq27xxx_device_info *di) static void bq27xxx_battery_poll(struct work_struct *work) { struct bq27xxx_device_info *di = - container_of(work, struct bq27xxx_device_info, work.work); + container_of(work, struct bq27xxx_device_info, + work.work); bq27xxx_battery_update(di); @@ -569,23 +767,23 @@ static int bq27xxx_battery_current(struct bq27xxx_device_info *di, int curr; int flags; - curr = bq27xxx_read(di, BQ27x00_REG_AI, false); + curr = bq27xxx_read(di, BQ27XXX_REG_AI, false); if (curr < 0) { dev_err(di->dev, "error reading current\n"); return curr; } - if (bq27xxx_is_chip_version_higher(di)) { - /* bq27500 returns signed value */ - val->intval = (int)((s16)curr) * 1000; - } else { - flags = bq27xxx_read(di, BQ27x00_REG_FLAGS, false); + if (di->chip == BQ27000 || di->chip == BQ27010) { + flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, false); if (flags & BQ27000_FLAG_CHGS) { dev_dbg(di->dev, "negative current!\n"); curr = -curr; } - val->intval = curr * 3570 / BQ27XXX_RS; + val->intval = curr * BQ27XXX_CURRENT_CONSTANT / BQ27XXX_RS; + } else { + /* Other gauges return signed value */ + val->intval = (int)((s16)curr) * 1000; } return 0; @@ -596,14 +794,7 @@ static int bq27xxx_battery_status(struct bq27xxx_device_info *di, { int status; - if (bq27xxx_is_chip_version_higher(di)) { - if (di->cache.flags & BQ27500_FLAG_FC) - status = POWER_SUPPLY_STATUS_FULL; - else if (di->cache.flags & BQ27500_FLAG_DSC) - status = POWER_SUPPLY_STATUS_DISCHARGING; - else - status = POWER_SUPPLY_STATUS_CHARGING; - } else { + if (di->chip == BQ27000 || di->chip == BQ27010) { if (di->cache.flags & BQ27000_FLAG_FC) status = POWER_SUPPLY_STATUS_FULL; else if (di->cache.flags & BQ27000_FLAG_CHGS) @@ -612,6 +803,13 @@ static int bq27xxx_battery_status(struct bq27xxx_device_info *di, status = POWER_SUPPLY_STATUS_NOT_CHARGING; else status = POWER_SUPPLY_STATUS_DISCHARGING; + } else { + if (di->cache.flags & BQ27XXX_FLAG_FC) + status = POWER_SUPPLY_STATUS_FULL; + else if (di->cache.flags & BQ27XXX_FLAG_DSC) + status = POWER_SUPPLY_STATUS_DISCHARGING; + else + status = POWER_SUPPLY_STATUS_CHARGING; } val->intval = status; @@ -624,21 +822,21 @@ static int bq27xxx_battery_capacity_level(struct bq27xxx_device_info *di, { int level; - if (bq27xxx_is_chip_version_higher(di)) { - if (di->cache.flags & BQ27500_FLAG_FC) + if (di->chip == BQ27000 || di->chip == BQ27010) { + if (di->cache.flags & BQ27000_FLAG_FC) level = POWER_SUPPLY_CAPACITY_LEVEL_FULL; - else if (di->cache.flags & BQ27500_FLAG_SOC1) + else if (di->cache.flags & BQ27000_FLAG_EDV1) level = POWER_SUPPLY_CAPACITY_LEVEL_LOW; - else if (di->cache.flags & BQ27500_FLAG_SOCF) + else if (di->cache.flags & BQ27000_FLAG_EDVF) level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; else level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; } else { - if (di->cache.flags & BQ27000_FLAG_FC) + if (di->cache.flags & BQ27XXX_FLAG_FC) level = POWER_SUPPLY_CAPACITY_LEVEL_FULL; - else if (di->cache.flags & BQ27000_FLAG_EDV1) + else if (di->cache.flags & BQ27XXX_FLAG_SOC1) level = POWER_SUPPLY_CAPACITY_LEVEL_LOW; - else if (di->cache.flags & BQ27000_FLAG_EDVF) + else if (di->cache.flags & BQ27XXX_FLAG_SOCF) level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; else level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; @@ -658,7 +856,7 @@ static int bq27xxx_battery_voltage(struct bq27xxx_device_info *di, { int volt; - volt = bq27xxx_read(di, BQ27x00_REG_VOLT, false); + volt = bq27xxx_read(di, BQ27XXX_REG_VOLT, false); if (volt < 0) { dev_err(di->dev, "error reading voltage\n"); return volt; @@ -719,7 +917,7 @@ static int bq27xxx_battery_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_TEMP: ret = bq27xxx_simple_value(di->cache.temperature, val); if (ret == 0) - val->intval -= 2731; + val->intval -= 2731; /* convert decidegree k to c */ break; case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: ret = bq27xxx_simple_value(di->cache.time_to_empty, val); @@ -785,19 +983,8 @@ static int bq27xxx_powersupply_init(struct bq27xxx_device_info *di, psy_desc->name = name; psy_desc->type = POWER_SUPPLY_TYPE_BATTERY; - if (di->chip == BQ27425) { - psy_desc->properties = bq27425_battery_props; - psy_desc->num_properties = ARRAY_SIZE(bq27425_battery_props); - } else if (di->chip == BQ27742) { - psy_desc->properties = bq27742_battery_props; - psy_desc->num_properties = ARRAY_SIZE(bq27742_battery_props); - } else if (di->chip == BQ27510) { - psy_desc->properties = bq27510_battery_props; - psy_desc->num_properties = ARRAY_SIZE(bq27510_battery_props); - } else { - psy_desc->properties = bq27x00_battery_props; - psy_desc->num_properties = ARRAY_SIZE(bq27x00_battery_props); - } + psy_desc->properties = bq27xxx_battery_props[di->chip].props; + psy_desc->num_properties = bq27xxx_battery_props[di->chip].size; psy_desc->get_property = bq27xxx_battery_get_property; psy_desc->external_power_changed = bq27xxx_external_power_changed; @@ -910,11 +1097,15 @@ static int bq27xxx_battery_i2c_probe(struct i2c_client *client, di->dev = &client->dev; di->chip = id->driver_data; di->bus.read = &bq27xxx_battery_i2c_read; + di->regs = bq27xxx_regs[di->chip]; retval = bq27xxx_powersupply_init(di, name); if (retval) goto batt_failed; + /* Schedule a polling after about 1 min */ + schedule_delayed_work(&di->work, 60 * HZ); + i2c_set_clientdata(client, di); return 0; @@ -941,11 +1132,22 @@ static int bq27xxx_battery_i2c_remove(struct i2c_client *client) } static const struct i2c_device_id bq27xxx_id[] = { - { "bq27200", BQ27000 }, /* bq27200 is same as bq27000, but with i2c */ + { "bq27200", BQ27000 }, + { "bq27210", BQ27010 }, { "bq27500", BQ27500 }, - { "bq27425", BQ27425 }, - { "bq27742", BQ27742 }, - { "bq27510", BQ27510 }, + { "bq27510", BQ27500 }, + { "bq27520", BQ27500 }, + { "bq27530", BQ27530 }, + { "bq27531", BQ27530 }, + { "bq27541", BQ27541 }, + { "bq27542", BQ27541 }, + { "bq27546", BQ27541 }, + { "bq27742", BQ27541 }, + { "bq27545", BQ27545 }, + { "bq27421", BQ27421 }, + { "bq27425", BQ27421 }, + { "bq27441", BQ27421 }, + { "bq27621", BQ27421 }, {}, }; MODULE_DEVICE_TABLE(i2c, bq27xxx_id); -- cgit v1.2.3 From 74aab849f3423f3f783a8e5e9767572c2b59b353 Mon Sep 17 00:00:00 2001 From: "Andrew F. Davis" Date: Tue, 22 Sep 2015 14:35:10 -0500 Subject: power: bq27xxx_battery: Cleanup health checking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reorganize the logic checking battery health and add under temperature condition checking. Signed-off-by: Andrew F. Davis Acked-by: Pali Rohár Signed-off-by: Sebastian Reichel --- drivers/power/bq27xxx_battery.c | 64 +++++++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 22 deletions(-) (limited to 'drivers') diff --git a/drivers/power/bq27xxx_battery.c b/drivers/power/bq27xxx_battery.c index 236c5888aa9d..fe18a2d8ccc2 100644 --- a/drivers/power/bq27xxx_battery.c +++ b/drivers/power/bq27xxx_battery.c @@ -63,6 +63,8 @@ #define BQ27XXX_FLAG_FC BIT(9) #define BQ27XXX_FLAG_OTD BIT(14) #define BQ27XXX_FLAG_OTC BIT(15) +#define BQ27XXX_FLAG_UT BIT(14) +#define BQ27XXX_FLAG_OT BIT(15) /* BQ27000 has different layout for Flags register */ #define BQ27000_FLAG_EDVF BIT(0) /* Final End-of-Discharge-Voltage flag */ @@ -650,12 +652,36 @@ static int bq27xxx_battery_read_pwr_avg(struct bq27xxx_device_info *di) /* * Returns true if a battery over temperature condition is detected */ -static int bq27xxx_battery_overtemp(struct bq27xxx_device_info *di, u16 flags) +static bool bq27xxx_battery_overtemp(struct bq27xxx_device_info *di, u16 flags) { - if (di->chip == BQ27500 || di->chip == BQ27541) + if (di->chip == BQ27500 || di->chip == BQ27541 || di->chip == BQ27545) return flags & (BQ27XXX_FLAG_OTC | BQ27XXX_FLAG_OTD); + if (di->chip == BQ27530 || di->chip == BQ27421) + return flags & BQ27XXX_FLAG_OT; + + return false; +} + +/* + * Returns true if a battery under temperature condition is detected + */ +static bool bq27xxx_battery_undertemp(struct bq27xxx_device_info *di, u16 flags) +{ + if (di->chip == BQ27530 || di->chip == BQ27421) + return flags & BQ27XXX_FLAG_UT; + + return false; +} + +/* + * Returns true if a low state of charge condition is detected + */ +static bool bq27xxx_battery_dead(struct bq27xxx_device_info *di, u16 flags) +{ + if (di->chip == BQ27000 || di->chip == BQ27010) + return flags & (BQ27000_FLAG_EDV1 | BQ27000_FLAG_EDVF); else - return flags & BQ27XXX_FLAG_OTC; + return flags & (BQ27XXX_FLAG_SOC1 | BQ27XXX_FLAG_SOCF); } /* @@ -664,29 +690,23 @@ static int bq27xxx_battery_overtemp(struct bq27xxx_device_info *di, u16 flags) */ static int bq27xxx_battery_read_health(struct bq27xxx_device_info *di) { - u16 tval; + u16 flags; - tval = bq27xxx_read(di, BQ27XXX_REG_FLAGS, false); - if (tval < 0) { - dev_err(di->dev, "error reading flag register:%d\n", tval); - return tval; + flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, false); + if (flags < 0) { + dev_err(di->dev, "error reading flag register:%d\n", flags); + return flags; } - if (di->chip == BQ27000 || di->chip == BQ27010) { - if (tval & BQ27000_FLAG_EDV1) - tval = POWER_SUPPLY_HEALTH_DEAD; - else - tval = POWER_SUPPLY_HEALTH_GOOD; - } else { - if (tval & BQ27XXX_FLAG_SOCF) - tval = POWER_SUPPLY_HEALTH_DEAD; - else if (bq27xxx_battery_overtemp(di, tval)) - tval = POWER_SUPPLY_HEALTH_OVERHEAT; - else - tval = POWER_SUPPLY_HEALTH_GOOD; - } + /* Unlikely but important to return first */ + if (unlikely(bq27xxx_battery_overtemp(di, flags))) + return POWER_SUPPLY_HEALTH_OVERHEAT; + if (unlikely(bq27xxx_battery_undertemp(di, flags))) + return POWER_SUPPLY_HEALTH_COLD; + if (unlikely(bq27xxx_battery_dead(di, flags))) + return POWER_SUPPLY_HEALTH_DEAD; - return tval; + return POWER_SUPPLY_HEALTH_GOOD; } static void bq27xxx_battery_update(struct bq27xxx_device_info *di) -- cgit v1.2.3 From 8807feb91b76dc3267cef58302aaeff3430cb8f2 Mon Sep 17 00:00:00 2001 From: "Andrew F. Davis" Date: Tue, 22 Sep 2015 14:35:11 -0500 Subject: power: bq27xxx_battery: Add interrupt handling support Some devices have a pin that can generate an interrupt when the battery's status changes. Add an interrupt handler to read the new battery status. Signed-off-by: Andrew F. Davis Signed-off-by: Sebastian Reichel --- drivers/power/bq27xxx_battery.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'drivers') diff --git a/drivers/power/bq27xxx_battery.c b/drivers/power/bq27xxx_battery.c index fe18a2d8ccc2..473aa2f94882 100644 --- a/drivers/power/bq27xxx_battery.c +++ b/drivers/power/bq27xxx_battery.c @@ -48,6 +48,7 @@ #include #include #include +#include #include #include @@ -761,6 +762,15 @@ static void bq27xxx_battery_update(struct bq27xxx_device_info *di) di->last_update = jiffies; } +static irqreturn_t bq27xxx_battery_irq_handler_thread(int irq, void *data) +{ + struct bq27xxx_device_info *di = data; + + bq27xxx_battery_update(di); + + return IRQ_HANDLED; +} + static void bq27xxx_battery_poll(struct work_struct *work) { struct bq27xxx_device_info *di = @@ -1128,6 +1138,19 @@ static int bq27xxx_battery_i2c_probe(struct i2c_client *client, i2c_set_clientdata(client, di); + if (client->irq) { + retval = devm_request_threaded_irq(&client->dev, client->irq, + NULL, bq27xxx_battery_irq_handler_thread, + IRQF_ONESHOT, + name, di); + if (retval) { + dev_err(&client->dev, + "Unable to register IRQ %d error %d\n", + client->irq, retval); + return retval; + } + } + return 0; batt_failed: -- cgit v1.2.3 From 28153a31b8b6c44e4f1e24dafce2f1b22cec427b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pali=20Roh=C3=A1r?= Date: Mon, 21 Sep 2015 16:58:20 +0200 Subject: bq2415x_charger: Fix null pointer dereference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit b68c3161430a (bq2415x_charger: Allow to load and use driver even if notify device is not registered yet) introduced null pointer dereference in case bq is NULL. This patch fixes it. Fixes: b68c3161430a ("bq2415x_charger: Allow to load and use driver even if notify device is not registered yet") Reported-by: Dan Carpenter Signed-off-by: Pali Rohár Signed-off-by: Sebastian Reichel --- drivers/power/bq2415x_charger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/power/bq2415x_charger.c b/drivers/power/bq2415x_charger.c index ec212b5be755..4afd76848bce 100644 --- a/drivers/power/bq2415x_charger.c +++ b/drivers/power/bq2415x_charger.c @@ -1704,7 +1704,7 @@ error_4: error_3: bq2415x_power_supply_exit(bq); error_2: - if (bq->notify_node) + if (bq && bq->notify_node) of_node_put(bq->notify_node); kfree(name); error_1: -- cgit v1.2.3 From 0bc58e93819d542475cd9390921db509a283fe72 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 24 Sep 2015 21:41:37 +0300 Subject: power: qcom_smbb: test the correct variable "state" is a bool so it's never less than zero. The intent was to test "ret" instead. Fixes: 56d7df8716b2 ('power: Add Qualcomm SMBB driver') Signed-off-by: Dan Carpenter Signed-off-by: Sebastian Reichel --- drivers/power/qcom_smbb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/power/qcom_smbb.c b/drivers/power/qcom_smbb.c index 0dabfe8cceb2..5eb1e9e543e2 100644 --- a/drivers/power/qcom_smbb.c +++ b/drivers/power/qcom_smbb.c @@ -351,7 +351,7 @@ static void smbb_set_line_flag(struct smbb_charger *chg, int irq, int flag) int ret; ret = irq_get_irqchip_state(irq, IRQCHIP_STATE_LINE_LEVEL, &state); - if (state < 0) { + if (ret < 0) { dev_err(chg->dev, "failed to read irq line\n"); return; } -- cgit v1.2.3 From 3636859b280ca595da4556274a509223f47e9a39 Mon Sep 17 00:00:00 2001 From: Enric Balletbo i Serra Date: Thu, 24 Sep 2015 21:44:21 +0200 Subject: power_supply: Add support for tps65217-charger. This patch adds support for the tps65217 charger driver. This driver is responsible for controlling the charger aspect of the tps65217 mfd. Currently, this mainly consists of turning on and off the charger, but some other features of the charger can be supported through this driver. Signed-off-by: Enric Balletbo i Serra Signed-off-by: Sebastian Reichel --- drivers/power/Kconfig | 7 ++ drivers/power/Makefile | 1 + drivers/power/tps65217_charger.c | 264 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 272 insertions(+) create mode 100644 drivers/power/tps65217_charger.c (limited to 'drivers') diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index d3cd2ea54440..eeb577653c9d 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -446,6 +446,13 @@ config CHARGER_TPS65090 Say Y here to enable support for battery charging with TPS65090 PMIC chips. +config CHARGER_TPS65217 + tristate "TPS65217 battery charger driver" + depends on MFD_TPS65217 + help + Say Y here to enable support for battery charging with TPS65217 + PMIC chips. + config BATTERY_GAUGE_LTC2941 tristate "LTC2941/LTC2943 Battery Gauge Driver" depends on I2C diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 8eb30a515d5c..b0e1bf190e3d 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -67,6 +67,7 @@ obj-$(CONFIG_CHARGER_BQ25890) += bq25890_charger.o obj-$(CONFIG_POWER_AVS) += avs/ obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o +obj-$(CONFIG_CHARGER_TPS65217) += tps65217_charger.o obj-$(CONFIG_POWER_RESET) += reset/ obj-$(CONFIG_AXP288_FUEL_GAUGE) += axp288_fuel_gauge.o obj-$(CONFIG_AXP288_CHARGER) += axp288_charger.o diff --git a/drivers/power/tps65217_charger.c b/drivers/power/tps65217_charger.c new file mode 100644 index 000000000000..d9f56730c735 --- /dev/null +++ b/drivers/power/tps65217_charger.c @@ -0,0 +1,264 @@ +/* + * Battery charger driver for TI's tps65217 + * + * Copyright (c) 2015, Collabora Ltd. + + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * Battery charger driver for TI's tps65217 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define POLL_INTERVAL (HZ * 2) + +struct tps65217_charger { + struct tps65217 *tps; + struct device *dev; + struct power_supply *ac; + + int ac_online; + int prev_ac_online; + + struct task_struct *poll_task; +}; + +static enum power_supply_property tps65217_ac_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static int tps65217_config_charger(struct tps65217_charger *charger) +{ + int ret; + + dev_dbg(charger->dev, "%s\n", __func__); + + /* + * tps65217 rev. G, p. 31 (see p. 32 for NTC schematic) + * + * The device can be configured to support a 100k NTC (B = 3960) by + * setting the the NTC_TYPE bit in register CHGCONFIG1 to 1. However it + * is not recommended to do so. In sleep mode, the charger continues + * charging the battery, but all register values are reset to default + * values. Therefore, the charger would get the wrong temperature + * information. If 100k NTC setting is required, please contact the + * factory. + * + * ATTENTION, conflicting information, from p. 46 + * + * NTC TYPE (for battery temperature measurement) + * 0 – 100k (curve 1, B = 3960) + * 1 – 10k (curve 2, B = 3480) (default on reset) + * + */ + ret = tps65217_clear_bits(charger->tps, TPS65217_REG_CHGCONFIG1, + TPS65217_CHGCONFIG1_NTC_TYPE, + TPS65217_PROTECT_NONE); + if (ret) { + dev_err(charger->dev, + "failed to set 100k NTC setting: %d\n", ret); + return ret; + } + + return 0; +} + +static int tps65217_enable_charging(struct tps65217_charger *charger) +{ + int ret; + + /* charger already enabled */ + if (charger->ac_online) + return 0; + + dev_dbg(charger->dev, "%s: enable charging\n", __func__); + ret = tps65217_set_bits(charger->tps, TPS65217_REG_CHGCONFIG1, + TPS65217_CHGCONFIG1_CHG_EN, + TPS65217_CHGCONFIG1_CHG_EN, + TPS65217_PROTECT_NONE); + if (ret) { + dev_err(charger->dev, + "%s: Error in writing CHG_EN in reg 0x%x: %d\n", + __func__, TPS65217_REG_CHGCONFIG1, ret); + return ret; + } + + charger->ac_online = 1; + + return 0; +} + +static int tps65217_ac_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct tps65217_charger *charger = power_supply_get_drvdata(psy); + + if (psp == POWER_SUPPLY_PROP_ONLINE) { + val->intval = charger->ac_online; + return 0; + } + return -EINVAL; +} + +static irqreturn_t tps65217_charger_irq(int irq, void *dev) +{ + int ret, val; + struct tps65217_charger *charger = dev; + + charger->prev_ac_online = charger->ac_online; + + ret = tps65217_reg_read(charger->tps, TPS65217_REG_STATUS, &val); + if (ret < 0) { + dev_err(charger->dev, "%s: Error in reading reg 0x%x\n", + __func__, TPS65217_REG_STATUS); + return IRQ_HANDLED; + } + + dev_dbg(charger->dev, "%s: 0x%x\n", __func__, val); + + /* check for AC status bit */ + if (val & TPS65217_STATUS_ACPWR) { + ret = tps65217_enable_charging(charger); + if (ret) { + dev_err(charger->dev, + "failed to enable charger: %d\n", ret); + return IRQ_HANDLED; + } + } else { + charger->ac_online = 0; + } + + if (charger->prev_ac_online != charger->ac_online) + power_supply_changed(charger->ac); + + ret = tps65217_reg_read(charger->tps, TPS65217_REG_CHGCONFIG0, &val); + if (ret < 0) { + dev_err(charger->dev, "%s: Error in reading reg 0x%x\n", + __func__, TPS65217_REG_CHGCONFIG0); + return IRQ_HANDLED; + } + + if (val & TPS65217_CHGCONFIG0_ACTIVE) + dev_dbg(charger->dev, "%s: charger is charging\n", __func__); + else + dev_dbg(charger->dev, + "%s: charger is NOT charging\n", __func__); + + return IRQ_HANDLED; +} + +static int tps65217_charger_poll_task(void *data) +{ + set_freezable(); + + while (!kthread_should_stop()) { + schedule_timeout_interruptible(POLL_INTERVAL); + try_to_freeze(); + tps65217_charger_irq(-1, data); + } + return 0; +} + +static const struct power_supply_desc tps65217_charger_desc = { + .name = "tps65217-ac", + .type = POWER_SUPPLY_TYPE_MAINS, + .get_property = tps65217_ac_get_property, + .properties = tps65217_ac_props, + .num_properties = ARRAY_SIZE(tps65217_ac_props), +}; + +static int tps65217_charger_probe(struct platform_device *pdev) +{ + struct tps65217 *tps = dev_get_drvdata(pdev->dev.parent); + struct tps65217_charger *charger; + int ret; + + dev_dbg(&pdev->dev, "%s\n", __func__); + + charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL); + if (!charger) + return -ENOMEM; + + charger->tps = tps; + charger->dev = &pdev->dev; + + charger->ac = devm_power_supply_register(&pdev->dev, + &tps65217_charger_desc, + NULL); + if (IS_ERR(charger->ac)) { + dev_err(&pdev->dev, "failed: power supply register\n"); + return PTR_ERR(charger->ac); + } + + ret = tps65217_config_charger(charger); + if (ret < 0) { + dev_err(charger->dev, "charger config failed, err %d\n", ret); + return ret; + } + + charger->poll_task = kthread_run(tps65217_charger_poll_task, + charger, "ktps65217charger"); + if (IS_ERR(charger->poll_task)) { + ret = PTR_ERR(charger->poll_task); + dev_err(charger->dev, "Unable to run kthread err %d\n", ret); + return ret; + } + + return 0; +} + +static int tps65217_charger_remove(struct platform_device *pdev) +{ + struct tps65217_charger *charger = platform_get_drvdata(pdev); + + kthread_stop(charger->poll_task); + + return 0; +} + +static const struct of_device_id tps65217_charger_match_table[] = { + { .compatible = "ti,tps65217-charger", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, tps65217_charger_match_table); + +static struct platform_driver tps65217_charger_driver = { + .probe = tps65217_charger_probe, + .remove = tps65217_charger_remove, + .driver = { + .name = "tps65217-charger", + .of_match_table = of_match_ptr(tps65217_charger_match_table), + }, + +}; +module_platform_driver(tps65217_charger_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Enric Balletbo Serra "); +MODULE_DESCRIPTION("TPS65217 battery charger driver"); -- cgit v1.2.3 From 90adf98d9530054b8e665ba5a928de4307231d84 Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Tue, 22 Sep 2015 19:00:40 +0200 Subject: wm831x_power: Use IRQF_ONESHOT to request threaded IRQs Since commit 1c6c69525b40 ("genirq: Reject bogus threaded irq requests") threaded IRQs without a primary handler need to be requested with IRQF_ONESHOT, otherwise the request will fail. scripts/coccinelle/misc/irqf_oneshot.cocci detected this issue. Fixes: b5874f33bbaf ("wm831x_power: Use genirq") Signed-off-by: Valentin Rothberg Signed-off-by: Sebastian Reichel --- drivers/power/wm831x_power.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/power/wm831x_power.c b/drivers/power/wm831x_power.c index c826c83fdf98..7082301da945 100644 --- a/drivers/power/wm831x_power.c +++ b/drivers/power/wm831x_power.c @@ -573,7 +573,7 @@ static int wm831x_power_probe(struct platform_device *pdev) irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "SYSLO")); ret = request_threaded_irq(irq, NULL, wm831x_syslo_irq, - IRQF_TRIGGER_RISING, "System power low", + IRQF_TRIGGER_RISING | IRQF_ONESHOT, "System power low", power); if (ret != 0) { dev_err(&pdev->dev, "Failed to request SYSLO IRQ %d: %d\n", @@ -583,7 +583,7 @@ static int wm831x_power_probe(struct platform_device *pdev) irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "PWR SRC")); ret = request_threaded_irq(irq, NULL, wm831x_pwr_src_irq, - IRQF_TRIGGER_RISING, "Power source", + IRQF_TRIGGER_RISING | IRQF_ONESHOT, "Power source", power); if (ret != 0) { dev_err(&pdev->dev, "Failed to request PWR SRC IRQ %d: %d\n", @@ -596,7 +596,7 @@ static int wm831x_power_probe(struct platform_device *pdev) platform_get_irq_byname(pdev, wm831x_bat_irqs[i])); ret = request_threaded_irq(irq, NULL, wm831x_bat_irq, - IRQF_TRIGGER_RISING, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, wm831x_bat_irqs[i], power); if (ret != 0) { -- cgit v1.2.3 From 2202e1fc5a29eab13fab31d287dd2019dc9edaa6 Mon Sep 17 00:00:00 2001 From: Marek Belisko Date: Fri, 25 Sep 2015 22:20:45 +0200 Subject: drivers: power: twl4030_charger: fix link problems when building as module If either twl4030_charger or twl4030_madc is configured as MODULE, we get build (link) errors. To solve, the direct call of twl4030_get_madc_conversion() is replaced by a call to iio_read_channel_processed(). Signed-off-by: H. Nikolaus Schaller Signed-off-by: Marek Belisko Signed-off-by: Sebastian Reichel --- drivers/power/twl4030_charger.c | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/power/twl4030_charger.c b/drivers/power/twl4030_charger.c index 74f2d3ff1d7c..bcd4dc304f27 100644 --- a/drivers/power/twl4030_charger.c +++ b/drivers/power/twl4030_charger.c @@ -22,7 +22,7 @@ #include #include #include -#include +#include #define TWL4030_BCIMDEN 0x00 #define TWL4030_BCIMDKEY 0x01 @@ -91,21 +91,23 @@ #define TWL4030_MSTATEC_COMPLETE1 0x0b #define TWL4030_MSTATEC_COMPLETE4 0x0e -#if IS_REACHABLE(CONFIG_TWL4030_MADC) /* * If AC (Accessory Charger) voltage exceeds 4.5V (MADC 11) * then AC is available. */ -static inline int ac_available(void) +static inline int ac_available(struct iio_channel *channel_vac) { - return twl4030_get_madc_conversion(11) > 4500; -} -#else -static inline int ac_available(void) -{ - return 0; + int val, err; + + if (!channel_vac) + return 0; + + err = iio_read_channel_processed(channel_vac, &val); + if (err < 0) + return 0; + return val > 4500; } -#endif + static bool allow_usb; module_param(allow_usb, bool, 0644); MODULE_PARM_DESC(allow_usb, "Allow USB charge drawing default current"); @@ -128,6 +130,7 @@ struct twl4030_bci { */ unsigned int ichg_eoc, ichg_lo, ichg_hi; unsigned int usb_cur, ac_cur; + struct iio_channel *channel_vac; bool ac_is_active; int usb_mode, ac_mode; /* charging mode requested */ #define CHARGE_OFF 0 @@ -278,7 +281,7 @@ static int twl4030_charger_update_current(struct twl4030_bci *bci) * If AC (Accessory Charger) voltage exceeds 4.5V (MADC 11) * and AC is enabled, set current for 'ac' */ - if (ac_available()) { + if (ac_available(bci->channel_vac)) { cur = bci->ac_cur; bci->ac_is_active = true; } else { @@ -1048,6 +1051,12 @@ static int twl4030_bci_probe(struct platform_device *pdev) return ret; } + bci->channel_vac = iio_channel_get(&pdev->dev, "vac"); + if (IS_ERR(bci->channel_vac)) { + bci->channel_vac = NULL; + dev_warn(&pdev->dev, "could not request vac iio channel"); + } + INIT_WORK(&bci->work, twl4030_bci_usb_work); INIT_DELAYED_WORK(&bci->current_worker, twl4030_current_worker); @@ -1069,7 +1078,7 @@ static int twl4030_bci_probe(struct platform_device *pdev) TWL4030_INTERRUPTS_BCIIMR1A); if (ret < 0) { dev_err(&pdev->dev, "failed to unmask interrupts: %d\n", ret); - return ret; + goto fail; } reg = ~(u32)(TWL4030_VBATOV | TWL4030_VBUSOV | TWL4030_ACCHGOV); @@ -1102,6 +1111,10 @@ static int twl4030_bci_probe(struct platform_device *pdev) twl4030_charger_enable_backup(0, 0); return 0; +fail: + iio_channel_release(bci->channel_vac); + + return ret; } static int __exit twl4030_bci_remove(struct platform_device *pdev) @@ -1112,6 +1125,8 @@ static int __exit twl4030_bci_remove(struct platform_device *pdev) twl4030_charger_enable_usb(bci, false); twl4030_charger_enable_backup(0, 0); + iio_channel_release(bci->channel_vac); + device_remove_file(&bci->usb->dev, &dev_attr_max_current); device_remove_file(&bci->usb->dev, &dev_attr_mode); device_remove_file(&bci->ac->dev, &dev_attr_max_current); -- cgit v1.2.3 From 2edd69a81dd4e8068bb512ffcc3959c77fc182ea Mon Sep 17 00:00:00 2001 From: Andrzej Hajda Date: Mon, 28 Sep 2015 10:51:27 +0200 Subject: power: bq27xxx_battery: fix signedness bug in bq27xxx_battery_read_health() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We need flags to be signed for the error handling to work. The problem has been detected using proposed semantic patch scripts/coccinelle/tests/unsigned_lesser_than_zero.cocci [1]. [1]: http://permalink.gmane.org/gmane.linux.kernel/2038576 Fixes: 74aab849f342 ('power: bq27xxx_battery: Cleanup health checking') Signed-off-by: Andrzej Hajda Signed-off-by: Dan Carpenter Acked-By: Pali Rohár Acked-by: Andrew F. Davis Signed-off-by: Sebastian Reichel --- drivers/power/bq27xxx_battery.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/power/bq27xxx_battery.c b/drivers/power/bq27xxx_battery.c index 473aa2f94882..994c78d9f426 100644 --- a/drivers/power/bq27xxx_battery.c +++ b/drivers/power/bq27xxx_battery.c @@ -691,7 +691,7 @@ static bool bq27xxx_battery_dead(struct bq27xxx_device_info *di, u16 flags) */ static int bq27xxx_battery_read_health(struct bq27xxx_device_info *di) { - u16 flags; + int flags; flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, false); if (flags < 0) { -- cgit v1.2.3 From 5ff8c89d112cbeb5776d5ba40a224b70f67e41bb Mon Sep 17 00:00:00 2001 From: Andreas Dannenberg Date: Fri, 25 Sep 2015 10:54:07 -0500 Subject: power: bq24257: Remove IRQ config through stat-gpios At the time the driver was written GpioInt resources in ACPI were not passed to the driver in client->irq, as opposed to DT enumeration. To accommodate this use case, a "stat-gpios" property was introduced to allow configuring the IRQ. However this issue with ACPI was fixed in commit "845c877 i2c / ACPI: Assign IRQ for devices that have GpioInt automatically" and makes this workaround no longer necessary, hence we can remove the support for the "stat-gpios" property and the associated code from the bq24257 driver. Signed-off-by: Andreas Dannenberg Signed-off-by: Sebastian Reichel --- drivers/power/bq24257_charger.c | 28 ++++------------------------ 1 file changed, 4 insertions(+), 24 deletions(-) (limited to 'drivers') diff --git a/drivers/power/bq24257_charger.c b/drivers/power/bq24257_charger.c index 5859bc7c1616..69b53d056b61 100644 --- a/drivers/power/bq24257_charger.c +++ b/drivers/power/bq24257_charger.c @@ -36,7 +36,6 @@ #define BQ24257_REG_7 0x06 #define BQ24257_MANUFACTURER "Texas Instruments" -#define BQ24257_STAT_IRQ "stat" #define BQ24257_PG_GPIO "pg" #define BQ24257_ILIM_SET_DELAY 1000 /* msec */ @@ -606,19 +605,6 @@ static int bq24257_power_supply_init(struct bq24257_device *bq) return 0; } -static int bq24257_irq_probe(struct bq24257_device *bq) -{ - struct gpio_desc *stat_irq; - - stat_irq = devm_gpiod_get_index(bq->dev, BQ24257_STAT_IRQ, 0, GPIOD_IN); - if (IS_ERR(stat_irq)) { - dev_err(bq->dev, "could not probe stat_irq pin\n"); - return PTR_ERR(stat_irq); - } - - return gpiod_to_irq(stat_irq); -} - static int bq24257_pg_gpio_probe(struct bq24257_device *bq) { bq->pg = devm_gpiod_get_index(bq->dev, BQ24257_PG_GPIO, 0, GPIOD_IN); @@ -740,21 +726,15 @@ static int bq24257_probe(struct i2c_client *client, return ret; } - if (client->irq <= 0) - client->irq = bq24257_irq_probe(bq); - - if (client->irq < 0) { - dev_err(dev, "no irq resource found\n"); - return client->irq; - } - ret = devm_request_threaded_irq(dev, client->irq, NULL, bq24257_irq_handler_thread, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_ONESHOT, - BQ24257_STAT_IRQ, bq); - if (ret) + "bq24257", bq); + if (ret) { + dev_err(dev, "Failed to request IRQ #%d\n", client->irq); return ret; + } ret = bq24257_power_supply_init(bq); if (ret < 0) -- cgit v1.2.3 From 9b1cf1e44d91e80b17a315d04193b1764ca82a15 Mon Sep 17 00:00:00 2001 From: Andreas Dannenberg Date: Fri, 25 Sep 2015 10:54:08 -0500 Subject: power: bq24257: Streamline input current limit setup The maximum amount of input current the charger should draw is dependent on the power supply and should only be (re-)configured when the power supply gets connected and disconnected. However the driver was also lowering the bq24257's input current limit setting to 500mA when the battery was removed and restored the previous setting according to the power supply capabilities when the battery was reconnected although these events are not impacting the amount of power that can be drawn from the supply. Furthermore, a re-configuration of the input current limit to 500mA when the battery gets disconnected is actually dangerous if the limit was set higher previously and the system draws more than 500mA in which case the system voltage would be reduced in order to maintain 500mA which could result in the system getting too low of a supply to maintain operation. Last but not least the mechanism itself used for battery re-connection detection did not work in corner cases such as when the device's input current loop becomes active and the bq24257 device clears its battery fault error resulting in incorrectly reporting that the battery got reconnected. This patches removes the impact the battery removal/insertion has on the input current limit configured for the bq24257 and simplifies the associated handler routine. Signed-off-by: Andreas Dannenberg Signed-off-by: Sebastian Reichel --- drivers/power/bq24257_charger.c | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) (limited to 'drivers') diff --git a/drivers/power/bq24257_charger.c b/drivers/power/bq24257_charger.c index 69b53d056b61..d2d077cfdc2f 100644 --- a/drivers/power/bq24257_charger.c +++ b/drivers/power/bq24257_charger.c @@ -448,14 +448,13 @@ static void bq24257_handle_state_change(struct bq24257_device *bq, { int ret; struct bq24257_state old_state; - bool reset_iilimit = false; - bool config_iilimit = false; mutex_lock(&bq->lock); old_state = bq->state; mutex_unlock(&bq->lock); - if (!new_state->power_good) { /* power removed */ + if (!new_state->power_good) { + dev_dbg(bq->dev, "Power removed\n"); cancel_delayed_work_sync(&bq->iilimit_setup_work); /* activate D+/D- port detection algorithm */ @@ -463,26 +462,20 @@ static void bq24257_handle_state_change(struct bq24257_device *bq, if (ret < 0) goto error; - reset_iilimit = true; - } else if (!old_state.power_good) { /* power inserted */ - config_iilimit = true; - } else if (new_state->fault == FAULT_NO_BAT) { /* battery removed */ - cancel_delayed_work_sync(&bq->iilimit_setup_work); - - reset_iilimit = true; - } else if (old_state.fault == FAULT_NO_BAT) { /* battery connected */ - config_iilimit = true; - } else if (new_state->fault == FAULT_TIMER) { /* safety timer expired */ - dev_err(bq->dev, "Safety timer expired! Battery dead?\n"); - } - - if (reset_iilimit) { + /* reset input current limit */ ret = bq24257_field_write(bq, F_IILIMIT, IILIMIT_500); if (ret < 0) goto error; - } else if (config_iilimit) { + } else if (!old_state.power_good) { + dev_dbg(bq->dev, "Power inserted\n"); + + /* configure input current limit */ schedule_delayed_work(&bq->iilimit_setup_work, msecs_to_jiffies(BQ24257_ILIM_SET_DELAY)); + } else if (new_state->fault == FAULT_NO_BAT) { + dev_warn(bq->dev, "Battery removed\n"); + } else if (new_state->fault == FAULT_TIMER) { + dev_err(bq->dev, "Safety timer expired! Battery dead?\n"); } return; -- cgit v1.2.3 From dfc602524b9f4ecc6de9a3050667412176db7c55 Mon Sep 17 00:00:00 2001 From: Andreas Dannenberg Date: Mon, 28 Sep 2015 17:33:51 -0500 Subject: power: bq24257: Use managed power supply register Use the devm_* managed version of the function to register the power supply and remove the associated unregister function. This will simplify error handling moving forward as it allows the unregister to happen automatically. It also saves a few lines of code. As this changes the order of putting the bq24257 into reset vs. unregistering the power-supply during driver remove re-tested various driver unload scenario to make sure that this doesn't cause any unintended side effects such as erroneous interrupts. Signed-off-by: Andreas Dannenberg Reviewed-by: Krzysztof Kozlowski Signed-off-by: Sebastian Reichel --- drivers/power/bq24257_charger.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/power/bq24257_charger.c b/drivers/power/bq24257_charger.c index d2d077cfdc2f..6757b4180972 100644 --- a/drivers/power/bq24257_charger.c +++ b/drivers/power/bq24257_charger.c @@ -590,8 +590,10 @@ static int bq24257_power_supply_init(struct bq24257_device *bq) psy_cfg.supplied_to = bq24257_charger_supplied_to; psy_cfg.num_supplicants = ARRAY_SIZE(bq24257_charger_supplied_to); - bq->charger = power_supply_register(bq->dev, &bq24257_power_supply_desc, - &psy_cfg); + bq->charger = devm_power_supply_register(bq->dev, + &bq24257_power_supply_desc, + &psy_cfg); + if (IS_ERR(bq->charger)) return PTR_ERR(bq->charger); @@ -742,8 +744,6 @@ static int bq24257_remove(struct i2c_client *client) cancel_delayed_work_sync(&bq->iilimit_setup_work); - power_supply_unregister(bq->charger); - bq24257_field_write(bq, F_RESET, 1); /* reset to defaults */ return 0; -- cgit v1.2.3 From 3b84b8efef054ba59e7679d1d430afb437aa4640 Mon Sep 17 00:00:00 2001 From: Andreas Dannenberg Date: Mon, 28 Sep 2015 17:33:52 -0500 Subject: power: bq24257: Simplify bq24257_power_supply_init() Eliminate a few lines of code by using the PTR_ERR_OR_ZERO() macro. Signed-off-by: Andreas Dannenberg Reviewed-by: Krzysztof Kozlowski Signed-off-by: Sebastian Reichel --- drivers/power/bq24257_charger.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/power/bq24257_charger.c b/drivers/power/bq24257_charger.c index 6757b4180972..060f754b41e5 100644 --- a/drivers/power/bq24257_charger.c +++ b/drivers/power/bq24257_charger.c @@ -594,10 +594,7 @@ static int bq24257_power_supply_init(struct bq24257_device *bq) &bq24257_power_supply_desc, &psy_cfg); - if (IS_ERR(bq->charger)) - return PTR_ERR(bq->charger); - - return 0; + return PTR_ERR_OR_ZERO(bq->charger); } static int bq24257_pg_gpio_probe(struct bq24257_device *bq) -- cgit v1.2.3 From bf02dca9ee9d5f9ea7a0ef2e15a2051b8cca6b09 Mon Sep 17 00:00:00 2001 From: Andreas Dannenberg Date: Mon, 28 Sep 2015 17:33:53 -0500 Subject: power: bq24257: Add basic support for bq24250/bq24251 This patch adds basic support for bq24250 and bq24251 which are very similar to the bq24257 the driver was originally written for. Basic support means the ability to select a device through Kconfig, DT and ACPI, an instance variable allowing to check which chip is active, and the reporting back of the selected device through the model_name power supply sysfs property. This patch by itself is not sufficient to actually use those two added devices in a real-world setting due to some feature differences which are addressed by other patches in this series. Signed-off-by: Andreas Dannenberg Reviewed-by: Krzysztof Kozlowski Signed-off-by: Sebastian Reichel --- drivers/power/Kconfig | 5 ++-- drivers/power/bq24257_charger.c | 52 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 52 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index eeb577653c9d..9e68853217ca 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -409,12 +409,13 @@ config CHARGER_BQ24190 Say Y to enable support for the TI BQ24190 battery charger. config CHARGER_BQ24257 - tristate "TI BQ24257 battery charger driver" + tristate "TI BQ24250/24251/24257 battery charger driver" depends on I2C depends on GPIOLIB || COMPILE_TEST depends on REGMAP_I2C help - Say Y to enable support for the TI BQ24257 battery charger. + Say Y to enable support for the TI BQ24250, BQ24251, and BQ24257 battery + chargers. config CHARGER_BQ24735 tristate "TI BQ24735 battery charger support" diff --git a/drivers/power/bq24257_charger.c b/drivers/power/bq24257_charger.c index 060f754b41e5..b0c853320b46 100644 --- a/drivers/power/bq24257_charger.c +++ b/drivers/power/bq24257_charger.c @@ -13,6 +13,10 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * + * Datasheets: + * http://www.ti.com/product/bq24250 + * http://www.ti.com/product/bq24251 + * http://www.ti.com/product/bq24257 */ #include @@ -40,6 +44,22 @@ #define BQ24257_ILIM_SET_DELAY 1000 /* msec */ +/* + * When adding support for new devices make sure that enum bq2425x_chip and + * bq2425x_chip_name[] always stay in sync! + */ +enum bq2425x_chip { + BQ24250, + BQ24251, + BQ24257, +}; + +static const char *const bq2425x_chip_name[] = { + "bq24250", + "bq24251", + "bq24257", +}; + enum bq24257_fields { F_WD_FAULT, F_WD_EN, F_STAT, F_FAULT, /* REG 1 */ F_RESET, F_IILIMIT, F_EN_STAT, F_EN_TERM, F_CE, F_HZ_MODE, /* REG 2 */ @@ -70,6 +90,8 @@ struct bq24257_device { struct device *dev; struct power_supply *charger; + enum bq2425x_chip chip; + struct regmap *rmap; struct regmap_field *rmap_fields[F_MAX_FIELDS]; @@ -248,6 +270,10 @@ static int bq24257_power_supply_get_property(struct power_supply *psy, val->strval = BQ24257_MANUFACTURER; break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = bq2425x_chip_name[bq->chip]; + break; + case POWER_SUPPLY_PROP_ONLINE: val->intval = state.power_good; break; @@ -561,6 +587,7 @@ static int bq24257_hw_init(struct bq24257_device *bq) static enum power_supply_property bq24257_power_supply_props[] = { POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_MODEL_NAME, POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_HEALTH, @@ -644,6 +671,7 @@ static int bq24257_probe(struct i2c_client *client, { struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); struct device *dev = &client->dev; + const struct acpi_device_id *acpi_id; struct bq24257_device *bq; int ret; int i; @@ -660,6 +688,18 @@ static int bq24257_probe(struct i2c_client *client, bq->client = client; bq->dev = dev; + if (ACPI_HANDLE(dev)) { + acpi_id = acpi_match_device(dev->driver->acpi_match_table, + &client->dev); + if (!acpi_id) { + dev_err(dev, "Failed to match ACPI device\n"); + return -ENODEV; + } + bq->chip = (enum bq2425x_chip)acpi_id->driver_data; + } else { + bq->chip = (enum bq2425x_chip)id->driver_data; + } + mutex_init(&bq->lock); bq->rmap = devm_regmap_init_i2c(client, &bq24257_regmap_config); @@ -722,7 +762,7 @@ static int bq24257_probe(struct i2c_client *client, bq24257_irq_handler_thread, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_ONESHOT, - "bq24257", bq); + bq2425x_chip_name[bq->chip], bq); if (ret) { dev_err(dev, "Failed to request IRQ #%d\n", client->irq); return ret; @@ -793,19 +833,25 @@ static const struct dev_pm_ops bq24257_pm = { }; static const struct i2c_device_id bq24257_i2c_ids[] = { - { "bq24257", 0 }, + { "bq24250", BQ24250 }, + { "bq24251", BQ24251 }, + { "bq24257", BQ24257 }, {}, }; MODULE_DEVICE_TABLE(i2c, bq24257_i2c_ids); static const struct of_device_id bq24257_of_match[] = { + { .compatible = "ti,bq24250", }, + { .compatible = "ti,bq24251", }, { .compatible = "ti,bq24257", }, { }, }; MODULE_DEVICE_TABLE(of, bq24257_of_match); static const struct acpi_device_id bq24257_acpi_match[] = { - {"BQ242570", 0}, + { "BQ242500", BQ24250 }, + { "BQ242510", BQ24251 }, + { "BQ242570", BQ24257 }, {}, }; MODULE_DEVICE_TABLE(acpi, bq24257_acpi_match); -- cgit v1.2.3 From 7ef62365c61572085e63e8a7c3483bacfff6e541 Mon Sep 17 00:00:00 2001 From: Andreas Dannenberg Date: Mon, 28 Sep 2015 17:33:54 -0500 Subject: power: bq24257: Add bit definition for temp sense enable Adding a missing bit definition for the sake of consistency device model vs. bit field representation. No change in functionality. Signed-off-by: Andreas Dannenberg Reviewed-by: Laurentiu Palcu Signed-off-by: Sebastian Reichel --- drivers/power/bq24257_charger.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/power/bq24257_charger.c b/drivers/power/bq24257_charger.c index b0c853320b46..93f75820020a 100644 --- a/drivers/power/bq24257_charger.c +++ b/drivers/power/bq24257_charger.c @@ -66,7 +66,7 @@ enum bq24257_fields { F_VBAT, F_USB_DET, /* REG 3 */ F_ICHG, F_ITERM, /* REG 4 */ F_LOOP_STATUS, F_LOW_CHG, F_DPDM_EN, F_CE_STATUS, F_VINDPM, /* REG 5 */ - F_X2_TMR_EN, F_TMR, F_SYSOFF, F_TS_STAT, /* REG 6 */ + F_X2_TMR_EN, F_TMR, F_SYSOFF, F_TS_EN, F_TS_STAT, /* REG 6 */ F_VOVP, F_CLR_VDP, F_FORCE_BATDET, F_FORCE_PTM, /* REG 7 */ F_MAX_FIELDS @@ -156,6 +156,7 @@ static const struct reg_field bq24257_reg_fields[] = { [F_X2_TMR_EN] = REG_FIELD(BQ24257_REG_6, 7, 7), [F_TMR] = REG_FIELD(BQ24257_REG_6, 5, 6), [F_SYSOFF] = REG_FIELD(BQ24257_REG_6, 4, 4), + [F_TS_EN] = REG_FIELD(BQ24257_REG_6, 3, 3), [F_TS_STAT] = REG_FIELD(BQ24257_REG_6, 0, 2), /* REG 7 */ [F_VOVP] = REG_FIELD(BQ24257_REG_7, 5, 7), -- cgit v1.2.3 From eb9fbcc6693ad73745198bbfc7c2b0e881580f59 Mon Sep 17 00:00:00 2001 From: Andreas Dannenberg Date: Mon, 28 Sep 2015 17:33:55 -0500 Subject: power: bq24257: Allow manual setting of input current limit A new optional device property called "ti,current-limit" is introduced to allow disabling the D+/D- USB signal-based charger type auto- detection algorithm used to set the input current limit and instead to use a fixed input current limit. Signed-off-by: Andreas Dannenberg Reviewed-by: Krzysztof Kozlowski Signed-off-by: Sebastian Reichel --- drivers/power/bq24257_charger.c | 93 +++++++++++++++++++++++++++++++++-------- 1 file changed, 75 insertions(+), 18 deletions(-) (limited to 'drivers') diff --git a/drivers/power/bq24257_charger.c b/drivers/power/bq24257_charger.c index 93f75820020a..7bbff806624f 100644 --- a/drivers/power/bq24257_charger.c +++ b/drivers/power/bq24257_charger.c @@ -77,6 +77,7 @@ struct bq24257_init_data { u8 ichg; /* charge current */ u8 vbat; /* regulation voltage */ u8 iterm; /* termination current */ + u8 iilimit; /* input current limit */ }; struct bq24257_state { @@ -103,6 +104,8 @@ struct bq24257_device { struct bq24257_state state; struct mutex lock; /* protect state data */ + + bool iilimit_autoset_enable; }; static bool bq24257_is_volatile_reg(struct device *dev, unsigned int reg) @@ -191,6 +194,12 @@ static const u32 bq24257_iterm_map[] = { #define BQ24257_ITERM_MAP_SIZE ARRAY_SIZE(bq24257_iterm_map) +static const u32 bq24257_iilimit_map[] = { + 100000, 150000, 500000, 900000, 1500000, 2000000 +}; + +#define BQ24257_IILIMIT_MAP_SIZE ARRAY_SIZE(bq24257_iilimit_map) + static int bq24257_field_read(struct bq24257_device *bq, enum bq24257_fields field_id) { @@ -480,24 +489,32 @@ static void bq24257_handle_state_change(struct bq24257_device *bq, old_state = bq->state; mutex_unlock(&bq->lock); + /* + * Handle BQ2425x state changes observing whether the D+/D- based input + * current limit autoset functionality is enabled. + */ if (!new_state->power_good) { dev_dbg(bq->dev, "Power removed\n"); - cancel_delayed_work_sync(&bq->iilimit_setup_work); - - /* activate D+/D- port detection algorithm */ - ret = bq24257_field_write(bq, F_DPDM_EN, 1); - if (ret < 0) - goto error; - - /* reset input current limit */ - ret = bq24257_field_write(bq, F_IILIMIT, IILIMIT_500); - if (ret < 0) - goto error; + if (bq->iilimit_autoset_enable) { + cancel_delayed_work_sync(&bq->iilimit_setup_work); + + /* activate D+/D- port detection algorithm */ + ret = bq24257_field_write(bq, F_DPDM_EN, 1); + if (ret < 0) + goto error; + + /* reset input current limit */ + ret = bq24257_field_write(bq, F_IILIMIT, + bq->init_data.iilimit); + if (ret < 0) + goto error; + } } else if (!old_state.power_good) { dev_dbg(bq->dev, "Power inserted\n"); - /* configure input current limit */ - schedule_delayed_work(&bq->iilimit_setup_work, + if (bq->iilimit_autoset_enable) + /* configure input current limit */ + schedule_delayed_work(&bq->iilimit_setup_work, msecs_to_jiffies(BQ24257_ILIM_SET_DELAY)); } else if (new_state->fault == FAULT_NO_BAT) { dev_warn(bq->dev, "Battery removed\n"); @@ -577,7 +594,16 @@ static int bq24257_hw_init(struct bq24257_device *bq) bq->state = state; mutex_unlock(&bq->lock); - if (!state.power_good) + if (!bq->iilimit_autoset_enable) { + dev_dbg(bq->dev, "manually setting iilimit = %u\n", + bq->init_data.iilimit); + + /* program fixed input current limit */ + ret = bq24257_field_write(bq, F_IILIMIT, + bq->init_data.iilimit); + if (ret < 0) + return ret; + } else if (!state.power_good) /* activate D+/D- detection algorithm */ ret = bq24257_field_write(bq, F_DPDM_EN, 1); else if (state.fault != FAULT_NO_BAT) @@ -641,6 +667,7 @@ static int bq24257_fw_probe(struct bq24257_device *bq) int ret; u32 property; + /* Required properties */ ret = device_property_read_u32(bq->dev, "ti,charge-current", &property); if (ret < 0) return ret; @@ -664,6 +691,24 @@ static int bq24257_fw_probe(struct bq24257_device *bq) bq->init_data.iterm = bq24257_find_idx(property, bq24257_iterm_map, BQ24257_ITERM_MAP_SIZE); + /* Optional properties. If not provided use reasonable default. */ + ret = device_property_read_u32(bq->dev, "ti,current-limit", + &property); + if (ret < 0) { + bq->iilimit_autoset_enable = true; + + /* + * Explicitly set a default value which will be needed for + * devices that don't support the automatic setting of the input + * current limit through the charger type detection mechanism. + */ + bq->init_data.iilimit = IILIMIT_500; + } else + bq->init_data.iilimit = + bq24257_find_idx(property, + bq24257_iilimit_map, + BQ24257_IILIMIT_MAP_SIZE); + return 0; } @@ -722,8 +767,6 @@ static int bq24257_probe(struct i2c_client *client, i2c_set_clientdata(client, bq); - INIT_DELAYED_WORK(&bq->iilimit_setup_work, bq24257_iilimit_setup_work); - if (!dev->platform_data) { ret = bq24257_fw_probe(bq); if (ret < 0) { @@ -734,6 +777,18 @@ static int bq24257_probe(struct i2c_client *client, return -ENODEV; } + /* + * The BQ24250 doesn't support the D+/D- based charger type detection + * used for the automatic setting of the input current limit setting so + * explicitly disable that feature. + */ + if (bq->chip == BQ24250) + bq->iilimit_autoset_enable = false; + + if (bq->iilimit_autoset_enable) + INIT_DELAYED_WORK(&bq->iilimit_setup_work, + bq24257_iilimit_setup_work); + /* we can only check Power Good status by probing the PG pin */ ret = bq24257_pg_gpio_probe(bq); if (ret < 0) @@ -780,7 +835,8 @@ static int bq24257_remove(struct i2c_client *client) { struct bq24257_device *bq = i2c_get_clientdata(client); - cancel_delayed_work_sync(&bq->iilimit_setup_work); + if (bq->iilimit_autoset_enable) + cancel_delayed_work_sync(&bq->iilimit_setup_work); bq24257_field_write(bq, F_RESET, 1); /* reset to defaults */ @@ -793,7 +849,8 @@ static int bq24257_suspend(struct device *dev) struct bq24257_device *bq = dev_get_drvdata(dev); int ret = 0; - cancel_delayed_work_sync(&bq->iilimit_setup_work); + if (bq->iilimit_autoset_enable) + cancel_delayed_work_sync(&bq->iilimit_setup_work); /* reset all registers to default (and activate standalone mode) */ ret = bq24257_field_write(bq, F_RESET, 1); -- cgit v1.2.3 From 7c071a0a08f6c6327b86df918d2dda728355d457 Mon Sep 17 00:00:00 2001 From: Andreas Dannenberg Date: Mon, 28 Sep 2015 17:33:56 -0500 Subject: power: bq24257: Add SW-based approach for Power Good determination A software-based approach for determining the charger's input voltage "Power Good" state is introduced for devices like the bq24250 which don't have a dedicated hardware pin for that purpose. This SW-based approach is also used for other devices (with dedicated PG pin) as a fall back solution if that pin is not configured to be used through "pg-gpios". Signed-off-by: Andreas Dannenberg Reviewed-by: Krzysztof Kozlowski Signed-off-by: Sebastian Reichel --- drivers/power/bq24257_charger.c | 56 +++++++++++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/power/bq24257_charger.c b/drivers/power/bq24257_charger.c index 7bbff806624f..15e0e9395371 100644 --- a/drivers/power/bq24257_charger.c +++ b/drivers/power/bq24257_charger.c @@ -359,7 +359,26 @@ static int bq24257_get_chip_state(struct bq24257_device *bq, state->fault = ret; - state->power_good = !gpiod_get_value_cansleep(bq->pg); + if (bq->pg) + state->power_good = !gpiod_get_value_cansleep(bq->pg); + else + /* + * If we have a chip without a dedicated power-good GPIO or + * some other explicit bit that would provide this information + * assume the power is good if there is no supply related + * fault - and not good otherwise. There is a possibility for + * other errors to mask that power in fact is not good but this + * is probably the best we can do here. + */ + switch (state->fault) { + case FAULT_INPUT_OVP: + case FAULT_INPUT_UVLO: + case FAULT_INPUT_LDO_LOW: + state->power_good = false; + break; + default: + state->power_good = true; + } return 0; } @@ -651,15 +670,21 @@ static int bq24257_power_supply_init(struct bq24257_device *bq) return PTR_ERR_OR_ZERO(bq->charger); } -static int bq24257_pg_gpio_probe(struct bq24257_device *bq) +static void bq24257_pg_gpio_probe(struct bq24257_device *bq) { - bq->pg = devm_gpiod_get_index(bq->dev, BQ24257_PG_GPIO, 0, GPIOD_IN); - if (IS_ERR(bq->pg)) { - dev_err(bq->dev, "could not probe PG pin\n"); - return PTR_ERR(bq->pg); + bq->pg = devm_gpiod_get_optional(bq->dev, BQ24257_PG_GPIO, GPIOD_IN); + + if (PTR_ERR(bq->pg) == -EPROBE_DEFER) { + dev_info(bq->dev, "probe retry requested for PG pin\n"); + return; + } else if (IS_ERR(bq->pg)) { + dev_err(bq->dev, "error probing PG pin\n"); + bq->pg = NULL; + return; } - return 0; + if (bq->pg) + dev_dbg(bq->dev, "probed PG pin = %d\n", desc_to_gpio(bq->pg)); } static int bq24257_fw_probe(struct bq24257_device *bq) @@ -789,10 +814,19 @@ static int bq24257_probe(struct i2c_client *client, INIT_DELAYED_WORK(&bq->iilimit_setup_work, bq24257_iilimit_setup_work); - /* we can only check Power Good status by probing the PG pin */ - ret = bq24257_pg_gpio_probe(bq); - if (ret < 0) - return ret; + /* + * The BQ24250 doesn't have a dedicated Power Good (PG) pin so let's + * not probe for it and instead use a SW-based approach to determine + * the PG state. We also use a SW-based approach for all other devices + * if the PG pin is either not defined or can't be probed. + */ + if (bq->chip != BQ24250) + bq24257_pg_gpio_probe(bq); + + if (PTR_ERR(bq->pg) == -EPROBE_DEFER) + return PTR_ERR(bq->pg); + else if (!bq->pg) + dev_info(bq->dev, "using SW-based power-good detection\n"); /* reset all registers to defaults */ ret = bq24257_field_write(bq, F_RESET, 1); -- cgit v1.2.3 From bb2956e8e1976d876a755896f5be287cb7e766b2 Mon Sep 17 00:00:00 2001 From: Andreas Dannenberg Date: Mon, 28 Sep 2015 17:33:57 -0500 Subject: power: bq24257: Add over voltage protection setting support A new optional device property called "ti,ovp-voltage" is introduced to allow configuring the input over voltage protection setting. This commit also adds the basic sysfs support for custom properties which is being used to allow userspace to read the current ovp-voltage setting. Signed-off-by: Andreas Dannenberg Reviewed-by: Laurentiu Palcu Reviewed-by: Krzysztof Kozlowski Signed-off-by: Sebastian Reichel --- drivers/power/bq24257_charger.c | 67 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/power/bq24257_charger.c b/drivers/power/bq24257_charger.c index 15e0e9395371..b617c3a20652 100644 --- a/drivers/power/bq24257_charger.c +++ b/drivers/power/bq24257_charger.c @@ -78,6 +78,7 @@ struct bq24257_init_data { u8 vbat; /* regulation voltage */ u8 iterm; /* termination current */ u8 iilimit; /* input current limit */ + u8 vovp; /* over voltage protection voltage */ }; struct bq24257_state { @@ -200,6 +201,13 @@ static const u32 bq24257_iilimit_map[] = { #define BQ24257_IILIMIT_MAP_SIZE ARRAY_SIZE(bq24257_iilimit_map) +static const u32 bq24257_vovp_map[] = { + 6000000, 6500000, 7000000, 8000000, 9000000, 9500000, 10000000, + 10500000 +}; + +#define BQ24257_VOVP_MAP_SIZE ARRAY_SIZE(bq24257_vovp_map) + static int bq24257_field_read(struct bq24257_device *bq, enum bq24257_fields field_id) { @@ -415,6 +423,17 @@ enum bq24257_in_ilimit { IILIMIT_NONE, }; +enum bq24257_vovp { + VOVP_6000, + VOVP_6500, + VOVP_7000, + VOVP_8000, + VOVP_9000, + VOVP_9500, + VOVP_10000, + VOVP_10500 +}; + enum bq24257_port_type { PORT_TYPE_DCP, /* Dedicated Charging Port */ PORT_TYPE_CDP, /* Charging Downstream Port */ @@ -586,7 +605,8 @@ static int bq24257_hw_init(struct bq24257_device *bq) } init_data[] = { {F_ICHG, bq->init_data.ichg}, {F_VBAT, bq->init_data.vbat}, - {F_ITERM, bq->init_data.iterm} + {F_ITERM, bq->init_data.iterm}, + {F_VOVP, bq->init_data.vovp}, }; /* @@ -656,6 +676,28 @@ static const struct power_supply_desc bq24257_power_supply_desc = { .get_property = bq24257_power_supply_get_property, }; +static ssize_t bq24257_show_ovp_voltage(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct bq24257_device *bq = power_supply_get_drvdata(psy); + + return scnprintf(buf, PAGE_SIZE, "%u\n", + bq24257_vovp_map[bq->init_data.vovp]); +} + +static DEVICE_ATTR(ovp_voltage, S_IRUGO, bq24257_show_ovp_voltage, NULL); + +static struct attribute *bq24257_charger_attr[] = { + &dev_attr_ovp_voltage.attr, + NULL, +}; + +static const struct attribute_group bq24257_attr_group = { + .attrs = bq24257_charger_attr, +}; + static int bq24257_power_supply_init(struct bq24257_device *bq) { struct power_supply_config psy_cfg = { .drv_data = bq, }; @@ -734,6 +776,15 @@ static int bq24257_fw_probe(struct bq24257_device *bq) bq24257_iilimit_map, BQ24257_IILIMIT_MAP_SIZE); + ret = device_property_read_u32(bq->dev, "ti,ovp-voltage", + &property); + if (ret < 0) + bq->init_data.vovp = VOVP_6500; + else + bq->init_data.vovp = bq24257_find_idx(property, + bq24257_vovp_map, + BQ24257_VOVP_MAP_SIZE); + return 0; } @@ -859,10 +910,18 @@ static int bq24257_probe(struct i2c_client *client, } ret = bq24257_power_supply_init(bq); - if (ret < 0) + if (ret < 0) { dev_err(dev, "Failed to register power supply\n"); + return ret; + } - return ret; + ret = sysfs_create_group(&bq->charger->dev.kobj, &bq24257_attr_group); + if (ret < 0) { + dev_err(dev, "Can't create sysfs entries\n"); + return ret; + } + + return 0; } static int bq24257_remove(struct i2c_client *client) @@ -872,6 +931,8 @@ static int bq24257_remove(struct i2c_client *client) if (bq->iilimit_autoset_enable) cancel_delayed_work_sync(&bq->iilimit_setup_work); + sysfs_remove_group(&bq->charger->dev.kobj, &bq24257_attr_group); + bq24257_field_write(bq, F_RESET, 1); /* reset to defaults */ return 0; -- cgit v1.2.3 From 138606ffe45511fa774e51df04f6da562ff9c44d Mon Sep 17 00:00:00 2001 From: Andreas Dannenberg Date: Mon, 28 Sep 2015 17:33:58 -0500 Subject: power: bq24257: Add input DPM voltage threshold setting support A new optional device property called "ti,in-dpm-voltage" is introduced to allow configuring the input voltage threshold for the devices' dynamic power path management (DPM) feature. In short, it can be used to prevent the input voltage from dropping below a certain value as current is drawn to charge the battery or supply the system. Signed-off-by: Andreas Dannenberg Reviewed-by: Laurentiu Palcu Reviewed-by: Krzysztof Kozlowski Signed-off-by: Sebastian Reichel --- drivers/power/bq24257_charger.c | 43 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) (limited to 'drivers') diff --git a/drivers/power/bq24257_charger.c b/drivers/power/bq24257_charger.c index b617c3a20652..ebb22f3be487 100644 --- a/drivers/power/bq24257_charger.c +++ b/drivers/power/bq24257_charger.c @@ -79,6 +79,7 @@ struct bq24257_init_data { u8 iterm; /* termination current */ u8 iilimit; /* input current limit */ u8 vovp; /* over voltage protection voltage */ + u8 vindpm; /* VDMP input threshold voltage */ }; struct bq24257_state { @@ -208,6 +209,13 @@ static const u32 bq24257_vovp_map[] = { #define BQ24257_VOVP_MAP_SIZE ARRAY_SIZE(bq24257_vovp_map) +static const u32 bq24257_vindpm_map[] = { + 4200000, 4280000, 4360000, 4440000, 4520000, 4600000, 4680000, + 4760000 +}; + +#define BQ24257_VINDPM_MAP_SIZE ARRAY_SIZE(bq24257_vindpm_map) + static int bq24257_field_read(struct bq24257_device *bq, enum bq24257_fields field_id) { @@ -434,6 +442,17 @@ enum bq24257_vovp { VOVP_10500 }; +enum bq24257_vindpm { + VINDPM_4200, + VINDPM_4280, + VINDPM_4360, + VINDPM_4440, + VINDPM_4520, + VINDPM_4600, + VINDPM_4680, + VINDPM_4760 +}; + enum bq24257_port_type { PORT_TYPE_DCP, /* Dedicated Charging Port */ PORT_TYPE_CDP, /* Charging Downstream Port */ @@ -607,6 +626,7 @@ static int bq24257_hw_init(struct bq24257_device *bq) {F_VBAT, bq->init_data.vbat}, {F_ITERM, bq->init_data.iterm}, {F_VOVP, bq->init_data.vovp}, + {F_VINDPM, bq->init_data.vindpm}, }; /* @@ -687,10 +707,23 @@ static ssize_t bq24257_show_ovp_voltage(struct device *dev, bq24257_vovp_map[bq->init_data.vovp]); } +static ssize_t bq24257_show_in_dpm_voltage(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct bq24257_device *bq = power_supply_get_drvdata(psy); + + return scnprintf(buf, PAGE_SIZE, "%u\n", + bq24257_vindpm_map[bq->init_data.vindpm]); +} + static DEVICE_ATTR(ovp_voltage, S_IRUGO, bq24257_show_ovp_voltage, NULL); +static DEVICE_ATTR(in_dpm_voltage, S_IRUGO, bq24257_show_in_dpm_voltage, NULL); static struct attribute *bq24257_charger_attr[] = { &dev_attr_ovp_voltage.attr, + &dev_attr_in_dpm_voltage.attr, NULL, }; @@ -785,6 +818,16 @@ static int bq24257_fw_probe(struct bq24257_device *bq) bq24257_vovp_map, BQ24257_VOVP_MAP_SIZE); + ret = device_property_read_u32(bq->dev, "ti,in-dpm-voltage", + &property); + if (ret < 0) + bq->init_data.vindpm = VINDPM_4360; + else + bq->init_data.vindpm = + bq24257_find_idx(property, + bq24257_vindpm_map, + BQ24257_VINDPM_MAP_SIZE); + return 0; } -- cgit v1.2.3 From 0cfbfde65aec4cd9ae4e7971f8a3e42c69e8e24f Mon Sep 17 00:00:00 2001 From: Andreas Dannenberg Date: Mon, 28 Sep 2015 17:33:59 -0500 Subject: power: bq24257: Allow input current limit sysfs access This patch allows reading and writing of the input current limit through the power supply's input_current_limit sysfs property. This allows userspace to see what charger was detected (if the D+/D- USB signal- based charger type detection is enabled) and to re-configure the maximum current drawn from the external supply at runtime based on system-level knowledge or user input. Note that upon charger disconnection and re-connection the limit configured through firmware becomes active again (or the D+/D- USB signal-based charger detection will be run again). Signed-off-by: Andreas Dannenberg Reviewed-by: Krzysztof Kozlowski Signed-off-by: Sebastian Reichel --- drivers/power/bq24257_charger.c | 85 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 79 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/power/bq24257_charger.c b/drivers/power/bq24257_charger.c index ebb22f3be487..db719bf96452 100644 --- a/drivers/power/bq24257_charger.c +++ b/drivers/power/bq24257_charger.c @@ -267,6 +267,47 @@ enum bq24257_fault { FAULT_INPUT_LDO_LOW, }; +static int bq24257_get_input_current_limit(struct bq24257_device *bq, + union power_supply_propval *val) +{ + int ret; + + ret = bq24257_field_read(bq, F_IILIMIT); + if (ret < 0) + return ret; + + /* + * The "External ILIM" and "Production & Test" modes are not exposed + * through this driver and not being covered by the lookup table. + * Should such a mode have become active let's return an error rather + * than exceeding the bounds of the lookup table and returning + * garbage. + */ + if (ret >= BQ24257_IILIMIT_MAP_SIZE) + return -ENODATA; + + val->intval = bq24257_iilimit_map[ret]; + + return 0; +} + +static int bq24257_set_input_current_limit(struct bq24257_device *bq, + const union power_supply_propval *val) +{ + /* + * Address the case where the user manually sets an input current limit + * while the charger auto-detection mechanism is is active. In this + * case we want to abort and go straight to the user-specified value. + */ + if (bq->iilimit_autoset_enable) + cancel_delayed_work_sync(&bq->iilimit_setup_work); + + return bq24257_field_write(bq, F_IILIMIT, + bq24257_find_idx(val->intval, + bq24257_iilimit_map, + BQ24257_IILIMIT_MAP_SIZE)); +} + static int bq24257_power_supply_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) @@ -351,6 +392,9 @@ static int bq24257_power_supply_get_property(struct power_supply *psy, val->intval = bq24257_iterm_map[bq->init_data.iterm]; break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + return bq24257_get_input_current_limit(bq, val); + default: return -EINVAL; } @@ -358,6 +402,31 @@ static int bq24257_power_supply_get_property(struct power_supply *psy, return 0; } +static int bq24257_power_supply_set_property(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + struct bq24257_device *bq = power_supply_get_drvdata(psy); + + switch (prop) { + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + return bq24257_set_input_current_limit(bq, val); + default: + return -EINVAL; + } +} + +static int bq24257_power_supply_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + return true; + default: + return false; + } +} + static int bq24257_get_chip_state(struct bq24257_device *bq, struct bq24257_state *state) { @@ -559,13 +628,14 @@ static void bq24257_handle_state_change(struct bq24257_device *bq, ret = bq24257_field_write(bq, F_DPDM_EN, 1); if (ret < 0) goto error; - - /* reset input current limit */ - ret = bq24257_field_write(bq, F_IILIMIT, - bq->init_data.iilimit); - if (ret < 0) - goto error; } + /* + * When power is removed always return to the default input + * current limit as configured during probe. + */ + ret = bq24257_field_write(bq, F_IILIMIT, bq->init_data.iilimit); + if (ret < 0) + goto error; } else if (!old_state.power_good) { dev_dbg(bq->dev, "Power inserted\n"); @@ -682,6 +752,7 @@ static enum power_supply_property bq24257_power_supply_props[] = { POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, }; static char *bq24257_charger_supplied_to[] = { @@ -694,6 +765,8 @@ static const struct power_supply_desc bq24257_power_supply_desc = { .properties = bq24257_power_supply_props, .num_properties = ARRAY_SIZE(bq24257_power_supply_props), .get_property = bq24257_power_supply_get_property, + .set_property = bq24257_power_supply_set_property, + .property_is_writeable = bq24257_power_supply_property_is_writeable, }; static ssize_t bq24257_show_ovp_voltage(struct device *dev, -- cgit v1.2.3 From 007ee5f65693fd7370c0f6e70269175ac2ed1a28 Mon Sep 17 00:00:00 2001 From: Andreas Dannenberg Date: Mon, 28 Sep 2015 17:34:00 -0500 Subject: power: bq24257: Add various device-specific sysfs properties This patch adds support for enabling/disabling optional device specific features through sysfs properties at runtime. * High-impedance mode enable/disable * Sysoff enable/disable Refer to the respective device datasheets for more information: http://www.ti.com/product/bq24250 http://www.ti.com/product/bq24251 http://www.ti.com/product/bq24257 Signed-off-by: Andreas Dannenberg Reviewed-by: Krzysztof Kozlowski Signed-off-by: Sebastian Reichel --- drivers/power/bq24257_charger.c | 53 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) (limited to 'drivers') diff --git a/drivers/power/bq24257_charger.c b/drivers/power/bq24257_charger.c index db719bf96452..1fea2c7ef97f 100644 --- a/drivers/power/bq24257_charger.c +++ b/drivers/power/bq24257_charger.c @@ -791,12 +791,65 @@ static ssize_t bq24257_show_in_dpm_voltage(struct device *dev, bq24257_vindpm_map[bq->init_data.vindpm]); } +static ssize_t bq24257_sysfs_show_enable(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct bq24257_device *bq = power_supply_get_drvdata(psy); + int ret; + + if (strcmp(attr->attr.name, "high_impedance_enable") == 0) + ret = bq24257_field_read(bq, F_HZ_MODE); + else if (strcmp(attr->attr.name, "sysoff_enable") == 0) + ret = bq24257_field_read(bq, F_SYSOFF); + else + return -EINVAL; + + if (ret < 0) + return ret; + + return scnprintf(buf, PAGE_SIZE, "%d\n", ret); +} + +static ssize_t bq24257_sysfs_set_enable(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct bq24257_device *bq = power_supply_get_drvdata(psy); + long val; + int ret; + + if (kstrtol(buf, 10, &val) < 0) + return -EINVAL; + + if (strcmp(attr->attr.name, "high_impedance_enable") == 0) + ret = bq24257_field_write(bq, F_HZ_MODE, (bool)val); + else if (strcmp(attr->attr.name, "sysoff_enable") == 0) + ret = bq24257_field_write(bq, F_SYSOFF, (bool)val); + else + return -EINVAL; + + if (ret < 0) + return ret; + + return count; +} + static DEVICE_ATTR(ovp_voltage, S_IRUGO, bq24257_show_ovp_voltage, NULL); static DEVICE_ATTR(in_dpm_voltage, S_IRUGO, bq24257_show_in_dpm_voltage, NULL); +static DEVICE_ATTR(high_impedance_enable, S_IWUSR | S_IRUGO, + bq24257_sysfs_show_enable, bq24257_sysfs_set_enable); +static DEVICE_ATTR(sysoff_enable, S_IWUSR | S_IRUGO, + bq24257_sysfs_show_enable, bq24257_sysfs_set_enable); static struct attribute *bq24257_charger_attr[] = { &dev_attr_ovp_voltage.attr, &dev_attr_in_dpm_voltage.attr, + &dev_attr_high_impedance_enable.attr, + &dev_attr_sysoff_enable.attr, NULL, }; -- cgit v1.2.3 From 8e5cfb74bc9c8c12cf91d253b3d27753d54b0d86 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sat, 10 Oct 2015 14:30:51 +0200 Subject: power_supply: charger-manager: add missing of_node_put for_each_child_of_node performs an of_node_get on each iteration, so a break out of the loop requires an of_node_put. The semantic patch that fixes this problem is as follows (http://coccinelle.lip6.fr): // @@ expression root,e; local idexpression child; @@ for_each_child_of_node(root, child) { ... when != of_node_put(child) when != e = child ( return child; | + of_node_put(child); ? return ...; ) ... } // Signed-off-by: Julia Lawall Reviewed-by: Krzysztof Kozlowski Signed-off-by: Sebastian Reichel --- drivers/power/charger-manager.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/power/charger-manager.c b/drivers/power/charger-manager.c index 907293e6f2a4..1ea5d1aa268b 100644 --- a/drivers/power/charger-manager.c +++ b/drivers/power/charger-manager.c @@ -1581,8 +1581,10 @@ static struct charger_desc *of_cm_parse_desc(struct device *dev) cables = devm_kzalloc(dev, sizeof(*cables) * chg_regs->num_cables, GFP_KERNEL); - if (!cables) + if (!cables) { + of_node_put(child); return ERR_PTR(-ENOMEM); + } chg_regs->cables = cables; -- cgit v1.2.3 From 0f4998cbb25855afb47f500e96483d8c38ba1524 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Thu, 15 Oct 2015 11:11:07 +0200 Subject: twl4030_charger: add missing iio dependency This driver fails to link without CONFIG_IIO, since there are no stubs for the iio_channels functions. Signed-off-by: Sebastian Reichel Acked-by: Marek Belisko Acked-by: Nikolaus Schaller --- drivers/power/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 9e68853217ca..cb0ae666dc68 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -313,7 +313,7 @@ config CHARGER_MAX8903 config CHARGER_TWL4030 tristate "OMAP TWL4030 BCI charger driver" - depends on TWL4030_CORE + depends on IIO && TWL4030_CORE help Say Y here to enable support for TWL4030 Battery Charge Interface. -- cgit v1.2.3 From 0077ae7e99de5720f8458e2db163fee77ee0fba5 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Fri, 16 Oct 2015 15:44:10 +0200 Subject: power: bq27xxx_battery: fix platform probe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add missing initialization of register mapping table to platform probe function. Signed-off-by: Sebastian Reichel Acked-by: Pali Rohár Acked-by: Andrew F. Davis --- drivers/power/bq27xxx_battery.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/power/bq27xxx_battery.c b/drivers/power/bq27xxx_battery.c index 994c78d9f426..69e6b371a51c 100644 --- a/drivers/power/bq27xxx_battery.c +++ b/drivers/power/bq27xxx_battery.c @@ -1292,6 +1292,7 @@ static int bq27xxx_battery_platform_probe(struct platform_device *pdev) di->dev = &pdev->dev; di->chip = pdata->chip; + di->regs = bq27xxx_regs[di->chip]; name = pdata->name ?: dev_name(&pdev->dev); di->bus.read = &bq27xxx_battery_platform_read; -- cgit v1.2.3 From 41a90db8fd35a682650ff8f01ff9cc05f53fa8a6 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Fri, 16 Oct 2015 15:44:11 +0200 Subject: power: bq27xxx_battery: move irq handler to i2c section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The IRQ handler is not used by the platform based code resulting in a 'defined but not used' warning, if CONFIG_BQ27XXX_I2C is not enabled. Signed-off-by: Sebastian Reichel Acked-by: Pali Rohár Acked-by: Andrew F. Davis --- drivers/power/bq27xxx_battery.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/power/bq27xxx_battery.c b/drivers/power/bq27xxx_battery.c index 69e6b371a51c..880233ce9343 100644 --- a/drivers/power/bq27xxx_battery.c +++ b/drivers/power/bq27xxx_battery.c @@ -762,15 +762,6 @@ static void bq27xxx_battery_update(struct bq27xxx_device_info *di) di->last_update = jiffies; } -static irqreturn_t bq27xxx_battery_irq_handler_thread(int irq, void *data) -{ - struct bq27xxx_device_info *di = data; - - bq27xxx_battery_update(di); - - return IRQ_HANDLED; -} - static void bq27xxx_battery_poll(struct work_struct *work) { struct bq27xxx_device_info *di = @@ -1061,6 +1052,15 @@ static void bq27xxx_powersupply_unregister(struct bq27xxx_device_info *di) static DEFINE_IDR(battery_id); static DEFINE_MUTEX(battery_mutex); +static irqreturn_t bq27xxx_battery_irq_handler_thread(int irq, void *data) +{ + struct bq27xxx_device_info *di = data; + + bq27xxx_battery_update(di); + + return IRQ_HANDLED; +} + static int bq27xxx_battery_i2c_read(struct bq27xxx_device_info *di, u8 reg, bool single) { -- cgit v1.2.3 From 6bd03ce3c12a22d86f59070f1da15aaa2bde8a51 Mon Sep 17 00:00:00 2001 From: "Andrew F. Davis" Date: Fri, 16 Oct 2015 09:49:20 -0500 Subject: power: bq27xxx_battery: Remove unneeded dependency in Kconfig MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I2C is only required when using the config item BATTERY_BQ27XXX_I2C which already depends on the I2C subsystem, remove the unneeded dependency from BATTERY_BQ27XXX. Signed-off-by: Andrew F. Davis Acked-by: Pali Rohár Signed-off-by: Sebastian Reichel --- drivers/power/Kconfig | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers') diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index cb0ae666dc68..02b3b313809a 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -159,7 +159,6 @@ config BATTERY_SBS config BATTERY_BQ27XXX tristate "BQ27xxx battery driver" - depends on I2C || I2C=n help Say Y here to enable support for batteries with BQ27xxx (I2C/HDQ) chips. -- cgit v1.2.3