source: trunk/target/linux/omap24xx/patches-2.6.35/900-n810-battery-management.patch @ 22760

Last change on this file since 22760 was 22760, checked in by mb, 6 years ago

n810: Add basic battery management driver

File size: 12.6 KB
  • drivers/cbus/Kconfig

    ---
     arch/arm/mach-omap2/board-n8x0.c |   13 +
     drivers/cbus/Kconfig             |   12 +
     drivers/cbus/Makefile            |    1 
     drivers/cbus/n810bm.c            |  396 +++++++++++++++++++++++++++++++++++++++
     drivers/cbus/retu.c              |    4 
     drivers/cbus/retu.h              |    2 
     6 files changed, 425 insertions(+), 3 deletions(-)
    
    old new config CBUS_RETU_HEADSET 
    9494          to Retu/Vilma. Detection state and events are exposed through 
    9595          sysfs. 
    9696 
     97config N810BM 
     98        depends on CBUS_RETU && CBUS_TAHVO 
     99        tristate "Nokia n810 battery management" 
     100        ---help--- 
     101          Nokia n810 device battery management. 
     102 
     103          WARNING: This driver is based on reverse engineered information. 
     104          It is possibly dangerous to use this software. 
     105          Use this software at your own risk! 
     106 
     107          If unsure, say N. 
     108 
    97109endmenu 
  • drivers/cbus/Makefile

    old new obj-$(CONFIG_CBUS_RETU_WDT) += retu-wdt. 
    1212obj-$(CONFIG_CBUS_TAHVO_USER)   += tahvo-user.o 
    1313obj-$(CONFIG_CBUS_RETU_USER)    += retu-user.o 
    1414obj-$(CONFIG_CBUS_RETU_HEADSET) += retu-headset.o 
     15obj-$(CONFIG_N810BM)            += n810bm.o 
  • new file linux-2.6.35.3/drivers/cbus/n810bm.c

    - +  
     1/* 
     2 *   Nokia n810 battery management 
     3 * 
     4 *   WARNING: This driver is based on reverse engineered information. 
     5 *            It is possibly dangerous to use this software. 
     6 *            Use this software at your own risk! 
     7 * 
     8 *   Copyright (c) 2010 Michael Buesch <mb@bu3sch.de> 
     9 * 
     10 *   This program is free software; you can redistribute it and/or 
     11 *   modify it under the terms of the GNU General Public License 
     12 *   as published by the Free Software Foundation; either version 2 
     13 *   of the License, or (at your option) any later version. 
     14 * 
     15 *   This program is distributed in the hope that it will be useful, 
     16 *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
     17 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
     18 *   GNU General Public License for more details. 
     19 */ 
     20 
     21#include <linux/module.h> 
     22#include <linux/device.h> 
     23#include <linux/platform_device.h> 
     24#include <linux/slab.h> 
     25#include <linux/spinlock.h> 
     26#include <linux/timer.h> 
     27#include <linux/reboot.h> 
     28 
     29#include "retu.h" 
     30#include "tahvo.h" 
     31 
     32 
     33#define N810BM_CHECK_INTERVAL           (HZ * 5) 
     34#define N810BM_MIN_VOLTAGE_THRES        3400 /* Absolute minimum voltage threshold */ 
     35/* FIXME: Not sure about the actual value of the low threshold, yet. 
     36 * We must give userspace a chance to detect undervoltage and perform 
     37 * a clean shutdown, before we flip the big switch. */ 
     38 
     39 
     40/* Battery related retu ADC channels */ 
     41#define RETU_ADC_BSI            0x01 /* Battery Size Indicator */ 
     42#define RETU_ADC_BATTVOLT       0x08 /* Battery voltage measurement */ 
     43 
     44/* RETU_ADC_BSI 
     45 * The battery size indicator ADC measures the resistance between 
     46 * the battery BSI pin and ground. This is used to detect the battery 
     47 * capacity, as the BSI resistor is related to capacity. 
     48 * 
     49 * Manually measured lookup table. 
     50 * Hard to measure, thus not very accurate. 
     51 * 
     52 * Resistance  |  ADC value 
     53 * ======================== 
     54 * 120k        |  0x3AC 
     55 * 110k        |  0x37C 
     56 * 100k        |  0x351 
     57 *  90k        |  0x329 
     58 */ 
     59 
     60/* RETU_ADC_BATTVOLT 
     61 * Manually measured lookup table. 
     62 * Hard to measure, thus not very accurate. 
     63 * 
     64 * Voltage  |  ADC value 
     65 * ===================== 
     66 * 2.80V    |  0x037 
     67 * 2.90V    |  0x05E 
     68 * 3.00V    |  0x090 
     69 * 3.10V    |  0x0A4 
     70 * 3.20V    |  0x0CC 
     71 * 3.30V    |  0x0EF 
     72 * 3.40V    |  0x115 
     73 * 3.50V    |  0x136 
     74 * 3.60V    |  0x15C 
     75 * 3.70V    |  0x187 
     76 * 3.80V    |  0x1A5 
     77 * 3.90V    |  0x1C9 
     78 * 4.00V    |  0x1ED 
     79 * 4.10V    |  0x212 
     80 * 4.20V    |  0x236 
     81 */ 
     82 
     83 
     84enum n810bm_capacity { 
     85        N810BM_CAP_UNKNOWN      = 0, 
     86        N810BM_CAP_1500MAH      = 1500, /* 1500 mAh battery */ 
     87}; 
     88 
     89struct n810bm { 
     90        struct platform_device *pdev; 
     91        enum n810bm_capacity capacity; 
     92        struct timer_list check_timer; 
     93        spinlock_t lock; 
     94}; 
     95 
     96 
     97static NORET_TYPE void n810bm_emergency(struct n810bm *bm, const char *message) ATTRIB_NORET; 
     98static void n810bm_emergency(struct n810bm *bm, const char *message) 
     99{ 
     100        printk(KERN_EMERG "Nokia n810 battery management fatal fault: %s\n", message); 
     101        /* Force a hard shutdown. */ 
     102        machine_power_off(); 
     103        panic("n810bm: Failed to halt machine in emergency state\n"); 
     104} 
     105 
     106#if 0 
     107static u16 retu_read(struct n810bm *bm, unsigned int reg) 
     108{ 
     109        int ret; 
     110        unsigned long flags; 
     111 
     112        spin_lock_irqsave(&retu_lock, flags); 
     113        ret = retu_read_reg(reg); 
     114        spin_unlock_irqrestore(&retu_lock, flags); 
     115        if (ret < 0 || ret > 0xFFFF) 
     116                n810bm_emergency(bm, "retu_read"); 
     117 
     118        return ret; 
     119} 
     120#endif 
     121 
     122static void retu_maskset(struct n810bm *bm, unsigned int reg, u16 mask, u16 set) 
     123{ 
     124        int ret; 
     125        unsigned long flags; 
     126        u16 value; 
     127 
     128        spin_lock_irqsave(&retu_lock, flags); 
     129        if (~mask) { 
     130                ret = retu_read_reg(reg); 
     131                if (ret < 0 || ret > 0xFFFF) 
     132                        goto fatal_unlock; 
     133                value = ret; 
     134        } else 
     135                value = 0; 
     136        value &= ~mask; 
     137        value |= set; 
     138        ret = retu_write_reg(reg, value); 
     139        if (ret) 
     140                goto fatal_unlock; 
     141        spin_unlock_irqrestore(&retu_lock, flags); 
     142 
     143        return; 
     144 
     145fatal_unlock: 
     146        spin_unlock_irqrestore(&retu_lock, flags); 
     147        n810bm_emergency(bm, "retu_maskset"); 
     148} 
     149 
     150static inline void retu_write(struct n810bm *bm, unsigned int reg, u16 value) 
     151{ 
     152        return retu_maskset(bm, reg, 0xFFFF, value); 
     153} 
     154 
     155static int retu_adc_average(struct n810bm *bm, unsigned int chan, 
     156                            unsigned int nr_passes) 
     157{ 
     158        unsigned int i, value = 0; 
     159        int ret; 
     160 
     161        if (WARN_ON(!nr_passes)) 
     162                return 0; 
     163        for (i = 0; i < nr_passes; i++) { 
     164                ret = retu_read_adc(chan); 
     165                if (ret < 0) 
     166                        return ret; 
     167                value += ret; 
     168        } 
     169        value /= nr_passes; 
     170 
     171        return value; 
     172} 
     173 
     174/* Measure the battery voltage. Returns the value in mV (or negative value on error). */ 
     175static int n810bm_measure_batt_voltage(struct n810bm *bm) 
     176{ 
     177        int adc; 
     178        unsigned int mv; 
     179        const unsigned int scale = 1000; 
     180 
     181        adc = retu_adc_average(bm, RETU_ADC_BATTVOLT, 5); 
     182        if (adc < 0) 
     183                return adc; 
     184        if (adc <= 0x37) 
     185                return 2800; 
     186        mv = 2800 + ((adc - 0x37) * (((4200 - 2800) * scale) / (0x236 - 0x37))) / scale; 
     187 
     188        return mv; 
     189} 
     190 
     191/* Read the battery capacity via BSI pin. */ 
     192static enum n810bm_capacity n810bm_read_batt_capacity(struct n810bm *bm) 
     193{ 
     194        int adc; 
     195        const unsigned int hyst = 16; 
     196 
     197        adc = retu_adc_average(bm, RETU_ADC_BSI, 5); 
     198        if (adc < 0) 
     199                return N810BM_CAP_UNKNOWN; 
     200 
     201        if (adc >= 0x3AC - hyst && adc <= 0x3AC + hyst) 
     202                return N810BM_CAP_1500MAH; 
     203 
     204        return N810BM_CAP_UNKNOWN; 
     205} 
     206 
     207struct mv2p { 
     208        unsigned int mv; 
     209        unsigned int p; 
     210}; 
     211 
     212/* Convert a battery voltage (in mV) to percentage. */ 
     213static unsigned int n810bm_mvolt2percent(unsigned int mv) 
     214{ 
     215        /* FIXME: Create a correct table. */ 
     216        static const struct mv2p table[] = { 
     217                { .mv = 4200, .p = 100, }, 
     218                { .mv = 4150, .p = 90, }, 
     219                { .mv = 4100, .p = 80, }, 
     220                { .mv = 4050, .p = 70, }, 
     221                { .mv = 4000, .p = 60, }, 
     222                { .mv = 3950, .p = 50, }, 
     223                { .mv = 3900, .p = 40, }, 
     224                { .mv = 3850, .p = 30, }, 
     225                { .mv = 3800, .p = 20, }, 
     226                { .mv = 3750, .p = 10, }, 
     227                { .mv = 3700, .p = 0, }, 
     228        }; 
     229        const struct mv2p *e; 
     230        unsigned int i; 
     231 
     232        for (i = 0; i < ARRAY_SIZE(table); i++) { 
     233                e = &table[i]; 
     234                if (mv >= e->mv) 
     235                        return e->p; 
     236        } 
     237 
     238        return 0; 
     239} 
     240 
     241static void n810bm_check_timer(unsigned long data) 
     242{ 
     243        struct n810bm *bm = (struct n810bm *)data; 
     244        unsigned long flags; 
     245        int mv; 
     246 
     247        spin_lock_irqsave(&bm->lock, flags); 
     248 
     249        mv = n810bm_measure_batt_voltage(bm); 
     250        if (mv < 0) 
     251                n810bm_emergency(bm, "check timer: Failed to measure"); 
     252        if (mv < N810BM_MIN_VOLTAGE_THRES) 
     253                n810bm_emergency(bm, "check timer: Minimum voltage threshold reached"); 
     254 
     255        mod_timer(&bm->check_timer, round_jiffies(jiffies + N810BM_CHECK_INTERVAL)); 
     256        spin_unlock_irqrestore(&bm->lock, flags); 
     257 
     258        return; 
     259} 
     260 
     261static ssize_t n810bm_attr_charge_show(struct device *dev, 
     262                                       struct device_attribute *attr, 
     263                                       char *buf) 
     264{ 
     265        struct platform_device *pdev = to_platform_device(dev); 
     266        struct n810bm *bm = platform_get_drvdata(pdev); 
     267        int err = -ENODEV; 
     268        ssize_t count = 0; 
     269        int millivolt; 
     270 
     271        spin_lock_irq(&bm->lock); 
     272        millivolt = n810bm_measure_batt_voltage(bm); 
     273        if (millivolt >= 0) { 
     274                count = snprintf(buf, PAGE_SIZE, "%u\n", 
     275                                 n810bm_mvolt2percent(millivolt)); 
     276                err = 0; 
     277        } 
     278        spin_unlock_irq(&bm->lock); 
     279 
     280        return err ? err : count; 
     281} 
     282static DEVICE_ATTR(batt_charge, 0444, n810bm_attr_charge_show, NULL); 
     283 
     284static ssize_t n810bm_attr_capacity_show(struct device *dev, 
     285                                         struct device_attribute *attr, 
     286                                         char *buf) 
     287{ 
     288        struct platform_device *pdev = to_platform_device(dev); 
     289        struct n810bm *bm = platform_get_drvdata(pdev); 
     290        ssize_t count; 
     291 
     292        spin_lock_irq(&bm->lock); 
     293        count = snprintf(buf, PAGE_SIZE, "%u\n", 
     294                         (unsigned int)bm->capacity); 
     295        spin_unlock_irq(&bm->lock); 
     296 
     297        return count; 
     298} 
     299static DEVICE_ATTR(batt_capacity, 0444, n810bm_attr_capacity_show, NULL); 
     300 
     301static void n810bm_hw_exit(struct n810bm *bm) 
     302{ 
     303        retu_write(bm, RETU_REG_ADCSCR, 0); 
     304} 
     305 
     306static int n810bm_hw_init(struct n810bm *bm) 
     307{ 
     308        retu_write(bm, RETU_REG_ADCSCR, 0); 
     309 
     310        bm->capacity = n810bm_read_batt_capacity(bm); 
     311        if (bm->capacity == N810BM_CAP_UNKNOWN) { 
     312                dev_err(&bm->pdev->dev, "Unknown battery detected"); 
     313                return -ENODEV; 
     314        } 
     315        dev_info(&bm->pdev->dev, "Detected %u mAh battery\n", 
     316                 (unsigned int)bm->capacity); 
     317 
     318        return 0; 
     319} 
     320 
     321static int __devinit n810bm_probe(struct platform_device *pdev) 
     322{ 
     323        struct n810bm *bm; 
     324        int err; 
     325 
     326        bm = kzalloc(sizeof(*bm), GFP_KERNEL); 
     327        if (!bm) 
     328                return -ENOMEM; 
     329        bm->pdev = pdev; 
     330        platform_set_drvdata(pdev, bm); 
     331        spin_lock_init(&bm->lock); 
     332        setup_timer(&bm->check_timer, n810bm_check_timer, (unsigned long)bm); 
     333 
     334        err = n810bm_hw_init(bm); 
     335        if (err) 
     336                goto err_free; 
     337        err = device_create_file(&pdev->dev, &dev_attr_batt_charge); 
     338        if (err) 
     339                goto err_exit; 
     340        err = device_create_file(&pdev->dev, &dev_attr_batt_capacity); 
     341        if (err) 
     342                goto err_rem_charge; 
     343 
     344        mod_timer(&bm->check_timer, round_jiffies(jiffies + N810BM_CHECK_INTERVAL)); 
     345 
     346        dev_info(&pdev->dev, "Battery management initialized"); 
     347 
     348        return 0; 
     349 
     350err_rem_charge: 
     351        device_remove_file(&pdev->dev, &dev_attr_batt_charge); 
     352err_exit: 
     353        n810bm_hw_exit(bm); 
     354err_free: 
     355        kfree(bm); 
     356        platform_set_drvdata(pdev, NULL); 
     357        return err; 
     358} 
     359 
     360static int __devexit n810bm_remove(struct platform_device *pdev) 
     361{ 
     362        struct n810bm *bm = platform_get_drvdata(pdev); 
     363 
     364        del_timer_sync(&bm->check_timer); 
     365        device_remove_file(&pdev->dev, &dev_attr_batt_capacity); 
     366        device_remove_file(&pdev->dev, &dev_attr_batt_charge); 
     367        n810bm_hw_exit(bm); 
     368 
     369        kfree(bm); 
     370        platform_set_drvdata(pdev, NULL); 
     371 
     372        return 0; 
     373} 
     374 
     375static struct platform_driver n810bm_driver = { 
     376        .remove         = __devexit_p(n810bm_remove), 
     377        .driver         = { 
     378                .name   = "n810bm", 
     379        } 
     380}; 
     381 
     382static int __init n810bm_modinit(void) 
     383{ 
     384        return platform_driver_probe(&n810bm_driver, n810bm_probe); 
     385} 
     386module_init(n810bm_modinit); 
     387 
     388static void __exit n810bm_modexit(void) 
     389{ 
     390        platform_driver_unregister(&n810bm_driver); 
     391} 
     392module_exit(n810bm_modexit); 
     393 
     394MODULE_DESCRIPTION("Nokia n810 battery management"); 
     395MODULE_LICENSE("GPL"); 
     396MODULE_AUTHOR("Michael Buesch"); 
  • drivers/cbus/retu.c

    old new int retu_read_reg(int reg) 
    8585 * 
    8686 * This function writes a value to the specified register 
    8787 */ 
    88 void retu_write_reg(int reg, u16 val) 
     88int retu_write_reg(int reg, u16 val) 
    8989{ 
    9090        BUG_ON(!retu_initialized); 
    91         cbus_write_reg(cbus_host, RETU_ID, reg, val); 
     91        return cbus_write_reg(cbus_host, RETU_ID, reg, val); 
    9292} 
    9393 
    9494void retu_set_clear_reg_bits(int reg, u16 set, u16 clear) 
  • drivers/cbus/retu.h

    old new  
    5858#define MAX_RETU_IRQ_HANDLERS   16 
    5959 
    6060int retu_read_reg(int reg); 
    61 void retu_write_reg(int reg, u16 val); 
     61int retu_write_reg(int reg, u16 val); 
    6262void retu_set_clear_reg_bits(int reg, u16 set, u16 clear); 
    6363int retu_read_adc(int channel); 
    6464int retu_request_irq(int id, void *irq_handler, unsigned long arg, char *name); 
  • arch/arm/mach-omap2/board-n8x0.c

    old new extern void n8x0_blizzard_init(void); 
    875875#define n8x0_blizzard_init() 0 
    876876#endif 
    877877 
     878static struct platform_device n810_bm_device = { 
     879        .name           = "n810bm", 
     880        .id             = -1, 
     881}; 
     882 
     883static void __init n810_bm_init(void) 
     884{ 
     885        if (platform_device_register(&n810_bm_device)) 
     886                BUG(); 
     887} 
     888 
    878889static void __init n8x0_init_machine(void) 
    879890{ 
    880891        platform_device_register(&n8x0_cbus_device); 
    static void __init n8x0_init_machine(voi 
    901912        n8x0_onenand_init(); 
    902913        n8x0_mmc_init(); 
    903914        n8x0_usb_init(); 
     915 
     916        n810_bm_init(); 
    904917} 
    905918 
    906919MACHINE_START(NOKIA_N800, "Nokia N800") 
Note: See TracBrowser for help on using the repository browser.