| #include <linux/module.h> |
| #include <linux/types.h> |
| #include <linux/init.h> |
| #include <linux/slab.h> |
| #include <linux/device.h> |
| #include <linux/err.h> |
| #include <linux/power_supply.h> |
| #include <linux/thermal.h> |
| #include <linux/extcon.h> |
| #include <linux/power/battery_id.h> |
| #include <linux/notifier.h> |
| #include <linux/usb/otg.h> |
| #include "power_supply.h" |
| #include "power_supply_charger.h" |
| |
| struct work_struct notifier_work; |
| #define MAX_CHARGER_COUNT 5 |
| |
| static LIST_HEAD(algo_list); |
| |
| struct power_supply_charger { |
| bool is_cable_evt_reg; |
| /*cache battery and charger properties */ |
| struct list_head chrgr_cache_lst; |
| struct list_head batt_cache_lst; |
| struct list_head evt_queue; |
| struct work_struct algo_trigger_work; |
| struct mutex evt_lock; |
| wait_queue_head_t wait_chrg_enable; |
| }; |
| |
| struct charger_cable { |
| struct work_struct work; |
| struct notifier_block nb; |
| struct extcon_chrgr_cbl_props cable_props; |
| enum extcon_cable_name extcon_cable_type; |
| enum power_supply_charger_cable_type psy_cable_type; |
| struct extcon_specific_cable_nb extcon_dev; |
| struct extcon_dev *edev; |
| }; |
| |
| static struct power_supply_charger psy_chrgr; |
| |
| static struct charger_cable cable_list[] = { |
| { |
| .psy_cable_type = POWER_SUPPLY_CHARGER_TYPE_USB_SDP, |
| .extcon_cable_type = EXTCON_SDP, |
| }, |
| { |
| .psy_cable_type = POWER_SUPPLY_CHARGER_TYPE_USB_CDP, |
| .extcon_cable_type = EXTCON_CDP, |
| }, |
| { |
| .psy_cable_type = POWER_SUPPLY_CHARGER_TYPE_USB_DCP, |
| .extcon_cable_type = EXTCON_DCP, |
| }, |
| { |
| .psy_cable_type = POWER_SUPPLY_CHARGER_TYPE_USB_ACA, |
| .extcon_cable_type = EXTCON_ACA, |
| }, |
| { |
| .psy_cable_type = POWER_SUPPLY_CHARGER_TYPE_ACA_DOCK, |
| .extcon_cable_type = EXTCON_ACA, |
| }, |
| { |
| .psy_cable_type = POWER_SUPPLY_CHARGER_TYPE_SE1, |
| .extcon_cable_type = EXTCON_TA, |
| }, |
| { |
| .psy_cable_type = POWER_SUPPLY_CHARGER_TYPE_AC, |
| .extcon_cable_type = EXTCON_AC, |
| }, |
| }; |
| |
| static int get_supplied_by_list(struct power_supply *psy, |
| struct power_supply *psy_lst[]); |
| |
| static int handle_cable_notification(struct notifier_block *nb, |
| unsigned long event, void *data); |
| struct usb_phy *otg_xceiver; |
| struct notifier_block nb = { |
| .notifier_call = handle_cable_notification, |
| }; |
| static void configure_chrgr_source(struct charger_cable *cable_lst); |
| |
| struct charger_cable *get_cable(unsigned long usb_chrgr_type) |
| { |
| |
| switch (usb_chrgr_type) { |
| case POWER_SUPPLY_CHARGER_TYPE_USB_SDP: |
| return &cable_list[0]; |
| case POWER_SUPPLY_CHARGER_TYPE_USB_CDP: |
| return &cable_list[1]; |
| case POWER_SUPPLY_CHARGER_TYPE_USB_DCP: |
| return &cable_list[2]; |
| case POWER_SUPPLY_CHARGER_TYPE_USB_ACA: |
| return &cable_list[3]; |
| case POWER_SUPPLY_CHARGER_TYPE_ACA_DOCK: |
| return &cable_list[4]; |
| case POWER_SUPPLY_CHARGER_TYPE_AC: |
| return &cable_list[6]; |
| case POWER_SUPPLY_CHARGER_TYPE_SE1: |
| return &cable_list[5]; |
| } |
| |
| return NULL; |
| } |
| |
| |
| static void notifier_event_worker(struct work_struct *work) |
| { |
| configure_chrgr_source(cable_list); |
| } |
| |
| static int process_cable_props(struct power_supply_cable_props *cap) |
| { |
| |
| struct charger_cable *cable = NULL; |
| |
| pr_info("%s: event:%d, type:%d, ma:%d\n", |
| __func__, cap->chrg_evt, cap->chrg_type, cap->ma); |
| |
| cable = get_cable(cap->chrg_type); |
| if (!cable) { |
| |
| pr_err("%s:Error in getting charger cable\n", __func__); |
| return -EINVAL; |
| } |
| |
| switch (cap->chrg_evt) { |
| case POWER_SUPPLY_CHARGER_EVENT_CONNECT: |
| case POWER_SUPPLY_CHARGER_EVENT_RESUME: |
| cable->cable_props.cable_stat = EXTCON_CHRGR_CABLE_CONNECTED; |
| break; |
| case POWER_SUPPLY_CHARGER_EVENT_UPDATE: |
| cable->cable_props.cable_stat = EXTCON_CHRGR_CABLE_UPDATED; |
| break; |
| case POWER_SUPPLY_CHARGER_EVENT_DISCONNECT: |
| cable->cable_props.cable_stat = EXTCON_CHRGR_CABLE_DISCONNECTED; |
| break; |
| case POWER_SUPPLY_CHARGER_EVENT_SUSPEND: |
| cable->cable_props.cable_stat = EXTCON_CHRGR_CABLE_SUSPENDED; |
| break; |
| default: |
| pr_err("%s:Invalid cable event\n", __func__); |
| return -EINVAL; |
| } |
| |
| cable->cable_props.ma = cap->ma; |
| schedule_work(¬ifier_work); |
| |
| return 0; |
| |
| } |
| |
| static int handle_cable_notification(struct notifier_block *nb, |
| unsigned long event, void *data) |
| { |
| struct power_supply_cable_props cap; |
| |
| memcpy(&cap, data, sizeof(struct power_supply_cable_props)); |
| |
| if (event != USB_EVENT_CHARGER && event != POWER_SUPPLY_CABLE_EVENT) |
| return NOTIFY_DONE; |
| |
| process_cable_props(&cap); |
| |
| return NOTIFY_OK; |
| } |
| |
| static int register_notifier(void) |
| { |
| int retval; |
| |
| otg_xceiver = usb_get_phy(USB_PHY_TYPE_USB2); |
| if (!otg_xceiver) { |
| pr_err("failure to get otg transceiver\n"); |
| retval = -EIO; |
| goto notifier_reg_failed; |
| } |
| retval = usb_register_notifier(otg_xceiver, &nb); |
| if (retval) { |
| pr_err("failure to register otg notifier\n"); |
| goto notifier_reg_failed; |
| } |
| |
| retval = power_supply_reg_notifier(&nb); |
| if (retval) { |
| pr_err("failure to register power_supply notifier\n"); |
| goto notifier_reg_failed; |
| |
| } |
| |
| INIT_WORK(¬ifier_work, notifier_event_worker); |
| |
| return 0; |
| |
| notifier_reg_failed: |
| return retval; |
| } |
| |
| static int charger_cable_notifier(struct notifier_block *nb, |
| unsigned long event, void *ptr); |
| static void charger_cable_event_worker(struct work_struct *work); |
| struct charging_algo *power_supply_get_charging_algo |
| (struct power_supply *, struct ps_batt_chg_prof *); |
| |
| static void init_charger_cables(struct charger_cable *cable_lst, int count) |
| { |
| struct charger_cable *cable; |
| struct extcon_chrgr_cbl_props cable_props; |
| const char *cable_name; |
| struct power_supply_cable_props cap; |
| |
| register_notifier(); |
| |
| while (--count) { |
| cable = cable_lst++; |
| /* initialize cable instance */ |
| INIT_WORK(&cable->work, charger_cable_event_worker); |
| cable->nb.notifier_call = charger_cable_notifier; |
| cable->cable_props.cable_stat = EXTCON_CHRGR_CABLE_DISCONNECTED; |
| cable->cable_props.ma = 0; |
| cable_name = extcon_cable_name[cable->extcon_cable_type]; |
| |
| if (extcon_register_interest(&cable->extcon_dev, |
| NULL, cable_name, &cable->nb)) |
| continue; |
| |
| cable->edev = cable->extcon_dev.edev; |
| |
| if (!cable->edev) |
| continue; |
| |
| if (cable->edev->get_cable_properties(cable_name, |
| (void *)&cable_props)) { |
| continue; |
| |
| } else if (cable_props.cable_stat != |
| cable->cable_props.cable_stat) { |
| cable->cable_props.cable_stat = cable_props.cable_stat; |
| cable->cable_props.ma = cable_props.ma; |
| } |
| } |
| |
| if (!otg_get_chrg_status(otg_xceiver, &cap)) |
| process_cable_props(&cap); |
| |
| } |
| |
| static inline int is_charging_can_be_enabled(struct power_supply *psy) |
| { |
| int health; |
| |
| health = HEALTH(psy); |
| if (IS_BATTERY(psy)) { |
| return (health == POWER_SUPPLY_HEALTH_GOOD) || |
| (health == POWER_SUPPLY_HEALTH_DEAD); |
| } else { |
| return |
| ((CURRENT_THROTTLE_ACTION(psy) != PSY_THROTTLE_DISABLE_CHARGER) && |
| (CURRENT_THROTTLE_ACTION(psy) != PSY_THROTTLE_DISABLE_CHARGING) && |
| (INLMT(psy) >= 100) && (health == POWER_SUPPLY_HEALTH_GOOD)); |
| } |
| } |
| |
| static inline void get_cur_chrgr_prop(struct power_supply *psy, |
| struct charger_props *chrgr_prop) |
| { |
| chrgr_prop->is_charging = IS_CHARGING_ENABLED(psy); |
| chrgr_prop->name = psy->name; |
| chrgr_prop->online = IS_ONLINE(psy); |
| chrgr_prop->present = IS_PRESENT(psy); |
| chrgr_prop->cable = CABLE_TYPE(psy); |
| chrgr_prop->health = HEALTH(psy); |
| chrgr_prop->tstamp = get_jiffies_64(); |
| |
| } |
| |
| static inline int get_chrgr_prop_cache(struct power_supply *psy, |
| struct charger_props *chrgr_cache) |
| { |
| |
| struct charger_props *chrgr_prop; |
| int ret = -ENODEV; |
| |
| list_for_each_entry(chrgr_prop, &psy_chrgr.chrgr_cache_lst, node) { |
| if (!strcmp(chrgr_prop->name, psy->name)) { |
| memcpy(chrgr_cache, chrgr_prop, sizeof(*chrgr_cache)); |
| ret = 0; |
| break; |
| } |
| } |
| |
| return ret; |
| } |
| |
| static void dump_charger_props(struct charger_props *props) |
| { |
| pr_devel("%s:name=%s present=%d is_charging=%d health=%d online=%d cable=%lu tstamp=%lu\n", |
| __func__, props->name, props->present, props->is_charging, |
| props->health, props->online, props->cable, props->tstamp); |
| } |
| |
| static void dump_battery_props(struct batt_props *props) |
| { |
| pr_devel("%s:name=%s voltage_now=%ld current_now=%ld temperature=%d status=%ld health=%d tstamp=%lu algo_stat=%d ", |
| __func__, props->name, props->voltage_now, props->current_now, |
| props->temperature, props->status, props->health, |
| props->tstamp, props->algo_stat); |
| } |
| |
| static inline void cache_chrgr_prop(struct charger_props *chrgr_prop_new) |
| { |
| |
| struct charger_props *chrgr_cache; |
| |
| list_for_each_entry(chrgr_cache, &psy_chrgr.chrgr_cache_lst, node) { |
| if (!strcmp(chrgr_cache->name, chrgr_prop_new->name)) |
| goto update_props; |
| } |
| |
| chrgr_cache = kzalloc(sizeof(*chrgr_cache), GFP_KERNEL); |
| if (chrgr_cache == NULL) { |
| pr_err("%s:Error in allocating memory\n", __func__); |
| return; |
| } |
| |
| INIT_LIST_HEAD(&chrgr_cache->node); |
| list_add_tail(&chrgr_cache->node, &psy_chrgr.chrgr_cache_lst); |
| |
| chrgr_cache->name = chrgr_prop_new->name; |
| |
| update_props: |
| chrgr_cache->is_charging = chrgr_prop_new->is_charging; |
| chrgr_cache->online = chrgr_prop_new->online; |
| chrgr_cache->health = chrgr_prop_new->health; |
| chrgr_cache->present = chrgr_prop_new->present; |
| chrgr_cache->cable = chrgr_prop_new->cable; |
| chrgr_cache->tstamp = chrgr_prop_new->tstamp; |
| } |
| |
| |
| static inline bool is_chrgr_prop_changed(struct power_supply *psy) |
| { |
| struct charger_props chrgr_prop_cache, chrgr_prop; |
| |
| get_cur_chrgr_prop(psy, &chrgr_prop); |
| /* Get cached battery property. If no cached property available |
| * then cache the new property and return true |
| */ |
| if (get_chrgr_prop_cache(psy, &chrgr_prop_cache)) { |
| cache_chrgr_prop(&chrgr_prop); |
| return true; |
| } |
| |
| dump_charger_props(&chrgr_prop); |
| dump_charger_props(&chrgr_prop_cache); |
| |
| if (!IS_CHARGER_PROP_CHANGED(chrgr_prop, chrgr_prop_cache)) |
| return false; |
| |
| cache_chrgr_prop(&chrgr_prop); |
| return true; |
| } |
| static void cache_successive_samples(long *sample_array, long new_sample) |
| { |
| int i; |
| |
| for (i = 0; i < MAX_CUR_VOLT_SAMPLES - 1; ++i) |
| *(sample_array + i) = *(sample_array + i + 1); |
| |
| *(sample_array + i) = new_sample; |
| } |
| |
| static inline void cache_bat_prop(struct batt_props *bat_prop_new, bool force) |
| { |
| |
| struct batt_props *bat_cache; |
| |
| /* Find entry in cache list. If an entry is located update |
| * the existing entry else create new entry in the list */ |
| list_for_each_entry(bat_cache, &psy_chrgr.batt_cache_lst, node) { |
| if (!strcmp(bat_cache->name, bat_prop_new->name)) |
| goto update_props; |
| } |
| |
| bat_cache = kzalloc(sizeof(*bat_cache), GFP_KERNEL); |
| if (bat_cache == NULL) { |
| pr_err("%s:Error in allocating memory\n", __func__); |
| return; |
| } |
| INIT_LIST_HEAD(&bat_cache->node); |
| list_add_tail(&bat_cache->node, &psy_chrgr.batt_cache_lst); |
| |
| bat_cache->name = bat_prop_new->name; |
| |
| update_props: |
| if (time_after(bat_prop_new->tstamp, |
| (bat_cache->tstamp + DEF_CUR_VOLT_SAMPLE_JIFF)) || force || |
| bat_cache->tstamp == 0) { |
| cache_successive_samples(bat_cache->voltage_now_cache, |
| bat_prop_new->voltage_now); |
| cache_successive_samples(bat_cache->current_now_cache, |
| bat_prop_new->current_now); |
| bat_cache->tstamp = bat_prop_new->tstamp; |
| } |
| |
| bat_cache->voltage_now = bat_prop_new->voltage_now; |
| bat_cache->current_now = bat_prop_new->current_now; |
| bat_cache->health = bat_prop_new->health; |
| |
| bat_cache->temperature = bat_prop_new->temperature; |
| bat_cache->status = bat_prop_new->status; |
| bat_cache->algo_stat = bat_prop_new->algo_stat; |
| bat_cache->throttle_state = bat_prop_new->throttle_state; |
| } |
| |
| static inline int get_bat_prop_cache(struct power_supply *psy, |
| struct batt_props *bat_cache) |
| { |
| struct batt_props *bat_prop; |
| int ret = -ENODEV; |
| |
| list_for_each_entry(bat_prop, &psy_chrgr.batt_cache_lst, node) { |
| if (!strcmp(bat_prop->name, psy->name)) { |
| memcpy(bat_cache, bat_prop, sizeof(*bat_cache)); |
| ret = 0; |
| break; |
| } |
| } |
| |
| return ret; |
| } |
| |
| static inline void get_cur_bat_prop(struct power_supply *psy, |
| struct batt_props *bat_prop) |
| { |
| struct batt_props bat_prop_cache; |
| int ret; |
| |
| bat_prop->name = psy->name; |
| bat_prop->voltage_now = VOLTAGE_OCV(psy) / 1000; |
| bat_prop->current_now = CURRENT_NOW(psy) / 1000; |
| bat_prop->temperature = TEMPERATURE(psy) / 10; |
| bat_prop->status = STATUS(psy); |
| bat_prop->health = HEALTH(psy); |
| bat_prop->tstamp = get_jiffies_64(); |
| bat_prop->throttle_state = CURRENT_THROTTLE_STATE(psy); |
| |
| /* Populate cached algo data to new profile */ |
| ret = get_bat_prop_cache(psy, &bat_prop_cache); |
| if (!ret) |
| bat_prop->algo_stat = bat_prop_cache.algo_stat; |
| } |
| |
| static inline bool is_batt_prop_changed(struct power_supply *psy) |
| { |
| struct batt_props bat_prop_cache, bat_prop; |
| |
| /* Get cached battery property. If no cached property available |
| * then cache the new property and return true |
| */ |
| get_cur_bat_prop(psy, &bat_prop); |
| if (get_bat_prop_cache(psy, &bat_prop_cache)) { |
| cache_bat_prop(&bat_prop, false); |
| return true; |
| } |
| |
| dump_battery_props(&bat_prop); |
| dump_battery_props(&bat_prop_cache); |
| |
| if (!IS_BAT_PROP_CHANGED(bat_prop, bat_prop_cache)) |
| return false; |
| |
| cache_bat_prop(&bat_prop, false); |
| return true; |
| } |
| |
| static inline bool is_supplied_to_has_ext_pwr_changed(struct power_supply *psy) |
| { |
| int i; |
| struct power_supply *psb; |
| bool is_pwr_changed_defined = true; |
| |
| for (i = 0; i < psy->num_supplicants; i++) { |
| psb = |
| power_supply_get_by_name(psy-> |
| supplied_to[i]); |
| if (psb && !psb->external_power_changed) |
| is_pwr_changed_defined &= false; |
| } |
| |
| return is_pwr_changed_defined; |
| |
| } |
| |
| static inline bool is_supplied_by_changed(struct power_supply *psy) |
| { |
| int cnt; |
| struct power_supply *chrgr_lst[MAX_CHARGER_COUNT]; |
| |
| cnt = get_supplied_by_list(psy, chrgr_lst); |
| while (cnt--) { |
| if ((IS_CHARGER(chrgr_lst[cnt])) && |
| is_chrgr_prop_changed(chrgr_lst[cnt])) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static inline bool is_trigger_charging_algo(struct power_supply *psy) |
| { |
| /* trigger charging alorithm if battery or |
| * charger properties are changed. Also no need to |
| * invoke algorithm for power_supply_changed from |
| * charger, if all supplied_to has the ext_port_changed defined. |
| * On invoking the ext_port_changed the supplied to can send |
| * power_supplied_changed event. |
| */ |
| |
| if ((IS_CHARGER(psy) && !is_supplied_to_has_ext_pwr_changed(psy)) && |
| is_chrgr_prop_changed(psy)) |
| return true; |
| |
| if ((IS_BATTERY(psy)) && (is_batt_prop_changed(psy) || |
| is_supplied_by_changed(psy))) |
| return true; |
| |
| return false; |
| } |
| |
| static int get_supplied_by_list(struct power_supply *psy, |
| struct power_supply *psy_lst[]) |
| { |
| struct class_dev_iter iter; |
| struct device *dev; |
| struct power_supply *pst; |
| int cnt = 0, i, j; |
| |
| if (!IS_BATTERY(psy)) |
| return 0; |
| |
| /* Identify chargers which are supplying power to the battery */ |
| class_dev_iter_init(&iter, power_supply_class, NULL, NULL); |
| while ((dev = class_dev_iter_next(&iter))) { |
| pst = (struct power_supply *)dev_get_drvdata(dev); |
| if (!IS_CHARGER(pst)) |
| continue; |
| for (i = 0; i < pst->num_supplicants; i++) { |
| if (!strcmp(pst->supplied_to[i], psy->name)) |
| psy_lst[cnt++] = pst; |
| } |
| } |
| class_dev_iter_exit(&iter); |
| |
| if (cnt <= 1) |
| return cnt; |
| |
| /*sort based on priority. 0 has the highest priority */ |
| for (i = 0; i < cnt; ++i) |
| for (j = 0; j < cnt; ++j) |
| if (PRIORITY(psy_lst[j]) > PRIORITY(psy_lst[i])) |
| swap(psy_lst[j], psy_lst[i]); |
| |
| return cnt; |
| } |
| |
| static int get_battery_status(struct power_supply *psy) |
| { |
| int cnt, status, ret; |
| struct power_supply *chrgr_lst[MAX_CHARGER_COUNT]; |
| struct batt_props bat_prop; |
| int health; |
| |
| if (!IS_BATTERY(psy)) |
| return -EINVAL; |
| |
| ret = get_bat_prop_cache(psy, &bat_prop); |
| if (ret) |
| return ret; |
| |
| status = POWER_SUPPLY_STATUS_DISCHARGING; |
| cnt = get_supplied_by_list(psy, chrgr_lst); |
| |
| |
| while (cnt--) { |
| if (IS_PRESENT(chrgr_lst[cnt])) |
| status = POWER_SUPPLY_STATUS_NOT_CHARGING; |
| |
| if (is_charging_can_be_enabled(chrgr_lst[cnt]) && |
| (IS_HEALTH_GOOD(chrgr_lst[cnt]))) { |
| health = HEALTH(psy); |
| if ((health == POWER_SUPPLY_HEALTH_GOOD) || |
| (health == POWER_SUPPLY_HEALTH_DEAD)) { |
| /* do charging with Good / Dead battery */ |
| if ((bat_prop.algo_stat == |
| PSY_ALGO_STAT_FULL) || |
| (bat_prop.algo_stat == |
| PSY_ALGO_STAT_MAINT)) |
| status = POWER_SUPPLY_STATUS_FULL; |
| else if (IS_CHARGING_ENABLED(chrgr_lst[cnt])) |
| status = POWER_SUPPLY_STATUS_CHARGING; |
| } |
| } |
| } |
| pr_devel("%s: Set status=%d for %s\n", __func__, status, psy->name); |
| |
| return status; |
| } |
| |
| static void update_charger_online(struct power_supply *psy) |
| { |
| if (IS_CHARGER_ENABLED(psy)) |
| set_charger_online(psy, 1); |
| else |
| set_charger_online(psy, 0); |
| } |
| |
| static inline void cache_cur_batt_prop_force(struct power_supply *psb) |
| { |
| struct batt_props bat_prop; |
| |
| if (!IS_BATTERY(psb)) |
| return; |
| |
| get_cur_bat_prop(psb, &bat_prop); |
| cache_bat_prop(&bat_prop, true); |
| } |
| |
| static void update_sysfs(struct power_supply *psy) |
| { |
| int i, cnt; |
| struct power_supply *psb; |
| struct power_supply *chrgr_lst[MAX_CHARGER_COUNT]; |
| |
| if (IS_BATTERY(psy)) { |
| /* set charger online */ |
| cnt = get_supplied_by_list(psy, chrgr_lst); |
| while (cnt--) { |
| if (!IS_PRESENT(chrgr_lst[cnt])) |
| continue; |
| |
| update_charger_online(psy); |
| } |
| /* set battery status */ |
| if (set_battery_status(psy, get_battery_status(psy))) |
| /* forcefully cache the battery properties */ |
| cache_cur_batt_prop_force(psy); |
| } else { |
| /*set charger online */ |
| update_charger_online(psy); |
| /*set battery status */ |
| for (i = 0; i < psy->num_supplicants; i++) { |
| psb = |
| power_supply_get_by_name(psy-> |
| supplied_to[i]); |
| if (psb && IS_BATTERY(psb) && IS_PRESENT(psb)) |
| if (set_battery_status(psb, |
| get_battery_status(psb))) |
| /* |
| * forcefully cache the battery |
| * properties |
| */ |
| cache_cur_batt_prop_force(psy); |
| } |
| } |
| } |
| |
| static int trigger_algo(struct power_supply *psy) |
| { |
| unsigned long cc = 0, cv = 0, cc_min; |
| struct power_supply *chrgr_lst[MAX_CHARGER_COUNT]; |
| struct batt_props bat_prop; |
| struct charging_algo *algo; |
| struct ps_batt_chg_prof chrg_profile; |
| int cnt; |
| |
| if (psy->type != POWER_SUPPLY_TYPE_BATTERY) |
| return 0; |
| |
| if (get_batt_prop(&chrg_profile)) { |
| pr_err("%s:Error in getting charge profile\n", __func__); |
| return -EINVAL; |
| } |
| |
| |
| get_bat_prop_cache(psy, &bat_prop); |
| |
| algo = power_supply_get_charging_algo(psy, &chrg_profile); |
| if (!algo) { |
| pr_err("%s:Error in getting charging algo!!\n", __func__); |
| return -EINVAL; |
| } |
| |
| bat_prop.algo_stat = algo->get_next_cc_cv(bat_prop, |
| chrg_profile, &cc, &cv); |
| |
| pr_info("%s:Algo_status:%d\n", __func__, bat_prop.algo_stat); |
| |
| cache_bat_prop(&bat_prop, false); |
| |
| if (!cc || !cv) |
| return -ENODATA; |
| |
| /* CC needs to be updated for all chargers which are supplying |
| * power to this battery to ensure that the sum of CCs of all |
| * chargers are never more than the CC selected by the algo. |
| * The CC is set based on the charger priority. |
| */ |
| cnt = get_supplied_by_list(psy, chrgr_lst); |
| |
| while (cnt--) { |
| if (!IS_PRESENT(chrgr_lst[cnt])) |
| continue; |
| |
| cc_min = min_t(unsigned long, MAX_CC(chrgr_lst[cnt]), cc); |
| if (cc_min < 0) |
| cc_min = 0; |
| cc -= cc_min; |
| set_cc(chrgr_lst[cnt], cc_min); |
| set_cv(chrgr_lst[cnt], cv); |
| } |
| |
| if ((bat_prop.algo_stat == PSY_ALGO_STAT_NOT_CHARGE) || |
| (bat_prop.algo_stat == PSY_ALGO_STAT_FULL)) |
| return -EOPNOTSUPP; |
| |
| return 0; |
| } |
| |
| static inline void wait_for_charging_enabled(struct power_supply *psy) |
| { |
| wait_event_timeout(psy_chrgr.wait_chrg_enable, |
| (IS_CHARGING_ENABLED(psy)), HZ); |
| } |
| |
| static inline void enable_supplied_by_charging |
| (struct power_supply *psy, bool is_enable) |
| { |
| struct power_supply *chrgr_lst[MAX_CHARGER_COUNT]; |
| int cnt; |
| |
| if (psy->type != POWER_SUPPLY_TYPE_BATTERY) |
| return; |
| /* Get list of chargers supplying power to this battery and |
| * disable charging for all chargers |
| */ |
| cnt = get_supplied_by_list(psy, chrgr_lst); |
| if (cnt == 0) |
| return; |
| while (cnt--) { |
| if (!IS_PRESENT(chrgr_lst[cnt])) |
| continue; |
| if (is_enable && is_charging_can_be_enabled(chrgr_lst[cnt]) && |
| is_charging_can_be_enabled(psy)) { |
| enable_charging(chrgr_lst[cnt]); |
| wait_for_charging_enabled(chrgr_lst[cnt]); |
| } else |
| disable_charging(chrgr_lst[cnt]); |
| } |
| } |
| |
| static void __power_supply_trigger_charging_handler(struct power_supply *psy) |
| { |
| int i; |
| struct power_supply *psb = NULL; |
| |
| mutex_lock(&psy_chrgr.evt_lock); |
| |
| if (is_trigger_charging_algo(psy)) { |
| |
| if (IS_BATTERY(psy)) { |
| if (trigger_algo(psy)) |
| enable_supplied_by_charging(psy, false); |
| else |
| enable_supplied_by_charging(psy, true); |
| } else if (IS_CHARGER(psy)) { |
| for (i = 0; i < psy->num_supplicants; i++) { |
| psb = |
| power_supply_get_by_name(psy-> |
| supplied_to[i]); |
| |
| if (psb && IS_BATTERY(psb) && IS_PRESENT(psb)) { |
| if (trigger_algo(psb)) { |
| disable_charging(psy); |
| break; |
| } else if (is_charging_can_be_enabled |
| (psy)) { |
| enable_charging(psy); |
| wait_for_charging_enabled(psy); |
| } |
| } |
| } |
| } |
| update_sysfs(psy); |
| power_supply_changed(psy); |
| } |
| mutex_unlock(&psy_chrgr.evt_lock); |
| |
| } |
| |
| static int __trigger_charging_handler(struct device *dev, void *data) |
| { |
| struct power_supply *psy = dev_get_drvdata(dev); |
| |
| __power_supply_trigger_charging_handler(psy); |
| |
| return 0; |
| } |
| |
| static void trigger_algo_psy_class(struct work_struct *work) |
| { |
| class_for_each_device(power_supply_class, NULL, NULL, |
| __trigger_charging_handler); |
| } |
| |
| static bool is_cable_connected(void) |
| { |
| int i; |
| struct charger_cable *cable; |
| |
| for (i = 0; i < ARRAY_SIZE(cable_list); ++i) { |
| cable = cable_list + i; |
| if (IS_CABLE_ACTIVE(cable->cable_props.cable_stat)) |
| return true; |
| } |
| return false; |
| } |
| |
| void power_supply_trigger_charging_handler(struct power_supply *psy) |
| { |
| if (!psy_chrgr.is_cable_evt_reg || !is_cable_connected()) |
| return; |
| |
| wake_up(&psy_chrgr.wait_chrg_enable); |
| |
| if (psy) |
| __power_supply_trigger_charging_handler(psy); |
| else |
| schedule_work(&psy_chrgr.algo_trigger_work); |
| |
| } |
| EXPORT_SYMBOL(power_supply_trigger_charging_handler); |
| |
| static inline int get_battery_thresholds(struct power_supply *psy, |
| struct psy_batt_thresholds *bat_thresh) |
| { |
| struct charging_algo *algo; |
| struct ps_batt_chg_prof chrg_profile; |
| |
| /* FIXME: Get iterm only for supplied_to arguments*/ |
| if (get_batt_prop(&chrg_profile)) { |
| pr_err("%s:Error in getting charge profile\n", __func__); |
| return -EINVAL; |
| } |
| |
| algo = power_supply_get_charging_algo(psy, &chrg_profile); |
| if (!algo) { |
| pr_err("%s:Error in getting charging algo!!\n", __func__); |
| return -EINVAL; |
| } |
| |
| if (algo->get_batt_thresholds) { |
| algo->get_batt_thresholds(chrg_profile, bat_thresh); |
| } else { |
| pr_err("%s:Error in getting battery thresholds from: %s\n", |
| __func__, algo->name); |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| static int select_chrgr_cable(struct device *dev, void *data) |
| { |
| struct power_supply *psy = dev_get_drvdata(dev); |
| struct charger_cable *cable, *max_ma_cable = NULL; |
| struct charger_cable *cable_lst = (struct charger_cable *)data; |
| int max_ma = -1, i; |
| |
| if (!IS_CHARGER(psy)) |
| return 0; |
| |
| mutex_lock(&psy_chrgr.evt_lock); |
| |
| /* get cable with maximum capability */ |
| for (i = 0; i < ARRAY_SIZE(cable_list); ++i) { |
| cable = cable_lst + i; |
| if ((!IS_CABLE_ACTIVE(cable->cable_props.cable_stat)) || |
| (!IS_SUPPORTED_CABLE(psy, cable->psy_cable_type))) |
| continue; |
| |
| if ((int)cable->cable_props.ma > max_ma) { |
| max_ma_cable = cable; |
| max_ma = cable->cable_props.ma; |
| } |
| } |
| |
| /* no cable connected. disable charging */ |
| if (!max_ma_cable) { |
| if ((IS_CHARGER_ENABLED(psy) || IS_CHARGING_ENABLED(psy))) { |
| disable_charging(psy); |
| disable_charger(psy); |
| } |
| set_cc(psy, 0); |
| set_cv(psy, 0); |
| set_inlmt(psy, 0); |
| |
| /* set present and online as 0 */ |
| set_present(psy, 0); |
| update_charger_online(psy); |
| |
| switch_cable(psy, POWER_SUPPLY_CHARGER_TYPE_NONE); |
| |
| mutex_unlock(&psy_chrgr.evt_lock); |
| power_supply_changed(psy); |
| return 0; |
| } |
| |
| /* cable type changed.New cable connected or existing cable |
| * capabilities changed.switch cable and enable charger and charging |
| */ |
| set_present(psy, 1); |
| |
| if (CABLE_TYPE(psy) != max_ma_cable->psy_cable_type) |
| switch_cable(psy, max_ma_cable->psy_cable_type); |
| |
| if (IS_CHARGER_CAN_BE_ENABLED(psy) && |
| (max_ma_cable->cable_props.ma >= 100)) { |
| struct psy_batt_thresholds bat_thresh; |
| memset(&bat_thresh, 0, sizeof(bat_thresh)); |
| enable_charger(psy); |
| |
| update_charger_online(psy); |
| |
| set_inlmt(psy, max_ma_cable->cable_props.ma); |
| if (!get_battery_thresholds(psy, &bat_thresh)) { |
| if (!ITERM(psy)) |
| SET_ITERM(psy, bat_thresh.iterm); |
| SET_MIN_TEMP(psy, bat_thresh.temp_min); |
| SET_MAX_TEMP(psy, bat_thresh.temp_max); |
| } |
| |
| } else { |
| set_inlmt(psy, max_ma_cable->cable_props.ma); |
| disable_charger(psy); |
| update_charger_online(psy); |
| } |
| |
| mutex_unlock(&psy_chrgr.evt_lock); |
| power_supply_trigger_charging_handler(NULL); |
| /* Cable status is same as previous. No action to be taken */ |
| return 0; |
| |
| } |
| |
| static void configure_chrgr_source(struct charger_cable *cable_lst) |
| { |
| class_for_each_device(power_supply_class, NULL, |
| cable_lst, select_chrgr_cable); |
| } |
| |
| static void charger_cable_event_worker(struct work_struct *work) |
| { |
| struct charger_cable *cable = |
| container_of(work, struct charger_cable, work); |
| struct extcon_chrgr_cbl_props cable_props; |
| |
| if (cable->edev-> |
| get_cable_properties(extcon_cable_name[cable->extcon_cable_type], |
| (void *)&cable_props)) { |
| pr_err("%s:Error in getting cable(%s) properties from extcon device(%s)", |
| __func__, extcon_cable_name[cable->extcon_cable_type], |
| cable->edev->name); |
| return; |
| } else { |
| if (cable_props.cable_stat != cable->cable_props.cable_stat) { |
| cable->cable_props.cable_stat = cable_props.cable_stat; |
| cable->cable_props.ma = cable_props.ma; |
| configure_chrgr_source(cable_list); |
| } |
| } |
| |
| } |
| |
| static int charger_cable_notifier(struct notifier_block *nb, |
| unsigned long stat, void *ptr) |
| { |
| struct charger_cable *cable = |
| container_of(nb, struct charger_cable, nb); |
| |
| schedule_work(&cable->work); |
| |
| return NOTIFY_DONE | NOTIFY_STOP_MASK; |
| } |
| |
| int psy_charger_throttle_charger(struct power_supply *psy, |
| unsigned long state) |
| { |
| int ret = 0; |
| |
| if (!IS_PRESENT(psy)) |
| return 0; |
| |
| if (state < 0 || state > MAX_THROTTLE_STATE(psy)) |
| return -EINVAL; |
| |
| mutex_lock(&psy_chrgr.evt_lock); |
| |
| switch THROTTLE_ACTION(psy, state) |
| { |
| |
| case PSY_THROTTLE_DISABLE_CHARGER: |
| SET_MAX_CC(psy, 0); |
| disable_charger(psy); |
| break; |
| case PSY_THROTTLE_DISABLE_CHARGING: |
| SET_MAX_CC(psy, 0); |
| disable_charging(psy); |
| break; |
| case PSY_THROTTLE_CC_LIMIT: |
| SET_MAX_CC(psy, THROTTLE_CC_VALUE(psy, state)); |
| break; |
| case PSY_THROTTLE_INPUT_LIMIT: |
| set_inlmt(psy, THROTTLE_CC_VALUE(psy, state)); |
| break; |
| default: |
| pr_err("%s:Invalid throttle action for %s\n", |
| __func__, psy->name); |
| ret = -EINVAL; |
| break; |
| } |
| mutex_unlock(&psy_chrgr.evt_lock); |
| |
| /* Configure the driver based on new state */ |
| if (!ret) |
| configure_chrgr_source(cable_list); |
| return ret; |
| } |
| EXPORT_SYMBOL(psy_charger_throttle_charger); |
| |
| int power_supply_register_charger(struct power_supply *psy) |
| { |
| int ret = 0; |
| |
| if (!psy_chrgr.is_cable_evt_reg) { |
| mutex_init(&psy_chrgr.evt_lock); |
| init_waitqueue_head(&psy_chrgr.wait_chrg_enable); |
| init_charger_cables(cable_list, ARRAY_SIZE(cable_list)); |
| INIT_LIST_HEAD(&psy_chrgr.chrgr_cache_lst); |
| INIT_LIST_HEAD(&psy_chrgr.batt_cache_lst); |
| INIT_WORK(&psy_chrgr.algo_trigger_work, trigger_algo_psy_class); |
| psy_chrgr.is_cable_evt_reg = true; |
| } |
| return ret; |
| } |
| EXPORT_SYMBOL(power_supply_register_charger); |
| |
| static inline void flush_charger_context(struct power_supply *psy) |
| { |
| struct charger_props *chrgr_prop, *tmp; |
| |
| list_for_each_entry_safe(chrgr_prop, tmp, |
| &psy_chrgr.chrgr_cache_lst, node) { |
| if (!strcmp(chrgr_prop->name, psy->name)) { |
| list_del(&chrgr_prop->node); |
| kfree(chrgr_prop); |
| } |
| } |
| } |
| int power_supply_unregister_charger(struct power_supply *psy) |
| { |
| flush_charger_context(psy); |
| return 0; |
| } |
| EXPORT_SYMBOL(power_supply_unregister_charger); |
| |
| int power_supply_register_charging_algo(struct charging_algo *algo) |
| { |
| struct charging_algo *algo_new; |
| |
| algo_new = kzalloc(sizeof(*algo_new), GFP_KERNEL); |
| if (algo_new == NULL) { |
| pr_err("%s: Error allocating memory for algo!!", __func__); |
| return -ENOMEM; |
| } |
| memcpy(algo_new, algo, sizeof(*algo_new)); |
| |
| list_add_tail(&algo_new->node, &algo_list); |
| return 0; |
| } |
| EXPORT_SYMBOL(power_supply_register_charging_algo); |
| |
| int power_supply_unregister_charging_algo(struct charging_algo *algo) |
| { |
| struct charging_algo *algo_l, *tmp; |
| |
| list_for_each_entry_safe(algo_l, tmp, &algo_list, node) { |
| if (!strcmp(algo_l->name, algo->name)) { |
| list_del(&algo_l->node); |
| kfree(algo_l); |
| } |
| } |
| return 0; |
| |
| } |
| EXPORT_SYMBOL(power_supply_unregister_charging_algo); |
| |
| #if 0 |
| static struct charging_algo *get_charging_algo_byname(char *algo_name) |
| { |
| struct charging_algo *algo; |
| |
| list_for_each_entry(algo, &algo_list, node) { |
| if (!strcmp(algo->name, algo_name)) |
| return algo; |
| } |
| |
| return NULL; |
| } |
| #endif |
| |
| static struct charging_algo *get_charging_algo_by_type |
| (enum batt_chrg_prof_type chrg_prof_type) |
| { |
| struct charging_algo *algo; |
| |
| list_for_each_entry(algo, &algo_list, node) { |
| if (algo->chrg_prof_type == chrg_prof_type) |
| return algo; |
| } |
| |
| return NULL; |
| } |
| |
| struct charging_algo *power_supply_get_charging_algo |
| (struct power_supply *psy, struct ps_batt_chg_prof *batt_prof) |
| { |
| return get_charging_algo_by_type(batt_prof->chrg_prof_type); |
| |
| } |
| EXPORT_SYMBOL_GPL(power_supply_get_charging_algo); |