This driver implements the Greybus power supply class protocol.

Signed-off-by: Greg Kroah-Hartman <gre...@linuxfoundation.org>
---
 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");


Reply via email to