12aae471dSThomas Gleixner // SPDX-License-Identifier: GPL-2.0 28c0984e5SSebastian Reichel /* 38c0984e5SSebastian Reichel * Power supply driver for the goldfish emulator 48c0984e5SSebastian Reichel * 58c0984e5SSebastian Reichel * Copyright (C) 2008 Google, Inc. 68c0984e5SSebastian Reichel * Copyright (C) 2012 Intel, Inc. 78c0984e5SSebastian Reichel * Copyright (C) 2013 Intel, Inc. 88c0984e5SSebastian Reichel * Author: Mike Lockwood <lockwood@android.com> 98c0984e5SSebastian Reichel */ 108c0984e5SSebastian Reichel 118c0984e5SSebastian Reichel #include <linux/module.h> 128c0984e5SSebastian Reichel #include <linux/err.h> 138c0984e5SSebastian Reichel #include <linux/platform_device.h> 148c0984e5SSebastian Reichel #include <linux/power_supply.h> 158c0984e5SSebastian Reichel #include <linux/types.h> 168c0984e5SSebastian Reichel #include <linux/pci.h> 178c0984e5SSebastian Reichel #include <linux/interrupt.h> 188c0984e5SSebastian Reichel #include <linux/io.h> 198c0984e5SSebastian Reichel #include <linux/acpi.h> 208c0984e5SSebastian Reichel 218c0984e5SSebastian Reichel struct goldfish_battery_data { 228c0984e5SSebastian Reichel void __iomem *reg_base; 238c0984e5SSebastian Reichel int irq; 248c0984e5SSebastian Reichel spinlock_t lock; 258c0984e5SSebastian Reichel 268c0984e5SSebastian Reichel struct power_supply *battery; 278c0984e5SSebastian Reichel struct power_supply *ac; 288c0984e5SSebastian Reichel }; 298c0984e5SSebastian Reichel 308c0984e5SSebastian Reichel #define GOLDFISH_BATTERY_READ(data, addr) \ 318c0984e5SSebastian Reichel (readl(data->reg_base + addr)) 328c0984e5SSebastian Reichel #define GOLDFISH_BATTERY_WRITE(data, addr, x) \ 338c0984e5SSebastian Reichel (writel(x, data->reg_base + addr)) 348c0984e5SSebastian Reichel 358c0984e5SSebastian Reichel enum { 368c0984e5SSebastian Reichel /* status register */ 378c0984e5SSebastian Reichel BATTERY_INT_STATUS = 0x00, 388c0984e5SSebastian Reichel /* set this to enable IRQ */ 398c0984e5SSebastian Reichel BATTERY_INT_ENABLE = 0x04, 408c0984e5SSebastian Reichel 418c0984e5SSebastian Reichel BATTERY_AC_ONLINE = 0x08, 428c0984e5SSebastian Reichel BATTERY_STATUS = 0x0C, 438c0984e5SSebastian Reichel BATTERY_HEALTH = 0x10, 448c0984e5SSebastian Reichel BATTERY_PRESENT = 0x14, 458c0984e5SSebastian Reichel BATTERY_CAPACITY = 0x18, 462a7b0a29SRoman Kiryanov BATTERY_VOLTAGE = 0x1C, 472a7b0a29SRoman Kiryanov BATTERY_TEMP = 0x20, 482a7b0a29SRoman Kiryanov BATTERY_CHARGE_COUNTER = 0x24, 492a7b0a29SRoman Kiryanov BATTERY_VOLTAGE_MAX = 0x28, 502a7b0a29SRoman Kiryanov BATTERY_CURRENT_MAX = 0x2C, 512a7b0a29SRoman Kiryanov BATTERY_CURRENT_NOW = 0x30, 522a7b0a29SRoman Kiryanov BATTERY_CURRENT_AVG = 0x34, 532a7b0a29SRoman Kiryanov BATTERY_CHARGE_FULL_UAH = 0x38, 542a7b0a29SRoman Kiryanov BATTERY_CYCLE_COUNT = 0x40, 558c0984e5SSebastian Reichel 568c0984e5SSebastian Reichel BATTERY_STATUS_CHANGED = 1U << 0, 578c0984e5SSebastian Reichel AC_STATUS_CHANGED = 1U << 1, 588c0984e5SSebastian Reichel BATTERY_INT_MASK = BATTERY_STATUS_CHANGED | AC_STATUS_CHANGED, 598c0984e5SSebastian Reichel }; 608c0984e5SSebastian Reichel 618c0984e5SSebastian Reichel 628c0984e5SSebastian Reichel static int goldfish_ac_get_property(struct power_supply *psy, 638c0984e5SSebastian Reichel enum power_supply_property psp, 648c0984e5SSebastian Reichel union power_supply_propval *val) 658c0984e5SSebastian Reichel { 668c0984e5SSebastian Reichel struct goldfish_battery_data *data = power_supply_get_drvdata(psy); 678c0984e5SSebastian Reichel int ret = 0; 688c0984e5SSebastian Reichel 698c0984e5SSebastian Reichel switch (psp) { 708c0984e5SSebastian Reichel case POWER_SUPPLY_PROP_ONLINE: 718c0984e5SSebastian Reichel val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_AC_ONLINE); 728c0984e5SSebastian Reichel break; 732a7b0a29SRoman Kiryanov case POWER_SUPPLY_PROP_VOLTAGE_MAX: 742a7b0a29SRoman Kiryanov val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_VOLTAGE_MAX); 752a7b0a29SRoman Kiryanov break; 762a7b0a29SRoman Kiryanov case POWER_SUPPLY_PROP_CURRENT_MAX: 772a7b0a29SRoman Kiryanov val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_CURRENT_MAX); 782a7b0a29SRoman Kiryanov break; 798c0984e5SSebastian Reichel default: 808c0984e5SSebastian Reichel ret = -EINVAL; 818c0984e5SSebastian Reichel break; 828c0984e5SSebastian Reichel } 838c0984e5SSebastian Reichel return ret; 848c0984e5SSebastian Reichel } 858c0984e5SSebastian Reichel 868c0984e5SSebastian Reichel static int goldfish_battery_get_property(struct power_supply *psy, 878c0984e5SSebastian Reichel enum power_supply_property psp, 888c0984e5SSebastian Reichel union power_supply_propval *val) 898c0984e5SSebastian Reichel { 908c0984e5SSebastian Reichel struct goldfish_battery_data *data = power_supply_get_drvdata(psy); 918c0984e5SSebastian Reichel int ret = 0; 928c0984e5SSebastian Reichel 938c0984e5SSebastian Reichel switch (psp) { 948c0984e5SSebastian Reichel case POWER_SUPPLY_PROP_STATUS: 958c0984e5SSebastian Reichel val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_STATUS); 968c0984e5SSebastian Reichel break; 978c0984e5SSebastian Reichel case POWER_SUPPLY_PROP_HEALTH: 988c0984e5SSebastian Reichel val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_HEALTH); 998c0984e5SSebastian Reichel break; 1008c0984e5SSebastian Reichel case POWER_SUPPLY_PROP_PRESENT: 1018c0984e5SSebastian Reichel val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_PRESENT); 1028c0984e5SSebastian Reichel break; 1038c0984e5SSebastian Reichel case POWER_SUPPLY_PROP_TECHNOLOGY: 1048c0984e5SSebastian Reichel val->intval = POWER_SUPPLY_TECHNOLOGY_LION; 1058c0984e5SSebastian Reichel break; 1068c0984e5SSebastian Reichel case POWER_SUPPLY_PROP_CAPACITY: 1078c0984e5SSebastian Reichel val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_CAPACITY); 1088c0984e5SSebastian Reichel break; 1092a7b0a29SRoman Kiryanov case POWER_SUPPLY_PROP_VOLTAGE_NOW: 1102a7b0a29SRoman Kiryanov val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_VOLTAGE); 1112a7b0a29SRoman Kiryanov break; 1122a7b0a29SRoman Kiryanov case POWER_SUPPLY_PROP_TEMP: 1132a7b0a29SRoman Kiryanov val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_TEMP); 1142a7b0a29SRoman Kiryanov break; 1152a7b0a29SRoman Kiryanov case POWER_SUPPLY_PROP_CHARGE_COUNTER: 1162a7b0a29SRoman Kiryanov val->intval = GOLDFISH_BATTERY_READ(data, 1172a7b0a29SRoman Kiryanov BATTERY_CHARGE_COUNTER); 1182a7b0a29SRoman Kiryanov break; 1192a7b0a29SRoman Kiryanov case POWER_SUPPLY_PROP_CURRENT_NOW: 1202a7b0a29SRoman Kiryanov val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_CURRENT_NOW); 1212a7b0a29SRoman Kiryanov break; 1222a7b0a29SRoman Kiryanov case POWER_SUPPLY_PROP_CURRENT_AVG: 1232a7b0a29SRoman Kiryanov val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_CURRENT_AVG); 1242a7b0a29SRoman Kiryanov break; 1252a7b0a29SRoman Kiryanov case POWER_SUPPLY_PROP_CHARGE_FULL: 1262a7b0a29SRoman Kiryanov val->intval = GOLDFISH_BATTERY_READ(data, 1272a7b0a29SRoman Kiryanov BATTERY_CHARGE_FULL_UAH); 1282a7b0a29SRoman Kiryanov break; 1292a7b0a29SRoman Kiryanov case POWER_SUPPLY_PROP_CYCLE_COUNT: 1302a7b0a29SRoman Kiryanov val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_CYCLE_COUNT); 1312a7b0a29SRoman Kiryanov break; 1328c0984e5SSebastian Reichel default: 1338c0984e5SSebastian Reichel ret = -EINVAL; 1348c0984e5SSebastian Reichel break; 1358c0984e5SSebastian Reichel } 1368c0984e5SSebastian Reichel 1378c0984e5SSebastian Reichel return ret; 1388c0984e5SSebastian Reichel } 1398c0984e5SSebastian Reichel 1408c0984e5SSebastian Reichel static enum power_supply_property goldfish_battery_props[] = { 1418c0984e5SSebastian Reichel POWER_SUPPLY_PROP_STATUS, 1428c0984e5SSebastian Reichel POWER_SUPPLY_PROP_HEALTH, 1438c0984e5SSebastian Reichel POWER_SUPPLY_PROP_PRESENT, 1448c0984e5SSebastian Reichel POWER_SUPPLY_PROP_TECHNOLOGY, 1458c0984e5SSebastian Reichel POWER_SUPPLY_PROP_CAPACITY, 1462a7b0a29SRoman Kiryanov POWER_SUPPLY_PROP_VOLTAGE_NOW, 1472a7b0a29SRoman Kiryanov POWER_SUPPLY_PROP_TEMP, 1482a7b0a29SRoman Kiryanov POWER_SUPPLY_PROP_CHARGE_COUNTER, 1492a7b0a29SRoman Kiryanov POWER_SUPPLY_PROP_CURRENT_NOW, 1502a7b0a29SRoman Kiryanov POWER_SUPPLY_PROP_CURRENT_AVG, 1512a7b0a29SRoman Kiryanov POWER_SUPPLY_PROP_CHARGE_FULL, 1522a7b0a29SRoman Kiryanov POWER_SUPPLY_PROP_CYCLE_COUNT, 1538c0984e5SSebastian Reichel }; 1548c0984e5SSebastian Reichel 1558c0984e5SSebastian Reichel static enum power_supply_property goldfish_ac_props[] = { 1568c0984e5SSebastian Reichel POWER_SUPPLY_PROP_ONLINE, 1572a7b0a29SRoman Kiryanov POWER_SUPPLY_PROP_VOLTAGE_MAX, 1582a7b0a29SRoman Kiryanov POWER_SUPPLY_PROP_CURRENT_MAX, 1598c0984e5SSebastian Reichel }; 1608c0984e5SSebastian Reichel 1618c0984e5SSebastian Reichel static irqreturn_t goldfish_battery_interrupt(int irq, void *dev_id) 1628c0984e5SSebastian Reichel { 1638c0984e5SSebastian Reichel unsigned long irq_flags; 1648c0984e5SSebastian Reichel struct goldfish_battery_data *data = dev_id; 1658c0984e5SSebastian Reichel uint32_t status; 1668c0984e5SSebastian Reichel 1678c0984e5SSebastian Reichel spin_lock_irqsave(&data->lock, irq_flags); 1688c0984e5SSebastian Reichel 1698c0984e5SSebastian Reichel /* read status flags, which will clear the interrupt */ 1708c0984e5SSebastian Reichel status = GOLDFISH_BATTERY_READ(data, BATTERY_INT_STATUS); 1718c0984e5SSebastian Reichel status &= BATTERY_INT_MASK; 1728c0984e5SSebastian Reichel 1738c0984e5SSebastian Reichel if (status & BATTERY_STATUS_CHANGED) 1748c0984e5SSebastian Reichel power_supply_changed(data->battery); 1758c0984e5SSebastian Reichel if (status & AC_STATUS_CHANGED) 1768c0984e5SSebastian Reichel power_supply_changed(data->ac); 1778c0984e5SSebastian Reichel 1788c0984e5SSebastian Reichel spin_unlock_irqrestore(&data->lock, irq_flags); 1798c0984e5SSebastian Reichel return status ? IRQ_HANDLED : IRQ_NONE; 1808c0984e5SSebastian Reichel } 1818c0984e5SSebastian Reichel 1828c0984e5SSebastian Reichel static const struct power_supply_desc battery_desc = { 1838c0984e5SSebastian Reichel .properties = goldfish_battery_props, 1848c0984e5SSebastian Reichel .num_properties = ARRAY_SIZE(goldfish_battery_props), 1858c0984e5SSebastian Reichel .get_property = goldfish_battery_get_property, 1868c0984e5SSebastian Reichel .name = "battery", 1878c0984e5SSebastian Reichel .type = POWER_SUPPLY_TYPE_BATTERY, 1888c0984e5SSebastian Reichel }; 1898c0984e5SSebastian Reichel 1908c0984e5SSebastian Reichel static const struct power_supply_desc ac_desc = { 1918c0984e5SSebastian Reichel .properties = goldfish_ac_props, 1928c0984e5SSebastian Reichel .num_properties = ARRAY_SIZE(goldfish_ac_props), 1938c0984e5SSebastian Reichel .get_property = goldfish_ac_get_property, 1948c0984e5SSebastian Reichel .name = "ac", 1958c0984e5SSebastian Reichel .type = POWER_SUPPLY_TYPE_MAINS, 1968c0984e5SSebastian Reichel }; 1978c0984e5SSebastian Reichel 1988c0984e5SSebastian Reichel static int goldfish_battery_probe(struct platform_device *pdev) 1998c0984e5SSebastian Reichel { 2008c0984e5SSebastian Reichel int ret; 2018c0984e5SSebastian Reichel struct resource *r; 2028c0984e5SSebastian Reichel struct goldfish_battery_data *data; 2038c0984e5SSebastian Reichel struct power_supply_config psy_cfg = {}; 2048c0984e5SSebastian Reichel 2058c0984e5SSebastian Reichel data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); 2068c0984e5SSebastian Reichel if (data == NULL) 2078c0984e5SSebastian Reichel return -ENOMEM; 2088c0984e5SSebastian Reichel 2098c0984e5SSebastian Reichel spin_lock_init(&data->lock); 2108c0984e5SSebastian Reichel 2118c0984e5SSebastian Reichel r = platform_get_resource(pdev, IORESOURCE_MEM, 0); 2128c0984e5SSebastian Reichel if (r == NULL) { 2138c0984e5SSebastian Reichel dev_err(&pdev->dev, "platform_get_resource failed\n"); 2148c0984e5SSebastian Reichel return -ENODEV; 2158c0984e5SSebastian Reichel } 2168c0984e5SSebastian Reichel 2178c0984e5SSebastian Reichel data->reg_base = devm_ioremap(&pdev->dev, r->start, resource_size(r)); 2188c0984e5SSebastian Reichel if (data->reg_base == NULL) { 2198c0984e5SSebastian Reichel dev_err(&pdev->dev, "unable to remap MMIO\n"); 2208c0984e5SSebastian Reichel return -ENOMEM; 2218c0984e5SSebastian Reichel } 2228c0984e5SSebastian Reichel 2238c0984e5SSebastian Reichel data->irq = platform_get_irq(pdev, 0); 2248c0984e5SSebastian Reichel if (data->irq < 0) { 2258c0984e5SSebastian Reichel dev_err(&pdev->dev, "platform_get_irq failed\n"); 2268c0984e5SSebastian Reichel return -ENODEV; 2278c0984e5SSebastian Reichel } 2288c0984e5SSebastian Reichel 22965ab18a1SRoman Kiryanov ret = devm_request_irq(&pdev->dev, data->irq, 23065ab18a1SRoman Kiryanov goldfish_battery_interrupt, 2318c0984e5SSebastian Reichel IRQF_SHARED, pdev->name, data); 2328c0984e5SSebastian Reichel if (ret) 2338c0984e5SSebastian Reichel return ret; 2348c0984e5SSebastian Reichel 2358c0984e5SSebastian Reichel psy_cfg.drv_data = data; 2368c0984e5SSebastian Reichel 2378c0984e5SSebastian Reichel data->ac = power_supply_register(&pdev->dev, &ac_desc, &psy_cfg); 2388c0984e5SSebastian Reichel if (IS_ERR(data->ac)) 2398c0984e5SSebastian Reichel return PTR_ERR(data->ac); 2408c0984e5SSebastian Reichel 2418c0984e5SSebastian Reichel data->battery = power_supply_register(&pdev->dev, &battery_desc, 2428c0984e5SSebastian Reichel &psy_cfg); 2438c0984e5SSebastian Reichel if (IS_ERR(data->battery)) { 2448c0984e5SSebastian Reichel power_supply_unregister(data->ac); 2458c0984e5SSebastian Reichel return PTR_ERR(data->battery); 2468c0984e5SSebastian Reichel } 2478c0984e5SSebastian Reichel 2488c0984e5SSebastian Reichel platform_set_drvdata(pdev, data); 2498c0984e5SSebastian Reichel 2508c0984e5SSebastian Reichel GOLDFISH_BATTERY_WRITE(data, BATTERY_INT_ENABLE, BATTERY_INT_MASK); 2518c0984e5SSebastian Reichel return 0; 2528c0984e5SSebastian Reichel } 2538c0984e5SSebastian Reichel 2548c0984e5SSebastian Reichel static int goldfish_battery_remove(struct platform_device *pdev) 2558c0984e5SSebastian Reichel { 2568c0984e5SSebastian Reichel struct goldfish_battery_data *data = platform_get_drvdata(pdev); 2578c0984e5SSebastian Reichel 2588c0984e5SSebastian Reichel power_supply_unregister(data->battery); 2598c0984e5SSebastian Reichel power_supply_unregister(data->ac); 2608c0984e5SSebastian Reichel return 0; 2618c0984e5SSebastian Reichel } 2628c0984e5SSebastian Reichel 2638c0984e5SSebastian Reichel static const struct of_device_id goldfish_battery_of_match[] = { 2648c0984e5SSebastian Reichel { .compatible = "google,goldfish-battery", }, 2658c0984e5SSebastian Reichel {}, 2668c0984e5SSebastian Reichel }; 2678c0984e5SSebastian Reichel MODULE_DEVICE_TABLE(of, goldfish_battery_of_match); 2688c0984e5SSebastian Reichel 269*439cd7edSKrzysztof Kozlowski #ifdef CONFIG_ACPI 2708c0984e5SSebastian Reichel static const struct acpi_device_id goldfish_battery_acpi_match[] = { 2718c0984e5SSebastian Reichel { "GFSH0001", 0 }, 2728c0984e5SSebastian Reichel { }, 2738c0984e5SSebastian Reichel }; 2748c0984e5SSebastian Reichel MODULE_DEVICE_TABLE(acpi, goldfish_battery_acpi_match); 275*439cd7edSKrzysztof Kozlowski #endif 2768c0984e5SSebastian Reichel 2778c0984e5SSebastian Reichel static struct platform_driver goldfish_battery_device = { 2788c0984e5SSebastian Reichel .probe = goldfish_battery_probe, 2798c0984e5SSebastian Reichel .remove = goldfish_battery_remove, 2808c0984e5SSebastian Reichel .driver = { 2818c0984e5SSebastian Reichel .name = "goldfish-battery", 2828c0984e5SSebastian Reichel .of_match_table = goldfish_battery_of_match, 2838c0984e5SSebastian Reichel .acpi_match_table = ACPI_PTR(goldfish_battery_acpi_match), 2848c0984e5SSebastian Reichel } 2858c0984e5SSebastian Reichel }; 2868c0984e5SSebastian Reichel module_platform_driver(goldfish_battery_device); 2878c0984e5SSebastian Reichel 2888c0984e5SSebastian Reichel MODULE_AUTHOR("Mike Lockwood lockwood@android.com"); 2898c0984e5SSebastian Reichel MODULE_LICENSE("GPL"); 2908c0984e5SSebastian Reichel MODULE_DESCRIPTION("Battery driver for the Goldfish emulator"); 291