linux/drivers/power/supply/tosa_battery.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * Battery and Power Management code for the Sharp SL-6000x
   4 *
   5 * Copyright (c) 2005 Dirk Opfer
   6 * Copyright (c) 2008 Dmitry Baryshkov
   7 */
   8#include <linux/kernel.h>
   9#include <linux/module.h>
  10#include <linux/power_supply.h>
  11#include <linux/wm97xx.h>
  12#include <linux/delay.h>
  13#include <linux/spinlock.h>
  14#include <linux/interrupt.h>
  15#include <linux/gpio.h>
  16
  17#include <asm/mach-types.h>
  18#include <mach/tosa.h>
  19
  20static DEFINE_MUTEX(bat_lock); /* protects gpio pins */
  21static struct work_struct bat_work;
  22
  23struct tosa_bat {
  24        int status;
  25        struct power_supply *psy;
  26        int full_chrg;
  27
  28        struct mutex work_lock; /* protects data */
  29
  30        bool (*is_present)(struct tosa_bat *bat);
  31        int gpio_full;
  32        int gpio_charge_off;
  33
  34        int technology;
  35
  36        int gpio_bat;
  37        int adc_bat;
  38        int adc_bat_divider;
  39        int bat_max;
  40        int bat_min;
  41
  42        int gpio_temp;
  43        int adc_temp;
  44        int adc_temp_divider;
  45};
  46
  47static struct tosa_bat tosa_bat_main;
  48static struct tosa_bat tosa_bat_jacket;
  49
  50static unsigned long tosa_read_bat(struct tosa_bat *bat)
  51{
  52        unsigned long value = 0;
  53
  54        if (bat->gpio_bat < 0 || bat->adc_bat < 0)
  55                return 0;
  56
  57        mutex_lock(&bat_lock);
  58        gpio_set_value(bat->gpio_bat, 1);
  59        msleep(5);
  60        value = wm97xx_read_aux_adc(dev_get_drvdata(bat->psy->dev.parent),
  61                        bat->adc_bat);
  62        gpio_set_value(bat->gpio_bat, 0);
  63        mutex_unlock(&bat_lock);
  64
  65        value = value * 1000000 / bat->adc_bat_divider;
  66
  67        return value;
  68}
  69
  70static unsigned long tosa_read_temp(struct tosa_bat *bat)
  71{
  72        unsigned long value = 0;
  73
  74        if (bat->gpio_temp < 0 || bat->adc_temp < 0)
  75                return 0;
  76
  77        mutex_lock(&bat_lock);
  78        gpio_set_value(bat->gpio_temp, 1);
  79        msleep(5);
  80        value = wm97xx_read_aux_adc(dev_get_drvdata(bat->psy->dev.parent),
  81                        bat->adc_temp);
  82        gpio_set_value(bat->gpio_temp, 0);
  83        mutex_unlock(&bat_lock);
  84
  85        value = value * 10000 / bat->adc_temp_divider;
  86
  87        return value;
  88}
  89
  90static int tosa_bat_get_property(struct power_supply *psy,
  91                            enum power_supply_property psp,
  92                            union power_supply_propval *val)
  93{
  94        int ret = 0;
  95        struct tosa_bat *bat = power_supply_get_drvdata(psy);
  96
  97        if (bat->is_present && !bat->is_present(bat)
  98                        && psp != POWER_SUPPLY_PROP_PRESENT) {
  99                return -ENODEV;
 100        }
 101
 102        switch (psp) {
 103        case POWER_SUPPLY_PROP_STATUS:
 104                val->intval = bat->status;
 105                break;
 106        case POWER_SUPPLY_PROP_TECHNOLOGY:
 107                val->intval = bat->technology;
 108                break;
 109        case POWER_SUPPLY_PROP_VOLTAGE_NOW:
 110                val->intval = tosa_read_bat(bat);
 111                break;
 112        case POWER_SUPPLY_PROP_VOLTAGE_MAX:
 113                if (bat->full_chrg == -1)
 114                        val->intval = bat->bat_max;
 115                else
 116                        val->intval = bat->full_chrg;
 117                break;
 118        case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
 119                val->intval = bat->bat_max;
 120                break;
 121        case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
 122                val->intval = bat->bat_min;
 123                break;
 124        case POWER_SUPPLY_PROP_TEMP:
 125                val->intval = tosa_read_temp(bat);
 126                break;
 127        case POWER_SUPPLY_PROP_PRESENT:
 128                val->intval = bat->is_present ? bat->is_present(bat) : 1;
 129                break;
 130        default:
 131                ret = -EINVAL;
 132                break;
 133        }
 134        return ret;
 135}
 136
 137static bool tosa_jacket_bat_is_present(struct tosa_bat *bat)
 138{
 139        return gpio_get_value(TOSA_GPIO_JACKET_DETECT) == 0;
 140}
 141
 142static void tosa_bat_external_power_changed(struct power_supply *psy)
 143{
 144        schedule_work(&bat_work);
 145}
 146
 147static irqreturn_t tosa_bat_gpio_isr(int irq, void *data)
 148{
 149        pr_info("tosa_bat_gpio irq\n");
 150        schedule_work(&bat_work);
 151        return IRQ_HANDLED;
 152}
 153
 154static void tosa_bat_update(struct tosa_bat *bat)
 155{
 156        int old;
 157        struct power_supply *psy = bat->psy;
 158
 159        mutex_lock(&bat->work_lock);
 160
 161        old = bat->status;
 162
 163        if (bat->is_present && !bat->is_present(bat)) {
 164                printk(KERN_NOTICE "%s not present\n", psy->desc->name);
 165                bat->status = POWER_SUPPLY_STATUS_UNKNOWN;
 166                bat->full_chrg = -1;
 167        } else if (power_supply_am_i_supplied(psy)) {
 168                if (bat->status == POWER_SUPPLY_STATUS_DISCHARGING) {
 169                        gpio_set_value(bat->gpio_charge_off, 0);
 170                        mdelay(15);
 171                }
 172
 173                if (gpio_get_value(bat->gpio_full)) {
 174                        if (old == POWER_SUPPLY_STATUS_CHARGING ||
 175                                        bat->full_chrg == -1)
 176                                bat->full_chrg = tosa_read_bat(bat);
 177
 178                        gpio_set_value(bat->gpio_charge_off, 1);
 179                        bat->status = POWER_SUPPLY_STATUS_FULL;
 180                } else {
 181                        gpio_set_value(bat->gpio_charge_off, 0);
 182                        bat->status = POWER_SUPPLY_STATUS_CHARGING;
 183                }
 184        } else {
 185                gpio_set_value(bat->gpio_charge_off, 1);
 186                bat->status = POWER_SUPPLY_STATUS_DISCHARGING;
 187        }
 188
 189        if (old != bat->status)
 190                power_supply_changed(psy);
 191
 192        mutex_unlock(&bat->work_lock);
 193}
 194
 195static void tosa_bat_work(struct work_struct *work)
 196{
 197        tosa_bat_update(&tosa_bat_main);
 198        tosa_bat_update(&tosa_bat_jacket);
 199}
 200
 201
 202static enum power_supply_property tosa_bat_main_props[] = {
 203        POWER_SUPPLY_PROP_STATUS,
 204        POWER_SUPPLY_PROP_TECHNOLOGY,
 205        POWER_SUPPLY_PROP_VOLTAGE_NOW,
 206        POWER_SUPPLY_PROP_VOLTAGE_MAX,
 207        POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
 208        POWER_SUPPLY_PROP_TEMP,
 209        POWER_SUPPLY_PROP_PRESENT,
 210};
 211
 212static enum power_supply_property tosa_bat_bu_props[] = {
 213        POWER_SUPPLY_PROP_STATUS,
 214        POWER_SUPPLY_PROP_TECHNOLOGY,
 215        POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
 216        POWER_SUPPLY_PROP_VOLTAGE_NOW,
 217        POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
 218        POWER_SUPPLY_PROP_PRESENT,
 219};
 220
 221static const struct power_supply_desc tosa_bat_main_desc = {
 222        .name           = "main-battery",
 223        .type           = POWER_SUPPLY_TYPE_BATTERY,
 224        .properties     = tosa_bat_main_props,
 225        .num_properties = ARRAY_SIZE(tosa_bat_main_props),
 226        .get_property   = tosa_bat_get_property,
 227        .external_power_changed = tosa_bat_external_power_changed,
 228        .use_for_apm    = 1,
 229};
 230
 231static const struct power_supply_desc tosa_bat_jacket_desc = {
 232        .name           = "jacket-battery",
 233        .type           = POWER_SUPPLY_TYPE_BATTERY,
 234        .properties     = tosa_bat_main_props,
 235        .num_properties = ARRAY_SIZE(tosa_bat_main_props),
 236        .get_property   = tosa_bat_get_property,
 237        .external_power_changed = tosa_bat_external_power_changed,
 238};
 239
 240static const struct power_supply_desc tosa_bat_bu_desc = {
 241        .name           = "backup-battery",
 242        .type           = POWER_SUPPLY_TYPE_BATTERY,
 243        .properties     = tosa_bat_bu_props,
 244        .num_properties = ARRAY_SIZE(tosa_bat_bu_props),
 245        .get_property   = tosa_bat_get_property,
 246        .external_power_changed = tosa_bat_external_power_changed,
 247};
 248
 249static struct tosa_bat tosa_bat_main = {
 250        .status = POWER_SUPPLY_STATUS_DISCHARGING,
 251        .full_chrg = -1,
 252        .psy = NULL,
 253
 254        .gpio_full = TOSA_GPIO_BAT0_CRG,
 255        .gpio_charge_off = TOSA_GPIO_CHARGE_OFF,
 256
 257        .technology = POWER_SUPPLY_TECHNOLOGY_LIPO,
 258
 259        .gpio_bat = TOSA_GPIO_BAT0_V_ON,
 260        .adc_bat = WM97XX_AUX_ID3,
 261        .adc_bat_divider = 414,
 262        .bat_max = 4310000,
 263        .bat_min = 1551 * 1000000 / 414,
 264
 265        .gpio_temp = TOSA_GPIO_BAT1_TH_ON,
 266        .adc_temp = WM97XX_AUX_ID2,
 267        .adc_temp_divider = 10000,
 268};
 269
 270static struct tosa_bat tosa_bat_jacket = {
 271        .status = POWER_SUPPLY_STATUS_DISCHARGING,
 272        .full_chrg = -1,
 273        .psy = NULL,
 274
 275        .is_present = tosa_jacket_bat_is_present,
 276        .gpio_full = TOSA_GPIO_BAT1_CRG,
 277        .gpio_charge_off = TOSA_GPIO_CHARGE_OFF_JC,
 278
 279        .technology = POWER_SUPPLY_TECHNOLOGY_LIPO,
 280
 281        .gpio_bat = TOSA_GPIO_BAT1_V_ON,
 282        .adc_bat = WM97XX_AUX_ID3,
 283        .adc_bat_divider = 414,
 284        .bat_max = 4310000,
 285        .bat_min = 1551 * 1000000 / 414,
 286
 287        .gpio_temp = TOSA_GPIO_BAT0_TH_ON,
 288        .adc_temp = WM97XX_AUX_ID2,
 289        .adc_temp_divider = 10000,
 290};
 291
 292static struct tosa_bat tosa_bat_bu = {
 293        .status = POWER_SUPPLY_STATUS_UNKNOWN,
 294        .full_chrg = -1,
 295        .psy = NULL,
 296
 297        .gpio_full = -1,
 298        .gpio_charge_off = -1,
 299
 300        .technology = POWER_SUPPLY_TECHNOLOGY_LiMn,
 301
 302        .gpio_bat = TOSA_GPIO_BU_CHRG_ON,
 303        .adc_bat = WM97XX_AUX_ID4,
 304        .adc_bat_divider = 1266,
 305
 306        .gpio_temp = -1,
 307        .adc_temp = -1,
 308        .adc_temp_divider = -1,
 309};
 310
 311static struct gpio tosa_bat_gpios[] = {
 312        { TOSA_GPIO_CHARGE_OFF,    GPIOF_OUT_INIT_HIGH, "main charge off" },
 313        { TOSA_GPIO_CHARGE_OFF_JC, GPIOF_OUT_INIT_HIGH, "jacket charge off" },
 314        { TOSA_GPIO_BAT_SW_ON,     GPIOF_OUT_INIT_LOW,  "battery switch" },
 315        { TOSA_GPIO_BAT0_V_ON,     GPIOF_OUT_INIT_LOW,  "main battery" },
 316        { TOSA_GPIO_BAT1_V_ON,     GPIOF_OUT_INIT_LOW,  "jacket battery" },
 317        { TOSA_GPIO_BAT1_TH_ON,    GPIOF_OUT_INIT_LOW,  "main battery temp" },
 318        { TOSA_GPIO_BAT0_TH_ON,    GPIOF_OUT_INIT_LOW,  "jacket battery temp" },
 319        { TOSA_GPIO_BU_CHRG_ON,    GPIOF_OUT_INIT_LOW,  "backup battery" },
 320        { TOSA_GPIO_BAT0_CRG,      GPIOF_IN,            "main battery full" },
 321        { TOSA_GPIO_BAT1_CRG,      GPIOF_IN,            "jacket battery full" },
 322        { TOSA_GPIO_BAT0_LOW,      GPIOF_IN,            "main battery low" },
 323        { TOSA_GPIO_BAT1_LOW,      GPIOF_IN,            "jacket battery low" },
 324        { TOSA_GPIO_JACKET_DETECT, GPIOF_IN,            "jacket detect" },
 325};
 326
 327#ifdef CONFIG_PM
 328static int tosa_bat_suspend(struct platform_device *dev, pm_message_t state)
 329{
 330        /* flush all pending status updates */
 331        flush_work(&bat_work);
 332        return 0;
 333}
 334
 335static int tosa_bat_resume(struct platform_device *dev)
 336{
 337        /* things may have changed while we were away */
 338        schedule_work(&bat_work);
 339        return 0;
 340}
 341#else
 342#define tosa_bat_suspend NULL
 343#define tosa_bat_resume NULL
 344#endif
 345
 346static int tosa_bat_probe(struct platform_device *dev)
 347{
 348        int ret;
 349        struct power_supply_config main_psy_cfg = {},
 350                                   jacket_psy_cfg = {},
 351                                   bu_psy_cfg = {};
 352
 353        if (!machine_is_tosa())
 354                return -ENODEV;
 355
 356        ret = gpio_request_array(tosa_bat_gpios, ARRAY_SIZE(tosa_bat_gpios));
 357        if (ret)
 358                return ret;
 359
 360        mutex_init(&tosa_bat_main.work_lock);
 361        mutex_init(&tosa_bat_jacket.work_lock);
 362
 363        INIT_WORK(&bat_work, tosa_bat_work);
 364
 365        main_psy_cfg.drv_data = &tosa_bat_main;
 366        tosa_bat_main.psy = power_supply_register(&dev->dev,
 367                                                  &tosa_bat_main_desc,
 368                                                  &main_psy_cfg);
 369        if (IS_ERR(tosa_bat_main.psy)) {
 370                ret = PTR_ERR(tosa_bat_main.psy);
 371                goto err_psy_reg_main;
 372        }
 373
 374        jacket_psy_cfg.drv_data = &tosa_bat_jacket;
 375        tosa_bat_jacket.psy = power_supply_register(&dev->dev,
 376                                                    &tosa_bat_jacket_desc,
 377                                                    &jacket_psy_cfg);
 378        if (IS_ERR(tosa_bat_jacket.psy)) {
 379                ret = PTR_ERR(tosa_bat_jacket.psy);
 380                goto err_psy_reg_jacket;
 381        }
 382
 383        bu_psy_cfg.drv_data = &tosa_bat_bu;
 384        tosa_bat_bu.psy = power_supply_register(&dev->dev, &tosa_bat_bu_desc,
 385                                                &bu_psy_cfg);
 386        if (IS_ERR(tosa_bat_bu.psy)) {
 387                ret = PTR_ERR(tosa_bat_bu.psy);
 388                goto err_psy_reg_bu;
 389        }
 390
 391        ret = request_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG),
 392                                tosa_bat_gpio_isr,
 393                                IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
 394                                "main full", &tosa_bat_main);
 395        if (ret)
 396                goto err_req_main;
 397
 398        ret = request_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG),
 399                                tosa_bat_gpio_isr,
 400                                IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
 401                                "jacket full", &tosa_bat_jacket);
 402        if (ret)
 403                goto err_req_jacket;
 404
 405        ret = request_irq(gpio_to_irq(TOSA_GPIO_JACKET_DETECT),
 406                                tosa_bat_gpio_isr,
 407                                IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
 408                                "jacket detect", &tosa_bat_jacket);
 409        if (!ret) {
 410                schedule_work(&bat_work);
 411                return 0;
 412        }
 413
 414        free_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG), &tosa_bat_jacket);
 415err_req_jacket:
 416        free_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG), &tosa_bat_main);
 417err_req_main:
 418        power_supply_unregister(tosa_bat_bu.psy);
 419err_psy_reg_bu:
 420        power_supply_unregister(tosa_bat_jacket.psy);
 421err_psy_reg_jacket:
 422        power_supply_unregister(tosa_bat_main.psy);
 423err_psy_reg_main:
 424
 425        /* see comment in tosa_bat_remove */
 426        cancel_work_sync(&bat_work);
 427
 428        gpio_free_array(tosa_bat_gpios, ARRAY_SIZE(tosa_bat_gpios));
 429        return ret;
 430}
 431
 432static int tosa_bat_remove(struct platform_device *dev)
 433{
 434        free_irq(gpio_to_irq(TOSA_GPIO_JACKET_DETECT), &tosa_bat_jacket);
 435        free_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG), &tosa_bat_jacket);
 436        free_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG), &tosa_bat_main);
 437
 438        power_supply_unregister(tosa_bat_bu.psy);
 439        power_supply_unregister(tosa_bat_jacket.psy);
 440        power_supply_unregister(tosa_bat_main.psy);
 441
 442        /*
 443         * Now cancel the bat_work.  We won't get any more schedules,
 444         * since all sources (isr and external_power_changed) are
 445         * unregistered now.
 446         */
 447        cancel_work_sync(&bat_work);
 448        gpio_free_array(tosa_bat_gpios, ARRAY_SIZE(tosa_bat_gpios));
 449        return 0;
 450}
 451
 452static struct platform_driver tosa_bat_driver = {
 453        .driver.name    = "wm97xx-battery",
 454        .driver.owner   = THIS_MODULE,
 455        .probe          = tosa_bat_probe,
 456        .remove         = tosa_bat_remove,
 457        .suspend        = tosa_bat_suspend,
 458        .resume         = tosa_bat_resume,
 459};
 460
 461module_platform_driver(tosa_bat_driver);
 462
 463MODULE_LICENSE("GPL");
 464MODULE_AUTHOR("Dmitry Baryshkov");
 465MODULE_DESCRIPTION("Tosa battery driver");
 466MODULE_ALIAS("platform:wm97xx-battery");
 467