1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 28c0984e5SSebastian Reichel /* 38c0984e5SSebastian Reichel * Battery measurement code for WM97xx 48c0984e5SSebastian Reichel * 58c0984e5SSebastian Reichel * based on tosa_battery.c 68c0984e5SSebastian Reichel * 78c0984e5SSebastian Reichel * Copyright (C) 2008 Marek Vasut <marek.vasut@gmail.com> 88c0984e5SSebastian Reichel */ 98c0984e5SSebastian Reichel 108c0984e5SSebastian Reichel #include <linux/init.h> 118c0984e5SSebastian Reichel #include <linux/kernel.h> 128c0984e5SSebastian Reichel #include <linux/module.h> 138c0984e5SSebastian Reichel #include <linux/platform_device.h> 148c0984e5SSebastian Reichel #include <linux/power_supply.h> 158c0984e5SSebastian Reichel #include <linux/wm97xx.h> 168c0984e5SSebastian Reichel #include <linux/spinlock.h> 178c0984e5SSebastian Reichel #include <linux/interrupt.h> 18cb6d6918SLinus Walleij #include <linux/gpio/consumer.h> 198c0984e5SSebastian Reichel #include <linux/irq.h> 208c0984e5SSebastian Reichel #include <linux/slab.h> 218c0984e5SSebastian Reichel 228c0984e5SSebastian Reichel static struct work_struct bat_work; 23cb6d6918SLinus Walleij static struct gpio_desc *charge_gpiod; 248c0984e5SSebastian Reichel static DEFINE_MUTEX(work_lock); 258c0984e5SSebastian Reichel static int bat_status = POWER_SUPPLY_STATUS_UNKNOWN; 268c0984e5SSebastian Reichel static enum power_supply_property *prop; 278c0984e5SSebastian Reichel 288c0984e5SSebastian Reichel static unsigned long wm97xx_read_bat(struct power_supply *bat_ps) 298c0984e5SSebastian Reichel { 306480af49SRobert Jarzmik struct wm97xx_batt_pdata *pdata = power_supply_get_drvdata(bat_ps); 318c0984e5SSebastian Reichel 328c0984e5SSebastian Reichel return wm97xx_read_aux_adc(dev_get_drvdata(bat_ps->dev.parent), 338c0984e5SSebastian Reichel pdata->batt_aux) * pdata->batt_mult / 348c0984e5SSebastian Reichel pdata->batt_div; 358c0984e5SSebastian Reichel } 368c0984e5SSebastian Reichel 378c0984e5SSebastian Reichel static unsigned long wm97xx_read_temp(struct power_supply *bat_ps) 388c0984e5SSebastian Reichel { 396480af49SRobert Jarzmik struct wm97xx_batt_pdata *pdata = power_supply_get_drvdata(bat_ps); 408c0984e5SSebastian Reichel 418c0984e5SSebastian Reichel return wm97xx_read_aux_adc(dev_get_drvdata(bat_ps->dev.parent), 428c0984e5SSebastian Reichel pdata->temp_aux) * pdata->temp_mult / 438c0984e5SSebastian Reichel pdata->temp_div; 448c0984e5SSebastian Reichel } 458c0984e5SSebastian Reichel 468c0984e5SSebastian Reichel static int wm97xx_bat_get_property(struct power_supply *bat_ps, 478c0984e5SSebastian Reichel enum power_supply_property psp, 488c0984e5SSebastian Reichel union power_supply_propval *val) 498c0984e5SSebastian Reichel { 506480af49SRobert Jarzmik struct wm97xx_batt_pdata *pdata = power_supply_get_drvdata(bat_ps); 518c0984e5SSebastian Reichel 528c0984e5SSebastian Reichel switch (psp) { 538c0984e5SSebastian Reichel case POWER_SUPPLY_PROP_STATUS: 548c0984e5SSebastian Reichel val->intval = bat_status; 558c0984e5SSebastian Reichel break; 568c0984e5SSebastian Reichel case POWER_SUPPLY_PROP_TECHNOLOGY: 578c0984e5SSebastian Reichel val->intval = pdata->batt_tech; 588c0984e5SSebastian Reichel break; 598c0984e5SSebastian Reichel case POWER_SUPPLY_PROP_VOLTAGE_NOW: 608c0984e5SSebastian Reichel if (pdata->batt_aux >= 0) 618c0984e5SSebastian Reichel val->intval = wm97xx_read_bat(bat_ps); 628c0984e5SSebastian Reichel else 638c0984e5SSebastian Reichel return -EINVAL; 648c0984e5SSebastian Reichel break; 658c0984e5SSebastian Reichel case POWER_SUPPLY_PROP_TEMP: 668c0984e5SSebastian Reichel if (pdata->temp_aux >= 0) 678c0984e5SSebastian Reichel val->intval = wm97xx_read_temp(bat_ps); 688c0984e5SSebastian Reichel else 698c0984e5SSebastian Reichel return -EINVAL; 708c0984e5SSebastian Reichel break; 718c0984e5SSebastian Reichel case POWER_SUPPLY_PROP_VOLTAGE_MAX: 728c0984e5SSebastian Reichel if (pdata->max_voltage >= 0) 738c0984e5SSebastian Reichel val->intval = pdata->max_voltage; 748c0984e5SSebastian Reichel else 758c0984e5SSebastian Reichel return -EINVAL; 768c0984e5SSebastian Reichel break; 778c0984e5SSebastian Reichel case POWER_SUPPLY_PROP_VOLTAGE_MIN: 788c0984e5SSebastian Reichel if (pdata->min_voltage >= 0) 798c0984e5SSebastian Reichel val->intval = pdata->min_voltage; 808c0984e5SSebastian Reichel else 818c0984e5SSebastian Reichel return -EINVAL; 828c0984e5SSebastian Reichel break; 838c0984e5SSebastian Reichel case POWER_SUPPLY_PROP_PRESENT: 848c0984e5SSebastian Reichel val->intval = 1; 858c0984e5SSebastian Reichel break; 868c0984e5SSebastian Reichel default: 878c0984e5SSebastian Reichel return -EINVAL; 888c0984e5SSebastian Reichel } 898c0984e5SSebastian Reichel return 0; 908c0984e5SSebastian Reichel } 918c0984e5SSebastian Reichel 928c0984e5SSebastian Reichel static void wm97xx_bat_external_power_changed(struct power_supply *bat_ps) 938c0984e5SSebastian Reichel { 948c0984e5SSebastian Reichel schedule_work(&bat_work); 958c0984e5SSebastian Reichel } 968c0984e5SSebastian Reichel 978c0984e5SSebastian Reichel static void wm97xx_bat_update(struct power_supply *bat_ps) 988c0984e5SSebastian Reichel { 998c0984e5SSebastian Reichel int old_status = bat_status; 1008c0984e5SSebastian Reichel 1018c0984e5SSebastian Reichel mutex_lock(&work_lock); 1028c0984e5SSebastian Reichel 103cb6d6918SLinus Walleij bat_status = (charge_gpiod) ? 104cb6d6918SLinus Walleij (gpiod_get_value(charge_gpiod) ? 1058c0984e5SSebastian Reichel POWER_SUPPLY_STATUS_DISCHARGING : 1068c0984e5SSebastian Reichel POWER_SUPPLY_STATUS_CHARGING) : 1078c0984e5SSebastian Reichel POWER_SUPPLY_STATUS_UNKNOWN; 1088c0984e5SSebastian Reichel 1098c0984e5SSebastian Reichel if (old_status != bat_status) { 1108c0984e5SSebastian Reichel pr_debug("%s: %i -> %i\n", bat_ps->desc->name, old_status, 1118c0984e5SSebastian Reichel bat_status); 1128c0984e5SSebastian Reichel power_supply_changed(bat_ps); 1138c0984e5SSebastian Reichel } 1148c0984e5SSebastian Reichel 1158c0984e5SSebastian Reichel mutex_unlock(&work_lock); 1168c0984e5SSebastian Reichel } 1178c0984e5SSebastian Reichel 1188c0984e5SSebastian Reichel static struct power_supply *bat_psy; 1198c0984e5SSebastian Reichel static struct power_supply_desc bat_psy_desc = { 1208c0984e5SSebastian Reichel .type = POWER_SUPPLY_TYPE_BATTERY, 1218c0984e5SSebastian Reichel .get_property = wm97xx_bat_get_property, 1228c0984e5SSebastian Reichel .external_power_changed = wm97xx_bat_external_power_changed, 1238c0984e5SSebastian Reichel .use_for_apm = 1, 1248c0984e5SSebastian Reichel }; 1258c0984e5SSebastian Reichel 1268c0984e5SSebastian Reichel static void wm97xx_bat_work(struct work_struct *work) 1278c0984e5SSebastian Reichel { 1288c0984e5SSebastian Reichel wm97xx_bat_update(bat_psy); 1298c0984e5SSebastian Reichel } 1308c0984e5SSebastian Reichel 1318c0984e5SSebastian Reichel static irqreturn_t wm97xx_chrg_irq(int irq, void *data) 1328c0984e5SSebastian Reichel { 1338c0984e5SSebastian Reichel schedule_work(&bat_work); 1348c0984e5SSebastian Reichel return IRQ_HANDLED; 1358c0984e5SSebastian Reichel } 1368c0984e5SSebastian Reichel 1378c0984e5SSebastian Reichel #ifdef CONFIG_PM 1388c0984e5SSebastian Reichel static int wm97xx_bat_suspend(struct device *dev) 1398c0984e5SSebastian Reichel { 1408c0984e5SSebastian Reichel flush_work(&bat_work); 1418c0984e5SSebastian Reichel return 0; 1428c0984e5SSebastian Reichel } 1438c0984e5SSebastian Reichel 1448c0984e5SSebastian Reichel static int wm97xx_bat_resume(struct device *dev) 1458c0984e5SSebastian Reichel { 1468c0984e5SSebastian Reichel schedule_work(&bat_work); 1478c0984e5SSebastian Reichel return 0; 1488c0984e5SSebastian Reichel } 1498c0984e5SSebastian Reichel 1508c0984e5SSebastian Reichel static const struct dev_pm_ops wm97xx_bat_pm_ops = { 1518c0984e5SSebastian Reichel .suspend = wm97xx_bat_suspend, 1528c0984e5SSebastian Reichel .resume = wm97xx_bat_resume, 1538c0984e5SSebastian Reichel }; 1548c0984e5SSebastian Reichel #endif 1558c0984e5SSebastian Reichel 1568c0984e5SSebastian Reichel static int wm97xx_bat_probe(struct platform_device *dev) 1578c0984e5SSebastian Reichel { 1588c0984e5SSebastian Reichel int ret = 0; 1598c0984e5SSebastian Reichel int props = 1; /* POWER_SUPPLY_PROP_PRESENT */ 1608c0984e5SSebastian Reichel int i = 0; 1616480af49SRobert Jarzmik struct wm97xx_batt_pdata *pdata = dev->dev.platform_data; 1626480af49SRobert Jarzmik struct power_supply_config cfg = {}; 1638c0984e5SSebastian Reichel 1646480af49SRobert Jarzmik if (!pdata) { 1658c0984e5SSebastian Reichel dev_err(&dev->dev, "No platform data supplied\n"); 1668c0984e5SSebastian Reichel return -EINVAL; 1678c0984e5SSebastian Reichel } 1688c0984e5SSebastian Reichel 1696480af49SRobert Jarzmik cfg.drv_data = pdata; 1708c0984e5SSebastian Reichel 1718c0984e5SSebastian Reichel if (dev->id != -1) 1728c0984e5SSebastian Reichel return -EINVAL; 1738c0984e5SSebastian Reichel 174cb6d6918SLinus Walleij charge_gpiod = devm_gpiod_get_optional(&dev->dev, NULL, GPIOD_IN); 175cb6d6918SLinus Walleij if (IS_ERR(charge_gpiod)) 176cb6d6918SLinus Walleij return dev_err_probe(&dev->dev, 177cb6d6918SLinus Walleij PTR_ERR(charge_gpiod), 178cb6d6918SLinus Walleij "failed to get charge GPIO\n"); 179cb6d6918SLinus Walleij if (charge_gpiod) { 180cb6d6918SLinus Walleij gpiod_set_consumer_name(charge_gpiod, "BATT CHRG"); 181cb6d6918SLinus Walleij ret = request_irq(gpiod_to_irq(charge_gpiod), 1828c0984e5SSebastian Reichel wm97xx_chrg_irq, 0, 1838c0984e5SSebastian Reichel "AC Detect", dev); 1848c0984e5SSebastian Reichel if (ret) 185cb6d6918SLinus Walleij return dev_err_probe(&dev->dev, ret, 186cb6d6918SLinus Walleij "failed to request GPIO irq\n"); 1878c0984e5SSebastian Reichel props++; /* POWER_SUPPLY_PROP_STATUS */ 1888c0984e5SSebastian Reichel } 1898c0984e5SSebastian Reichel 1908c0984e5SSebastian Reichel if (pdata->batt_tech >= 0) 1918c0984e5SSebastian Reichel props++; /* POWER_SUPPLY_PROP_TECHNOLOGY */ 1928c0984e5SSebastian Reichel if (pdata->temp_aux >= 0) 1938c0984e5SSebastian Reichel props++; /* POWER_SUPPLY_PROP_TEMP */ 1948c0984e5SSebastian Reichel if (pdata->batt_aux >= 0) 1958c0984e5SSebastian Reichel props++; /* POWER_SUPPLY_PROP_VOLTAGE_NOW */ 1968c0984e5SSebastian Reichel if (pdata->max_voltage >= 0) 1978c0984e5SSebastian Reichel props++; /* POWER_SUPPLY_PROP_VOLTAGE_MAX */ 1988c0984e5SSebastian Reichel if (pdata->min_voltage >= 0) 1998c0984e5SSebastian Reichel props++; /* POWER_SUPPLY_PROP_VOLTAGE_MIN */ 2008c0984e5SSebastian Reichel 2016396bb22SKees Cook prop = kcalloc(props, sizeof(*prop), GFP_KERNEL); 2028c0984e5SSebastian Reichel if (!prop) { 2038c0984e5SSebastian Reichel ret = -ENOMEM; 2048c0984e5SSebastian Reichel goto err3; 2058c0984e5SSebastian Reichel } 2068c0984e5SSebastian Reichel 2078c0984e5SSebastian Reichel prop[i++] = POWER_SUPPLY_PROP_PRESENT; 208cb6d6918SLinus Walleij if (charge_gpiod) 2098c0984e5SSebastian Reichel prop[i++] = POWER_SUPPLY_PROP_STATUS; 2108c0984e5SSebastian Reichel if (pdata->batt_tech >= 0) 2118c0984e5SSebastian Reichel prop[i++] = POWER_SUPPLY_PROP_TECHNOLOGY; 2128c0984e5SSebastian Reichel if (pdata->temp_aux >= 0) 2138c0984e5SSebastian Reichel prop[i++] = POWER_SUPPLY_PROP_TEMP; 2148c0984e5SSebastian Reichel if (pdata->batt_aux >= 0) 2158c0984e5SSebastian Reichel prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_NOW; 2168c0984e5SSebastian Reichel if (pdata->max_voltage >= 0) 2178c0984e5SSebastian Reichel prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MAX; 2188c0984e5SSebastian Reichel if (pdata->min_voltage >= 0) 2198c0984e5SSebastian Reichel prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MIN; 2208c0984e5SSebastian Reichel 2218c0984e5SSebastian Reichel INIT_WORK(&bat_work, wm97xx_bat_work); 2228c0984e5SSebastian Reichel 2238c0984e5SSebastian Reichel if (!pdata->batt_name) { 2248c0984e5SSebastian Reichel dev_info(&dev->dev, "Please consider setting proper battery " 2258c0984e5SSebastian Reichel "name in platform definition file, falling " 2268c0984e5SSebastian Reichel "back to name \"wm97xx-batt\"\n"); 2278c0984e5SSebastian Reichel bat_psy_desc.name = "wm97xx-batt"; 2288c0984e5SSebastian Reichel } else 2298c0984e5SSebastian Reichel bat_psy_desc.name = pdata->batt_name; 2308c0984e5SSebastian Reichel 2318c0984e5SSebastian Reichel bat_psy_desc.properties = prop; 2328c0984e5SSebastian Reichel bat_psy_desc.num_properties = props; 2338c0984e5SSebastian Reichel 2346480af49SRobert Jarzmik bat_psy = power_supply_register(&dev->dev, &bat_psy_desc, &cfg); 2358c0984e5SSebastian Reichel if (!IS_ERR(bat_psy)) { 2368c0984e5SSebastian Reichel schedule_work(&bat_work); 2378c0984e5SSebastian Reichel } else { 2388c0984e5SSebastian Reichel ret = PTR_ERR(bat_psy); 2398c0984e5SSebastian Reichel goto err4; 2408c0984e5SSebastian Reichel } 2418c0984e5SSebastian Reichel 2428c0984e5SSebastian Reichel return 0; 2438c0984e5SSebastian Reichel err4: 2448c0984e5SSebastian Reichel kfree(prop); 2458c0984e5SSebastian Reichel err3: 246cb6d6918SLinus Walleij if (charge_gpiod) 247cb6d6918SLinus Walleij free_irq(gpiod_to_irq(charge_gpiod), dev); 2488c0984e5SSebastian Reichel return ret; 2498c0984e5SSebastian Reichel } 2508c0984e5SSebastian Reichel 251*6f9fb8afSUwe Kleine-König static void wm97xx_bat_remove(struct platform_device *dev) 2528c0984e5SSebastian Reichel { 253cb6d6918SLinus Walleij if (charge_gpiod) 254cb6d6918SLinus Walleij free_irq(gpiod_to_irq(charge_gpiod), dev); 2558c0984e5SSebastian Reichel cancel_work_sync(&bat_work); 2568c0984e5SSebastian Reichel power_supply_unregister(bat_psy); 2578c0984e5SSebastian Reichel kfree(prop); 2588c0984e5SSebastian Reichel } 2598c0984e5SSebastian Reichel 2608c0984e5SSebastian Reichel static struct platform_driver wm97xx_bat_driver = { 2618c0984e5SSebastian Reichel .driver = { 2628c0984e5SSebastian Reichel .name = "wm97xx-battery", 2638c0984e5SSebastian Reichel #ifdef CONFIG_PM 2648c0984e5SSebastian Reichel .pm = &wm97xx_bat_pm_ops, 2658c0984e5SSebastian Reichel #endif 2668c0984e5SSebastian Reichel }, 2678c0984e5SSebastian Reichel .probe = wm97xx_bat_probe, 268*6f9fb8afSUwe Kleine-König .remove_new = wm97xx_bat_remove, 2698c0984e5SSebastian Reichel }; 2708c0984e5SSebastian Reichel 2718c0984e5SSebastian Reichel module_platform_driver(wm97xx_bat_driver); 2728c0984e5SSebastian Reichel 2738c0984e5SSebastian Reichel MODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>"); 2748c0984e5SSebastian Reichel MODULE_DESCRIPTION("WM97xx battery driver"); 275