Blame view

drivers/power/wm97xx_battery.c 7.41 KB
4e9687d9c   Marek Vašut   [ARM] 5248/1: wm9...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  /*
   * linux/drivers/power/wm97xx_battery.c
   *
   * Battery measurement code for WM97xx
   *
   * based on tosa_battery.c
   *
   * Copyright (C) 2008 Marek Vasut <marek.vasut@gmail.com>
   *
   * This program is free software; you can redistribute it and/or modify
   * it under the terms of the GNU General Public License version 2 as
   * published by the Free Software Foundation.
   *
   */
  
  #include <linux/init.h>
  #include <linux/kernel.h>
  #include <linux/module.h>
  #include <linux/platform_device.h>
  #include <linux/power_supply.h>
  #include <linux/wm97xx.h>
  #include <linux/spinlock.h>
  #include <linux/interrupt.h>
  #include <linux/gpio.h>
7c87942ae   Marek Vasut   wm97xx_battery: U...
25
  #include <linux/irq.h>
5a0e3ad6a   Tejun Heo   include cleanup: ...
26
  #include <linux/slab.h>
4e9687d9c   Marek Vašut   [ARM] 5248/1: wm9...
27
28
29
  
  static DEFINE_MUTEX(bat_lock);
  static struct work_struct bat_work;
f8d94eb77   Mark Brown   wm97xx_battery: C...
30
  static struct mutex work_lock;
4e9687d9c   Marek Vašut   [ARM] 5248/1: wm9...
31
  static int bat_status = POWER_SUPPLY_STATUS_UNKNOWN;
4e9687d9c   Marek Vašut   [ARM] 5248/1: wm9...
32
33
34
35
  static enum power_supply_property *prop;
  
  static unsigned long wm97xx_read_bat(struct power_supply *bat_ps)
  {
b8bdc1d0c   Marek Vasut   wm97xx_battery: U...
36
37
  	struct wm97xx_pdata *wmdata = bat_ps->dev->parent->platform_data;
  	struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata;
38c7dc373   Alexander Beregalov   wm97xx_batery: re...
38
  	return wm97xx_read_aux_adc(dev_get_drvdata(bat_ps->dev->parent),
4e9687d9c   Marek Vašut   [ARM] 5248/1: wm9...
39
40
41
42
43
44
  					pdata->batt_aux) * pdata->batt_mult /
  					pdata->batt_div;
  }
  
  static unsigned long wm97xx_read_temp(struct power_supply *bat_ps)
  {
b8bdc1d0c   Marek Vasut   wm97xx_battery: U...
45
46
  	struct wm97xx_pdata *wmdata = bat_ps->dev->parent->platform_data;
  	struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata;
38c7dc373   Alexander Beregalov   wm97xx_batery: re...
47
  	return wm97xx_read_aux_adc(dev_get_drvdata(bat_ps->dev->parent),
4e9687d9c   Marek Vašut   [ARM] 5248/1: wm9...
48
49
50
51
52
53
54
55
  					pdata->temp_aux) * pdata->temp_mult /
  					pdata->temp_div;
  }
  
  static int wm97xx_bat_get_property(struct power_supply *bat_ps,
  			    enum power_supply_property psp,
  			    union power_supply_propval *val)
  {
b8bdc1d0c   Marek Vasut   wm97xx_battery: U...
56
57
  	struct wm97xx_pdata *wmdata = bat_ps->dev->parent->platform_data;
  	struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata;
4e9687d9c   Marek Vašut   [ARM] 5248/1: wm9...
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
  	switch (psp) {
  	case POWER_SUPPLY_PROP_STATUS:
  		val->intval = bat_status;
  		break;
  	case POWER_SUPPLY_PROP_TECHNOLOGY:
  		val->intval = pdata->batt_tech;
  		break;
  	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
  		if (pdata->batt_aux >= 0)
  			val->intval = wm97xx_read_bat(bat_ps);
  		else
  			return -EINVAL;
  		break;
  	case POWER_SUPPLY_PROP_TEMP:
  		if (pdata->temp_aux >= 0)
  			val->intval = wm97xx_read_temp(bat_ps);
  		else
  			return -EINVAL;
  		break;
  	case POWER_SUPPLY_PROP_VOLTAGE_MAX:
  		if (pdata->max_voltage >= 0)
  			val->intval = pdata->max_voltage;
  		else
  			return -EINVAL;
  		break;
  	case POWER_SUPPLY_PROP_VOLTAGE_MIN:
  		if (pdata->min_voltage >= 0)
  			val->intval = pdata->min_voltage;
  		else
  			return -EINVAL;
  		break;
  	case POWER_SUPPLY_PROP_PRESENT:
  		val->intval = 1;
  		break;
  	default:
  		return -EINVAL;
  	}
  	return 0;
  }
  
  static void wm97xx_bat_external_power_changed(struct power_supply *bat_ps)
  {
  	schedule_work(&bat_work);
  }
  
  static void wm97xx_bat_update(struct power_supply *bat_ps)
  {
  	int old_status = bat_status;
b8bdc1d0c   Marek Vasut   wm97xx_battery: U...
106
107
  	struct wm97xx_pdata *wmdata = bat_ps->dev->parent->platform_data;
  	struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata;
4e9687d9c   Marek Vašut   [ARM] 5248/1: wm9...
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
  
  	mutex_lock(&work_lock);
  
  	bat_status = (pdata->charge_gpio >= 0) ?
  			(gpio_get_value(pdata->charge_gpio) ?
  			POWER_SUPPLY_STATUS_DISCHARGING :
  			POWER_SUPPLY_STATUS_CHARGING) :
  			POWER_SUPPLY_STATUS_UNKNOWN;
  
  	if (old_status != bat_status) {
  		pr_debug("%s: %i -> %i
  ", bat_ps->name, old_status,
  					bat_status);
  		power_supply_changed(bat_ps);
  	}
  
  	mutex_unlock(&work_lock);
  }
  
  static struct power_supply bat_ps = {
  	.type			= POWER_SUPPLY_TYPE_BATTERY,
  	.get_property		= wm97xx_bat_get_property,
  	.external_power_changed = wm97xx_bat_external_power_changed,
  	.use_for_apm		= 1,
  };
  
  static void wm97xx_bat_work(struct work_struct *work)
  {
  	wm97xx_bat_update(&bat_ps);
  }
7c87942ae   Marek Vasut   wm97xx_battery: U...
138
139
140
141
142
  static irqreturn_t wm97xx_chrg_irq(int irq, void *data)
  {
  	schedule_work(&bat_work);
  	return IRQ_HANDLED;
  }
4e9687d9c   Marek Vašut   [ARM] 5248/1: wm9...
143
  #ifdef CONFIG_PM
83a8af0d3   Marek Vasut   wm97xx_battery: C...
144
  static int wm97xx_bat_suspend(struct device *dev)
4e9687d9c   Marek Vašut   [ARM] 5248/1: wm9...
145
  {
bc51e7ff5   Tejun Heo   power_supply: Don...
146
  	flush_work_sync(&bat_work);
4e9687d9c   Marek Vašut   [ARM] 5248/1: wm9...
147
148
  	return 0;
  }
83a8af0d3   Marek Vasut   wm97xx_battery: C...
149
  static int wm97xx_bat_resume(struct device *dev)
4e9687d9c   Marek Vašut   [ARM] 5248/1: wm9...
150
151
152
153
  {
  	schedule_work(&bat_work);
  	return 0;
  }
83a8af0d3   Marek Vasut   wm97xx_battery: C...
154

471452104   Alexey Dobriyan   const: constify r...
155
  static const struct dev_pm_ops wm97xx_bat_pm_ops = {
83a8af0d3   Marek Vasut   wm97xx_battery: C...
156
157
158
  	.suspend	= wm97xx_bat_suspend,
  	.resume		= wm97xx_bat_resume,
  };
4e9687d9c   Marek Vašut   [ARM] 5248/1: wm9...
159
160
161
162
163
164
165
  #endif
  
  static int __devinit wm97xx_bat_probe(struct platform_device *dev)
  {
  	int ret = 0;
  	int props = 1;	/* POWER_SUPPLY_PROP_PRESENT */
  	int i = 0;
b8bdc1d0c   Marek Vasut   wm97xx_battery: U...
166
167
  	struct wm97xx_pdata *wmdata = dev->dev.platform_data;
  	struct wm97xx_batt_pdata *pdata;
12b336a8b   Mark Brown   wm97xx_battery: H...
168
169
170
171
172
173
174
  	if (!wmdata) {
  		dev_err(&dev->dev, "No platform data supplied
  ");
  		return -EINVAL;
  	}
  
  	pdata = wmdata->batt_pdata;
4e9687d9c   Marek Vašut   [ARM] 5248/1: wm9...
175
176
177
178
179
180
181
  
  	if (dev->id != -1)
  		return -EINVAL;
  
  	mutex_init(&work_lock);
  
  	if (!pdata) {
b8bdc1d0c   Marek Vasut   wm97xx_battery: U...
182
183
  		dev_err(&dev->dev, "No platform_data supplied
  ");
4e9687d9c   Marek Vašut   [ARM] 5248/1: wm9...
184
185
  		return -EINVAL;
  	}
7c87942ae   Marek Vasut   wm97xx_battery: U...
186
  	if (gpio_is_valid(pdata->charge_gpio)) {
4e9687d9c   Marek Vašut   [ARM] 5248/1: wm9...
187
188
189
190
191
192
  		ret = gpio_request(pdata->charge_gpio, "BATT CHRG");
  		if (ret)
  			goto err;
  		ret = gpio_direction_input(pdata->charge_gpio);
  		if (ret)
  			goto err2;
7c87942ae   Marek Vasut   wm97xx_battery: U...
193
194
  		ret = request_irq(gpio_to_irq(pdata->charge_gpio),
  				wm97xx_chrg_irq, IRQF_DISABLED,
f8d94eb77   Mark Brown   wm97xx_battery: C...
195
  				"AC Detect", dev);
7c87942ae   Marek Vasut   wm97xx_battery: U...
196
197
  		if (ret)
  			goto err2;
4e9687d9c   Marek Vašut   [ARM] 5248/1: wm9...
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
  		props++;	/* POWER_SUPPLY_PROP_STATUS */
  	}
  
  	if (pdata->batt_tech >= 0)
  		props++;	/* POWER_SUPPLY_PROP_TECHNOLOGY */
  	if (pdata->temp_aux >= 0)
  		props++;	/* POWER_SUPPLY_PROP_TEMP */
  	if (pdata->batt_aux >= 0)
  		props++;	/* POWER_SUPPLY_PROP_VOLTAGE_NOW */
  	if (pdata->max_voltage >= 0)
  		props++;	/* POWER_SUPPLY_PROP_VOLTAGE_MAX */
  	if (pdata->min_voltage >= 0)
  		props++;	/* POWER_SUPPLY_PROP_VOLTAGE_MIN */
  
  	prop = kzalloc(props * sizeof(*prop), GFP_KERNEL);
  	if (!prop)
7c87942ae   Marek Vasut   wm97xx_battery: U...
214
  		goto err3;
4e9687d9c   Marek Vašut   [ARM] 5248/1: wm9...
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
  
  	prop[i++] = POWER_SUPPLY_PROP_PRESENT;
  	if (pdata->charge_gpio >= 0)
  		prop[i++] = POWER_SUPPLY_PROP_STATUS;
  	if (pdata->batt_tech >= 0)
  		prop[i++] = POWER_SUPPLY_PROP_TECHNOLOGY;
  	if (pdata->temp_aux >= 0)
  		prop[i++] = POWER_SUPPLY_PROP_TEMP;
  	if (pdata->batt_aux >= 0)
  		prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_NOW;
  	if (pdata->max_voltage >= 0)
  		prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MAX;
  	if (pdata->min_voltage >= 0)
  		prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MIN;
  
  	INIT_WORK(&bat_work, wm97xx_bat_work);
  
  	if (!pdata->batt_name) {
  		dev_info(&dev->dev, "Please consider setting proper battery "
  				"name in platform definition file, falling "
  				"back to name \"wm97xx-batt\"
  ");
  		bat_ps.name = "wm97xx-batt";
  	} else
  		bat_ps.name = pdata->batt_name;
  
  	bat_ps.properties = prop;
  	bat_ps.num_properties = props;
  
  	ret = power_supply_register(&dev->dev, &bat_ps);
  	if (!ret)
  		schedule_work(&bat_work);
  	else
7c87942ae   Marek Vasut   wm97xx_battery: U...
248
  		goto err4;
4e9687d9c   Marek Vašut   [ARM] 5248/1: wm9...
249
250
  
  	return 0;
7c87942ae   Marek Vasut   wm97xx_battery: U...
251
  err4:
4e9687d9c   Marek Vašut   [ARM] 5248/1: wm9...
252
  	kfree(prop);
7c87942ae   Marek Vasut   wm97xx_battery: U...
253
254
255
  err3:
  	if (gpio_is_valid(pdata->charge_gpio))
  		free_irq(gpio_to_irq(pdata->charge_gpio), dev);
4e9687d9c   Marek Vašut   [ARM] 5248/1: wm9...
256
  err2:
7c87942ae   Marek Vasut   wm97xx_battery: U...
257
258
  	if (gpio_is_valid(pdata->charge_gpio))
  		gpio_free(pdata->charge_gpio);
4e9687d9c   Marek Vašut   [ARM] 5248/1: wm9...
259
260
261
262
263
264
  err:
  	return ret;
  }
  
  static int __devexit wm97xx_bat_remove(struct platform_device *dev)
  {
b8bdc1d0c   Marek Vasut   wm97xx_battery: U...
265
266
  	struct wm97xx_pdata *wmdata = dev->dev.platform_data;
  	struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata;
7c87942ae   Marek Vasut   wm97xx_battery: U...
267
268
  	if (pdata && gpio_is_valid(pdata->charge_gpio)) {
  		free_irq(gpio_to_irq(pdata->charge_gpio), dev);
4e9687d9c   Marek Vašut   [ARM] 5248/1: wm9...
269
  		gpio_free(pdata->charge_gpio);
7c87942ae   Marek Vasut   wm97xx_battery: U...
270
  	}
bc51e7ff5   Tejun Heo   power_supply: Don...
271
  	cancel_work_sync(&bat_work);
4e9687d9c   Marek Vašut   [ARM] 5248/1: wm9...
272
273
274
275
276
277
278
279
280
  	power_supply_unregister(&bat_ps);
  	kfree(prop);
  	return 0;
  }
  
  static struct platform_driver wm97xx_bat_driver = {
  	.driver	= {
  		.name	= "wm97xx-battery",
  		.owner	= THIS_MODULE,
83a8af0d3   Marek Vasut   wm97xx_battery: C...
281
282
283
  #ifdef CONFIG_PM
  		.pm	= &wm97xx_bat_pm_ops,
  #endif
4e9687d9c   Marek Vašut   [ARM] 5248/1: wm9...
284
285
286
  	},
  	.probe		= wm97xx_bat_probe,
  	.remove		= __devexit_p(wm97xx_bat_remove),
4e9687d9c   Marek Vašut   [ARM] 5248/1: wm9...
287
288
289
290
291
292
293
294
295
296
297
  };
  
  static int __init wm97xx_bat_init(void)
  {
  	return platform_driver_register(&wm97xx_bat_driver);
  }
  
  static void __exit wm97xx_bat_exit(void)
  {
  	platform_driver_unregister(&wm97xx_bat_driver);
  }
4e9687d9c   Marek Vašut   [ARM] 5248/1: wm9...
298
299
300
301
302
303
  module_init(wm97xx_bat_init);
  module_exit(wm97xx_bat_exit);
  
  MODULE_LICENSE("GPL");
  MODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>");
  MODULE_DESCRIPTION("WM97xx battery driver");