From a2ebfe2fc6e088a70d06cd15a5bc9bcb621cc195 Mon Sep 17 00:00:00 2001 From: Ramakrishna Pallala Date: Tue, 10 Apr 2012 16:21:20 +0530 Subject: power_supply: Add voltage_ocv property and use it for max17042 driver This adds a new sysfs file called 'voltage_ocv' which gives the Open Circuit Voltage of the battery. This property can be used for platform shutdown policies and can be useful for initial capacity estimations. Note: This patch is generated against linux-next branch. Signed-off-by: Ramakrishna Pallala Signed-off-by: Anton Vorontsov --- include/linux/power_supply.h | 2 ++ 1 file changed, 2 insertions(+) (limited to 'include/linux') diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index c38c13db883..fd17ae0f9c2 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -96,6 +96,7 @@ enum power_supply_property { POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_VOLTAGE_OCV, POWER_SUPPLY_PROP_CURRENT_MAX, POWER_SUPPLY_PROP_CURRENT_NOW, POWER_SUPPLY_PROP_CURRENT_AVG, @@ -261,6 +262,7 @@ static inline bool power_supply_is_watt_property(enum power_supply_property psp) case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: case POWER_SUPPLY_PROP_VOLTAGE_NOW: case POWER_SUPPLY_PROP_VOLTAGE_AVG: + case POWER_SUPPLY_PROP_VOLTAGE_OCV: case POWER_SUPPLY_PROP_POWER_NOW: return 1; default: -- cgit v1.2.3-70-g09d2 From 0d4ed4e27a4cb180af395fa3d7aa98d79f3d3015 Mon Sep 17 00:00:00 2001 From: Anton Vorontsov Date: Fri, 4 May 2012 21:06:19 -0700 Subject: power_supply: Make the core a boolean instead of a tristate On Mon, Apr 02, 2012 at 01:53:23PM +1000, Benjamin Herrenschmidt wrote: > > drivers/built-in.o: In function `.nouveau_pm_trigger': > > (.text+0xa56e8): undefined reference to `.power_supply_is_system_supplied' > > > > nouveau probably needs to depends on CONFIG_POWER_SUPPLY to force a module > > build with the latter is =m > > Ok, not that trivial... > > The problem is more like POWER_SUPPLY should be a bool, not a tristate. > > If you think about it: you don't want things like nouveau to depend on a > random subsystem like that, people will never get it. In fact, > POWER_SUPPLY provides empty inline stubs when not enabled, so that's > really designed to not have depends... > > However that -cannot- work if POWER_SUPPLY is modular and the drivers > who use it are not. > > The only fixes here that make sense I can think of > that don't also involve Kconfig horrors are: > > - Ugly: in power_supply.h, use the extern variant if > > defined(CONFIG_POWER_SUPPLY) || > (defined(CONFIG_POWER_SUPPLY_MODULE) && defined(MODULE)) > > IE. use the stub if power supply is a module and what is being built is > built-in. Of course that's not only ugly, it somewhat sucks from a user > perspective as the subsystem now exists but can't be used by some > drivers... > > - Better: Just make the bloody thing a bool :-) The power supply > framework itself is small enough, just make it a boolean option and > avoid the problem entirely. The actual power supply sub drivers can > remain modular of course. Suggested-by: Benjamin Herrenschmidt Signed-off-by: Anton Vorontsov --- drivers/power/Kconfig | 2 +- include/linux/power_supply.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'include/linux') diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 99dc29f2f2f..0c52a407912 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -1,5 +1,5 @@ menuconfig POWER_SUPPLY - tristate "Power supply class support" + bool "Power supply class support" help Say Y here to enable power supply class support. This allows power supply (batteries, AC, USB) monitoring by userspace diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index fd17ae0f9c2..3b912bee28d 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -212,7 +212,7 @@ extern void power_supply_changed(struct power_supply *psy); extern int power_supply_am_i_supplied(struct power_supply *psy); extern int power_supply_set_battery_charged(struct power_supply *psy); -#if defined(CONFIG_POWER_SUPPLY) || defined(CONFIG_POWER_SUPPLY_MODULE) +#ifdef CONFIG_POWER_SUPPLY extern int power_supply_is_system_supplied(void); #else static inline int power_supply_is_system_supplied(void) { return -ENOSYS; } -- cgit v1.2.3-70-g09d2 From d829dc75bafb10754f35fb8895e5143d20267b04 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Sat, 5 May 2012 06:24:10 -0700 Subject: charger-manager: Poll battery health in normal state Charger-Manager needs to check battery health in normal state as well as suspend-to-RAM state. When the battery is fully charged, Charger-Manager needs to determine when the chargers restart charging. This patch allows Charger-Manager to monitor battery health in normal state and handle operation for chargers after battery is fully charged. Signed-off-by: MyungJoo Ham Signed-off-by: Donggeun Kim Signed-off-by: Kyungmin Park Signed-off-by: Anton Vorontsov --- Documentation/power/charger-manager.txt | 25 +++- drivers/power/charger-manager.c | 229 ++++++++++++++++++++++++++++++++ include/linux/power/charger-manager.h | 25 ++++ 3 files changed, 278 insertions(+), 1 deletion(-) (limited to 'include/linux') diff --git a/Documentation/power/charger-manager.txt b/Documentation/power/charger-manager.txt index fdcca991df3..9b3863386e5 100644 --- a/Documentation/power/charger-manager.txt +++ b/Documentation/power/charger-manager.txt @@ -44,6 +44,12 @@ Charger Manager supports the following: Normally, the platform will need to resume and suspend some devices that are used by Charger Manager. +* Support for premature full-battery event handling + If the battery voltage drops by "fullbatt_vchkdrop_uV" after + "fullbatt_vchkdrop_ms" from the full-battery event, the framework + restarts charging. This check is also performed while suspended by + setting wakeup time accordingly and using suspend_again. + 2. Global Charger-Manager Data related with suspend_again ======================================================== In order to setup Charger Manager with suspend-again feature @@ -55,7 +61,7 @@ if there are multiple batteries. If there are multiple batteries, the multiple instances of Charger Manager share the same charger_global_desc and it will manage in-suspend monitoring for all instances of Charger Manager. -The user needs to provide all the two entries properly in order to activate +The user needs to provide all the three entries properly in order to activate in-suspend monitoring: struct charger_global_desc { @@ -74,6 +80,11 @@ bool (*rtc_only_wakeup)(void); same struct. If there is any other wakeup source triggered the wakeup, it should return false. If the "rtc" is the only wakeup reason, it should return true. + +bool assume_timer_stops_in_suspend; + : if true, Charger Manager assumes that + the timer (CM uses jiffies as timer) stops during suspend. Then, CM + assumes that the suspend-duration is same as the alarm length. }; 3. How to setup suspend_again @@ -111,6 +122,16 @@ enum polling_modes polling_mode; CM_POLL_CHARGING_ONLY: poll this battery if and only if the battery is being charged. +unsigned int fullbatt_vchkdrop_ms; +unsigned int fullbatt_vchkdrop_uV; + : If both have non-zero values, Charger Manager will check the + battery voltage drop fullbatt_vchkdrop_ms after the battery is fully + charged. If the voltage drop is over fullbatt_vchkdrop_uV, Charger + Manager will try to recharge the battery by disabling and enabling + chargers. Recharge with voltage drop condition only (without delay + condition) is needed to be implemented with hardware interrupts from + fuel gauges or charger devices/chips. + unsigned int fullbatt_uV; : If specified with a non-zero value, Charger Manager assumes that the battery is full (capacity = 100) if the battery is not being @@ -122,6 +143,8 @@ unsigned int polling_interval_ms; this battery every polling_interval_ms or more frequently. enum data_source battery_present; + : CM_BATTERY_PRESENT: assume that the battery exists. + CM_NO_BATTERY: assume that the battery does not exists. CM_FUEL_GAUGE: get battery presence information from fuel gauge. CM_CHARGER_STAT: get battery presence from chargers. diff --git a/drivers/power/charger-manager.c b/drivers/power/charger-manager.c index 9eca9f1ff0e..959062d16ba 100644 --- a/drivers/power/charger-manager.c +++ b/drivers/power/charger-manager.c @@ -57,6 +57,12 @@ static bool cm_suspended; static bool cm_rtc_set; static unsigned long cm_suspend_duration_ms; +/* About normal (not suspended) monitoring */ +static unsigned long polling_jiffy = ULONG_MAX; /* ULONG_MAX: no polling */ +static unsigned long next_polling; /* Next appointed polling time */ +static struct workqueue_struct *cm_wq; /* init at driver add */ +static struct delayed_work cm_monitor_work; /* init at driver add */ + /* Global charger-manager description */ static struct charger_global_desc *g_desc; /* init with setup_charger_manager */ @@ -71,6 +77,11 @@ static bool is_batt_present(struct charger_manager *cm) int i, ret; switch (cm->desc->battery_present) { + case CM_BATTERY_PRESENT: + present = true; + break; + case CM_NO_BATTERY: + break; case CM_FUEL_GAUGE: ret = cm->fuel_gauge->get_property(cm->fuel_gauge, POWER_SUPPLY_PROP_PRESENT, &val); @@ -278,6 +289,26 @@ static int try_charger_enable(struct charger_manager *cm, bool enable) return err; } +/** + * try_charger_restart - Restart charging. + * @cm: the Charger Manager representing the battery. + * + * Restart charging by turning off and on the charger. + */ +static int try_charger_restart(struct charger_manager *cm) +{ + int err; + + if (cm->emergency_stop) + return -EAGAIN; + + err = try_charger_enable(cm, false); + if (err) + return err; + + return try_charger_enable(cm, true); +} + /** * uevent_notify - Let users know something has changed. * @cm: the Charger Manager representing the battery. @@ -333,6 +364,46 @@ static void uevent_notify(struct charger_manager *cm, const char *event) dev_info(cm->dev, event); } +/** + * fullbatt_vchk - Check voltage drop some times after "FULL" event. + * @work: the work_struct appointing the function + * + * If a user has designated "fullbatt_vchkdrop_ms/uV" values with + * charger_desc, Charger Manager checks voltage drop after the battery + * "FULL" event. It checks whether the voltage has dropped more than + * fullbatt_vchkdrop_uV by calling this function after fullbatt_vchkrop_ms. + */ +static void fullbatt_vchk(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct charger_manager *cm = container_of(dwork, + struct charger_manager, fullbatt_vchk_work); + struct charger_desc *desc = cm->desc; + int batt_uV, err, diff; + + /* remove the appointment for fullbatt_vchk */ + cm->fullbatt_vchk_jiffies_at = 0; + + if (!desc->fullbatt_vchkdrop_uV || !desc->fullbatt_vchkdrop_ms) + return; + + err = get_batt_uV(cm, &batt_uV); + if (err) { + dev_err(cm->dev, "%s: get_batt_uV error(%d).\n", __func__, err); + return; + } + + diff = cm->fullbatt_vchk_uV; + diff -= batt_uV; + + dev_dbg(cm->dev, "VBATT dropped %duV after full-batt.\n", diff); + + if (diff > desc->fullbatt_vchkdrop_uV) { + try_charger_restart(cm); + uevent_notify(cm, "Recharge"); + } +} + /** * _cm_monitor - Monitor the temperature and return true for exceptions. * @cm: the Charger Manager representing the battery. @@ -392,6 +463,68 @@ static bool cm_monitor(void) return stop; } +/** + * _setup_polling - Setup the next instance of polling. + * @work: work_struct of the function _setup_polling. + */ +static void _setup_polling(struct work_struct *work) +{ + unsigned long min = ULONG_MAX; + struct charger_manager *cm; + bool keep_polling = false; + unsigned long _next_polling; + + mutex_lock(&cm_list_mtx); + + list_for_each_entry(cm, &cm_list, entry) { + if (is_polling_required(cm) && cm->desc->polling_interval_ms) { + keep_polling = true; + + if (min > cm->desc->polling_interval_ms) + min = cm->desc->polling_interval_ms; + } + } + + polling_jiffy = msecs_to_jiffies(min); + if (polling_jiffy <= CM_JIFFIES_SMALL) + polling_jiffy = CM_JIFFIES_SMALL + 1; + + if (!keep_polling) + polling_jiffy = ULONG_MAX; + if (polling_jiffy == ULONG_MAX) + goto out; + + WARN(cm_wq == NULL, "charger-manager: workqueue not initialized" + ". try it later. %s\n", __func__); + + _next_polling = jiffies + polling_jiffy; + + if (!delayed_work_pending(&cm_monitor_work) || + (delayed_work_pending(&cm_monitor_work) && + time_after(next_polling, _next_polling))) { + cancel_delayed_work_sync(&cm_monitor_work); + next_polling = jiffies + polling_jiffy; + queue_delayed_work(cm_wq, &cm_monitor_work, polling_jiffy); + } + +out: + mutex_unlock(&cm_list_mtx); +} +static DECLARE_WORK(setup_polling, _setup_polling); + +/** + * cm_monitor_poller - The Monitor / Poller. + * @work: work_struct of the function cm_monitor_poller + * + * During non-suspended state, cm_monitor_poller is used to poll and monitor + * the batteries. + */ +static void cm_monitor_poller(struct work_struct *work) +{ + cm_monitor(); + schedule_work(&setup_polling); +} + static int charger_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) @@ -613,6 +746,21 @@ static bool cm_setup_timer(void) mutex_lock(&cm_list_mtx); list_for_each_entry(cm, &cm_list, entry) { + unsigned int fbchk_ms = 0; + + /* fullbatt_vchk is required. setup timer for that */ + if (cm->fullbatt_vchk_jiffies_at) { + fbchk_ms = jiffies_to_msecs(cm->fullbatt_vchk_jiffies_at + - jiffies); + if (time_is_before_eq_jiffies( + cm->fullbatt_vchk_jiffies_at) || + msecs_to_jiffies(fbchk_ms) < CM_JIFFIES_SMALL) { + fullbatt_vchk(&cm->fullbatt_vchk_work.work); + fbchk_ms = 0; + } + } + CM_MIN_VALID(wakeup_ms, fbchk_ms); + /* Skip if polling is not required for this CM */ if (!is_polling_required(cm) && !cm->emergency_stop) continue; @@ -672,6 +820,23 @@ static bool cm_setup_timer(void) return false; } +static void _cm_fbchk_in_suspend(struct charger_manager *cm) +{ + unsigned long jiffy_now = jiffies; + + if (!cm->fullbatt_vchk_jiffies_at) + return; + + if (g_desc && g_desc->assume_timer_stops_in_suspend) + jiffy_now += msecs_to_jiffies(cm_suspend_duration_ms); + + /* Execute now if it's going to be executed not too long after */ + jiffy_now += CM_JIFFIES_SMALL; + + if (time_after_eq(jiffy_now, cm->fullbatt_vchk_jiffies_at)) + fullbatt_vchk(&cm->fullbatt_vchk_work.work); +} + /** * cm_suspend_again - Determine whether suspend again or not * @@ -693,6 +858,8 @@ bool cm_suspend_again(void) ret = true; mutex_lock(&cm_list_mtx); list_for_each_entry(cm, &cm_list, entry) { + _cm_fbchk_in_suspend(cm); + if (cm->status_save_ext_pwr_inserted != is_ext_pwr_online(cm) || cm->status_save_batt != is_batt_present(cm)) { ret = false; @@ -796,6 +963,21 @@ static int charger_manager_probe(struct platform_device *pdev) memcpy(cm->desc, desc, sizeof(struct charger_desc)); cm->last_temp_mC = INT_MIN; /* denotes "unmeasured, yet" */ + /* + * The following two do not need to be errors. + * Users may intentionally ignore those two features. + */ + if (desc->fullbatt_uV == 0) { + dev_info(&pdev->dev, "Ignoring full-battery voltage threshold" + " as it is not supplied."); + } + if (!desc->fullbatt_vchkdrop_ms || !desc->fullbatt_vchkdrop_uV) { + dev_info(&pdev->dev, "Disabling full-battery voltage drop " + "checking mechanism as it is not supplied."); + desc->fullbatt_vchkdrop_ms = 0; + desc->fullbatt_vchkdrop_uV = 0; + } + if (!desc->charger_regulators || desc->num_charger_regulators < 1) { ret = -EINVAL; dev_err(&pdev->dev, "charger_regulators undefined.\n"); @@ -903,6 +1085,8 @@ static int charger_manager_probe(struct platform_device *pdev) cm->charger_psy.num_properties++; } + INIT_DELAYED_WORK(&cm->fullbatt_vchk_work, fullbatt_vchk); + ret = power_supply_register(NULL, &cm->charger_psy); if (ret) { dev_err(&pdev->dev, "Cannot register charger-manager with" @@ -928,6 +1112,8 @@ static int charger_manager_probe(struct platform_device *pdev) list_add(&cm->entry, &cm_list); mutex_unlock(&cm_list_mtx); + schedule_work(&setup_polling); + return 0; err_chg_enable: @@ -958,9 +1144,17 @@ static int __devexit charger_manager_remove(struct platform_device *pdev) list_del(&cm->entry); mutex_unlock(&cm_list_mtx); + if (work_pending(&setup_polling)) + cancel_work_sync(&setup_polling); + if (delayed_work_pending(&cm_monitor_work)) + cancel_delayed_work_sync(&cm_monitor_work); + regulator_bulk_free(desc->num_charger_regulators, desc->charger_regulators); power_supply_unregister(&cm->charger_psy); + + try_charger_enable(cm, false); + kfree(cm->charger_psy.properties); kfree(cm->charger_stat); kfree(cm->desc); @@ -1000,6 +1194,8 @@ static int cm_suspend_prepare(struct device *dev) cm_suspended = true; } + if (delayed_work_pending(&cm->fullbatt_vchk_work)) + cancel_delayed_work(&cm->fullbatt_vchk_work); cm->status_save_ext_pwr_inserted = is_ext_pwr_online(cm); cm->status_save_batt = is_batt_present(cm); @@ -1027,6 +1223,33 @@ static void cm_suspend_complete(struct device *dev) cm_rtc_set = false; } + /* Re-enqueue delayed work (fullbatt_vchk_work) */ + if (cm->fullbatt_vchk_jiffies_at) { + unsigned long delay = 0; + unsigned long now = jiffies + CM_JIFFIES_SMALL; + + if (time_after_eq(now, cm->fullbatt_vchk_jiffies_at)) { + delay = (unsigned long)((long)now + - (long)(cm->fullbatt_vchk_jiffies_at)); + delay = jiffies_to_msecs(delay); + } else { + delay = 0; + } + + /* + * Account for cm_suspend_duration_ms if + * assume_timer_stops_in_suspend is active + */ + if (g_desc && g_desc->assume_timer_stops_in_suspend) { + if (delay > cm_suspend_duration_ms) + delay -= cm_suspend_duration_ms; + else + delay = 0; + } + + queue_delayed_work(cm_wq, &cm->fullbatt_vchk_work, + msecs_to_jiffies(delay)); + } uevent_notify(cm, NULL); } @@ -1048,12 +1271,18 @@ static struct platform_driver charger_manager_driver = { static int __init charger_manager_init(void) { + cm_wq = create_freezable_workqueue("charger_manager"); + INIT_DELAYED_WORK(&cm_monitor_work, cm_monitor_poller); + return platform_driver_register(&charger_manager_driver); } late_initcall(charger_manager_init); static void __exit charger_manager_cleanup(void) { + destroy_workqueue(cm_wq); + cm_wq = NULL; + platform_driver_unregister(&charger_manager_driver); } module_exit(charger_manager_cleanup); diff --git a/include/linux/power/charger-manager.h b/include/linux/power/charger-manager.h index 4f75e531c11..baa299a95e1 100644 --- a/include/linux/power/charger-manager.h +++ b/include/linux/power/charger-manager.h @@ -18,6 +18,8 @@ #include enum data_source { + CM_BATTERY_PRESENT, + CM_NO_BATTERY, CM_FUEL_GAUGE, CM_CHARGER_STAT, }; @@ -38,11 +40,18 @@ enum polling_modes { * rtc_only_wakeup() returning false. * If the RTC given to CM is the only wakeup reason, * rtc_only_wakeup should return true. + * @assume_timer_stops_in_suspend: + * Assume that the jiffy timer stops in suspend-to-RAM. + * When enabled, CM does not rely on jiffies value in + * suspend_again and assumes that jiffies value does not + * change during suspend. */ struct charger_global_desc { char *rtc_name; bool (*rtc_only_wakeup)(void); + + bool assume_timer_stops_in_suspend; }; /** @@ -50,6 +59,11 @@ struct charger_global_desc { * @psy_name: the name of power-supply-class for charger manager * @polling_mode: * Determine which polling mode will be used + * @fullbatt_vchkdrop_ms: + * @fullbatt_vchkdrop_uV: + * Check voltage drop after the battery is fully charged. + * If it has dropped more than fullbatt_vchkdrop_uV after + * fullbatt_vchkdrop_ms, CM will restart charging. * @fullbatt_uV: voltage in microvolt * If it is not being charged and VBATT >= fullbatt_uV, * it is assumed to be full. @@ -76,6 +90,8 @@ struct charger_desc { enum polling_modes polling_mode; unsigned int polling_interval_ms; + unsigned int fullbatt_vchkdrop_ms; + unsigned int fullbatt_vchkdrop_uV; unsigned int fullbatt_uV; enum data_source battery_present; @@ -101,6 +117,11 @@ struct charger_desc { * @fuel_gauge: power_supply for fuel gauge * @charger_stat: array of power_supply for chargers * @charger_enabled: the state of charger + * @fullbatt_vchk_jiffies_at: + * jiffies at the time full battery check will occur. + * @fullbatt_vchk_uV: voltage in microvolt + * criteria for full battery + * @fullbatt_vchk_work: work queue for full battery check * @emergency_stop: * When setting true, stop charging * @last_temp_mC: the measured temperature in milli-Celsius @@ -121,6 +142,10 @@ struct charger_manager { bool charger_enabled; + unsigned long fullbatt_vchk_jiffies_at; + unsigned int fullbatt_vchk_uV; + struct delayed_work fullbatt_vchk_work; + int emergency_stop; int last_temp_mC; -- cgit v1.2.3-70-g09d2 From dfeccb12b4614befc49a92eb121c2211294ca669 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Sat, 5 May 2012 06:26:47 -0700 Subject: charger-manager: Provide cm_notify_event function for in-kernel use By using cm_notify_event function, charger driver can report several charger events (e.g. battery full and external power in/out, etc) to Charger-Manager. Charger-Manager can properly and immediately control chargers by the reported event. Signed-off-by: MyungJoo Ham Signed-off-by: Donggeun Kim Signed-off-by: Kyungmin Park Signed-off-by: Anton Vorontsov --- Documentation/power/charger-manager.txt | 16 +++- drivers/power/charger-manager.c | 163 ++++++++++++++++++++++++++++++++ include/linux/power/charger-manager.h | 25 +++-- 3 files changed, 195 insertions(+), 9 deletions(-) (limited to 'include/linux') diff --git a/Documentation/power/charger-manager.txt b/Documentation/power/charger-manager.txt index 9b3863386e5..b4f7f4b23f6 100644 --- a/Documentation/power/charger-manager.txt +++ b/Documentation/power/charger-manager.txt @@ -50,6 +50,10 @@ Charger Manager supports the following: restarts charging. This check is also performed while suspended by setting wakeup time accordingly and using suspend_again. +* Support for uevent-notify + With the charger-related events, the device sends + notification to users with UEVENT. + 2. Global Charger-Manager Data related with suspend_again ======================================================== In order to setup Charger Manager with suspend-again feature @@ -174,7 +178,17 @@ bool measure_battery_temp; the value of measure_battery_temp. }; -5. Other Considerations +5. Notify Charger-Manager of charger events: cm_notify_event() +========================================================= +If there is an charger event is required to notify +Charger Manager, a charger device driver that triggers the event can call +cm_notify_event(psy, type, msg) to notify the corresponding Charger Manager. +In the function, psy is the charger driver's power_supply pointer, which is +associated with Charger-Manager. The parameter "type" +is the same as irq's type (enum cm_event_types). The event message "msg" is +optional and is effective only if the event type is "UNDESCRIBED" or "OTHERS". + +6. Other Considerations ======================= At the charger/battery-related events such as battery-pulled-out, diff --git a/drivers/power/charger-manager.c b/drivers/power/charger-manager.c index 959062d16ba..86935ec1895 100644 --- a/drivers/power/charger-manager.c +++ b/drivers/power/charger-manager.c @@ -23,6 +23,16 @@ #include #include +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_EXT_PWR_IN_OUT] = "External Power Attach/Detach", + [CM_EVENT_CHG_START_STOP] = "Charging Start/Stop", + [CM_EVENT_OTHERS] = "Other battery events" +}; + /* * Regard CM_JIFFIES_SMALL jiffies is small enough to ignore for * delayed works so that we can run delayed works with CM_JIFFIES_SMALL @@ -525,6 +535,69 @@ static void cm_monitor_poller(struct work_struct *work) schedule_work(&setup_polling); } +/** + * fullbatt_handler - Event handler for CM_EVENT_BATT_FULL + * @cm: the Charger Manager representing the battery. + */ +static void fullbatt_handler(struct charger_manager *cm) +{ + struct charger_desc *desc = cm->desc; + + if (!desc->fullbatt_vchkdrop_uV || !desc->fullbatt_vchkdrop_ms) + goto out; + + if (cm_suspended) + device_set_wakeup_capable(cm->dev, true); + + if (delayed_work_pending(&cm->fullbatt_vchk_work)) + cancel_delayed_work(&cm->fullbatt_vchk_work); + queue_delayed_work(cm_wq, &cm->fullbatt_vchk_work, + msecs_to_jiffies(desc->fullbatt_vchkdrop_ms)); + cm->fullbatt_vchk_jiffies_at = jiffies + msecs_to_jiffies( + desc->fullbatt_vchkdrop_ms); + + if (cm->fullbatt_vchk_jiffies_at == 0) + cm->fullbatt_vchk_jiffies_at = 1; + +out: + dev_info(cm->dev, "EVENT_HANDLE: Battery Fully Charged.\n"); + uevent_notify(cm, default_event_names[CM_EVENT_BATT_FULL]); +} + +/** + * battout_handler - Event handler for CM_EVENT_BATT_OUT + * @cm: the Charger Manager representing the battery. + */ +static void battout_handler(struct charger_manager *cm) +{ + if (cm_suspended) + device_set_wakeup_capable(cm->dev, true); + + if (!is_batt_present(cm)) { + dev_emerg(cm->dev, "Battery Pulled Out!\n"); + uevent_notify(cm, default_event_names[CM_EVENT_BATT_OUT]); + } else { + uevent_notify(cm, "Battery Reinserted?"); + } +} + +/** + * misc_event_handler - Handler for other evnets + * @cm: the Charger Manager representing the battery. + * @type: the Charger Manager representing the battery. + */ +static void misc_event_handler(struct charger_manager *cm, + enum cm_event_types type) +{ + if (cm_suspended) + device_set_wakeup_capable(cm->dev, true); + + if (!delayed_work_pending(&cm_monitor_work) && + is_polling_required(cm) && cm->desc->polling_interval_ms) + schedule_work(&setup_polling); + uevent_notify(cm, default_event_names[type]); +} + static int charger_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) @@ -1112,6 +1185,13 @@ static int charger_manager_probe(struct platform_device *pdev) list_add(&cm->entry, &cm_list); mutex_unlock(&cm_list_mtx); + /* + * Charger-manager is capable of waking up the systme from sleep + * when event is happend through cm_notify_event() + */ + device_init_wakeup(&pdev->dev, true); + device_set_wakeup_capable(&pdev->dev, false); + schedule_work(&setup_polling); return 0; @@ -1169,6 +1249,18 @@ static const struct platform_device_id charger_manager_id[] = { }; 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 ret; +} + static int cm_suspend_prepare(struct device *dev) { struct charger_manager *cm = dev_get_drvdata(dev); @@ -1250,11 +1342,13 @@ static void cm_suspend_complete(struct device *dev) queue_delayed_work(cm_wq, &cm->fullbatt_vchk_work, msecs_to_jiffies(delay)); } + device_set_wakeup_capable(cm->dev, false); uevent_notify(cm, NULL); } static const struct dev_pm_ops charger_manager_pm = { .prepare = cm_suspend_prepare, + .suspend_noirq = cm_suspend_noirq, .complete = cm_suspend_complete, }; @@ -1287,6 +1381,75 @@ static void __exit charger_manager_cleanup(void) } module_exit(charger_manager_cleanup); +/** + * find_power_supply - find the associated power_supply of charger + * @cm: the Charger Manager representing the battery + * @psy: pointer to instance of charger's power_supply + */ +static bool find_power_supply(struct charger_manager *cm, + struct power_supply *psy) +{ + int i; + bool found = false; + + for (i = 0; cm->charger_stat[i]; i++) { + if (psy == cm->charger_stat[i]) { + found = true; + break; + } + } + + return found; +} + +/** + * cm_notify_event - charger driver notify Charger Manager of charger event + * @psy: pointer to instance of charger's power_supply + * @type: type of charger event + * @msg: optional message passed to uevent_notify fuction + */ +void cm_notify_event(struct power_supply *psy, enum cm_event_types type, + char *msg) +{ + struct charger_manager *cm; + bool found_power_supply = false; + + if (psy == NULL) + return; + + mutex_lock(&cm_list_mtx); + list_for_each_entry(cm, &cm_list, entry) { + found_power_supply = find_power_supply(cm, psy); + if (found_power_supply) + break; + } + mutex_unlock(&cm_list_mtx); + + if (!found_power_supply) + return; + + switch (type) { + case CM_EVENT_BATT_FULL: + fullbatt_handler(cm); + break; + case CM_EVENT_BATT_OUT: + battout_handler(cm); + break; + case CM_EVENT_BATT_IN: + case CM_EVENT_EXT_PWR_IN_OUT ... CM_EVENT_CHG_START_STOP: + misc_event_handler(cm, type); + break; + case CM_EVENT_UNKNOWN: + case CM_EVENT_OTHERS: + uevent_notify(cm, msg ? msg : default_event_names[type]); + break; + default: + dev_err(cm->dev, "%s type not specified.\n", __func__); + break; + } +} +EXPORT_SYMBOL_GPL(cm_notify_event); + MODULE_AUTHOR("MyungJoo Ham "); MODULE_DESCRIPTION("Charger Manager"); MODULE_LICENSE("GPL"); diff --git a/include/linux/power/charger-manager.h b/include/linux/power/charger-manager.h index baa299a95e1..241065c9ce5 100644 --- a/include/linux/power/charger-manager.h +++ b/include/linux/power/charger-manager.h @@ -31,6 +31,16 @@ enum polling_modes { CM_POLL_CHARGING_ONLY, }; +enum cm_event_types { + CM_EVENT_UNKNOWN = 0, + CM_EVENT_BATT_FULL, + CM_EVENT_BATT_IN, + CM_EVENT_BATT_OUT, + CM_EVENT_EXT_PWR_IN_OUT, + CM_EVENT_CHG_START_STOP, + CM_EVENT_OTHERS, +}; + /** * struct charger_global_desc * @rtc_name: the name of RTC used to wake up the system from suspend. @@ -159,14 +169,13 @@ struct charger_manager { #ifdef CONFIG_CHARGER_MANAGER extern int setup_charger_manager(struct charger_global_desc *gd); extern bool cm_suspend_again(void); +extern void cm_notify_event(struct power_supply *psy, + enum cm_event_types type, char *msg); #else -static void __maybe_unused setup_charger_manager(struct charger_global_desc *gd) -{ } - -static bool __maybe_unused cm_suspend_again(void) -{ - return false; -} +static inline int setup_charger_manager(struct charger_global_desc *gd) +{ return 0; } +static inline bool cm_suspend_again(void) { return false; } +static inline void cm_notify_event(struct power_supply *psy, + enum cm_event_types type, char *msg) { } #endif - #endif /* _CHARGER_MANAGER_H */ -- cgit v1.2.3-70-g09d2 From 9a8422d205ea142a27c2573e5ca3d2cc87d75260 Mon Sep 17 00:00:00 2001 From: Ramakrishna Pallala Date: Sat, 5 May 2012 14:34:26 +0530 Subject: max17042_battery: Add support for max17047/50 chip max17047 is improved version of max17042 chip. It has few HW bug fixes with minor changes in register set. max17050 is same as max17047 chip except its silicon packging. So from driver's point of view there is no difference btw max1047 and max1050. This patch adds the support to dynamically detect the chip type and adds steps to initialize the max17047 chip. Signed-off-by: Ramakrishna Pallala Signed-off-by: Anton Vorontsov --- drivers/power/Kconfig | 5 ++- drivers/power/max17042_battery.c | 69 ++++++++++++++++++++++++++++------ include/linux/power/max17042_battery.h | 17 +++++++++ 3 files changed, 78 insertions(+), 13 deletions(-) (limited to 'include/linux') diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index b711795c666..e3a3b4956f0 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -181,14 +181,15 @@ config BATTERY_MAX17040 to operate with a single lithium cell config BATTERY_MAX17042 - tristate "Maxim MAX17042/8997/8966 Fuel Gauge" + tristate "Maxim MAX17042/17047/17050/8997/8966 Fuel Gauge" depends on I2C help MAX17042 is fuel-gauge systems for lithium-ion (Li+) batteries in handheld and portable equipment. The MAX17042 is configured to operate with a single lithium cell. MAX8997 and MAX8966 are multi-function devices that include fuel gauages that are compatible - with MAX17042. + with MAX17042. This driver also supports max17047/50 chips which are + improved version of max17042. config BATTERY_Z2 tristate "Z2 battery driver" diff --git a/drivers/power/max17042_battery.c b/drivers/power/max17042_battery.c index 8d28006322e..140788b309f 100644 --- a/drivers/power/max17042_battery.c +++ b/drivers/power/max17042_battery.c @@ -62,9 +62,13 @@ #define dP_ACC_100 0x1900 #define dP_ACC_200 0x3200 +#define MAX17042_IC_VERSION 0x0092 +#define MAX17047_IC_VERSION 0x00AC /* same for max17050 */ + struct max17042_chip { struct i2c_client *client; struct power_supply battery; + enum max170xx_chip_type chip_type; struct max17042_platform_data *pdata; struct work_struct work; int init_complete; @@ -152,7 +156,10 @@ static int max17042_get_property(struct power_supply *psy, val->intval *= 20000; /* Units of LSB = 20mV */ break; case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: - ret = max17042_read_reg(chip->client, MAX17042_V_empty); + if (chip->chip_type == MAX17042) + ret = max17042_read_reg(chip->client, MAX17042_V_empty); + else + ret = max17042_read_reg(chip->client, MAX17047_V_empty); if (ret < 0) return ret; @@ -389,6 +396,9 @@ static void max17042_write_config_regs(struct max17042_chip *chip) max17042_write_reg(chip->client, MAX17042_FilterCFG, config->filter_cfg); max17042_write_reg(chip->client, MAX17042_RelaxCFG, config->relax_cfg); + if (chip->chip_type == MAX17047) + max17042_write_reg(chip->client, MAX17047_FullSOCThr, + config->full_soc_thresh); } static void max17042_write_custom_regs(struct max17042_chip *chip) @@ -399,12 +409,23 @@ static void max17042_write_custom_regs(struct max17042_chip *chip) config->rcomp0); max17042_write_verify_reg(chip->client, MAX17042_TempCo, config->tcompc0); - max17042_write_reg(chip->client, MAX17042_EmptyTempCo, - config->empty_tempco); - max17042_write_verify_reg(chip->client, MAX17042_K_empty0, - config->kempty0); max17042_write_verify_reg(chip->client, MAX17042_ICHGTerm, config->ichgt_term); + if (chip->chip_type == MAX17042) { + max17042_write_reg(chip->client, MAX17042_EmptyTempCo, + config->empty_tempco); + max17042_write_verify_reg(chip->client, MAX17042_K_empty0, + config->kempty0); + } else { + max17042_write_verify_reg(chip->client, MAX17047_QRTbl00, + config->qrtbl00); + max17042_write_verify_reg(chip->client, MAX17047_QRTbl10, + config->qrtbl10); + max17042_write_verify_reg(chip->client, MAX17047_QRTbl20, + config->qrtbl20); + max17042_write_verify_reg(chip->client, MAX17047_QRTbl30, + config->qrtbl30); + } } static void max17042_update_capacity_regs(struct max17042_chip *chip) @@ -460,6 +481,8 @@ static void max17042_load_new_capacity_params(struct max17042_chip *chip) config->design_cap); max17042_write_verify_reg(chip->client, MAX17042_FullCAPNom, config->fullcapnom); + /* Update SOC register with new SOC */ + max17042_write_reg(chip->client, MAX17042_RepSOC, vfSoc); } /* @@ -496,20 +519,28 @@ static inline void max17042_override_por_values(struct max17042_chip *chip) max17042_override_por(client, MAX17042_FullCAP, config->fullcap); max17042_override_por(client, MAX17042_FullCAPNom, config->fullcapnom); - max17042_override_por(client, MAX17042_SOC_empty, config->socempty); + if (chip->chip_type == MAX17042) + max17042_override_por(client, MAX17042_SOC_empty, + config->socempty); max17042_override_por(client, MAX17042_LAvg_empty, config->lavg_empty); max17042_override_por(client, MAX17042_dQacc, config->dqacc); max17042_override_por(client, MAX17042_dPacc, config->dpacc); - max17042_override_por(client, MAX17042_V_empty, config->vempty); + if (chip->chip_type == MAX17042) + max17042_override_por(client, MAX17042_V_empty, config->vempty); + else + max17042_override_por(client, MAX17047_V_empty, config->vempty); max17042_override_por(client, MAX17042_TempNom, config->temp_nom); max17042_override_por(client, MAX17042_TempLim, config->temp_lim); max17042_override_por(client, MAX17042_FCTC, config->fctc); max17042_override_por(client, MAX17042_RCOMP0, config->rcomp0); max17042_override_por(client, MAX17042_TempCo, config->tcompc0); - max17042_override_por(client, MAX17042_EmptyTempCo, - config->empty_tempco); - max17042_override_por(client, MAX17042_K_empty0, config->kempty0); + if (chip->chip_type) { + max17042_override_por(client, MAX17042_EmptyTempCo, + config->empty_tempco); + max17042_override_por(client, MAX17042_K_empty0, + config->kempty0); + } } static int max17042_init_chip(struct max17042_chip *chip) @@ -666,7 +697,19 @@ static int __devinit max17042_probe(struct i2c_client *client, i2c_set_clientdata(client, chip); - chip->battery.name = "max17042_battery"; + ret = max17042_read_reg(chip->client, MAX17042_DevName); + if (ret == MAX17042_IC_VERSION) { + dev_dbg(&client->dev, "chip type max17042 detected\n"); + chip->chip_type = MAX17042; + } else if (ret == MAX17047_IC_VERSION) { + dev_dbg(&client->dev, "chip type max17047/50 detected\n"); + chip->chip_type = MAX17047; + } else { + dev_err(&client->dev, "device version mismatch: %x\n", ret); + return -EIO; + } + + chip->battery.name = "max170xx_battery"; chip->battery.type = POWER_SUPPLY_TYPE_BATTERY; chip->battery.get_property = max17042_get_property; chip->battery.properties = max17042_battery_props; @@ -778,6 +821,8 @@ static const struct dev_pm_ops max17042_pm_ops = { #ifdef CONFIG_OF static const struct of_device_id max17042_dt_match[] = { { .compatible = "maxim,max17042" }, + { .compatible = "maxim,max17047" }, + { .compatible = "maxim,max17050" }, { }, }; MODULE_DEVICE_TABLE(of, max17042_dt_match); @@ -785,6 +830,8 @@ MODULE_DEVICE_TABLE(of, max17042_dt_match); static const struct i2c_device_id max17042_id[] = { { "max17042", 0 }, + { "max17047", 1 }, + { "max17050", 2 }, { } }; MODULE_DEVICE_TABLE(i2c, max17042_id); diff --git a/include/linux/power/max17042_battery.h b/include/linux/power/max17042_battery.h index e01b167e66f..89dd84f47c6 100644 --- a/include/linux/power/max17042_battery.h +++ b/include/linux/power/max17042_battery.h @@ -116,6 +116,18 @@ enum max17042_register { MAX17042_VFSOC = 0xFF, }; +/* Registers specific to max17047/50 */ +enum max17047_register { + MAX17047_QRTbl00 = 0x12, + MAX17047_FullSOCThr = 0x13, + MAX17047_QRTbl10 = 0x22, + MAX17047_QRTbl20 = 0x32, + MAX17047_V_empty = 0x3A, + MAX17047_QRTbl30 = 0x42, +}; + +enum max170xx_chip_type {MAX17042, MAX17047}; + /* * used for setting a register to a desired value * addr : address for a register @@ -144,6 +156,7 @@ struct max17042_config_data { u16 shdntimer; /* 0x03F */ /* App data */ + u16 full_soc_thresh; /* 0x13 */ u16 design_cap; /* 0x18 */ u16 ichgt_term; /* 0x1E */ @@ -162,6 +175,10 @@ struct max17042_config_data { u16 lavg_empty; /* 0x36 */ u16 dqacc; /* 0x45 */ u16 dpacc; /* 0x46 */ + u16 qrtbl00; /* 0x12 */ + u16 qrtbl10; /* 0x22 */ + u16 qrtbl20; /* 0x32 */ + u16 qrtbl30; /* 0x42 */ /* Cell technology from power_supply.h */ u16 cell_technology; -- cgit v1.2.3-70-g09d2