summaryrefslogblamecommitdiffstats
path: root/drivers/staging/greybus/battery-gb.c
blob: 28c0d0b9aefeaa4664bb328d3cd43f8ccb013a2b (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16















                                       



                                                                               



                                         


                                                                


































































                                                                 









































                                                                              
                              




























                                                                              

                                          









                                                                        


                                                                          

                                                          























                                                             
                          

 

                                            









                                                                            


                                                                          

                                                                    

















                                                                  
                              

 

                                                 










                                                                        

 

                                              










                                                                                



                                          










                                                                        



                                             










                                                                              

 







                                                        
                                           






                                                  
                                                  




















                                               









                                                                           
                                                                       








                                              
                                    
                                 
 






                                 








                                                            
                                                                             



                              



                 
                                                                        






                                                    



                                                              
/*
 * 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);
}

struct gb_connection_handler gb_battery_connection_handler = {
	.connection_init	= gb_battery_connection_init,
	.connection_exit	= gb_battery_connection_exit,
};