diff options
Diffstat (limited to 'greybus_power.patch')
| -rw-r--r-- | greybus_power.patch | 1148 |
1 files changed, 1148 insertions, 0 deletions
diff --git a/greybus_power.patch b/greybus_power.patch new file mode 100644 index 00000000000000..d1826b130a3e83 --- /dev/null +++ b/greybus_power.patch @@ -0,0 +1,1148 @@ +--- + drivers/greybus/power_supply.c | 1141 +++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 1141 insertions(+) + +--- /dev/null ++++ b/drivers/greybus/power_supply.c +@@ -0,0 +1,1141 @@ ++/* ++ * Power Supply driver for a Greybus module. ++ * ++ * Copyright 2014-2015 Google Inc. ++ * Copyright 2014-2015 Linaro Ltd. ++ * ++ * Released under the GPLv2 only. ++ */ ++ ++#include <linux/kernel.h> ++#include <linux/module.h> ++#include <linux/power_supply.h> ++#include <linux/slab.h> ++ ++#include "greybus.h" ++ ++#define PROP_MAX 32 ++ ++struct gb_power_supply_prop { ++ enum power_supply_property prop; ++ u8 gb_prop; ++ int val; ++ int previous_val; ++ bool is_writeable; ++}; ++ ++struct gb_power_supply { ++ u8 id; ++ bool registered; ++ struct power_supply *psy; ++ struct power_supply_desc desc; ++ char name[64]; ++ struct gb_power_supplies *supplies; ++ struct delayed_work work; ++ char *manufacturer; ++ char *model_name; ++ char *serial_number; ++ u8 type; ++ u8 properties_count; ++ u8 properties_count_str; ++ unsigned long last_update; ++ u8 cache_invalid; ++ unsigned int update_interval; ++ bool changed; ++ struct gb_power_supply_prop *props; ++ enum power_supply_property *props_raw; ++ bool pm_acquired; ++ struct mutex supply_lock; ++}; ++ ++struct gb_power_supplies { ++ struct gb_connection *connection; ++ u8 supplies_count; ++ struct gb_power_supply *supply; ++ struct mutex supplies_lock; ++}; ++ ++#define to_gb_power_supply(x) power_supply_get_drvdata(x) ++ ++/* ++ * General power supply properties that could be absent from various reasons, ++ * like kernel versions or vendor specific versions ++ */ ++#ifndef POWER_SUPPLY_PROP_VOLTAGE_BOOT ++ #define POWER_SUPPLY_PROP_VOLTAGE_BOOT -1 ++#endif ++#ifndef POWER_SUPPLY_PROP_CURRENT_BOOT ++ #define POWER_SUPPLY_PROP_CURRENT_BOOT -1 ++#endif ++#ifndef POWER_SUPPLY_PROP_CALIBRATE ++ #define POWER_SUPPLY_PROP_CALIBRATE -1 ++#endif ++ ++/* cache time in milliseconds, if cache_time is set to 0 cache is disable */ ++static unsigned int cache_time = 1000; ++/* ++ * update interval initial and maximum value, between the two will ++ * back-off exponential ++ */ ++static unsigned int update_interval_init = 1 * HZ; ++static unsigned int update_interval_max = 30 * HZ; ++ ++struct gb_power_supply_changes { ++ enum power_supply_property prop; ++ u32 tolerance_change; ++ void (*prop_changed)(struct gb_power_supply *gbpsy, ++ struct gb_power_supply_prop *prop); ++}; ++ ++static void gb_power_supply_state_change(struct gb_power_supply *gbpsy, ++ struct gb_power_supply_prop *prop); ++ ++static const struct gb_power_supply_changes psy_props_changes[] = { ++ { .prop = GB_POWER_SUPPLY_PROP_STATUS, ++ .tolerance_change = 0, ++ .prop_changed = gb_power_supply_state_change, ++ }, ++ { .prop = GB_POWER_SUPPLY_PROP_TEMP, ++ .tolerance_change = 500, ++ .prop_changed = NULL, ++ }, ++ { .prop = GB_POWER_SUPPLY_PROP_ONLINE, ++ .tolerance_change = 0, ++ .prop_changed = NULL, ++ }, ++}; ++ ++static int get_psp_from_gb_prop(int gb_prop, enum power_supply_property *psp) ++{ ++ int prop; ++ ++ switch (gb_prop) { ++ case GB_POWER_SUPPLY_PROP_STATUS: ++ prop = POWER_SUPPLY_PROP_STATUS; ++ break; ++ case GB_POWER_SUPPLY_PROP_CHARGE_TYPE: ++ prop = POWER_SUPPLY_PROP_CHARGE_TYPE; ++ break; ++ case GB_POWER_SUPPLY_PROP_HEALTH: ++ prop = POWER_SUPPLY_PROP_HEALTH; ++ break; ++ case GB_POWER_SUPPLY_PROP_PRESENT: ++ prop = POWER_SUPPLY_PROP_PRESENT; ++ break; ++ case GB_POWER_SUPPLY_PROP_ONLINE: ++ prop = POWER_SUPPLY_PROP_ONLINE; ++ break; ++ case GB_POWER_SUPPLY_PROP_AUTHENTIC: ++ prop = POWER_SUPPLY_PROP_AUTHENTIC; ++ break; ++ case GB_POWER_SUPPLY_PROP_TECHNOLOGY: ++ prop = POWER_SUPPLY_PROP_TECHNOLOGY; ++ break; ++ case GB_POWER_SUPPLY_PROP_CYCLE_COUNT: ++ prop = POWER_SUPPLY_PROP_CYCLE_COUNT; ++ break; ++ case GB_POWER_SUPPLY_PROP_VOLTAGE_MAX: ++ prop = POWER_SUPPLY_PROP_VOLTAGE_MAX; ++ break; ++ case GB_POWER_SUPPLY_PROP_VOLTAGE_MIN: ++ prop = POWER_SUPPLY_PROP_VOLTAGE_MIN; ++ break; ++ case GB_POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: ++ prop = POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN; ++ break; ++ case GB_POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: ++ prop = POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN; ++ break; ++ case GB_POWER_SUPPLY_PROP_VOLTAGE_NOW: ++ prop = POWER_SUPPLY_PROP_VOLTAGE_NOW; ++ break; ++ case GB_POWER_SUPPLY_PROP_VOLTAGE_AVG: ++ prop = POWER_SUPPLY_PROP_VOLTAGE_AVG; ++ break; ++ case GB_POWER_SUPPLY_PROP_VOLTAGE_OCV: ++ prop = POWER_SUPPLY_PROP_VOLTAGE_OCV; ++ break; ++ case GB_POWER_SUPPLY_PROP_VOLTAGE_BOOT: ++ prop = POWER_SUPPLY_PROP_VOLTAGE_BOOT; ++ break; ++ case GB_POWER_SUPPLY_PROP_CURRENT_MAX: ++ prop = POWER_SUPPLY_PROP_CURRENT_MAX; ++ break; ++ case GB_POWER_SUPPLY_PROP_CURRENT_NOW: ++ prop = POWER_SUPPLY_PROP_CURRENT_NOW; ++ break; ++ case GB_POWER_SUPPLY_PROP_CURRENT_AVG: ++ prop = POWER_SUPPLY_PROP_CURRENT_AVG; ++ break; ++ case GB_POWER_SUPPLY_PROP_CURRENT_BOOT: ++ prop = POWER_SUPPLY_PROP_CURRENT_BOOT; ++ break; ++ case GB_POWER_SUPPLY_PROP_POWER_NOW: ++ prop = POWER_SUPPLY_PROP_POWER_NOW; ++ break; ++ case GB_POWER_SUPPLY_PROP_POWER_AVG: ++ prop = POWER_SUPPLY_PROP_POWER_AVG; ++ break; ++ case GB_POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: ++ prop = POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN; ++ break; ++ case GB_POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN: ++ prop = POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN; ++ break; ++ case GB_POWER_SUPPLY_PROP_CHARGE_FULL: ++ prop = POWER_SUPPLY_PROP_CHARGE_FULL; ++ break; ++ case GB_POWER_SUPPLY_PROP_CHARGE_EMPTY: ++ prop = POWER_SUPPLY_PROP_CHARGE_EMPTY; ++ break; ++ case GB_POWER_SUPPLY_PROP_CHARGE_NOW: ++ prop = POWER_SUPPLY_PROP_CHARGE_NOW; ++ break; ++ case GB_POWER_SUPPLY_PROP_CHARGE_AVG: ++ prop = POWER_SUPPLY_PROP_CHARGE_AVG; ++ break; ++ case GB_POWER_SUPPLY_PROP_CHARGE_COUNTER: ++ prop = POWER_SUPPLY_PROP_CHARGE_COUNTER; ++ break; ++ case GB_POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: ++ prop = POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT; ++ break; ++ case GB_POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: ++ prop = POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX; ++ break; ++ case GB_POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: ++ prop = POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE; ++ break; ++ case GB_POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: ++ prop = POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX; ++ break; ++ case GB_POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: ++ prop = POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT; ++ break; ++ case GB_POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX: ++ prop = POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX; ++ break; ++ case GB_POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: ++ prop = POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT; ++ break; ++ case GB_POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: ++ prop = POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN; ++ break; ++ case GB_POWER_SUPPLY_PROP_ENERGY_EMPTY_DESIGN: ++ prop = POWER_SUPPLY_PROP_ENERGY_EMPTY_DESIGN; ++ break; ++ case GB_POWER_SUPPLY_PROP_ENERGY_FULL: ++ prop = POWER_SUPPLY_PROP_ENERGY_FULL; ++ break; ++ case GB_POWER_SUPPLY_PROP_ENERGY_EMPTY: ++ prop = POWER_SUPPLY_PROP_ENERGY_EMPTY; ++ break; ++ case GB_POWER_SUPPLY_PROP_ENERGY_NOW: ++ prop = POWER_SUPPLY_PROP_ENERGY_NOW; ++ break; ++ case GB_POWER_SUPPLY_PROP_ENERGY_AVG: ++ prop = POWER_SUPPLY_PROP_ENERGY_AVG; ++ break; ++ case GB_POWER_SUPPLY_PROP_CAPACITY: ++ prop = POWER_SUPPLY_PROP_CAPACITY; ++ break; ++ case GB_POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN: ++ prop = POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN; ++ break; ++ case GB_POWER_SUPPLY_PROP_CAPACITY_ALERT_MAX: ++ prop = POWER_SUPPLY_PROP_CAPACITY_ALERT_MAX; ++ break; ++ case GB_POWER_SUPPLY_PROP_CAPACITY_LEVEL: ++ prop = POWER_SUPPLY_PROP_CAPACITY_LEVEL; ++ break; ++ case GB_POWER_SUPPLY_PROP_TEMP: ++ prop = POWER_SUPPLY_PROP_TEMP; ++ break; ++ case GB_POWER_SUPPLY_PROP_TEMP_MAX: ++ prop = POWER_SUPPLY_PROP_TEMP_MAX; ++ break; ++ case GB_POWER_SUPPLY_PROP_TEMP_MIN: ++ prop = POWER_SUPPLY_PROP_TEMP_MIN; ++ break; ++ case GB_POWER_SUPPLY_PROP_TEMP_ALERT_MIN: ++ prop = POWER_SUPPLY_PROP_TEMP_ALERT_MIN; ++ break; ++ case GB_POWER_SUPPLY_PROP_TEMP_ALERT_MAX: ++ prop = POWER_SUPPLY_PROP_TEMP_ALERT_MAX; ++ break; ++ case GB_POWER_SUPPLY_PROP_TEMP_AMBIENT: ++ prop = POWER_SUPPLY_PROP_TEMP_AMBIENT; ++ break; ++ case GB_POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MIN: ++ prop = POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MIN; ++ break; ++ case GB_POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MAX: ++ prop = POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MAX; ++ break; ++ case GB_POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: ++ prop = POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW; ++ break; ++ case GB_POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: ++ prop = POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG; ++ break; ++ case GB_POWER_SUPPLY_PROP_TIME_TO_FULL_NOW: ++ prop = POWER_SUPPLY_PROP_TIME_TO_FULL_NOW; ++ break; ++ case GB_POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: ++ prop = POWER_SUPPLY_PROP_TIME_TO_FULL_AVG; ++ break; ++ case GB_POWER_SUPPLY_PROP_TYPE: ++ prop = POWER_SUPPLY_PROP_TYPE; ++ break; ++ case GB_POWER_SUPPLY_PROP_SCOPE: ++ prop = POWER_SUPPLY_PROP_SCOPE; ++ break; ++ case GB_POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: ++ prop = POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT; ++ break; ++ case GB_POWER_SUPPLY_PROP_CALIBRATE: ++ prop = POWER_SUPPLY_PROP_CALIBRATE; ++ break; ++ default: ++ prop = -1; ++ break; ++ } ++ ++ if (prop < 0) ++ return prop; ++ ++ *psp = (enum power_supply_property)prop; ++ ++ return 0; ++} ++ ++static struct gb_connection *get_conn_from_psy(struct gb_power_supply *gbpsy) ++{ ++ return gbpsy->supplies->connection; ++} ++ ++static struct gb_power_supply_prop *get_psy_prop(struct gb_power_supply *gbpsy, ++ enum power_supply_property psp) ++{ ++ int i; ++ ++ for (i = 0; i < gbpsy->properties_count; i++) ++ if (gbpsy->props[i].prop == psp) ++ return &gbpsy->props[i]; ++ return NULL; ++} ++ ++static int is_psy_prop_writeable(struct gb_power_supply *gbpsy, ++ enum power_supply_property psp) ++{ ++ struct gb_power_supply_prop *prop; ++ ++ prop = get_psy_prop(gbpsy, psp); ++ if (!prop) ++ return -ENOENT; ++ return prop->is_writeable ? 1 : 0; ++} ++ ++static int is_prop_valint(enum power_supply_property psp) ++{ ++ return ((psp < POWER_SUPPLY_PROP_MODEL_NAME) ? 1 : 0); ++} ++ ++static void next_interval(struct gb_power_supply *gbpsy) ++{ ++ if (gbpsy->update_interval == update_interval_max) ++ return; ++ ++ /* do some exponential back-off in the update interval */ ++ gbpsy->update_interval *= 2; ++ if (gbpsy->update_interval > update_interval_max) ++ gbpsy->update_interval = update_interval_max; ++} ++ ++static void __gb_power_supply_changed(struct gb_power_supply *gbpsy) ++{ ++ power_supply_changed(gbpsy->psy); ++} ++ ++static void gb_power_supply_state_change(struct gb_power_supply *gbpsy, ++ struct gb_power_supply_prop *prop) ++{ ++ struct gb_connection *connection = get_conn_from_psy(gbpsy); ++ int ret; ++ ++ /* ++ * Check gbpsy->pm_acquired to make sure only one pair of 'get_sync' ++ * and 'put_autosuspend' runtime pm call for state property change. ++ */ ++ mutex_lock(&gbpsy->supply_lock); ++ ++ if ((prop->val == GB_POWER_SUPPLY_STATUS_CHARGING) && ++ !gbpsy->pm_acquired) { ++ ret = gb_pm_runtime_get_sync(connection->bundle); ++ if (ret) ++ dev_err(&connection->bundle->dev, ++ "Fail to set wake lock for charging state\n"); ++ else ++ gbpsy->pm_acquired = true; ++ } else { ++ if (gbpsy->pm_acquired) { ++ ret = gb_pm_runtime_put_autosuspend(connection->bundle); ++ if (ret) ++ dev_err(&connection->bundle->dev, ++ "Fail to set wake unlock for none charging\n"); ++ else ++ gbpsy->pm_acquired = false; ++ } ++ } ++ ++ mutex_unlock(&gbpsy->supply_lock); ++} ++ ++static void check_changed(struct gb_power_supply *gbpsy, ++ struct gb_power_supply_prop *prop) ++{ ++ const struct gb_power_supply_changes *psyc; ++ int val = prop->val; ++ int prev_val = prop->previous_val; ++ bool changed = false; ++ int i; ++ ++ for (i = 0; i < ARRAY_SIZE(psy_props_changes); i++) { ++ psyc = &psy_props_changes[i]; ++ if (prop->prop == psyc->prop) { ++ if (!psyc->tolerance_change) ++ changed = true; ++ else if (val < prev_val && ++ prev_val - val > psyc->tolerance_change) ++ changed = true; ++ else if (val > prev_val && ++ val - prev_val > psyc->tolerance_change) ++ changed = true; ++ ++ if (changed && psyc->prop_changed) ++ psyc->prop_changed(gbpsy, prop); ++ ++ if (changed) ++ gbpsy->changed = true; ++ break; ++ } ++ } ++} ++ ++static int total_props(struct gb_power_supply *gbpsy) ++{ ++ /* this return the intval plus the strval properties */ ++ return (gbpsy->properties_count + gbpsy->properties_count_str); ++} ++ ++static void prop_append(struct gb_power_supply *gbpsy, ++ enum power_supply_property prop) ++{ ++ enum power_supply_property *new_props_raw; ++ ++ gbpsy->properties_count_str++; ++ new_props_raw = krealloc(gbpsy->props_raw, total_props(gbpsy) * ++ sizeof(enum power_supply_property), ++ GFP_KERNEL); ++ if (!new_props_raw) ++ return; ++ gbpsy->props_raw = new_props_raw; ++ gbpsy->props_raw[total_props(gbpsy) - 1] = prop; ++} ++ ++static int __gb_power_supply_set_name(char *init_name, char *name, size_t len) ++{ ++ unsigned int i = 0; ++ int ret = 0; ++ struct power_supply *psy; ++ ++ if (!strlen(init_name)) ++ init_name = "gb_power_supply"; ++ strlcpy(name, init_name, len); ++ ++ while ((ret < len) && (psy = power_supply_get_by_name(name))) { ++ power_supply_put(psy); ++ ++ ret = snprintf(name, len, "%s_%u", init_name, ++i); ++ } ++ if (ret >= len) ++ return -ENOMEM; ++ return i; ++} ++ ++static void _gb_power_supply_append_props(struct gb_power_supply *gbpsy) ++{ ++ if (strlen(gbpsy->manufacturer)) ++ prop_append(gbpsy, POWER_SUPPLY_PROP_MANUFACTURER); ++ if (strlen(gbpsy->model_name)) ++ prop_append(gbpsy, POWER_SUPPLY_PROP_MODEL_NAME); ++ if (strlen(gbpsy->serial_number)) ++ prop_append(gbpsy, POWER_SUPPLY_PROP_SERIAL_NUMBER); ++} ++ ++static int gb_power_supply_description_get(struct gb_power_supply *gbpsy) ++{ ++ struct gb_connection *connection = get_conn_from_psy(gbpsy); ++ struct gb_power_supply_get_description_request req; ++ struct gb_power_supply_get_description_response resp; ++ int ret; ++ ++ req.psy_id = gbpsy->id; ++ ++ ret = gb_operation_sync(connection, ++ GB_POWER_SUPPLY_TYPE_GET_DESCRIPTION, ++ &req, sizeof(req), &resp, sizeof(resp)); ++ if (ret < 0) ++ return ret; ++ ++ gbpsy->manufacturer = kstrndup(resp.manufacturer, PROP_MAX, GFP_KERNEL); ++ if (!gbpsy->manufacturer) ++ return -ENOMEM; ++ gbpsy->model_name = kstrndup(resp.model, PROP_MAX, GFP_KERNEL); ++ if (!gbpsy->model_name) ++ return -ENOMEM; ++ gbpsy->serial_number = kstrndup(resp.serial_number, PROP_MAX, ++ GFP_KERNEL); ++ if (!gbpsy->serial_number) ++ return -ENOMEM; ++ ++ gbpsy->type = le16_to_cpu(resp.type); ++ gbpsy->properties_count = resp.properties_count; ++ ++ return 0; ++} ++ ++static int gb_power_supply_prop_descriptors_get(struct gb_power_supply *gbpsy) ++{ ++ struct gb_connection *connection = get_conn_from_psy(gbpsy); ++ struct gb_power_supply_get_property_descriptors_request *req; ++ struct gb_power_supply_get_property_descriptors_response *resp; ++ struct gb_operation *op; ++ u8 props_count = gbpsy->properties_count; ++ enum power_supply_property psp; ++ int ret; ++ int i, r = 0; ++ ++ if (props_count == 0) ++ return 0; ++ ++ op = gb_operation_create(connection, ++ GB_POWER_SUPPLY_TYPE_GET_PROP_DESCRIPTORS, ++ sizeof(req), sizeof(*resp) + props_count * ++ sizeof(struct gb_power_supply_props_desc), ++ GFP_KERNEL); ++ if (!op) ++ return -ENOMEM; ++ ++ req = op->request->payload; ++ req->psy_id = gbpsy->id; ++ ++ ret = gb_operation_request_send_sync(op); ++ if (ret < 0) ++ goto out_put_operation; ++ ++ resp = op->response->payload; ++ ++ /* validate received properties */ ++ for (i = 0; i < props_count; i++) { ++ ret = get_psp_from_gb_prop(resp->props[i].property, &psp); ++ if (ret < 0) { ++ dev_warn(&connection->bundle->dev, ++ "greybus property %u it is not supported by this kernel, dropped\n", ++ resp->props[i].property); ++ gbpsy->properties_count--; ++ } ++ } ++ ++ gbpsy->props = kcalloc(gbpsy->properties_count, sizeof(*gbpsy->props), ++ GFP_KERNEL); ++ if (!gbpsy->props) { ++ ret = -ENOMEM; ++ goto out_put_operation; ++ } ++ ++ gbpsy->props_raw = kcalloc(gbpsy->properties_count, ++ sizeof(*gbpsy->props_raw), GFP_KERNEL); ++ if (!gbpsy->props_raw) { ++ ret = -ENOMEM; ++ goto out_put_operation; ++ } ++ ++ /* Store available properties, skip the ones we do not support */ ++ for (i = 0; i < props_count; i++) { ++ ret = get_psp_from_gb_prop(resp->props[i].property, &psp); ++ if (ret < 0) { ++ r++; ++ continue; ++ } ++ gbpsy->props[i - r].prop = psp; ++ gbpsy->props[i - r].gb_prop = resp->props[i].property; ++ gbpsy->props_raw[i - r] = psp; ++ if (resp->props[i].is_writeable) ++ gbpsy->props[i - r].is_writeable = true; ++ } ++ ++ /* ++ * now append the properties that we already got information in the ++ * get_description operation. (char * ones) ++ */ ++ _gb_power_supply_append_props(gbpsy); ++ ++ ret = 0; ++out_put_operation: ++ gb_operation_put(op); ++ ++ return ret; ++} ++ ++static int __gb_power_supply_property_update(struct gb_power_supply *gbpsy, ++ enum power_supply_property psp) ++{ ++ struct gb_connection *connection = get_conn_from_psy(gbpsy); ++ struct gb_power_supply_prop *prop; ++ struct gb_power_supply_get_property_request req; ++ struct gb_power_supply_get_property_response resp; ++ int val; ++ int ret; ++ ++ prop = get_psy_prop(gbpsy, psp); ++ if (!prop) ++ return -EINVAL; ++ req.psy_id = gbpsy->id; ++ req.property = prop->gb_prop; ++ ++ ret = gb_operation_sync(connection, GB_POWER_SUPPLY_TYPE_GET_PROPERTY, ++ &req, sizeof(req), &resp, sizeof(resp)); ++ if (ret < 0) ++ return ret; ++ ++ val = le32_to_cpu(resp.prop_val); ++ if (val == prop->val) ++ return 0; ++ ++ prop->previous_val = prop->val; ++ prop->val = val; ++ ++ check_changed(gbpsy, prop); ++ ++ return 0; ++} ++ ++static int __gb_power_supply_property_get(struct gb_power_supply *gbpsy, ++ enum power_supply_property psp, ++ union power_supply_propval *val) ++{ ++ struct gb_power_supply_prop *prop; ++ ++ prop = get_psy_prop(gbpsy, psp); ++ if (!prop) ++ return -EINVAL; ++ ++ val->intval = prop->val; ++ return 0; ++} ++ ++static int __gb_power_supply_property_strval_get(struct gb_power_supply *gbpsy, ++ enum power_supply_property psp, ++ union power_supply_propval *val) ++{ ++ switch (psp) { ++ case POWER_SUPPLY_PROP_MODEL_NAME: ++ val->strval = gbpsy->model_name; ++ break; ++ case POWER_SUPPLY_PROP_MANUFACTURER: ++ val->strval = gbpsy->manufacturer; ++ break; ++ case POWER_SUPPLY_PROP_SERIAL_NUMBER: ++ val->strval = gbpsy->serial_number; ++ break; ++ default: ++ break; ++ } ++ ++ return 0; ++} ++ ++static int _gb_power_supply_property_get(struct gb_power_supply *gbpsy, ++ enum power_supply_property psp, ++ union power_supply_propval *val) ++{ ++ struct gb_connection *connection = get_conn_from_psy(gbpsy); ++ int ret; ++ ++ /* ++ * Properties of type const char *, were already fetched on ++ * get_description operation and should be cached in gb ++ */ ++ if (is_prop_valint(psp)) ++ ret = __gb_power_supply_property_get(gbpsy, psp, val); ++ else ++ ret = __gb_power_supply_property_strval_get(gbpsy, psp, val); ++ ++ if (ret < 0) ++ dev_err(&connection->bundle->dev, "get property %u\n", psp); ++ ++ return 0; ++} ++ ++static int is_cache_valid(struct gb_power_supply *gbpsy) ++{ ++ /* check if cache is good enough or it has expired */ ++ if (gbpsy->cache_invalid) { ++ gbpsy->cache_invalid = 0; ++ return 0; ++ } ++ ++ if (gbpsy->last_update && ++ time_is_after_jiffies(gbpsy->last_update + ++ msecs_to_jiffies(cache_time))) ++ return 1; ++ ++ return 0; ++} ++ ++static int gb_power_supply_status_get(struct gb_power_supply *gbpsy) ++{ ++ struct gb_connection *connection = get_conn_from_psy(gbpsy); ++ int ret = 0; ++ int i; ++ ++ if (is_cache_valid(gbpsy)) ++ return 0; ++ ++ ret = gb_pm_runtime_get_sync(connection->bundle); ++ if (ret) ++ return ret; ++ ++ for (i = 0; i < gbpsy->properties_count; i++) { ++ ret = __gb_power_supply_property_update(gbpsy, ++ gbpsy->props[i].prop); ++ if (ret < 0) ++ break; ++ } ++ ++ if (ret == 0) ++ gbpsy->last_update = jiffies; ++ ++ gb_pm_runtime_put_autosuspend(connection->bundle); ++ return ret; ++} ++ ++static void gb_power_supply_status_update(struct gb_power_supply *gbpsy) ++{ ++ /* check if there a change that need to be reported */ ++ gb_power_supply_status_get(gbpsy); ++ ++ if (!gbpsy->changed) ++ return; ++ ++ gbpsy->update_interval = update_interval_init; ++ __gb_power_supply_changed(gbpsy); ++ gbpsy->changed = false; ++} ++ ++static void gb_power_supply_work(struct work_struct *work) ++{ ++ struct gb_power_supply *gbpsy = container_of(work, ++ struct gb_power_supply, ++ work.work); ++ ++ /* ++ * if the poll interval is not set, disable polling, this is helpful ++ * specially at unregister time. ++ */ ++ if (!gbpsy->update_interval) ++ return; ++ ++ gb_power_supply_status_update(gbpsy); ++ next_interval(gbpsy); ++ schedule_delayed_work(&gbpsy->work, gbpsy->update_interval); ++} ++ ++static int get_property(struct power_supply *b, ++ enum power_supply_property psp, ++ union power_supply_propval *val) ++{ ++ struct gb_power_supply *gbpsy = to_gb_power_supply(b); ++ ++ gb_power_supply_status_get(gbpsy); ++ ++ return _gb_power_supply_property_get(gbpsy, psp, val); ++} ++ ++static int gb_power_supply_property_set(struct gb_power_supply *gbpsy, ++ enum power_supply_property psp, ++ int val) ++{ ++ struct gb_connection *connection = get_conn_from_psy(gbpsy); ++ struct gb_power_supply_prop *prop; ++ struct gb_power_supply_set_property_request req; ++ int ret; ++ ++ ret = gb_pm_runtime_get_sync(connection->bundle); ++ if (ret) ++ return ret; ++ ++ prop = get_psy_prop(gbpsy, psp); ++ if (!prop) { ++ ret = -EINVAL; ++ goto out; ++ } ++ ++ req.psy_id = gbpsy->id; ++ req.property = prop->gb_prop; ++ req.prop_val = cpu_to_le32((s32)val); ++ ++ ret = gb_operation_sync(connection, GB_POWER_SUPPLY_TYPE_SET_PROPERTY, ++ &req, sizeof(req), NULL, 0); ++ if (ret < 0) ++ goto out; ++ ++ /* cache immediately the new value */ ++ prop->val = val; ++ ++out: ++ gb_pm_runtime_put_autosuspend(connection->bundle); ++ return ret; ++} ++ ++static int set_property(struct power_supply *b, ++ enum power_supply_property psp, ++ const union power_supply_propval *val) ++{ ++ struct gb_power_supply *gbpsy = to_gb_power_supply(b); ++ ++ return gb_power_supply_property_set(gbpsy, psp, val->intval); ++} ++ ++static int property_is_writeable(struct power_supply *b, ++ enum power_supply_property psp) ++{ ++ struct gb_power_supply *gbpsy = to_gb_power_supply(b); ++ ++ return is_psy_prop_writeable(gbpsy, psp); ++} ++ ++static int gb_power_supply_register(struct gb_power_supply *gbpsy) ++{ ++ struct gb_connection *connection = get_conn_from_psy(gbpsy); ++ struct power_supply_config cfg = {}; ++ ++ cfg.drv_data = gbpsy; ++ ++ gbpsy->desc.name = gbpsy->name; ++ gbpsy->desc.type = gbpsy->type; ++ gbpsy->desc.properties = gbpsy->props_raw; ++ gbpsy->desc.num_properties = total_props(gbpsy); ++ gbpsy->desc.get_property = get_property; ++ gbpsy->desc.set_property = set_property; ++ gbpsy->desc.property_is_writeable = property_is_writeable; ++ ++ gbpsy->psy = power_supply_register(&connection->bundle->dev, ++ &gbpsy->desc, &cfg); ++ return PTR_ERR_OR_ZERO(gbpsy->psy); ++} ++ ++static void _gb_power_supply_free(struct gb_power_supply *gbpsy) ++{ ++ kfree(gbpsy->serial_number); ++ kfree(gbpsy->model_name); ++ kfree(gbpsy->manufacturer); ++ kfree(gbpsy->props_raw); ++ kfree(gbpsy->props); ++} ++ ++static void _gb_power_supply_release(struct gb_power_supply *gbpsy) ++{ ++ gbpsy->update_interval = 0; ++ ++ cancel_delayed_work_sync(&gbpsy->work); ++ ++ if (gbpsy->registered) ++ power_supply_unregister(gbpsy->psy); ++ ++ _gb_power_supply_free(gbpsy); ++} ++ ++static void _gb_power_supplies_release(struct gb_power_supplies *supplies) ++{ ++ int i; ++ ++ if (!supplies->supply) ++ return; ++ ++ mutex_lock(&supplies->supplies_lock); ++ for (i = 0; i < supplies->supplies_count; i++) ++ _gb_power_supply_release(&supplies->supply[i]); ++ kfree(supplies->supply); ++ mutex_unlock(&supplies->supplies_lock); ++ kfree(supplies); ++} ++ ++static int gb_power_supplies_get_count(struct gb_power_supplies *supplies) ++{ ++ struct gb_power_supply_get_supplies_response resp; ++ int ret; ++ ++ ret = gb_operation_sync(supplies->connection, ++ GB_POWER_SUPPLY_TYPE_GET_SUPPLIES, ++ NULL, 0, &resp, sizeof(resp)); ++ if (ret < 0) ++ return ret; ++ ++ if (!resp.supplies_count) ++ return -EINVAL; ++ ++ supplies->supplies_count = resp.supplies_count; ++ ++ return ret; ++} ++ ++static int gb_power_supply_config(struct gb_power_supplies *supplies, int id) ++{ ++ struct gb_power_supply *gbpsy = &supplies->supply[id]; ++ int ret; ++ ++ gbpsy->supplies = supplies; ++ gbpsy->id = id; ++ ++ ret = gb_power_supply_description_get(gbpsy); ++ if (ret < 0) ++ return ret; ++ ++ return gb_power_supply_prop_descriptors_get(gbpsy); ++} ++ ++static int gb_power_supply_enable(struct gb_power_supply *gbpsy) ++{ ++ int ret; ++ ++ /* guarantee that we have an unique name, before register */ ++ ret = __gb_power_supply_set_name(gbpsy->model_name, gbpsy->name, ++ sizeof(gbpsy->name)); ++ if (ret < 0) ++ return ret; ++ ++ mutex_init(&gbpsy->supply_lock); ++ ++ ret = gb_power_supply_register(gbpsy); ++ if (ret < 0) ++ return ret; ++ ++ gbpsy->update_interval = update_interval_init; ++ INIT_DELAYED_WORK(&gbpsy->work, gb_power_supply_work); ++ schedule_delayed_work(&gbpsy->work, 0); ++ ++ /* everything went fine, mark it for release code to know */ ++ gbpsy->registered = true; ++ ++ return 0; ++} ++ ++static int gb_power_supplies_setup(struct gb_power_supplies *supplies) ++{ ++ struct gb_connection *connection = supplies->connection; ++ int ret; ++ int i; ++ ++ mutex_lock(&supplies->supplies_lock); ++ ++ ret = gb_power_supplies_get_count(supplies); ++ if (ret < 0) ++ goto out; ++ ++ supplies->supply = kzalloc(supplies->supplies_count * ++ sizeof(struct gb_power_supply), ++ GFP_KERNEL); ++ ++ if (!supplies->supply) { ++ ret = -ENOMEM; ++ goto out; ++ } ++ ++ for (i = 0; i < supplies->supplies_count; i++) { ++ ret = gb_power_supply_config(supplies, i); ++ if (ret < 0) { ++ dev_err(&connection->bundle->dev, ++ "Fail to configure supplies devices\n"); ++ goto out; ++ } ++ } ++out: ++ mutex_unlock(&supplies->supplies_lock); ++ return ret; ++} ++ ++static int gb_power_supplies_register(struct gb_power_supplies *supplies) ++{ ++ struct gb_connection *connection = supplies->connection; ++ int ret = 0; ++ int i; ++ ++ mutex_lock(&supplies->supplies_lock); ++ ++ for (i = 0; i < supplies->supplies_count; i++) { ++ ret = gb_power_supply_enable(&supplies->supply[i]); ++ if (ret < 0) { ++ dev_err(&connection->bundle->dev, ++ "Fail to enable supplies devices\n"); ++ break; ++ } ++ } ++ ++ mutex_unlock(&supplies->supplies_lock); ++ return ret; ++} ++ ++static int gb_supplies_request_handler(struct gb_operation *op) ++{ ++ struct gb_connection *connection = op->connection; ++ struct gb_power_supplies *supplies = gb_connection_get_data(connection); ++ struct gb_power_supply *gbpsy; ++ struct gb_message *request; ++ struct gb_power_supply_event_request *payload; ++ u8 psy_id; ++ u8 event; ++ int ret = 0; ++ ++ if (op->type != GB_POWER_SUPPLY_TYPE_EVENT) { ++ dev_err(&connection->bundle->dev, ++ "Unsupported unsolicited event: %u\n", op->type); ++ return -EINVAL; ++ } ++ ++ request = op->request; ++ ++ if (request->payload_size < sizeof(*payload)) { ++ dev_err(&connection->bundle->dev, ++ "Wrong event size received (%zu < %zu)\n", ++ request->payload_size, sizeof(*payload)); ++ return -EINVAL; ++ } ++ ++ payload = request->payload; ++ psy_id = payload->psy_id; ++ mutex_lock(&supplies->supplies_lock); ++ if (psy_id >= supplies->supplies_count || ++ !supplies->supply[psy_id].registered) { ++ dev_err(&connection->bundle->dev, ++ "Event received for unconfigured power_supply id: %d\n", ++ psy_id); ++ ret = -EINVAL; ++ goto out_unlock; ++ } ++ ++ event = payload->event; ++ /* ++ * we will only handle events after setup is done and before release is ++ * running. For that just check update_interval. ++ */ ++ gbpsy = &supplies->supply[psy_id]; ++ if (!gbpsy->update_interval) { ++ ret = -ESHUTDOWN; ++ goto out_unlock; ++ } ++ ++ if (event & GB_POWER_SUPPLY_UPDATE) { ++ /* ++ * we need to make sure we invalidate cache, if not no new ++ * values for the properties will be fetch and the all propose ++ * of this event is missed ++ */ ++ gbpsy->cache_invalid = 1; ++ gb_power_supply_status_update(gbpsy); ++ } ++ ++out_unlock: ++ mutex_unlock(&supplies->supplies_lock); ++ return ret; ++} ++ ++static int gb_power_supply_probe(struct gb_bundle *bundle, ++ const struct greybus_bundle_id *id) ++{ ++ struct greybus_descriptor_cport *cport_desc; ++ struct gb_connection *connection; ++ struct gb_power_supplies *supplies; ++ int ret; ++ ++ if (bundle->num_cports != 1) ++ return -ENODEV; ++ ++ cport_desc = &bundle->cport_desc[0]; ++ if (cport_desc->protocol_id != GREYBUS_PROTOCOL_POWER_SUPPLY) ++ return -ENODEV; ++ ++ supplies = kzalloc(sizeof(*supplies), GFP_KERNEL); ++ if (!supplies) ++ return -ENOMEM; ++ ++ connection = gb_connection_create(bundle, le16_to_cpu(cport_desc->id), ++ gb_supplies_request_handler); ++ if (IS_ERR(connection)) { ++ ret = PTR_ERR(connection); ++ goto out; ++ } ++ ++ supplies->connection = connection; ++ gb_connection_set_data(connection, supplies); ++ ++ mutex_init(&supplies->supplies_lock); ++ ++ greybus_set_drvdata(bundle, supplies); ++ ++ /* We aren't ready to receive an incoming request yet */ ++ ret = gb_connection_enable_tx(connection); ++ if (ret) ++ goto error_connection_destroy; ++ ++ ret = gb_power_supplies_setup(supplies); ++ if (ret < 0) ++ goto error_connection_disable; ++ ++ /* We are ready to receive an incoming request now, enable RX as well */ ++ ret = gb_connection_enable(connection); ++ if (ret) ++ goto error_connection_disable; ++ ++ ret = gb_power_supplies_register(supplies); ++ if (ret < 0) ++ goto error_connection_disable; ++ ++ gb_pm_runtime_put_autosuspend(bundle); ++ return 0; ++ ++error_connection_disable: ++ gb_connection_disable(connection); ++error_connection_destroy: ++ gb_connection_destroy(connection); ++out: ++ _gb_power_supplies_release(supplies); ++ return ret; ++} ++ ++static void gb_power_supply_disconnect(struct gb_bundle *bundle) ++{ ++ struct gb_power_supplies *supplies = greybus_get_drvdata(bundle); ++ ++ gb_connection_disable(supplies->connection); ++ gb_connection_destroy(supplies->connection); ++ ++ _gb_power_supplies_release(supplies); ++} ++ ++static const struct greybus_bundle_id gb_power_supply_id_table[] = { ++ { GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_POWER_SUPPLY) }, ++ { } ++}; ++MODULE_DEVICE_TABLE(greybus, gb_power_supply_id_table); ++ ++static struct greybus_driver gb_power_supply_driver = { ++ .name = "power_supply", ++ .probe = gb_power_supply_probe, ++ .disconnect = gb_power_supply_disconnect, ++ .id_table = gb_power_supply_id_table, ++}; ++module_greybus_driver(gb_power_supply_driver); ++ ++MODULE_LICENSE("GPL v2"); |
