greybus: battery-gb: Add battery communication with the module
authorGreg Kroah-Hartman <greg@kroah.com>
Tue, 21 Oct 2014 06:31:24 +0000 (14:31 +0800)
committerGreg Kroah-Hartman <greg@kroah.com>
Tue, 21 Oct 2014 06:31:24 +0000 (14:31 +0800)
This adds support to talk to the battery to get the various requests
made to it, based on the battery protocol defined in the Greybus
Specification.

Signed-off-by: Greg Kroah-Hartman <greg@kroah.com>
drivers/staging/greybus/battery-gb.c

index 09c3290e1eaf1cd15dbda6fba231e37f17613b54..eca1d094870544420b9cec16d0758d63fb3cc261 100644 (file)
@@ -25,45 +25,245 @@ struct gb_battery {
 };
 #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 const struct greybus_module_id id_table[] = {
        { GREYBUS_DEVICE(0x42, 0x42) }, /* make shit up */
        { },    /* terminating NULL entry */
 };
 
+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);
+
+       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)
 {
-       // FIXME - guess!
-       return POWER_SUPPLY_TECHNOLOGY_NiMH;
+       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;
+
+       /*
+        * We have a one-to-one mapping of tech types to power_supply
+        * status, so just return that value.
+        */
+       technology = le32_to_cpu(tech_request.technology);
+       return technology;
 }
 
 static int get_status(struct gb_battery *gb)
 {
-       // FIXME!!!
-       return 0;
+       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;
+
+       /*
+        * We have a one-to-one mapping of battery status to power_supply
+        * status, so just return that value.
+        */
+       battery_status = le16_to_cpu(status_request.battery_status);
+       return battery_status;
 }
 
 static int get_max_voltage(struct gb_battery *gb)
 {
-       // FIXME!!!
-       return 4700000;
+       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)
 {
-       // FIXME!!!
-       return 0;
+       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)
 {
-       // FIXME!!!
-       return 0;
+       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)
 {
-       // FIXME!!!
-       return 0;
+       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,
@@ -126,6 +326,13 @@ int gb_battery_device_init(struct gb_connection *connection)
 
        gb->connection = connection;    // FIXME refcount!
 
+       /* 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?