#define GB_POWER_SUPPLY_VERSION_MINOR 0x01
/* Greybus power supply request types */
-#define GB_POWER_SUPPLY_TYPE_TECHNOLOGY 0x02
-#define GB_POWER_SUPPLY_TYPE_STATUS 0x03
-#define GB_POWER_SUPPLY_TYPE_MAX_VOLTAGE 0x04
-#define GB_POWER_SUPPLY_TYPE_PERCENT_CAPACITY 0x05
-#define GB_POWER_SUPPLY_TYPE_TEMPERATURE 0x06
-#define GB_POWER_SUPPLY_TYPE_VOLTAGE 0x07
-#define GB_POWER_SUPPLY_TYPE_CURRENT 0x08
-#define GB_POWER_SUPPLY_TYPE_CAPACITY 0x09 // TODO - POWER_SUPPLY_PROP_CURRENT_MAX
-#define GB_POWER_SUPPLY_TYPE_SHUTDOWN_TEMP 0x0a // TODO - POWER_SUPPLY_PROP_TEMP_ALERT_MAX
-
-/* Should match up with battery technology types in linux/power_supply.h */
+#define GB_POWER_SUPPLY_TYPE_GET_SUPPLIES 0x02
+#define GB_POWER_SUPPLY_TYPE_GET_DESCRIPTION 0x03
+#define GB_POWER_SUPPLY_TYPE_GET_PROP_DESCRIPTORS 0x04
+#define GB_POWER_SUPPLY_TYPE_GET_PROPERTY 0x05
+#define GB_POWER_SUPPLY_TYPE_SET_PROPERTY 0x06
+#define GB_POWER_SUPPLY_TYPE_EVENT 0x07
+
+/* Should match up with battery technologies in linux/power_supply.h */
#define GB_POWER_SUPPLY_TECH_UNKNOWN 0x0000
#define GB_POWER_SUPPLY_TECH_NiMH 0x0001
#define GB_POWER_SUPPLY_TECH_LION 0x0002
#define GB_POWER_SUPPLY_TECH_NiCd 0x0005
#define GB_POWER_SUPPLY_TECH_LiMn 0x0006
-struct gb_power_supply_technology_response {
- __le32 technology;
-} __packed;
-
-/* Should match up with power supply status in linux/power_supply.h */
-#define GB_POWER_SUPPLY_STATUS_UNKNOWN 0x0000
-#define GB_POWER_SUPPLY_STATUS_CHARGING 0x0001
-#define GB_POWER_SUPPLY_STATUS_DISCHARGING 0x0002
-#define GB_POWER_SUPPLY_STATUS_NOT_CHARGING 0x0003
-#define GB_POWER_SUPPLY_STATUS_FULL 0x0004
-
-struct gb_power_supply_status_response {
- __le16 psy_status;
-} __packed;
-
-struct gb_power_supply_max_voltage_response {
- __le32 max_voltage;
-} __packed;
-
-struct gb_power_supply_capacity_response {
- __le32 capacity;
-} __packed;
-
-struct gb_power_supply_temperature_response {
- __le32 temperature;
-} __packed;
-
-struct gb_power_supply_voltage_response {
- __le32 voltage;
+/* Should match up with power supply types in linux/power_supply.h */
+#define GB_POWER_SUPPLY_UNKNOWN_TYPE 0x0000
+#define GB_POWER_SUPPLY_BATTERY_TYPE 0x0001
+#define GB_POWER_SUPPLY_UPS_TYPE 0x0002
+#define GB_POWER_SUPPLY_MAINS_TYPE 0x0003
+#define GB_POWER_SUPPLY_USB_TYPE 0x0004
+#define GB_POWER_SUPPLY_USB_DCP_TYPE 0x0005
+#define GB_POWER_SUPPLY_USB_CDP_TYPE 0x0006
+#define GB_POWER_SUPPLY_USB_ACA_TYPE 0x0007
+
+/* Should match up with power supply health in linux/power_supply.h */
+#define GB_POWER_SUPPLY_HEALTH_UNKNOWN 0x0000
+#define GB_POWER_SUPPLY_HEALTH_GOOD 0x0001
+#define GB_POWER_SUPPLY_HEALTH_OVERHEAT 0x0002
+#define GB_POWER_SUPPLY_HEALTH_DEAD 0x0003
+#define GB_POWER_SUPPLY_HEALTH_OVERVOLTAGE 0x0004
+#define GB_POWER_SUPPLY_HEALTH_UNSPEC_FAILURE 0x0005
+#define GB_POWER_SUPPLY_HEALTH_COLD 0x0006
+#define GB_POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE 0x0007
+#define GB_POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE 0x0008
+
+/* Should match up with battery status in linux/power_supply.h */
+#define GB_POWER_SUPPLY_STATUS_UNKNOWN 0x0000
+#define GB_POWER_SUPPLY_STATUS_CHARGING 0x0001
+#define GB_POWER_SUPPLY_STATUS_DISCHARGING 0x0002
+#define GB_POWER_SUPPLY_STATUS_NOT_CHARGING 0x0003
+#define GB_POWER_SUPPLY_STATUS_FULL 0x0004
+
+struct gb_power_supply_get_supplies_response {
+ __u8 supplies_count;
+} __packed;
+
+struct gb_power_supply_get_description_request {
+ __u8 psy_id;
+} __packed;
+
+struct gb_power_supply_get_description_response {
+ __u8 manufacturer[32];
+ __u8 model[32];
+ __u8 serial_number[32];
+ __le16 type;
+ __u8 properties_count;
+} __packed;
+
+struct gb_power_supply_props_desc {
+ __u8 property;
+#define GB_POWER_SUPPLY_PROP_STATUS 0x00
+#define GB_POWER_SUPPLY_PROP_CHARGE_TYPE 0x01
+#define GB_POWER_SUPPLY_PROP_HEALTH 0x02
+#define GB_POWER_SUPPLY_PROP_PRESENT 0x03
+#define GB_POWER_SUPPLY_PROP_ONLINE 0x04
+#define GB_POWER_SUPPLY_PROP_AUTHENTIC 0x05
+#define GB_POWER_SUPPLY_PROP_TECHNOLOGY 0x06
+#define GB_POWER_SUPPLY_PROP_CYCLE_COUNT 0x07
+#define GB_POWER_SUPPLY_PROP_VOLTAGE_MAX 0x08
+#define GB_POWER_SUPPLY_PROP_VOLTAGE_MIN 0x09
+#define GB_POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN 0x0A
+#define GB_POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN 0x0B
+#define GB_POWER_SUPPLY_PROP_VOLTAGE_NOW 0x0C
+#define GB_POWER_SUPPLY_PROP_VOLTAGE_AVG 0x0D
+#define GB_POWER_SUPPLY_PROP_VOLTAGE_OCV 0x0E
+#define GB_POWER_SUPPLY_PROP_VOLTAGE_BOOT 0x0F
+#define GB_POWER_SUPPLY_PROP_CURRENT_MAX 0x10
+#define GB_POWER_SUPPLY_PROP_CURRENT_NOW 0x11
+#define GB_POWER_SUPPLY_PROP_CURRENT_AVG 0x12
+#define GB_POWER_SUPPLY_PROP_CURRENT_BOOT 0x13
+#define GB_POWER_SUPPLY_PROP_POWER_NOW 0x14
+#define GB_POWER_SUPPLY_PROP_POWER_AVG 0x15
+#define GB_POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN 0x16
+#define GB_POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN 0x17
+#define GB_POWER_SUPPLY_PROP_CHARGE_FULL 0x18
+#define GB_POWER_SUPPLY_PROP_CHARGE_EMPTY 0x19
+#define GB_POWER_SUPPLY_PROP_CHARGE_NOW 0x1A
+#define GB_POWER_SUPPLY_PROP_CHARGE_AVG 0x1B
+#define GB_POWER_SUPPLY_PROP_CHARGE_COUNTER 0x1C
+#define GB_POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT 0x1D
+#define GB_POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX 0x1E
+#define GB_POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE 0x1F
+#define GB_POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX 0x20
+#define GB_POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT 0x21
+#define GB_POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX 0x22
+#define GB_POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT 0x23
+#define GB_POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN 0x24
+#define GB_POWER_SUPPLY_PROP_ENERGY_EMPTY_DESIGN 0x25
+#define GB_POWER_SUPPLY_PROP_ENERGY_FULL 0x26
+#define GB_POWER_SUPPLY_PROP_ENERGY_EMPTY 0x27
+#define GB_POWER_SUPPLY_PROP_ENERGY_NOW 0x28
+#define GB_POWER_SUPPLY_PROP_ENERGY_AVG 0x29
+#define GB_POWER_SUPPLY_PROP_CAPACITY 0x2A
+#define GB_POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN 0x2B
+#define GB_POWER_SUPPLY_PROP_CAPACITY_ALERT_MAX 0x2C
+#define GB_POWER_SUPPLY_PROP_CAPACITY_LEVEL 0x2D
+#define GB_POWER_SUPPLY_PROP_TEMP 0x2E
+#define GB_POWER_SUPPLY_PROP_TEMP_MAX 0x2F
+#define GB_POWER_SUPPLY_PROP_TEMP_MIN 0x30
+#define GB_POWER_SUPPLY_PROP_TEMP_ALERT_MIN 0x31
+#define GB_POWER_SUPPLY_PROP_TEMP_ALERT_MAX 0x32
+#define GB_POWER_SUPPLY_PROP_TEMP_AMBIENT 0x33
+#define GB_POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MIN 0x34
+#define GB_POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MAX 0x35
+#define GB_POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW 0x36
+#define GB_POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG 0x37
+#define GB_POWER_SUPPLY_PROP_TIME_TO_FULL_NOW 0x38
+#define GB_POWER_SUPPLY_PROP_TIME_TO_FULL_AVG 0x39
+#define GB_POWER_SUPPLY_PROP_TYPE 0x3A
+#define GB_POWER_SUPPLY_PROP_SCOPE 0x3B
+#define GB_POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT 0x3C
+#define GB_POWER_SUPPLY_PROP_CALIBRATE 0x3D
+ __u8 is_writeable;
+} __packed;
+
+struct gb_power_supply_get_property_descriptors_request {
+ __u8 psy_id;
+} __packed;
+
+struct gb_power_supply_get_property_descriptors_response {
+ __u8 properties_count;
+ struct gb_power_supply_props_desc props[];
+} __packed;
+
+struct gb_power_supply_get_property_request {
+ __u8 psy_id;
+ __u8 property;
+} __packed;
+
+struct gb_power_supply_get_property_response {
+ __le32 prop_val;
+};
+
+struct gb_power_supply_set_property_request {
+ __u8 psy_id;
+ __u8 property;
+ __le32 prop_val;
+} __packed;
+
+struct gb_power_supply_event_request {
+ __u8 psy_id;
+ __u8 event;
+#define GB_POWER_SUPPLY_UPDATE 0x01
} __packed;
/*
* Power Supply driver for a Greybus module.
*
- * Copyright 2014 Google Inc.
- * Copyright 2014 Linaro Ltd.
+ * 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/slab.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;
+ u32 val;
+ u32 previous_val;
+ bool is_writeable;
+};
+
struct gb_power_supply {
- /*
- * The power supply api changed in 4.1, so handle both the old
- * and new apis in the same driver for now, until this is merged
- * upstream, when all of these version checks can be removed.
- */
+ u8 id;
#ifdef DRIVER_OWNS_PSY_STRUCT
- struct power_supply psy;
+ struct power_supply psy;
#define to_gb_power_supply(x) container_of(x, struct gb_power_supply, psy)
#else
- struct power_supply *psy;
- struct power_supply_desc desc;
+ struct power_supply *psy;
+ struct power_supply_desc desc;
#define to_gb_power_supply(x) power_supply_get_drvdata(x)
#endif
- // FIXME
- // we will want to keep the power supply stats in here as we will be
- // getting updates from the SVC "on the fly" so we don't have to always
- // go ask the power supply for some information. Hopefully...
- struct gb_connection *connection;
+ 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;
+ unsigned int update_interval;
+ bool changed;
+ struct gb_power_supply_prop *props;
+ enum power_supply_property *props_raw;
+};
+
+struct gb_power_supplies {
+ struct gb_connection *connection;
+ u8 supplies_count;
+ struct gb_power_supply *supply;
+ struct mutex supplies_lock;
+};
+
+/* 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;
+};
+static const struct gb_power_supply_changes psy_props_changes[] = {
+ { .prop = GB_POWER_SUPPLY_PROP_STATUS,
+ .tolerance_change = 0,
+ },
+ { .prop = GB_POWER_SUPPLY_PROP_TEMP,
+ .tolerance_change = 500,
+ },
+ { .prop = GB_POWER_SUPPLY_PROP_ONLINE,
+ .tolerance_change = 0,
+ },
};
-static int get_tech(struct gb_power_supply *gb)
+static struct gb_connection *get_conn_from_psy(struct gb_power_supply *gbpsy)
{
- struct gb_power_supply_technology_response tech_response;
- u32 technology;
- int retval;
+ return gbpsy->supplies->connection;
+}
- retval = gb_operation_sync(gb->connection,
- GB_POWER_SUPPLY_TYPE_TECHNOLOGY,
- NULL, 0,
- &tech_response, sizeof(tech_response));
- if (retval)
- return retval;
+static struct gb_power_supply_prop *get_psy_prop(struct gb_power_supply *gbpsy,
+ enum power_supply_property psp)
+{
+ int i;
- /*
- * Map greybus values to power_supply values. Hopefully these are
- * "identical" which should allow gcc to optimize the code away to
- * nothing.
- */
- technology = le32_to_cpu(tech_response.technology);
- switch (technology) {
- case GB_POWER_SUPPLY_TECH_NiMH:
- technology = POWER_SUPPLY_TECHNOLOGY_NiMH;
- break;
- case GB_POWER_SUPPLY_TECH_LION:
- technology = POWER_SUPPLY_TECHNOLOGY_LION;
- break;
- case GB_POWER_SUPPLY_TECH_LIPO:
- technology = POWER_SUPPLY_TECHNOLOGY_LIPO;
- break;
- case GB_POWER_SUPPLY_TECH_LiFe:
- technology = POWER_SUPPLY_TECHNOLOGY_LiFe;
- break;
- case GB_POWER_SUPPLY_TECH_NiCd:
- technology = POWER_SUPPLY_TECHNOLOGY_NiCd;
- break;
- case GB_POWER_SUPPLY_TECH_LiMn:
- technology = POWER_SUPPLY_TECHNOLOGY_LiMn;
- break;
- case GB_POWER_SUPPLY_TECH_UNKNOWN:
- default:
- technology = POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
- break;
+ 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;
+}
+
+#ifdef DRIVER_OWNS_PSY_STRUCT
+static void __gb_power_supply_changed(struct gb_power_supply *gbpsy)
+{
+ power_supply_changed(&gbpsy->psy);
+}
+#else
+static void __gb_power_supply_changed(struct gb_power_supply *gbpsy)
+{
+ power_supply_changed(gbpsy->psy);
+}
+#endif
+
+static void check_changed(struct gb_power_supply *gbpsy,
+ struct gb_power_supply_prop *prop)
+{
+ const struct gb_power_supply_changes *psyc;
+ u32 val = prop->val;
+ u32 prev_val = prop->previous_val;
+ 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)
+ gbpsy->changed = true;
+ else if (val < prev_val &&
+ prev_val - val > psyc->tolerance_change)
+ gbpsy->changed = true;
+ else if (val > prev_val &&
+ val - prev_val > psyc->tolerance_change)
+ gbpsy->changed = true;
+ break;
+ }
}
- return technology;
}
-static int get_status(struct gb_power_supply *gb)
+static int total_props(struct gb_power_supply *gbpsy)
{
- struct gb_power_supply_status_response status_response;
- u16 psy_status;
- int retval;
+ /* this return the intval plus the strval properties */
+ return (gbpsy->properties_count + gbpsy->properties_count_str);
+}
- retval = gb_operation_sync(gb->connection, GB_POWER_SUPPLY_TYPE_STATUS,
- NULL, 0,
- &status_response, sizeof(status_response));
- if (retval)
- return retval;
+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))) {
+#ifdef PSY_HAVE_PUT
+ power_supply_put(psy);
+#endif
+ 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;
+ int ret;
+ int i;
+
+ if (gbpsy->properties_count == 0)
+ return 0;
+
+ req.psy_id = gbpsy->id;
+
+ ret = gb_operation_sync(connection,
+ GB_POWER_SUPPLY_TYPE_GET_PROP_DESCRIPTORS,
+ &req, sizeof(req), &resp,
+ sizeof(resp) + gbpsy->properties_count *
+ sizeof(struct gb_power_supply_props_desc));
+ if (ret < 0)
+ return ret;
+
+ gbpsy->props = kcalloc(gbpsy->properties_count, sizeof(*gbpsy->props),
+ GFP_KERNEL);
+ if (!gbpsy->props)
+ return -ENOMEM;
+
+ gbpsy->props_raw = kzalloc(gbpsy->properties_count *
+ sizeof(*gbpsy->props_raw), GFP_KERNEL);
+ if (!gbpsy->props_raw)
+ return -ENOMEM;
+
+ /* Store available properties */
+ for (i = 0; i < gbpsy->properties_count; i++) {
+ gbpsy->props[i].prop = resp.props[i].property;
+ gbpsy->props_raw[i] = resp.props[i].property;
+ if (resp.props[i].is_writeable)
+ gbpsy->props[i].is_writeable = true;
+ }
/*
- * Map greybus values to power_supply values. Hopefully these are
- * "identical" which should allow gcc to optimize the code away to
- * nothing.
+ * now append the properties that we already got information in the
+ * get_description operation. (char * ones)
*/
- psy_status = le16_to_cpu(status_response.psy_status);
- switch (psy_status) {
- case GB_POWER_SUPPLY_STATUS_CHARGING:
- psy_status = POWER_SUPPLY_STATUS_CHARGING;
- break;
- case GB_POWER_SUPPLY_STATUS_DISCHARGING:
- psy_status = POWER_SUPPLY_STATUS_DISCHARGING;
+ _gb_power_supply_append_props(gbpsy);
+
+ return 0;
+}
+
+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;
+ u32 val;
+ int ret;
+
+ prop = get_psy_prop(gbpsy, psp);
+ if (!prop)
+ return -EINVAL;
+ req.psy_id = gbpsy->id;
+ req.property = (u8)psp;
+
+ 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 = kstrndup(gbpsy->model_name, PROP_MAX, GFP_KERNEL);
break;
- case GB_POWER_SUPPLY_STATUS_NOT_CHARGING:
- psy_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = kstrndup(gbpsy->manufacturer, PROP_MAX,
+ GFP_KERNEL);
break;
- case GB_POWER_SUPPLY_STATUS_FULL:
- psy_status = POWER_SUPPLY_STATUS_FULL;
+ case POWER_SUPPLY_PROP_SERIAL_NUMBER:
+ val->strval = kstrndup(gbpsy->serial_number, PROP_MAX,
+ GFP_KERNEL);
break;
- case GB_POWER_SUPPLY_STATUS_UNKNOWN:
default:
- psy_status = POWER_SUPPLY_STATUS_UNKNOWN;
break;
}
- return psy_status;
+
+ return 0;
}
-static int get_max_voltage(struct gb_power_supply *gb)
+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_max_voltage_response volt_response;
- u32 max_voltage;
- int retval;
+ 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);
- retval = gb_operation_sync(gb->connection,
- GB_POWER_SUPPLY_TYPE_MAX_VOLTAGE,
- NULL, 0,
- &volt_response, sizeof(volt_response));
- if (retval)
- return retval;
+ if (ret < 0)
+ dev_err(&connection->bundle->dev, "get property %u\n", psp);
- max_voltage = le32_to_cpu(volt_response.max_voltage);
- return max_voltage;
+ return 0;
}
-static int get_percent_capacity(struct gb_power_supply *gb)
+static int gb_power_supply_status_get(struct gb_power_supply *gbpsy)
{
- struct gb_power_supply_capacity_response capacity_response;
- u32 capacity;
- int retval;
+ int ret = 0;
+ int i;
+
+ /* check if cache is good enough */
+ if (gbpsy->last_update &&
+ time_is_after_jiffies(gbpsy->last_update +
+ msecs_to_jiffies(cache_time)))
+ return 0;
+
+ for (i = 0; i < gbpsy->properties_count; i++) {
+ ret = __gb_power_supply_property_update(gbpsy,
+ gbpsy->props[i].prop);
+ if (ret < 0)
+ break;
+ }
- retval = gb_operation_sync(gb->connection,
- GB_POWER_SUPPLY_TYPE_PERCENT_CAPACITY,
- NULL, 0, &capacity_response,
- sizeof(capacity_response));
- if (retval)
- return retval;
+ if (ret == 0)
+ gbpsy->last_update = jiffies;
- capacity = le32_to_cpu(capacity_response.capacity);
- return capacity;
+ return ret;
}
-static int get_temp(struct gb_power_supply *gb)
+static void gb_power_supply_status_update(struct gb_power_supply *gbpsy)
{
- struct gb_power_supply_temperature_response temp_response;
- u32 temperature;
- int retval;
+ /* check if there a change that need to be reported */
+ gb_power_supply_status_get(gbpsy);
- retval = gb_operation_sync(gb->connection,
- GB_POWER_SUPPLY_TYPE_TEMPERATURE,
- NULL, 0,
- &temp_response, sizeof(temp_response));
- if (retval)
- return retval;
+ if (!gbpsy->changed)
+ return;
- temperature = le32_to_cpu(temp_response.temperature);
- return temperature;
+ gbpsy->update_interval = update_interval_init;
+ __gb_power_supply_changed(gbpsy);
+ gbpsy->changed = false;
}
-static int get_voltage(struct gb_power_supply *gb)
+static void gb_power_supply_work(struct work_struct *work)
{
- struct gb_power_supply_voltage_response voltage_response;
- u32 voltage;
- int retval;
+ struct gb_power_supply *gbpsy = container_of(work,
+ struct gb_power_supply,
+ work.work);
- retval = gb_operation_sync(gb->connection, GB_POWER_SUPPLY_TYPE_VOLTAGE,
- NULL, 0,
- &voltage_response, sizeof(voltage_response));
- if (retval)
- return retval;
+ /*
+ * if the poll interval is not set, disable polling, this is helpful
+ * specially at unregister time.
+ */
+ if (!gbpsy->update_interval)
+ return;
- voltage = le32_to_cpu(voltage_response.voltage);
- return voltage;
+ 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 *gb = to_gb_power_supply(b);
+ struct gb_power_supply *gbpsy = to_gb_power_supply(b);
- switch (psp) {
- case POWER_SUPPLY_PROP_TECHNOLOGY:
- val->intval = get_tech(gb);
- break;
+ gb_power_supply_status_get(gbpsy);
- case POWER_SUPPLY_PROP_STATUS:
- val->intval = get_status(gb);
- break;
+ return _gb_power_supply_property_get(gbpsy, psp, val);
+}
- case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
- val->intval = get_max_voltage(gb);
- break;
+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;
- case POWER_SUPPLY_PROP_CAPACITY:
- val->intval = get_percent_capacity(gb);
- break;
+ prop = get_psy_prop(gbpsy, psp);
+ if (!prop)
+ return -EINVAL;
+ req.psy_id = gbpsy->id;
+ req.property = (u8)psp;
+ req.prop_val = cpu_to_le32(val);
- case POWER_SUPPLY_PROP_TEMP:
- val->intval = get_temp(gb);
- break;
+ ret = gb_operation_sync(connection, GB_POWER_SUPPLY_TYPE_SET_PROPERTY,
+ &req, sizeof(req), NULL, 0);
+ if (ret < 0)
+ goto out;
- case POWER_SUPPLY_PROP_VOLTAGE_NOW:
- val->intval = get_voltage(gb);
- break;
+ /* cache immediately the new value */
+ prop->val = val;
- default:
- return -EINVAL;
- }
+out:
+ return ret;
+}
- return (val->intval < 0) ? val->intval : 0;
+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);
}
-// FIXME - verify this list, odds are some can be removed and others added.
-static enum power_supply_property psy_props[] = {
- POWER_SUPPLY_PROP_TECHNOLOGY,
- POWER_SUPPLY_PROP_STATUS,
- POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
- POWER_SUPPLY_PROP_CAPACITY,
- POWER_SUPPLY_PROP_TEMP,
- POWER_SUPPLY_PROP_VOLTAGE_NOW,
-};
#ifdef DRIVER_OWNS_PSY_STRUCT
-static int init_and_register(struct gb_connection *connection,
- struct gb_battery *gb)
+static int gb_power_supply_register(struct gb_power_supply *gbpsy)
{
- // FIXME - get a better (i.e. unique) name
- // FIXME - anything else needs to be set?
- gb->psy.name = "gb_battery";
- gb->psy.type = POWER_SUPPLY_TYPE_BATTERY;
- gb->psy.properties = psy_props;
- gb->psy.num_properties = ARRAY_SIZE(psy_props);
- gb->psy.get_property = get_property;
-
- return power_supply_register(&connection->bundle->dev, &gb->psy);
+ struct gb_connection *connection = get_conn_from_psy(gbpsy);
+
+ gbpsy->psy.name = gbpsy->name;
+ gbpsy->psy.type = gbpsy->type;
+ gbpsy->psy.properties = gbpsy->props_raw;
+ gbpsy->psy.num_properties = total_props(gbpsy);
+ gbpsy->psy.get_property = get_property;
+ gbpsy->psy.set_property = set_property;
+ gbpsy->psy.property_is_writeable = property_is_writeable;
+
+ return power_supply_register(&connection->bundle->dev,
+ &gbpsy->psy);
}
#else
-static int init_and_register(struct gb_connection *connection,
- struct gb_power_supply *gb)
+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 = gb;
+ cfg.drv_data = gbpsy;
- // FIXME - get a better (i.e. unique) name
- // FIXME - anything else needs to be set?
- gb->desc.name = "gb_battery";
- gb->desc.type = POWER_SUPPLY_TYPE_BATTERY;
- gb->desc.properties = psy_props;
- gb->desc.num_properties = ARRAY_SIZE(psy_props);
- gb->desc.get_property = get_property;
+ 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;
- gb->psy = power_supply_register(&connection->bundle->dev,
- &gb->desc, &cfg);
- if (IS_ERR(gb->psy))
- return PTR_ERR(gb->psy);
+ gbpsy->psy = power_supply_register(&connection->bundle->dev,
+ &gbpsy->desc, &cfg);
+ if (IS_ERR(gbpsy->psy))
+ return PTR_ERR(gbpsy->psy);
return 0;
}
#endif
+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);
+ kfree(gbpsy);
+}
+
+static void _gb_power_supply_release(struct gb_power_supply *gbpsy)
+{
+ if (!gbpsy)
+ return;
+
+ gbpsy->update_interval = 0;
+
+ cancel_delayed_work_sync(&gbpsy->work);
+#ifdef DRIVER_OWNS_PSY_STRUCT
+ power_supply_unregister(&gbpsy->psy);
+#else
+ power_supply_unregister(gbpsy->psy);
+#endif
+
+ _gb_power_supply_free(gbpsy);
+}
+
+static void _gb_power_supplies_release(struct gb_power_supplies *supplies)
+{
+ int i;
+
+ mutex_lock(&supplies->supplies_lock);
+ for (i = 0; i < supplies->supplies_count; i++)
+ _gb_power_supply_release(&supplies->supply[i]);
+ mutex_unlock(&supplies->supplies_lock);
+}
+
+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)
+ goto out;
+
+ ret = gb_power_supply_prop_descriptors_get(gbpsy);
+ if (ret < 0)
+ goto out;
+
+ /* 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)
+ goto out;
+
+ ret = gb_power_supply_register(gbpsy);
+ if (ret < 0)
+ goto out;
+
+ gbpsy->update_interval = update_interval_init;
+ INIT_DELAYED_WORK(&gbpsy->work, gb_power_supply_work);
+ schedule_delayed_work(&gbpsy->work, 0);
+
+out:
+ return ret;
+}
+
+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)
+ return -ENOMEM;
+
+ 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_supply_event_recv(u8 type, struct gb_operation *op)
+{
+ struct gb_connection *connection = op->connection;
+ struct gb_power_supplies *supplies = connection->private;
+ 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 (type != GB_POWER_SUPPLY_TYPE_EVENT) {
+ dev_err(&connection->bundle->dev,
+ "Unsupported unsolicited event: %u\n", 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]) {
+ 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)
+ gb_power_supply_status_update(gbpsy);
+
+out_unlock:
+ mutex_unlock(&supplies->supplies_lock);
+ return ret;
+}
+
static int gb_power_supply_connection_init(struct gb_connection *connection)
{
- struct gb_power_supply *gb;
- int retval;
+ struct gb_power_supplies *supplies;
- gb = kzalloc(sizeof(*gb), GFP_KERNEL);
- if (!gb)
+ supplies = kzalloc(sizeof(*supplies), GFP_KERNEL);
+ if (!supplies)
return -ENOMEM;
- gb->connection = connection;
- connection->private = gb;
+ supplies->connection = connection;
+ connection->private = supplies;
- retval = init_and_register(connection, gb);
- if (retval)
- kfree(gb);
+ mutex_init(&supplies->supplies_lock);
- return retval;
+ return gb_power_supplies_setup(supplies);
}
static void gb_power_supply_connection_exit(struct gb_connection *connection)
{
- struct gb_power_supply *gb = connection->private;
+ struct gb_power_supplies *supplies = connection->private;
-#ifdef DRIVER_OWNS_PSY_STRUCT
- power_supply_unregister(&gb->psy);
-#else
- power_supply_unregister(gb->psy);
-#endif
- kfree(gb);
+ _gb_power_supplies_release(supplies);
}
static struct gb_protocol power_supply_protocol = {
.minor = GB_POWER_SUPPLY_VERSION_MINOR,
.connection_init = gb_power_supply_connection_init,
.connection_exit = gb_power_supply_connection_exit,
- .request_recv = NULL, /* no incoming requests */
+ .request_recv = gb_power_supply_event_recv,
};
gb_protocol_driver(&power_supply_protocol);