diff --git a/Documentation/power/charger-manager.txt b/Documentation/power/charger-manager.txt index 081489f3db259d20526cbb4f4386139557d123ff..fdcca991df3067100330da55136d25b240fde3f5 100644 --- a/Documentation/power/charger-manager.txt +++ b/Documentation/power/charger-manager.txt @@ -98,6 +98,11 @@ battery), an instance of Charger Manager is attached to it. struct charger_desc { +char *psy_name; + : The power-supply-class name of the battery. Default is + "battery" if psy_name is NULL. Users can access the psy entries + at "/sys/class/power_supply/[psy_name]/". + enum polling_modes polling_mode; : CM_POLL_DISABLE: do not poll this battery. CM_POLL_ALWAYS: always poll this battery. @@ -106,6 +111,12 @@ enum polling_modes polling_mode; CM_POLL_CHARGING_ONLY: poll this battery if and only if the battery is being charged. +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 + charged and the battery voltage is equal to or greater than + fullbatt_uV. + unsigned int polling_interval_ms; : Required polling interval in ms. Charger Manager will poll this battery every polling_interval_ms or more frequently. @@ -131,10 +142,13 @@ char *psy_fuel_gauge; : Power-supply-class name of the fuel gauge. int (*temperature_out_of_range)(int *mC); +bool measure_battery_temp; : This callback returns 0 if the temperature is safe for charging, a positive number if it is too hot to charge, and a negative number if it is too cold to charge. With the variable mC, the callback returns the temperature in 1/1000 of centigrade. + The source of temperature can be battery or ambient one according to + the value of measure_battery_temp. }; 5. Other Considerations diff --git a/drivers/power/charger-manager.c b/drivers/power/charger-manager.c index 727a259ea46c9b6a68655872a6f7307fbc0b91cf..0378d019efae4454395485571e273ce45c8f6abf 100644 --- a/drivers/power/charger-manager.c +++ b/drivers/power/charger-manager.c @@ -121,6 +121,32 @@ static bool is_ext_pwr_online(struct charger_manager *cm) return online; } +/** + * get_batt_uV - Get the voltage level of the battery + * @cm: the Charger Manager representing the battery. + * @uV: the voltage level returned. + * + * Returns 0 if there is no error. + * Returns a negative value on error. + */ +static int get_batt_uV(struct charger_manager *cm, int *uV) +{ + union power_supply_propval val; + int ret; + + if (cm->fuel_gauge) + ret = cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_VOLTAGE_NOW, &val); + else + return -ENODEV; + + if (ret) + return ret; + + *uV = val.intval; + return 0; +} + /** * is_charging - Returns true if the battery is being charged. * @cm: the Charger Manager representing the battery. @@ -369,6 +395,208 @@ static bool cm_monitor(void) return stop; } +static int charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct charger_manager *cm = container_of(psy, + struct charger_manager, charger_psy); + struct charger_desc *desc = cm->desc; + int i, ret = 0, uV; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (is_charging(cm)) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else if (is_ext_pwr_online(cm)) + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + else + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case POWER_SUPPLY_PROP_HEALTH: + if (cm->emergency_stop > 0) + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (cm->emergency_stop < 0) + val->intval = POWER_SUPPLY_HEALTH_COLD; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_PRESENT: + if (is_batt_present(cm)) + val->intval = 1; + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = get_batt_uV(cm, &i); + val->intval = i; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_CURRENT_NOW, val); + break; + case POWER_SUPPLY_PROP_TEMP: + /* in thenth of centigrade */ + if (cm->last_temp_mC == INT_MIN) + desc->temperature_out_of_range(&cm->last_temp_mC); + val->intval = cm->last_temp_mC / 100; + if (!desc->measure_battery_temp) + ret = -ENODEV; + break; + case POWER_SUPPLY_PROP_TEMP_AMBIENT: + /* in thenth of centigrade */ + if (cm->last_temp_mC == INT_MIN) + desc->temperature_out_of_range(&cm->last_temp_mC); + val->intval = cm->last_temp_mC / 100; + if (desc->measure_battery_temp) + ret = -ENODEV; + break; + case POWER_SUPPLY_PROP_CAPACITY: + if (!cm->fuel_gauge) { + ret = -ENODEV; + break; + } + + if (!is_batt_present(cm)) { + /* There is no battery. Assume 100% */ + val->intval = 100; + break; + } + + ret = cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_CAPACITY, val); + if (ret) + break; + + if (val->intval > 100) { + val->intval = 100; + break; + } + if (val->intval < 0) + val->intval = 0; + + /* Do not adjust SOC when charging: voltage is overrated */ + if (is_charging(cm)) + break; + + /* + * If the capacity value is inconsistent, calibrate it base on + * the battery voltage values and the thresholds given as desc + */ + ret = get_batt_uV(cm, &uV); + if (ret) { + /* Voltage information not available. No calibration */ + ret = 0; + break; + } + + if (desc->fullbatt_uV > 0 && uV >= desc->fullbatt_uV && + !is_charging(cm)) { + val->intval = 100; + break; + } + + break; + case POWER_SUPPLY_PROP_ONLINE: + if (is_ext_pwr_online(cm)) + val->intval = 1; + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + if (cm->fuel_gauge) { + if (cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_CHARGE_FULL, val) == 0) + break; + } + + if (is_ext_pwr_online(cm)) { + /* Not full if it's charging. */ + if (is_charging(cm)) { + val->intval = 0; + break; + } + /* + * Full if it's powered but not charging andi + * not forced stop by emergency + */ + if (!cm->emergency_stop) { + val->intval = 1; + break; + } + } + + /* Full if it's over the fullbatt voltage */ + ret = get_batt_uV(cm, &uV); + if (!ret && desc->fullbatt_uV > 0 && uV >= desc->fullbatt_uV && + !is_charging(cm)) { + val->intval = 1; + break; + } + + /* Full if the cap is 100 */ + if (cm->fuel_gauge) { + ret = cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_CAPACITY, val); + if (!ret && val->intval >= 100 && !is_charging(cm)) { + val->intval = 1; + break; + } + } + + val->intval = 0; + ret = 0; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + if (is_charging(cm)) { + ret = cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_CHARGE_NOW, + val); + if (ret) { + val->intval = 1; + ret = 0; + } else { + /* If CHARGE_NOW is supplied, use it */ + val->intval = (val->intval > 0) ? + val->intval : 1; + } + } else { + val->intval = 0; + } + break; + default: + return -EINVAL; + } + return ret; +} + +#define NUM_CHARGER_PSY_OPTIONAL (4) +static enum power_supply_property default_charger_props[] = { + /* Guaranteed to provide */ + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CHARGE_FULL, + /* + * Optional properties are: + * POWER_SUPPLY_PROP_CHARGE_NOW, + * POWER_SUPPLY_PROP_CURRENT_NOW, + * POWER_SUPPLY_PROP_TEMP, and + * POWER_SUPPLY_PROP_TEMP_AMBIENT, + */ +}; + +static struct power_supply psy_default = { + .name = "battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = default_charger_props, + .num_properties = ARRAY_SIZE(default_charger_props), + .get_property = charger_get_property, +}; + /** * cm_setup_timer - For in-suspend monitoring setup wakeup alarm * for suspend_again. @@ -532,6 +760,7 @@ static int charger_manager_probe(struct platform_device *pdev) struct charger_desc *desc = dev_get_platdata(&pdev->dev); struct charger_manager *cm; int ret = 0, i = 0; + union power_supply_propval val; if (g_desc && !rtc_dev && g_desc->rtc_name) { rtc_dev = rtc_class_open(g_desc->rtc_name); @@ -626,11 +855,68 @@ static int charger_manager_probe(struct platform_device *pdev) platform_set_drvdata(pdev, cm); + memcpy(&cm->charger_psy, &psy_default, + sizeof(psy_default)); + if (!desc->psy_name) { + strncpy(cm->psy_name_buf, psy_default.name, + PSY_NAME_MAX); + } else { + strncpy(cm->psy_name_buf, desc->psy_name, PSY_NAME_MAX); + } + cm->charger_psy.name = cm->psy_name_buf; + + /* Allocate for psy properties because they may vary */ + cm->charger_psy.properties = kzalloc(sizeof(enum power_supply_property) + * (ARRAY_SIZE(default_charger_props) + + NUM_CHARGER_PSY_OPTIONAL), + GFP_KERNEL); + if (!cm->charger_psy.properties) { + dev_err(&pdev->dev, "Cannot allocate for psy properties.\n"); + ret = -ENOMEM; + goto err_chg_stat; + } + memcpy(cm->charger_psy.properties, default_charger_props, + sizeof(enum power_supply_property) * + ARRAY_SIZE(default_charger_props)); + cm->charger_psy.num_properties = psy_default.num_properties; + + /* Find which optional psy-properties are available */ + if (!cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_CHARGE_NOW, &val)) { + cm->charger_psy.properties[cm->charger_psy.num_properties] = + POWER_SUPPLY_PROP_CHARGE_NOW; + cm->charger_psy.num_properties++; + } + if (!cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_CURRENT_NOW, + &val)) { + cm->charger_psy.properties[cm->charger_psy.num_properties] = + POWER_SUPPLY_PROP_CURRENT_NOW; + cm->charger_psy.num_properties++; + } + if (!desc->measure_battery_temp) { + cm->charger_psy.properties[cm->charger_psy.num_properties] = + POWER_SUPPLY_PROP_TEMP_AMBIENT; + cm->charger_psy.num_properties++; + } + if (desc->measure_battery_temp) { + cm->charger_psy.properties[cm->charger_psy.num_properties] = + POWER_SUPPLY_PROP_TEMP; + cm->charger_psy.num_properties++; + } + + ret = power_supply_register(NULL, &cm->charger_psy); + if (ret) { + dev_err(&pdev->dev, "Cannot register charger-manager with" + " name \"%s\".\n", cm->charger_psy.name); + goto err_register; + } + ret = regulator_bulk_get(&pdev->dev, desc->num_charger_regulators, desc->charger_regulators); if (ret) { dev_err(&pdev->dev, "Cannot get charger regulators.\n"); - goto err_chg_stat; + goto err_bulk_get; } ret = try_charger_enable(cm, true); @@ -650,6 +936,10 @@ static int charger_manager_probe(struct platform_device *pdev) if (desc->charger_regulators) regulator_bulk_free(desc->num_charger_regulators, desc->charger_regulators); +err_bulk_get: + power_supply_unregister(&cm->charger_psy); +err_register: + kfree(cm->charger_psy.properties); err_chg_stat: kfree(cm->charger_stat); err_no_charger_stat: @@ -674,6 +964,9 @@ static int __devexit charger_manager_remove(struct platform_device *pdev) if (desc->charger_regulators) regulator_bulk_free(desc->num_charger_regulators, desc->charger_regulators); + + power_supply_unregister(&cm->charger_psy); + kfree(cm->charger_psy.properties); kfree(cm->charger_stat); kfree(cm->desc); kfree(cm); diff --git a/include/linux/power/charger-manager.h b/include/linux/power/charger-manager.h index 102c5b3f33258652ddd6332b89bab8d3d6394b11..4f75e531c112c176b7a29146c6581e857442dba2 100644 --- a/include/linux/power/charger-manager.h +++ b/include/linux/power/charger-manager.h @@ -47,8 +47,12 @@ struct charger_global_desc { /** * struct charger_desc + * @psy_name: the name of power-supply-class for charger manager * @polling_mode: * Determine which polling mode will be used + * @fullbatt_uV: voltage in microvolt + * If it is not being charged and VBATT >= fullbatt_uV, + * it is assumed to be full. * @polling_interval_ms: interval in millisecond at which * charger manager will monitor battery health * @battery_present: @@ -62,11 +66,18 @@ struct charger_global_desc { * return_value > 0: overheat * return_value == 0: normal * return_value < 0: cold + * @measure_battery_temp: + * true: measure battery temperature + * false: measure ambient temperature */ struct charger_desc { + char *psy_name; + enum polling_modes polling_mode; unsigned int polling_interval_ms; + unsigned int fullbatt_uV; + enum data_source battery_present; char **psy_charger_stat; @@ -77,6 +88,7 @@ struct charger_desc { char *psy_fuel_gauge; int (*temperature_out_of_range)(int *mC); + bool measure_battery_temp; }; #define PSY_NAME_MAX 30 @@ -92,6 +104,8 @@ struct charger_desc { * @emergency_stop: * When setting true, stop charging * @last_temp_mC: the measured temperature in milli-Celsius + * @psy_name_buf: the name of power-supply-class for charger manager + * @charger_psy: power_supply for charger manager * @status_save_ext_pwr_inserted: * saved status of external power before entering suspend-to-RAM * @status_save_batt: @@ -110,6 +124,9 @@ struct charger_manager { int emergency_stop; int last_temp_mC; + char psy_name_buf[PSY_NAME_MAX + 1]; + struct power_supply charger_psy; + bool status_save_ext_pwr_inserted; bool status_save_batt; };