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
goldfish_ac_get_property(struct power_supply * psy,enum power_supply_property psp,union power_supply_propval * val)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
goldfish_battery_get_property(struct power_supply * psy,enum power_supply_property psp,union power_supply_propval * val)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
goldfish_battery_interrupt(int irq,void * dev_id)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
goldfish_battery_probe(struct platform_device * pdev)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);
224*b770583bSYang Li if (data->irq < 0)
2258c0984e5SSebastian Reichel return -ENODEV;
2268c0984e5SSebastian Reichel
22765ab18a1SRoman Kiryanov ret = devm_request_irq(&pdev->dev, data->irq,
22865ab18a1SRoman Kiryanov goldfish_battery_interrupt,
2298c0984e5SSebastian Reichel IRQF_SHARED, pdev->name, data);
2308c0984e5SSebastian Reichel if (ret)
2318c0984e5SSebastian Reichel return ret;
2328c0984e5SSebastian Reichel
2338c0984e5SSebastian Reichel psy_cfg.drv_data = data;
2348c0984e5SSebastian Reichel
2358c0984e5SSebastian Reichel data->ac = power_supply_register(&pdev->dev, &ac_desc, &psy_cfg);
2368c0984e5SSebastian Reichel if (IS_ERR(data->ac))
2378c0984e5SSebastian Reichel return PTR_ERR(data->ac);
2388c0984e5SSebastian Reichel
2398c0984e5SSebastian Reichel data->battery = power_supply_register(&pdev->dev, &battery_desc,
2408c0984e5SSebastian Reichel &psy_cfg);
2418c0984e5SSebastian Reichel if (IS_ERR(data->battery)) {
2428c0984e5SSebastian Reichel power_supply_unregister(data->ac);
2438c0984e5SSebastian Reichel return PTR_ERR(data->battery);
2448c0984e5SSebastian Reichel }
2458c0984e5SSebastian Reichel
2468c0984e5SSebastian Reichel platform_set_drvdata(pdev, data);
2478c0984e5SSebastian Reichel
2488c0984e5SSebastian Reichel GOLDFISH_BATTERY_WRITE(data, BATTERY_INT_ENABLE, BATTERY_INT_MASK);
2498c0984e5SSebastian Reichel return 0;
2508c0984e5SSebastian Reichel }
2518c0984e5SSebastian Reichel
goldfish_battery_remove(struct platform_device * pdev)2528c0984e5SSebastian Reichel static int goldfish_battery_remove(struct platform_device *pdev)
2538c0984e5SSebastian Reichel {
2548c0984e5SSebastian Reichel struct goldfish_battery_data *data = platform_get_drvdata(pdev);
2558c0984e5SSebastian Reichel
2568c0984e5SSebastian Reichel power_supply_unregister(data->battery);
2578c0984e5SSebastian Reichel power_supply_unregister(data->ac);
2588c0984e5SSebastian Reichel return 0;
2598c0984e5SSebastian Reichel }
2608c0984e5SSebastian Reichel
2618c0984e5SSebastian Reichel static const struct of_device_id goldfish_battery_of_match[] = {
2628c0984e5SSebastian Reichel { .compatible = "google,goldfish-battery", },
2638c0984e5SSebastian Reichel {},
2648c0984e5SSebastian Reichel };
2658c0984e5SSebastian Reichel MODULE_DEVICE_TABLE(of, goldfish_battery_of_match);
2668c0984e5SSebastian Reichel
267439cd7edSKrzysztof Kozlowski #ifdef CONFIG_ACPI
2688c0984e5SSebastian Reichel static const struct acpi_device_id goldfish_battery_acpi_match[] = {
2698c0984e5SSebastian Reichel { "GFSH0001", 0 },
2708c0984e5SSebastian Reichel { },
2718c0984e5SSebastian Reichel };
2728c0984e5SSebastian Reichel MODULE_DEVICE_TABLE(acpi, goldfish_battery_acpi_match);
273439cd7edSKrzysztof Kozlowski #endif
2748c0984e5SSebastian Reichel
2758c0984e5SSebastian Reichel static struct platform_driver goldfish_battery_device = {
2768c0984e5SSebastian Reichel .probe = goldfish_battery_probe,
2778c0984e5SSebastian Reichel .remove = goldfish_battery_remove,
2788c0984e5SSebastian Reichel .driver = {
2798c0984e5SSebastian Reichel .name = "goldfish-battery",
2808c0984e5SSebastian Reichel .of_match_table = goldfish_battery_of_match,
2818c0984e5SSebastian Reichel .acpi_match_table = ACPI_PTR(goldfish_battery_acpi_match),
2828c0984e5SSebastian Reichel }
2838c0984e5SSebastian Reichel };
2848c0984e5SSebastian Reichel module_platform_driver(goldfish_battery_device);
2858c0984e5SSebastian Reichel
2868c0984e5SSebastian Reichel MODULE_AUTHOR("Mike Lockwood lockwood@android.com");
2878c0984e5SSebastian Reichel MODULE_LICENSE("GPL");
2888c0984e5SSebastian Reichel MODULE_DESCRIPTION("Battery driver for the Goldfish emulator");
289