blob: 4bd7aed0a27b581c56ef4b3889b22da0d36ce3e9 [file] [log] [blame]
/*
* Battery driver for a Greybus module.
*
* Copyright 2014 Google Inc.
*
* 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 {
struct power_supply bat;
// 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...
struct gb_connection *connection;
u8 version_major;
u8 version_minor;
};
#define to_gb_battery(x) container_of(x, struct gb_battery, bat)
/* 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
#define GB_BATTERY_TYPE_CAPACITY 0x05
#define GB_BATTERY_TYPE_TEMPERATURE 0x06
#define GB_BATTERY_TYPE_VOLTAGE 0x07
struct gb_battery_proto_version_response {
__u8 status;
__u8 major;
__u8 minor;
};
/* 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
struct gb_battery_technology_request {
__u8 status;
__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
struct gb_battery_status_request {
__u8 status;
__le16 battery_status;
};
struct gb_battery_max_voltage_request {
__u8 status;
__le32 max_voltage;
};
struct gb_battery_capacity_request {
__u8 status;
__le32 capacity;
};
struct gb_battery_temperature_request {
__u8 status;
__le32 temperature;
};
struct gb_battery_voltage_request {
__u8 status;
__le32 voltage;
};
static int battery_operation(struct gb_battery *gb, int type,
void *response, int response_size)
{
struct gb_connection *connection = gb->connection;
struct gb_operation *operation;
struct gb_battery_technology_request *fake_request;
u8 *local_response;
int ret;
local_response = kmalloc(response_size, GFP_KERNEL);
if (!local_response)
return -ENOMEM;
operation = gb_operation_create(connection, type, 0, response_size);
if (!operation) {
kfree(local_response);
return -ENOMEM;
}
/* Synchronous operation--no callback */
ret = gb_operation_request_send(operation, NULL);
if (ret) {
pr_err("version operation failed (%d)\n", ret);
goto out;
}
/*
* We only want to look at the status, and all requests have the same
* layout for where the status is, so cast this to a random request so
* we can see the status easier.
*/
fake_request = (struct gb_battery_technology_request *)local_response;
if (fake_request->status) {
gb_connection_err(connection, "version response %hhu",
fake_request->status);
ret = -EIO;
} else {
/* Good request, so copy to the caller's buffer */
memcpy(response, local_response, response_size);
}
out:
gb_operation_destroy(operation);
kfree(local_response);
return ret;
}
/*
* This request only uses the connection field, and if successful,
* fills in the major and minor protocol version of the target.
*/
static int get_version(struct gb_battery *gb)
{
struct gb_battery_proto_version_response version_request;
int retval;
retval = battery_operation(gb, GB_BATTERY_TYPE_PROTOCOL_VERSION,
&version_request, sizeof(version_request));
if (retval)
return retval;
if (version_request.major > GB_BATTERY_VERSION_MAJOR) {
pr_err("unsupported major version (%hhu > %hhu)\n",
version_request.major, GB_BATTERY_VERSION_MAJOR);
return -ENOTSUPP;
}
gb->version_major = version_request.major;
gb->version_minor = version_request.minor;
return 0;
}
static int get_tech(struct gb_battery *gb)
{
struct gb_battery_technology_request tech_request;
u32 technology;
int retval;
retval = battery_operation(gb, GB_BATTERY_TYPE_TECHNOLOGY,
&tech_request, sizeof(tech_request));
if (retval)
return retval;
/*
* Map greybus values to power_supply values. Hopefully these are
* "identical" which should allow gcc to optomize the code away to
* nothing.
*/
technology = le32_to_cpu(tech_request.technology);
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;
}
return technology;
}
static int get_status(struct gb_battery *gb)
{
struct gb_battery_status_request status_request;
u16 battery_status;
int retval;
retval = battery_operation(gb, GB_BATTERY_TYPE_STATUS,
&status_request, sizeof(status_request));
if (retval)
return retval;
/*
* Map greybus values to power_supply values. Hopefully these are
* "identical" which should allow gcc to optomize the code away to
* nothing.
*/
battery_status = le16_to_cpu(status_request.battery_status);
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;
}
return battery_status;
}
static int get_max_voltage(struct gb_battery *gb)
{
struct gb_battery_max_voltage_request volt_request;
u32 max_voltage;
int retval;
retval = battery_operation(gb, GB_BATTERY_TYPE_MAX_VOLTAGE,
&volt_request, sizeof(volt_request));
if (retval)
return retval;
max_voltage = le32_to_cpu(volt_request.max_voltage);
return max_voltage;
}
static int get_capacity(struct gb_battery *gb)
{
struct gb_battery_capacity_request capacity_request;
u32 capacity;
int retval;
retval = battery_operation(gb, GB_BATTERY_TYPE_CAPACITY,
&capacity_request, sizeof(capacity_request));
if (retval)
return retval;
capacity = le32_to_cpu(capacity_request.capacity);
return capacity;
}
static int get_temp(struct gb_battery *gb)
{
struct gb_battery_temperature_request temp_request;
u32 temperature;
int retval;
retval = battery_operation(gb, GB_BATTERY_TYPE_TEMPERATURE,
&temp_request, sizeof(temp_request));
if (retval)
return retval;
temperature = le32_to_cpu(temp_request.temperature);
return temperature;
}
static int get_voltage(struct gb_battery *gb)
{
struct gb_battery_voltage_request voltage_request;
u32 voltage;
int retval;
retval = battery_operation(gb, GB_BATTERY_TYPE_VOLTAGE,
&voltage_request, sizeof(voltage_request));
if (retval)
return retval;
voltage = le32_to_cpu(voltage_request.voltage);
return voltage;
}
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:
val->intval = get_tech(gb);
break;
case POWER_SUPPLY_PROP_STATUS:
val->intval = get_status(gb);
break;
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
val->intval = get_max_voltage(gb);
break;
case POWER_SUPPLY_PROP_CAPACITY:
val->intval = get_capacity(gb);
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,
};
static int gb_battery_connection_init(struct gb_connection *connection)
{
struct gb_battery *gb;
struct power_supply *b;
int retval;
gb = kzalloc(sizeof(*gb), GFP_KERNEL);
if (!gb)
return -ENOMEM;
gb->connection = connection;
connection->private = gb;
/* Check the version */
retval = get_version(gb);
if (retval) {
kfree(gb);
return retval;
}
b = &gb->bat;
// FIXME - get a better (i.e. unique) name
// FIXME - anything else needs to be set?
b->name = "gb_battery";
b->type = POWER_SUPPLY_TYPE_BATTERY,
b->properties = battery_props,
b->num_properties = ARRAY_SIZE(battery_props),
b->get_property = get_property,
retval = power_supply_register(&connection->interface->gmod->dev, b);
if (retval) {
kfree(gb);
return retval;
}
return 0;
}
static void gb_battery_connection_exit(struct gb_connection *connection)
{
struct gb_battery *gb = connection->private;
power_supply_unregister(&gb->bat);
kfree(gb);
}
static struct gb_protocol battery_protocol = {
.id = GREYBUS_PROTOCOL_BATTERY,
.major = 0,
.minor = 1,
.connection_init = gb_battery_connection_init,
.connection_exit = gb_battery_connection_exit,
.request_recv = NULL, /* no incoming requests */
};
bool gb_battery_protocol_init(void)
{
return gb_protocol_register(&battery_protocol);
}
void gb_battery_protocol_exit(void)
{
gb_protocol_deregister(&battery_protocol);
}