2014-09-07 16:39:34 -06:00
|
|
|
/*
|
|
|
|
* Battery driver for a Greybus module.
|
|
|
|
*
|
|
|
|
* Copyright 2014 Google Inc.
|
2014-12-12 11:08:42 -07:00
|
|
|
* Copyright 2014 Linaro Ltd.
|
2014-09-07 16:39:34 -06:00
|
|
|
*
|
|
|
|
* Released under the GPLv2 only.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/power_supply.h>
|
|
|
|
#include "greybus.h"
|
|
|
|
|
|
|
|
struct gb_battery {
|
2015-05-01 12:41:00 -06:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2015-05-07 12:00:21 -06:00
|
|
|
#ifdef DRIVER_OWNS_PSY_STRUCT
|
2014-09-07 16:39:34 -06:00
|
|
|
struct power_supply bat;
|
2015-05-01 12:41:00 -06:00
|
|
|
#define to_gb_battery(x) container_of(x, struct gb_battery, bat)
|
|
|
|
#else
|
|
|
|
struct power_supply *bat;
|
|
|
|
struct power_supply_desc desc;
|
|
|
|
#define to_gb_battery(x) power_supply_get_drvdata(x)
|
|
|
|
#endif
|
2014-09-07 16:54:24 -06:00
|
|
|
// FIXME
|
|
|
|
// we will want to keep the battery 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 battery for some information. Hopefully...
|
2014-10-20 01:24:57 -06:00
|
|
|
struct gb_connection *connection;
|
|
|
|
u8 version_major;
|
|
|
|
u8 version_minor;
|
|
|
|
|
2014-09-07 16:39:34 -06:00
|
|
|
};
|
|
|
|
|
2014-10-21 00:31:24 -06:00
|
|
|
/* Version of the Greybus battery protocol we support */
|
|
|
|
#define GB_BATTERY_VERSION_MAJOR 0x00
|
|
|
|
#define GB_BATTERY_VERSION_MINOR 0x01
|
|
|
|
|
|
|
|
/* Greybus battery request types */
|
|
|
|
#define GB_BATTERY_TYPE_INVALID 0x00
|
|
|
|
#define GB_BATTERY_TYPE_PROTOCOL_VERSION 0x01
|
|
|
|
#define GB_BATTERY_TYPE_TECHNOLOGY 0x02
|
|
|
|
#define GB_BATTERY_TYPE_STATUS 0x03
|
|
|
|
#define GB_BATTERY_TYPE_MAX_VOLTAGE 0x04
|
2014-12-08 15:45:10 -07:00
|
|
|
#define GB_BATTERY_TYPE_PERCENT_CAPACITY 0x05
|
2014-10-21 00:31:24 -06:00
|
|
|
#define GB_BATTERY_TYPE_TEMPERATURE 0x06
|
|
|
|
#define GB_BATTERY_TYPE_VOLTAGE 0x07
|
2014-12-08 15:45:10 -07:00
|
|
|
#define GB_BATTERY_TYPE_CURRENT 0x08
|
|
|
|
#define GB_BATTERY_TYPE_CAPACITY 0x09 // TODO - POWER_SUPPLY_PROP_CURRENT_MAX
|
|
|
|
#define GB_BATTERY_TYPE_SHUTDOWN_TEMP 0x0a // TODO - POWER_SUPPLY_PROP_TEMP_ALERT_MAX
|
2014-10-21 00:31:24 -06:00
|
|
|
|
|
|
|
/* Should match up with battery types in linux/power_supply.h */
|
|
|
|
#define GB_BATTERY_TECH_UNKNOWN 0x0000
|
|
|
|
#define GB_BATTERY_TECH_NiMH 0x0001
|
|
|
|
#define GB_BATTERY_TECH_LION 0x0002
|
|
|
|
#define GB_BATTERY_TECH_LIPO 0x0003
|
|
|
|
#define GB_BATTERY_TECH_LiFe 0x0004
|
|
|
|
#define GB_BATTERY_TECH_NiCd 0x0005
|
|
|
|
#define GB_BATTERY_TECH_LiMn 0x0006
|
|
|
|
|
2014-11-19 15:29:14 -07:00
|
|
|
struct gb_battery_technology_response {
|
2014-10-21 00:31:24 -06:00
|
|
|
__le32 technology;
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Should match up with battery status in linux/power_supply.h */
|
|
|
|
#define GB_BATTERY_STATUS_UNKNOWN 0x0000
|
|
|
|
#define GB_BATTERY_STATUS_CHARGING 0x0001
|
|
|
|
#define GB_BATTERY_STATUS_DISCHARGING 0x0002
|
|
|
|
#define GB_BATTERY_STATUS_NOT_CHARGING 0x0003
|
|
|
|
#define GB_BATTERY_STATUS_FULL 0x0004
|
|
|
|
|
2014-11-19 15:29:14 -07:00
|
|
|
struct gb_battery_status_response {
|
2014-10-21 00:31:24 -06:00
|
|
|
__le16 battery_status;
|
|
|
|
};
|
|
|
|
|
2014-11-19 15:29:14 -07:00
|
|
|
struct gb_battery_max_voltage_response {
|
2014-10-21 00:31:24 -06:00
|
|
|
__le32 max_voltage;
|
|
|
|
};
|
|
|
|
|
2014-11-19 15:29:14 -07:00
|
|
|
struct gb_battery_capacity_response {
|
2014-10-21 00:31:24 -06:00
|
|
|
__le32 capacity;
|
|
|
|
};
|
|
|
|
|
2014-11-19 15:29:14 -07:00
|
|
|
struct gb_battery_temperature_response {
|
2014-10-21 00:31:24 -06:00
|
|
|
__le32 temperature;
|
|
|
|
};
|
|
|
|
|
2014-11-19 15:29:14 -07:00
|
|
|
struct gb_battery_voltage_response {
|
2014-10-21 00:31:24 -06:00
|
|
|
__le32 voltage;
|
|
|
|
};
|
|
|
|
|
2015-01-21 05:42:36 -07:00
|
|
|
/* Define get_version() routine */
|
|
|
|
define_get_version(gb_battery, BATTERY);
|
2014-10-21 00:31:24 -06:00
|
|
|
|
2014-10-20 01:09:49 -06:00
|
|
|
static int get_tech(struct gb_battery *gb)
|
|
|
|
{
|
2014-11-19 15:29:14 -07:00
|
|
|
struct gb_battery_technology_response tech_response;
|
2014-10-21 00:31:24 -06:00
|
|
|
u32 technology;
|
|
|
|
int retval;
|
|
|
|
|
2014-11-23 18:45:19 -07:00
|
|
|
retval = gb_operation_sync(gb->connection, GB_BATTERY_TYPE_TECHNOLOGY,
|
|
|
|
NULL, 0,
|
2014-11-19 15:29:14 -07:00
|
|
|
&tech_response, sizeof(tech_response));
|
2014-10-21 00:31:24 -06:00
|
|
|
if (retval)
|
|
|
|
return retval;
|
|
|
|
|
|
|
|
/*
|
2014-10-21 02:25:13 -06:00
|
|
|
* Map greybus values to power_supply values. Hopefully these are
|
2014-11-20 22:56:30 -07:00
|
|
|
* "identical" which should allow gcc to optimize the code away to
|
2014-10-21 02:25:13 -06:00
|
|
|
* nothing.
|
2014-10-21 00:31:24 -06:00
|
|
|
*/
|
2014-11-19 15:29:14 -07:00
|
|
|
technology = le32_to_cpu(tech_response.technology);
|
2014-10-21 02:25:13 -06:00
|
|
|
switch (technology) {
|
|
|
|
case GB_BATTERY_TECH_NiMH:
|
|
|
|
technology = POWER_SUPPLY_TECHNOLOGY_NiMH;
|
|
|
|
break;
|
|
|
|
case GB_BATTERY_TECH_LION:
|
|
|
|
technology = POWER_SUPPLY_TECHNOLOGY_LION;
|
|
|
|
break;
|
|
|
|
case GB_BATTERY_TECH_LIPO:
|
|
|
|
technology = POWER_SUPPLY_TECHNOLOGY_LIPO;
|
|
|
|
break;
|
|
|
|
case GB_BATTERY_TECH_LiFe:
|
|
|
|
technology = POWER_SUPPLY_TECHNOLOGY_LiFe;
|
|
|
|
break;
|
|
|
|
case GB_BATTERY_TECH_NiCd:
|
|
|
|
technology = POWER_SUPPLY_TECHNOLOGY_NiCd;
|
|
|
|
break;
|
|
|
|
case GB_BATTERY_TECH_LiMn:
|
|
|
|
technology = POWER_SUPPLY_TECHNOLOGY_LiMn;
|
|
|
|
break;
|
|
|
|
case GB_BATTERY_TECH_UNKNOWN:
|
|
|
|
default:
|
|
|
|
technology = POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
|
|
|
|
break;
|
|
|
|
}
|
2014-10-21 00:31:24 -06:00
|
|
|
return technology;
|
2014-10-20 01:09:49 -06:00
|
|
|
}
|
|
|
|
|
2014-09-07 16:39:34 -06:00
|
|
|
static int get_status(struct gb_battery *gb)
|
|
|
|
{
|
2014-11-19 15:29:14 -07:00
|
|
|
struct gb_battery_status_response status_response;
|
2014-10-21 00:31:24 -06:00
|
|
|
u16 battery_status;
|
|
|
|
int retval;
|
|
|
|
|
2014-11-23 18:45:19 -07:00
|
|
|
retval = gb_operation_sync(gb->connection, GB_BATTERY_TYPE_STATUS,
|
|
|
|
NULL, 0,
|
2014-11-19 15:29:14 -07:00
|
|
|
&status_response, sizeof(status_response));
|
2014-10-21 00:31:24 -06:00
|
|
|
if (retval)
|
|
|
|
return retval;
|
|
|
|
|
|
|
|
/*
|
2014-10-21 02:25:13 -06:00
|
|
|
* Map greybus values to power_supply values. Hopefully these are
|
2014-11-20 22:56:30 -07:00
|
|
|
* "identical" which should allow gcc to optimize the code away to
|
2014-10-21 02:25:13 -06:00
|
|
|
* nothing.
|
2014-10-21 00:31:24 -06:00
|
|
|
*/
|
2014-11-19 15:29:14 -07:00
|
|
|
battery_status = le16_to_cpu(status_response.battery_status);
|
2014-10-21 02:25:13 -06:00
|
|
|
switch (battery_status) {
|
|
|
|
case GB_BATTERY_STATUS_CHARGING:
|
|
|
|
battery_status = POWER_SUPPLY_STATUS_CHARGING;
|
|
|
|
break;
|
|
|
|
case GB_BATTERY_STATUS_DISCHARGING:
|
|
|
|
battery_status = POWER_SUPPLY_STATUS_DISCHARGING;
|
|
|
|
break;
|
|
|
|
case GB_BATTERY_STATUS_NOT_CHARGING:
|
|
|
|
battery_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
|
|
|
|
break;
|
|
|
|
case GB_BATTERY_STATUS_FULL:
|
|
|
|
battery_status = POWER_SUPPLY_STATUS_FULL;
|
|
|
|
break;
|
|
|
|
case GB_BATTERY_STATUS_UNKNOWN:
|
|
|
|
default:
|
|
|
|
battery_status = POWER_SUPPLY_STATUS_UNKNOWN;
|
|
|
|
break;
|
|
|
|
}
|
2014-10-21 00:31:24 -06:00
|
|
|
return battery_status;
|
2014-09-07 16:39:34 -06:00
|
|
|
}
|
|
|
|
|
2014-10-20 01:09:49 -06:00
|
|
|
static int get_max_voltage(struct gb_battery *gb)
|
|
|
|
{
|
2014-11-19 15:29:14 -07:00
|
|
|
struct gb_battery_max_voltage_response volt_response;
|
2014-10-21 00:31:24 -06:00
|
|
|
u32 max_voltage;
|
|
|
|
int retval;
|
|
|
|
|
2014-11-23 18:45:19 -07:00
|
|
|
retval = gb_operation_sync(gb->connection, GB_BATTERY_TYPE_MAX_VOLTAGE,
|
|
|
|
NULL, 0,
|
2014-11-19 15:29:14 -07:00
|
|
|
&volt_response, sizeof(volt_response));
|
2014-10-21 00:31:24 -06:00
|
|
|
if (retval)
|
|
|
|
return retval;
|
|
|
|
|
2014-11-19 15:29:14 -07:00
|
|
|
max_voltage = le32_to_cpu(volt_response.max_voltage);
|
2014-10-21 00:31:24 -06:00
|
|
|
return max_voltage;
|
2014-10-20 01:09:49 -06:00
|
|
|
}
|
|
|
|
|
2014-12-08 15:45:10 -07:00
|
|
|
static int get_percent_capacity(struct gb_battery *gb)
|
2014-09-07 16:39:34 -06:00
|
|
|
{
|
2014-11-19 15:29:14 -07:00
|
|
|
struct gb_battery_capacity_response capacity_response;
|
2014-10-21 00:31:24 -06:00
|
|
|
u32 capacity;
|
|
|
|
int retval;
|
|
|
|
|
2014-12-08 15:45:10 -07:00
|
|
|
retval = gb_operation_sync(gb->connection,
|
|
|
|
GB_BATTERY_TYPE_PERCENT_CAPACITY,
|
2014-11-23 18:45:19 -07:00
|
|
|
NULL, 0, &capacity_response,
|
|
|
|
sizeof(capacity_response));
|
2014-10-21 00:31:24 -06:00
|
|
|
if (retval)
|
|
|
|
return retval;
|
|
|
|
|
2014-11-19 15:29:14 -07:00
|
|
|
capacity = le32_to_cpu(capacity_response.capacity);
|
2014-10-21 00:31:24 -06:00
|
|
|
return capacity;
|
2014-09-07 16:39:34 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
static int get_temp(struct gb_battery *gb)
|
|
|
|
{
|
2014-11-19 15:29:14 -07:00
|
|
|
struct gb_battery_temperature_response temp_response;
|
2014-10-21 00:31:24 -06:00
|
|
|
u32 temperature;
|
|
|
|
int retval;
|
|
|
|
|
2014-11-23 18:45:19 -07:00
|
|
|
retval = gb_operation_sync(gb->connection, GB_BATTERY_TYPE_TEMPERATURE,
|
|
|
|
NULL, 0,
|
2014-11-19 15:29:14 -07:00
|
|
|
&temp_response, sizeof(temp_response));
|
2014-10-21 00:31:24 -06:00
|
|
|
if (retval)
|
|
|
|
return retval;
|
|
|
|
|
2014-11-19 15:29:14 -07:00
|
|
|
temperature = le32_to_cpu(temp_response.temperature);
|
2014-10-21 00:31:24 -06:00
|
|
|
return temperature;
|
2014-09-07 16:39:34 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
static int get_voltage(struct gb_battery *gb)
|
|
|
|
{
|
2014-11-19 15:29:14 -07:00
|
|
|
struct gb_battery_voltage_response voltage_response;
|
2014-10-21 00:31:24 -06:00
|
|
|
u32 voltage;
|
|
|
|
int retval;
|
|
|
|
|
2014-11-23 18:45:19 -07:00
|
|
|
retval = gb_operation_sync(gb->connection, GB_BATTERY_TYPE_VOLTAGE,
|
|
|
|
NULL, 0,
|
2014-11-19 15:29:14 -07:00
|
|
|
&voltage_response, sizeof(voltage_response));
|
2014-10-21 00:31:24 -06:00
|
|
|
if (retval)
|
|
|
|
return retval;
|
|
|
|
|
2014-11-19 15:29:14 -07:00
|
|
|
voltage = le32_to_cpu(voltage_response.voltage);
|
2014-10-21 00:31:24 -06:00
|
|
|
return voltage;
|
2014-09-07 16:39:34 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
static int get_property(struct power_supply *b,
|
|
|
|
enum power_supply_property psp,
|
|
|
|
union power_supply_propval *val)
|
|
|
|
{
|
|
|
|
struct gb_battery *gb = to_gb_battery(b);
|
|
|
|
|
|
|
|
switch (psp) {
|
|
|
|
case POWER_SUPPLY_PROP_TECHNOLOGY:
|
2014-10-20 01:09:49 -06:00
|
|
|
val->intval = get_tech(gb);
|
2014-09-07 16:39:34 -06:00
|
|
|
break;
|
|
|
|
|
|
|
|
case POWER_SUPPLY_PROP_STATUS:
|
|
|
|
val->intval = get_status(gb);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
|
2014-10-20 01:09:49 -06:00
|
|
|
val->intval = get_max_voltage(gb);
|
2014-09-07 16:39:34 -06:00
|
|
|
break;
|
|
|
|
|
|
|
|
case POWER_SUPPLY_PROP_CAPACITY:
|
2014-12-08 15:45:10 -07:00
|
|
|
val->intval = get_percent_capacity(gb);
|
2014-09-07 16:39:34 -06:00
|
|
|
break;
|
|
|
|
|
|
|
|
case POWER_SUPPLY_PROP_TEMP:
|
|
|
|
val->intval = get_temp(gb);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
|
|
|
|
val->intval = get_voltage(gb);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// FIXME - verify this list, odds are some can be removed and others added.
|
|
|
|
static enum power_supply_property battery_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,
|
|
|
|
};
|
|
|
|
|
2015-05-07 12:00:21 -06:00
|
|
|
#ifdef DRIVER_OWNS_PSY_STRUCT
|
2015-05-01 12:41:00 -06:00
|
|
|
static int init_and_register(struct gb_connection *connection,
|
|
|
|
struct gb_battery *gb)
|
|
|
|
{
|
|
|
|
// FIXME - get a better (i.e. unique) name
|
|
|
|
// FIXME - anything else needs to be set?
|
|
|
|
gb->bat.name = "gb_battery";
|
|
|
|
gb->bat.type = POWER_SUPPLY_TYPE_BATTERY;
|
|
|
|
gb->bat.properties = battery_props;
|
|
|
|
gb->bat.num_properties = ARRAY_SIZE(battery_props);
|
|
|
|
gb->bat.get_property = get_property;
|
|
|
|
|
2015-05-07 12:00:20 -06:00
|
|
|
return power_supply_register(&connection->bundle->intf->dev, &gb->bat);
|
2015-05-01 12:41:00 -06:00
|
|
|
}
|
|
|
|
#else
|
|
|
|
static int init_and_register(struct gb_connection *connection,
|
|
|
|
struct gb_battery *gb)
|
|
|
|
{
|
|
|
|
struct power_supply_config cfg = {};
|
|
|
|
|
|
|
|
cfg.drv_data = gb;
|
|
|
|
|
|
|
|
// 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 = battery_props;
|
|
|
|
gb->desc.num_properties = ARRAY_SIZE(battery_props);
|
|
|
|
gb->desc.get_property = get_property;
|
|
|
|
|
|
|
|
gb->bat = power_supply_register(&connection->bundle->intf->dev,
|
|
|
|
&gb->desc, &cfg);
|
2015-05-07 12:00:20 -06:00
|
|
|
if (IS_ERR(gb->bat))
|
|
|
|
return PTR_ERR(gb->bat);
|
|
|
|
|
|
|
|
return 0;
|
2015-05-01 12:41:00 -06:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2014-10-27 05:04:30 -06:00
|
|
|
static int gb_battery_connection_init(struct gb_connection *connection)
|
2014-09-07 16:39:34 -06:00
|
|
|
{
|
|
|
|
struct gb_battery *gb;
|
|
|
|
int retval;
|
|
|
|
|
|
|
|
gb = kzalloc(sizeof(*gb), GFP_KERNEL);
|
|
|
|
if (!gb)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2014-10-27 03:58:54 -06:00
|
|
|
gb->connection = connection;
|
2014-10-20 22:01:03 -06:00
|
|
|
connection->private = gb;
|
2014-10-20 01:24:57 -06:00
|
|
|
|
2014-10-21 00:31:24 -06:00
|
|
|
/* Check the version */
|
|
|
|
retval = get_version(gb);
|
2015-05-07 12:00:20 -06:00
|
|
|
if (retval)
|
|
|
|
goto out;
|
|
|
|
retval = init_and_register(connection, gb);
|
|
|
|
out:
|
|
|
|
if (retval)
|
2014-10-21 00:31:24 -06:00
|
|
|
kfree(gb);
|
|
|
|
|
2015-05-07 12:00:20 -06:00
|
|
|
return retval;
|
2014-09-07 16:39:34 -06:00
|
|
|
}
|
|
|
|
|
2014-10-27 05:04:30 -06:00
|
|
|
static void gb_battery_connection_exit(struct gb_connection *connection)
|
2014-10-20 22:01:04 -06:00
|
|
|
{
|
|
|
|
struct gb_battery *gb = connection->private;
|
|
|
|
|
2015-05-07 12:00:21 -06:00
|
|
|
#ifdef DRIVER_OWNS_PSY_STRUCT
|
2014-10-20 22:01:04 -06:00
|
|
|
power_supply_unregister(&gb->bat);
|
2015-05-01 12:41:00 -06:00
|
|
|
#else
|
|
|
|
power_supply_unregister(gb->bat);
|
|
|
|
#endif
|
2014-10-20 22:01:04 -06:00
|
|
|
kfree(gb);
|
|
|
|
}
|
|
|
|
|
2014-11-05 15:12:53 -07:00
|
|
|
static struct gb_protocol battery_protocol = {
|
2014-12-24 14:01:45 -07:00
|
|
|
.name = "battery",
|
2014-11-05 15:12:53 -07:00
|
|
|
.id = GREYBUS_PROTOCOL_BATTERY,
|
|
|
|
.major = 0,
|
|
|
|
.minor = 1,
|
2014-11-05 15:12:54 -07:00
|
|
|
.connection_init = gb_battery_connection_init,
|
|
|
|
.connection_exit = gb_battery_connection_exit,
|
2014-11-05 15:12:55 -07:00
|
|
|
.request_recv = NULL, /* no incoming requests */
|
2014-11-05 15:12:53 -07:00
|
|
|
};
|
|
|
|
|
2014-12-24 14:01:42 -07:00
|
|
|
gb_protocol_driver(&battery_protocol);
|
2014-11-05 15:12:53 -07:00
|
|
|
|
2014-12-24 14:01:42 -07:00
|
|
|
MODULE_LICENSE("GPL v2");
|