1/*
2 * Power supply driver for the goldfish emulator
3 *
4 * Copyright (C) 2008 Google, Inc.
5 * Copyright (C) 2012 Intel, Inc.
6 * Copyright (C) 2013 Intel, Inc.
7 * Author: Mike Lockwood <lockwood@android.com>
8 *
9 * This software is licensed under the terms of the GNU General Public
10 * License version 2, as published by the Free Software Foundation, and
11 * may be copied, distributed, and modified under those terms.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 * GNU General Public License for more details.
17 */
18
19#include <linux/module.h>
20#include <linux/err.h>
21#include <linux/platform_device.h>
22#include <linux/power_supply.h>
23#include <linux/types.h>
24#include <linux/pci.h>
25#include <linux/interrupt.h>
26#include <linux/io.h>
27
28struct goldfish_battery_data {
29	void __iomem *reg_base;
30	int irq;
31	spinlock_t lock;
32
33	struct power_supply battery;
34	struct power_supply ac;
35};
36
37#define GOLDFISH_BATTERY_READ(data, addr) \
38	(readl(data->reg_base + addr))
39#define GOLDFISH_BATTERY_WRITE(data, addr, x) \
40	(writel(x, data->reg_base + addr))
41
42/*
43 * Temporary variable used between goldfish_battery_probe() and
44 * goldfish_battery_open().
45 */
46static struct goldfish_battery_data *battery_data;
47
48enum {
49	/* status register */
50	BATTERY_INT_STATUS	    = 0x00,
51	/* set this to enable IRQ */
52	BATTERY_INT_ENABLE	    = 0x04,
53
54	BATTERY_AC_ONLINE       = 0x08,
55	BATTERY_STATUS          = 0x0C,
56	BATTERY_HEALTH          = 0x10,
57	BATTERY_PRESENT         = 0x14,
58	BATTERY_CAPACITY        = 0x18,
59
60	BATTERY_STATUS_CHANGED	= 1U << 0,
61	AC_STATUS_CHANGED	= 1U << 1,
62	BATTERY_INT_MASK        = BATTERY_STATUS_CHANGED | AC_STATUS_CHANGED,
63};
64
65
66static int goldfish_ac_get_property(struct power_supply *psy,
67			enum power_supply_property psp,
68			union power_supply_propval *val)
69{
70	struct goldfish_battery_data *data = container_of(psy,
71		struct goldfish_battery_data, ac);
72	int ret = 0;
73
74	switch (psp) {
75	case POWER_SUPPLY_PROP_ONLINE:
76		val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_AC_ONLINE);
77		break;
78	default:
79		ret = -EINVAL;
80		break;
81	}
82	return ret;
83}
84
85static int goldfish_battery_get_property(struct power_supply *psy,
86				 enum power_supply_property psp,
87				 union power_supply_propval *val)
88{
89	struct goldfish_battery_data *data = container_of(psy,
90		struct goldfish_battery_data, battery);
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	default:
110		ret = -EINVAL;
111		break;
112	}
113
114	return ret;
115}
116
117static enum power_supply_property goldfish_battery_props[] = {
118	POWER_SUPPLY_PROP_STATUS,
119	POWER_SUPPLY_PROP_HEALTH,
120	POWER_SUPPLY_PROP_PRESENT,
121	POWER_SUPPLY_PROP_TECHNOLOGY,
122	POWER_SUPPLY_PROP_CAPACITY,
123};
124
125static enum power_supply_property goldfish_ac_props[] = {
126	POWER_SUPPLY_PROP_ONLINE,
127};
128
129static irqreturn_t goldfish_battery_interrupt(int irq, void *dev_id)
130{
131	unsigned long irq_flags;
132	struct goldfish_battery_data *data = dev_id;
133	uint32_t status;
134
135	spin_lock_irqsave(&data->lock, irq_flags);
136
137	/* read status flags, which will clear the interrupt */
138	status = GOLDFISH_BATTERY_READ(data, BATTERY_INT_STATUS);
139	status &= BATTERY_INT_MASK;
140
141	if (status & BATTERY_STATUS_CHANGED)
142		power_supply_changed(&data->battery);
143	if (status & AC_STATUS_CHANGED)
144		power_supply_changed(&data->ac);
145
146	spin_unlock_irqrestore(&data->lock, irq_flags);
147	return status ? IRQ_HANDLED : IRQ_NONE;
148}
149
150
151static int goldfish_battery_probe(struct platform_device *pdev)
152{
153	int ret;
154	struct resource *r;
155	struct goldfish_battery_data *data;
156
157	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
158	if (data == NULL)
159		return -ENOMEM;
160
161	spin_lock_init(&data->lock);
162
163	data->battery.properties = goldfish_battery_props;
164	data->battery.num_properties = ARRAY_SIZE(goldfish_battery_props);
165	data->battery.get_property = goldfish_battery_get_property;
166	data->battery.name = "battery";
167	data->battery.type = POWER_SUPPLY_TYPE_BATTERY;
168
169	data->ac.properties = goldfish_ac_props;
170	data->ac.num_properties = ARRAY_SIZE(goldfish_ac_props);
171	data->ac.get_property = goldfish_ac_get_property;
172	data->ac.name = "ac";
173	data->ac.type = POWER_SUPPLY_TYPE_MAINS;
174
175	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
176	if (r == NULL) {
177		dev_err(&pdev->dev, "platform_get_resource failed\n");
178		return -ENODEV;
179	}
180
181	data->reg_base = devm_ioremap(&pdev->dev, r->start, resource_size(r));
182	if (data->reg_base == NULL) {
183		dev_err(&pdev->dev, "unable to remap MMIO\n");
184		return -ENOMEM;
185	}
186
187	data->irq = platform_get_irq(pdev, 0);
188	if (data->irq < 0) {
189		dev_err(&pdev->dev, "platform_get_irq failed\n");
190		return -ENODEV;
191	}
192
193	ret = devm_request_irq(&pdev->dev, data->irq, goldfish_battery_interrupt,
194						IRQF_SHARED, pdev->name, data);
195	if (ret)
196		return ret;
197
198	ret = power_supply_register(&pdev->dev, &data->ac);
199	if (ret)
200		return ret;
201
202	ret = power_supply_register(&pdev->dev, &data->battery);
203	if (ret) {
204		power_supply_unregister(&data->ac);
205		return ret;
206	}
207
208	platform_set_drvdata(pdev, data);
209	battery_data = data;
210
211	GOLDFISH_BATTERY_WRITE(data, BATTERY_INT_ENABLE, BATTERY_INT_MASK);
212	return 0;
213}
214
215static int goldfish_battery_remove(struct platform_device *pdev)
216{
217	struct goldfish_battery_data *data = platform_get_drvdata(pdev);
218
219	power_supply_unregister(&data->battery);
220	power_supply_unregister(&data->ac);
221	battery_data = NULL;
222	return 0;
223}
224
225static struct platform_driver goldfish_battery_device = {
226	.probe		= goldfish_battery_probe,
227	.remove		= goldfish_battery_remove,
228	.driver = {
229		.name = "goldfish-battery"
230	}
231};
232module_platform_driver(goldfish_battery_device);
233
234MODULE_AUTHOR("Mike Lockwood lockwood@android.com");
235MODULE_LICENSE("GPL");
236MODULE_DESCRIPTION("Battery driver for the Goldfish emulator");
237