486 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Battery and Power Management code for the Sharp SL-5x004 *5 * Copyright (C) 2009 Thomas Kunze6 *7 * based on tosa_battery.c8 */9#include <linux/kernel.h>10#include <linux/module.h>11#include <linux/power_supply.h>12#include <linux/delay.h>13#include <linux/spinlock.h>14#include <linux/interrupt.h>15#include <linux/gpio/driver.h>16#include <linux/gpio/machine.h>17#include <linux/gpio/consumer.h>18#include <linux/mfd/ucb1x00.h>19 20#include <asm/mach/sharpsl_param.h>21#include <asm/mach-types.h>22#include <mach/collie.h>23 24static DEFINE_MUTEX(bat_lock); /* protects gpio pins */25static struct work_struct bat_work;26static struct ucb1x00 *ucb;27 28struct collie_bat {29 int status;30 struct power_supply *psy;31 int full_chrg;32 33 struct mutex work_lock; /* protects data */34 35 bool (*is_present)(struct collie_bat *bat);36 struct gpio_desc *gpio_full;37 struct gpio_desc *gpio_charge_on;38 39 int technology;40 41 struct gpio_desc *gpio_bat;42 int adc_bat;43 int adc_bat_divider;44 int bat_max;45 int bat_min;46 47 struct gpio_desc *gpio_temp;48 int adc_temp;49 int adc_temp_divider;50};51 52static struct collie_bat collie_bat_main;53 54static unsigned long collie_read_bat(struct collie_bat *bat)55{56 unsigned long value = 0;57 58 if (!bat->gpio_bat || bat->adc_bat < 0)59 return 0;60 mutex_lock(&bat_lock);61 gpiod_set_value(bat->gpio_bat, 1);62 msleep(5);63 ucb1x00_adc_enable(ucb);64 value = ucb1x00_adc_read(ucb, bat->adc_bat, UCB_SYNC);65 ucb1x00_adc_disable(ucb);66 gpiod_set_value(bat->gpio_bat, 0);67 mutex_unlock(&bat_lock);68 value = value * 1000000 / bat->adc_bat_divider;69 70 return value;71}72 73static unsigned long collie_read_temp(struct collie_bat *bat)74{75 unsigned long value = 0;76 if (!bat->gpio_temp || bat->adc_temp < 0)77 return 0;78 79 mutex_lock(&bat_lock);80 gpiod_set_value(bat->gpio_temp, 1);81 msleep(5);82 ucb1x00_adc_enable(ucb);83 value = ucb1x00_adc_read(ucb, bat->adc_temp, UCB_SYNC);84 ucb1x00_adc_disable(ucb);85 gpiod_set_value(bat->gpio_temp, 0);86 mutex_unlock(&bat_lock);87 88 value = value * 10000 / bat->adc_temp_divider;89 90 return value;91}92 93static int collie_bat_get_property(struct power_supply *psy,94 enum power_supply_property psp,95 union power_supply_propval *val)96{97 int ret = 0;98 struct collie_bat *bat = power_supply_get_drvdata(psy);99 100 if (bat->is_present && !bat->is_present(bat)101 && psp != POWER_SUPPLY_PROP_PRESENT) {102 return -ENODEV;103 }104 105 switch (psp) {106 case POWER_SUPPLY_PROP_STATUS:107 val->intval = bat->status;108 break;109 case POWER_SUPPLY_PROP_TECHNOLOGY:110 val->intval = bat->technology;111 break;112 case POWER_SUPPLY_PROP_VOLTAGE_NOW:113 val->intval = collie_read_bat(bat);114 break;115 case POWER_SUPPLY_PROP_VOLTAGE_MAX:116 if (bat->full_chrg == -1)117 val->intval = bat->bat_max;118 else119 val->intval = bat->full_chrg;120 break;121 case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:122 val->intval = bat->bat_max;123 break;124 case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:125 val->intval = bat->bat_min;126 break;127 case POWER_SUPPLY_PROP_TEMP:128 val->intval = collie_read_temp(bat);129 break;130 case POWER_SUPPLY_PROP_PRESENT:131 val->intval = bat->is_present ? bat->is_present(bat) : 1;132 break;133 default:134 ret = -EINVAL;135 break;136 }137 return ret;138}139 140static void collie_bat_external_power_changed(struct power_supply *psy)141{142 schedule_work(&bat_work);143}144 145static irqreturn_t collie_bat_gpio_isr(int irq, void *data)146{147 pr_info("collie_bat_gpio irq\n");148 schedule_work(&bat_work);149 return IRQ_HANDLED;150}151 152static void collie_bat_update(struct collie_bat *bat)153{154 int old;155 struct power_supply *psy = bat->psy;156 157 mutex_lock(&bat->work_lock);158 159 old = bat->status;160 161 if (bat->is_present && !bat->is_present(bat)) {162 printk(KERN_NOTICE "%s not present\n", psy->desc->name);163 bat->status = POWER_SUPPLY_STATUS_UNKNOWN;164 bat->full_chrg = -1;165 } else if (power_supply_am_i_supplied(psy)) {166 if (bat->status == POWER_SUPPLY_STATUS_DISCHARGING) {167 gpiod_set_value(bat->gpio_charge_on, 1);168 mdelay(15);169 }170 171 if (gpiod_get_value(bat->gpio_full)) {172 if (old == POWER_SUPPLY_STATUS_CHARGING ||173 bat->full_chrg == -1)174 bat->full_chrg = collie_read_bat(bat);175 176 gpiod_set_value(bat->gpio_charge_on, 0);177 bat->status = POWER_SUPPLY_STATUS_FULL;178 } else {179 gpiod_set_value(bat->gpio_charge_on, 1);180 bat->status = POWER_SUPPLY_STATUS_CHARGING;181 }182 } else {183 gpiod_set_value(bat->gpio_charge_on, 0);184 bat->status = POWER_SUPPLY_STATUS_DISCHARGING;185 }186 187 if (old != bat->status)188 power_supply_changed(psy);189 190 mutex_unlock(&bat->work_lock);191}192 193static void collie_bat_work(struct work_struct *work)194{195 collie_bat_update(&collie_bat_main);196}197 198 199static enum power_supply_property collie_bat_main_props[] = {200 POWER_SUPPLY_PROP_STATUS,201 POWER_SUPPLY_PROP_TECHNOLOGY,202 POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,203 POWER_SUPPLY_PROP_VOLTAGE_NOW,204 POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,205 POWER_SUPPLY_PROP_VOLTAGE_MAX,206 POWER_SUPPLY_PROP_PRESENT,207 POWER_SUPPLY_PROP_TEMP,208};209 210static enum power_supply_property collie_bat_bu_props[] = {211 POWER_SUPPLY_PROP_STATUS,212 POWER_SUPPLY_PROP_TECHNOLOGY,213 POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,214 POWER_SUPPLY_PROP_VOLTAGE_NOW,215 POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,216 POWER_SUPPLY_PROP_VOLTAGE_MAX,217 POWER_SUPPLY_PROP_PRESENT,218};219 220static const struct power_supply_desc collie_bat_main_desc = {221 .name = "main-battery",222 .type = POWER_SUPPLY_TYPE_BATTERY,223 .properties = collie_bat_main_props,224 .num_properties = ARRAY_SIZE(collie_bat_main_props),225 .get_property = collie_bat_get_property,226 .external_power_changed = collie_bat_external_power_changed,227 .use_for_apm = 1,228};229 230static struct collie_bat collie_bat_main = {231 .status = POWER_SUPPLY_STATUS_DISCHARGING,232 .full_chrg = -1,233 .psy = NULL,234 235 .gpio_full = NULL,236 .gpio_charge_on = NULL,237 238 .technology = POWER_SUPPLY_TECHNOLOGY_LIPO,239 240 .gpio_bat = NULL,241 .adc_bat = UCB_ADC_INP_AD1,242 .adc_bat_divider = 155,243 .bat_max = 4310000,244 .bat_min = 1551 * 1000000 / 414,245 246 .gpio_temp = NULL,247 .adc_temp = UCB_ADC_INP_AD0,248 .adc_temp_divider = 10000,249};250 251static const struct power_supply_desc collie_bat_bu_desc = {252 .name = "backup-battery",253 .type = POWER_SUPPLY_TYPE_BATTERY,254 .properties = collie_bat_bu_props,255 .num_properties = ARRAY_SIZE(collie_bat_bu_props),256 .get_property = collie_bat_get_property,257 .external_power_changed = collie_bat_external_power_changed,258};259 260static struct collie_bat collie_bat_bu = {261 .status = POWER_SUPPLY_STATUS_UNKNOWN,262 .full_chrg = -1,263 .psy = NULL,264 265 .gpio_full = NULL,266 .gpio_charge_on = NULL,267 268 .technology = POWER_SUPPLY_TECHNOLOGY_LiMn,269 270 .gpio_bat = NULL,271 .adc_bat = UCB_ADC_INP_AD1,272 .adc_bat_divider = 155,273 .bat_max = 3000000,274 .bat_min = 1900000,275 276 .gpio_temp = NULL,277 .adc_temp = -1,278 .adc_temp_divider = -1,279};280 281/* Obtained but unused GPIO */282static struct gpio_desc *collie_mbat_low;283 284#ifdef CONFIG_PM285static int wakeup_enabled;286 287static int collie_bat_suspend(struct ucb1x00_dev *dev)288{289 /* flush all pending status updates */290 flush_work(&bat_work);291 292 if (device_may_wakeup(&dev->ucb->dev) &&293 collie_bat_main.status == POWER_SUPPLY_STATUS_CHARGING)294 wakeup_enabled = !enable_irq_wake(gpiod_to_irq(collie_bat_main.gpio_full));295 else296 wakeup_enabled = 0;297 298 return 0;299}300 301static int collie_bat_resume(struct ucb1x00_dev *dev)302{303 if (wakeup_enabled)304 disable_irq_wake(gpiod_to_irq(collie_bat_main.gpio_full));305 306 /* things may have changed while we were away */307 schedule_work(&bat_work);308 return 0;309}310#else311#define collie_bat_suspend NULL312#define collie_bat_resume NULL313#endif314 315static int collie_bat_probe(struct ucb1x00_dev *dev)316{317 int ret;318 struct power_supply_config psy_main_cfg = {}, psy_bu_cfg = {};319 struct gpio_chip *gc = &dev->ucb->gpio;320 321 if (!machine_is_collie())322 return -ENODEV;323 324 ucb = dev->ucb;325 326 /* Obtain all the main battery GPIOs */327 collie_bat_main.gpio_full = gpiod_get(&dev->ucb->dev,328 "main battery full",329 GPIOD_IN);330 if (IS_ERR(collie_bat_main.gpio_full))331 return PTR_ERR(collie_bat_main.gpio_full);332 333 collie_mbat_low = gpiod_get(&dev->ucb->dev,334 "main battery low",335 GPIOD_IN);336 if (IS_ERR(collie_mbat_low)) {337 ret = PTR_ERR(collie_mbat_low);338 goto err_put_gpio_full;339 }340 341 collie_bat_main.gpio_charge_on = gpiod_get(&dev->ucb->dev,342 "main charge on",343 GPIOD_OUT_LOW);344 if (IS_ERR(collie_bat_main.gpio_charge_on)) {345 ret = PTR_ERR(collie_bat_main.gpio_charge_on);346 goto err_put_mbat_low;347 }348 349 /* COLLIE_GPIO_MBAT_ON = GPIO 7 on the UCB (TC35143) */350 collie_bat_main.gpio_bat = gpiochip_request_own_desc(gc,351 7,352 "main battery",353 GPIO_ACTIVE_HIGH,354 GPIOD_OUT_LOW);355 if (IS_ERR(collie_bat_main.gpio_bat)) {356 ret = PTR_ERR(collie_bat_main.gpio_bat);357 goto err_put_gpio_charge_on;358 }359 360 /* COLLIE_GPIO_TMP_ON = GPIO 9 on the UCB (TC35143) */361 collie_bat_main.gpio_temp = gpiochip_request_own_desc(gc,362 9,363 "main battery temp",364 GPIO_ACTIVE_HIGH,365 GPIOD_OUT_LOW);366 if (IS_ERR(collie_bat_main.gpio_temp)) {367 ret = PTR_ERR(collie_bat_main.gpio_temp);368 goto err_free_gpio_bat;369 }370 371 /*372 * Obtain the backup battery COLLIE_GPIO_BBAT_ON which is373 * GPIO 8 on the UCB (TC35143)374 */375 collie_bat_bu.gpio_bat = gpiochip_request_own_desc(gc,376 8,377 "backup battery",378 GPIO_ACTIVE_HIGH,379 GPIOD_OUT_LOW);380 if (IS_ERR(collie_bat_bu.gpio_bat)) {381 ret = PTR_ERR(collie_bat_bu.gpio_bat);382 goto err_free_gpio_temp;383 }384 385 mutex_init(&collie_bat_main.work_lock);386 387 INIT_WORK(&bat_work, collie_bat_work);388 389 psy_main_cfg.drv_data = &collie_bat_main;390 collie_bat_main.psy = power_supply_register(&dev->ucb->dev,391 &collie_bat_main_desc,392 &psy_main_cfg);393 if (IS_ERR(collie_bat_main.psy)) {394 ret = PTR_ERR(collie_bat_main.psy);395 goto err_psy_reg_main;396 }397 398 psy_bu_cfg.drv_data = &collie_bat_bu;399 collie_bat_bu.psy = power_supply_register(&dev->ucb->dev,400 &collie_bat_bu_desc,401 &psy_bu_cfg);402 if (IS_ERR(collie_bat_bu.psy)) {403 ret = PTR_ERR(collie_bat_bu.psy);404 goto err_psy_reg_bu;405 }406 407 ret = request_irq(gpiod_to_irq(collie_bat_main.gpio_full),408 collie_bat_gpio_isr,409 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,410 "main full", &collie_bat_main);411 if (ret)412 goto err_irq;413 414 device_init_wakeup(&ucb->dev, 1);415 schedule_work(&bat_work);416 417 return 0;418 419err_irq:420 power_supply_unregister(collie_bat_bu.psy);421err_psy_reg_bu:422 power_supply_unregister(collie_bat_main.psy);423err_psy_reg_main:424 /* see comment in collie_bat_remove */425 cancel_work_sync(&bat_work);426 gpiochip_free_own_desc(collie_bat_bu.gpio_bat);427err_free_gpio_temp:428 gpiochip_free_own_desc(collie_bat_main.gpio_temp);429err_free_gpio_bat:430 gpiochip_free_own_desc(collie_bat_main.gpio_bat);431err_put_gpio_charge_on:432 gpiod_put(collie_bat_main.gpio_charge_on);433err_put_mbat_low:434 gpiod_put(collie_mbat_low);435err_put_gpio_full:436 gpiod_put(collie_bat_main.gpio_full);437 438 return ret;439}440 441static void collie_bat_remove(struct ucb1x00_dev *dev)442{443 free_irq(gpiod_to_irq(collie_bat_main.gpio_full), &collie_bat_main);444 power_supply_unregister(collie_bat_bu.psy);445 power_supply_unregister(collie_bat_main.psy);446 447 /* These are obtained from the machine */448 gpiod_put(collie_bat_main.gpio_full);449 gpiod_put(collie_mbat_low);450 gpiod_put(collie_bat_main.gpio_charge_on);451 /* These are directly from the UCB so let's free them */452 gpiochip_free_own_desc(collie_bat_main.gpio_bat);453 gpiochip_free_own_desc(collie_bat_main.gpio_temp);454 gpiochip_free_own_desc(collie_bat_bu.gpio_bat);455 /*456 * Now cancel the bat_work. We won't get any more schedules,457 * since all sources (isr and external_power_changed) are458 * unregistered now.459 */460 cancel_work_sync(&bat_work);461}462 463static struct ucb1x00_driver collie_bat_driver = {464 .add = collie_bat_probe,465 .remove = collie_bat_remove,466 .suspend = collie_bat_suspend,467 .resume = collie_bat_resume,468};469 470static int __init collie_bat_init(void)471{472 return ucb1x00_register_driver(&collie_bat_driver);473}474 475static void __exit collie_bat_exit(void)476{477 ucb1x00_unregister_driver(&collie_bat_driver);478}479 480module_init(collie_bat_init);481module_exit(collie_bat_exit);482 483MODULE_LICENSE("GPL");484MODULE_AUTHOR("Thomas Kunze");485MODULE_DESCRIPTION("Collie battery driver");486