summaryrefslogblamecommitdiffstats
path: root/drivers/staging/greybus/power_supply.c
blob: e119775fb19bfb80526854d85623b010730ff566 (plain) (tree)
1
2
3
4
5



                                       
                             










                                 




                                                                        
                             
                                





                                                                



                                                                               
                                         
 
  
 

                                          
                                                            


                       

                                                                              
                                                                          



                              
                                                                          
                                                                          
                   
           
                                                           























                                                             
                          

 

                                            
                                                          


                           

                                                                          
                                                                              



                              
                                                                          
                                                                          
                   
           
                                                                     

















                                                                  
                              

 

                                                 
                                                             


                        

                                                                               
                                                                          


                              
                                                             
                           

 
                                                      
 
                                                              


                     

                                                                    

                                                               


                              
                                                           
                        



                                          
                                                             


                        

                                                                               
                                                                          


                              
                                                             
                           



                                             
                                                            


                    

                                                                           
                                                                                


                              
                                                        
                       

 







                                                        
                                           






                                                  
                                                  


                                        
                                                       













                                              
                                                   

 









                                                                           
                             


                                                              







                                                            
                                                                         





                                                              










                                                            
                                                                 
                                                         



                                        


      
                                                                       

                              





                                              
                                    
                                 
 
                                                   
                   
                          
 
                      

 
                                                                        


                                                    
                             
                                          


                                         


                  
                                              
                                            
                                                           

                                                           

                                                             
                                                                  

  
                                      
 
                         
/*
 * Battery driver for a Greybus module.
 *
 * Copyright 2014 Google Inc.
 * Copyright 2014 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 "greybus.h"

struct gb_battery {
	/*
	 * 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.
	 */
#ifdef DRIVER_OWNS_PSY_STRUCT
	struct power_supply bat;
#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
	// 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;

};

static int get_tech(struct gb_battery *gb)
{
	struct gb_battery_technology_response tech_response;
	u32 technology;
	int retval;

	retval = gb_operation_sync(gb->connection, GB_BATTERY_TYPE_TECHNOLOGY,
				   NULL, 0,
				   &tech_response, sizeof(tech_response));
	if (retval)
		return retval;

	/*
	 * 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_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_response status_response;
	u16 battery_status;
	int retval;

	retval = gb_operation_sync(gb->connection, GB_BATTERY_TYPE_STATUS,
				   NULL, 0,
				   &status_response, sizeof(status_response));
	if (retval)
		return retval;

	/*
	 * Map greybus values to power_supply values.  Hopefully these are
	 * "identical" which should allow gcc to optimize the code away to
	 * nothing.
	 */
	battery_status = le16_to_cpu(status_response.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_response volt_response;
	u32 max_voltage;
	int retval;

	retval = gb_operation_sync(gb->connection, GB_BATTERY_TYPE_MAX_VOLTAGE,
				   NULL, 0,
				   &volt_response, sizeof(volt_response));
	if (retval)
		return retval;

	max_voltage = le32_to_cpu(volt_response.max_voltage);
	return max_voltage;
}

static int get_percent_capacity(struct gb_battery *gb)
{
	struct gb_battery_capacity_response capacity_response;
	u32 capacity;
	int retval;

	retval = gb_operation_sync(gb->connection,
				   GB_BATTERY_TYPE_PERCENT_CAPACITY,
				   NULL, 0, &capacity_response,
				   sizeof(capacity_response));
	if (retval)
		return retval;

	capacity = le32_to_cpu(capacity_response.capacity);
	return capacity;
}

static int get_temp(struct gb_battery *gb)
{
	struct gb_battery_temperature_response temp_response;
	u32 temperature;
	int retval;

	retval = gb_operation_sync(gb->connection, GB_BATTERY_TYPE_TEMPERATURE,
				   NULL, 0,
				   &temp_response, sizeof(temp_response));
	if (retval)
		return retval;

	temperature = le32_to_cpu(temp_response.temperature);
	return temperature;
}

static int get_voltage(struct gb_battery *gb)
{
	struct gb_battery_voltage_response voltage_response;
	u32 voltage;
	int retval;

	retval = gb_operation_sync(gb->connection, GB_BATTERY_TYPE_VOLTAGE,
				   NULL, 0,
				   &voltage_response, sizeof(voltage_response));
	if (retval)
		return retval;

	voltage = le32_to_cpu(voltage_response.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_percent_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 (val->intval < 0) ? val->intval : 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,
};

#ifdef DRIVER_OWNS_PSY_STRUCT
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;

	return power_supply_register(&connection->bundle->dev, &gb->bat);
}
#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->dev,
					&gb->desc, &cfg);
	if (IS_ERR(gb->bat))
		return PTR_ERR(gb->bat);

	return 0;
}
#endif

static int gb_battery_connection_init(struct gb_connection *connection)
{
	struct gb_battery *gb;
	int retval;

	gb = kzalloc(sizeof(*gb), GFP_KERNEL);
	if (!gb)
		return -ENOMEM;

	gb->connection = connection;
	connection->private = gb;

	retval = init_and_register(connection, gb);
	if (retval)
		kfree(gb);

	return retval;
}

static void gb_battery_connection_exit(struct gb_connection *connection)
{
	struct gb_battery *gb = connection->private;

#ifdef DRIVER_OWNS_PSY_STRUCT
	power_supply_unregister(&gb->bat);
#else
	power_supply_unregister(gb->bat);
#endif
	kfree(gb);
}

static struct gb_protocol battery_protocol = {
	.name			= "battery",
	.id			= GREYBUS_PROTOCOL_BATTERY,
	.major			= GB_BATTERY_VERSION_MAJOR,
	.minor			= GB_BATTERY_VERSION_MINOR,
	.connection_init	= gb_battery_connection_init,
	.connection_exit	= gb_battery_connection_exit,
	.request_recv		= NULL,	/* no incoming requests */
};

gb_protocol_driver(&battery_protocol);

MODULE_LICENSE("GPL v2");