From d36240d26025bec95f3499e2401a56db98d9f01c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pali=20Roh=C3=A1r?= Date: Tue, 19 Nov 2013 11:18:03 +0100 Subject: power_supply: Add power_supply notifier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch adds a notifier chain to the power_supply, this helps drivers in other subsystem to listen to changes in power supply subsystem. This would help to take some actions in those drivers on changing the power supply properties. One such scenario is to increase/decrease system performance based on the battery capacity/voltage. Another scenario is to adjust the h/w peak current detection voltage/current thresholds based on battery voltage/capacity. The notifier helps drivers to listen to changes in power_suppy susbystem without polling the power_supply properties Signed-off-by: Jenny TC Signed-off-by: Pali Rohár Acked-by: Jenny TC Signed-off-by: Anton Vorontsov --- drivers/power/power_supply_core.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'drivers') diff --git a/drivers/power/power_supply_core.c b/drivers/power/power_supply_core.c index 557af943b2f5..6aac23cc6566 100644 --- a/drivers/power/power_supply_core.c +++ b/drivers/power/power_supply_core.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -24,6 +25,9 @@ struct class *power_supply_class; EXPORT_SYMBOL_GPL(power_supply_class); +ATOMIC_NOTIFIER_HEAD(power_supply_notifier); +EXPORT_SYMBOL_GPL(power_supply_notifier); + static struct device_type power_supply_dev_type; static bool __power_supply_is_supplied_by(struct power_supply *supplier, @@ -80,6 +84,8 @@ static void power_supply_changed_work(struct work_struct *work) class_for_each_device(power_supply_class, NULL, psy, __power_supply_changed_work); power_supply_update_leds(psy); + atomic_notifier_call_chain(&power_supply_notifier, + PSY_EVENT_PROP_CHANGED, psy); kobject_uevent(&psy->dev->kobj, KOBJ_CHANGE); spin_lock_irqsave(&psy->changed_lock, flags); } @@ -347,6 +353,18 @@ static void power_supply_dev_release(struct device *dev) kfree(dev); } +int power_supply_reg_notifier(struct notifier_block *nb) +{ + return atomic_notifier_chain_register(&power_supply_notifier, nb); +} +EXPORT_SYMBOL_GPL(power_supply_reg_notifier); + +void power_supply_unreg_notifier(struct notifier_block *nb) +{ + atomic_notifier_chain_unregister(&power_supply_notifier, nb); +} +EXPORT_SYMBOL_GPL(power_supply_unreg_notifier); + #ifdef CONFIG_THERMAL static int power_supply_read_temp(struct thermal_zone_device *tzd, unsigned long *temp) -- cgit v1.2.3 From 5c49a6256bed52f639ed70d252b1c91d1ab899d6 Mon Sep 17 00:00:00 2001 From: Jonghwa Lee Date: Wed, 18 Dec 2013 15:42:34 +0900 Subject: charger-manager: Modify the way of checking battery's temperature Charger-manager driver used to check battery temperature through the callback function passed by platform data. Unfortunatley, without that pre-defined callback function, charger-manager can't get battery's temperature at all. Also passing callback function through platform data ruins DT support for charger-manager. This patch mondifies charger-manager driver to get temperature of battery without pre-defined callback function. Now, charger-manager can use either of battery thermometer in fuel-gauge and ouside of battery. It uses thermal framework interface for outer thermometer if thermal fw is enabled. Otherwise, it tries to use fuel-gauge's through the power supply interface. Signed-off-by: Jonghwa Lee Signed-off-by: Myungjoo Ham Signed-off-by: Anton Vorontsov --- drivers/power/charger-manager.c | 161 ++++++++++++++++++++++++++++++---------- 1 file changed, 120 insertions(+), 41 deletions(-) (limited to 'drivers') diff --git a/drivers/power/charger-manager.c b/drivers/power/charger-manager.c index 7287c0efd6bf..6b68d158e3c1 100644 --- a/drivers/power/charger-manager.c +++ b/drivers/power/charger-manager.c @@ -25,12 +25,22 @@ #include #include #include +#include + +/* + * Default termperature threshold for charging. + * Every temperature units are in tenth of centigrade. + */ +#define CM_DEFAULT_RECHARGE_TEMP_DIFF 50 +#define CM_DEFAULT_CHARGE_TEMP_MAX 500 static const char * const default_event_names[] = { [CM_EVENT_UNKNOWN] = "Unknown", [CM_EVENT_BATT_FULL] = "Battery Full", [CM_EVENT_BATT_IN] = "Battery Inserted", [CM_EVENT_BATT_OUT] = "Battery Pulled Out", + [CM_EVENT_BATT_OVERHEAT] = "Battery Overheat", + [CM_EVENT_BATT_COLD] = "Battery Cold", [CM_EVENT_EXT_PWR_IN_OUT] = "External Power Attach/Detach", [CM_EVENT_CHG_START_STOP] = "Charging Start/Stop", [CM_EVENT_OTHERS] = "Other battery events" @@ -540,6 +550,60 @@ static int check_charging_duration(struct charger_manager *cm) return ret; } +static int cm_get_battery_temperature(struct charger_manager *cm, + int *temp) +{ + int ret; + + if (!cm->desc->measure_battery_temp) + return -ENODEV; + +#ifdef CONFIG_THERMAL + ret = thermal_zone_get_temp(cm->tzd_batt, (unsigned long *)temp); + if (!ret) + /* Calibrate temperature unit */ + *temp /= 100; +#else + ret = cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_TEMP, + (union power_supply_propval *)temp); +#endif + return ret; +} + +static int cm_check_thermal_status(struct charger_manager *cm) +{ + struct charger_desc *desc = cm->desc; + int temp, upper_limit, lower_limit; + int ret = 0; + + ret = cm_get_battery_temperature(cm, &temp); + if (ret) { + /* FIXME: + * No information of battery temperature might + * occur hazadous result. We have to handle it + * depending on battery type. + */ + dev_err(cm->dev, "Failed to get battery temperature\n"); + return 0; + } + + upper_limit = desc->temp_max; + lower_limit = desc->temp_min; + + if (cm->emergency_stop) { + upper_limit -= desc->temp_diff; + lower_limit += desc->temp_diff; + } + + if (temp > upper_limit) + ret = CM_EVENT_BATT_OVERHEAT; + else if (temp < lower_limit) + ret = CM_EVENT_BATT_COLD; + + return ret; +} + /** * _cm_monitor - Monitor the temperature and return true for exceptions. * @cm: the Charger Manager representing the battery. @@ -549,28 +613,22 @@ static int check_charging_duration(struct charger_manager *cm) */ static bool _cm_monitor(struct charger_manager *cm) { - struct charger_desc *desc = cm->desc; - int temp = desc->temperature_out_of_range(&cm->last_temp_mC); + int temp_alrt; - dev_dbg(cm->dev, "monitoring (%2.2d.%3.3dC)\n", - cm->last_temp_mC / 1000, cm->last_temp_mC % 1000); + temp_alrt = cm_check_thermal_status(cm); /* It has been stopped already */ - if (temp && cm->emergency_stop) + if (temp_alrt && cm->emergency_stop) return false; /* * Check temperature whether overheat or cold. * If temperature is out of range normal state, stop charging. */ - if (temp) { - cm->emergency_stop = temp; - if (!try_charger_enable(cm, false)) { - if (temp > 0) - uevent_notify(cm, "OVERHEAT"); - else - uevent_notify(cm, "COLD"); - } + if (temp_alrt) { + cm->emergency_stop = temp_alrt; + if (!try_charger_enable(cm, false)) + uevent_notify(cm, default_event_names[temp_alrt]); /* * Check whole charging duration and discharing duration @@ -802,21 +860,8 @@ static int charger_get_property(struct power_supply *psy, POWER_SUPPLY_PROP_CURRENT_NOW, val); break; case POWER_SUPPLY_PROP_TEMP: - /* in thenth of centigrade */ - if (cm->last_temp_mC == INT_MIN) - desc->temperature_out_of_range(&cm->last_temp_mC); - val->intval = cm->last_temp_mC / 100; - if (!desc->measure_battery_temp) - ret = -ENODEV; - break; case POWER_SUPPLY_PROP_TEMP_AMBIENT: - /* in thenth of centigrade */ - if (cm->last_temp_mC == INT_MIN) - desc->temperature_out_of_range(&cm->last_temp_mC); - val->intval = cm->last_temp_mC / 100; - if (desc->measure_battery_temp) - ret = -ENODEV; - break; + return cm_get_battery_temperature(cm, &val->intval); case POWER_SUPPLY_PROP_CAPACITY: if (!cm->fuel_gauge) { ret = -ENODEV; @@ -1439,6 +1484,50 @@ err: return ret; } +static int cm_init_thermal_data(struct charger_manager *cm) +{ + struct charger_desc *desc = cm->desc; + union power_supply_propval val; + int ret; + + /* Verify whether fuel gauge provides battery temperature */ + ret = cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_TEMP, &val); + + if (!ret) { + cm->charger_psy.properties[cm->charger_psy.num_properties] = + POWER_SUPPLY_PROP_TEMP; + cm->charger_psy.num_properties++; + cm->desc->measure_battery_temp = true; + } +#ifdef CONFIG_THERMAL + cm->tzd_batt = cm->fuel_gauge->tzd; + + if (ret && desc->thermal_zone) { + cm->tzd_batt = + thermal_zone_get_zone_by_name(desc->thermal_zone); + if (IS_ERR(cm->tzd_batt)) + return PTR_ERR(cm->tzd_batt); + + /* Use external thermometer */ + cm->charger_psy.properties[cm->charger_psy.num_properties] = + POWER_SUPPLY_PROP_TEMP_AMBIENT; + cm->charger_psy.num_properties++; + cm->desc->measure_battery_temp = true; + ret = 0; + } +#endif + if (cm->desc->measure_battery_temp) { + /* NOTICE : Default allowable minimum charge temperature is 0 */ + if (!desc->temp_max) + desc->temp_max = CM_DEFAULT_CHARGE_TEMP_MAX; + if (!desc->temp_diff) + desc->temp_diff = CM_DEFAULT_RECHARGE_TEMP_DIFF; + } + + return ret; +} + static int charger_manager_probe(struct platform_device *pdev) { struct charger_desc *desc = dev_get_platdata(&pdev->dev); @@ -1470,7 +1559,6 @@ static int charger_manager_probe(struct platform_device *pdev) /* Basic Values. Unspecified are Null or 0 */ cm->dev = &pdev->dev; cm->desc = desc; - cm->last_temp_mC = INT_MIN; /* denotes "unmeasured, yet" */ /* * The following two do not need to be errors. @@ -1533,11 +1621,6 @@ static int charger_manager_probe(struct platform_device *pdev) return -EINVAL; } - if (!desc->temperature_out_of_range) { - dev_err(&pdev->dev, "there is no temperature_out_of_range\n"); - return -EINVAL; - } - if (!desc->charging_max_duration_ms || !desc->discharging_max_duration_ms) { dev_info(&pdev->dev, "Cannot limit charging duration checking mechanism to prevent overcharge/overheat and control discharging duration\n"); @@ -1583,14 +1666,10 @@ static int charger_manager_probe(struct platform_device *pdev) cm->charger_psy.num_properties++; } - if (desc->measure_battery_temp) { - cm->charger_psy.properties[cm->charger_psy.num_properties] = - POWER_SUPPLY_PROP_TEMP; - cm->charger_psy.num_properties++; - } else { - cm->charger_psy.properties[cm->charger_psy.num_properties] = - POWER_SUPPLY_PROP_TEMP_AMBIENT; - cm->charger_psy.num_properties++; + ret = cm_init_thermal_data(cm); + if (ret) { + dev_err(&pdev->dev, "Failed to initialize thermal data\n"); + cm->desc->measure_battery_temp = false; } INIT_DELAYED_WORK(&cm->fullbatt_vchk_work, fullbatt_vchk); -- cgit v1.2.3 From 856ee6115e2de98cb83389ce18116e6d5b90e817 Mon Sep 17 00:00:00 2001 From: Jonghwa Lee Date: Wed, 18 Dec 2013 15:42:35 +0900 Subject: charger-manager: Support deivce tree in charger manager driver Charger-manager can parse charger_desc data from devicetree which is used to register charger manager. Signed-off-by: Jonghwa Lee Signed-off-by: Myungjoo Ham Signed-off-by: Anton Vorontsov --- drivers/power/charger-manager.c | 138 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 135 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/power/charger-manager.c b/drivers/power/charger-manager.c index 6b68d158e3c1..ec5c140a52b8 100644 --- a/drivers/power/charger-manager.c +++ b/drivers/power/charger-manager.c @@ -25,6 +25,7 @@ #include #include #include +#include #include /* @@ -528,7 +529,7 @@ static int check_charging_duration(struct charger_manager *cm) duration = curr - cm->charging_start_time; if (duration > desc->charging_max_duration_ms) { - dev_info(cm->dev, "Charging duration exceed %lldms\n", + dev_info(cm->dev, "Charging duration exceed %ums\n", desc->charging_max_duration_ms); uevent_notify(cm, "Discharging"); try_charger_enable(cm, false); @@ -539,7 +540,7 @@ static int check_charging_duration(struct charger_manager *cm) if (duration > desc->charging_max_duration_ms && is_ext_pwr_online(cm)) { - dev_info(cm->dev, "Discharging duration exceed %lldms\n", + dev_info(cm->dev, "Discharging duration exceed %ums\n", desc->discharging_max_duration_ms); uevent_notify(cm, "Recharging"); try_charger_enable(cm, true); @@ -1528,9 +1529,139 @@ static int cm_init_thermal_data(struct charger_manager *cm) return ret; } +static struct of_device_id charger_manager_match[] = { + { + .compatible = "charger-manager", + }, + {}, +}; + +struct charger_desc *of_cm_parse_desc(struct device *dev) +{ + struct charger_desc *desc; + struct device_node *np = dev->of_node; + u32 poll_mode = CM_POLL_DISABLE; + u32 battery_stat = CM_NO_BATTERY; + int num_chgs = 0; + + desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); + if (!desc) + return ERR_PTR(-ENOMEM); + + of_property_read_string(np, "cm-name", &desc->psy_name); + + of_property_read_u32(np, "cm-poll-mode", &poll_mode); + desc->polling_mode = poll_mode; + + of_property_read_u32(np, "cm-poll-interval", + &desc->polling_interval_ms); + + of_property_read_u32(np, "cm-fullbatt-vchkdrop-ms", + &desc->fullbatt_vchkdrop_ms); + of_property_read_u32(np, "cm-fullbatt-vchkdrop-volt", + &desc->fullbatt_vchkdrop_uV); + of_property_read_u32(np, "cm-fullbatt-voltage", &desc->fullbatt_uV); + of_property_read_u32(np, "cm-fullbatt-soc", &desc->fullbatt_soc); + of_property_read_u32(np, "cm-fullbatt-capacity", + &desc->fullbatt_full_capacity); + + of_property_read_u32(np, "cm-battery-stat", &battery_stat); + desc->battery_present = battery_stat; + + /* chargers */ + of_property_read_u32(np, "cm-num-chargers", &num_chgs); + if (num_chgs) { + /* Allocate empty bin at the tail of array */ + desc->psy_charger_stat = devm_kzalloc(dev, sizeof(char *) + * (num_chgs + 1), GFP_KERNEL); + if (desc->psy_charger_stat) { + int i; + for (i = 0; i < num_chgs; i++) + of_property_read_string_index(np, "cm-chargers", + i, &desc->psy_charger_stat[i]); + } else { + return ERR_PTR(-ENOMEM); + } + } + + of_property_read_string(np, "cm-fuel-gauge", &desc->psy_fuel_gauge); + + of_property_read_string(np, "cm-thermal-zone", &desc->thermal_zone); + + of_property_read_u32(np, "cm-battery-cold", &desc->temp_min); + if (of_get_property(np, "cm-battery-cold-in-minus", NULL)) + desc->temp_min *= -1; + of_property_read_u32(np, "cm-battery-hot", &desc->temp_max); + of_property_read_u32(np, "cm-battery-temp-diff", &desc->temp_diff); + + of_property_read_u32(np, "cm-charging-max", + &desc->charging_max_duration_ms); + of_property_read_u32(np, "cm-discharging-max", + &desc->discharging_max_duration_ms); + + /* battery charger regualtors */ + desc->num_charger_regulators = of_get_child_count(np); + if (desc->num_charger_regulators) { + struct charger_regulator *chg_regs; + struct device_node *child; + + chg_regs = devm_kzalloc(dev, sizeof(*chg_regs) + * desc->num_charger_regulators, + GFP_KERNEL); + if (!chg_regs) + return ERR_PTR(-ENOMEM); + + desc->charger_regulators = chg_regs; + + for_each_child_of_node(np, child) { + struct charger_cable *cables; + struct device_node *_child; + + of_property_read_string(child, "cm-regulator-name", + &chg_regs->regulator_name); + + /* charger cables */ + chg_regs->num_cables = of_get_child_count(child); + if (chg_regs->num_cables) { + cables = devm_kzalloc(dev, sizeof(*cables) + * chg_regs->num_cables, + GFP_KERNEL); + if (!cables) + return ERR_PTR(-ENOMEM); + + chg_regs->cables = cables; + + for_each_child_of_node(child, _child) { + of_property_read_string(_child, + "cm-cable-name", &cables->name); + of_property_read_string(_child, + "cm-cable-extcon", + &cables->extcon_name); + of_property_read_u32(_child, + "cm-cable-min", + &cables->min_uA); + of_property_read_u32(_child, + "cm-cable-max", + &cables->max_uA); + cables++; + } + } + chg_regs++; + } + } + return desc; +} + +static inline struct charger_desc *cm_get_drv_data(struct platform_device *pdev) +{ + if (pdev->dev.of_node) + return of_cm_parse_desc(&pdev->dev); + return (struct charger_desc *)dev_get_platdata(&pdev->dev); +} + static int charger_manager_probe(struct platform_device *pdev) { - struct charger_desc *desc = dev_get_platdata(&pdev->dev); + struct charger_desc *desc = cm_get_drv_data(pdev); struct charger_manager *cm; int ret = 0, i = 0; int j = 0; @@ -1887,6 +2018,7 @@ static struct platform_driver charger_manager_driver = { .name = "charger-manager", .owner = THIS_MODULE, .pm = &charger_manager_pm, + .of_match_table = charger_manager_match, }, .probe = charger_manager_probe, .remove = charger_manager_remove, -- cgit v1.2.3 From 56fb8de53e20a853e1a919bf9fb25148ef7560cb Mon Sep 17 00:00:00 2001 From: Laxman Dewangan Date: Fri, 20 Dec 2013 18:54:00 +0530 Subject: power: reset: Add as3722 power-off driver ams AS3722 supports the power off functionality to turn off system. This commit adds power off driver for ams AS3722. Signed-off-by: Laxman Dewangan Tested-by: Stephen Warren Signed-off-by: Anton Vorontsov --- drivers/power/reset/Kconfig | 6 +++ drivers/power/reset/Makefile | 1 + drivers/power/reset/as3722-poweroff.c | 96 +++++++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+) create mode 100644 drivers/power/reset/as3722-poweroff.c (limited to 'drivers') diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig index 9b3ea535b472..6d452a78b19c 100644 --- a/drivers/power/reset/Kconfig +++ b/drivers/power/reset/Kconfig @@ -6,6 +6,12 @@ menuconfig POWER_RESET Say Y here to enable board reset and power off +config POWER_RESET_AS3722 + bool "ams AS3722 power-off driver" + depends on MFD_AS3722 && POWER_RESET + help + This driver supports turning off board via a ams AS3722 power-off. + config POWER_RESET_GPIO bool "GPIO power-off driver" depends on OF_GPIO && POWER_RESET diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile index 3e6ed88725ac..a5b4a77d1a41 100644 --- a/drivers/power/reset/Makefile +++ b/drivers/power/reset/Makefile @@ -1,3 +1,4 @@ +obj-$(CONFIG_POWER_RESET_AS3722) += as3722-poweroff.o obj-$(CONFIG_POWER_RESET_GPIO) += gpio-poweroff.o obj-$(CONFIG_POWER_RESET_MSM) += msm-poweroff.o obj-$(CONFIG_POWER_RESET_QNAP) += qnap-poweroff.o diff --git a/drivers/power/reset/as3722-poweroff.c b/drivers/power/reset/as3722-poweroff.c new file mode 100644 index 000000000000..684971199bd3 --- /dev/null +++ b/drivers/power/reset/as3722-poweroff.c @@ -0,0 +1,96 @@ +/* + * Power off driver for ams AS3722 device. + * + * Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved. + * + * Author: Laxman Dewangan + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#include +#include +#include +#include +#include +#include + +struct as3722_poweroff { + struct device *dev; + struct as3722 *as3722; +}; + +static struct as3722_poweroff *as3722_pm_poweroff; + +static void as3722_pm_power_off(void) +{ + int ret; + + if (!as3722_pm_poweroff) { + pr_err("AS3722 poweroff is not initialised\n"); + return; + } + + ret = as3722_update_bits(as3722_pm_poweroff->as3722, + AS3722_RESET_CONTROL_REG, AS3722_POWER_OFF, AS3722_POWER_OFF); + if (ret < 0) + dev_err(as3722_pm_poweroff->dev, + "RESET_CONTROL_REG update failed, %d\n", ret); +} + +static int as3722_poweroff_probe(struct platform_device *pdev) +{ + struct as3722_poweroff *as3722_poweroff; + struct device_node *np = pdev->dev.parent->of_node; + + if (!np) + return -EINVAL; + + if (!of_property_read_bool(np, "ams,system-power-controller")) + return 0; + + as3722_poweroff = devm_kzalloc(&pdev->dev, sizeof(*as3722_poweroff), + GFP_KERNEL); + if (!as3722_poweroff) + return -ENOMEM; + + as3722_poweroff->as3722 = dev_get_drvdata(pdev->dev.parent); + as3722_poweroff->dev = &pdev->dev; + as3722_pm_poweroff = as3722_poweroff; + if (!pm_power_off) + pm_power_off = as3722_pm_power_off; + + return 0; +} + +static int as3722_poweroff_remove(struct platform_device *pdev) +{ + if (pm_power_off == as3722_pm_power_off) + pm_power_off = NULL; + as3722_pm_poweroff = NULL; + + return 0; +} + +static struct platform_driver as3722_poweroff_driver = { + .driver = { + .name = "as3722-power-off", + .owner = THIS_MODULE, + }, + .probe = as3722_poweroff_probe, + .remove = as3722_poweroff_remove, +}; + +module_platform_driver(as3722_poweroff_driver); + +MODULE_DESCRIPTION("Power off driver for ams AS3722 PMIC Device"); +MODULE_ALIAS("platform:as3722-power-off"); +MODULE_AUTHOR("Laxman Dewangan "); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 32260308b4cafcd71b1c0aac35675dc68120c33d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pali=20Roh=C3=A1r?= Date: Tue, 19 Nov 2013 11:18:04 +0100 Subject: bq2415x_charger: Use power_supply notifier for automode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch removing set_mode_hook function from board data and replacing it with new string variable of notifier power supply device. After this change it is possible to add DT support because driver does not need specific board function anymore. Only static data and name of power supply device is required. Signed-off-by: Pali Rohár Reviewed-by: Pavel Machek Signed-off-by: Anton Vorontsov --- drivers/power/bq2415x_charger.c | 77 ++++++++++++++++++++++++++++++++--------- 1 file changed, 60 insertions(+), 17 deletions(-) (limited to 'drivers') diff --git a/drivers/power/bq2415x_charger.c b/drivers/power/bq2415x_charger.c index df893dd1447d..0bece7026056 100644 --- a/drivers/power/bq2415x_charger.c +++ b/drivers/power/bq2415x_charger.c @@ -1,7 +1,7 @@ /* * bq2415x charger driver * - * Copyright (C) 2011-2012 Pali Rohár + * Copyright (C) 2011-2013 Pali Rohár * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -170,6 +170,7 @@ struct bq2415x_device { struct bq2415x_platform_data init_data; struct power_supply charger; struct delayed_work work; + struct notifier_block nb; enum bq2415x_mode reported_mode;/* mode reported by hook function */ enum bq2415x_mode mode; /* current configured mode */ enum bq2415x_chip chip; @@ -795,24 +796,53 @@ static int bq2415x_set_mode(struct bq2415x_device *bq, enum bq2415x_mode mode) } -/* hook function called by other driver which set reported mode */ -static void bq2415x_hook_function(enum bq2415x_mode mode, void *data) +static int bq2415x_notifier_call(struct notifier_block *nb, + unsigned long val, void *v) { - struct bq2415x_device *bq = data; + struct bq2415x_device *bq = + container_of(nb, struct bq2415x_device, nb); + struct power_supply *psy = v; + enum bq2415x_mode mode; + union power_supply_propval prop; + int ret; + int mA; - if (!bq) - return; + if (val != PSY_EVENT_PROP_CHANGED) + return NOTIFY_OK; + + if (strcmp(psy->name, bq->init_data.notify_device) != 0) + return NOTIFY_OK; + + dev_dbg(bq->dev, "notifier call was called\n"); + + ret = psy->get_property(psy, POWER_SUPPLY_PROP_CURRENT_MAX, &prop); + if (ret != 0) + return NOTIFY_OK; + + mA = prop.intval; + + if (mA == 0) + mode = BQ2415X_MODE_OFF; + else if (mA < 500) + mode = BQ2415X_MODE_NONE; + else if (mA < 1800) + mode = BQ2415X_MODE_HOST_CHARGER; + else + mode = BQ2415X_MODE_DEDICATED_CHARGER; + + if (bq->reported_mode == mode) + return NOTIFY_OK; - dev_dbg(bq->dev, "hook function was called\n"); bq->reported_mode = mode; /* if automode is not enabled do not tell about reported_mode */ if (bq->automode < 1) - return; + return NOTIFY_OK; sysfs_notify(&bq->charger.dev->kobj, NULL, "reported_mode"); bq2415x_set_mode(bq, bq->reported_mode); + return NOTIFY_OK; } /**** timer functions ****/ @@ -1512,6 +1542,7 @@ static int bq2415x_probe(struct i2c_client *client, int num; char *name; struct bq2415x_device *bq; + struct power_supply *psy; if (!client->dev.platform_data) { dev_err(&client->dev, "platform data not set\n"); @@ -1573,16 +1604,27 @@ static int bq2415x_probe(struct i2c_client *client, goto error_4; } - if (bq->init_data.set_mode_hook) { - if (bq->init_data.set_mode_hook( - bq2415x_hook_function, bq)) { - bq->automode = 1; + if (bq->init_data.notify_device) { + bq->nb.notifier_call = bq2415x_notifier_call; + ret = power_supply_reg_notifier(&bq->nb); + if (ret) { + dev_err(bq->dev, "failed to reg notifier: %d\n", ret); + goto error_5; + } + psy = power_supply_get_by_name(bq->init_data.notify_device); + if (psy) { + /* Query for initial reported_mode and set it */ + bq2415x_notifier_call(&bq->nb, + PSY_EVENT_PROP_CHANGED, psy); bq2415x_set_mode(bq, bq->reported_mode); - dev_info(bq->dev, "automode enabled\n"); } else { - bq->automode = -1; - dev_info(bq->dev, "automode failed\n"); + dev_info(bq->dev, "notifier power supply device (%s) " + "for automode is not registred yet... " + "automode will not work without that device\n", + bq->init_data.notify_device); } + bq->automode = 1; + dev_info(bq->dev, "automode enabled\n"); } else { bq->automode = -1; dev_info(bq->dev, "automode not supported\n"); @@ -1594,6 +1636,7 @@ static int bq2415x_probe(struct i2c_client *client, dev_info(bq->dev, "driver registered\n"); return 0; +error_5: error_4: bq2415x_sysfs_exit(bq); error_3: @@ -1614,8 +1657,8 @@ static int bq2415x_remove(struct i2c_client *client) { struct bq2415x_device *bq = i2c_get_clientdata(client); - if (bq->init_data.set_mode_hook) - bq->init_data.set_mode_hook(NULL, NULL); + if (bq->init_data.notify_device) + power_supply_unreg_notifier(&bq->nb); bq2415x_sysfs_exit(bq); bq2415x_power_supply_exit(bq); -- cgit v1.2.3 From abce97708a9b5ba897ad94fa289804d8af8d3ea9 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Sun, 24 Nov 2013 17:49:29 +0100 Subject: power_supply: Add power_supply_get_by_phandle Add method to get power supply by device tree phandle. Signed-off-by: Sebastian Reichel Reviewed-by: Pavel Machek Signed-off-by: Anton Vorontsov --- drivers/power/power_supply_core.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 'drivers') diff --git a/drivers/power/power_supply_core.c b/drivers/power/power_supply_core.c index 6aac23cc6566..26606641fe44 100644 --- a/drivers/power/power_supply_core.c +++ b/drivers/power/power_supply_core.c @@ -341,6 +341,32 @@ struct power_supply *power_supply_get_by_name(const char *name) } EXPORT_SYMBOL_GPL(power_supply_get_by_name); +#ifdef CONFIG_OF +static int power_supply_match_device_node(struct device *dev, const void *data) +{ + return dev->parent && dev->parent->of_node == data; +} + +struct power_supply *power_supply_get_by_phandle(struct device_node *np, + const char *property) +{ + struct device_node *power_supply_np; + struct device *dev; + + power_supply_np = of_parse_phandle(np, property, 0); + if (!power_supply_np) + return ERR_PTR(-ENODEV); + + dev = class_find_device(power_supply_class, NULL, power_supply_np, + power_supply_match_device_node); + + of_node_put(power_supply_np); + + return dev ? dev_get_drvdata(dev) : NULL; +} +EXPORT_SYMBOL_GPL(power_supply_get_by_phandle); +#endif /* CONFIG_OF */ + int power_supply_powers(struct power_supply *psy, struct device *dev) { return sysfs_create_link(&psy->dev->kobj, &dev->kobj, "powers"); -- cgit v1.2.3 From faffd234cf85ddfe3959055bea0af2d21bc85f63 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Sun, 24 Nov 2013 17:49:30 +0100 Subject: bq2415x_charger: Add DT support This adds DT support to the bq2415x driver. Signed-off-by: Sebastian Reichel Reviewed-by: Pavel Machek Signed-off-by: Anton Vorontsov --- drivers/power/bq2415x_charger.c | 72 +++++++++++++++++++++++++++++------------ 1 file changed, 52 insertions(+), 20 deletions(-) (limited to 'drivers') diff --git a/drivers/power/bq2415x_charger.c b/drivers/power/bq2415x_charger.c index 0bece7026056..79a37f6d3307 100644 --- a/drivers/power/bq2415x_charger.c +++ b/drivers/power/bq2415x_charger.c @@ -170,6 +170,7 @@ struct bq2415x_device { struct bq2415x_platform_data init_data; struct power_supply charger; struct delayed_work work; + struct power_supply *notify_psy; struct notifier_block nb; enum bq2415x_mode reported_mode;/* mode reported by hook function */ enum bq2415x_mode mode; /* current configured mode */ @@ -810,7 +811,7 @@ static int bq2415x_notifier_call(struct notifier_block *nb, if (val != PSY_EVENT_PROP_CHANGED) return NOTIFY_OK; - if (strcmp(psy->name, bq->init_data.notify_device) != 0) + if (psy != bq->notify_psy) return NOTIFY_OK; dev_dbg(bq->dev, "notifier call was called\n"); @@ -1542,10 +1543,11 @@ static int bq2415x_probe(struct i2c_client *client, int num; char *name; struct bq2415x_device *bq; - struct power_supply *psy; + struct device_node *np = client->dev.of_node; + struct bq2415x_platform_data *pdata = client->dev.platform_data; - if (!client->dev.platform_data) { - dev_err(&client->dev, "platform data not set\n"); + if (!np && !pdata) { + dev_err(&client->dev, "platform data missing\n"); return -ENODEV; } @@ -1570,6 +1572,17 @@ static int bq2415x_probe(struct i2c_client *client, goto error_2; } + if (np) { + bq->notify_psy = power_supply_get_by_phandle(np, "ti,usb-charger-detection"); + + if (!bq->notify_psy) + return -EPROBE_DEFER; + } + else if (pdata->notify_device) + bq->notify_psy = power_supply_get_by_name(pdata->notify_device); + else + bq->notify_psy = NULL; + i2c_set_clientdata(client, bq); bq->id = num; @@ -1581,8 +1594,34 @@ static int bq2415x_probe(struct i2c_client *client, bq->autotimer = 0; bq->automode = 0; - memcpy(&bq->init_data, client->dev.platform_data, - sizeof(bq->init_data)); + if (np) { + ret = of_property_read_u32(np, "ti,current-limit", + &bq->init_data.current_limit); + if (ret) + return ret; + ret = of_property_read_u32(np, "ti,weak-battery-voltage", + &bq->init_data.weak_battery_voltage); + if (ret) + return ret; + ret = of_property_read_u32(np, "ti,battery-regulation-voltage", + &bq->init_data.battery_regulation_voltage); + if (ret) + return ret; + ret = of_property_read_u32(np, "ti,charge-current", + &bq->init_data.charge_current); + if (ret) + return ret; + ret = of_property_read_u32(np, "ti,termination-current", + &bq->init_data.termination_current); + if (ret) + return ret; + ret = of_property_read_u32(np, "ti,resistor-sense", + &bq->init_data.resistor_sense); + if (ret) + return ret; + } else { + memcpy(&bq->init_data, pdata, sizeof(bq->init_data)); + } bq2415x_reset_chip(bq); @@ -1604,25 +1643,18 @@ static int bq2415x_probe(struct i2c_client *client, goto error_4; } - if (bq->init_data.notify_device) { + if (bq->notify_psy) { bq->nb.notifier_call = bq2415x_notifier_call; ret = power_supply_reg_notifier(&bq->nb); if (ret) { dev_err(bq->dev, "failed to reg notifier: %d\n", ret); goto error_5; } - psy = power_supply_get_by_name(bq->init_data.notify_device); - if (psy) { - /* Query for initial reported_mode and set it */ - bq2415x_notifier_call(&bq->nb, - PSY_EVENT_PROP_CHANGED, psy); - bq2415x_set_mode(bq, bq->reported_mode); - } else { - dev_info(bq->dev, "notifier power supply device (%s) " - "for automode is not registred yet... " - "automode will not work without that device\n", - bq->init_data.notify_device); - } + + /* Query for initial reported_mode and set it */ + bq2415x_notifier_call(&bq->nb, PSY_EVENT_PROP_CHANGED, bq->notify_psy); + bq2415x_set_mode(bq, bq->reported_mode); + bq->automode = 1; dev_info(bq->dev, "automode enabled\n"); } else { @@ -1657,7 +1689,7 @@ static int bq2415x_remove(struct i2c_client *client) { struct bq2415x_device *bq = i2c_get_clientdata(client); - if (bq->init_data.notify_device) + if (bq->notify_psy) power_supply_unreg_notifier(&bq->nb); bq2415x_sysfs_exit(bq); -- cgit v1.2.3 From 434a09f9c43ae5136774fb632440beb4f0081719 Mon Sep 17 00:00:00 2001 From: Anton Vorontsov Date: Mon, 23 Dec 2013 18:17:05 -0800 Subject: charger-manager: of_cm_parse_desc() should be static Fixes the following warning: drivers/power/charger-manager.c:1539:21: warning: symbol 'of_cm_parse_desc' was not declared. Should it be static? Signed-off-by: Anton Vorontsov --- drivers/power/charger-manager.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/power/charger-manager.c b/drivers/power/charger-manager.c index ec5c140a52b8..9e4dab46eefd 100644 --- a/drivers/power/charger-manager.c +++ b/drivers/power/charger-manager.c @@ -1536,7 +1536,7 @@ static struct of_device_id charger_manager_match[] = { {}, }; -struct charger_desc *of_cm_parse_desc(struct device *dev) +static struct charger_desc *of_cm_parse_desc(struct device *dev) { struct charger_desc *desc; struct device_node *np = dev->of_node; -- cgit v1.2.3 From 34a109610e2a78b56be22fa314054c09669075eb Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Sun, 1 Dec 2013 02:00:10 +0100 Subject: isp1704_charger: Add DT support This patch introduces device tree support to the isp1704 charger driver. Adding support involved moving the handling of the enable GPIO from board code into the driver. Signed-off-by: Sebastian Reichel Signed-off-by: Anton Vorontsov --- drivers/power/isp1704_charger.c | 54 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/power/isp1704_charger.c b/drivers/power/isp1704_charger.c index 1bb3a91b1acc..80edb7d8cb54 100644 --- a/drivers/power/isp1704_charger.c +++ b/drivers/power/isp1704_charger.c @@ -29,6 +29,8 @@ #include #include #include +#include +#include #include #include @@ -88,6 +90,8 @@ static void isp1704_charger_set_power(struct isp1704_charger *isp, bool on) if (board && board->set_power) board->set_power(on); + else if (board) + gpio_set_value(board->enable_gpio, on); } /* @@ -400,12 +404,47 @@ static int isp1704_charger_probe(struct platform_device *pdev) struct isp1704_charger *isp; int ret = -ENODEV; + struct isp1704_charger_data *pdata = dev_get_platdata(&pdev->dev); + struct device_node *np = pdev->dev.of_node; + + if (np) { + int gpio = of_get_named_gpio(np, "nxp,enable-gpio", 0); + + if (gpio < 0) + return gpio; + + pdata = devm_kzalloc(&pdev->dev, + sizeof(struct isp1704_charger_data), GFP_KERNEL); + pdata->enable_gpio = gpio; + + dev_info(&pdev->dev, "init gpio %d\n", pdata->enable_gpio); + + ret = devm_gpio_request_one(&pdev->dev, pdata->enable_gpio, + GPIOF_OUT_INIT_HIGH, "isp1704_reset"); + if (ret) + goto fail0; + } + + if (!pdata) { + dev_err(&pdev->dev, "missing platform data!\n"); + return -ENODEV; + } + + isp = devm_kzalloc(&pdev->dev, sizeof(*isp), GFP_KERNEL); if (!isp) return -ENOMEM; - isp->phy = usb_get_phy(USB_PHY_TYPE_USB2); - if (IS_ERR_OR_NULL(isp->phy)) + if (np) + isp->phy = devm_usb_get_phy_by_phandle(&pdev->dev, "usb-phy", 0); + else + isp->phy = devm_usb_get_phy(&pdev->dev, USB_PHY_TYPE_USB2); + + if (IS_ERR(isp->phy)) { + ret = PTR_ERR(isp->phy); + goto fail0; + } + if (!isp->phy) goto fail0; isp->dev = &pdev->dev; @@ -464,7 +503,6 @@ fail2: power_supply_unregister(&isp->psy); fail1: isp1704_charger_set_power(isp, 0); - usb_put_phy(isp->phy); fail0: dev_err(&pdev->dev, "failed to register isp1704 with error %d\n", ret); @@ -477,15 +515,23 @@ static int isp1704_charger_remove(struct platform_device *pdev) usb_unregister_notifier(isp->phy, &isp->nb); power_supply_unregister(&isp->psy); - usb_put_phy(isp->phy); isp1704_charger_set_power(isp, 0); return 0; } +#ifdef CONFIG_OF +static const struct of_device_id omap_isp1704_of_match[] = { + { .compatible = "nxp,isp1704", }, + {}, +}; +MODULE_DEVICE_TABLE(of, omap_isp1704_of_match); +#endif + static struct platform_driver isp1704_charger_driver = { .driver = { .name = "isp1704_charger", + .of_match_table = of_match_ptr(omap_isp1704_of_match), }, .probe = isp1704_charger_probe, .remove = isp1704_charger_remove, -- cgit v1.2.3 From 55e6470120b2cb94700e2cb97077dfe9a5744244 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 6 Dec 2013 12:32:12 +0100 Subject: power_supply: Add charger support for Maxim 14577 MAX14577 chip is a multi-function device which includes MUIC, charger and voltage regulator. The driver is located in drivers/mfd. This patch supports battery charging control of MAX14577 chip and provides power supply class information to userspace. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Kyungmin Park Signed-off-by: Anton Vorontsov --- drivers/power/Kconfig | 7 + drivers/power/Makefile | 1 + drivers/power/max14577_charger.c | 311 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 319 insertions(+) create mode 100644 drivers/power/max14577_charger.c (limited to 'drivers') diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 85ad58c6da17..0196acf05231 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -317,6 +317,13 @@ config CHARGER_MANAGER runtime and in suspend-to-RAM by waking up the system periodically with help of suspend_again support. +config CHARGER_MAX14577 + tristate "Maxim MAX14577 MUIC battery charger driver" + depends on MFD_MAX14577 + help + Say Y to enable support for the battery charger control sysfs and + platform data of MAX14577 MUICs. + config CHARGER_MAX8997 tristate "Maxim MAX8997/MAX8966 PMIC battery charger driver" depends on MFD_MAX8997 && REGULATOR_MAX8997 diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 372b4e8ab598..ee54a3e4c90a 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -48,6 +48,7 @@ obj-$(CONFIG_CHARGER_LP8727) += lp8727_charger.o obj-$(CONFIG_CHARGER_LP8788) += lp8788-charger.o obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o obj-$(CONFIG_CHARGER_MANAGER) += charger-manager.o +obj-$(CONFIG_CHARGER_MAX14577) += max14577_charger.o obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o obj-$(CONFIG_CHARGER_BQ2415X) += bq2415x_charger.o diff --git a/drivers/power/max14577_charger.c b/drivers/power/max14577_charger.c new file mode 100644 index 000000000000..fad2a75b3604 --- /dev/null +++ b/drivers/power/max14577_charger.c @@ -0,0 +1,311 @@ +/* + * Battery charger driver for the Maxim 14577 + * + * Copyright (C) 2013 Samsung Electronics + * Krzysztof Kozlowski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include + +struct max14577_charger { + struct device *dev; + struct max14577 *max14577; + struct power_supply charger; + + unsigned int charging_state; + unsigned int battery_state; +}; + +static int max14577_get_charger_state(struct max14577_charger *chg) +{ + struct regmap *rmap = chg->max14577->regmap; + int state = POWER_SUPPLY_STATUS_DISCHARGING; + u8 reg_data; + + /* + * Charging occurs only if: + * - CHGCTRL2/MBCHOSTEN == 1 + * - STATUS2/CGMBC == 1 + * + * TODO: + * - handle FULL after Top-off timer (EOC register may be off + * and the charger won't be charging although MBCHOSTEN is on) + * - handle properly dead-battery charging (respect timer) + * - handle timers (fast-charge and prequal) /MBCCHGERR/ + */ + max14577_read_reg(rmap, MAX14577_CHG_REG_CHG_CTRL2, ®_data); + if ((reg_data & CHGCTRL2_MBCHOSTEN_MASK) == 0) + goto state_set; + + max14577_read_reg(rmap, MAX14577_CHG_REG_STATUS3, ®_data); + if (reg_data & STATUS3_CGMBC_MASK) { + /* Charger or USB-cable is connected */ + if (reg_data & STATUS3_EOC_MASK) + state = POWER_SUPPLY_STATUS_FULL; + else + state = POWER_SUPPLY_STATUS_CHARGING; + goto state_set; + } + +state_set: + chg->charging_state = state; + return state; +} + +/* + * Supported charge types: + * - POWER_SUPPLY_CHARGE_TYPE_NONE + * - POWER_SUPPLY_CHARGE_TYPE_FAST + */ +static int max14577_get_charge_type(struct max14577_charger *chg) +{ + /* + * TODO: CHARGE_TYPE_TRICKLE (VCHGR_RC or EOC)? + * As spec says: + * [after reaching EOC interrupt] + * "When the battery is fully charged, the 30-minute (typ) + * top-off timer starts. The device continues to trickle + * charge the battery until the top-off timer runs out." + */ + if (max14577_get_charger_state(chg) == POWER_SUPPLY_STATUS_CHARGING) + return POWER_SUPPLY_CHARGE_TYPE_FAST; + return POWER_SUPPLY_CHARGE_TYPE_NONE; +} + +static int max14577_get_online(struct max14577_charger *chg) +{ + struct regmap *rmap = chg->max14577->regmap; + u8 reg_data; + + max14577_read_reg(rmap, MAX14577_MUIC_REG_STATUS2, ®_data); + reg_data = ((reg_data & STATUS2_CHGTYP_MASK) >> STATUS2_CHGTYP_SHIFT); + switch (reg_data) { + case MAX14577_CHARGER_TYPE_USB: + case MAX14577_CHARGER_TYPE_DEDICATED_CHG: + case MAX14577_CHARGER_TYPE_SPECIAL_500MA: + case MAX14577_CHARGER_TYPE_SPECIAL_1A: + case MAX14577_CHARGER_TYPE_DEAD_BATTERY: + return 1; + case MAX14577_CHARGER_TYPE_NONE: + case MAX14577_CHARGER_TYPE_DOWNSTREAM_PORT: + case MAX14577_CHARGER_TYPE_RESERVED: + default: + return 0; + } +} + +/* + * Supported health statuses: + * - POWER_SUPPLY_HEALTH_DEAD + * - POWER_SUPPLY_HEALTH_OVERVOLTAGE + * - POWER_SUPPLY_HEALTH_GOOD + */ +static int max14577_get_battery_health(struct max14577_charger *chg) +{ + struct regmap *rmap = chg->max14577->regmap; + int state = POWER_SUPPLY_HEALTH_GOOD; + u8 reg_data; + + max14577_read_reg(rmap, MAX14577_MUIC_REG_STATUS2, ®_data); + reg_data = ((reg_data & STATUS2_CHGTYP_MASK) >> STATUS2_CHGTYP_SHIFT); + if (reg_data == MAX14577_CHARGER_TYPE_DEAD_BATTERY) { + state = POWER_SUPPLY_HEALTH_DEAD; + goto state_set; + } + + max14577_read_reg(rmap, MAX14577_CHG_REG_STATUS3, ®_data); + if (reg_data & STATUS3_OVP_MASK) { + state = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + goto state_set; + } + +state_set: + chg->battery_state = state; + return state; +} + +/* + * Always returns 1. + * The max14577 chip doesn't report any status of battery presence. + * Lets assume that it will always be used with some battery. + */ +static int max14577_get_present(struct max14577_charger *chg) +{ + return 1; +} + +/* + * Sets charger registers to proper and safe default values. + * Some of these values are equal to defaults in MAX14577E + * data sheet but there are minor differences. + */ +static void max14577_charger_reg_init(struct max14577_charger *chg) +{ + struct regmap *rmap = chg->max14577->regmap; + u8 reg_data; + + /* + * Charger-Type Manual Detection, default off (set CHGTYPMAN to 0) + * Charger-Detection Enable, default on (set CHGDETEN to 1) + * Combined mask of CHGDETEN and CHGTYPMAN will zero the CHGTYPMAN bit + */ + reg_data = 0x1 << CDETCTRL1_CHGDETEN_SHIFT; + max14577_update_reg(rmap, MAX14577_REG_CDETCTRL1, + CDETCTRL1_CHGDETEN_MASK | CDETCTRL1_CHGTYPMAN_MASK, + reg_data); + + /* Battery Fast-Charge Timer, from SM-V700: 6hrs */ + reg_data = 0x3 << CHGCTRL1_TCHW_SHIFT; + max14577_write_reg(rmap, MAX14577_REG_CHGCTRL1, reg_data); + + /* + * Wall-Adapter Rapid Charge, default on + * Battery-Charger, default on + */ + reg_data = 0x1 << CHGCTRL2_VCHGR_RC_SHIFT; + reg_data |= 0x1 << CHGCTRL2_MBCHOSTEN_SHIFT; + max14577_write_reg(rmap, MAX14577_REG_CHGCTRL2, reg_data); + + /* Battery-Charger Constant Voltage (CV) Mode, from SM-V700: 4.35V */ + reg_data = 0xf << CHGCTRL3_MBCCVWRC_SHIFT; + max14577_write_reg(rmap, MAX14577_REG_CHGCTRL3, reg_data); + + /* + * Fast Battery-Charge Current Low, default 200-950mA + * Fast Battery-Charge Current High, from SM-V700: 450mA + */ + reg_data = 0x1 << CHGCTRL4_MBCICHWRCL_SHIFT; + reg_data |= 0x5 << CHGCTRL4_MBCICHWRCH_SHIFT; + max14577_write_reg(rmap, MAX14577_REG_CHGCTRL4, reg_data); + + /* End-of-Charge Current, from SM-V700: 50mA */ + reg_data = 0x0 << CHGCTRL5_EOCS_SHIFT; + max14577_write_reg(rmap, MAX14577_REG_CHGCTRL5, reg_data); + + /* Auto Charging Stop, default off */ + reg_data = 0x0 << CHGCTRL6_AUTOSTOP_SHIFT; + max14577_write_reg(rmap, MAX14577_REG_CHGCTRL6, reg_data); + + /* Overvoltage-Protection Threshold, from SM-V700: 6.5V */ + reg_data = 0x2 << CHGCTRL7_OTPCGHCVS_SHIFT; + max14577_write_reg(rmap, MAX14577_REG_CHGCTRL7, reg_data); +} + +/* Support property from charger */ +static enum power_supply_property max14577_charger_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static const char *model_name = "MAX14577"; +static const char *manufacturer = "Maxim Integrated"; + +static int max14577_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max14577_charger *chg = container_of(psy, + struct max14577_charger, + charger); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = max14577_get_charger_state(chg); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = max14577_get_charge_type(chg); + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = max14577_get_battery_health(chg); + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = max14577_get_present(chg); + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = max14577_get_online(chg); + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = model_name; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = manufacturer; + break; + default: + return -EINVAL; + } + + return ret; +} + +static int max14577_charger_probe(struct platform_device *pdev) +{ + struct max14577_charger *chg; + struct max14577 *max14577 = dev_get_drvdata(pdev->dev.parent); + int ret; + + chg = devm_kzalloc(&pdev->dev, sizeof(*chg), GFP_KERNEL); + if (!chg) + return -ENOMEM; + + platform_set_drvdata(pdev, chg); + chg->dev = &pdev->dev; + chg->max14577 = max14577; + + max14577_charger_reg_init(chg); + + chg->charger.name = "max14577-charger", + chg->charger.type = POWER_SUPPLY_TYPE_BATTERY, + chg->charger.properties = max14577_charger_props, + chg->charger.num_properties = ARRAY_SIZE(max14577_charger_props), + chg->charger.get_property = max14577_charger_get_property, + + ret = power_supply_register(&pdev->dev, &chg->charger); + if (ret) { + dev_err(&pdev->dev, "failed: power supply register\n"); + return ret; + } + + return 0; +} + +static int max14577_charger_remove(struct platform_device *pdev) +{ + struct max14577_charger *chg = platform_get_drvdata(pdev); + + power_supply_unregister(&chg->charger); + + return 0; +} + +static struct platform_driver max14577_charger_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "max14577-charger", + }, + .probe = max14577_charger_probe, + .remove = max14577_charger_remove, +}; +module_platform_driver(max14577_charger_driver); + +MODULE_AUTHOR("Krzysztof Kozlowski "); +MODULE_DESCRIPTION("MAXIM 14577 charger driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 4ea81266010ae41a48c7ce393335dfe241931209 Mon Sep 17 00:00:00 2001 From: Dmitry Eremin-Solenikov Date: Wed, 11 Dec 2013 02:47:16 +0400 Subject: gpio-charger: Support wakeup events Add support for using gpio-charger IRQ as a wakeup event. Signed-off-by: Dmitry Eremin-Solenikov Signed-off-by: Anton Vorontsov --- drivers/power/gpio-charger.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/power/gpio-charger.c b/drivers/power/gpio-charger.c index 4e858a23568f..a0024b252197 100644 --- a/drivers/power/gpio-charger.c +++ b/drivers/power/gpio-charger.c @@ -28,6 +28,7 @@ struct gpio_charger { const struct gpio_charger_platform_data *pdata; unsigned int irq; + bool wakeup_enabled; struct power_supply charger; }; @@ -136,6 +137,8 @@ static int gpio_charger_probe(struct platform_device *pdev) platform_set_drvdata(pdev, gpio_charger); + device_init_wakeup(&pdev->dev, 1); + return 0; err_gpio_free: @@ -159,18 +162,32 @@ static int gpio_charger_remove(struct platform_device *pdev) } #ifdef CONFIG_PM_SLEEP +static int gpio_charger_suspend(struct device *dev) +{ + struct gpio_charger *gpio_charger = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) + gpio_charger->wakeup_enabled = + enable_irq_wake(gpio_charger->irq); + + return 0; +} + static int gpio_charger_resume(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct gpio_charger *gpio_charger = platform_get_drvdata(pdev); + if (gpio_charger->wakeup_enabled) + disable_irq_wake(gpio_charger->irq); power_supply_changed(&gpio_charger->charger); return 0; } #endif -static SIMPLE_DEV_PM_OPS(gpio_charger_pm_ops, NULL, gpio_charger_resume); +static SIMPLE_DEV_PM_OPS(gpio_charger_pm_ops, + gpio_charger_suspend, gpio_charger_resume); static struct platform_driver gpio_charger_driver = { .probe = gpio_charger_probe, -- cgit v1.2.3 From 26740dbb50f2421f1a33e23ac5ece609761fc1d5 Mon Sep 17 00:00:00 2001 From: Jonghwa Lee Date: Wed, 18 Dec 2013 12:53:53 +0900 Subject: max17042_battery: Add IRQF_ONESHOT flag to use default irq handler This patch adds IRQF_ONESHOT flag to max17042's irq since it uses primary default handler. Without this flag, requesting irq will be denied with returning error. Signed-off-by: Jonghwa Lee Signed-off-by: MyungJoo Ham Signed-off-by: Anton Vorontsov --- drivers/power/max17042_battery.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/power/max17042_battery.c b/drivers/power/max17042_battery.c index e0b22f9b6fdd..66da691c41cf 100644 --- a/drivers/power/max17042_battery.c +++ b/drivers/power/max17042_battery.c @@ -741,9 +741,9 @@ static int max17042_probe(struct i2c_client *client, if (client->irq) { ret = request_threaded_irq(client->irq, NULL, - max17042_thread_handler, - IRQF_TRIGGER_FALLING, - chip->battery.name, chip); + max17042_thread_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + chip->battery.name, chip); if (!ret) { regmap_read(chip->regmap, MAX17042_CONFIG, &val); val |= CONFIG_ALRT_BIT_ENBL; -- cgit v1.2.3