|
@@ -23,6 +23,16 @@
|
|
#include <linux/power/charger-manager.h>
|
|
#include <linux/power/charger-manager.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/regulator/consumer.h>
|
|
|
|
|
|
|
|
+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
|
|
* Regard CM_JIFFIES_SMALL jiffies is small enough to ignore for
|
|
* delayed works so that we can run delayed works with CM_JIFFIES_SMALL
|
|
* delayed works so that we can run delayed works with CM_JIFFIES_SMALL
|
|
@@ -57,6 +67,12 @@ static bool cm_suspended;
|
|
static bool cm_rtc_set;
|
|
static bool cm_rtc_set;
|
|
static unsigned long cm_suspend_duration_ms;
|
|
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 */
|
|
/* Global charger-manager description */
|
|
static struct charger_global_desc *g_desc; /* init with setup_charger_manager */
|
|
static struct charger_global_desc *g_desc; /* init with setup_charger_manager */
|
|
|
|
|
|
@@ -71,6 +87,11 @@ static bool is_batt_present(struct charger_manager *cm)
|
|
int i, ret;
|
|
int i, ret;
|
|
|
|
|
|
switch (cm->desc->battery_present) {
|
|
switch (cm->desc->battery_present) {
|
|
|
|
+ case CM_BATTERY_PRESENT:
|
|
|
|
+ present = true;
|
|
|
|
+ break;
|
|
|
|
+ case CM_NO_BATTERY:
|
|
|
|
+ break;
|
|
case CM_FUEL_GAUGE:
|
|
case CM_FUEL_GAUGE:
|
|
ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
|
|
ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
|
|
POWER_SUPPLY_PROP_PRESENT, &val);
|
|
POWER_SUPPLY_PROP_PRESENT, &val);
|
|
@@ -278,6 +299,26 @@ static int try_charger_enable(struct charger_manager *cm, bool enable)
|
|
return err;
|
|
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.
|
|
* uevent_notify - Let users know something has changed.
|
|
* @cm: the Charger Manager representing the battery.
|
|
* @cm: the Charger Manager representing the battery.
|
|
@@ -333,6 +374,46 @@ static void uevent_notify(struct charger_manager *cm, const char *event)
|
|
dev_info(cm->dev, 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_monitor - Monitor the temperature and return true for exceptions.
|
|
* @cm: the Charger Manager representing the battery.
|
|
* @cm: the Charger Manager representing the battery.
|
|
@@ -392,6 +473,131 @@ static bool cm_monitor(void)
|
|
return stop;
|
|
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);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * 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,
|
|
static int charger_get_property(struct power_supply *psy,
|
|
enum power_supply_property psp,
|
|
enum power_supply_property psp,
|
|
union power_supply_propval *val)
|
|
union power_supply_propval *val)
|
|
@@ -613,6 +819,21 @@ static bool cm_setup_timer(void)
|
|
mutex_lock(&cm_list_mtx);
|
|
mutex_lock(&cm_list_mtx);
|
|
|
|
|
|
list_for_each_entry(cm, &cm_list, entry) {
|
|
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 */
|
|
/* Skip if polling is not required for this CM */
|
|
if (!is_polling_required(cm) && !cm->emergency_stop)
|
|
if (!is_polling_required(cm) && !cm->emergency_stop)
|
|
continue;
|
|
continue;
|
|
@@ -672,6 +893,23 @@ static bool cm_setup_timer(void)
|
|
return false;
|
|
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
|
|
* cm_suspend_again - Determine whether suspend again or not
|
|
*
|
|
*
|
|
@@ -693,6 +931,8 @@ bool cm_suspend_again(void)
|
|
ret = true;
|
|
ret = true;
|
|
mutex_lock(&cm_list_mtx);
|
|
mutex_lock(&cm_list_mtx);
|
|
list_for_each_entry(cm, &cm_list, entry) {
|
|
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) ||
|
|
if (cm->status_save_ext_pwr_inserted != is_ext_pwr_online(cm) ||
|
|
cm->status_save_batt != is_batt_present(cm)) {
|
|
cm->status_save_batt != is_batt_present(cm)) {
|
|
ret = false;
|
|
ret = false;
|
|
@@ -796,6 +1036,21 @@ static int charger_manager_probe(struct platform_device *pdev)
|
|
memcpy(cm->desc, desc, sizeof(struct charger_desc));
|
|
memcpy(cm->desc, desc, sizeof(struct charger_desc));
|
|
cm->last_temp_mC = INT_MIN; /* denotes "unmeasured, yet" */
|
|
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) {
|
|
if (!desc->charger_regulators || desc->num_charger_regulators < 1) {
|
|
ret = -EINVAL;
|
|
ret = -EINVAL;
|
|
dev_err(&pdev->dev, "charger_regulators undefined.\n");
|
|
dev_err(&pdev->dev, "charger_regulators undefined.\n");
|
|
@@ -903,6 +1158,8 @@ static int charger_manager_probe(struct platform_device *pdev)
|
|
cm->charger_psy.num_properties++;
|
|
cm->charger_psy.num_properties++;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ INIT_DELAYED_WORK(&cm->fullbatt_vchk_work, fullbatt_vchk);
|
|
|
|
+
|
|
ret = power_supply_register(NULL, &cm->charger_psy);
|
|
ret = power_supply_register(NULL, &cm->charger_psy);
|
|
if (ret) {
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Cannot register charger-manager with"
|
|
dev_err(&pdev->dev, "Cannot register charger-manager with"
|
|
@@ -928,6 +1185,15 @@ static int charger_manager_probe(struct platform_device *pdev)
|
|
list_add(&cm->entry, &cm_list);
|
|
list_add(&cm->entry, &cm_list);
|
|
mutex_unlock(&cm_list_mtx);
|
|
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;
|
|
return 0;
|
|
|
|
|
|
err_chg_enable:
|
|
err_chg_enable:
|
|
@@ -958,9 +1224,17 @@ static int __devexit charger_manager_remove(struct platform_device *pdev)
|
|
list_del(&cm->entry);
|
|
list_del(&cm->entry);
|
|
mutex_unlock(&cm_list_mtx);
|
|
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,
|
|
regulator_bulk_free(desc->num_charger_regulators,
|
|
desc->charger_regulators);
|
|
desc->charger_regulators);
|
|
power_supply_unregister(&cm->charger_psy);
|
|
power_supply_unregister(&cm->charger_psy);
|
|
|
|
+
|
|
|
|
+ try_charger_enable(cm, false);
|
|
|
|
+
|
|
kfree(cm->charger_psy.properties);
|
|
kfree(cm->charger_psy.properties);
|
|
kfree(cm->charger_stat);
|
|
kfree(cm->charger_stat);
|
|
kfree(cm->desc);
|
|
kfree(cm->desc);
|
|
@@ -975,6 +1249,18 @@ static const struct platform_device_id charger_manager_id[] = {
|
|
};
|
|
};
|
|
MODULE_DEVICE_TABLE(platform, 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)
|
|
static int cm_suspend_prepare(struct device *dev)
|
|
{
|
|
{
|
|
struct charger_manager *cm = dev_get_drvdata(dev);
|
|
struct charger_manager *cm = dev_get_drvdata(dev);
|
|
@@ -1000,6 +1286,8 @@ static int cm_suspend_prepare(struct device *dev)
|
|
cm_suspended = true;
|
|
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_ext_pwr_inserted = is_ext_pwr_online(cm);
|
|
cm->status_save_batt = is_batt_present(cm);
|
|
cm->status_save_batt = is_batt_present(cm);
|
|
|
|
|
|
@@ -1027,11 +1315,40 @@ static void cm_suspend_complete(struct device *dev)
|
|
cm_rtc_set = false;
|
|
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));
|
|
|
|
+ }
|
|
|
|
+ device_set_wakeup_capable(cm->dev, false);
|
|
uevent_notify(cm, NULL);
|
|
uevent_notify(cm, NULL);
|
|
}
|
|
}
|
|
|
|
|
|
static const struct dev_pm_ops charger_manager_pm = {
|
|
static const struct dev_pm_ops charger_manager_pm = {
|
|
.prepare = cm_suspend_prepare,
|
|
.prepare = cm_suspend_prepare,
|
|
|
|
+ .suspend_noirq = cm_suspend_noirq,
|
|
.complete = cm_suspend_complete,
|
|
.complete = cm_suspend_complete,
|
|
};
|
|
};
|
|
|
|
|
|
@@ -1048,16 +1365,91 @@ static struct platform_driver charger_manager_driver = {
|
|
|
|
|
|
static int __init charger_manager_init(void)
|
|
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);
|
|
return platform_driver_register(&charger_manager_driver);
|
|
}
|
|
}
|
|
late_initcall(charger_manager_init);
|
|
late_initcall(charger_manager_init);
|
|
|
|
|
|
static void __exit charger_manager_cleanup(void)
|
|
static void __exit charger_manager_cleanup(void)
|
|
{
|
|
{
|
|
|
|
+ destroy_workqueue(cm_wq);
|
|
|
|
+ cm_wq = NULL;
|
|
|
|
+
|
|
platform_driver_unregister(&charger_manager_driver);
|
|
platform_driver_unregister(&charger_manager_driver);
|
|
}
|
|
}
|
|
module_exit(charger_manager_cleanup);
|
|
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 <myungjoo.ham@samsung.com>");
|
|
MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
|
|
MODULE_DESCRIPTION("Charger Manager");
|
|
MODULE_DESCRIPTION("Charger Manager");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_LICENSE("GPL");
|