From 0f160a6d66bef81c40a81d6d82775156d7b6b2b9 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Michael=20B=C3=BCsch?= Date: Wed, 9 Feb 2011 12:53:37 +0000 Subject: [PATCH] n810bm: Add charger detection SVN-Revision: 25429 --- .../900-n810-battery-management.patch | 907 +++++++++++++----- 1 file changed, 646 insertions(+), 261 deletions(-) diff --git a/target/linux/omap24xx/patches-2.6.37/900-n810-battery-management.patch b/target/linux/omap24xx/patches-2.6.37/900-n810-battery-management.patch index 2f2061629a..31eeeb857a 100644 --- a/target/linux/omap24xx/patches-2.6.37/900-n810-battery-management.patch +++ b/target/linux/omap24xx/patches-2.6.37/900-n810-battery-management.patch @@ -12,8 +12,8 @@ Index: linux-2.6.37/drivers/cbus/Kconfig =================================================================== ---- linux-2.6.37.orig/drivers/cbus/Kconfig 2011-02-06 00:24:48.502005279 +0100 -+++ linux-2.6.37/drivers/cbus/Kconfig 2011-02-06 00:24:48.550008091 +0100 +--- linux-2.6.37.orig/drivers/cbus/Kconfig 2011-02-06 14:05:49.838388760 +0100 ++++ linux-2.6.37/drivers/cbus/Kconfig 2011-02-06 14:05:49.885395646 +0100 @@ -94,4 +94,12 @@ to Retu/Vilma. Detection state and events are exposed through sysfs. @@ -29,8 +29,8 @@ Index: linux-2.6.37/drivers/cbus/Kconfig endmenu Index: linux-2.6.37/drivers/cbus/Makefile =================================================================== ---- linux-2.6.37.orig/drivers/cbus/Makefile 2011-02-06 00:24:48.493004751 +0100 -+++ linux-2.6.37/drivers/cbus/Makefile 2011-02-06 00:24:48.550008091 +0100 +--- linux-2.6.37.orig/drivers/cbus/Makefile 2011-02-06 14:05:49.829387442 +0100 ++++ linux-2.6.37/drivers/cbus/Makefile 2011-02-06 14:05:49.885395646 +0100 @@ -12,3 +12,6 @@ obj-$(CONFIG_CBUS_TAHVO_USER) += tahvo-user.o obj-$(CONFIG_CBUS_RETU_USER) += retu-user.o @@ -41,8 +41,8 @@ Index: linux-2.6.37/drivers/cbus/Makefile Index: linux-2.6.37/drivers/cbus/n810bm_main.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 -+++ linux-2.6.37/drivers/cbus/n810bm_main.c 2011-02-06 13:36:49.581078785 +0100 -@@ -0,0 +1,959 @@ ++++ linux-2.6.37/drivers/cbus/n810bm_main.c 2011-02-09 13:47:23.831144291 +0100 +@@ -0,0 +1,1169 @@ +/* + * Nokia n810 battery management + * @@ -69,11 +69,14 @@ Index: linux-2.6.37/drivers/cbus/n810bm_main.c +#include +#include +#include -+#include ++#include +#include -+#include +#include ++#include ++#include ++#include + ++#include "cbus.h" +#include "retu.h" +#include "tahvo.h" +#include "lipocharge.h" @@ -132,23 +135,38 @@ Index: linux-2.6.37/drivers/cbus/n810bm_main.c + N810BM_CAP_1500MAH = 1500, /* 1500 mAh battery */ +}; + ++enum n810bm_notify_flags { ++ N810BM_NOTIFY_battery_charging, ++ N810BM_NOTIFY_charger_pwm, ++}; ++ +struct n810bm { + bool battery_present; /* A battery is inserted */ + bool charger_present; /* The charger is connected */ + enum n810bm_capacity capacity; /* The capacity of the inserted battery (if any) */ + -+ bool charger_enabled; /* Want to charge? */ //TODO ++ bool charger_enabled; /* Want to charge? */ + struct lipocharge charger; /* Charger subsystem */ ++ unsigned int active_current_pwm; /* Active value of TAHVO_REG_CHGCURR */ ++ int current_measure_enabled; /* Current measure enable refcount */ + + struct platform_device *pdev; + const struct firmware *pmm_block; /* CAL PMM block */ + -+ struct timer_list check_timer; ++ bool verbose_charge_log; /* Verbose charge logging */ ++ ++ unsigned long notify_flags; ++ struct work_struct notify_work; ++ struct work_struct currmeas_irq_work; ++ struct delayed_work periodic_check_work; + + bool initialized; /* The hardware was initialized */ -+ spinlock_t lock; ++ struct mutex mutex; +}; + ++static void n810bm_notify_battery_charging(struct n810bm *bm); ++static void n810bm_notify_charger_pwm(struct n810bm *bm); ++ + +static inline struct n810bm * device_to_n810bm(struct device *dev) +{ @@ -158,13 +176,18 @@ Index: linux-2.6.37/drivers/cbus/n810bm_main.c + return bm; +} + ++static inline bool n810bm_known_battery_present(struct n810bm *bm) ++{ ++ return bm->battery_present && ++ bm->capacity != N810BM_CAP_UNKNOWN && ++ bm->capacity != N810BM_CAP_NONE; ++} ++ +static NORET_TYPE void n810bm_emergency(struct n810bm *bm, const char *message) ATTRIB_NORET; +static void n810bm_emergency(struct n810bm *bm, const char *message) +{ + printk(KERN_EMERG "n810 battery management fatal fault: %s\n", message); -+ /* Force a hard shutdown. */ -+ machine_power_off(); -+ panic("n810bm: Failed to halt machine in emergency state\n"); ++ cbus_emergency(); +} + +static u16 tahvo_read(struct n810bm *bm, unsigned int reg) @@ -290,7 +313,8 @@ Index: linux-2.6.37/drivers/cbus/n810bm_main.c + return value; +} + -+/* Set the current measure timer that triggers on Tahvo IRQ 7 */ ++/* Set the current measure timer that triggers on Tahvo IRQ 7 ++ * An interval of zero disables the timer. */ +static void n810bm_set_current_measure_timer(struct n810bm *bm, + u16 millisec_interval) +{ @@ -308,38 +332,39 @@ Index: linux-2.6.37/drivers/cbus/n810bm_main.c + TAHVO_REG_CHGCTL_CURTIMRST); + tahvo_clear(bm, TAHVO_REG_CHGCTL, + TAHVO_REG_CHGCTL_CURTIMRST); -+} -+ -+static void n810bm_enable_current_measure(struct n810bm *bm, bool slow, bool irq) -+{ -+ u16 millisec_interval; + -+ if (slow) -+ millisec_interval = 1000; -+ else -+ millisec_interval = 250; -+ -+ /* Enable the current measurement circuitry */ -+ tahvo_set(bm, TAHVO_REG_CHGCTL, -+ TAHVO_REG_CHGCTL_CURMEAS); -+ -+ /* Setup the measurement timer */ -+ n810bm_set_current_measure_timer(bm, millisec_interval); -+ if (irq) ++ if (millisec_interval) + tahvo_enable_irq(TAHVO_INT_BATCURR); ++ else ++ tahvo_disable_irq(TAHVO_INT_BATCURR); + + //TODO also do a software timer for safety. +} + -+static void n810bm_disable_current_measure(struct n810bm *bm) ++static void n810bm_enable_current_measure(struct n810bm *bm) +{ -+ /* Disable the measurement timer */ -+ n810bm_set_current_measure_timer(bm, 0); -+ tahvo_disable_irq(TAHVO_INT_BATCURR); ++ WARN_ON(bm->current_measure_enabled < 0); ++ if (!bm->current_measure_enabled) { ++ /* Enable the current measurement circuitry */ ++ tahvo_set(bm, TAHVO_REG_CHGCTL, ++ TAHVO_REG_CHGCTL_CURMEAS); ++ dev_dbg(&bm->pdev->dev, ++ "Current measurement circuitry enabled"); ++ } ++ bm->current_measure_enabled++; ++} + -+ /* Disable the current measurement circuitry */ -+ tahvo_clear(bm, TAHVO_REG_CHGCTL, -+ TAHVO_REG_CHGCTL_CURMEAS); ++static void n810bm_disable_current_measure(struct n810bm *bm) ++{ ++ bm->current_measure_enabled--; ++ WARN_ON(bm->current_measure_enabled < 0); ++ if (!bm->current_measure_enabled) { ++ /* Disable the current measurement circuitry */ ++ tahvo_clear(bm, TAHVO_REG_CHGCTL, ++ TAHVO_REG_CHGCTL_CURMEAS); ++ dev_dbg(&bm->pdev->dev, ++ "Current measurement circuitry disabled"); ++ } +} + +/* Measure the actual battery current. Returns a signed value in mA. @@ -349,6 +374,8 @@ Index: linux-2.6.37/drivers/cbus/n810bm_main.c + u16 retval; + int adc = 0, ma, i; + ++ if (WARN_ON(bm->current_measure_enabled <= 0)) ++ return 0; + for (i = 0; i < 3; i++) { + retval = tahvo_read(bm, TAHVO_REG_BATCURR); + adc += (s16)retval; /* Value is signed */ @@ -361,6 +388,33 @@ Index: linux-2.6.37/drivers/cbus/n810bm_main.c + return ma; +} + ++/* Requires bm->mutex locked */ ++static int n810bm_measure_batt_current_async(struct n810bm *bm) ++{ ++ int ma; ++ bool charging = lipocharge_is_charging(&bm->charger); ++ ++ n810bm_enable_current_measure(bm); ++ if (!charging) ++ WARN_ON(bm->active_current_pwm != 0); ++ tahvo_maskset(bm, TAHVO_REG_CHGCTL, ++ TAHVO_REG_CHGCTL_EN | ++ TAHVO_REG_CHGCTL_PWMOVR | ++ TAHVO_REG_CHGCTL_PWMOVRZERO, ++ TAHVO_REG_CHGCTL_EN | ++ TAHVO_REG_CHGCTL_PWMOVR | ++ (charging ? 0 : TAHVO_REG_CHGCTL_PWMOVRZERO)); ++ ma = n810bm_measure_batt_current(bm); ++ tahvo_maskset(bm, TAHVO_REG_CHGCTL, ++ TAHVO_REG_CHGCTL_EN | ++ TAHVO_REG_CHGCTL_PWMOVR | ++ TAHVO_REG_CHGCTL_PWMOVRZERO, ++ (charging ? TAHVO_REG_CHGCTL_EN : 0)); ++ n810bm_disable_current_measure(bm); ++ ++ return ma; ++} ++ +static int adc_sanity_check(struct n810bm *bm, unsigned int channel) +{ + int value; @@ -411,6 +465,9 @@ Index: linux-2.6.37/drivers/cbus/n810bm_main.c + return 2800; + mv = 2800 + ((adc - 0x37) * (((4200 - 2800) * scale) / (0x236 - 0x37))) / scale; + ++ //TODO compensate for power consumption ++ //TODO honor calibration values ++ + return mv; +} + @@ -492,17 +549,73 @@ Index: linux-2.6.37/drivers/cbus/n810bm_main.c + return percent; +} + ++static void n810bm_start_charge(struct n810bm *bm) ++{ ++ int err; ++ ++ WARN_ON(!bm->battery_present); ++ WARN_ON(!bm->charger_present); ++ ++ /* Set PWM to zero */ ++ bm->active_current_pwm = 0; ++ tahvo_write(bm, TAHVO_REG_CHGCURR, bm->active_current_pwm); ++ ++ /* Charge global enable */ ++ tahvo_maskset(bm, TAHVO_REG_CHGCTL, ++ TAHVO_REG_CHGCTL_EN | ++ TAHVO_REG_CHGCTL_PWMOVR | ++ TAHVO_REG_CHGCTL_PWMOVRZERO, ++ TAHVO_REG_CHGCTL_EN); ++ ++ WARN_ON((int)bm->capacity <= 0); ++ bm->charger.capacity = bm->capacity; ++ err = lipocharge_start(&bm->charger); ++ WARN_ON(err); ++ ++ /* Initialize current measurement circuitry */ ++ n810bm_enable_current_measure(bm); ++ n810bm_set_current_measure_timer(bm, 250); ++ ++ dev_info(&bm->pdev->dev, "Charging battery"); ++ n810bm_notify_charger_pwm(bm); ++ n810bm_notify_battery_charging(bm); ++} ++ ++static void n810bm_stop_charge(struct n810bm *bm) ++{ ++ if (lipocharge_is_charging(&bm->charger)) { ++ n810bm_set_current_measure_timer(bm, 0); ++ n810bm_disable_current_measure(bm); ++ } ++ lipocharge_stop(&bm->charger); ++ ++ /* Set PWM to zero */ ++ bm->active_current_pwm = 0; ++ tahvo_write(bm, TAHVO_REG_CHGCURR, bm->active_current_pwm); ++ ++ /* Charge global disable */ ++ tahvo_maskset(bm, TAHVO_REG_CHGCTL, ++ TAHVO_REG_CHGCTL_EN | ++ TAHVO_REG_CHGCTL_PWMOVR | ++ TAHVO_REG_CHGCTL_PWMOVRZERO, ++ 0); ++ ++ dev_info(&bm->pdev->dev, "Not charging battery"); ++ n810bm_notify_charger_pwm(bm); ++ n810bm_notify_battery_charging(bm); ++} ++ +/* Periodic check */ -+static void n810bm_check_timer(unsigned long data) ++static void n810bm_periodic_check_work(struct work_struct *work) +{ -+ struct n810bm *bm = (struct n810bm *)data; -+ unsigned long flags; ++ struct n810bm *bm = container_of(to_delayed_work(work), ++ struct n810bm, periodic_check_work); + u16 status; + bool battery_was_present, charger_was_present; + bool force_charge = 0; + int mv; + -+ spin_lock_irqsave(&bm->lock, flags); ++ mutex_lock(&bm->mutex); + + status = retu_read(bm, RETU_REG_STATUS); + battery_was_present = bm->battery_present; @@ -523,7 +636,7 @@ Index: linux-2.6.37/drivers/cbus/n810bm_main.c + } else { + bm->capacity = N810BM_CAP_NONE; + dev_info(&bm->pdev->dev, "The main battery was removed"); -+ //TODO what do if charging? ++ //TODO disable charging + } + } + @@ -534,6 +647,7 @@ Index: linux-2.6.37/drivers/cbus/n810bm_main.c + } + + if (bm->battery_present && !lipocharge_is_charging(&bm->charger)) { ++ /* We're draining the battery */ + mv = n810bm_measure_batt_voltage(bm); + if (mv < 0) + n810bm_emergency(bm, "check timer: Failed to measure"); @@ -546,18 +660,25 @@ Index: linux-2.6.37/drivers/cbus/n810bm_main.c + } + } + -+ if (bm->charger_present && bm->battery_present) { ++ if (bm->charger_present && n810bm_known_battery_present(bm)) { ++ /* Known battery and charger are connected */ + if (bm->charger_enabled || force_charge) { ++ /* Charger is enabled */ + if (!lipocharge_is_charging(&bm->charger)) { + //TODO start charging, if battery is below some threshold ++ n810bm_start_charge(bm); + } + } + } + -+ mod_timer(&bm->check_timer, round_jiffies(jiffies + N810BM_CHECK_INTERVAL)); -+ spin_unlock_irqrestore(&bm->lock, flags); ++ if (lipocharge_is_charging(&bm->charger) && !bm->charger_present) { ++ /* Charger was unplugged. */ ++ n810bm_stop_charge(bm); ++ } + -+ return; ++ mutex_unlock(&bm->mutex); ++ schedule_delayed_work(&bm->periodic_check_work, ++ round_jiffies_relative(N810BM_CHECK_INTERVAL)); +} + +static void n810bm_adc_irq_handler(unsigned long data) @@ -566,7 +687,54 @@ Index: linux-2.6.37/drivers/cbus/n810bm_main.c + + retu_ack_irq(RETU_INT_ADCS); + //TODO -+dev_dbg(&bm->pdev->dev, "ADC interrupt triggered\n"); ++dev_info(&bm->pdev->dev, "ADC interrupt triggered\n"); ++} ++ ++static void n810bm_tahvo_current_measure_work(struct work_struct *work) ++{ ++ struct n810bm *bm = container_of(work, struct n810bm, currmeas_irq_work); ++ int res, ma, mv, temp; ++ ++ mutex_lock(&bm->mutex); ++ if (!lipocharge_is_charging(&bm->charger)) ++ goto out_unlock; ++ ++ tahvo_maskset(bm, TAHVO_REG_CHGCTL, ++ TAHVO_REG_CHGCTL_PWMOVR | ++ TAHVO_REG_CHGCTL_PWMOVRZERO, ++ TAHVO_REG_CHGCTL_PWMOVR); ++ ma = n810bm_measure_batt_current(bm); ++ tahvo_maskset(bm, TAHVO_REG_CHGCTL, ++ TAHVO_REG_CHGCTL_PWMOVR | ++ TAHVO_REG_CHGCTL_PWMOVRZERO, ++ TAHVO_REG_CHGCTL_PWMOVR | ++ TAHVO_REG_CHGCTL_PWMOVRZERO); ++ msleep(10); ++ mv = n810bm_measure_batt_voltage(bm); ++ tahvo_maskset(bm, TAHVO_REG_CHGCTL, ++ TAHVO_REG_CHGCTL_PWMOVR | ++ TAHVO_REG_CHGCTL_PWMOVRZERO, ++ 0); ++ temp = n810bm_measure_batt_temp(bm); ++ if (WARN_ON(mv < 0)) ++ goto out_unlock; ++ if (WARN_ON(temp < 0)) ++ goto out_unlock; ++ ++ if (bm->verbose_charge_log) { ++ dev_info(&bm->pdev->dev, ++ "Battery charge state: %d mV, %d mA (%s)", ++ mv, ma, ++ (ma <= 0) ? "discharging" : "charging"); ++ } ++ res = lipocharge_update_state(&bm->charger, mv, ma, temp); ++ if (res) { ++ if (res > 0) ++ dev_info(&bm->pdev->dev, "Battery fully charged"); ++ n810bm_stop_charge(bm); ++ } ++out_unlock: ++ mutex_unlock(&bm->mutex); +} + +static void n810bm_tahvo_current_measure_irq_handler(unsigned long data) @@ -574,261 +742,279 @@ Index: linux-2.6.37/drivers/cbus/n810bm_main.c + struct n810bm *bm = (struct n810bm *)data; + + tahvo_ack_irq(TAHVO_INT_BATCURR); -+ //TODO -+dev_dbg(&bm->pdev->dev, "Tahvo measure IRQ triggered\n"); ++ schedule_work(&bm->currmeas_irq_work); +} + ++#define DEFINE_ATTR_NOTIFY(attr_name) \ ++ void n810bm_notify_##attr_name(struct n810bm *bm) \ ++ { \ ++ set_bit(N810BM_NOTIFY_##attr_name, &bm->notify_flags); \ ++ wmb(); \ ++ schedule_work(&bm->notify_work); \ ++ } ++ +#define DEFINE_SHOW_INT_FUNC(name, member) \ -+ static ssize_t n810bm_##name##_show(struct device *dev, \ -+ struct device_attribute *attr, \ -+ char *buf) \ ++ static ssize_t n810bm_attr_##name##_show(struct device *dev, \ ++ struct device_attribute *attr, \ ++ char *buf) \ + { \ + struct n810bm *bm = device_to_n810bm(dev); \ + ssize_t count; \ + \ -+ spin_lock_irq(&bm->lock); \ ++ mutex_lock(&bm->mutex); \ + count = snprintf(buf, PAGE_SIZE, "%d\n", (int)(bm->member)); \ -+ spin_unlock_irq(&bm->lock); \ ++ mutex_unlock(&bm->mutex); \ + \ + return count; \ + } + +#define DEFINE_STORE_INT_FUNC(name, member) \ -+ static ssize_t n810bm_##name##_store(struct device *dev, \ -+ struct device_attribute *attr, \ -+ const char *buf, size_t count) \ ++ static ssize_t n810bm_attr_##name##_store(struct device *dev, \ ++ struct device_attribute *attr,\ ++ const char *buf, size_t count)\ + { \ + struct n810bm *bm = device_to_n810bm(dev); \ + long val; \ + int err; \ + \ -+ spin_lock_irq(&bm->lock); \ ++ mutex_lock(&bm->mutex); \ + err = strict_strtol(buf, 0, &val); \ + if (!err) \ + bm->member = (typeof(bm->member))val; \ -+ spin_unlock_irq(&bm->lock); \ ++ mutex_unlock(&bm->mutex); \ + \ + return err ? err : count; \ + } + +#define DEFINE_ATTR_SHOW_INT(name, member) \ + DEFINE_SHOW_INT_FUNC(name, member) \ -+ static DEVICE_ATTR(name, 0444, n810bm_##name##_show, NULL); ++ static DEVICE_ATTR(name, S_IRUGO, \ ++ n810bm_attr_##name##_show, NULL); + +#define DEFINE_ATTR_SHOW_STORE_INT(name, member) \ + DEFINE_SHOW_INT_FUNC(name, member) \ + DEFINE_STORE_INT_FUNC(name, member) \ -+ static DEVICE_ATTR(name, 0644, n810bm_##name##_show, \ -+ n810bm_##name##_store); ++ static DEVICE_ATTR(name, S_IRUGO | S_IWUSR, \ ++ n810bm_attr_##name##_show, \ ++ n810bm_attr_##name##_store); + -+DEFINE_ATTR_SHOW_INT(batt_present, battery_present); ++DEFINE_ATTR_SHOW_INT(battery_present, battery_present); +DEFINE_ATTR_SHOW_INT(charger_present, charger_present); ++DEFINE_ATTR_SHOW_INT(charger_pwm, active_current_pwm); ++static DEFINE_ATTR_NOTIFY(charger_pwm); +DEFINE_ATTR_SHOW_STORE_INT(charger_enable, charger_enabled); ++DEFINE_ATTR_SHOW_STORE_INT(charger_verbose, verbose_charge_log); + -+static ssize_t n810bm_attr_charge_show(struct device *dev, -+ struct device_attribute *attr, -+ char *buf) ++static ssize_t n810bm_attr_battery_charging(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) +{ + struct n810bm *bm = device_to_n810bm(dev); -+ int err = -ENODEV; -+ ssize_t count = 0; ++ ssize_t count; ++ ++ mutex_lock(&bm->mutex); ++ count = snprintf(buf, PAGE_SIZE, "%d\n", ++ (int)lipocharge_is_charging(&bm->charger)); ++ mutex_unlock(&bm->mutex); ++ ++ return count; ++} ++static DEVICE_ATTR(battery_charging, S_IRUGO, ++ n810bm_attr_battery_charging, NULL); ++static DEFINE_ATTR_NOTIFY(battery_charging); ++ ++static ssize_t n810bm_attr_battery_level_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct n810bm *bm = device_to_n810bm(dev); ++ ssize_t count = -ENODEV; + int millivolt; + -+ spin_lock_irq(&bm->lock); -+ if (bm->battery_present) { ++ mutex_lock(&bm->mutex); ++ if (!bm->battery_present || lipocharge_is_charging(&bm->charger)) ++ millivolt = 0; ++ else + millivolt = n810bm_measure_batt_voltage(bm); -+ if (millivolt >= 0) { -+ count = snprintf(buf, PAGE_SIZE, "%u\n", -+ n810bm_mvolt2percent(millivolt)); -+ err = 0; -+ } -+ } else { -+ count = snprintf(buf, PAGE_SIZE, "no battery\n"); -+ err = 0; ++ if (millivolt >= 0) { ++ count = snprintf(buf, PAGE_SIZE, "%u\n", ++ n810bm_mvolt2percent(millivolt)); + } -+ spin_unlock_irq(&bm->lock); ++ mutex_unlock(&bm->mutex); + -+ return err ? err : count; ++ return count; +} -+static DEVICE_ATTR(batt_charge, 0444, n810bm_attr_charge_show, NULL); ++static DEVICE_ATTR(battery_level, S_IRUGO, ++ n810bm_attr_battery_level_show, NULL); + -+static ssize_t n810bm_attr_capacity_show(struct device *dev, -+ struct device_attribute *attr, -+ char *buf) ++static ssize_t n810bm_attr_battery_capacity_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) +{ + struct n810bm *bm = device_to_n810bm(dev); + ssize_t count; ++ int capacity = 0; + -+ spin_lock_irq(&bm->lock); -+ if (bm->battery_present) { -+ count = snprintf(buf, PAGE_SIZE, "%d\n", -+ (int)bm->capacity); -+ } else -+ count = snprintf(buf, PAGE_SIZE, "no battery\n"); -+ spin_unlock_irq(&bm->lock); ++ mutex_lock(&bm->mutex); ++ if (n810bm_known_battery_present(bm)) ++ capacity = (int)bm->capacity; ++ count = snprintf(buf, PAGE_SIZE, "%d\n", capacity); ++ mutex_unlock(&bm->mutex); + + return count; +} -+static DEVICE_ATTR(batt_capacity, 0444, n810bm_attr_capacity_show, NULL); ++static DEVICE_ATTR(battery_capacity, S_IRUGO, ++ n810bm_attr_battery_capacity_show, NULL); + -+static ssize_t n810bm_attr_battemp_show(struct device *dev, -+ struct device_attribute *attr, -+ char *buf) ++static ssize_t n810bm_attr_battery_temp_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) +{ + struct n810bm *bm = device_to_n810bm(dev); -+ ssize_t count = 0; -+ int k, err = -ENODEV; ++ ssize_t count = -ENODEV; ++ int k; + -+ spin_lock_irq(&bm->lock); ++ mutex_lock(&bm->mutex); + k = n810bm_measure_batt_temp(bm); -+ if (k >= 0) { ++ if (k >= 0) + count = snprintf(buf, PAGE_SIZE, "%d\n", k); -+ err = 0; -+ } -+ spin_unlock_irq(&bm->lock); ++ mutex_unlock(&bm->mutex); + -+ return err ? err : count; ++ return count; +} -+static DEVICE_ATTR(batt_temp, 0444, n810bm_attr_battemp_show, NULL); ++static DEVICE_ATTR(battery_temp, S_IRUGO, ++ n810bm_attr_battery_temp_show, NULL); + +static ssize_t n810bm_attr_charger_voltage_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct n810bm *bm = device_to_n810bm(dev); -+ ssize_t count = 0; -+ int mv, err = -ENODEV; ++ ssize_t count = -ENODEV; ++ int mv = 0; + -+ spin_lock_irq(&bm->lock); -+ if (bm->charger_present) { ++ mutex_lock(&bm->mutex); ++ if (bm->charger_present) + mv = n810bm_measure_charger_voltage(bm); -+ if (mv >= 0) { -+ count = snprintf(buf, PAGE_SIZE, "%d\n", mv); -+ err = 0; -+ } -+ } else { -+ count = snprintf(buf, PAGE_SIZE, "no charger\n"); -+ err = 0; -+ } -+ spin_unlock_irq(&bm->lock); ++ if (mv >= 0) ++ count = snprintf(buf, PAGE_SIZE, "%d\n", mv); ++ mutex_unlock(&bm->mutex); + -+ return err ? err : count; ++ return count; +} -+static DEVICE_ATTR(charger_voltage, 0444, n810bm_attr_charger_voltage_show, NULL); ++static DEVICE_ATTR(charger_voltage, S_IRUGO, ++ n810bm_attr_charger_voltage_show, NULL); + -+static ssize_t n810bm_attr_backup_batt_voltage_show(struct device *dev, -+ struct device_attribute *attr, -+ char *buf) ++static ssize_t n810bm_attr_backup_battery_voltage_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) +{ + struct n810bm *bm = device_to_n810bm(dev); -+ ssize_t count = 0; -+ int mv, err = -ENODEV; ++ ssize_t count = -ENODEV; ++ int mv; + -+ spin_lock_irq(&bm->lock); ++ mutex_lock(&bm->mutex); + mv = n810bm_measure_backup_batt_voltage(bm); -+ if (mv >= 0) { ++ if (mv >= 0) + count = snprintf(buf, PAGE_SIZE, "%d\n", mv); -+ err = 0; -+ } -+ spin_unlock_irq(&bm->lock); ++ mutex_unlock(&bm->mutex); + -+ return err ? err : count; ++ return count; +} -+static DEVICE_ATTR(backup_batt_voltage, 0444, n810bm_attr_backup_batt_voltage_show, NULL); ++static DEVICE_ATTR(backup_battery_voltage, S_IRUGO, ++ n810bm_attr_backup_battery_voltage_show, NULL); + -+static ssize_t n810bm_attr_batt_current_show(struct device *dev, -+ struct device_attribute *attr, -+ char *buf) ++static ssize_t n810bm_attr_battery_current_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) +{ + struct n810bm *bm = device_to_n810bm(dev); -+ ssize_t count = 0; -+ int ma; ++ ssize_t count = -ENODEV; ++ int ma = 0; + -+ spin_lock_irq(&bm->lock); -+ if (bm->battery_present) { -+ ma = n810bm_measure_batt_current(bm);//FIXME -+ count = snprintf(buf, PAGE_SIZE, "%d\n", ma); -+ } else -+ count = snprintf(buf, PAGE_SIZE, "no battery\n"); -+ spin_unlock_irq(&bm->lock); ++ mutex_lock(&bm->mutex); ++ if (bm->battery_present) ++ ma = n810bm_measure_batt_current_async(bm); ++ count = snprintf(buf, PAGE_SIZE, "%d\n", ma); ++ mutex_unlock(&bm->mutex); + + return count; +} -+static DEVICE_ATTR(batt_current, 0444, n810bm_attr_batt_current_show, NULL); -+ -+//TODO remove this -+static ssize_t n810bm_attr_charger_status_show(struct device *dev, -+ struct device_attribute *attr, -+ char *buf) -+{ -+ struct n810bm *bm = device_to_n810bm(dev); -+ ssize_t count = 0; -+ unsigned int stat; ++static DEVICE_ATTR(battery_current, S_IRUGO, ++ n810bm_attr_battery_current_show, NULL); + -+ spin_lock_irq(&bm->lock); -+ stat = retu_read(bm, RETU_REG_STATUS); -+ count = snprintf(buf, PAGE_SIZE, "0x%X\n", stat); -+ spin_unlock_irq(&bm->lock); ++static const struct device_attribute *n810bm_attrs[] = { ++ &dev_attr_battery_present, ++ &dev_attr_battery_level, ++ &dev_attr_battery_charging, ++ &dev_attr_battery_current, ++ &dev_attr_battery_capacity, ++ &dev_attr_battery_temp, ++ &dev_attr_backup_battery_voltage, ++ &dev_attr_charger_present, ++ &dev_attr_charger_verbose, ++ &dev_attr_charger_voltage, ++ &dev_attr_charger_enable, ++ &dev_attr_charger_pwm, ++}; + -+ return count; ++static void n810bm_notify_work(struct work_struct *work) ++{ ++ struct n810bm *bm = container_of(work, struct n810bm, notify_work); ++ unsigned long notify_flags; ++ ++ notify_flags = xchg(&bm->notify_flags, 0); ++ mb(); ++ ++#define do_notify(attr_name) \ ++ do { \ ++ if (notify_flags & (1 << N810BM_NOTIFY_##attr_name)) { \ ++ sysfs_notify(&bm->pdev->dev.kobj, NULL, \ ++ dev_attr_##attr_name.attr.name); \ ++ } \ ++ } while (0) ++ ++ do_notify(battery_charging); ++ do_notify(charger_pwm); +} -+static DEVICE_ATTR(charger_status, 0444, n810bm_attr_charger_status_show, NULL); + -+//TODO remove this -+static ssize_t n810bm_attr_charge_current_show(struct device *dev, -+ struct device_attribute *attr, -+ char *buf) ++static int n810bm_charger_set_current_pwm(struct lipocharge *c, ++ unsigned int duty_cycle) +{ -+ struct n810bm *bm = device_to_n810bm(dev); -+ ssize_t count = 0; -+ unsigned int val; ++ struct n810bm *bm = container_of(c, struct n810bm, charger); ++ int err = -EINVAL; ++ ++ WARN_ON(!mutex_is_locked(&bm->mutex)); ++ if (WARN_ON(duty_cycle > 0xFF)) ++ goto out; ++ if (WARN_ON(!bm->charger_enabled)) ++ goto out; ++ if (WARN_ON(!bm->battery_present || !bm->charger_present)) ++ goto out; ++ ++ if (duty_cycle != bm->active_current_pwm) { ++ bm->active_current_pwm = duty_cycle; ++ tahvo_write(bm, TAHVO_REG_CHGCURR, duty_cycle); ++ n810bm_notify_charger_pwm(bm); ++ } + -+ spin_lock_irq(&bm->lock); -+ val = tahvo_read(bm, TAHVO_REG_CHGCURR); -+ count = snprintf(buf, PAGE_SIZE, "0x%X\n", val); -+ spin_unlock_irq(&bm->lock); ++ err = 0; ++out: + -+ return count; ++ return err; +} + -+static ssize_t n810bm_attr_charge_current_store(struct device *dev, -+ struct device_attribute *attr, -+ const char *buf, size_t count) ++static void n810bm_charger_emergency(struct lipocharge *c) +{ -+ struct n810bm *bm = device_to_n810bm(dev); -+ unsigned long val; -+ int err; -+ -+ spin_lock_irq(&bm->lock); -+ err = strict_strtoul(buf, 0, &val); -+ if (!err && val <= 0xFF) -+ tahvo_write(bm, TAHVO_REG_CHGCURR, val); -+ spin_unlock_irq(&bm->lock); ++ struct n810bm *bm = container_of(c, struct n810bm, charger); + -+ return err ? err : count; ++ n810bm_emergency(bm, "Battery charger fault"); +} -+static DEVICE_ATTR(charge_current, 0644, -+ n810bm_attr_charge_current_show, -+ n810bm_attr_charge_current_store); -+ -+static const struct device_attribute *n810bm_attrs[] = { -+ &dev_attr_batt_present, -+ &dev_attr_batt_charge, -+ &dev_attr_batt_current, -+ &dev_attr_batt_capacity, -+ &dev_attr_batt_temp, -+ &dev_attr_backup_batt_voltage, -+ &dev_attr_charger_present, -+ &dev_attr_charger_voltage, -+ &dev_attr_charger_status, -+ &dev_attr_charger_enable, -+ &dev_attr_charge_current, -+}; + +static void n810bm_hw_exit(struct n810bm *bm) +{ ++ n810bm_stop_charge(bm); + retu_write(bm, RETU_REG_ADCSCR, 0); -+ //TODO +} + +static int n810bm_hw_init(struct n810bm *bm) @@ -839,14 +1025,31 @@ Index: linux-2.6.37/drivers/cbus/n810bm_main.c + if (err) + return err; + ++ n810bm_stop_charge(bm); ++ + return 0; +} + ++static void n810bm_cancel_and_flush_work(struct n810bm *bm) ++{ ++ cancel_delayed_work_sync(&bm->periodic_check_work); ++ cancel_work_sync(&bm->notify_work); ++ cancel_work_sync(&bm->currmeas_irq_work); ++ flush_scheduled_work(); ++} ++ +static int n810bm_device_init(struct n810bm *bm) +{ + int attr_index; + int err; + ++ bm->charger.rate = LIPORATE_p6C; ++ bm->charger.top_voltage = 4100; ++ bm->charger.duty_cycle_max = 0xFF; ++ bm->charger.set_current_pwm = n810bm_charger_set_current_pwm; ++ bm->charger.emergency = n810bm_charger_emergency; ++ lipocharge_init(&bm->charger, &bm->pdev->dev); ++ + err = n810bm_hw_init(bm); + if (err) + goto error; @@ -865,9 +1068,10 @@ Index: linux-2.6.37/drivers/cbus/n810bm_main.c + (unsigned long)bm, "n810bm"); + if (err) + goto err_free_retu_irq; ++ tahvo_disable_irq(TAHVO_INT_BATCURR); + -+ lipocharge_init(&bm->charger); -+ mod_timer(&bm->check_timer, round_jiffies(jiffies + N810BM_CHECK_INTERVAL)); ++ schedule_delayed_work(&bm->periodic_check_work, ++ round_jiffies_relative(N810BM_CHECK_INTERVAL)); + + bm->initialized = 1; + dev_info(&bm->pdev->dev, "Battery management initialized"); @@ -882,6 +1086,8 @@ Index: linux-2.6.37/drivers/cbus/n810bm_main.c +/*err_exit:*/ + n810bm_hw_exit(bm); +error: ++ n810bm_cancel_and_flush_work(bm); ++ + return err; +} + @@ -895,9 +1101,11 @@ Index: linux-2.6.37/drivers/cbus/n810bm_main.c + lipocharge_exit(&bm->charger); + tahvo_free_irq(TAHVO_INT_BATCURR); + retu_free_irq(RETU_INT_ADCS); -+ del_timer_sync(&bm->check_timer); + for (i = 0; i < ARRAY_SIZE(n810bm_attrs); i++) + device_remove_file(&bm->pdev->dev, n810bm_attrs[i]); ++ ++ n810bm_cancel_and_flush_work(bm); ++ + n810bm_hw_exit(bm); + release_firmware(bm->pmm_block); + @@ -944,8 +1152,10 @@ Index: linux-2.6.37/drivers/cbus/n810bm_main.c + return -ENOMEM; + bm->pdev = pdev; + platform_set_drvdata(pdev, bm); -+ spin_lock_init(&bm->lock); -+ setup_timer(&bm->check_timer, n810bm_check_timer, (unsigned long)bm); ++ mutex_init(&bm->mutex); ++ INIT_DELAYED_WORK(&bm->periodic_check_work, n810bm_periodic_check_work); ++ INIT_WORK(&bm->notify_work, n810bm_notify_work); ++ INIT_WORK(&bm->currmeas_irq_work, n810bm_tahvo_current_measure_work); + + dev_info(&bm->pdev->dev, "Requesting CAL BME PMM block firmware file " + N810BM_PMM_BLOCK_FILENAME); @@ -1004,8 +1214,8 @@ Index: linux-2.6.37/drivers/cbus/n810bm_main.c +MODULE_AUTHOR("Michael Buesch"); Index: linux-2.6.37/drivers/cbus/retu.c =================================================================== ---- linux-2.6.37.orig/drivers/cbus/retu.c 2011-02-06 00:24:48.493004751 +0100 -+++ linux-2.6.37/drivers/cbus/retu.c 2011-02-06 00:24:48.551008149 +0100 +--- linux-2.6.37.orig/drivers/cbus/retu.c 2011-02-06 14:05:49.829387442 +0100 ++++ linux-2.6.37/drivers/cbus/retu.c 2011-02-08 17:33:18.981369017 +0100 @@ -85,10 +85,10 @@ * * This function writes a value to the specified register @@ -1019,10 +1229,18 @@ Index: linux-2.6.37/drivers/cbus/retu.c } void retu_set_clear_reg_bits(int reg, u16 set, u16 clear) +@@ -459,6 +459,7 @@ + EXPORT_SYMBOL(retu_ack_irq); + EXPORT_SYMBOL(retu_read_reg); + EXPORT_SYMBOL(retu_write_reg); ++EXPORT_SYMBOL(retu_read_adc); + + subsys_initcall(retu_init); + module_exit(retu_exit); Index: linux-2.6.37/drivers/cbus/retu.h =================================================================== ---- linux-2.6.37.orig/drivers/cbus/retu.h 2011-02-06 00:24:48.493004751 +0100 -+++ linux-2.6.37/drivers/cbus/retu.h 2011-02-06 00:24:48.551008149 +0100 +--- linux-2.6.37.orig/drivers/cbus/retu.h 2011-02-06 14:05:49.829387442 +0100 ++++ linux-2.6.37/drivers/cbus/retu.h 2011-02-06 14:05:49.886395793 +0100 @@ -40,6 +40,8 @@ #define RETU_REG_CTRL_CLR 0x0f /* Regulator clear register */ #define RETU_REG_CTRL_SET 0x10 /* Regulator set register */ @@ -1061,8 +1279,8 @@ Index: linux-2.6.37/drivers/cbus/retu.h int retu_request_irq(int id, void *irq_handler, unsigned long arg, char *name); Index: linux-2.6.37/arch/arm/mach-omap2/board-n8x0.c =================================================================== ---- linux-2.6.37.orig/arch/arm/mach-omap2/board-n8x0.c 2011-02-06 00:24:48.478003872 +0100 -+++ linux-2.6.37/arch/arm/mach-omap2/board-n8x0.c 2011-02-06 00:24:48.551008149 +0100 +--- linux-2.6.37.orig/arch/arm/mach-omap2/board-n8x0.c 2011-02-06 14:05:49.815385390 +0100 ++++ linux-2.6.37/arch/arm/mach-omap2/board-n8x0.c 2011-02-06 14:05:49.886395793 +0100 @@ -907,6 +907,17 @@ ARRAY_SIZE(n8x0_gpio_switches)); } @@ -1093,12 +1311,12 @@ Index: linux-2.6.37/arch/arm/mach-omap2/board-n8x0.c Index: linux-2.6.37/drivers/cbus/lipocharge.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 -+++ linux-2.6.37/drivers/cbus/lipocharge.c 2011-02-06 12:57:01.427475867 +0100 -@@ -0,0 +1,58 @@ ++++ linux-2.6.37/drivers/cbus/lipocharge.c 2011-02-09 12:42:58.243147563 +0100 +@@ -0,0 +1,183 @@ +/* + * Generic LIPO battery charger + * -+ * Copyright (c) 2010 Michael Buesch ++ * Copyright (c) 2010-2011 Michael Buesch + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License @@ -1111,112 +1329,242 @@ Index: linux-2.6.37/drivers/cbus/lipocharge.c + * GNU General Public License for more details. + */ + ++#define DEBUG ++ +#include "lipocharge.h" + +#include + + -+static void lipocharge_timer(unsigned long data) -+{ -+ struct lipocharge *c = (struct lipocharge *)data; ++/* Hysteresis constants */ ++#define CURRENT_HYST 30 /* mA */ ++#define VOLTAGE_HYST 10 /* mV */ + -+ spin_lock(&c->lock); -+ //TODO -+ spin_unlock(&c->lock); ++/* Threshold constants */ ++#define FINISH_CURRENT_PERCENT 3 ++ ++ ++/* Returns the requested first-stage charge current in mA */ ++static inline unsigned int get_stage1_charge_current(struct lipocharge *c) ++{ ++ /* current = (capacity * C) */ ++ return c->capacity * c->rate / 1000; +} + -+void lipocharge_init(struct lipocharge *c) ++void lipocharge_init(struct lipocharge *c, struct device *dev) +{ -+ spin_lock_init(&c->lock); -+ setup_timer(&c->timer, lipocharge_timer, (unsigned long)c); ++ c->dev = dev; ++ c->state = LIPO_IDLE; +} + +void lipocharge_exit(struct lipocharge *c) +{ ++ c->state = LIPO_IDLE; +} + +int lipocharge_start(struct lipocharge *c) +{ -+ if (!c->set_charge_current || !c->get_charge_current || -+ !c->get_voltage || -+ !c->finished || !c->emergency) ++ int err; ++ ++ if (c->state != LIPO_IDLE) ++ return -EBUSY; ++ if (!c->set_current_pwm || !c->emergency) + return -EINVAL; + if (!c->top_voltage || c->top_voltage > 4200) + return -EINVAL; -+ //TODO ++ ++ c->active_duty_cycle = 0; ++ err = c->set_current_pwm(c, c->active_duty_cycle); ++ if (err) ++ return err; ++ c->state = LIPO_FIRST_STAGE; + + return 0; +} + +void lipocharge_stop(struct lipocharge *c) +{ -+ del_timer_sync(&c->timer); -+ //TODO ++ if (c->state == LIPO_IDLE) ++ return; ++ c->state = LIPO_IDLE; ++} ++ ++static int lipocharge_increase_current(struct lipocharge *c, ++ unsigned int inc_permille) ++{ ++ int old_pwm, new_pwm; ++ ++ if (c->active_duty_cycle >= c->duty_cycle_max) ++ return 0; ++ ++ old_pwm = c->active_duty_cycle; ++ new_pwm = old_pwm + (c->duty_cycle_max * inc_permille / 1000); ++ new_pwm = min(new_pwm, (int)c->duty_cycle_max); ++ c->active_duty_cycle = new_pwm; ++ ++ dev_dbg(c->dev, "lipo: Increasing duty_cycle by " ++ "%u permille (0x%02X -> 0x%02X)", ++ inc_permille, old_pwm, new_pwm); ++ ++ return c->set_current_pwm(c, c->active_duty_cycle); ++} ++ ++static int lipocharge_decrease_current(struct lipocharge *c, ++ unsigned int dec_permille) ++{ ++ int old_pwm, new_pwm; ++ ++ if (c->active_duty_cycle <= 0) ++ return 0; ++ ++ old_pwm = c->active_duty_cycle; ++ new_pwm = old_pwm - (c->duty_cycle_max * dec_permille / 1000); ++ new_pwm = max(0, new_pwm); ++ c->active_duty_cycle = new_pwm; ++ ++ dev_dbg(c->dev, "lipo: Decreasing duty_cycle by " ++ "%u permille (0x%02X -> 0x%02X)", ++ dec_permille, old_pwm, new_pwm); ++ ++ return c->set_current_pwm(c, c->active_duty_cycle); ++} ++ ++/** lipocharge_update_state - Update the charge state ++ * @c: The context. ++ * @voltage_mV: The measured battery voltage. ++ * @current_mA: The measured charge current. ++ * negative -> drain. ++ * positive -> charge. ++ * @temp_K: Battery temperature in K. ++ * ++ * Returns 0 on success, -1 on error. ++ * Returns 1, if the charging process is finished. ++ */ ++int lipocharge_update_state(struct lipocharge *c, ++ unsigned int voltage_mV, ++ int current_mA, ++ unsigned int temp_K) ++{ ++ int requested_current, current_diff; ++ int err; ++ unsigned int permille; ++ ++ //TODO temp ++ ++restart: ++ switch (c->state) { ++ case LIPO_IDLE: ++ dev_err(c->dev, "%s: called while idle", __func__); ++ return -EINVAL; ++ case LIPO_FIRST_STAGE: /* Constant current */ ++//printk("GOT %u %d %u\n", voltage_mV, current_mA, temp_K); ++ if (voltage_mV >= c->top_voltage) { ++ /* Float voltage reached. ++ * Switch charger mode to "constant current" */ ++ c->state = LIPO_SECOND_STAGE; ++ dev_dbg(c->dev, "Switched to second charging stage."); ++ goto restart; ++ } ++ /* Float voltage not reached, yet. ++ * Try to get the requested constant current. */ ++ requested_current = get_stage1_charge_current(c); ++ if (current_mA < 0) ++ current_mA = 0; ++ current_diff = requested_current - current_mA; ++ if (abs(requested_current - current_mA) > CURRENT_HYST) { ++ if (current_diff > 0) { ++ /* Increase current */ ++ permille = current_diff * 1000 / requested_current; ++ permille /= 2; ++ err = lipocharge_increase_current(c, permille); ++ if (err) ++ return err; ++ } else { ++ /* Decrease current */ ++ permille = (-current_diff) * 1000 / requested_current; ++ permille /= 2; ++ err = lipocharge_decrease_current(c, permille); ++ if (err) ++ return err; ++ } ++ } ++ break; ++ case LIPO_SECOND_STAGE: /* Constant voltage */ ++ //TODO ++ break; ++ } ++ ++ return 0; +} Index: linux-2.6.37/drivers/cbus/lipocharge.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 -+++ linux-2.6.37/drivers/cbus/lipocharge.h 2011-02-06 12:36:29.800830614 +0100 -@@ -0,0 +1,55 @@ ++++ linux-2.6.37/drivers/cbus/lipocharge.h 2011-02-07 20:07:29.669098631 +0100 +@@ -0,0 +1,60 @@ +#ifndef LIPOCHARGE_H_ +#define LIPOCHARGE_H_ + -+#include -+#include ++#include ++#include + + -+#define LIPORATE(a,b) (((a) * 1000) + (b)) -+#define LIPORATE_1C LIPORATE(1,0) /* 1C */ -+#define LIPORATE_p8C LIPORATE(0,8) /* 0.8C */ ++#define LIPORATE(a,b) (((a) * 1000) + ((b) * 100)) +#define LIPORATE_p6C LIPORATE(0,6) /* 0.6C */ + ++enum lipocharge_state { ++ LIPO_IDLE, /* Not charging */ ++ LIPO_FIRST_STAGE, /* Charging: constant current */ ++ LIPO_SECOND_STAGE, /* Charging: constant voltage */ ++}; ++ +/** struct lipocharge - A generic LIPO charger + * + * @capacity: Battery capacity in mAh. + * @rate: Charge rate. + * @top_voltage: Fully charged voltage, in mV. ++ * @duty_cycle_max: Max value for duty_cycle. + * -+ * @set_charge_current: Set the charge current, in mA. -+ * @get_charge_current: Get the battery current, in signed mA. -+ * @get_voltage: Get the battery voltage, in mV. -+ * -+ * @finished: Charging finished. ++ * @set_charge_current: Set the charge current PWM duty cycle. + * @emergency: Something went wrong. Force shutdown. + */ +struct lipocharge { + unsigned int capacity; + unsigned int rate; + unsigned int top_voltage; ++ unsigned int duty_cycle_max; + -+ int (*set_charge_current)(struct lipocharge *c, unsigned int ma); -+ int (*get_charge_current)(struct lipocharge *c, int *ma); -+ int (*get_voltage)(struct lipocharge *c, unsigned int *mv); -+ -+ void (*finished)(struct lipocharge *c); ++ int (*set_current_pwm)(struct lipocharge *c, unsigned int duty_cycle); + void (*emergency)(struct lipocharge *c); + + /* internal */ -+ spinlock_t lock; -+ struct timer_list timer; -+ bool charging; ++ struct device *dev; ++ enum lipocharge_state state; ++ unsigned int active_duty_cycle; ++ ++ //TODO implement timer to cut power after maximum charge time. +}; + -+void lipocharge_init(struct lipocharge *c); ++void lipocharge_init(struct lipocharge *c, struct device *dev); +void lipocharge_exit(struct lipocharge *c); + +int lipocharge_start(struct lipocharge *c); +void lipocharge_stop(struct lipocharge *c); + ++int lipocharge_update_state(struct lipocharge *c, ++ unsigned int voltage_mV, ++ int current_mA, ++ unsigned int temp_K); ++ +static inline bool lipocharge_is_charging(struct lipocharge *c) +{ -+ return c->charging; ++ return (c->state != LIPO_IDLE); +} + +#endif /* LIPOCHARGE_H_ */ Index: linux-2.6.37/drivers/cbus/tahvo.h =================================================================== ---- linux-2.6.37.orig/drivers/cbus/tahvo.h 2011-02-06 00:24:48.494004810 +0100 -+++ linux-2.6.37/drivers/cbus/tahvo.h 2011-02-06 00:24:48.551008149 +0100 +--- linux-2.6.37.orig/drivers/cbus/tahvo.h 2011-02-06 14:05:49.830387588 +0100 ++++ linux-2.6.37/drivers/cbus/tahvo.h 2011-02-06 16:22:25.902331536 +0100 @@ -30,17 +30,28 @@ #define TAHVO_REG_IDR 0x01 /* Interrupt ID */ #define TAHVO_REG_IDSR 0x02 /* Interrupt status */ @@ -1226,8 +1574,8 @@ Index: linux-2.6.37/drivers/cbus/tahvo.h #define TAHVO_REG_USBR 0x06 /* USB control */ +#define TAHVO_REG_CHGCTL 0x08 /* Charge control register */ +#define TAHVO_REG_CHGCTL_EN 0x0001 /* Global charge enable */ -+#define TAHVO_REG_CHGCTL_PWMOVR 0x0004 /* PWM override. Force charge PWM to 100% duty cycle. */ -+#define TAHVO_REG_CHGCTL_UNK8 0x0008 /* XXX: Unknown. Written on init. */ ++#define TAHVO_REG_CHGCTL_PWMOVR 0x0004 /* PWM override. Force charge PWM to 0%/100% duty cycle. */ ++#define TAHVO_REG_CHGCTL_PWMOVRZERO 0x0008 /* If set, PWM override is 0% (If unset -> 100%) */ +#define TAHVO_REG_CHGCTL_CURMEAS 0x0040 /* Enable battery current measurement. */ +#define TAHVO_REG_CHGCTL_CURTIMRST 0x0080 /* Current measure timer reset. */ +#define TAHVO_REG_BATCURRTIMER 0x0c /* Battery current measure timer (8-bit) */ @@ -1249,8 +1597,8 @@ Index: linux-2.6.37/drivers/cbus/tahvo.h void tahvo_free_irq(int id); Index: linux-2.6.37/drivers/cbus/tahvo.c =================================================================== ---- linux-2.6.37.orig/drivers/cbus/tahvo.c 2011-02-06 00:24:48.494004810 +0100 -+++ linux-2.6.37/drivers/cbus/tahvo.c 2011-02-06 00:24:48.552008207 +0100 +--- linux-2.6.37.orig/drivers/cbus/tahvo.c 2011-02-06 14:05:49.830387588 +0100 ++++ linux-2.6.37/drivers/cbus/tahvo.c 2011-02-06 14:05:49.886395793 +0100 @@ -85,10 +85,10 @@ * * This function writes a value to the specified register @@ -1264,3 +1612,40 @@ Index: linux-2.6.37/drivers/cbus/tahvo.c } /** +Index: linux-2.6.37/drivers/cbus/cbus.c +=================================================================== +--- linux-2.6.37.orig/drivers/cbus/cbus.c 2011-02-08 17:34:34.988926130 +0100 ++++ linux-2.6.37/drivers/cbus/cbus.c 2011-02-08 17:38:16.980407594 +0100 +@@ -31,6 +31,7 @@ + #include + #include + #include ++#include + + #include + #include +@@ -301,6 +302,13 @@ + } + module_exit(cbus_bus_exit); + ++void cbus_emergency(void) ++{ ++ machine_power_off(); ++ panic("cbus: Failed to halt machine in emergency state\n"); ++} ++EXPORT_SYMBOL(cbus_emergency); ++ + MODULE_DESCRIPTION("CBUS serial protocol"); + MODULE_LICENSE("GPL"); + MODULE_AUTHOR("Juha Yrjölä"); +Index: linux-2.6.37/drivers/cbus/cbus.h +=================================================================== +--- linux-2.6.37.orig/drivers/cbus/cbus.h 2011-02-08 17:36:40.172074049 +0100 ++++ linux-2.6.37/drivers/cbus/cbus.h 2011-02-08 17:41:32.680647478 +0100 +@@ -33,4 +33,6 @@ + extern int cbus_read_reg(struct cbus_host *host, int dev, int reg); + extern int cbus_write_reg(struct cbus_host *host, int dev, int reg, u16 val); + ++NORET_TYPE void cbus_emergency(void) ATTRIB_NORET; ++ + #endif /* __DRIVERS_CBUS_CBUS_H */ -- 2.30.2