![Greg Kroah-Hartman](/assets/img/avatar_default.png)
Now that the SPDX tag is in all greybus files, that identifies the license in a specific and legally-defined manner. So the extra GPL text wording can be removed as it is no longer needed at all. This is done on a quest to remove the 700+ different ways that files in the kernel describe the GPL license text. And there's unneeded stuff like the address (sometimes incorrect) for the FSF which is never needed. No copyright headers or other non-license-description text was removed. Cc: Vaibhav Hiremath <hvaibhav.linux@gmail.com> Reviewed-by: Alex Elder <elder@linaro.org> Acked-by: Vaibhav Agarwal <vaibhav.sr@gmail.com> Acked-by: David Lin <dtwlin@gmail.com> Acked-by: Johan Hovold <johan@kernel.org> Acked-by: Viresh Kumar <viresh.kumar@linaro.org> Acked-by: Mark Greer <mgreer@animalcreek.com> Acked-by: Rui Miguel Silva <rmfrfs@gmail.com> Acked-by: "Bryan O'Donoghue" <pure.logic@nexus-software.ie> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1141 lines
28 KiB
C
1141 lines
28 KiB
C
// 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 "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 = 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");
|