Commit e3e8d1c9 authored by Marek Vasut's avatar Marek Vasut Committed by Anton Vorontsov

Driver for Zipit Z2 battery chip

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: default avatarMarek Vasut <marek.vasut@gmail.com>
Signed-off-by: default avatarAnton Vorontsov <cbouatmailru@gmail.com>
parent a009d29e
......@@ -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
......
......@@ -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
/*
* 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");
#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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment