From 815429b39d94c64f6d05eed9e7c1a9bdfdd5bd70 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Wed, 29 Mar 2017 19:30:17 +0900 Subject: [PATCH 01/29] extcon: Add new extcon_register_notifier_all() to monitor all external connectors The extcon core already provides the extcon_register_notifier() function in order to register the notifier block which is used to monitor the state change for the specific external connector such as EXTCON_USB, EXTCON_USB_HOST and so on. The extcon consumer uses the this function. The extcon consumer might need to monitor the all supported external connectors from the extcon device. In this case, The extcon consumer should have each notifier_block structure for each external connector. This patch adds the new extcon_register_notifier_all() function that extcon consumer is able to monitor the state change of all supported external connectors by using only one notifier_block structure. - List of new added functions: int extcon_register_notifier_all(struct extcon_dev *edev, struct notifier_block *nb); int extcon_unregister_notifier_all(struct extcon_dev *edev, struct notifier_block *nb); int devm_extcon_register_notifier_all(struct device *dev, struct extcon_dev *edev, struct notifier_block *nb); void devm_extcon_unregister_notifier_all(struct device *dev, struct extcon_dev *edev, struct notifier_block *nb); Suggested-by: Hans de Goede Signed-off-by: Chanwoo Choi Tested-by: Hans de Goede Acked-by: Hans de Goede --- drivers/extcon/devres.c | 61 +++++++++++++++++++++++++++++++++++++ drivers/extcon/extcon.c | 66 +++++++++++++++++++++++++++++++++++++++++ drivers/extcon/extcon.h | 3 ++ include/linux/extcon.h | 21 +++++++++---- 4 files changed, 146 insertions(+), 5 deletions(-) diff --git a/drivers/extcon/devres.c b/drivers/extcon/devres.c index b40eb1805927..186fd735eb28 100644 --- a/drivers/extcon/devres.c +++ b/drivers/extcon/devres.c @@ -50,6 +50,13 @@ static void devm_extcon_dev_notifier_unreg(struct device *dev, void *res) extcon_unregister_notifier(this->edev, this->id, this->nb); } +static void devm_extcon_dev_notifier_all_unreg(struct device *dev, void *res) +{ + struct extcon_dev_notifier_devres *this = res; + + extcon_unregister_notifier_all(this->edev, this->nb); +} + /** * devm_extcon_dev_allocate - Allocate managed extcon device * @dev: device owning the extcon device being created @@ -214,3 +221,57 @@ void devm_extcon_unregister_notifier(struct device *dev, devm_extcon_dev_match, edev)); } EXPORT_SYMBOL(devm_extcon_unregister_notifier); + +/** + * devm_extcon_register_notifier_all() + * - Resource-managed extcon_register_notifier_all() + * @dev: device to allocate extcon device + * @edev: the extcon device that has the external connecotr. + * @nb: a notifier block to be registered. + * + * This function manages automatically the notifier of extcon device using + * device resource management and simplify the control of unregistering + * the notifier of extcon device. To get more information, refer that function. + * + * Returns 0 if success or negaive error number if failure. + */ +int devm_extcon_register_notifier_all(struct device *dev, struct extcon_dev *edev, + struct notifier_block *nb) +{ + struct extcon_dev_notifier_devres *ptr; + int ret; + + ptr = devres_alloc(devm_extcon_dev_notifier_all_unreg, sizeof(*ptr), + GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + ret = extcon_register_notifier_all(edev, nb); + if (ret) { + devres_free(ptr); + return ret; + } + + ptr->edev = edev; + ptr->nb = nb; + devres_add(dev, ptr); + + return 0; +} +EXPORT_SYMBOL(devm_extcon_register_notifier_all); + +/** + * devm_extcon_unregister_notifier_all() + * - Resource-managed extcon_unregister_notifier_all() + * @dev: device to allocate extcon device + * @edev: the extcon device that has the external connecotr. + * @nb: a notifier block to be registered. + */ +void devm_extcon_unregister_notifier_all(struct device *dev, + struct extcon_dev *edev, + struct notifier_block *nb) +{ + WARN_ON(devres_release(dev, devm_extcon_dev_notifier_all_unreg, + devm_extcon_dev_match, edev)); +} +EXPORT_SYMBOL(devm_extcon_unregister_notifier_all); diff --git a/drivers/extcon/extcon.c b/drivers/extcon/extcon.c index 09ac5e70c2f3..e7750545469f 100644 --- a/drivers/extcon/extcon.c +++ b/drivers/extcon/extcon.c @@ -448,8 +448,19 @@ int extcon_sync(struct extcon_dev *edev, unsigned int id) spin_lock_irqsave(&edev->lock, flags); state = !!(edev->state & BIT(index)); + + /* + * Call functions in a raw notifier chain for the specific one + * external connector. + */ raw_notifier_call_chain(&edev->nh[index], state, edev); + /* + * Call functions in a raw notifier chain for the all supported + * external connectors. + */ + raw_notifier_call_chain(&edev->nh_all, state, edev); + /* This could be in interrupt handler */ prop_buf = (char *)get_zeroed_page(GFP_ATOMIC); if (!prop_buf) { @@ -954,6 +965,59 @@ int extcon_unregister_notifier(struct extcon_dev *edev, unsigned int id, } EXPORT_SYMBOL_GPL(extcon_unregister_notifier); +/** + * extcon_register_notifier_all() - Register a notifier block for all connectors + * @edev: the extcon device that has the external connecotr. + * @nb: a notifier block to be registered. + * + * This fucntion registers a notifier block in order to receive the state + * change of all supported external connectors from extcon device. + * And The second parameter given to the callback of nb (val) is + * the current state and third parameter is the edev pointer. + * + * Returns 0 if success or error number if fail + */ +int extcon_register_notifier_all(struct extcon_dev *edev, + struct notifier_block *nb) +{ + unsigned long flags; + int ret; + + if (!edev || !nb) + return -EINVAL; + + spin_lock_irqsave(&edev->lock, flags); + ret = raw_notifier_chain_register(&edev->nh_all, nb); + spin_unlock_irqrestore(&edev->lock, flags); + + return ret; +} +EXPORT_SYMBOL_GPL(extcon_register_notifier_all); + +/** + * extcon_unregister_notifier_all() - Unregister a notifier block from extcon. + * @edev: the extcon device that has the external connecotr. + * @nb: a notifier block to be registered. + * + * Returns 0 if success or error number if fail + */ +int extcon_unregister_notifier_all(struct extcon_dev *edev, + struct notifier_block *nb) +{ + unsigned long flags; + int ret; + + if (!edev || !nb) + return -EINVAL; + + spin_lock_irqsave(&edev->lock, flags); + ret = raw_notifier_chain_unregister(&edev->nh_all, nb); + spin_unlock_irqrestore(&edev->lock, flags); + + return ret; +} +EXPORT_SYMBOL_GPL(extcon_unregister_notifier_all); + static struct attribute *extcon_attrs[] = { &dev_attr_state.attr, &dev_attr_name.attr, @@ -1212,6 +1276,8 @@ int extcon_dev_register(struct extcon_dev *edev) for (index = 0; index < edev->max_supported; index++) RAW_INIT_NOTIFIER_HEAD(&edev->nh[index]); + RAW_INIT_NOTIFIER_HEAD(&edev->nh_all); + dev_set_drvdata(&edev->dev, edev); edev->state = 0; diff --git a/drivers/extcon/extcon.h b/drivers/extcon/extcon.h index 993ddccafe11..dddddcfa0587 100644 --- a/drivers/extcon/extcon.h +++ b/drivers/extcon/extcon.h @@ -21,6 +21,8 @@ * @dev: Device of this extcon. * @state: Attach/detach state of this extcon. Do not provide at * register-time. + * @nh_all: Notifier for the state change events for all supported + * external connectors from this extcon. * @nh: Notifier for the state change events from this extcon * @entry: To support list of extcon devices so that users can * search for extcon devices based on the extcon name. @@ -43,6 +45,7 @@ struct extcon_dev { /* Internal data. Please do not set. */ struct device dev; + struct raw_notifier_head nh_all; struct raw_notifier_head *nh; struct list_head entry; int max_supported; diff --git a/include/linux/extcon.h b/include/linux/extcon.h index 7010fb01a81a..7e206a9f88db 100644 --- a/include/linux/extcon.h +++ b/include/linux/extcon.h @@ -236,11 +236,11 @@ extern int extcon_set_property_capability(struct extcon_dev *edev, unsigned int id, unsigned int prop); /* - * Following APIs are to monitor every action of a notifier. - * Registrar gets notified for every external port of a connection device. - * Probably this could be used to debug an action of notifier; however, - * we do not recommend to use this for normal 'notifiee' device drivers who - * want to be notified by a specific external port of the notifier. + * Following APIs are to monitor the status change of the external connectors. + * extcon_register_notifier(*edev, id, *nb) : Register a notifier block + * for specific external connector of the extcon. + * extcon_register_notifier_all(*edev, *nb) : Register a notifier block + * for all supported external connectors of the extcon. */ extern int extcon_register_notifier(struct extcon_dev *edev, unsigned int id, struct notifier_block *nb); @@ -253,6 +253,17 @@ extern void devm_extcon_unregister_notifier(struct device *dev, struct extcon_dev *edev, unsigned int id, struct notifier_block *nb); +extern int extcon_register_notifier_all(struct extcon_dev *edev, + struct notifier_block *nb); +extern int extcon_unregister_notifier_all(struct extcon_dev *edev, + struct notifier_block *nb); +extern int devm_extcon_register_notifier_all(struct device *dev, + struct extcon_dev *edev, + struct notifier_block *nb); +extern void devm_extcon_unregister_notifier_all(struct device *dev, + struct extcon_dev *edev, + struct notifier_block *nb); + /* * Following API get the extcon device from devicetree. * This function use phandle of devicetree to get extcon device directly. From 35c3c196d3948d6862b19112d19b8dda880083ff Mon Sep 17 00:00:00 2001 From: Tony Lindgren Date: Wed, 8 Feb 2017 15:13:00 -0800 Subject: [PATCH 02/29] power: bq24190_charger: Check the interrupt status on resume Some SoCs like omap3 can configure GPIO irqs to use Linux generic dedicated wakeirq support. If the dedicated wakeirq is configured, the SoC will use a always-on interrupt controller to produce wake-up events. If bq24190 is configured for dedicated wakeirq, we need to check the interrupt status on PM runtime resume. This is because the Linux generic wakeirq will call pm_runtime_resume() on the device on a wakeirq. And as the bq24190 interrupt is falling edge sensitive and only active for 250 us, there will be no device interrupt seen by the runtime SoC IRQ controller. Note that this can cause spurious interrupts on omap3 devices with bq24190 connected to gpio banks 2 - 5 as there's a glitch on those pins waking from off mode as listed in "Advisory 1.45". Devices with this issue should not configure the optional wakeirq interrupt in the dts file. Acked-by: Mark Greer Acked-by: Liam Breck Signed-off-by: Tony Lindgren Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq24190_charger.c | 62 +++++++++++++++++++++----- 1 file changed, 52 insertions(+), 10 deletions(-) diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c index a4f08492abeb..deb56f31c7a0 100644 --- a/drivers/power/supply/bq24190_charger.c +++ b/drivers/power/supply/bq24190_charger.c @@ -155,6 +155,8 @@ struct bq24190_dev_info { kernel_ulong_t model; unsigned int gpio_int; unsigned int irq; + bool initialized; + bool irq_event; struct mutex f_reg_lock; u8 f_reg; u8 ss_reg; @@ -1157,9 +1159,8 @@ static const struct power_supply_desc bq24190_battery_desc = { .property_is_writeable = bq24190_battery_property_is_writeable, }; -static irqreturn_t bq24190_irq_handler_thread(int irq, void *data) +static void bq24190_check_status(struct bq24190_dev_info *bdi) { - struct bq24190_dev_info *bdi = data; const u8 battery_mask_ss = BQ24190_REG_SS_CHRG_STAT_MASK; const u8 battery_mask_f = BQ24190_REG_F_BAT_FAULT_MASK | BQ24190_REG_F_NTC_FAULT_MASK; @@ -1167,12 +1168,10 @@ static irqreturn_t bq24190_irq_handler_thread(int irq, void *data) u8 ss_reg = 0, f_reg = 0; int i, ret; - pm_runtime_get_sync(bdi->dev); - ret = bq24190_read(bdi, BQ24190_REG_SS, &ss_reg); if (ret < 0) { dev_err(bdi->dev, "Can't read SS reg: %d\n", ret); - goto out; + return; } i = 0; @@ -1180,7 +1179,7 @@ static irqreturn_t bq24190_irq_handler_thread(int irq, void *data) ret = bq24190_read(bdi, BQ24190_REG_F, &f_reg); if (ret < 0) { dev_err(bdi->dev, "Can't read F reg: %d\n", ret); - goto out; + return; } } while (f_reg && ++i < 2); @@ -1229,10 +1228,18 @@ static irqreturn_t bq24190_irq_handler_thread(int irq, void *data) if (alert_battery) power_supply_changed(bdi->battery); -out: - pm_runtime_put_sync(bdi->dev); - dev_dbg(bdi->dev, "ss_reg: 0x%02x, f_reg: 0x%02x\n", ss_reg, f_reg); +} + +static irqreturn_t bq24190_irq_handler_thread(int irq, void *data) +{ + struct bq24190_dev_info *bdi = data; + + bdi->irq_event = true; + pm_runtime_get_sync(bdi->dev); + bq24190_check_status(bdi); + pm_runtime_put_sync(bdi->dev); + bdi->irq_event = false; return IRQ_HANDLED; } @@ -1391,6 +1398,8 @@ static int bq24190_probe(struct i2c_client *client, goto out3; } + bdi->initialized = true; + ret = devm_request_threaded_irq(dev, bdi->irq, NULL, bq24190_irq_handler_thread, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, @@ -1437,6 +1446,35 @@ static int bq24190_remove(struct i2c_client *client) return 0; } +static int bq24190_runtime_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct bq24190_dev_info *bdi = i2c_get_clientdata(client); + + if (!bdi->initialized) + return 0; + + dev_dbg(bdi->dev, "%s\n", __func__); + + return 0; +} + +static int bq24190_runtime_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct bq24190_dev_info *bdi = i2c_get_clientdata(client); + + if (!bdi->initialized) + return 0; + + if (!bdi->irq_event) { + dev_dbg(bdi->dev, "checking events on possible wakeirq\n"); + bq24190_check_status(bdi); + } + + return 0; +} + #ifdef CONFIG_PM_SLEEP static int bq24190_pm_suspend(struct device *dev) { @@ -1472,7 +1510,11 @@ static int bq24190_pm_resume(struct device *dev) } #endif -static SIMPLE_DEV_PM_OPS(bq24190_pm_ops, bq24190_pm_suspend, bq24190_pm_resume); +static const struct dev_pm_ops bq24190_pm_ops = { + SET_RUNTIME_PM_OPS(bq24190_runtime_suspend, bq24190_runtime_resume, + NULL) + SET_SYSTEM_SLEEP_PM_OPS(bq24190_pm_suspend, bq24190_pm_resume) +}; /* * Only support the bq24190 right now. The bq24192, bq24192i, and bq24193 From f385e6e2a153233033aa348766bac21125651d05 Mon Sep 17 00:00:00 2001 From: Tony Lindgren Date: Wed, 8 Feb 2017 15:13:01 -0800 Subject: [PATCH 03/29] power: bq24190_charger: Use PM runtime autosuspend We can get quite a few interrupts when the battery is trickle charging. Let's enable PM runtime autosuspend to avoid constantly toggling device driver PM runtime state. Let's use a 600 ms timeout as that's how long the USB chager detection might take. Acked-by: Mark Greer Acked-by: Liam Breck Signed-off-by: Tony Lindgren Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq24190_charger.c | 158 ++++++++++++++++++------- 1 file changed, 115 insertions(+), 43 deletions(-) diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c index deb56f31c7a0..6d80670586eb 100644 --- a/drivers/power/supply/bq24190_charger.c +++ b/drivers/power/supply/bq24190_charger.c @@ -420,6 +420,7 @@ static ssize_t bq24190_sysfs_show(struct device *dev, struct power_supply *psy = dev_get_drvdata(dev); struct bq24190_dev_info *bdi = power_supply_get_drvdata(psy); struct bq24190_sysfs_field_info *info; + ssize_t count; int ret; u8 v; @@ -427,11 +428,20 @@ static ssize_t bq24190_sysfs_show(struct device *dev, if (!info) return -EINVAL; - ret = bq24190_read_mask(bdi, info->reg, info->mask, info->shift, &v); - if (ret) + ret = pm_runtime_get_sync(bdi->dev); + if (ret < 0) return ret; - return scnprintf(buf, PAGE_SIZE, "%hhx\n", v); + ret = bq24190_read_mask(bdi, info->reg, info->mask, info->shift, &v); + if (ret) + count = ret; + else + count = scnprintf(buf, PAGE_SIZE, "%hhx\n", v); + + pm_runtime_mark_last_busy(bdi->dev); + pm_runtime_put_autosuspend(bdi->dev); + + return count; } static ssize_t bq24190_sysfs_store(struct device *dev, @@ -451,9 +461,16 @@ static ssize_t bq24190_sysfs_store(struct device *dev, if (ret < 0) return ret; + ret = pm_runtime_get_sync(bdi->dev); + if (ret < 0) + return ret; + ret = bq24190_write_mask(bdi, info->reg, info->mask, info->shift, v); if (ret) - return ret; + count = ret; + + pm_runtime_mark_last_busy(bdi->dev); + pm_runtime_put_autosuspend(bdi->dev); return count; } @@ -795,7 +812,9 @@ static int bq24190_charger_get_property(struct power_supply *psy, dev_dbg(bdi->dev, "prop: %d\n", psp); - pm_runtime_get_sync(bdi->dev); + ret = pm_runtime_get_sync(bdi->dev); + if (ret < 0) + return ret; switch (psp) { case POWER_SUPPLY_PROP_CHARGE_TYPE: @@ -835,7 +854,9 @@ static int bq24190_charger_get_property(struct power_supply *psy, ret = -ENODATA; } - pm_runtime_put_sync(bdi->dev); + pm_runtime_mark_last_busy(bdi->dev); + pm_runtime_put_autosuspend(bdi->dev); + return ret; } @@ -848,7 +869,9 @@ static int bq24190_charger_set_property(struct power_supply *psy, dev_dbg(bdi->dev, "prop: %d\n", psp); - pm_runtime_get_sync(bdi->dev); + ret = pm_runtime_get_sync(bdi->dev); + if (ret < 0) + return ret; switch (psp) { case POWER_SUPPLY_PROP_CHARGE_TYPE: @@ -864,7 +887,9 @@ static int bq24190_charger_set_property(struct power_supply *psy, ret = -EINVAL; } - pm_runtime_put_sync(bdi->dev); + pm_runtime_mark_last_busy(bdi->dev); + pm_runtime_put_autosuspend(bdi->dev); + return ret; } @@ -1065,7 +1090,9 @@ static int bq24190_battery_get_property(struct power_supply *psy, dev_dbg(bdi->dev, "prop: %d\n", psp); - pm_runtime_get_sync(bdi->dev); + ret = pm_runtime_get_sync(bdi->dev); + if (ret < 0) + return ret; switch (psp) { case POWER_SUPPLY_PROP_STATUS: @@ -1093,7 +1120,9 @@ static int bq24190_battery_get_property(struct power_supply *psy, ret = -ENODATA; } - pm_runtime_put_sync(bdi->dev); + pm_runtime_mark_last_busy(bdi->dev); + pm_runtime_put_autosuspend(bdi->dev); + return ret; } @@ -1106,7 +1135,9 @@ static int bq24190_battery_set_property(struct power_supply *psy, dev_dbg(bdi->dev, "prop: %d\n", psp); - pm_runtime_get_sync(bdi->dev); + ret = pm_runtime_get_sync(bdi->dev); + if (ret < 0) + return ret; switch (psp) { case POWER_SUPPLY_PROP_ONLINE: @@ -1119,7 +1150,9 @@ static int bq24190_battery_set_property(struct power_supply *psy, ret = -EINVAL; } - pm_runtime_put_sync(bdi->dev); + pm_runtime_mark_last_busy(bdi->dev); + pm_runtime_put_autosuspend(bdi->dev); + return ret; } @@ -1234,11 +1267,17 @@ static void bq24190_check_status(struct bq24190_dev_info *bdi) static irqreturn_t bq24190_irq_handler_thread(int irq, void *data) { struct bq24190_dev_info *bdi = data; + int ret; bdi->irq_event = true; - pm_runtime_get_sync(bdi->dev); + ret = pm_runtime_get_sync(bdi->dev); + if (ret < 0) { + dev_warn(bdi->dev, "pm_runtime_get failed: %i\n", ret); + return IRQ_NONE; + } bq24190_check_status(bdi); - pm_runtime_put_sync(bdi->dev); + pm_runtime_mark_last_busy(bdi->dev); + pm_runtime_put_autosuspend(bdi->dev); bdi->irq_event = false; return IRQ_HANDLED; @@ -1249,33 +1288,26 @@ static int bq24190_hw_init(struct bq24190_dev_info *bdi) u8 v; int ret; - pm_runtime_get_sync(bdi->dev); - /* First check that the device really is what its supposed to be */ ret = bq24190_read_mask(bdi, BQ24190_REG_VPRS, BQ24190_REG_VPRS_PN_MASK, BQ24190_REG_VPRS_PN_SHIFT, &v); if (ret < 0) - goto out; + return ret; - if (v != bdi->model) { - ret = -ENODEV; - goto out; - } + if (v != bdi->model) + return -ENODEV; ret = bq24190_register_reset(bdi); if (ret < 0) - goto out; + return ret; ret = bq24190_set_mode_host(bdi); if (ret < 0) - goto out; + return ret; - ret = bq24190_read(bdi, BQ24190_REG_SS, &bdi->ss_reg); -out: - pm_runtime_put_sync(bdi->dev); - return ret; + return bq24190_read(bdi, BQ24190_REG_SS, &bdi->ss_reg); } #ifdef CONFIG_OF @@ -1364,12 +1396,16 @@ static int bq24190_probe(struct i2c_client *client, } pm_runtime_enable(dev); - pm_runtime_resume(dev); + pm_runtime_use_autosuspend(dev); + pm_runtime_set_autosuspend_delay(dev, 600); + ret = pm_runtime_get_sync(dev); + if (ret < 0) + goto out1; ret = bq24190_hw_init(bdi); if (ret < 0) { dev_err(dev, "Hardware init failed\n"); - goto out1; + goto out2; } charger_cfg.drv_data = bdi; @@ -1380,7 +1416,7 @@ static int bq24190_probe(struct i2c_client *client, if (IS_ERR(bdi->charger)) { dev_err(dev, "Can't register charger\n"); ret = PTR_ERR(bdi->charger); - goto out1; + goto out2; } battery_cfg.drv_data = bdi; @@ -1389,13 +1425,13 @@ static int bq24190_probe(struct i2c_client *client, if (IS_ERR(bdi->battery)) { dev_err(dev, "Can't register battery\n"); ret = PTR_ERR(bdi->battery); - goto out2; + goto out3; } ret = bq24190_sysfs_create_group(bdi); if (ret) { dev_err(dev, "Can't create sysfs entries\n"); - goto out3; + goto out4; } bdi->initialized = true; @@ -1406,21 +1442,30 @@ static int bq24190_probe(struct i2c_client *client, "bq24190-charger", bdi); if (ret < 0) { dev_err(dev, "Can't set up irq handler\n"); - goto out4; + goto out5; } + enable_irq_wake(bdi->irq); + + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + return 0; -out4: +out5: bq24190_sysfs_remove_group(bdi); -out3: +out4: power_supply_unregister(bdi->battery); -out2: +out3: power_supply_unregister(bdi->charger); +out2: + pm_runtime_put_sync(dev); + out1: + pm_runtime_dont_use_autosuspend(dev); pm_runtime_disable(dev); if (bdi->gpio_int) gpio_free(bdi->gpio_int); @@ -1430,14 +1475,21 @@ out1: static int bq24190_remove(struct i2c_client *client) { struct bq24190_dev_info *bdi = i2c_get_clientdata(client); + int error; + + error = pm_runtime_get_sync(bdi->dev); + if (error < 0) { + dev_warn(bdi->dev, "pm_runtime_get failed: %i\n", error); + pm_runtime_put_noidle(bdi->dev); + } - pm_runtime_get_sync(bdi->dev); bq24190_register_reset(bdi); - pm_runtime_put_sync(bdi->dev); - bq24190_sysfs_remove_group(bdi); power_supply_unregister(bdi->battery); power_supply_unregister(bdi->charger); + if (error >= 0) + pm_runtime_put_sync(bdi->dev); + pm_runtime_dont_use_autosuspend(bdi->dev); pm_runtime_disable(bdi->dev); if (bdi->gpio_int) @@ -1480,10 +1532,20 @@ static int bq24190_pm_suspend(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct bq24190_dev_info *bdi = i2c_get_clientdata(client); + int error; + + error = pm_runtime_get_sync(bdi->dev); + if (error < 0) { + dev_warn(bdi->dev, "pm_runtime_get failed: %i\n", error); + pm_runtime_put_noidle(bdi->dev); + } - pm_runtime_get_sync(bdi->dev); bq24190_register_reset(bdi); - pm_runtime_put_sync(bdi->dev); + + if (error >= 0) { + pm_runtime_mark_last_busy(bdi->dev); + pm_runtime_put_autosuspend(bdi->dev); + } return 0; } @@ -1492,15 +1554,25 @@ static int bq24190_pm_resume(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct bq24190_dev_info *bdi = i2c_get_clientdata(client); + int error; bdi->f_reg = 0; bdi->ss_reg = BQ24190_REG_SS_VBUS_STAT_MASK; /* impossible state */ - pm_runtime_get_sync(bdi->dev); + error = pm_runtime_get_sync(bdi->dev); + if (error < 0) { + dev_warn(bdi->dev, "pm_runtime_get failed: %i\n", error); + pm_runtime_put_noidle(bdi->dev); + } + bq24190_register_reset(bdi); bq24190_set_mode_host(bdi); bq24190_read(bdi, BQ24190_REG_SS, &bdi->ss_reg); - pm_runtime_put_sync(bdi->dev); + + if (error >= 0) { + pm_runtime_mark_last_busy(bdi->dev); + pm_runtime_put_autosuspend(bdi->dev); + } /* Things may have changed while suspended so alert upper layer */ power_supply_changed(bdi->charger); From f2c199db477efd8233e75b2d69bac8be8640308d Mon Sep 17 00:00:00 2001 From: Guy Shapiro Date: Tue, 7 Feb 2017 17:56:05 +0200 Subject: [PATCH 04/29] power: reset: syscon-poweroff: add a mask property Make the syscon-poweroff driver accept value and mask instead of just value. Prior to this patch, the property name for the value was 'mask'. If only the mask property is defined on a node, maintain compatibility by using it as the value. Signed-off-by: Guy Shapiro Acked-by: Rob Herring Signed-off-by: Sebastian Reichel --- .../bindings/power/reset/syscon-poweroff.txt | 11 +++++++++-- drivers/power/reset/syscon-poweroff.c | 19 ++++++++++++++++--- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/Documentation/devicetree/bindings/power/reset/syscon-poweroff.txt b/Documentation/devicetree/bindings/power/reset/syscon-poweroff.txt index 1e2546f8b08a..022ed1f3bc80 100644 --- a/Documentation/devicetree/bindings/power/reset/syscon-poweroff.txt +++ b/Documentation/devicetree/bindings/power/reset/syscon-poweroff.txt @@ -3,13 +3,20 @@ Generic SYSCON mapped register poweroff driver This is a generic poweroff driver using syscon to map the poweroff register. The poweroff is generally performed with a write to the poweroff register defined by the register map pointed by syscon reference plus the offset -with the mask defined in the poweroff node. +with the value and mask defined in the poweroff node. Required properties: - compatible: should contain "syscon-poweroff" - regmap: this is phandle to the register map node - offset: offset in the register map for the poweroff register (in bytes) -- mask: the poweroff value written to the poweroff register (32 bit access) +- value: the poweroff value written to the poweroff register (32 bit access) + +Optional properties: +- mask: update only the register bits defined by the mask (32 bit) + +Legacy usage: +If a node doesn't contain a value property but contains a mask property, the +mask property is used as the value. Default will be little endian mode, 32 bit access only. diff --git a/drivers/power/reset/syscon-poweroff.c b/drivers/power/reset/syscon-poweroff.c index b68338399e5e..f9f1cb54fbf9 100644 --- a/drivers/power/reset/syscon-poweroff.c +++ b/drivers/power/reset/syscon-poweroff.c @@ -28,12 +28,13 @@ static struct regmap *map; static u32 offset; +static u32 value; static u32 mask; static void syscon_poweroff(void) { /* Issue the poweroff */ - regmap_write(map, offset, mask); + regmap_update_bits(map, offset, mask, value); mdelay(1000); @@ -43,6 +44,7 @@ static void syscon_poweroff(void) static int syscon_poweroff_probe(struct platform_device *pdev) { char symname[KSYM_NAME_LEN]; + int mask_err, value_err; map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "regmap"); if (IS_ERR(map)) { @@ -55,11 +57,22 @@ static int syscon_poweroff_probe(struct platform_device *pdev) return -EINVAL; } - if (of_property_read_u32(pdev->dev.of_node, "mask", &mask)) { - dev_err(&pdev->dev, "unable to read 'mask'"); + value_err = of_property_read_u32(pdev->dev.of_node, "value", &value); + mask_err = of_property_read_u32(pdev->dev.of_node, "mask", &mask); + if (value_err && mask_err) { + dev_err(&pdev->dev, "unable to read 'value' and 'mask'"); return -EINVAL; } + if (value_err) { + /* support old binding */ + value = mask; + mask = 0xFFFFFFFF; + } else if (mask_err) { + /* support value without mask*/ + mask = 0xFFFFFFFF; + } + if (pm_power_off) { lookup_symbol_name((ulong)pm_power_off, symname); dev_err(&pdev->dev, From da28122c82fc60c34b19cf9d3879fd2548657028 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Wed, 15 Mar 2017 00:43:49 -0300 Subject: [PATCH 05/29] power: supply: max17040: Add OF device ID table The driver doesn't have a struct of_device_id table but supported devices are registered via Device Trees. This is working on the assumption that a I2C device registered via OF will always match a legacy I2C device ID and that the MODALIAS reported will always be of the form i2c:. But this could change in the future so the correct approach is to have an OF device ID table if the devices are registered via OF. Signed-off-by: Javier Martinez Canillas Signed-off-by: Sebastian Reichel --- drivers/power/supply/max17040_battery.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/power/supply/max17040_battery.c b/drivers/power/supply/max17040_battery.c index e7c3649b31a0..33c40f79d23d 100644 --- a/drivers/power/supply/max17040_battery.c +++ b/drivers/power/supply/max17040_battery.c @@ -277,9 +277,17 @@ static const struct i2c_device_id max17040_id[] = { }; MODULE_DEVICE_TABLE(i2c, max17040_id); +static const struct of_device_id max17040_of_match[] = { + { .compatible = "maxim,max17040" }, + { .compatible = "maxim,max77836-battery" }, + { }, +}; +MODULE_DEVICE_TABLE(of, max17040_of_match); + static struct i2c_driver max17040_i2c_driver = { .driver = { .name = "max17040", + .of_match_table = max17040_of_match, .pm = MAX17040_PM_OPS, }, .probe = max17040_probe, From ba443b5ab454a9b5f49229a94b2dadf06ac8b79e Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sun, 12 Mar 2017 23:36:01 +0100 Subject: [PATCH 06/29] power: reset: Add Gemini poweroff DT bindings This adds device tree bindings to the power management controller in the Gemini SoC. Cc: devicetree@vger.kernel.org Cc: Janos Laube Cc: Paulius Zaleckas Cc: Hans Ulli Kroll Cc: Florian Fainelli Acked-by: Rob Herring Signed-off-by: Linus Walleij Signed-off-by: Sebastian Reichel --- .../bindings/power/reset/gemini-poweroff.txt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 Documentation/devicetree/bindings/power/reset/gemini-poweroff.txt diff --git a/Documentation/devicetree/bindings/power/reset/gemini-poweroff.txt b/Documentation/devicetree/bindings/power/reset/gemini-poweroff.txt new file mode 100644 index 000000000000..7fec3e100214 --- /dev/null +++ b/Documentation/devicetree/bindings/power/reset/gemini-poweroff.txt @@ -0,0 +1,17 @@ +* Device-Tree bindings for Cortina Systems Gemini Poweroff + +This is a special IP block in the Cortina Gemini SoC that only +deals with different ways to power the system down. + +Required properties: +- compatible: should be "cortina,gemini-power-controller" +- reg: should contain the physical memory base and size +- interrupts: should contain the power management interrupt + +Example: + +power-controller@4b000000 { + compatible = "cortina,gemini-power-controller"; + reg = <0x4b000000 0x100>; + interrupts = <26 IRQ_TYPE_EDGE_FALLING>; +}; From f7a388d6cd1ccebfe7d2850ae4d33f84e954e96b Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sun, 12 Mar 2017 23:36:21 +0100 Subject: [PATCH 07/29] power: reset: Add a driver for the Gemini poweroff The Gemini (SL3516) SoC has a special power controller block that only deal with shutting down the system. If you do not register a driver and activate the block, the power button on the systems utilizing this SoC will do an uncontrolled power cut, which is why it is important to have a special poweroff driver. The most basic functionality is to just shut down the system by writing a special bit in the control register after the system has reached pm_poweroff. It also handles the poweroff from a button or other sources: When the poweroff button is pressed, or a signal is sent to poweroff from an infrared remote control, or when the RTC fires a special alarm (!) the system emits an interrupt. At this point, Linux must acknowledge the interrupt and proceed to do an orderly shutdown of the system. After adding this driver, pressing the poweroff button gives this dmesg: root@gemini:/ root@gemini:/ gemini-poweroff 4b000000.power-controller: poweroff button pressed calling shutdown scripts.. setting /dev/rtc0 from system time unmounting file systems... umount: tmpfs busy - remounted read-only umount: can't unmount /: Invalid argument The system is going down NOW! Sent SIGTERM to all processes Sent SIGKILL to all processes Requesting system poweroff uhci_hcd 0000:00:09.1: HCRESET not completed yet! uhci_hcd 0000:00:09.0: HCRESET not completed yet! reboot: Power down gemini-poweroff 4b000000.power-controller: Gemini power off Cc: Janos Laube Cc: Paulius Zaleckas Cc: Hans Ulli Kroll Cc: Florian Fainelli Cc: linux-pm@vger.kernel.org Cc: Sebastian Reichel Signed-off-by: Linus Walleij Signed-off-by: Sebastian Reichel --- drivers/power/reset/Kconfig | 9 ++ drivers/power/reset/Makefile | 1 + drivers/power/reset/gemini-poweroff.c | 160 ++++++++++++++++++++++++++ 3 files changed, 170 insertions(+) create mode 100644 drivers/power/reset/gemini-poweroff.c diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig index b8cacccf18c8..13f1714cf6f7 100644 --- a/drivers/power/reset/Kconfig +++ b/drivers/power/reset/Kconfig @@ -67,6 +67,15 @@ config POWER_RESET_BRCMSTB Say Y here if you have a Broadcom STB board and you wish to have restart support. +config POWER_RESET_GEMINI_POWEROFF + bool "Cortina Gemini power-off driver" + depends on ARCH_GEMINI || COMPILE_TEST + depends on OF && HAS_IOMEM + default ARCH_GEMINI + help + This driver supports turning off the Cortina Gemini SoC. + Select this if you're building a kernel with Gemini SoC support. + config POWER_RESET_GPIO bool "GPIO power-off driver" depends on OF_GPIO diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile index 11dae3b56ff9..58cf5b30559f 100644 --- a/drivers/power/reset/Makefile +++ b/drivers/power/reset/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_POWER_RESET_AT91_SAMA5D2_SHDWC) += at91-sama5d2_shdwc.o obj-$(CONFIG_POWER_RESET_AXXIA) += axxia-reset.o obj-$(CONFIG_POWER_RESET_BRCMKONA) += brcm-kona-reset.o obj-$(CONFIG_POWER_RESET_BRCMSTB) += brcmstb-reboot.o +obj-$(CONFIG_POWER_RESET_GEMINI_POWEROFF) += gemini-poweroff.o obj-$(CONFIG_POWER_RESET_GPIO) += gpio-poweroff.o obj-$(CONFIG_POWER_RESET_GPIO_RESTART) += gpio-restart.o obj-$(CONFIG_POWER_RESET_HISI) += hisi-reboot.o diff --git a/drivers/power/reset/gemini-poweroff.c b/drivers/power/reset/gemini-poweroff.c new file mode 100644 index 000000000000..de878fd26f27 --- /dev/null +++ b/drivers/power/reset/gemini-poweroff.c @@ -0,0 +1,160 @@ +/* + * Gemini power management controller + * Copyright (C) 2017 Linus Walleij + * + * Inspired by code from the SL3516 board support by Jason Lee + * Inspired by code from Janos Laube + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#define GEMINI_PWC_ID 0x00010500 +#define GEMINI_PWC_IDREG 0x00 +#define GEMINI_PWC_CTRLREG 0x04 +#define GEMINI_PWC_STATREG 0x08 + +#define GEMINI_CTRL_SHUTDOWN BIT(0) +#define GEMINI_CTRL_ENABLE BIT(1) +#define GEMINI_CTRL_IRQ_CLR BIT(2) + +#define GEMINI_STAT_CIR BIT(4) +#define GEMINI_STAT_RTC BIT(5) +#define GEMINI_STAT_POWERBUTTON BIT(6) + +struct gemini_powercon { + struct device *dev; + void __iomem *base; +}; + +static irqreturn_t gemini_powerbutton_interrupt(int irq, void *data) +{ + struct gemini_powercon *gpw = data; + u32 val; + + /* ACK the IRQ */ + val = readl(gpw->base + GEMINI_PWC_CTRLREG); + val |= GEMINI_CTRL_IRQ_CLR; + writel(val, gpw->base + GEMINI_PWC_CTRLREG); + + val = readl(gpw->base + GEMINI_PWC_STATREG); + val &= 0x70U; + switch (val) { + case GEMINI_STAT_CIR: + dev_info(gpw->dev, "infrared poweroff\n"); + orderly_poweroff(true); + break; + case GEMINI_STAT_RTC: + dev_info(gpw->dev, "RTC poweroff\n"); + orderly_poweroff(true); + break; + case GEMINI_STAT_POWERBUTTON: + dev_info(gpw->dev, "poweroff button pressed\n"); + orderly_poweroff(true); + break; + default: + dev_info(gpw->dev, "other power management IRQ\n"); + break; + } + + return IRQ_HANDLED; +} + +/* This callback needs this static local as it has void as argument */ +static struct gemini_powercon *gpw_poweroff; + +static void gemini_poweroff(void) +{ + struct gemini_powercon *gpw = gpw_poweroff; + u32 val; + + dev_crit(gpw->dev, "Gemini power off\n"); + val = readl(gpw->base + GEMINI_PWC_CTRLREG); + val |= GEMINI_CTRL_ENABLE | GEMINI_CTRL_IRQ_CLR; + writel(val, gpw->base + GEMINI_PWC_CTRLREG); + + val &= ~GEMINI_CTRL_ENABLE; + val |= GEMINI_CTRL_SHUTDOWN; + writel(val, gpw->base + GEMINI_PWC_CTRLREG); +} + +static int gemini_poweroff_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct gemini_powercon *gpw; + u32 val; + int irq; + int ret; + + gpw = devm_kzalloc(dev, sizeof(*gpw), GFP_KERNEL); + if (!gpw) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + gpw->base = devm_ioremap_resource(dev, res); + if (IS_ERR(gpw->base)) + return PTR_ERR(gpw->base); + + irq = platform_get_irq(pdev, 0); + if (!irq) + return -EINVAL; + + gpw->dev = dev; + + val = readl(gpw->base + GEMINI_PWC_IDREG); + val &= 0xFFFFFF00U; + if (val != GEMINI_PWC_ID) { + dev_err(dev, "wrong power controller ID: %08x\n", + val); + return -ENODEV; + } + + /* Clear the power management IRQ */ + val = readl(gpw->base + GEMINI_PWC_CTRLREG); + val |= GEMINI_CTRL_IRQ_CLR; + writel(val, gpw->base + GEMINI_PWC_CTRLREG); + + ret = devm_request_irq(dev, irq, gemini_powerbutton_interrupt, 0, + "poweroff", gpw); + if (ret) + return ret; + + pm_power_off = gemini_poweroff; + gpw_poweroff = gpw; + + /* + * Enable the power controller. This is crucial on Gemini + * systems: if this is not done, pressing the power button + * will result in unconditional poweroff without any warning. + * This makes the kernel handle the poweroff. + */ + val = readl(gpw->base + GEMINI_PWC_CTRLREG); + val |= GEMINI_CTRL_ENABLE; + writel(val, gpw->base + GEMINI_PWC_CTRLREG); + + dev_info(dev, "Gemini poweroff driver registered\n"); + + return 0; +} + +static const struct of_device_id gemini_poweroff_of_match[] = { + { + .compatible = "cortina,gemini-power-controller", + }, + {} +}; + +static struct platform_driver gemini_poweroff_driver = { + .probe = gemini_poweroff_probe, + .driver = { + .name = "gemini-poweroff", + .of_match_table = gemini_poweroff_of_match, + }, +}; +builtin_platform_driver(gemini_poweroff_driver); From 420b2d443b7ee2a6a8c0d4fab887a641f83e76af Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Wed, 1 Mar 2017 15:44:53 -0800 Subject: [PATCH 08/29] power: supply: twl4030_charger: remove incorrect __exit markups Even if bus is not hot-pluggable, the devices can be unbound from the driver via sysfs, so we should not be using __exit annotations on remove() methods. The only exception is drivers registered with platform_driver_probe() which specifically disables sysfs bind/unbind attributes. Signed-off-by: Dmitry Torokhov Signed-off-by: Sebastian Reichel --- drivers/power/supply/twl4030_charger.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/power/supply/twl4030_charger.c b/drivers/power/supply/twl4030_charger.c index bcd4dc304f27..990ff3d218bc 100644 --- a/drivers/power/supply/twl4030_charger.c +++ b/drivers/power/supply/twl4030_charger.c @@ -1117,7 +1117,7 @@ fail: return ret; } -static int __exit twl4030_bci_remove(struct platform_device *pdev) +static int twl4030_bci_remove(struct platform_device *pdev) { struct twl4030_bci *bci = platform_get_drvdata(pdev); @@ -1148,11 +1148,11 @@ MODULE_DEVICE_TABLE(of, twl_bci_of_match); static struct platform_driver twl4030_bci_driver = { .probe = twl4030_bci_probe, + .remove = twl4030_bci_remove, .driver = { .name = "twl4030_bci", .of_match_table = of_match_ptr(twl_bci_of_match), }, - .remove = __exit_p(twl4030_bci_remove), }; module_platform_driver(twl4030_bci_driver); From cbe5c2878955a1d4fd11c04203002bcf60e7d7d7 Mon Sep 17 00:00:00 2001 From: Enric Balletbo i Serra Date: Thu, 23 Feb 2017 16:35:04 +0100 Subject: [PATCH 09/29] dt-bindings: power/supply: rename max8925_batter.txt to max8925_battery.txt Fix the max8925_batter typo in the file name. Signed-off-by: Enric Balletbo i Serra Signed-off-by: Sebastian Reichel --- .../power/supply/{max8925_batter.txt => max8925_battery.txt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Documentation/devicetree/bindings/power/supply/{max8925_batter.txt => max8925_battery.txt} (100%) diff --git a/Documentation/devicetree/bindings/power/supply/max8925_batter.txt b/Documentation/devicetree/bindings/power/supply/max8925_battery.txt similarity index 100% rename from Documentation/devicetree/bindings/power/supply/max8925_batter.txt rename to Documentation/devicetree/bindings/power/supply/max8925_battery.txt From 867758793a765b568a28b4973321875752c662bf Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 20 Feb 2017 19:12:23 +0200 Subject: [PATCH 10/29] power: supply: bq25890: Use gpiod_get() Since index is always 0, replace gpiod_get_index() by gpiod_get(). Signed-off-by: Andy Shevchenko Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq25890_charger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/bq25890_charger.c b/drivers/power/supply/bq25890_charger.c index f993a55cde20..8e2c41ded171 100644 --- a/drivers/power/supply/bq25890_charger.c +++ b/drivers/power/supply/bq25890_charger.c @@ -723,7 +723,7 @@ static int bq25890_irq_probe(struct bq25890_device *bq) { struct gpio_desc *irq; - irq = devm_gpiod_get_index(bq->dev, BQ25890_IRQ_PIN, 0, GPIOD_IN); + irq = devm_gpiod_get(bq->dev, BQ25890_IRQ_PIN, GPIOD_IN); if (IS_ERR(irq)) { dev_err(bq->dev, "Could not probe irq pin.\n"); return PTR_ERR(irq); From d71472465a883b37c52283c96fa0f924127a2e71 Mon Sep 17 00:00:00 2001 From: Munir Contractor Date: Sun, 19 Feb 2017 18:19:53 -0500 Subject: [PATCH 11/29] power: supply: ab8500: Replaced spaces with tabs in indent This patch fixes 4 checkpatch.pl errors on lines 433 to 436: ERROR: code indent should use tabs where possible Signed-off-by: Munir Contractor Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_bmdata.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/power/supply/ab8500_bmdata.c b/drivers/power/supply/ab8500_bmdata.c index d29864533093..8c49586015d0 100644 --- a/drivers/power/supply/ab8500_bmdata.c +++ b/drivers/power/supply/ab8500_bmdata.c @@ -430,10 +430,10 @@ static const struct abx500_maxim_parameters ab8500_maxi_params = { }; static const struct abx500_maxim_parameters abx540_maxi_params = { - .ena_maxi = true, - .chg_curr = 3000, - .wait_cycles = 10, - .charger_curr_step = 200, + .ena_maxi = true, + .chg_curr = 3000, + .wait_cycles = 10, + .charger_curr_step = 200, }; static const struct abx500_bm_charger_parameters chg = { From dba83476966b725736f99afb7d80c945c06ba6dc Mon Sep 17 00:00:00 2001 From: Daniel Perez Date: Wed, 15 Feb 2017 16:46:21 +0100 Subject: [PATCH 12/29] power: supply: sbs-charger: simplified bool function Signed-off-by: Daniel Perez Signed-off-by: Sebastian Reichel --- drivers/power/supply/sbs-charger.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/power/supply/sbs-charger.c b/drivers/power/supply/sbs-charger.c index 353765a5f44c..15947dbb511e 100644 --- a/drivers/power/supply/sbs-charger.c +++ b/drivers/power/supply/sbs-charger.c @@ -137,10 +137,7 @@ static enum power_supply_property sbs_properties[] = { static bool sbs_readable_reg(struct device *dev, unsigned int reg) { - if (reg < SBS_CHARGER_REG_SPEC_INFO) - return false; - else - return true; + return reg >= SBS_CHARGER_REG_SPEC_INFO; } static bool sbs_volatile_reg(struct device *dev, unsigned int reg) From b98074e2adc21b52b59e2c7889f58d0c9f6fd8e5 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Mon, 20 Mar 2017 14:14:15 +0100 Subject: [PATCH 13/29] power: bq24190_charger: mark PM functions as __maybe_unused Without CONFIG_PM, we get a harmless warning: drivers/power/supply/bq24190_charger.c:1514:12: error: 'bq24190_runtime_resume' defined but not used [-Werror=unused-function] drivers/power/supply/bq24190_charger.c:1501:12: error: 'bq24190_runtime_suspend' defined but not used [-Werror=unused-function] To avoid the warning, we can mark all four PM functions as __maybe_unused, which also lets us remove the incorrect #ifdef. Fixes: 3d8090cba638 ("power: bq24190_charger: Check the interrupt status on resume") Signed-off-by: Arnd Bergmann Acked-by: Tony Lindgren Acked-by: Liam Breck Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq24190_charger.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c index 6d80670586eb..451f2bc05ea5 100644 --- a/drivers/power/supply/bq24190_charger.c +++ b/drivers/power/supply/bq24190_charger.c @@ -1498,7 +1498,7 @@ static int bq24190_remove(struct i2c_client *client) return 0; } -static int bq24190_runtime_suspend(struct device *dev) +static __maybe_unused int bq24190_runtime_suspend(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct bq24190_dev_info *bdi = i2c_get_clientdata(client); @@ -1511,7 +1511,7 @@ static int bq24190_runtime_suspend(struct device *dev) return 0; } -static int bq24190_runtime_resume(struct device *dev) +static __maybe_unused int bq24190_runtime_resume(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct bq24190_dev_info *bdi = i2c_get_clientdata(client); @@ -1527,8 +1527,7 @@ static int bq24190_runtime_resume(struct device *dev) return 0; } -#ifdef CONFIG_PM_SLEEP -static int bq24190_pm_suspend(struct device *dev) +static __maybe_unused int bq24190_pm_suspend(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct bq24190_dev_info *bdi = i2c_get_clientdata(client); @@ -1550,7 +1549,7 @@ static int bq24190_pm_suspend(struct device *dev) return 0; } -static int bq24190_pm_resume(struct device *dev) +static __maybe_unused int bq24190_pm_resume(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct bq24190_dev_info *bdi = i2c_get_clientdata(client); @@ -1580,7 +1579,6 @@ static int bq24190_pm_resume(struct device *dev) return 0; } -#endif static const struct dev_pm_ops bq24190_pm_ops = { SET_RUNTIME_PM_OPS(bq24190_runtime_suspend, bq24190_runtime_resume, From 01c0e0a28da749e80cb7d549f75a5f52e2f40d0e Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 22 Mar 2017 15:55:30 +0100 Subject: [PATCH 14/29] power: supply: bq24190_charger: Use i2c-core irq-mapping code The i2c-core already maps of irqs before calling the driver's probe function and there are no in tree users of bq24190_platform_data->gpio_int. Remove the redundant custom irq-mapping code and just use client->irq. Cc: Liam Breck Cc: Tony Lindgren Signed-off-by: Hans de Goede Reviewed-by: Andy Shevchenko Acked-by: Tony Lindgren Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq24190_charger.c | 66 ++------------------------ include/linux/power/bq24190_charger.h | 16 ------- 2 files changed, 3 insertions(+), 79 deletions(-) delete mode 100644 include/linux/power/bq24190_charger.h diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c index 451f2bc05ea5..fa2d2da2de5f 100644 --- a/drivers/power/supply/bq24190_charger.c +++ b/drivers/power/supply/bq24190_charger.c @@ -18,9 +18,6 @@ #include #include -#include - - #define BQ24190_MANUFACTURER "Texas Instruments" #define BQ24190_REG_ISC 0x00 /* Input Source Control */ @@ -153,8 +150,6 @@ struct bq24190_dev_info { struct power_supply *battery; char model_name[I2C_NAME_SIZE]; kernel_ulong_t model; - unsigned int gpio_int; - unsigned int irq; bool initialized; bool irq_event; struct mutex f_reg_lock; @@ -1310,56 +1305,11 @@ static int bq24190_hw_init(struct bq24190_dev_info *bdi) return bq24190_read(bdi, BQ24190_REG_SS, &bdi->ss_reg); } -#ifdef CONFIG_OF -static int bq24190_setup_dt(struct bq24190_dev_info *bdi) -{ - bdi->irq = irq_of_parse_and_map(bdi->dev->of_node, 0); - if (bdi->irq <= 0) - return -1; - - return 0; -} -#else -static int bq24190_setup_dt(struct bq24190_dev_info *bdi) -{ - return -1; -} -#endif - -static int bq24190_setup_pdata(struct bq24190_dev_info *bdi, - struct bq24190_platform_data *pdata) -{ - int ret; - - if (!gpio_is_valid(pdata->gpio_int)) - return -1; - - ret = gpio_request(pdata->gpio_int, dev_name(bdi->dev)); - if (ret < 0) - return -1; - - ret = gpio_direction_input(pdata->gpio_int); - if (ret < 0) - goto out; - - bdi->irq = gpio_to_irq(pdata->gpio_int); - if (!bdi->irq) - goto out; - - bdi->gpio_int = pdata->gpio_int; - return 0; - -out: - gpio_free(pdata->gpio_int); - return -1; -} - static int bq24190_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); struct device *dev = &client->dev; - struct bq24190_platform_data *pdata = client->dev.platform_data; struct power_supply_config charger_cfg = {}, battery_cfg = {}; struct bq24190_dev_info *bdi; int ret; @@ -1385,12 +1335,7 @@ static int bq24190_probe(struct i2c_client *client, i2c_set_clientdata(client, bdi); - if (dev->of_node) - ret = bq24190_setup_dt(bdi); - else - ret = bq24190_setup_pdata(bdi, pdata); - - if (ret) { + if (!client->irq) { dev_err(dev, "Can't get irq info\n"); return -EINVAL; } @@ -1436,7 +1381,7 @@ static int bq24190_probe(struct i2c_client *client, bdi->initialized = true; - ret = devm_request_threaded_irq(dev, bdi->irq, NULL, + ret = devm_request_threaded_irq(dev, client->irq, NULL, bq24190_irq_handler_thread, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "bq24190-charger", bdi); @@ -1445,7 +1390,7 @@ static int bq24190_probe(struct i2c_client *client, goto out5; } - enable_irq_wake(bdi->irq); + enable_irq_wake(client->irq); pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); @@ -1467,8 +1412,6 @@ out2: out1: pm_runtime_dont_use_autosuspend(dev); pm_runtime_disable(dev); - if (bdi->gpio_int) - gpio_free(bdi->gpio_int); return ret; } @@ -1492,9 +1435,6 @@ static int bq24190_remove(struct i2c_client *client) pm_runtime_dont_use_autosuspend(bdi->dev); pm_runtime_disable(bdi->dev); - if (bdi->gpio_int) - gpio_free(bdi->gpio_int); - return 0; } diff --git a/include/linux/power/bq24190_charger.h b/include/linux/power/bq24190_charger.h deleted file mode 100644 index 9f0283721cbc..000000000000 --- a/include/linux/power/bq24190_charger.h +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Platform data for the TI bq24190 battery charger driver. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#ifndef _BQ24190_CHARGER_H_ -#define _BQ24190_CHARGER_H_ - -struct bq24190_platform_data { - unsigned int gpio_int; /* GPIO pin that's connected to INT# */ -}; - -#endif From 99c88eb244aa6b61cc0c4ca6e6b6b9c7ce00cc53 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 22 Mar 2017 15:55:31 +0100 Subject: [PATCH 15/29] power: supply: bq24190_charger: Add support for bq24192i The bq24192 and bq24192i are mostly identical to the bq24190, TI even published a single datasheet for all 3 of them. The difference between the bq24190 and bq24192[i] is the way charger-type detection is done, the bq24190 is to be directly connected to the USB a/b lines, where as the the bq24192[i] has a gpio which should be driven high/low externally depending on the type of charger connected, from a register level access pov there is no difference. The differences between the bq24192 and bq24192i are: 1) Lower default charge rate on the bq24192i 2) Pre-charge-current can be max 640 mA on the bq24192i On x86/ACPI systems the code which instantiates the i2c client may not know the exact variant being used, so instead of coding the model-id in the i2c_id struct and bailing if it does not match, check the reported model-id matches one of the supported variants. This commit only adds support for the bq24192i as I don't have a bq24192 to test with, adding support for the bq24192 should be as simple as also accepting its model-id in the model-id test. Cc: Liam Breck Cc: Tony Lindgren Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq24190_charger.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c index fa2d2da2de5f..d74c8cce0770 100644 --- a/drivers/power/supply/bq24190_charger.c +++ b/drivers/power/supply/bq24190_charger.c @@ -149,7 +149,6 @@ struct bq24190_dev_info { struct power_supply *charger; struct power_supply *battery; char model_name[I2C_NAME_SIZE]; - kernel_ulong_t model; bool initialized; bool irq_event; struct mutex f_reg_lock; @@ -1291,8 +1290,11 @@ static int bq24190_hw_init(struct bq24190_dev_info *bdi) if (ret < 0) return ret; - if (v != bdi->model) + if (v != BQ24190_REG_VPRS_PN_24190 && + v != BQ24190_REG_VPRS_PN_24192I) { + dev_err(bdi->dev, "Error unknown model: 0x%02x\n", v); return -ENODEV; + } ret = bq24190_register_reset(bdi); if (ret < 0) @@ -1327,7 +1329,6 @@ static int bq24190_probe(struct i2c_client *client, bdi->client = client; bdi->dev = dev; - bdi->model = id->driver_data; strncpy(bdi->model_name, id->name, I2C_NAME_SIZE); mutex_init(&bdi->f_reg_lock); bdi->f_reg = 0; @@ -1526,13 +1527,9 @@ static const struct dev_pm_ops bq24190_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(bq24190_pm_suspend, bq24190_pm_resume) }; -/* - * Only support the bq24190 right now. The bq24192, bq24192i, and bq24193 - * are similar but not identical so the driver needs to be extended to - * support them. - */ static const struct i2c_device_id bq24190_i2c_ids[] = { - { "bq24190", BQ24190_REG_VPRS_PN_24190 }, + { "bq24190" }, + { "bq24192i" }, { }, }; MODULE_DEVICE_TABLE(i2c, bq24190_i2c_ids); From da26580f4eb81f3614cf5e3af43155fb45d118fc Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 23 Mar 2017 09:32:35 +0100 Subject: [PATCH 16/29] power: supply: bq24190_charger: Use extcon to determine ilimit, 5v boost Add support for monitoring an extcon device with USB SDP/CDP/DCP and HOST cables and adjust ilimit and enable/disable the 5V boost converter accordingly. This is necessary on systems where the PSEL pin is hardwired high and ILIM needs to be set by software based on the detected charger type, as well as on systems where the 5V boost converter is used, as that always needs to be enabled from software. Cc: Liam Breck Cc: Tony Lindgren Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/Kconfig | 1 + drivers/power/supply/bq24190_charger.c | 109 +++++++++++++++++++++++++ 2 files changed, 110 insertions(+) diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index da54ac88f068..cc50e15e72cc 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -438,6 +438,7 @@ config CHARGER_BQ2415X config CHARGER_BQ24190 tristate "TI BQ24190 battery charger driver" depends on I2C + depends on EXTCON depends on GPIOLIB || COMPILE_TEST help Say Y to enable support for the TI BQ24190 battery charger. diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c index d74c8cce0770..2c404a59e613 100644 --- a/drivers/power/supply/bq24190_charger.c +++ b/drivers/power/supply/bq24190_charger.c @@ -11,10 +11,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include @@ -36,6 +38,8 @@ #define BQ24190_REG_POC_WDT_RESET_SHIFT 6 #define BQ24190_REG_POC_CHG_CONFIG_MASK (BIT(5) | BIT(4)) #define BQ24190_REG_POC_CHG_CONFIG_SHIFT 4 +#define BQ24190_REG_POC_CHG_CONFIG_CHARGE 1 +#define BQ24190_REG_POC_CHG_CONFIG_OTG 2 #define BQ24190_REG_POC_SYS_MIN_MASK (BIT(3) | BIT(2) | BIT(1)) #define BQ24190_REG_POC_SYS_MIN_SHIFT 1 #define BQ24190_REG_POC_BOOST_LIM_MASK BIT(0) @@ -148,6 +152,9 @@ struct bq24190_dev_info { struct device *dev; struct power_supply *charger; struct power_supply *battery; + struct extcon_dev *extcon; + struct notifier_block extcon_nb; + struct delayed_work extcon_work; char model_name[I2C_NAME_SIZE]; bool initialized; bool irq_event; @@ -164,6 +171,11 @@ struct bq24190_dev_info { * number at that index in the array is the real-world value that it * represents. */ + +/* REG00[2:0] (IINLIM) in uAh */ +static const int bq24190_iinlim_values[] = { + 100000, 150000, 500000, 900000, 1200000, 1500000, 2000000, 3000000 }; + /* REG02[7:2] (ICHG) in uAh */ static const int bq24190_ccc_ichg_values[] = { 512000, 576000, 640000, 704000, 768000, 832000, 896000, 960000, @@ -1277,6 +1289,78 @@ static irqreturn_t bq24190_irq_handler_thread(int irq, void *data) return IRQ_HANDLED; } +static void bq24190_extcon_work(struct work_struct *work) +{ + struct bq24190_dev_info *bdi = + container_of(work, struct bq24190_dev_info, extcon_work.work); + int ret, iinlim = 0; + + ret = pm_runtime_get_sync(bdi->dev); + if (ret < 0) { + dev_err(bdi->dev, "Error getting runtime-pm ref: %d\n", ret); + return; + } + + if (extcon_get_state(bdi->extcon, EXTCON_CHG_USB_SDP) == 1) + iinlim = 500000; + else if (extcon_get_state(bdi->extcon, EXTCON_CHG_USB_CDP) == 1 || + extcon_get_state(bdi->extcon, EXTCON_CHG_USB_ACA) == 1) + iinlim = 1500000; + else if (extcon_get_state(bdi->extcon, EXTCON_CHG_USB_DCP) == 1) + iinlim = 2000000; + + if (iinlim) { + ret = bq24190_set_field_val(bdi, BQ24190_REG_ISC, + BQ24190_REG_ISC_IINLIM_MASK, + BQ24190_REG_ISC_IINLIM_SHIFT, + bq24190_iinlim_values, + ARRAY_SIZE(bq24190_iinlim_values), + iinlim); + if (ret) + dev_err(bdi->dev, "Can't set IINLIM: %d\n", ret); + } + + /* + * If no charger has been detected and host mode is requested, activate + * the 5V boost converter, otherwise deactivate it. + */ + if (!iinlim && extcon_get_state(bdi->extcon, EXTCON_USB_HOST) == 1) { + ret = bq24190_write_mask(bdi, BQ24190_REG_POC, + BQ24190_REG_POC_CHG_CONFIG_MASK, + BQ24190_REG_POC_CHG_CONFIG_SHIFT, + BQ24190_REG_POC_CHG_CONFIG_OTG); + } else { + ret = bq24190_write_mask(bdi, BQ24190_REG_POC, + BQ24190_REG_POC_CHG_CONFIG_MASK, + BQ24190_REG_POC_CHG_CONFIG_SHIFT, + BQ24190_REG_POC_CHG_CONFIG_CHARGE); + } + if (ret) + dev_err(bdi->dev, "Can't set CHG_CONFIG: %d\n", ret); + + pm_runtime_mark_last_busy(bdi->dev); + pm_runtime_put_autosuspend(bdi->dev); +} + +static int bq24190_extcon_event(struct notifier_block *nb, unsigned long event, + void *param) +{ + struct bq24190_dev_info *bdi = + container_of(nb, struct bq24190_dev_info, extcon_nb); + + /* + * The Power-Good detection may take up to 220ms, sometimes + * the external charger detection is quicker, and the bq24190 will + * reset to iinlim based on its own charger detection (which is not + * hooked up when using external charger detection) resulting in + * a too low default 500mA iinlim. Delay applying the extcon value + * for 300ms to avoid this. + */ + queue_delayed_work(system_wq, &bdi->extcon_work, msecs_to_jiffies(300)); + + return NOTIFY_OK; +} + static int bq24190_hw_init(struct bq24190_dev_info *bdi) { u8 v; @@ -1314,6 +1398,7 @@ static int bq24190_probe(struct i2c_client *client, struct device *dev = &client->dev; struct power_supply_config charger_cfg = {}, battery_cfg = {}; struct bq24190_dev_info *bdi; + const char *name; int ret; if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { @@ -1341,6 +1426,18 @@ static int bq24190_probe(struct i2c_client *client, return -EINVAL; } + /* + * The extcon-name property is purely an in kernel interface for + * x86/ACPI use, DT platforms should get extcon via phandle. + */ + if (device_property_read_string(dev, "extcon-name", &name) == 0) { + bdi->extcon = extcon_get_extcon_dev(name); + if (!bdi->extcon) + return -EPROBE_DEFER; + + dev_info(bdi->dev, "using extcon device %s\n", name); + } + pm_runtime_enable(dev); pm_runtime_use_autosuspend(dev); pm_runtime_set_autosuspend_delay(dev, 600); @@ -1391,6 +1488,18 @@ static int bq24190_probe(struct i2c_client *client, goto out5; } + if (bdi->extcon) { + INIT_DELAYED_WORK(&bdi->extcon_work, bq24190_extcon_work); + bdi->extcon_nb.notifier_call = bq24190_extcon_event; + ret = devm_extcon_register_notifier(dev, bdi->extcon, -1, + &bdi->extcon_nb); + if (ret) + goto out5; + + /* Sync initial cable state */ + queue_delayed_work(system_wq, &bdi->extcon_work, 0); + } + enable_irq_wake(client->irq); pm_runtime_mark_last_busy(dev); From 0c9888e3c1925ef4b18a04f71fa3a8228a648a08 Mon Sep 17 00:00:00 2001 From: Tony Lindgren Date: Sun, 26 Mar 2017 20:25:13 -0700 Subject: [PATCH 17/29] power: supply: cpcap-charger: Add minimal CPCAP PMIC battery charger The custom CPCAP PMIC used on Motorola phones such as Droid 4 has a USB battery charger. It can optionally also have a companion chip that is used for wireless charging. The charger on CPCAP also can feed VBUS for the USB host mode. This can be handled by the existing kernel phy_companion interface. Cc: devicetree@vger.kernel.org Cc: Marcel Partap Cc: Michael Scott Signed-off-by: Tony Lindgren Acked-by: Rob Herring Signed-off-by: Sebastian Reichel --- .../bindings/power/supply/cpcap-charger.txt | 37 + drivers/power/supply/Kconfig | 8 + drivers/power/supply/Makefile | 1 + drivers/power/supply/cpcap-charger.c | 681 ++++++++++++++++++ 4 files changed, 727 insertions(+) create mode 100644 Documentation/devicetree/bindings/power/supply/cpcap-charger.txt create mode 100644 drivers/power/supply/cpcap-charger.c diff --git a/Documentation/devicetree/bindings/power/supply/cpcap-charger.txt b/Documentation/devicetree/bindings/power/supply/cpcap-charger.txt new file mode 100644 index 000000000000..80bd873c3b1d --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/cpcap-charger.txt @@ -0,0 +1,37 @@ +Motorola CPCAP PMIC battery charger binding + +Required properties: +- compatible: Shall be "motorola,mapphone-cpcap-charger" +- interrupts: Interrupt specifier for each name in interrupt-names +- interrupt-names: Should contain the following entries: + "chrg_det", "rvrs_chrg", "chrg_se1b", "se0conn", + "rvrs_mode", "chrgcurr1", "vbusvld", "battdetb" +- io-channels: IIO ADC channel specifier for each name in io-channel-names +- io-channel-names: Should contain the following entries: + "battdetb", "battp", "vbus", "chg_isense", "batti" + +Optional properties: +- mode-gpios: Optionally CPCAP charger can have a companion wireless + charge controller that is controlled with two GPIOs + that are active low. + +Example: + +cpcap_charger: charger { + compatible = "motorola,mapphone-cpcap-charger"; + interrupts-extended = < + &cpcap 13 0 &cpcap 12 0 &cpcap 29 0 &cpcap 28 0 + &cpcap 22 0 &cpcap 20 0 &cpcap 19 0 &cpcap 54 0 + >; + interrupt-names = + "chrg_det", "rvrs_chrg", "chrg_se1b", "se0conn", + "rvrs_mode", "chrgcurr1", "vbusvld", "battdetb"; + mode-gpios = <&gpio3 29 GPIO_ACTIVE_LOW + &gpio3 23 GPIO_ACTIVE_LOW>; + io-channels = <&cpcap_adc 0 &cpcap_adc 1 + &cpcap_adc 2 &cpcap_adc 5 + &cpcap_adc 6>; + io-channel-names = "battdetb", "battp", + "vbus", "chg_isense", + "batti"; +}; diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index cc50e15e72cc..2ac3dd41f15b 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -317,6 +317,14 @@ config BATTERY_RX51 Say Y here to enable support for battery information on Nokia RX-51, also known as N900 tablet. +config CHARGER_CPCAP + tristate "CPCAP PMIC Charger Driver" + depends on MFD_CPCAP && IIO + default MFD_CPCAP + help + Say Y to enable support for CPCAP PMIC charger driver for Motorola + mobile devices such as Droid 4. + config CHARGER_ISP1704 tristate "ISP1704 USB Charger Detection" depends on USB_PHY diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index 3789a2c06fdf..957e099a05cd 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -51,6 +51,7 @@ obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o obj-$(CONFIG_BATTERY_JZ4740) += jz4740-battery.o obj-$(CONFIG_BATTERY_RX51) += rx51_battery.o obj-$(CONFIG_AB8500_BM) += ab8500_bmdata.o ab8500_charger.o ab8500_fg.o ab8500_btemp.o abx500_chargalg.o pm2301_charger.o +obj-$(CONFIG_CHARGER_CPCAP) += cpcap-charger.o obj-$(CONFIG_CHARGER_ISP1704) += isp1704_charger.o obj-$(CONFIG_CHARGER_MAX8903) += max8903_charger.o obj-$(CONFIG_CHARGER_TWL4030) += twl4030_charger.o diff --git a/drivers/power/supply/cpcap-charger.c b/drivers/power/supply/cpcap-charger.c new file mode 100644 index 000000000000..543a1bd21ab9 --- /dev/null +++ b/drivers/power/supply/cpcap-charger.c @@ -0,0 +1,681 @@ +/* + * Motorola CPCAP PMIC battery charger driver + * + * Copyright (C) 2017 Tony Lindgren + * + * Rewritten for Linux power framework with some parts based on + * on earlier driver found in the Motorola Linux kernel: + * + * Copyright (C) 2009-2010 Motorola, 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 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* CPCAP_REG_CRM register bits */ +#define CPCAP_REG_CRM_UNUSED_641_15 BIT(15) /* 641 = register number */ +#define CPCAP_REG_CRM_UNUSED_641_14 BIT(14) /* 641 = register number */ +#define CPCAP_REG_CRM_CHRG_LED_EN BIT(13) +#define CPCAP_REG_CRM_RVRSMODE BIT(12) +#define CPCAP_REG_CRM_ICHRG_TR1 BIT(11) +#define CPCAP_REG_CRM_ICHRG_TR0 BIT(10) +#define CPCAP_REG_CRM_FET_OVRD BIT(9) +#define CPCAP_REG_CRM_FET_CTRL BIT(8) +#define CPCAP_REG_CRM_VCHRG3 BIT(7) +#define CPCAP_REG_CRM_VCHRG2 BIT(6) +#define CPCAP_REG_CRM_VCHRG1 BIT(5) +#define CPCAP_REG_CRM_VCHRG0 BIT(4) +#define CPCAP_REG_CRM_ICHRG3 BIT(3) +#define CPCAP_REG_CRM_ICHRG2 BIT(2) +#define CPCAP_REG_CRM_ICHRG1 BIT(1) +#define CPCAP_REG_CRM_ICHRG0 BIT(0) + +/* CPCAP_REG_CRM trickle charge voltages */ +#define CPCAP_REG_CRM_TR(val) (((val) & 0x3) << 10) +#define CPCAP_REG_CRM_TR_0A00 CPCAP_REG_CRM_TR(0x0) +#define CPCAP_REG_CRM_TR_0A24 CPCAP_REG_CRM_TR(0x1) +#define CPCAP_REG_CRM_TR_0A48 CPCAP_REG_CRM_TR(0x2) +#define CPCAP_REG_CRM_TR_0A72 CPCAP_REG_CRM_TR(0x4) + +/* CPCAP_REG_CRM charge voltages */ +#define CPCAP_REG_CRM_VCHRG(val) (((val) & 0xf) << 4) +#define CPCAP_REG_CRM_VCHRG_3V80 CPCAP_REG_CRM_VCHRG(0x0) +#define CPCAP_REG_CRM_VCHRG_4V10 CPCAP_REG_CRM_VCHRG(0x1) +#define CPCAP_REG_CRM_VCHRG_4V15 CPCAP_REG_CRM_VCHRG(0x2) +#define CPCAP_REG_CRM_VCHRG_4V20 CPCAP_REG_CRM_VCHRG(0x3) +#define CPCAP_REG_CRM_VCHRG_4V22 CPCAP_REG_CRM_VCHRG(0x4) +#define CPCAP_REG_CRM_VCHRG_4V24 CPCAP_REG_CRM_VCHRG(0x5) +#define CPCAP_REG_CRM_VCHRG_4V26 CPCAP_REG_CRM_VCHRG(0x6) +#define CPCAP_REG_CRM_VCHRG_4V28 CPCAP_REG_CRM_VCHRG(0x7) +#define CPCAP_REG_CRM_VCHRG_4V30 CPCAP_REG_CRM_VCHRG(0x8) +#define CPCAP_REG_CRM_VCHRG_4V32 CPCAP_REG_CRM_VCHRG(0x9) +#define CPCAP_REG_CRM_VCHRG_4V34 CPCAP_REG_CRM_VCHRG(0xa) +#define CPCAP_REG_CRM_VCHRG_4V36 CPCAP_REG_CRM_VCHRG(0xb) +#define CPCAP_REG_CRM_VCHRG_4V38 CPCAP_REG_CRM_VCHRG(0xc) +#define CPCAP_REG_CRM_VCHRG_4V40 CPCAP_REG_CRM_VCHRG(0xd) +#define CPCAP_REG_CRM_VCHRG_4V42 CPCAP_REG_CRM_VCHRG(0xe) +#define CPCAP_REG_CRM_VCHRG_4V44 CPCAP_REG_CRM_VCHRG(0xf) + +/* CPCAP_REG_CRM charge currents */ +#define CPCAP_REG_CRM_ICHRG(val) (((val) & 0xf) << 0) +#define CPCAP_REG_CRM_ICHRG_0A000 CPCAP_REG_CRM_ICHRG(0x0) +#define CPCAP_REG_CRM_ICHRG_0A070 CPCAP_REG_CRM_ICHRG(0x1) +#define CPCAP_REG_CRM_ICHRG_0A176 CPCAP_REG_CRM_ICHRG(0x2) +#define CPCAP_REG_CRM_ICHRG_0A264 CPCAP_REG_CRM_ICHRG(0x3) +#define CPCAP_REG_CRM_ICHRG_0A352 CPCAP_REG_CRM_ICHRG(0x4) +#define CPCAP_REG_CRM_ICHRG_0A440 CPCAP_REG_CRM_ICHRG(0x5) +#define CPCAP_REG_CRM_ICHRG_0A528 CPCAP_REG_CRM_ICHRG(0x6) +#define CPCAP_REG_CRM_ICHRG_0A616 CPCAP_REG_CRM_ICHRG(0x7) +#define CPCAP_REG_CRM_ICHRG_0A704 CPCAP_REG_CRM_ICHRG(0x8) +#define CPCAP_REG_CRM_ICHRG_0A792 CPCAP_REG_CRM_ICHRG(0x9) +#define CPCAP_REG_CRM_ICHRG_0A880 CPCAP_REG_CRM_ICHRG(0xa) +#define CPCAP_REG_CRM_ICHRG_0A968 CPCAP_REG_CRM_ICHRG(0xb) +#define CPCAP_REG_CRM_ICHRG_1A056 CPCAP_REG_CRM_ICHRG(0xc) +#define CPCAP_REG_CRM_ICHRG_1A144 CPCAP_REG_CRM_ICHRG(0xd) +#define CPCAP_REG_CRM_ICHRG_1A584 CPCAP_REG_CRM_ICHRG(0xe) +#define CPCAP_REG_CRM_ICHRG_NO_LIMIT CPCAP_REG_CRM_ICHRG(0xf) + +enum { + CPCAP_CHARGER_IIO_BATTDET, + CPCAP_CHARGER_IIO_VOLTAGE, + CPCAP_CHARGER_IIO_VBUS, + CPCAP_CHARGER_IIO_CHRG_CURRENT, + CPCAP_CHARGER_IIO_BATT_CURRENT, + CPCAP_CHARGER_IIO_NR, +}; + +struct cpcap_charger_ddata { + struct device *dev; + struct regmap *reg; + struct list_head irq_list; + struct delayed_work detect_work; + struct delayed_work vbus_work; + struct gpio_desc *gpio[2]; /* gpio_reven0 & 1 */ + + struct iio_channel *channels[CPCAP_CHARGER_IIO_NR]; + + struct power_supply *usb; + + struct phy_companion comparator; /* For USB VBUS */ + bool vbus_enabled; + atomic_t active; + + int status; +}; + +struct cpcap_interrupt_desc { + int irq; + struct list_head node; + const char *name; +}; + +struct cpcap_charger_ints_state { + bool chrg_det; + bool rvrs_chrg; + bool vbusov; + + bool chrg_se1b; + bool rvrs_mode; + bool chrgcurr1; + bool vbusvld; + + bool battdetb; +}; + +static enum power_supply_property cpcap_charger_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, +}; + +static bool cpcap_charger_battery_found(struct cpcap_charger_ddata *ddata) +{ + struct iio_channel *channel; + int error, value; + + channel = ddata->channels[CPCAP_CHARGER_IIO_BATTDET]; + error = iio_read_channel_raw(channel, &value); + if (error < 0) { + dev_warn(ddata->dev, "%s failed: %i\n", __func__, error); + + return false; + } + + return value == 1; +} + +static int cpcap_charger_get_charge_voltage(struct cpcap_charger_ddata *ddata) +{ + struct iio_channel *channel; + int error, value = 0; + + channel = ddata->channels[CPCAP_CHARGER_IIO_VOLTAGE]; + error = iio_read_channel_processed(channel, &value); + if (error < 0) { + dev_warn(ddata->dev, "%s failed: %i\n", __func__, error); + + return 0; + } + + return value; +} + +static int cpcap_charger_get_charge_current(struct cpcap_charger_ddata *ddata) +{ + struct iio_channel *channel; + int error, value = 0; + + channel = ddata->channels[CPCAP_CHARGER_IIO_CHRG_CURRENT]; + error = iio_read_channel_processed(channel, &value); + if (error < 0) { + dev_warn(ddata->dev, "%s failed: %i\n", __func__, error); + + return 0; + } + + return value; +} + +static int cpcap_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct cpcap_charger_ddata *ddata = dev_get_drvdata(psy->dev.parent); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = ddata->status; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + if (ddata->status == POWER_SUPPLY_STATUS_CHARGING) + val->intval = cpcap_charger_get_charge_voltage(ddata) * + 1000; + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + if (ddata->status == POWER_SUPPLY_STATUS_CHARGING) + val->intval = cpcap_charger_get_charge_current(ddata) * + 1000; + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = ddata->status == POWER_SUPPLY_STATUS_CHARGING; + break; + default: + return -EINVAL; + } + + return 0; +} + +static void cpcap_charger_set_cable_path(struct cpcap_charger_ddata *ddata, + bool enabled) +{ + if (!ddata->gpio[0]) + return; + + gpiod_set_value(ddata->gpio[0], enabled); +} + +static void cpcap_charger_set_inductive_path(struct cpcap_charger_ddata *ddata, + bool enabled) +{ + if (!ddata->gpio[1]) + return; + + gpiod_set_value(ddata->gpio[1], enabled); +} + +static int cpcap_charger_set_state(struct cpcap_charger_ddata *ddata, + int max_voltage, int charge_current, + int trickle_current) +{ + bool enable; + int error; + + enable = max_voltage && (charge_current || trickle_current); + dev_dbg(ddata->dev, "%s enable: %i\n", __func__, enable); + + if (!enable) { + error = regmap_update_bits(ddata->reg, CPCAP_REG_CRM, + 0x3fff, + CPCAP_REG_CRM_FET_OVRD | + CPCAP_REG_CRM_FET_CTRL); + if (error) { + ddata->status = POWER_SUPPLY_STATUS_UNKNOWN; + goto out_err; + } + + ddata->status = POWER_SUPPLY_STATUS_DISCHARGING; + + return 0; + } + + error = regmap_update_bits(ddata->reg, CPCAP_REG_CRM, 0x3fff, + CPCAP_REG_CRM_CHRG_LED_EN | + trickle_current | + CPCAP_REG_CRM_FET_OVRD | + CPCAP_REG_CRM_FET_CTRL | + max_voltage | + charge_current); + if (error) { + ddata->status = POWER_SUPPLY_STATUS_UNKNOWN; + goto out_err; + } + + ddata->status = POWER_SUPPLY_STATUS_CHARGING; + + return 0; + +out_err: + dev_err(ddata->dev, "%s failed with %i\n", __func__, error); + + return error; +} + +static bool cpcap_charger_vbus_valid(struct cpcap_charger_ddata *ddata) +{ + int error, value = 0; + struct iio_channel *channel = + ddata->channels[CPCAP_CHARGER_IIO_VBUS]; + + error = iio_read_channel_processed(channel, &value); + if (error >= 0) + return value > 3900 ? true : false; + + dev_err(ddata->dev, "error reading VBUS: %i\n", error); + + return false; +} + +/* VBUS control functions for the USB PHY companion */ + +static void cpcap_charger_vbus_work(struct work_struct *work) +{ + struct cpcap_charger_ddata *ddata; + bool vbus = false; + int error; + + ddata = container_of(work, struct cpcap_charger_ddata, + vbus_work.work); + + if (ddata->vbus_enabled) { + vbus = cpcap_charger_vbus_valid(ddata); + if (vbus) { + dev_info(ddata->dev, "VBUS already provided\n"); + + return; + } + + cpcap_charger_set_cable_path(ddata, false); + cpcap_charger_set_inductive_path(ddata, false); + + error = cpcap_charger_set_state(ddata, 0, 0, 0); + if (error) + goto out_err; + + error = regmap_update_bits(ddata->reg, CPCAP_REG_CRM, + CPCAP_REG_CRM_RVRSMODE, + CPCAP_REG_CRM_RVRSMODE); + if (error) + goto out_err; + } else { + error = regmap_update_bits(ddata->reg, CPCAP_REG_CRM, + CPCAP_REG_CRM_RVRSMODE, 0); + if (error) + goto out_err; + + cpcap_charger_set_cable_path(ddata, true); + cpcap_charger_set_inductive_path(ddata, true); + } + + return; + +out_err: + dev_err(ddata->dev, "%s could not %s vbus: %i\n", __func__, + ddata->vbus_enabled ? "enable" : "disable", error); +} + +static int cpcap_charger_set_vbus(struct phy_companion *comparator, + bool enabled) +{ + struct cpcap_charger_ddata *ddata = + container_of(comparator, struct cpcap_charger_ddata, + comparator); + + ddata->vbus_enabled = enabled; + schedule_delayed_work(&ddata->vbus_work, 0); + + return 0; +} + +/* Charger interrupt handling functions */ + +static int cpcap_charger_get_ints_state(struct cpcap_charger_ddata *ddata, + struct cpcap_charger_ints_state *s) +{ + int val, error; + + error = regmap_read(ddata->reg, CPCAP_REG_INTS1, &val); + if (error) + return error; + + s->chrg_det = val & BIT(13); + s->rvrs_chrg = val & BIT(12); + s->vbusov = val & BIT(11); + + error = regmap_read(ddata->reg, CPCAP_REG_INTS2, &val); + if (error) + return error; + + s->chrg_se1b = val & BIT(13); + s->rvrs_mode = val & BIT(6); + s->chrgcurr1 = val & BIT(4); + s->vbusvld = val & BIT(3); + + error = regmap_read(ddata->reg, CPCAP_REG_INTS4, &val); + if (error) + return error; + + s->battdetb = val & BIT(6); + + return 0; +} + +static void cpcap_usb_detect(struct work_struct *work) +{ + struct cpcap_charger_ddata *ddata; + struct cpcap_charger_ints_state s; + int error; + + ddata = container_of(work, struct cpcap_charger_ddata, + detect_work.work); + + error = cpcap_charger_get_ints_state(ddata, &s); + if (error) + return; + + if (cpcap_charger_vbus_valid(ddata) && s.chrgcurr1) { + int max_current; + + if (cpcap_charger_battery_found(ddata)) + max_current = CPCAP_REG_CRM_ICHRG_1A584; + else + max_current = CPCAP_REG_CRM_ICHRG_0A528; + + error = cpcap_charger_set_state(ddata, + CPCAP_REG_CRM_VCHRG_4V20, + max_current, + CPCAP_REG_CRM_TR_0A72); + if (error) + goto out_err; + } else { + error = cpcap_charger_set_state(ddata, 0, 0, 0); + if (error) + goto out_err; + } + + return; + +out_err: + dev_err(ddata->dev, "%s failed with %i\n", __func__, error); +} + +static irqreturn_t cpcap_charger_irq_thread(int irq, void *data) +{ + struct cpcap_charger_ddata *ddata = data; + + if (!atomic_read(&ddata->active)) + return IRQ_NONE; + + schedule_delayed_work(&ddata->detect_work, 0); + + return IRQ_HANDLED; +} + +static int cpcap_usb_init_irq(struct platform_device *pdev, + struct cpcap_charger_ddata *ddata, + const char *name) +{ + struct cpcap_interrupt_desc *d; + int irq, error; + + irq = platform_get_irq_byname(pdev, name); + if (!irq) + return -ENODEV; + + error = devm_request_threaded_irq(ddata->dev, irq, NULL, + cpcap_charger_irq_thread, + IRQF_SHARED, + name, ddata); + if (error) { + dev_err(ddata->dev, "could not get irq %s: %i\n", + name, error); + + return error; + } + + d = devm_kzalloc(ddata->dev, sizeof(*d), GFP_KERNEL); + if (!d) + return -ENOMEM; + + d->name = name; + d->irq = irq; + list_add(&d->node, &ddata->irq_list); + + return 0; +} + +static const char * const cpcap_charger_irqs[] = { + /* REG_INT_0 */ + "chrg_det", "rvrs_chrg", + + /* REG_INT1 */ + "chrg_se1b", "se0conn", "rvrs_mode", "chrgcurr1", "vbusvld", + + /* REG_INT_3 */ + "battdetb", +}; + +static int cpcap_usb_init_interrupts(struct platform_device *pdev, + struct cpcap_charger_ddata *ddata) +{ + int i, error; + + for (i = 0; i < ARRAY_SIZE(cpcap_charger_irqs); i++) { + error = cpcap_usb_init_irq(pdev, ddata, cpcap_charger_irqs[i]); + if (error) + return error; + } + + return 0; +} + +static void cpcap_charger_init_optional_gpios(struct cpcap_charger_ddata *ddata) +{ + int i; + + for (i = 0; i < 2; i++) { + ddata->gpio[i] = devm_gpiod_get_index(ddata->dev, "mode", + i, GPIOD_OUT_HIGH); + if (IS_ERR(ddata->gpio[i])) { + dev_info(ddata->dev, "no mode change GPIO%i: %li\n", + i, PTR_ERR(ddata->gpio[i])); + ddata->gpio[i] = NULL; + } + } +} + +static int cpcap_charger_init_iio(struct cpcap_charger_ddata *ddata) +{ + const char * const names[CPCAP_CHARGER_IIO_NR] = { + "battdetb", "battp", "vbus", "chg_isense", "batti", + }; + int error, i; + + for (i = 0; i < CPCAP_CHARGER_IIO_NR; i++) { + ddata->channels[i] = devm_iio_channel_get(ddata->dev, + names[i]); + if (IS_ERR(ddata->channels[i])) { + error = PTR_ERR(ddata->channels[i]); + goto out_err; + } + + if (!ddata->channels[i]->indio_dev) { + error = -ENXIO; + goto out_err; + } + } + + return 0; + +out_err: + dev_err(ddata->dev, "could not initialize VBUS or ID IIO: %i\n", + error); + + return error; +} + +static const struct power_supply_desc cpcap_charger_usb_desc = { + .name = "cpcap_usb", + .type = POWER_SUPPLY_TYPE_USB, + .properties = cpcap_charger_props, + .num_properties = ARRAY_SIZE(cpcap_charger_props), + .get_property = cpcap_charger_get_property, +}; + +#ifdef CONFIG_OF +static const struct of_device_id cpcap_charger_id_table[] = { + { + .compatible = "motorola,mapphone-cpcap-charger", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, cpcap_charger_id_table); +#endif + +static int cpcap_charger_probe(struct platform_device *pdev) +{ + struct cpcap_charger_ddata *ddata; + const struct of_device_id *of_id; + int error; + + of_id = of_match_device(of_match_ptr(cpcap_charger_id_table), + &pdev->dev); + if (!of_id) + return -EINVAL; + + ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL); + if (!ddata) + return -ENOMEM; + + ddata->dev = &pdev->dev; + + ddata->reg = dev_get_regmap(ddata->dev->parent, NULL); + if (!ddata->reg) + return -ENODEV; + + INIT_LIST_HEAD(&ddata->irq_list); + INIT_DELAYED_WORK(&ddata->detect_work, cpcap_usb_detect); + INIT_DELAYED_WORK(&ddata->vbus_work, cpcap_charger_vbus_work); + platform_set_drvdata(pdev, ddata); + + error = cpcap_charger_init_iio(ddata); + if (error) + return error; + + atomic_set(&ddata->active, 1); + + ddata->usb = devm_power_supply_register(ddata->dev, + &cpcap_charger_usb_desc, + NULL); + if (IS_ERR(ddata->usb)) { + error = PTR_ERR(ddata->usb); + dev_err(ddata->dev, "failed to register USB charger: %i\n", + error); + + return error; + } + + error = cpcap_usb_init_interrupts(pdev, ddata); + if (error) + return error; + + ddata->comparator.set_vbus = cpcap_charger_set_vbus; + error = omap_usb2_set_comparator(&ddata->comparator); + if (error == -ENODEV) { + dev_info(ddata->dev, "charger needs phy, deferring probe\n"); + return -EPROBE_DEFER; + } + + cpcap_charger_init_optional_gpios(ddata); + + schedule_delayed_work(&ddata->detect_work, 0); + + return 0; +} + +static int cpcap_charger_remove(struct platform_device *pdev) +{ + struct cpcap_charger_ddata *ddata = platform_get_drvdata(pdev); + int error; + + atomic_set(&ddata->active, 0); + error = omap_usb2_set_comparator(NULL); + if (error) + dev_warn(ddata->dev, "could not clear USB comparator: %i\n", + error); + + error = cpcap_charger_set_state(ddata, 0, 0, 0); + if (error) + dev_warn(ddata->dev, "could not clear charger: %i\n", + error); + cancel_delayed_work_sync(&ddata->vbus_work); + cancel_delayed_work_sync(&ddata->detect_work); + + return 0; +} + +static struct platform_driver cpcap_charger_driver = { + .probe = cpcap_charger_probe, + .driver = { + .name = "cpcap-charger", + .of_match_table = of_match_ptr(cpcap_charger_id_table), + }, + .remove = cpcap_charger_remove, +}; +module_platform_driver(cpcap_charger_driver); + +MODULE_AUTHOR("Tony Lindgren "); +MODULE_DESCRIPTION("CPCAP Battery Charger Interface driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:cpcap-charger"); From bdd9968d35f7fcdb76089347d1529bf079534214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Giedrius=20Statkevi=C4=8Dius?= Date: Sat, 25 Mar 2017 18:00:49 +0200 Subject: [PATCH 18/29] power: supply: lp8788: prevent out of bounds array access MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit val might become 7 in which case stime[7] (array of length 7) would be accessed during the scnprintf call later and that will cause issues. Obviously, string concatenation is not intended here so just a comma needs to be added to fix the issue. Fixes: 98a276649358 ("power_supply: Add new lp8788 charger driver") Signed-off-by: Giedrius Statkevičius Acked-by: Milo Kim Signed-off-by: Sebastian Reichel --- drivers/power/supply/lp8788-charger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/lp8788-charger.c b/drivers/power/supply/lp8788-charger.c index 509e2b341bd6..677f7c40b25a 100644 --- a/drivers/power/supply/lp8788-charger.c +++ b/drivers/power/supply/lp8788-charger.c @@ -651,7 +651,7 @@ static ssize_t lp8788_show_eoc_time(struct device *dev, { struct lp8788_charger *pchg = dev_get_drvdata(dev); char *stime[] = { "400ms", "5min", "10min", "15min", - "20min", "25min", "30min" "No timeout" }; + "20min", "25min", "30min", "No timeout" }; u8 val; lp8788_read_byte(pchg->lp, LP8788_CHG_EOC, &val); From dc6ea7d4cd780414facb9d201434f47abd197917 Mon Sep 17 00:00:00 2001 From: Andi Shyti Date: Thu, 30 Mar 2017 00:13:57 +0900 Subject: [PATCH 19/29] power: supply: charger-manager: simplify return statements Some trivial improvements on the returning value of the functions: - remove unnecessary goto labels that just return, return immediately, instead. - do not initialize when not needed. - return the value from the calling function that fails instead of politically choosing -EINVAL. Signed-off-by: Andi Shyti Signed-off-by: Sebastian Reichel --- drivers/power/supply/charger-manager.c | 35 +++++++++++--------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/drivers/power/supply/charger-manager.c b/drivers/power/supply/charger-manager.c index e664ca7c0afd..adc3761831e1 100644 --- a/drivers/power/supply/charger-manager.c +++ b/drivers/power/supply/charger-manager.c @@ -1198,7 +1198,7 @@ static int charger_extcon_notifier(struct notifier_block *self, static int charger_extcon_init(struct charger_manager *cm, struct charger_cable *cable) { - int ret = 0; + int ret; /* * Charger manager use Extcon framework to identify @@ -1232,7 +1232,7 @@ static int charger_manager_register_extcon(struct charger_manager *cm) { struct charger_desc *desc = cm->desc; struct charger_regulator *charger; - int ret = 0; + int ret; int i; int j; @@ -1255,15 +1255,14 @@ static int charger_manager_register_extcon(struct charger_manager *cm) if (ret < 0) { dev_err(cm->dev, "Cannot initialize charger(%s)\n", charger->regulator_name); - goto err; + return ret; } cable->charger = charger; cable->cm = cm; } } -err: - return ret; + return 0; } /* help function of sysfs node to control charger(regulator) */ @@ -1372,7 +1371,7 @@ static int charger_manager_register_sysfs(struct charger_manager *cm) int chargers_externally_control = 1; char buf[11]; char *str; - int ret = 0; + int ret; int i; /* Create sysfs entry to control charger(regulator) */ @@ -1382,10 +1381,9 @@ static int charger_manager_register_sysfs(struct charger_manager *cm) snprintf(buf, 10, "charger.%d", i); str = devm_kzalloc(cm->dev, sizeof(char) * (strlen(buf) + 1), GFP_KERNEL); - if (!str) { - ret = -ENOMEM; - goto err; - } + if (!str) + return -ENOMEM; + strcpy(str, buf); charger->attrs[0] = &charger->attr_name.attr; @@ -1426,19 +1424,16 @@ static int charger_manager_register_sysfs(struct charger_manager *cm) if (ret < 0) { dev_err(cm->dev, "Cannot create sysfs entry of %s regulator\n", charger->regulator_name); - ret = -EINVAL; - goto err; + return ret; } } if (chargers_externally_control) { dev_err(cm->dev, "Cannot register regulator because charger-manager must need at least one charger for charging battery\n"); - ret = -EINVAL; - goto err; + return -EINVAL; } -err: - return ret; + return 0; } static int cm_init_thermal_data(struct charger_manager *cm, @@ -1626,7 +1621,7 @@ static int charger_manager_probe(struct platform_device *pdev) { struct charger_desc *desc = cm_get_drv_data(pdev); struct charger_manager *cm; - int ret = 0, i = 0; + int ret, i = 0; int j = 0; union power_supply_propval val; struct power_supply *fuel_gauge; @@ -1887,14 +1882,12 @@ MODULE_DEVICE_TABLE(platform, charger_manager_id); static int cm_suspend_noirq(struct device *dev) { - int ret = 0; - if (device_may_wakeup(dev)) { device_set_wakeup_capable(dev, false); - ret = -EAGAIN; + return -EAGAIN; } - return ret; + return 0; } static bool cm_need_to_awake(void) From c67c372ca667eef047538eb3456f87ea94bd2949 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Wed, 29 Mar 2017 12:00:56 -0400 Subject: [PATCH 20/29] power: supply: ltc2941-battery-gauge: Add vendor to compatibles in binding The DT binding document for LTC2941 and LTC2943 battery gauges did not use a vendor prefix in the listed compatible strings. The driver says that the manufacturer is Linear Technology which is "lltc" in vendor-prefixes.txt. There isn't an upstream Device Tree source file that has nodes defined for these devices, so there's no need to keep the old compatible strings. Signed-off-by: Javier Martinez Canillas Acked-by: Rob Herring Signed-off-by: Sebastian Reichel --- Documentation/devicetree/bindings/power/supply/ltc2941.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/power/supply/ltc2941.txt b/Documentation/devicetree/bindings/power/supply/ltc2941.txt index ea42ae12d924..a9d7aa60558b 100644 --- a/Documentation/devicetree/bindings/power/supply/ltc2941.txt +++ b/Documentation/devicetree/bindings/power/supply/ltc2941.txt @@ -6,8 +6,8 @@ temperature monitoring, and uses a slightly different conversion formula for the charge counter. Required properties: -- compatible: Should contain "ltc2941" or "ltc2943" which also indicates the - type of I2C chip attached. +- compatible: Should contain "lltc,ltc2941" or "lltc,ltc2943" which also + indicates the type of I2C chip attached. - reg: The 7-bit I2C address. - lltc,resistor-sense: The sense resistor value in milli-ohms. Can be a 32-bit negative value when the battery has been connected to the wrong end of the @@ -20,7 +20,7 @@ Required properties: Example from the Topic Miami Florida board: fuelgauge: ltc2943@64 { - compatible = "ltc2943"; + compatible = "lltc,ltc2943"; reg = <0x64>; lltc,resistor-sense = <15>; lltc,prescaler-exponent = <5>; /* 2^(2*5) = 1024 */ From 9697277ed52980629774ffa603404ee44600154e Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Wed, 29 Mar 2017 12:00:57 -0400 Subject: [PATCH 21/29] power: supply: ltc2941-battery-gauge: Add OF device ID table The driver doesn't have a struct of_device_id table but supported devices are registered via Device Trees. This is working on the assumption that a I2C device registered via OF will always match a legacy I2C device ID and that the MODALIAS reported will always be of the form i2c:. But this could change in the future so the correct approach is to have an OF device ID table if the devices are registered via OF. Signed-off-by: Javier Martinez Canillas Signed-off-by: Sebastian Reichel --- drivers/power/supply/ltc2941-battery-gauge.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/drivers/power/supply/ltc2941-battery-gauge.c b/drivers/power/supply/ltc2941-battery-gauge.c index 4adf2ba021ce..7efb908f4451 100644 --- a/drivers/power/supply/ltc2941-battery-gauge.c +++ b/drivers/power/supply/ltc2941-battery-gauge.c @@ -9,6 +9,7 @@ */ #include #include +#include #include #include #include @@ -61,7 +62,7 @@ struct ltc294x_info { struct power_supply *supply; /* Supply pointer */ struct power_supply_desc supply_desc; /* Supply description */ struct delayed_work work; /* Work scheduler */ - int num_regs; /* Number of registers (chip type) */ + unsigned long num_regs; /* Number of registers (chip type) */ int charge; /* Last charge register content */ int r_sense; /* mOhm */ int Qlsb; /* nAh */ @@ -387,7 +388,7 @@ static int ltc294x_i2c_probe(struct i2c_client *client, np = of_node_get(client->dev.of_node); - info->num_regs = id->driver_data; + info->num_regs = (unsigned long)of_device_get_match_data(&client->dev); info->supply_desc.name = np->name; /* r_sense can be negative, when sense+ is connected to the battery @@ -497,9 +498,23 @@ static const struct i2c_device_id ltc294x_i2c_id[] = { }; MODULE_DEVICE_TABLE(i2c, ltc294x_i2c_id); +static const struct of_device_id ltc294x_i2c_of_match[] = { + { + .compatible = "lltc,ltc2941", + .data = (void *)LTC2941_NUM_REGS + }, + { + .compatible = "lltc,ltc2943", + .data = (void *)LTC2943_NUM_REGS + }, + { }, +}; +MODULE_DEVICE_TABLE(of, ltc294x_i2c_of_match); + static struct i2c_driver ltc294x_driver = { .driver = { .name = "LTC2941", + .of_match_table = ltc294x_i2c_of_match, .pm = LTC294X_PM_OPS, }, .probe = ltc294x_i2c_probe, From 53a022d0c1625db0f8e4a29f2913dfdbf4bb7152 Mon Sep 17 00:00:00 2001 From: Enric Balletbo i Serra Date: Mon, 3 Apr 2017 18:44:45 +0200 Subject: [PATCH 22/29] power: supply: tps65217: remove debug messages for function calls Equivalent information can be nowadays obtained using function tracer. Signed-off-by: Enric Balletbo i Serra Signed-off-by: Sebastian Reichel --- drivers/power/supply/tps65217_charger.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/power/supply/tps65217_charger.c b/drivers/power/supply/tps65217_charger.c index 29b61e81b385..1f5234098aaf 100644 --- a/drivers/power/supply/tps65217_charger.c +++ b/drivers/power/supply/tps65217_charger.c @@ -58,8 +58,6 @@ 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) * @@ -205,8 +203,6 @@ static int tps65217_charger_probe(struct platform_device *pdev) int ret; int i; - dev_dbg(&pdev->dev, "%s\n", __func__); - charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL); if (!charger) return -ENOMEM; From 11ecb0d234ea7f3b33760281fd4113115c71d5f7 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Tue, 11 Apr 2017 16:05:21 -0500 Subject: [PATCH 23/29] dt-bindings: power: supply: New bindings for LEGO MINDSTORMS EV3 battery This add a new device tree binding for LEGO MINDSTORMS EV3 battery. The EV3 has some built-in capability for monitoring the attached battery. Signed-off-by: David Lechner Acked-by: Rob Herring Signed-off-by: Sebastian Reichel --- .../power/supply/lego_ev3_battery.txt | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 Documentation/devicetree/bindings/power/supply/lego_ev3_battery.txt diff --git a/Documentation/devicetree/bindings/power/supply/lego_ev3_battery.txt b/Documentation/devicetree/bindings/power/supply/lego_ev3_battery.txt new file mode 100644 index 000000000000..5485633b1faa --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/lego_ev3_battery.txt @@ -0,0 +1,21 @@ +LEGO MINDSTORMS EV3 Battery +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +LEGO MINDSTORMS EV3 has some built-in capability for monitoring the battery. +It uses 6 AA batteries or a special Li-ion rechargeable battery pack that is +detected by a key switch in the battery compartment. + +Required properties: + - compatible: Must be "lego,ev3-battery" + - io-channels: phandles to analog inputs for reading voltage and current + - io-channel-names: Must be "voltage", "current" + - rechargeable-gpios: phandle to the rechargeable battery indication gpio + +Example: + + battery { + compatible = "lego,ev3-battery"; + io-channels = <&adc 4>, <&adc 3>; + io-channel-names = "voltage", "current"; + rechargeable-gpios = <&gpio 136 GPIO_ACTIVE_LOW>; + }; From 53db88586acd39400665d32914d1bb7b3da07276 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Tue, 11 Apr 2017 16:05:22 -0500 Subject: [PATCH 24/29] power: supply: New driver for LEGO MINDSTORMS EV3 battery This adds a new driver for the LEGO MINDSTORMS EV3 battery. The EV3 is an embedded ARM device that can use 6 AA batteries or a special rechargeable Li-ion battery pack. The rechargeable battery pack presses a special key switch in the battery compartment to indicate that it is present. The EV3 is only capable of monitoring battery voltage and current. The charging circuit is built into the rechargeable battery pack and there is no way to communicate with is, so we can't provide any information about charging status. When not using the rechargeable battery pack, it is most common to use alkaline batteries to power the device, but it is also common for people to use rechargeable NiMH batteries. Since there is not a way to automatically differentiate between these, the technology property is made writable. Signed-off-by: David Lechner Signed-off-by: Sebastian Reichel --- drivers/power/supply/Kconfig | 6 + drivers/power/supply/Makefile | 1 + drivers/power/supply/lego_ev3_battery.c | 228 ++++++++++++++++++++++++ 3 files changed, 235 insertions(+) create mode 100644 drivers/power/supply/lego_ev3_battery.c diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index 2ac3dd41f15b..da922756149f 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -117,6 +117,12 @@ config BATTERY_DS2782 Say Y here to enable support for the DS2782/DS2786 standalone battery gas-gauge. +config BATTERY_LEGO_EV3 + tristate "LEGO MINDSTORMS EV3 battery" + depends on OF && IIO && GPIOLIB + help + Say Y here to enable support for the LEGO MINDSTORMS EV3 battery. + config BATTERY_PMU tristate "Apple PMU battery" depends on PPC32 && ADB_PMU diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index 957e099a05cd..39fc733e6cc4 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -25,6 +25,7 @@ obj-$(CONFIG_BATTERY_DS2781) += ds2781_battery.o obj-$(CONFIG_BATTERY_DS2782) += ds2782_battery.o obj-$(CONFIG_BATTERY_GAUGE_LTC2941) += ltc2941-battery-gauge.o obj-$(CONFIG_BATTERY_GOLDFISH) += goldfish_battery.o +obj-$(CONFIG_BATTERY_LEGO_EV3) += lego_ev3_battery.o obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o obj-$(CONFIG_BATTERY_TOSA) += tosa_battery.o diff --git a/drivers/power/supply/lego_ev3_battery.c b/drivers/power/supply/lego_ev3_battery.c new file mode 100644 index 000000000000..7b993d669f7f --- /dev/null +++ b/drivers/power/supply/lego_ev3_battery.c @@ -0,0 +1,228 @@ +/* + * Battery driver for LEGO MINDSTORMS EV3 + * + * Copyright (C) 2017 David Lechner + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct lego_ev3_battery { + struct iio_channel *iio_v; + struct iio_channel *iio_i; + struct gpio_desc *rechargeable_gpio; + struct power_supply *psy; + int technology; + int v_max; + int v_min; +}; + +static int lego_ev3_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct lego_ev3_battery *batt = power_supply_get_drvdata(psy); + int val2; + + switch (psp) { + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = batt->technology; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + /* battery voltage is iio channel * 2 + Vce of transistor */ + iio_read_channel_processed(batt->iio_v, &val->intval); + val->intval *= 2000; + val->intval += 200000; + /* plus adjust for shunt resistor drop */ + iio_read_channel_processed(batt->iio_i, &val2); + val2 *= 1000; + val2 /= 15; + val->intval += val2; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = batt->v_max; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = batt->v_min; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + /* battery current is iio channel / 15 / 0.05 ohms */ + iio_read_channel_processed(batt->iio_i, &val->intval); + val->intval *= 20000; + val->intval /= 15; + break; + case POWER_SUPPLY_PROP_SCOPE: + val->intval = POWER_SUPPLY_SCOPE_SYSTEM; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int lego_ev3_battery_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct lego_ev3_battery *batt = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_TECHNOLOGY: + /* + * Only allow changing technology from Unknown to NiMH. Li-ion + * batteries are automatically detected and should not be + * overridden. Rechargeable AA batteries, on the other hand, + * cannot be automatically detected, and so must be manually + * specified. This should only be set once during system init, + * so there is no mechanism to go back to Unknown. + */ + if (batt->technology != POWER_SUPPLY_TECHNOLOGY_UNKNOWN) + return -EINVAL; + switch (val->intval) { + case POWER_SUPPLY_TECHNOLOGY_NiMH: + batt->technology = POWER_SUPPLY_TECHNOLOGY_NiMH; + batt->v_max = 7800000; + batt->v_min = 5400000; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static int lego_ev3_battery_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + struct lego_ev3_battery *batt = power_supply_get_drvdata(psy); + + return psp == POWER_SUPPLY_PROP_TECHNOLOGY && + batt->technology == POWER_SUPPLY_TECHNOLOGY_UNKNOWN; +} + +static enum power_supply_property lego_ev3_battery_props[] = { + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_SCOPE, +}; + +static const struct power_supply_desc lego_ev3_battery_desc = { + .name = "lego-ev3-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = lego_ev3_battery_props, + .num_properties = ARRAY_SIZE(lego_ev3_battery_props), + .get_property = lego_ev3_battery_get_property, + .set_property = lego_ev3_battery_set_property, + .property_is_writeable = lego_ev3_battery_property_is_writeable, +}; + +static int lego_ev3_battery_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct lego_ev3_battery *batt; + struct power_supply_config psy_cfg = {}; + int err; + + batt = devm_kzalloc(dev, sizeof(*batt), GFP_KERNEL); + if (!batt) + return -ENOMEM; + + platform_set_drvdata(pdev, batt); + + batt->iio_v = devm_iio_channel_get(dev, "voltage"); + err = PTR_ERR_OR_ZERO(batt->iio_v); + if (err) { + if (err != -EPROBE_DEFER) + dev_err(dev, "Failed to get voltage iio channel\n"); + return err; + } + + batt->iio_i = devm_iio_channel_get(dev, "current"); + err = PTR_ERR_OR_ZERO(batt->iio_i); + if (err) { + if (err != -EPROBE_DEFER) + dev_err(dev, "Failed to get current iio channel\n"); + return err; + } + + batt->rechargeable_gpio = devm_gpiod_get(dev, "rechargeable", GPIOD_IN); + err = PTR_ERR_OR_ZERO(batt->rechargeable_gpio); + if (err) { + if (err != -EPROBE_DEFER) + dev_err(dev, "Failed to get rechargeable gpio\n"); + return err; + } + + /* + * The rechargeable battery indication switch cannot be changed without + * removing the battery, so we only need to read it once. + */ + if (gpiod_get_value(batt->rechargeable_gpio)) { + /* 2-cell Li-ion, 7.4V nominal */ + batt->technology = POWER_SUPPLY_TECHNOLOGY_LION; + batt->v_max = 84000000; + batt->v_min = 60000000; + } else { + /* 6x AA Alkaline, 9V nominal */ + batt->technology = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; + batt->v_max = 90000000; + batt->v_min = 48000000; + } + + psy_cfg.of_node = pdev->dev.of_node; + psy_cfg.drv_data = batt; + + batt->psy = devm_power_supply_register(dev, &lego_ev3_battery_desc, + &psy_cfg); + err = PTR_ERR_OR_ZERO(batt->psy); + if (err) { + dev_err(dev, "failed to register power supply\n"); + return err; + } + + return 0; +} + +static const struct of_device_id of_lego_ev3_battery_match[] = { + { .compatible = "lego,ev3-battery", }, + { } +}; +MODULE_DEVICE_TABLE(of, of_lego_ev3_battery_match); + +static struct platform_driver lego_ev3_battery_driver = { + .driver = { + .name = "lego-ev3-battery", + .of_match_table = of_lego_ev3_battery_match, + }, + .probe = lego_ev3_battery_probe, +}; +module_platform_driver(lego_ev3_battery_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("David Lechner "); +MODULE_DESCRIPTION("LEGO MINDSTORMS EV3 Battery Driver"); From d63d07c6fc25182af6d3ab5b3b8737b0c1025ebd Mon Sep 17 00:00:00 2001 From: Liam Breck Date: Tue, 11 Apr 2017 04:59:54 -0700 Subject: [PATCH 25/29] power: supply: bq24190_charger: Limit over/under voltage fault logging If the charger is unplugged before the battery is full we may see an over/under voltage fault. Ignore this rather then emitting a message or uevent. This fixes messages like these getting logged on charger unplug + replug: bq24190-charger 15-006b: Fault: boost 0, charge 1, battery 0, ntc 0 bq24190-charger 15-006b: Fault: boost 0, charge 0, battery 0, ntc 0 Cc: Tony Lindgren Cc: Hans de Goede Signed-off-by: Liam Breck Acked-by: Tony Lindgren Reviewed-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq24190_charger.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c index 2c404a59e613..59a7b6cbf5fa 100644 --- a/drivers/power/supply/bq24190_charger.c +++ b/drivers/power/supply/bq24190_charger.c @@ -1222,8 +1222,13 @@ static void bq24190_check_status(struct bq24190_dev_info *bdi) } } while (f_reg && ++i < 2); + /* ignore over/under voltage fault after disconnect */ + if (f_reg == (1 << BQ24190_REG_F_CHRG_FAULT_SHIFT) && + !(ss_reg & BQ24190_REG_SS_PG_STAT_MASK)) + f_reg = 0; + if (f_reg != bdi->f_reg) { - dev_info(bdi->dev, + dev_warn(bdi->dev, "Fault: boost %d, charge %d, battery %d, ntc %d\n", !!(f_reg & BQ24190_REG_F_BOOST_FAULT_MASK), !!(f_reg & BQ24190_REG_F_CHRG_FAULT_MASK), From 03add17fe338af82a28b41bda6afeb3b25c0a323 Mon Sep 17 00:00:00 2001 From: Liam Breck Date: Tue, 11 Apr 2017 04:59:55 -0700 Subject: [PATCH 26/29] power: supply: bq24190_charger: Clean up extcon code Polishing and fixes for initial extcon patch. Fixes: 4db249b6f3b4 ("power: supply: bq24190_charger: Use extcon to determine ilimit, 5v boost") Cc: Tony Lindgren Signed-off-by: Liam Breck Acked-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq24190_charger.c | 84 ++++++++++++++------------ 1 file changed, 46 insertions(+), 38 deletions(-) diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c index 59a7b6cbf5fa..145276f5056b 100644 --- a/drivers/power/supply/bq24190_charger.c +++ b/drivers/power/supply/bq24190_charger.c @@ -38,8 +38,9 @@ #define BQ24190_REG_POC_WDT_RESET_SHIFT 6 #define BQ24190_REG_POC_CHG_CONFIG_MASK (BIT(5) | BIT(4)) #define BQ24190_REG_POC_CHG_CONFIG_SHIFT 4 -#define BQ24190_REG_POC_CHG_CONFIG_CHARGE 1 -#define BQ24190_REG_POC_CHG_CONFIG_OTG 2 +#define BQ24190_REG_POC_CHG_CONFIG_DISABLE 0x0 +#define BQ24190_REG_POC_CHG_CONFIG_CHARGE 0x1 +#define BQ24190_REG_POC_CHG_CONFIG_OTG 0x2 #define BQ24190_REG_POC_SYS_MIN_MASK (BIT(3) | BIT(2) | BIT(1)) #define BQ24190_REG_POC_SYS_MIN_SHIFT 1 #define BQ24190_REG_POC_BOOST_LIM_MASK BIT(0) @@ -173,8 +174,9 @@ struct bq24190_dev_info { */ /* REG00[2:0] (IINLIM) in uAh */ -static const int bq24190_iinlim_values[] = { - 100000, 150000, 500000, 900000, 1200000, 1500000, 2000000, 3000000 }; +static const int bq24190_isc_iinlim_values[] = { + 100000, 150000, 500000, 900000, 1200000, 1500000, 2000000, 3000000 +}; /* REG02[7:2] (ICHG) in uAh */ static const int bq24190_ccc_ichg_values[] = { @@ -1298,16 +1300,18 @@ static void bq24190_extcon_work(struct work_struct *work) { struct bq24190_dev_info *bdi = container_of(work, struct bq24190_dev_info, extcon_work.work); - int ret, iinlim = 0; + int error, iinlim = 0; + u8 v; - ret = pm_runtime_get_sync(bdi->dev); - if (ret < 0) { - dev_err(bdi->dev, "Error getting runtime-pm ref: %d\n", ret); + error = pm_runtime_get_sync(bdi->dev); + if (error < 0) { + dev_warn(bdi->dev, "pm_runtime_get failed: %i\n", error); + pm_runtime_put_noidle(bdi->dev); return; } - if (extcon_get_state(bdi->extcon, EXTCON_CHG_USB_SDP) == 1) - iinlim = 500000; + if (extcon_get_state(bdi->extcon, EXTCON_CHG_USB_SDP) == 1) + iinlim = 500000; else if (extcon_get_state(bdi->extcon, EXTCON_CHG_USB_CDP) == 1 || extcon_get_state(bdi->extcon, EXTCON_CHG_USB_ACA) == 1) iinlim = 1500000; @@ -1315,33 +1319,28 @@ static void bq24190_extcon_work(struct work_struct *work) iinlim = 2000000; if (iinlim) { - ret = bq24190_set_field_val(bdi, BQ24190_REG_ISC, - BQ24190_REG_ISC_IINLIM_MASK, - BQ24190_REG_ISC_IINLIM_SHIFT, - bq24190_iinlim_values, - ARRAY_SIZE(bq24190_iinlim_values), - iinlim); - if (ret) - dev_err(bdi->dev, "Can't set IINLIM: %d\n", ret); + error = bq24190_set_field_val(bdi, BQ24190_REG_ISC, + BQ24190_REG_ISC_IINLIM_MASK, + BQ24190_REG_ISC_IINLIM_SHIFT, + bq24190_isc_iinlim_values, + ARRAY_SIZE(bq24190_isc_iinlim_values), + iinlim); + if (error < 0) + dev_err(bdi->dev, "Can't set IINLIM: %d\n", error); } - /* - * If no charger has been detected and host mode is requested, activate - * the 5V boost converter, otherwise deactivate it. - */ - if (!iinlim && extcon_get_state(bdi->extcon, EXTCON_USB_HOST) == 1) { - ret = bq24190_write_mask(bdi, BQ24190_REG_POC, - BQ24190_REG_POC_CHG_CONFIG_MASK, - BQ24190_REG_POC_CHG_CONFIG_SHIFT, - BQ24190_REG_POC_CHG_CONFIG_OTG); - } else { - ret = bq24190_write_mask(bdi, BQ24190_REG_POC, - BQ24190_REG_POC_CHG_CONFIG_MASK, - BQ24190_REG_POC_CHG_CONFIG_SHIFT, - BQ24190_REG_POC_CHG_CONFIG_CHARGE); - } - if (ret) - dev_err(bdi->dev, "Can't set CHG_CONFIG: %d\n", ret); + /* if no charger found and in USB host mode, set OTG 5V boost, else normal */ + if (!iinlim && extcon_get_state(bdi->extcon, EXTCON_USB_HOST) == 1) + v = BQ24190_REG_POC_CHG_CONFIG_OTG; + else + v = BQ24190_REG_POC_CHG_CONFIG_CHARGE; + + error = bq24190_write_mask(bdi, BQ24190_REG_POC, + BQ24190_REG_POC_CHG_CONFIG_MASK, + BQ24190_REG_POC_CHG_CONFIG_SHIFT, + v); + if (error < 0) + dev_err(bdi->dev, "Can't set CHG_CONFIG: %d\n", error); pm_runtime_mark_last_busy(bdi->dev); pm_runtime_put_autosuspend(bdi->dev); @@ -1432,8 +1431,15 @@ static int bq24190_probe(struct i2c_client *client, } /* - * The extcon-name property is purely an in kernel interface for - * x86/ACPI use, DT platforms should get extcon via phandle. + * Devicetree platforms should get extcon via phandle (not yet supported). + * On ACPI platforms, extcon clients may invoke us with: + * struct property_entry pe[] = + * { PROPERTY_ENTRY_STRING("extcon-name", client_name), ... }; + * struct i2c_board_info bi = + * { .type = "bq24190", .addr = 0x6b, .properties = pe, .irq = irq }; + * struct i2c_adapter ad = { ... }; + * i2c_add_adapter(&ad); + * i2c_new_device(&ad, &bi); */ if (device_property_read_string(dev, "extcon-name", &name) == 0) { bdi->extcon = extcon_get_extcon_dev(name); @@ -1498,8 +1504,10 @@ static int bq24190_probe(struct i2c_client *client, bdi->extcon_nb.notifier_call = bq24190_extcon_event; ret = devm_extcon_register_notifier(dev, bdi->extcon, -1, &bdi->extcon_nb); - if (ret) + if (ret) { + dev_err(dev, "Can't register extcon\n"); goto out5; + } /* Sync initial cable state */ queue_delayed_work(system_wq, &bdi->extcon_work, 0); From e3ebc381a966f7fb41b13c2823ff96f9d6d7a377 Mon Sep 17 00:00:00 2001 From: Liam Breck Date: Tue, 11 Apr 2017 04:59:56 -0700 Subject: [PATCH 27/29] power: supply: bq24190_charger: Uniform pm_runtime_get() failure handling On pm_runtime_get() failure, always emit an error message. Prevent unbalanced pm_runtime_get by calling: pm_runtime_put_noidle() in irq handler pm_runtime_put_sync() on any probe() failure Rename probe() out labels instead of renumbering them. Fixes: 13d6fa8447fa ("power: bq24190_charger: Use PM runtime autosuspend") Signed-off-by: Liam Breck Acked-by: Tony Lindgren Reviewed-by: Hans de Goede Acked-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq24190_charger.c | 37 +++++++++++++------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c index 145276f5056b..13b22e4a80cd 100644 --- a/drivers/power/supply/bq24190_charger.c +++ b/drivers/power/supply/bq24190_charger.c @@ -1280,12 +1280,13 @@ static void bq24190_check_status(struct bq24190_dev_info *bdi) static irqreturn_t bq24190_irq_handler_thread(int irq, void *data) { struct bq24190_dev_info *bdi = data; - int ret; + int error; bdi->irq_event = true; - ret = pm_runtime_get_sync(bdi->dev); - if (ret < 0) { - dev_warn(bdi->dev, "pm_runtime_get failed: %i\n", ret); + error = pm_runtime_get_sync(bdi->dev); + if (error < 0) { + dev_warn(bdi->dev, "pm_runtime_get failed: %i\n", error); + pm_runtime_put_noidle(bdi->dev); return IRQ_NONE; } bq24190_check_status(bdi); @@ -1453,13 +1454,15 @@ static int bq24190_probe(struct i2c_client *client, pm_runtime_use_autosuspend(dev); pm_runtime_set_autosuspend_delay(dev, 600); ret = pm_runtime_get_sync(dev); - if (ret < 0) - goto out1; + if (ret < 0) { + dev_err(dev, "pm_runtime_get failed: %i\n", ret); + goto out_pmrt; + } ret = bq24190_hw_init(bdi); if (ret < 0) { dev_err(dev, "Hardware init failed\n"); - goto out2; + goto out_pmrt; } charger_cfg.drv_data = bdi; @@ -1470,7 +1473,7 @@ static int bq24190_probe(struct i2c_client *client, if (IS_ERR(bdi->charger)) { dev_err(dev, "Can't register charger\n"); ret = PTR_ERR(bdi->charger); - goto out2; + goto out_pmrt; } battery_cfg.drv_data = bdi; @@ -1479,13 +1482,13 @@ static int bq24190_probe(struct i2c_client *client, if (IS_ERR(bdi->battery)) { dev_err(dev, "Can't register battery\n"); ret = PTR_ERR(bdi->battery); - goto out3; + goto out_charger; } ret = bq24190_sysfs_create_group(bdi); if (ret) { dev_err(dev, "Can't create sysfs entries\n"); - goto out4; + goto out_battery; } bdi->initialized = true; @@ -1496,7 +1499,7 @@ static int bq24190_probe(struct i2c_client *client, "bq24190-charger", bdi); if (ret < 0) { dev_err(dev, "Can't set up irq handler\n"); - goto out5; + goto out_sysfs; } if (bdi->extcon) { @@ -1506,7 +1509,7 @@ static int bq24190_probe(struct i2c_client *client, &bdi->extcon_nb); if (ret) { dev_err(dev, "Can't register extcon\n"); - goto out5; + goto out_sysfs; } /* Sync initial cable state */ @@ -1520,19 +1523,17 @@ static int bq24190_probe(struct i2c_client *client, return 0; -out5: +out_sysfs: bq24190_sysfs_remove_group(bdi); -out4: +out_battery: power_supply_unregister(bdi->battery); -out3: +out_charger: power_supply_unregister(bdi->charger); -out2: +out_pmrt: pm_runtime_put_sync(dev); - -out1: pm_runtime_dont_use_autosuspend(dev); pm_runtime_disable(dev); return ret; From 61489b0f9fa835cd2fef67ae26790cdf64b6b1c7 Mon Sep 17 00:00:00 2001 From: Liam Breck Date: Tue, 11 Apr 2017 04:59:57 -0700 Subject: [PATCH 28/29] power: supply: bq24190_charger: Longer delay while polling reset flag On chip reset, polling loop used udelay(10) which is too short to be useful. Instead, use usleep_range(100, 200). Signed-off-by: Liam Breck Acked-by: Tony Lindgren Acked-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq24190_charger.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c index 13b22e4a80cd..7c893c00fa05 100644 --- a/drivers/power/supply/bq24190_charger.c +++ b/drivers/power/supply/bq24190_charger.c @@ -550,16 +550,13 @@ static int bq24190_register_reset(struct bq24190_dev_info *bdi) if (ret < 0) return ret; - if (!v) - break; + if (v == 0) + return 0; - udelay(10); + usleep_range(100, 200); } while (--limit); - if (!limit) - return -EIO; - - return 0; + return -EIO; } /* Charger power supply property routines */ From 6c381663bb3b4febc15b2fb33f046f0b986ce5c5 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 13 Apr 2017 14:04:11 +0200 Subject: [PATCH 29/29] power: supply: bq24190_charger: Use new extcon_register_notifier_all() When I submitted the extcon handling I had a patch pending for the extcon sub-system for extcon_register_notifier to take -1 as cable id for listening for all type cable events on an extcon with a single notifier. In the end it was decided to instead add a new extcon_register_notifier_all function for this, switch to using this. Signed-off-by: Hans de Goede Acked-by: Liam Breck Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq24190_charger.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c index 7c893c00fa05..bd9e5c3d8cc2 100644 --- a/drivers/power/supply/bq24190_charger.c +++ b/drivers/power/supply/bq24190_charger.c @@ -1502,8 +1502,8 @@ static int bq24190_probe(struct i2c_client *client, if (bdi->extcon) { INIT_DELAYED_WORK(&bdi->extcon_work, bq24190_extcon_work); bdi->extcon_nb.notifier_call = bq24190_extcon_event; - ret = devm_extcon_register_notifier(dev, bdi->extcon, -1, - &bdi->extcon_nb); + ret = devm_extcon_register_notifier_all(dev, bdi->extcon, + &bdi->extcon_nb); if (ret) { dev_err(dev, "Can't register extcon\n"); goto out_sysfs;