Driver for Zipit Z2 battery chip
authorMarek Vasut <marek.vasut@gmail.com>
Sun, 28 Feb 2010 11:47:49 +0000 (12:47 +0100)
committerAnton Vorontsov <cbouatmailru@gmail.com>
Tue, 6 Apr 2010 16:35:58 +0000 (20:35 +0400)
This patch adds driver for Zipit Z2 battery chip called AER915. No
details are known about the chip. The chip is available through I2C bus
at address 0x55 and it's register 0x02 contains battery voltage.

Signed-off-by: Marek Vasut <marek.vasut@gmail.com>
Signed-off-by: Anton Vorontsov <cbouatmailru@gmail.com>
drivers/power/Kconfig
drivers/power/Makefile
drivers/power/z2_battery.c [new file with mode: 0644]
include/linux/z2_battery.h [new file with mode: 0644]

index faaa9b4d0d0711ea2fc2420f37cbe62959891ba9..22f2fa9121270d7d817008abafa9b50fd1f2bba1 100644 (file)
@@ -125,6 +125,12 @@ config BATTERY_MAX17040
          in handheld and portable equipment. The MAX17040 is configured
          to operate with a single lithium cell
 
+config BATTERY_Z2
+       tristate "Z2 battery driver"
+       depends on I2C && MACH_ZIPIT2
+       help
+         Say Y to include support for the battery on the Zipit Z2.
+
 config CHARGER_PCF50633
        tristate "NXP PCF50633 MBC"
        depends on MFD_PCF50633
index a2ba7c85c97a55623f05e61b9473fcb9c714c689..a82f292e5c949b82ba3a1bf3c6356202a07805f4 100644 (file)
@@ -31,4 +31,5 @@ obj-$(CONFIG_BATTERY_WM97XX)  += wm97xx_battery.o
 obj-$(CONFIG_BATTERY_BQ27x00)  += bq27x00_battery.o
 obj-$(CONFIG_BATTERY_DA9030)   += da9030_battery.o
 obj-$(CONFIG_BATTERY_MAX17040) += max17040_battery.o
+obj-$(CONFIG_BATTERY_Z2)       += z2_battery.o
 obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o
diff --git a/drivers/power/z2_battery.c b/drivers/power/z2_battery.c
new file mode 100644 (file)
index 0000000..9cca465
--- /dev/null
@@ -0,0 +1,328 @@
+/*
+ * Battery measurement code for Zipit Z2
+ *
+ * Copyright (C) 2009 Peter Edwards <sweetlilmre@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/i2c.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <asm/irq.h>
+#include <asm/mach/irq.h>
+#include <linux/z2_battery.h>
+
+#define        Z2_DEFAULT_NAME "Z2"
+
+struct z2_charger {
+       struct z2_battery_info  *info;
+       int                     bat_status;
+       struct i2c_client       *client;
+       struct power_supply     batt_ps;
+       struct mutex            work_lock;
+       struct work_struct      bat_work;
+};
+
+static unsigned long z2_read_bat(struct z2_charger *charger)
+{
+       int data;
+       data = i2c_smbus_read_byte_data(charger->client,
+                                       charger->info->batt_I2C_reg);
+       if (data < 0)
+               return 0;
+
+       return data * charger->info->batt_mult / charger->info->batt_div;
+}
+
+static int z2_batt_get_property(struct power_supply *batt_ps,
+                           enum power_supply_property psp,
+                           union power_supply_propval *val)
+{
+       struct z2_charger *charger = container_of(batt_ps, struct z2_charger,
+                                               batt_ps);
+       struct z2_battery_info *info = charger->info;
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_STATUS:
+               val->intval = charger->bat_status;
+               break;
+       case POWER_SUPPLY_PROP_TECHNOLOGY:
+               val->intval = info->batt_tech;
+               break;
+       case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+               if (info->batt_I2C_reg >= 0)
+                       val->intval = z2_read_bat(charger);
+               else
+                       return -EINVAL;
+               break;
+       case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+               if (info->max_voltage >= 0)
+                       val->intval = info->max_voltage;
+               else
+                       return -EINVAL;
+               break;
+       case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+               if (info->min_voltage >= 0)
+                       val->intval = info->min_voltage;
+               else
+                       return -EINVAL;
+               break;
+       case POWER_SUPPLY_PROP_PRESENT:
+               val->intval = 1;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static void z2_batt_ext_power_changed(struct power_supply *batt_ps)
+{
+       struct z2_charger *charger = container_of(batt_ps, struct z2_charger,
+                                               batt_ps);
+       schedule_work(&charger->bat_work);
+}
+
+static void z2_batt_update(struct z2_charger *charger)
+{
+       int old_status = charger->bat_status;
+       struct z2_battery_info *info;
+
+       info = charger->info;
+
+       mutex_lock(&charger->work_lock);
+
+       charger->bat_status = (info->charge_gpio >= 0) ?
+               (gpio_get_value(info->charge_gpio) ?
+               POWER_SUPPLY_STATUS_CHARGING :
+               POWER_SUPPLY_STATUS_DISCHARGING) :
+               POWER_SUPPLY_STATUS_UNKNOWN;
+
+       if (old_status != charger->bat_status) {
+               pr_debug("%s: %i -> %i\n", charger->batt_ps.name, old_status,
+                       charger->bat_status);
+               power_supply_changed(&charger->batt_ps);
+       }
+
+       mutex_unlock(&charger->work_lock);
+}
+
+static void z2_batt_work(struct work_struct *work)
+{
+       struct z2_charger *charger;
+       charger = container_of(work, struct z2_charger, bat_work);
+       z2_batt_update(charger);
+}
+
+static irqreturn_t z2_charge_switch_irq(int irq, void *devid)
+{
+       struct z2_charger *charger = devid;
+       schedule_work(&charger->bat_work);
+       return IRQ_HANDLED;
+}
+
+static int z2_batt_ps_init(struct z2_charger *charger, int props)
+{
+       int i = 0;
+       enum power_supply_property *prop;
+       struct z2_battery_info *info = charger->info;
+
+       if (info->batt_tech >= 0)
+               props++;        /* POWER_SUPPLY_PROP_TECHNOLOGY */
+       if (info->batt_I2C_reg >= 0)
+               props++;        /* POWER_SUPPLY_PROP_VOLTAGE_NOW */
+       if (info->max_voltage >= 0)
+               props++;        /* POWER_SUPPLY_PROP_VOLTAGE_MAX */
+       if (info->min_voltage >= 0)
+               props++;        /* POWER_SUPPLY_PROP_VOLTAGE_MIN */
+
+       prop = kzalloc(props * sizeof(*prop), GFP_KERNEL);
+       if (!prop)
+               return -ENOMEM;
+
+       prop[i++] = POWER_SUPPLY_PROP_PRESENT;
+       if (info->charge_gpio >= 0)
+               prop[i++] = POWER_SUPPLY_PROP_STATUS;
+       if (info->batt_tech >= 0)
+               prop[i++] = POWER_SUPPLY_PROP_TECHNOLOGY;
+       if (info->batt_I2C_reg >= 0)
+               prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_NOW;
+       if (info->max_voltage >= 0)
+               prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MAX;
+       if (info->min_voltage >= 0)
+               prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MIN;
+
+       if (!info->batt_name) {
+               dev_info(&charger->client->dev,
+                               "Please consider setting proper battery "
+                               "name in platform definition file, falling "
+                               "back to name \" Z2_DEFAULT_NAME \"\n");
+               charger->batt_ps.name = Z2_DEFAULT_NAME;
+       } else
+               charger->batt_ps.name = info->batt_name;
+
+       charger->batt_ps.properties             = prop;
+       charger->batt_ps.num_properties         = props;
+       charger->batt_ps.type                   = POWER_SUPPLY_TYPE_BATTERY;
+       charger->batt_ps.get_property           = z2_batt_get_property;
+       charger->batt_ps.external_power_changed = z2_batt_ext_power_changed;
+       charger->batt_ps.use_for_apm            = 1;
+
+       return 0;
+}
+
+static int __devinit z2_batt_probe(struct i2c_client *client,
+                               const struct i2c_device_id *id)
+{
+       int ret = 0;
+       int props = 1;  /* POWER_SUPPLY_PROP_PRESENT */
+       struct z2_charger *charger;
+       struct z2_battery_info *info = client->dev.platform_data;
+
+       if (info == NULL) {
+               dev_err(&client->dev,
+                       "Please set platform device platform_data"
+                       " to a valid z2_battery_info pointer!\n");
+               return -EINVAL;
+       }
+
+       charger = kzalloc(sizeof(*charger), GFP_KERNEL);
+       if (charger == NULL)
+               return -ENOMEM;
+
+       charger->bat_status = POWER_SUPPLY_STATUS_UNKNOWN;
+       charger->info = info;
+       charger->client = client;
+       i2c_set_clientdata(client, charger);
+
+       mutex_init(&charger->work_lock);
+
+       if (info->charge_gpio >= 0 && gpio_is_valid(info->charge_gpio)) {
+               ret = gpio_request(info->charge_gpio, "BATT CHRG");
+               if (ret)
+                       goto err;
+
+               ret = gpio_direction_input(info->charge_gpio);
+               if (ret)
+                       goto err2;
+
+               set_irq_type(gpio_to_irq(info->charge_gpio),
+                               IRQ_TYPE_EDGE_BOTH);
+               ret = request_irq(gpio_to_irq(info->charge_gpio),
+                               z2_charge_switch_irq, IRQF_DISABLED,
+                               "AC Detect", charger);
+               if (ret)
+                       goto err3;
+       }
+
+       ret = z2_batt_ps_init(charger, props);
+       if (ret)
+               goto err3;
+
+       INIT_WORK(&charger->bat_work, z2_batt_work);
+
+       ret = power_supply_register(&client->dev, &charger->batt_ps);
+       if (ret)
+               goto err4;
+
+       schedule_work(&charger->bat_work);
+
+       return 0;
+
+err4:
+       kfree(charger->batt_ps.properties);
+err3:
+       if (info->charge_gpio >= 0 && gpio_is_valid(info->charge_gpio))
+               free_irq(gpio_to_irq(info->charge_gpio), charger);
+err2:
+       if (info->charge_gpio >= 0 && gpio_is_valid(info->charge_gpio))
+               gpio_free(info->charge_gpio);
+err:
+       kfree(charger);
+       return ret;
+}
+
+static int __devexit z2_batt_remove(struct i2c_client *client)
+{
+       struct z2_charger *charger = i2c_get_clientdata(client);
+       struct z2_battery_info *info = charger->info;
+
+       flush_scheduled_work();
+       power_supply_unregister(&charger->batt_ps);
+
+       kfree(charger->batt_ps.properties);
+       if (info->charge_gpio >= 0 && gpio_is_valid(info->charge_gpio)) {
+               free_irq(gpio_to_irq(info->charge_gpio), charger);
+               gpio_free(info->charge_gpio);
+       }
+
+       kfree(charger);
+
+       return 0;
+}
+
+#ifdef CONFIG_PM
+static int z2_batt_suspend(struct i2c_client *client, pm_message_t state)
+{
+       flush_scheduled_work();
+       return 0;
+}
+
+static int z2_batt_resume(struct i2c_client *client)
+{
+       struct z2_charger *charger = i2c_get_clientdata(client);
+
+       schedule_work(&charger->bat_work);
+       return 0;
+}
+#else
+#define z2_batt_suspend NULL
+#define z2_batt_resume NULL
+#endif
+
+static const struct i2c_device_id z2_batt_id[] = {
+       { "aer915", 0 },
+       { }
+};
+
+static struct i2c_driver z2_batt_driver = {
+       .driver = {
+               .name   = "z2-battery",
+               .owner  = THIS_MODULE,
+       },
+       .probe          = z2_batt_probe,
+       .remove         = z2_batt_remove,
+       .suspend        = z2_batt_suspend,
+       .resume         = z2_batt_resume,
+       .id_table       = z2_batt_id,
+};
+
+static int __init z2_batt_init(void)
+{
+       return i2c_add_driver(&z2_batt_driver);
+}
+
+static void __exit z2_batt_exit(void)
+{
+       i2c_del_driver(&z2_batt_driver);
+}
+
+module_init(z2_batt_init);
+module_exit(z2_batt_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Peter Edwards <sweetlilmre@gmail.com>");
+MODULE_DESCRIPTION("Zipit Z2 battery driver");
diff --git a/include/linux/z2_battery.h b/include/linux/z2_battery.h
new file mode 100644 (file)
index 0000000..7b97504
--- /dev/null
@@ -0,0 +1,17 @@
+#ifndef _LINUX_Z2_BATTERY_H
+#define _LINUX_Z2_BATTERY_H
+
+struct z2_battery_info {
+       int      batt_I2C_bus;
+       int      batt_I2C_addr;
+       int      batt_I2C_reg;
+       int      charge_gpio;
+       int      min_voltage;
+       int      max_voltage;
+       int      batt_div;
+       int      batt_mult;
+       int      batt_tech;
+       char    *batt_name;
+};
+
+#endif