1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Power supply driver for the goldfish emulator |
4 | * |
5 | * Copyright (C) 2008 Google, Inc. |
6 | * Copyright (C) 2012 Intel, Inc. |
7 | * Copyright (C) 2013 Intel, Inc. |
8 | * Author: Mike Lockwood <lockwood@android.com> |
9 | */ |
10 | |
11 | #include <linux/module.h> |
12 | #include <linux/err.h> |
13 | #include <linux/platform_device.h> |
14 | #include <linux/power_supply.h> |
15 | #include <linux/types.h> |
16 | #include <linux/pci.h> |
17 | #include <linux/interrupt.h> |
18 | #include <linux/io.h> |
19 | #include <linux/acpi.h> |
20 | |
21 | struct goldfish_battery_data { |
22 | void __iomem *reg_base; |
23 | int irq; |
24 | spinlock_t lock; |
25 | |
26 | struct power_supply *battery; |
27 | struct power_supply *ac; |
28 | }; |
29 | |
30 | #define GOLDFISH_BATTERY_READ(data, addr) \ |
31 | (readl(data->reg_base + addr)) |
32 | #define GOLDFISH_BATTERY_WRITE(data, addr, x) \ |
33 | (writel(x, data->reg_base + addr)) |
34 | |
35 | enum { |
36 | /* status register */ |
37 | BATTERY_INT_STATUS = 0x00, |
38 | /* set this to enable IRQ */ |
39 | BATTERY_INT_ENABLE = 0x04, |
40 | |
41 | BATTERY_AC_ONLINE = 0x08, |
42 | BATTERY_STATUS = 0x0C, |
43 | BATTERY_HEALTH = 0x10, |
44 | BATTERY_PRESENT = 0x14, |
45 | BATTERY_CAPACITY = 0x18, |
46 | BATTERY_VOLTAGE = 0x1C, |
47 | BATTERY_TEMP = 0x20, |
48 | BATTERY_CHARGE_COUNTER = 0x24, |
49 | BATTERY_VOLTAGE_MAX = 0x28, |
50 | BATTERY_CURRENT_MAX = 0x2C, |
51 | BATTERY_CURRENT_NOW = 0x30, |
52 | BATTERY_CURRENT_AVG = 0x34, |
53 | BATTERY_CHARGE_FULL_UAH = 0x38, |
54 | BATTERY_CYCLE_COUNT = 0x40, |
55 | |
56 | BATTERY_STATUS_CHANGED = 1U << 0, |
57 | AC_STATUS_CHANGED = 1U << 1, |
58 | BATTERY_INT_MASK = BATTERY_STATUS_CHANGED | AC_STATUS_CHANGED, |
59 | }; |
60 | |
61 | |
62 | static int goldfish_ac_get_property(struct power_supply *psy, |
63 | enum power_supply_property psp, |
64 | union power_supply_propval *val) |
65 | { |
66 | struct goldfish_battery_data *data = power_supply_get_drvdata(psy); |
67 | int ret = 0; |
68 | |
69 | switch (psp) { |
70 | case POWER_SUPPLY_PROP_ONLINE: |
71 | val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_AC_ONLINE); |
72 | break; |
73 | case POWER_SUPPLY_PROP_VOLTAGE_MAX: |
74 | val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_VOLTAGE_MAX); |
75 | break; |
76 | case POWER_SUPPLY_PROP_CURRENT_MAX: |
77 | val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_CURRENT_MAX); |
78 | break; |
79 | default: |
80 | ret = -EINVAL; |
81 | break; |
82 | } |
83 | return ret; |
84 | } |
85 | |
86 | static int goldfish_battery_get_property(struct power_supply *psy, |
87 | enum power_supply_property psp, |
88 | union power_supply_propval *val) |
89 | { |
90 | struct goldfish_battery_data *data = power_supply_get_drvdata(psy); |
91 | int ret = 0; |
92 | |
93 | switch (psp) { |
94 | case POWER_SUPPLY_PROP_STATUS: |
95 | val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_STATUS); |
96 | break; |
97 | case POWER_SUPPLY_PROP_HEALTH: |
98 | val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_HEALTH); |
99 | break; |
100 | case POWER_SUPPLY_PROP_PRESENT: |
101 | val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_PRESENT); |
102 | break; |
103 | case POWER_SUPPLY_PROP_TECHNOLOGY: |
104 | val->intval = POWER_SUPPLY_TECHNOLOGY_LION; |
105 | break; |
106 | case POWER_SUPPLY_PROP_CAPACITY: |
107 | val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_CAPACITY); |
108 | break; |
109 | case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
110 | val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_VOLTAGE); |
111 | break; |
112 | case POWER_SUPPLY_PROP_TEMP: |
113 | val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_TEMP); |
114 | break; |
115 | case POWER_SUPPLY_PROP_CHARGE_COUNTER: |
116 | val->intval = GOLDFISH_BATTERY_READ(data, |
117 | BATTERY_CHARGE_COUNTER); |
118 | break; |
119 | case POWER_SUPPLY_PROP_CURRENT_NOW: |
120 | val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_CURRENT_NOW); |
121 | break; |
122 | case POWER_SUPPLY_PROP_CURRENT_AVG: |
123 | val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_CURRENT_AVG); |
124 | break; |
125 | case POWER_SUPPLY_PROP_CHARGE_FULL: |
126 | val->intval = GOLDFISH_BATTERY_READ(data, |
127 | BATTERY_CHARGE_FULL_UAH); |
128 | break; |
129 | case POWER_SUPPLY_PROP_CYCLE_COUNT: |
130 | val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_CYCLE_COUNT); |
131 | break; |
132 | default: |
133 | ret = -EINVAL; |
134 | break; |
135 | } |
136 | |
137 | return ret; |
138 | } |
139 | |
140 | static enum power_supply_property goldfish_battery_props[] = { |
141 | POWER_SUPPLY_PROP_STATUS, |
142 | POWER_SUPPLY_PROP_HEALTH, |
143 | POWER_SUPPLY_PROP_PRESENT, |
144 | POWER_SUPPLY_PROP_TECHNOLOGY, |
145 | POWER_SUPPLY_PROP_CAPACITY, |
146 | POWER_SUPPLY_PROP_VOLTAGE_NOW, |
147 | POWER_SUPPLY_PROP_TEMP, |
148 | POWER_SUPPLY_PROP_CHARGE_COUNTER, |
149 | POWER_SUPPLY_PROP_CURRENT_NOW, |
150 | POWER_SUPPLY_PROP_CURRENT_AVG, |
151 | POWER_SUPPLY_PROP_CHARGE_FULL, |
152 | POWER_SUPPLY_PROP_CYCLE_COUNT, |
153 | }; |
154 | |
155 | static enum power_supply_property goldfish_ac_props[] = { |
156 | POWER_SUPPLY_PROP_ONLINE, |
157 | POWER_SUPPLY_PROP_VOLTAGE_MAX, |
158 | POWER_SUPPLY_PROP_CURRENT_MAX, |
159 | }; |
160 | |
161 | static irqreturn_t goldfish_battery_interrupt(int irq, void *dev_id) |
162 | { |
163 | unsigned long irq_flags; |
164 | struct goldfish_battery_data *data = dev_id; |
165 | uint32_t status; |
166 | |
167 | spin_lock_irqsave(&data->lock, irq_flags); |
168 | |
169 | /* read status flags, which will clear the interrupt */ |
170 | status = GOLDFISH_BATTERY_READ(data, BATTERY_INT_STATUS); |
171 | status &= BATTERY_INT_MASK; |
172 | |
173 | if (status & BATTERY_STATUS_CHANGED) |
174 | power_supply_changed(psy: data->battery); |
175 | if (status & AC_STATUS_CHANGED) |
176 | power_supply_changed(psy: data->ac); |
177 | |
178 | spin_unlock_irqrestore(lock: &data->lock, flags: irq_flags); |
179 | return status ? IRQ_HANDLED : IRQ_NONE; |
180 | } |
181 | |
182 | static const struct power_supply_desc battery_desc = { |
183 | .properties = goldfish_battery_props, |
184 | .num_properties = ARRAY_SIZE(goldfish_battery_props), |
185 | .get_property = goldfish_battery_get_property, |
186 | .name = "battery" , |
187 | .type = POWER_SUPPLY_TYPE_BATTERY, |
188 | }; |
189 | |
190 | static const struct power_supply_desc ac_desc = { |
191 | .properties = goldfish_ac_props, |
192 | .num_properties = ARRAY_SIZE(goldfish_ac_props), |
193 | .get_property = goldfish_ac_get_property, |
194 | .name = "ac" , |
195 | .type = POWER_SUPPLY_TYPE_MAINS, |
196 | }; |
197 | |
198 | static int goldfish_battery_probe(struct platform_device *pdev) |
199 | { |
200 | int ret; |
201 | struct resource *r; |
202 | struct goldfish_battery_data *data; |
203 | struct power_supply_config psy_cfg = {}; |
204 | |
205 | data = devm_kzalloc(dev: &pdev->dev, size: sizeof(*data), GFP_KERNEL); |
206 | if (data == NULL) |
207 | return -ENOMEM; |
208 | |
209 | spin_lock_init(&data->lock); |
210 | |
211 | r = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
212 | if (r == NULL) { |
213 | dev_err(&pdev->dev, "platform_get_resource failed\n" ); |
214 | return -ENODEV; |
215 | } |
216 | |
217 | data->reg_base = devm_ioremap(dev: &pdev->dev, offset: r->start, size: resource_size(res: r)); |
218 | if (data->reg_base == NULL) { |
219 | dev_err(&pdev->dev, "unable to remap MMIO\n" ); |
220 | return -ENOMEM; |
221 | } |
222 | |
223 | data->irq = platform_get_irq(pdev, 0); |
224 | if (data->irq < 0) |
225 | return -ENODEV; |
226 | |
227 | ret = devm_request_irq(dev: &pdev->dev, irq: data->irq, |
228 | handler: goldfish_battery_interrupt, |
229 | IRQF_SHARED, devname: pdev->name, dev_id: data); |
230 | if (ret) |
231 | return ret; |
232 | |
233 | psy_cfg.drv_data = data; |
234 | |
235 | data->ac = power_supply_register(parent: &pdev->dev, desc: &ac_desc, cfg: &psy_cfg); |
236 | if (IS_ERR(ptr: data->ac)) |
237 | return PTR_ERR(ptr: data->ac); |
238 | |
239 | data->battery = power_supply_register(parent: &pdev->dev, desc: &battery_desc, |
240 | cfg: &psy_cfg); |
241 | if (IS_ERR(ptr: data->battery)) { |
242 | power_supply_unregister(psy: data->ac); |
243 | return PTR_ERR(ptr: data->battery); |
244 | } |
245 | |
246 | platform_set_drvdata(pdev, data); |
247 | |
248 | GOLDFISH_BATTERY_WRITE(data, BATTERY_INT_ENABLE, BATTERY_INT_MASK); |
249 | return 0; |
250 | } |
251 | |
252 | static void goldfish_battery_remove(struct platform_device *pdev) |
253 | { |
254 | struct goldfish_battery_data *data = platform_get_drvdata(pdev); |
255 | |
256 | power_supply_unregister(psy: data->battery); |
257 | power_supply_unregister(psy: data->ac); |
258 | } |
259 | |
260 | static const struct of_device_id goldfish_battery_of_match[] = { |
261 | { .compatible = "google,goldfish-battery" , }, |
262 | {}, |
263 | }; |
264 | MODULE_DEVICE_TABLE(of, goldfish_battery_of_match); |
265 | |
266 | #ifdef CONFIG_ACPI |
267 | static const struct acpi_device_id goldfish_battery_acpi_match[] = { |
268 | { "GFSH0001" , 0 }, |
269 | { }, |
270 | }; |
271 | MODULE_DEVICE_TABLE(acpi, goldfish_battery_acpi_match); |
272 | #endif |
273 | |
274 | static struct platform_driver goldfish_battery_device = { |
275 | .probe = goldfish_battery_probe, |
276 | .remove_new = goldfish_battery_remove, |
277 | .driver = { |
278 | .name = "goldfish-battery" , |
279 | .of_match_table = goldfish_battery_of_match, |
280 | .acpi_match_table = ACPI_PTR(goldfish_battery_acpi_match), |
281 | } |
282 | }; |
283 | module_platform_driver(goldfish_battery_device); |
284 | |
285 | MODULE_AUTHOR("Mike Lockwood lockwood@android.com" ); |
286 | MODULE_LICENSE("GPL" ); |
287 | MODULE_DESCRIPTION("Battery driver for the Goldfish emulator" ); |
288 | |