Re: [PATCH] [RFC]power: battery: Generic battery driver using IIO
From: Sannu K
Date: Sun Sep 02 2012 - 12:14:52 EST
On Sun, Sep 2, 2012 at 9:09 PM, anish kumar <anish198519851985@xxxxxxxxx> wrote:
> From: anish kumar <anish198519851985@xxxxxxxxx>
>
> This patch is to use IIO to write a generic batttery driver.
> There are some inherent assumptions here:
> 1.User is having both main battery and backup battery.
> 2.Both batteries use same channel to get the information.
>
> Signed-off-by: anish kumar <anish198519851985@xxxxxxxxx>
> ---
> drivers/power/Kconfig | 8 +
> drivers/power/Makefile | 1 +
> drivers/power/generic-battery.c | 374 +++++++++++++++++++++++++++++++++
> include/linux/power/generic-battery.h | 26 +++
> 4 files changed, 409 insertions(+), 0 deletions(-)
> create mode 100644 drivers/power/generic-battery.c
> create mode 100644 include/linux/power/generic-battery.h
>
> diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
> index fcc1bb0..546e86b 100644
> --- a/drivers/power/Kconfig
> +++ b/drivers/power/Kconfig
> @@ -309,6 +309,14 @@ config AB8500_BATTERY_THERM_ON_BATCTRL
> help
> Say Y to enable battery temperature measurements using
> thermistor connected on BATCTRL ADC.
> +
> +config GENERIC_BATTERY
> + tristate "Generic battery support using IIO"
> + depends on IIO
> + help
> + Say Y here to enable support for the generic battery driver
> + which uses IIO framework to read adc for it's main and backup
> + battery.
> endif # POWER_SUPPLY
>
> source "drivers/power/avs/Kconfig"
> diff --git a/drivers/power/Makefile b/drivers/power/Makefile
> index ee58afb..8284f9c 100644
> --- a/drivers/power/Makefile
> +++ b/drivers/power/Makefile
> @@ -45,3 +45,4 @@ obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o
> obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o
> obj-$(CONFIG_POWER_AVS) += avs/
> obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o
> +obj-$(CONFIG_GENERIC_BATTERY) += generic-battery.o
> diff --git a/drivers/power/generic-battery.c b/drivers/power/generic-battery.c
> new file mode 100644
> index 0000000..33170b7
> --- /dev/null
> +++ b/drivers/power/generic-battery.c
> @@ -0,0 +1,374 @@
> +/*
> + * Generice battery driver code using IIO
> + * Copyright (C) 2012, Anish Kumar <anish198519851985@xxxxxxxxx>
> + * based on jz4740-battery.c
> + * based on s3c_adc_battery.c
> + *
> + * This file is subject to the terms and conditions of the GNU General Public
> + * License. See the file COPYING in the main directory of this archive for
> + * more details.
> + *
> + */
> +
> +#include <linux/interrupt.h>
> +#include <linux/platform_device.h>
> +#include <linux/power_supply.h>
> +#include <linux/gpio.h>
> +#include <linux/err.h>
> +#include <linux/timer.h>
> +#include <linux/jiffies.h>
> +#include <linux/errno.h>
> +#include <linux/init.h>
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include <linux/iio/consumer.h>
> +#include <linux/iio/types.h>
> +
> +#include <linux/power/generic-battery.h>
> +
> +#define BAT_POLL_INTERVAL 10000 /* ms */
> +#define JITTER_DELAY 500 /* ms */
> +
> +enum BAT {
> + MAIN_BAT = 0,
> + BACKUP_BAT,
> + BAT_MAX,
> +};
> +
> +struct generic_adc_bat {
> + struct power_supply psy;
> + struct iio_channel *channels;
> + int channel_index;
> +};
> +
> +struct generic_bat {
> + struct generic_adc_bat bat[BAT_MAX];
> + struct generic_platform_data pdata;
> + int volt_value;
> + int cur_value;
> + unsigned int timestamp;
> + int level;
> + int status;
> + int cable_plugged:1;
> +};
> +
> +static struct generic_bat generic_bat = {
> + .bat[MAIN_BAT] = {
> + .psy.name = "main-bat",
> + },
> + .bat[BACKUP_BAT] = {
> + .psy.name = "backup-bat",
> + },
> +};
> +
> +static struct delayed_work bat_work;
> +
> +static void generic_adc_bat_ext_power_changed(struct power_supply *psy)
> +{
> + schedule_delayed_work(&bat_work,
> + msecs_to_jiffies(JITTER_DELAY));
> +}
> +
> +static enum power_supply_property generic_adc_main_bat_props[] = {
> + POWER_SUPPLY_PROP_STATUS,
> + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
> + POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN,
> + POWER_SUPPLY_PROP_CHARGE_NOW,
> + POWER_SUPPLY_PROP_VOLTAGE_NOW,
> + POWER_SUPPLY_PROP_CURRENT_NOW,
> + POWER_SUPPLY_PROP_TECHNOLOGY,
> + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
> + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
> + POWER_SUPPLY_PROP_MODEL_NAME,
> +};
> +
> +static int charge_finished(struct generic_bat *bat)
> +{
> + return bat->pdata.gpio_inverted ?
> + !gpio_get_value(bat->pdata.gpio_charge_finished) :
> + gpio_get_value(bat->pdata.gpio_charge_finished);
> +}
> +
> +static int generic_adc_bat_get_property(struct power_supply *psy,
> + enum power_supply_property psp,
> + union power_supply_propval *val)
> +{
> + struct generic_adc_bat *adc_bat;
> + struct generic_bat *bat;
> + int ret, scaleint, scalepart, iio_val;
> + long result = 0;
> +
> + if (!strcmp(psy->name, "main-battery")) {
> + adc_bat = container_of(psy,
> + struct generic_adc_bat, psy);
> + bat = container_of(adc_bat,
> + struct generic_bat, bat[MAIN_BAT]);
> + } else if (!strcmp(psy->name, "backup-battery")) {
> + adc_bat = container_of(psy, struct generic_adc_bat, psy);
> + bat = container_of(adc_bat,
> + struct generic_bat, bat[BACKUP_BAT]);
> + } else {
> + /* Ideally this should never happen */
> + WARN_ON(1);
> + return -EINVAL;
> + }
> +
> + if (!bat) {
> + dev_err(psy->dev, "no battery infos ?!\n");
> + return -EINVAL;
> + }
> + ret = iio_read_channel_raw(&adc_bat->channels[adc_bat->channel_index],
> + &iio_val);
> + ret = iio_read_channel_scale(&adc_bat->channels[adc_bat->channel_index],
> + &scaleint, &scalepart);
> + if (ret < 0)
> + return ret;
> +
> + switch (ret) {
> + case IIO_VAL_INT:
> + result = iio_val * scaleint;
> + break;
> + case IIO_VAL_INT_PLUS_MICRO:
> + result = (s64)iio_val * (s64)scaleint +
> + div_s64((s64)iio_val * (s64)scalepart, 1000000LL);
> + break;
> + case IIO_VAL_INT_PLUS_NANO:
> + result = (s64)iio_val * (s64)scaleint +
> + div_s64((s64)iio_val * (s64)scalepart, 1000000000LL);
> + break;
> + }
> +
> + switch (psp) {
> + case POWER_SUPPLY_PROP_STATUS:
> + if (bat->pdata.gpio_charge_finished < 0)
> + val->intval = bat->level == 100000 ?
> + POWER_SUPPLY_STATUS_FULL : bat->status;
> + else
> + val->intval = bat->status;
> + return 0;
> + case POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN:
> + val->intval = 0;
> + return 0;
> + case POWER_SUPPLY_PROP_CHARGE_NOW:
> + val->intval = bat->pdata.cal_charge(result);
> + return 0;
> + case POWER_SUPPLY_PROP_VOLTAGE_NOW:
> + val->intval = result;
> + return 0;
> + case POWER_SUPPLY_PROP_CURRENT_NOW:
> + val->intval = result;
> + return 0;
> + case POWER_SUPPLY_PROP_TECHNOLOGY:
> + val->intval = bat->pdata.battery_info.technology;
> + break;
> + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
> + val->intval = bat->pdata.battery_info.voltage_min_design;
> + break;
> + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
> + val->intval = bat->pdata.battery_info.voltage_max_design;
> + break;
> + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
> + val->intval = bat->pdata.battery_info.charge_full_design;
> + break;
> + case POWER_SUPPLY_PROP_MODEL_NAME:
> + val->strval = bat->pdata.battery_info.name;
> + break;
> + default:
> + return -EINVAL;
> + }
> + return ret;
> +}
> +
> +static void generic_adc_bat_work(struct work_struct *work)
> +{
> + struct generic_bat *bat = &generic_bat;
> + int is_charged;
> + int is_plugged;
> + static int was_plugged;
> +
> + /* backup battery doesn't have current monitoring capability anyway */
> + is_plugged = power_supply_am_i_supplied(&bat->bat[MAIN_BAT].psy);
> + bat->cable_plugged = is_plugged;
> + if (is_plugged != was_plugged) {
> + was_plugged = is_plugged;
> + if (is_plugged)
> + bat->status = POWER_SUPPLY_STATUS_CHARGING;
> + else
> + bat->status = POWER_SUPPLY_STATUS_DISCHARGING;
> + } else {
> + if ((bat->pdata.gpio_charge_finished >= 0) && is_plugged) {
> + is_charged = charge_finished(&generic_bat);
> + if (is_charged)
> + bat->status = POWER_SUPPLY_STATUS_FULL;
> + else
> + bat->status = POWER_SUPPLY_STATUS_CHARGING;
> + }
> + }
> +
> + power_supply_changed(&bat->bat[MAIN_BAT].psy);
> +}
> +
> +static irqreturn_t generic_adc_bat_charged(int irq, void *dev_id)
> +{
> + schedule_delayed_work(&bat_work,
> + msecs_to_jiffies(JITTER_DELAY));
> + return IRQ_HANDLED;
> +}
> +
> +static int __devinit generic_adc_bat_probe(struct platform_device *pdev)
> +{
> + struct generic_platform_data *pdata = pdev->dev.platform_data;
> + struct iio_channel *channels;
> + int num_channels = 0;
> + int ret, i, j, k = 0;
> + enum iio_chan_type type;
> +
> + for (i = 0; i < ARRAY_SIZE(generic_bat.bat); i++) {
> + generic_bat.bat[i].psy.type = POWER_SUPPLY_TYPE_BATTERY;
> + generic_bat.bat[i].psy.properties =
> + generic_adc_main_bat_props;
> + generic_bat.bat[i].psy.num_properties =
> + ARRAY_SIZE(generic_adc_main_bat_props);
> + generic_bat.bat[i].psy.get_property =
> + generic_adc_bat_get_property;
> + generic_bat.bat[i].psy.external_power_changed =
> + generic_adc_bat_ext_power_changed;
> + }
> +
> + generic_bat.volt_value = -1;
> + generic_bat.cur_value = -1;
> + generic_bat.cable_plugged = 0;
> + generic_bat.status = POWER_SUPPLY_STATUS_DISCHARGING;
> +
> + memcpy(&generic_bat.pdata, pdata, sizeof(struct generic_platform_data));
> +
> + for (i = 0; i < ARRAY_SIZE(generic_bat.bat); i++) {
> + ret = power_supply_register(&pdev->dev,
> + &generic_bat.bat[i].psy);
> + if (ret)
> + goto err_reg_main;
> + }
> +
> + channels = iio_channel_get_all(dev_name(&pdev->dev));
> + if (IS_ERR(channels)) {
> + ret = PTR_ERR(channels);
> + goto err_reg_backup;
> + }
> +
> + while (channels[num_channels].indio_dev)
> + num_channels++;
> +
> + for (i = 0; i < ARRAY_SIZE(generic_bat.bat); i++)
> + generic_bat.bat[i].channels = kzalloc(sizeof(struct iio_channel)
> + * BAT_MAX, GFP_KERNEL);
> +
> + /* assuming main battery and backup battery is using the same channel */
> + for (i = 0; i < num_channels; i++) {
> + ret = iio_get_channel_type(&channels[i], &type);
> + if (ret < 0)
> + goto err_gpio;
> +
> + if (type == IIO_VOLTAGE || type == IIO_CURRENT) {
> + for (j = 0; j < BAT_MAX; j++) {
> + generic_bat.bat[j].channel_index = k;
> + generic_bat.bat[j].channels[k] = channels[i];
> + }
> + k++;
> + }
> + continue;
> + }
> +
> + INIT_DELAYED_WORK(&bat_work, generic_adc_bat_work);
> +
> + if (pdata->gpio_charge_finished >= 0) {
> + ret = gpio_request(pdata->gpio_charge_finished, "charged");
> + if (ret)
> + goto err_gpio;
> +
> + ret = request_irq(gpio_to_irq(pdata->gpio_charge_finished),
> + generic_adc_bat_charged,
> + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
> + "battery charged", NULL);
> + if (ret)
> + goto err_gpio;
> + }
> +
> + dev_info(&pdev->dev, "successfully loaded\n");
> +
> + /* Schedule timer to check current status */
> + schedule_delayed_work(&bat_work,
> + msecs_to_jiffies(JITTER_DELAY));
> +
> + iio_channel_release_all(channels);
> +
> + return 0;
> +
> +err_gpio:
> + iio_channel_release_all(channels);
> +err_reg_backup:
> + for (i = 0; i < ARRAY_SIZE(generic_bat.bat); i++)
> + power_supply_unregister(&generic_bat.bat[i].psy);
> +err_reg_main:
> + return ret;
> +}
> +
> +static int generic_adc_bat_remove(struct platform_device *pdev)
> +{
> + int i;
> + struct generic_platform_data *pdata = pdev->dev.platform_data;
> +
> + for (i = 0; i < ARRAY_SIZE(generic_bat.bat); i++)
> + power_supply_unregister(&generic_bat.bat[i].psy);
> +
> + if (pdata->gpio_charge_finished >= 0) {
> + free_irq(gpio_to_irq(pdata->gpio_charge_finished), NULL);
> + gpio_free(pdata->gpio_charge_finished);
> + }
> +
> + cancel_delayed_work(&bat_work);
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int generic_adc_bat_suspend(struct platform_device *pdev,
> + pm_message_t state)
> +{
> + struct generic_platform_data *pdata = pdev->dev.platform_data;
> + struct generic_bat *bat = container_of(pdata,
> + struct generic_bat, pdata);
> +
> + cancel_delayed_work_sync(&bat_work);
> + bat->status = POWER_SUPPLY_STATUS_UNKNOWN;
> +
> + return 0;
> +}
> +
> +static int generic_adc_bat_resume(struct platform_device *pdev)
> +{
> + /* Schedule timer to check current status */
> + schedule_delayed_work(&bat_work,
> + msecs_to_jiffies(JITTER_DELAY));
> +
> + return 0;
> +}
> +#else
> +#define generic_adc_bat_suspend NULL
> +#define generic_adc_bat_resume NULL
> +#endif
> +
> +static struct platform_driver generic_adc_bat_driver = {
> + .driver = {
> + .name = "generic-adc-battery",
> + },
> + .probe = generic_adc_bat_probe,
> + .remove = generic_adc_bat_remove,
> + .suspend = generic_adc_bat_suspend,
> + .resume = generic_adc_bat_resume,
> +};
> +
> +module_platform_driver(generic_adc_bat_driver);
> +
> +MODULE_AUTHOR("anish kumar <anish198519851985@xxxxxxxxx>");
> +MODULE_DESCRIPTION("generic battery driver using IIO");
> +MODULE_LICENSE("GPL");
> diff --git a/include/linux/power/generic-battery.h b/include/linux/power/generic-battery.h
> new file mode 100644
> index 0000000..7a43aa0
> --- /dev/null
> +++ b/include/linux/power/generic-battery.h
> @@ -0,0 +1,26 @@
> +/*
> + * 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.
> + */
> +
> +#ifndef GENERIC_BATTERY_H
> +#define GENERIC_BATTERY_H
> +
> +#include <linux/power_supply.h>
> +
> +/**
> + * struct generic_platform_data - platform data for generic battery
> + * @battery_info: Information about the battery
> + * @cal_charge: platform callback to calculate charge
> + * @gpio_charge_finished: GPIO number used for interrupts
> + * @gpio_inverted: Is the gpio inverted?
> + */
> +struct generic_platform_data {
> + struct power_supply_info battery_info;
> + int (*cal_charge)(long);
> + int gpio_charge_finished;
> + int gpio_inverted;
> +};
> +
> +#endif /* GENERIC_BATTERY_H */
> --
> 1.7.1
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to majordomo@xxxxxxxxxxxxxxx
> More majordomo info at http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at http://www.tux.org/lkml/
Just Curious what is the use of this module? Is there any user space
program uses this? When ACPI drivers for battery is available how
useful will this be?
Thanks,
Sannu K
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/