diff options
Diffstat (limited to 'recipes/linux/linux-2.6.35/rx1950/0007-Add-s3c-adc-battery-driver.patch')
-rw-r--r-- | recipes/linux/linux-2.6.35/rx1950/0007-Add-s3c-adc-battery-driver.patch | 530 |
1 files changed, 530 insertions, 0 deletions
diff --git a/recipes/linux/linux-2.6.35/rx1950/0007-Add-s3c-adc-battery-driver.patch b/recipes/linux/linux-2.6.35/rx1950/0007-Add-s3c-adc-battery-driver.patch new file mode 100644 index 0000000000..f0b763afda --- /dev/null +++ b/recipes/linux/linux-2.6.35/rx1950/0007-Add-s3c-adc-battery-driver.patch @@ -0,0 +1,530 @@ +From 874bc0df0eeddae98517190375e9e5697826f1a9 Mon Sep 17 00:00:00 2001 +From: Vasily Khoruzhick <anarsoul@gmail.com> +Date: Wed, 16 Jun 2010 22:35:52 +0300 +Subject: [PATCH v5 3/4] Add s3c-adc-battery driver + +s3c-adc-battery is driver for monitoring +and charging battery on iPAQ H1930/H1940/RX1950. +It depends on s3c-adc driver to get battery voltage and current. + +Signed-off-by: Vasily Khoruzhick <anarsoul@gmail.com> +--- + drivers/power/Kconfig | 6 + + drivers/power/Makefile | 1 + + drivers/power/s3c_adc_battery.c | 433 +++++++++++++++++++++++++++++++++++++++ + include/linux/s3c_adc_battery.h | 36 ++++ + 4 files changed, 476 insertions(+), 0 deletions(-) + create mode 100644 drivers/power/s3c_adc_battery.c + create mode 100644 include/linux/s3c_adc_battery.h + +diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig +index 8e9ba17..645baa5 100644 +--- a/drivers/power/Kconfig ++++ b/drivers/power/Kconfig +@@ -136,6 +136,12 @@ config BATTERY_Z2 + help + Say Y to include support for the battery on the Zipit Z2. + ++config BATTERY_S3C_ADC ++ tristate "Battery driver for Samsung ADC based monitoring" ++ depends on S3C_ADC ++ help ++ Say Y here to enable support for iPAQ h1930/h1940/rx1950 battery ++ + config CHARGER_PCF50633 + tristate "NXP PCF50633 MBC" + depends on MFD_PCF50633 +diff --git a/drivers/power/Makefile b/drivers/power/Makefile +index 0005080..166bcbf 100644 +--- a/drivers/power/Makefile ++++ b/drivers/power/Makefile +@@ -33,4 +33,5 @@ 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_BATTERY_S3C_ADC) += s3c_adc_battery.o + obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o +diff --git a/drivers/power/s3c_adc_battery.c b/drivers/power/s3c_adc_battery.c +new file mode 100644 +index 0000000..d7a0771 +--- /dev/null ++++ b/drivers/power/s3c_adc_battery.c +@@ -0,0 +1,433 @@ ++/* ++ * iPAQ h1930/h1940/rx1950 battery controler driver ++ * Copyright (c) Vasily Khoruzhick ++ * Based on h1940_battery.c by Arnaud Patard ++ * ++ * 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/leds.h> ++#include <linux/gpio.h> ++#include <linux/err.h> ++#include <linux/timer.h> ++#include <linux/jiffies.h> ++#include <linux/s3c_adc_battery.h> ++#include <linux/errno.h> ++#include <linux/init.h> ++ ++#include <plat/adc.h> ++ ++#define BAT_POLL_INTERVAL 10000 /* ms */ ++#define JITTER_DELAY 500 /* ms */ ++ ++struct s3c_adc_bat { ++ struct power_supply psy; ++ struct s3c_adc_client *client; ++ struct s3c_adc_bat_pdata *pdata; ++ int volt_value; ++ int cur_value; ++ unsigned int timestamp; ++ int level; ++ int status; ++ int cable_plugged:1; ++}; ++ ++static struct delayed_work bat_work; ++ ++static void s3c_adc_bat_ext_power_changed(struct power_supply *psy) ++{ ++ schedule_delayed_work(&bat_work, ++ msecs_to_jiffies(JITTER_DELAY)); ++} ++ ++static enum power_supply_property s3c_adc_backup_bat_props[] = { ++ POWER_SUPPLY_PROP_VOLTAGE_NOW, ++ POWER_SUPPLY_PROP_VOLTAGE_MIN, ++ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, ++}; ++ ++static int s3c_adc_backup_bat_get_property(struct power_supply *psy, ++ enum power_supply_property psp, ++ union power_supply_propval *val) ++{ ++ struct s3c_adc_bat *bat = container_of(psy, struct s3c_adc_bat, psy); ++ ++ if (!bat) { ++ dev_err(psy->dev, "%s: no battery infos ?!\n", __func__); ++ return -EINVAL; ++ } ++ ++ if (bat->volt_value < 0 || ++ jiffies_to_msecs(jiffies - bat->timestamp) > ++ BAT_POLL_INTERVAL) { ++ bat->volt_value = s3c_adc_read(bat->client, ++ bat->pdata->backup_volt_channel); ++ bat->volt_value *= bat->pdata->backup_volt_mult; ++ bat->timestamp = jiffies; ++ } ++ ++ switch (psp) { ++ case POWER_SUPPLY_PROP_VOLTAGE_NOW: ++ val->intval = bat->volt_value; ++ return 0; ++ case POWER_SUPPLY_PROP_VOLTAGE_MIN: ++ val->intval = bat->pdata->backup_volt_min; ++ return 0; ++ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: ++ val->intval = bat->pdata->backup_volt_max; ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++static struct s3c_adc_bat backup_bat = { ++ .psy = { ++ .name = "backup-battery", ++ .type = POWER_SUPPLY_TYPE_BATTERY, ++ .properties = s3c_adc_backup_bat_props, ++ .num_properties = ARRAY_SIZE(s3c_adc_backup_bat_props), ++ .get_property = s3c_adc_backup_bat_get_property, ++ .use_for_apm = 1, ++ }, ++}; ++ ++static enum power_supply_property s3c_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, ++}; ++ ++static int calc_full_volt(int volt_val, int cur_val, int impedance) ++{ ++ return volt_val + cur_val * impedance / 1000; ++} ++ ++static int s3c_adc_bat_get_property(struct power_supply *psy, ++ enum power_supply_property psp, ++ union power_supply_propval *val) ++{ ++ struct s3c_adc_bat *bat = container_of(psy, struct s3c_adc_bat, psy); ++ ++ int new_level; ++ int full_volt; ++ const struct s3c_adc_bat_thresh *lut = bat->pdata->lut_noac; ++ unsigned int lut_size = bat->pdata->lut_noac_cnt; ++ ++ if (!bat) { ++ dev_err(psy->dev, "no battery infos ?!\n"); ++ return -EINVAL; ++ } ++ ++ if (bat->volt_value < 0 || bat->cur_value < 0 || ++ jiffies_to_msecs(jiffies - bat->timestamp) > ++ BAT_POLL_INTERVAL) { ++ bat->volt_value = s3c_adc_read(bat->client, ++ bat->pdata->volt_channel) * bat->pdata->volt_mult; ++ bat->cur_value = s3c_adc_read(bat->client, ++ bat->pdata->current_channel) * bat->pdata->current_mult; ++ bat->timestamp = jiffies; ++ } ++ ++ if (bat->cable_plugged && ++ ((bat->pdata->gpio_charge_finished < 0) || ++ !gpio_get_value(bat->pdata->gpio_charge_finished))) { ++ lut = bat->pdata->lut_acin; ++ lut_size = bat->pdata->lut_acin_cnt; ++ } ++ ++ new_level = 100000; ++ full_volt = calc_full_volt((bat->volt_value / 1000), ++ (bat->cur_value / 1000), bat->pdata->internal_impedance); ++ ++ if (full_volt < calc_full_volt(lut->volt, lut->cur, ++ bat->pdata->internal_impedance)) { ++ lut_size--; ++ while (lut_size--) { ++ int lut_volt1; ++ int lut_volt2; ++ ++ lut_volt1 = calc_full_volt(lut[0].volt, lut[0].cur, ++ bat->pdata->internal_impedance); ++ lut_volt2 = calc_full_volt(lut[1].volt, lut[1].cur, ++ bat->pdata->internal_impedance); ++ if (full_volt < lut_volt1 && full_volt >= lut_volt2) { ++ new_level = (lut[1].level + ++ (lut[0].level - lut[1].level) * ++ (full_volt - lut_volt2) / ++ (lut_volt1 - lut_volt2)) * 1000; ++ break; ++ } ++ new_level = lut[1].level * 1000; ++ lut++; ++ } ++ } ++ ++ bat->level = new_level; ++ ++ 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_FULL_DESIGN: ++ val->intval = 100000; ++ return 0; ++ case POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN: ++ val->intval = 0; ++ return 0; ++ case POWER_SUPPLY_PROP_CHARGE_NOW: ++ val->intval = bat->level; ++ return 0; ++ case POWER_SUPPLY_PROP_VOLTAGE_NOW: ++ val->intval = bat->volt_value; ++ return 0; ++ case POWER_SUPPLY_PROP_CURRENT_NOW: ++ val->intval = bat->cur_value; ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++static struct s3c_adc_bat main_bat = { ++ .psy = { ++ .name = "main-battery", ++ .type = POWER_SUPPLY_TYPE_BATTERY, ++ .properties = s3c_adc_main_bat_props, ++ .num_properties = ARRAY_SIZE(s3c_adc_main_bat_props), ++ .get_property = s3c_adc_bat_get_property, ++ .external_power_changed = s3c_adc_bat_ext_power_changed, ++ .use_for_apm = 1, ++ }, ++}; ++ ++static void s3c_adc_bat_work(struct work_struct *work) ++{ ++ struct s3c_adc_bat *bat = &main_bat; ++ int is_charged; ++ int is_plugged; ++ static int was_plugged; ++ ++ is_plugged = power_supply_am_i_supplied(&bat->psy); ++ bat->cable_plugged = is_plugged; ++ if (is_plugged != was_plugged) { ++ was_plugged = is_plugged; ++ if (is_plugged) { ++ if (bat->pdata->enable_charger) ++ bat->pdata->enable_charger(); ++ bat->status = POWER_SUPPLY_STATUS_CHARGING; ++ } else { ++ if (bat->pdata->disable_charger) ++ bat->pdata->disable_charger(); ++ bat->status = POWER_SUPPLY_STATUS_DISCHARGING; ++ } ++ } else { ++ if ((bat->pdata->gpio_charge_finished >= 0) && is_plugged) { ++ is_charged = gpio_get_value( ++ main_bat.pdata->gpio_charge_finished); ++ if (is_charged) { ++ if (bat->pdata->disable_charger) ++ bat->pdata->disable_charger(); ++ bat->status = POWER_SUPPLY_STATUS_FULL; ++ } else { ++ if (bat->pdata->enable_charger) ++ bat->pdata->enable_charger(); ++ bat->status = POWER_SUPPLY_STATUS_CHARGING; ++ } ++ } ++ } ++ ++ power_supply_changed(&bat->psy); ++} ++ ++static irqreturn_t s3c_adc_bat_charged(int irq, void *dev_id) ++{ ++ schedule_delayed_work(&bat_work, ++ msecs_to_jiffies(JITTER_DELAY)); ++ return IRQ_HANDLED; ++} ++ ++static int __init s3c_adc_bat_probe(struct platform_device *pdev) ++{ ++ struct s3c_adc_client *client; ++ struct s3c_adc_bat_pdata *pdata = pdev->dev.platform_data; ++ int ret; ++ ++ client = s3c_adc_register(pdev, NULL, NULL, 0); ++ if (IS_ERR(client)) { ++ dev_err(&pdev->dev, "cannot register adc\n"); ++ return PTR_ERR(client); ++ } ++ ++ platform_set_drvdata(pdev, client); ++ ++ main_bat.client = client; ++ main_bat.pdata = pdata; ++ main_bat.volt_value = -1; ++ main_bat.cur_value = -1; ++ main_bat.cable_plugged = 0; ++ main_bat.status = POWER_SUPPLY_STATUS_DISCHARGING; ++ ++ ret = power_supply_register(&pdev->dev, &main_bat.psy); ++ if (ret) ++ goto err_reg_main; ++ if (pdata->backup_volt_mult) { ++ backup_bat.client = client; ++ backup_bat.pdata = pdev->dev.platform_data; ++ backup_bat.volt_value = -1; ++ ret = power_supply_register(&pdev->dev, &backup_bat.psy); ++ if (ret) ++ goto err_reg_backup; ++ } ++ ++ INIT_DELAYED_WORK(&bat_work, s3c_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), ++ s3c_adc_bat_charged, ++ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, ++ "battery charged", NULL); ++ if (ret) ++ goto err_irq; ++ } ++ ++ if (pdata->init) { ++ ret = pdata->init(); ++ if (ret) ++ goto err_platform; ++ } ++ ++ dev_info(&pdev->dev, "successfully loaded\n"); ++ device_init_wakeup(&pdev->dev, 1); ++ ++ /* Schedule timer to check current status */ ++ schedule_delayed_work(&bat_work, ++ msecs_to_jiffies(JITTER_DELAY)); ++ ++ return 0; ++ ++err_platform: ++ if (pdata->gpio_charge_finished >= 0) ++ free_irq(gpio_to_irq(pdata->gpio_charge_finished), NULL); ++err_irq: ++ if (pdata->gpio_charge_finished >= 0) ++ gpio_free(pdata->gpio_charge_finished); ++err_gpio: ++ if (pdata->backup_volt_mult) ++ power_supply_unregister(&backup_bat.psy); ++err_reg_backup: ++ power_supply_unregister(&main_bat.psy); ++err_reg_main: ++ return ret; ++} ++ ++static int s3c_adc_bat_remove(struct platform_device *pdev) ++{ ++ struct s3c_adc_client *client = platform_get_drvdata(pdev); ++ struct s3c_adc_bat_pdata *pdata = pdev->dev.platform_data; ++ ++ power_supply_unregister(&main_bat.psy); ++ if (pdata->backup_volt_mult) ++ power_supply_unregister(&backup_bat.psy); ++ ++ s3c_adc_release(client); ++ ++ 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); ++ ++ if (pdata->exit) ++ pdata->exit(); ++ ++ return 0; ++} ++ ++#ifdef CONFIG_PM ++static int s3c_adc_bat_suspend(struct platform_device *pdev, ++ pm_message_t state) ++{ ++ struct s3c_adc_bat_pdata *pdata = pdev->dev.platform_data; ++ ++ if (pdata->gpio_charge_finished >= 0) { ++ if (device_may_wakeup(&pdev->dev)) ++ enable_irq_wake( ++ gpio_to_irq(pdata->gpio_charge_finished)); ++ else { ++ disable_irq(gpio_to_irq(pdata->gpio_charge_finished)); ++ main_bat.pdata->disable_charger(); ++ } ++ } ++ ++ return 0; ++} ++ ++static int s3c_adc_bat_resume(struct platform_device *pdev) ++{ ++ struct s3c_adc_bat_pdata *pdata = pdev->dev.platform_data; ++ ++ if (pdata->gpio_charge_finished >= 0) { ++ if (device_may_wakeup(&pdev->dev)) ++ disable_irq_wake( ++ gpio_to_irq(pdata->gpio_charge_finished)); ++ else ++ enable_irq(gpio_to_irq(pdata->gpio_charge_finished)); ++ } ++ ++ /* Schedule timer to check current status */ ++ schedule_delayed_work(&bat_work, ++ msecs_to_jiffies(JITTER_DELAY)); ++ ++ return 0; ++} ++#else ++#define s3c_adc_battery_suspend NULL ++#define s3c_adc_battery_resume NULL ++#endif ++ ++static struct platform_driver s3c_adc_bat_driver = { ++ .driver = { ++ .name = "s3c-adc-battery", ++ }, ++ .probe = s3c_adc_bat_probe, ++ .remove = s3c_adc_bat_remove, ++ .suspend = s3c_adc_bat_suspend, ++ .resume = s3c_adc_bat_resume, ++}; ++ ++ ++static int __init s3c_adc_bat_init(void) ++{ ++ return platform_driver_register(&s3c_adc_bat_driver); ++} ++ ++static void __exit s3c_adc_bat_exit(void) ++{ ++ platform_driver_unregister(&s3c_adc_bat_driver); ++} ++ ++module_init(s3c_adc_bat_init); ++module_exit(s3c_adc_bat_exit); ++ ++MODULE_AUTHOR("Vasily Khoruzhick <anarsoul@gmail.com>"); ++MODULE_DESCRIPTION("iPAQ H1930/H1940/RX1950 battery controler driver"); ++MODULE_LICENSE("GPL"); +diff --git a/include/linux/s3c_adc_battery.h b/include/linux/s3c_adc_battery.h +new file mode 100644 +index 0000000..dbce22f +--- /dev/null ++++ b/include/linux/s3c_adc_battery.h +@@ -0,0 +1,36 @@ ++#ifndef _S3C_ADC_BATTERY_H ++#define _S3C_ADC_BATTERY_H ++ ++struct s3c_adc_bat_thresh { ++ int volt; /* mV */ ++ int cur; /* mA */ ++ int level; /* percent */ ++}; ++ ++struct s3c_adc_bat_pdata { ++ int (*init)(void); ++ void (*exit)(void); ++ void (*enable_charger)(void); ++ void (*disable_charger)(void); ++ ++ int gpio_charge_finished; ++ ++ const struct s3c_adc_bat_thresh *lut_noac; ++ unsigned int lut_noac_cnt; ++ const struct s3c_adc_bat_thresh *lut_acin; ++ unsigned int lut_acin_cnt; ++ ++ const unsigned int volt_channel; ++ const unsigned int current_channel; ++ const unsigned int backup_volt_channel; ++ ++ const unsigned int volt_mult; ++ const unsigned int current_mult; ++ const unsigned int backup_volt_mult; ++ const unsigned int internal_impedance; ++ ++ const unsigned int backup_volt_max; ++ const unsigned int backup_volt_min; ++}; ++ ++#endif +-- +1.7.1.1 + |