| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Power Supply driver for a Greybus module. |
| * |
| * Copyright 2014-2015 Google Inc. |
| * Copyright 2014-2015 Linaro Ltd. |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/power_supply.h> |
| #include <linux/slab.h> |
| #include <linux/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"; |
| strscpy(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), |
| struct_size(resp, props, props_count), |
| 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 = kcalloc(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"); |