Blame view

drivers/power/collie_battery.c 10.1 KB
f1fce597e   Thomas Kunze   collie: add batte...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
  /*
   * Battery and Power Management code for the Sharp SL-5x00
   *
   * Copyright (C) 2009 Thomas Kunze
   *
   * based on tosa_battery.c
   *
   * 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/kernel.h>
  #include <linux/module.h>
  #include <linux/power_supply.h>
  #include <linux/delay.h>
  #include <linux/spinlock.h>
  #include <linux/interrupt.h>
  #include <linux/gpio.h>
  #include <linux/mfd/ucb1x00.h>
  
  #include <asm/mach/sharpsl_param.h>
  #include <asm/mach-types.h>
  #include <mach/collie.h>
  
  static DEFINE_MUTEX(bat_lock); /* protects gpio pins */
  static struct work_struct bat_work;
  static struct ucb1x00 *ucb;
f1300e7f6   Dmitry Eremin-Solenikov   power: collie_bat...
29
  static int wakeup_enabled;
f1fce597e   Thomas Kunze   collie: add batte...
30
31
32
  
  struct collie_bat {
  	int status;
297d716f6   Krzysztof Kozlowski   power_supply: Cha...
33
  	struct power_supply *psy;
f1fce597e   Thomas Kunze   collie: add batte...
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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
  	int full_chrg;
  
  	struct mutex work_lock; /* protects data */
  
  	bool (*is_present)(struct collie_bat *bat);
  	int gpio_full;
  	int gpio_charge_on;
  
  	int technology;
  
  	int gpio_bat;
  	int adc_bat;
  	int adc_bat_divider;
  	int bat_max;
  	int bat_min;
  
  	int gpio_temp;
  	int adc_temp;
  	int adc_temp_divider;
  };
  
  static struct collie_bat collie_bat_main;
  
  static unsigned long collie_read_bat(struct collie_bat *bat)
  {
  	unsigned long value = 0;
  
  	if (bat->gpio_bat < 0 || bat->adc_bat < 0)
  		return 0;
  	mutex_lock(&bat_lock);
  	gpio_set_value(bat->gpio_bat, 1);
  	msleep(5);
  	ucb1x00_adc_enable(ucb);
  	value = ucb1x00_adc_read(ucb, bat->adc_bat, UCB_SYNC);
  	ucb1x00_adc_disable(ucb);
  	gpio_set_value(bat->gpio_bat, 0);
  	mutex_unlock(&bat_lock);
  	value = value * 1000000 / bat->adc_bat_divider;
  
  	return value;
  }
  
  static unsigned long collie_read_temp(struct collie_bat *bat)
  {
  	unsigned long value = 0;
  	if (bat->gpio_temp < 0 || bat->adc_temp < 0)
  		return 0;
  
  	mutex_lock(&bat_lock);
  	gpio_set_value(bat->gpio_temp, 1);
  	msleep(5);
  	ucb1x00_adc_enable(ucb);
  	value = ucb1x00_adc_read(ucb, bat->adc_temp, UCB_SYNC);
  	ucb1x00_adc_disable(ucb);
  	gpio_set_value(bat->gpio_temp, 0);
  	mutex_unlock(&bat_lock);
  
  	value = value * 10000 / bat->adc_temp_divider;
  
  	return value;
  }
  
  static int collie_bat_get_property(struct power_supply *psy,
  			    enum power_supply_property psp,
  			    union power_supply_propval *val)
  {
  	int ret = 0;
297d716f6   Krzysztof Kozlowski   power_supply: Cha...
101
  	struct collie_bat *bat = power_supply_get_drvdata(psy);
f1fce597e   Thomas Kunze   collie: add batte...
102
103
104
105
106
107
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
138
139
140
141
142
143
144
145
146
147
148
149
  
  	if (bat->is_present && !bat->is_present(bat)
  			&& psp != POWER_SUPPLY_PROP_PRESENT) {
  		return -ENODEV;
  	}
  
  	switch (psp) {
  	case POWER_SUPPLY_PROP_STATUS:
  		val->intval = bat->status;
  		break;
  	case POWER_SUPPLY_PROP_TECHNOLOGY:
  		val->intval = bat->technology;
  		break;
  	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
  		val->intval = collie_read_bat(bat);
  		break;
  	case POWER_SUPPLY_PROP_VOLTAGE_MAX:
  		if (bat->full_chrg == -1)
  			val->intval = bat->bat_max;
  		else
  			val->intval = bat->full_chrg;
  		break;
  	case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
  		val->intval = bat->bat_max;
  		break;
  	case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
  		val->intval = bat->bat_min;
  		break;
  	case POWER_SUPPLY_PROP_TEMP:
  		val->intval = collie_read_temp(bat);
  		break;
  	case POWER_SUPPLY_PROP_PRESENT:
  		val->intval = bat->is_present ? bat->is_present(bat) : 1;
  		break;
  	default:
  		ret = -EINVAL;
  		break;
  	}
  	return ret;
  }
  
  static void collie_bat_external_power_changed(struct power_supply *psy)
  {
  	schedule_work(&bat_work);
  }
  
  static irqreturn_t collie_bat_gpio_isr(int irq, void *data)
  {
629bcb4b7   Jochen Friedrich   collie_battery: G...
150
151
  	pr_info("collie_bat_gpio irq
  ");
f1fce597e   Thomas Kunze   collie: add batte...
152
153
154
155
156
157
158
  	schedule_work(&bat_work);
  	return IRQ_HANDLED;
  }
  
  static void collie_bat_update(struct collie_bat *bat)
  {
  	int old;
297d716f6   Krzysztof Kozlowski   power_supply: Cha...
159
  	struct power_supply *psy = bat->psy;
f1fce597e   Thomas Kunze   collie: add batte...
160
161
162
163
164
165
  
  	mutex_lock(&bat->work_lock);
  
  	old = bat->status;
  
  	if (bat->is_present && !bat->is_present(bat)) {
297d716f6   Krzysztof Kozlowski   power_supply: Cha...
166
167
  		printk(KERN_NOTICE "%s not present
  ", psy->desc->name);
f1fce597e   Thomas Kunze   collie: add batte...
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
  		bat->status = POWER_SUPPLY_STATUS_UNKNOWN;
  		bat->full_chrg = -1;
  	} else if (power_supply_am_i_supplied(psy)) {
  		if (bat->status == POWER_SUPPLY_STATUS_DISCHARGING) {
  			gpio_set_value(bat->gpio_charge_on, 1);
  			mdelay(15);
  		}
  
  		if (gpio_get_value(bat->gpio_full)) {
  			if (old == POWER_SUPPLY_STATUS_CHARGING ||
  					bat->full_chrg == -1)
  				bat->full_chrg = collie_read_bat(bat);
  
  			gpio_set_value(bat->gpio_charge_on, 0);
  			bat->status = POWER_SUPPLY_STATUS_FULL;
  		} else {
  			gpio_set_value(bat->gpio_charge_on, 1);
  			bat->status = POWER_SUPPLY_STATUS_CHARGING;
  		}
  	} else {
  		gpio_set_value(bat->gpio_charge_on, 0);
  		bat->status = POWER_SUPPLY_STATUS_DISCHARGING;
  	}
  
  	if (old != bat->status)
  		power_supply_changed(psy);
  
  	mutex_unlock(&bat->work_lock);
  }
  
  static void collie_bat_work(struct work_struct *work)
  {
  	collie_bat_update(&collie_bat_main);
  }
  
  
  static enum power_supply_property collie_bat_main_props[] = {
  	POWER_SUPPLY_PROP_STATUS,
  	POWER_SUPPLY_PROP_TECHNOLOGY,
  	POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
  	POWER_SUPPLY_PROP_VOLTAGE_NOW,
  	POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
  	POWER_SUPPLY_PROP_VOLTAGE_MAX,
  	POWER_SUPPLY_PROP_PRESENT,
  	POWER_SUPPLY_PROP_TEMP,
  };
  
  static enum power_supply_property collie_bat_bu_props[] = {
  	POWER_SUPPLY_PROP_STATUS,
  	POWER_SUPPLY_PROP_TECHNOLOGY,
  	POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
  	POWER_SUPPLY_PROP_VOLTAGE_NOW,
  	POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
  	POWER_SUPPLY_PROP_VOLTAGE_MAX,
  	POWER_SUPPLY_PROP_PRESENT,
  };
297d716f6   Krzysztof Kozlowski   power_supply: Cha...
224
225
226
227
228
229
230
231
232
  static const struct power_supply_desc collie_bat_main_desc = {
  	.name		= "main-battery",
  	.type		= POWER_SUPPLY_TYPE_BATTERY,
  	.properties	= collie_bat_main_props,
  	.num_properties	= ARRAY_SIZE(collie_bat_main_props),
  	.get_property	= collie_bat_get_property,
  	.external_power_changed = collie_bat_external_power_changed,
  	.use_for_apm	= 1,
  };
f1fce597e   Thomas Kunze   collie: add batte...
233
234
235
  static struct collie_bat collie_bat_main = {
  	.status = POWER_SUPPLY_STATUS_DISCHARGING,
  	.full_chrg = -1,
297d716f6   Krzysztof Kozlowski   power_supply: Cha...
236
  	.psy = NULL,
f1fce597e   Thomas Kunze   collie: add batte...
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
  
  	.gpio_full = COLLIE_GPIO_CO,
  	.gpio_charge_on = COLLIE_GPIO_CHARGE_ON,
  
  	.technology = POWER_SUPPLY_TECHNOLOGY_LIPO,
  
  	.gpio_bat = COLLIE_GPIO_MBAT_ON,
  	.adc_bat = UCB_ADC_INP_AD1,
  	.adc_bat_divider = 155,
  	.bat_max = 4310000,
  	.bat_min = 1551 * 1000000 / 414,
  
  	.gpio_temp = COLLIE_GPIO_TMP_ON,
  	.adc_temp = UCB_ADC_INP_AD0,
  	.adc_temp_divider = 10000,
  };
297d716f6   Krzysztof Kozlowski   power_supply: Cha...
253
254
255
256
257
258
259
260
  static const struct power_supply_desc collie_bat_bu_desc = {
  	.name		= "backup-battery",
  	.type		= POWER_SUPPLY_TYPE_BATTERY,
  	.properties	= collie_bat_bu_props,
  	.num_properties	= ARRAY_SIZE(collie_bat_bu_props),
  	.get_property	= collie_bat_get_property,
  	.external_power_changed = collie_bat_external_power_changed,
  };
f1fce597e   Thomas Kunze   collie: add batte...
261
262
263
  static struct collie_bat collie_bat_bu = {
  	.status = POWER_SUPPLY_STATUS_UNKNOWN,
  	.full_chrg = -1,
297d716f6   Krzysztof Kozlowski   power_supply: Cha...
264
  	.psy = NULL,
f1fce597e   Thomas Kunze   collie: add batte...
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
  
  	.gpio_full = -1,
  	.gpio_charge_on = -1,
  
  	.technology = POWER_SUPPLY_TECHNOLOGY_LiMn,
  
  	.gpio_bat = COLLIE_GPIO_BBAT_ON,
  	.adc_bat = UCB_ADC_INP_AD1,
  	.adc_bat_divider = 155,
  	.bat_max = 3000000,
  	.bat_min = 1900000,
  
  	.gpio_temp = -1,
  	.adc_temp = -1,
  	.adc_temp_divider = -1,
  };
389cd203d   Axel Lin   collie_battery: C...
281
282
283
284
285
286
287
  static struct gpio collie_batt_gpios[] = {
  	{ COLLIE_GPIO_CO,	    GPIOF_IN,		"main battery full" },
  	{ COLLIE_GPIO_MAIN_BAT_LOW, GPIOF_IN,		"main battery low" },
  	{ COLLIE_GPIO_CHARGE_ON,    GPIOF_OUT_INIT_LOW,	"main charge on" },
  	{ COLLIE_GPIO_MBAT_ON,	    GPIOF_OUT_INIT_LOW,	"main battery" },
  	{ COLLIE_GPIO_TMP_ON,	    GPIOF_OUT_INIT_LOW,	"main battery temp" },
  	{ COLLIE_GPIO_BBAT_ON,	    GPIOF_OUT_INIT_LOW,	"backup battery" },
f1fce597e   Thomas Kunze   collie: add batte...
288
289
290
  };
  
  #ifdef CONFIG_PM
1ba641149   Andrea Adami   power supply: col...
291
  static int collie_bat_suspend(struct ucb1x00_dev *dev)
f1fce597e   Thomas Kunze   collie: add batte...
292
293
  {
  	/* flush all pending status updates */
43829731d   Tejun Heo   workqueue: deprec...
294
  	flush_work(&bat_work);
f1300e7f6   Dmitry Eremin-Solenikov   power: collie_bat...
295
296
297
298
299
300
  
  	if (device_may_wakeup(&dev->ucb->dev) &&
  	    collie_bat_main.status == POWER_SUPPLY_STATUS_CHARGING)
  		wakeup_enabled = !enable_irq_wake(gpio_to_irq(COLLIE_GPIO_CO));
  	else
  		wakeup_enabled = 0;
f1fce597e   Thomas Kunze   collie: add batte...
301
302
303
304
305
  	return 0;
  }
  
  static int collie_bat_resume(struct ucb1x00_dev *dev)
  {
f1300e7f6   Dmitry Eremin-Solenikov   power: collie_bat...
306
307
  	if (wakeup_enabled)
  		disable_irq_wake(gpio_to_irq(COLLIE_GPIO_CO));
f1fce597e   Thomas Kunze   collie: add batte...
308
309
310
311
312
313
314
315
  	/* things may have changed while we were away */
  	schedule_work(&bat_work);
  	return 0;
  }
  #else
  #define collie_bat_suspend NULL
  #define collie_bat_resume NULL
  #endif
c8afa6406   Bill Pemberton   power: remove use...
316
  static int collie_bat_probe(struct ucb1x00_dev *dev)
f1fce597e   Thomas Kunze   collie: add batte...
317
318
  {
  	int ret;
297d716f6   Krzysztof Kozlowski   power_supply: Cha...
319
  	struct power_supply_config psy_main_cfg = {}, psy_bu_cfg = {};
f1fce597e   Thomas Kunze   collie: add batte...
320
321
322
323
324
  
  	if (!machine_is_collie())
  		return -ENODEV;
  
  	ucb = dev->ucb;
389cd203d   Axel Lin   collie_battery: C...
325
326
327
328
  	ret = gpio_request_array(collie_batt_gpios,
  				 ARRAY_SIZE(collie_batt_gpios));
  	if (ret)
  		return ret;
f1fce597e   Thomas Kunze   collie: add batte...
329
330
331
332
  
  	mutex_init(&collie_bat_main.work_lock);
  
  	INIT_WORK(&bat_work, collie_bat_work);
297d716f6   Krzysztof Kozlowski   power_supply: Cha...
333
334
335
336
337
338
  	psy_main_cfg.drv_data = &collie_bat_main;
  	collie_bat_main.psy = power_supply_register(&dev->ucb->dev,
  						    &collie_bat_main_desc,
  						    &psy_main_cfg);
  	if (IS_ERR(collie_bat_main.psy)) {
  		ret = PTR_ERR(collie_bat_main.psy);
f1fce597e   Thomas Kunze   collie: add batte...
339
  		goto err_psy_reg_main;
297d716f6   Krzysztof Kozlowski   power_supply: Cha...
340
341
342
343
344
345
346
347
  	}
  
  	psy_main_cfg.drv_data = &collie_bat_bu;
  	collie_bat_bu.psy = power_supply_register(&dev->ucb->dev,
  						  &collie_bat_bu_desc,
  						  &psy_bu_cfg);
  	if (IS_ERR(collie_bat_bu.psy)) {
  		ret = PTR_ERR(collie_bat_bu.psy);
f1fce597e   Thomas Kunze   collie: add batte...
348
  		goto err_psy_reg_bu;
297d716f6   Krzysztof Kozlowski   power_supply: Cha...
349
  	}
f1fce597e   Thomas Kunze   collie: add batte...
350
351
352
353
354
  
  	ret = request_irq(gpio_to_irq(COLLIE_GPIO_CO),
  				collie_bat_gpio_isr,
  				IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
  				"main full", &collie_bat_main);
f1300e7f6   Dmitry Eremin-Solenikov   power: collie_bat...
355
356
357
358
359
360
361
362
363
  	if (ret)
  		goto err_irq;
  
  	device_init_wakeup(&ucb->dev, 1);
  	schedule_work(&bat_work);
  
  	return 0;
  
  err_irq:
297d716f6   Krzysztof Kozlowski   power_supply: Cha...
364
  	power_supply_unregister(collie_bat_bu.psy);
f1fce597e   Thomas Kunze   collie: add batte...
365
  err_psy_reg_bu:
297d716f6   Krzysztof Kozlowski   power_supply: Cha...
366
  	power_supply_unregister(collie_bat_main.psy);
f1fce597e   Thomas Kunze   collie: add batte...
367
368
369
  err_psy_reg_main:
  
  	/* see comment in collie_bat_remove */
bc51e7ff5   Tejun Heo   power_supply: Don...
370
  	cancel_work_sync(&bat_work);
389cd203d   Axel Lin   collie_battery: C...
371
  	gpio_free_array(collie_batt_gpios, ARRAY_SIZE(collie_batt_gpios));
f1fce597e   Thomas Kunze   collie: add batte...
372
373
  	return ret;
  }
415ec69fb   Bill Pemberton   power: remove use...
374
  static void collie_bat_remove(struct ucb1x00_dev *dev)
f1fce597e   Thomas Kunze   collie: add batte...
375
  {
f1fce597e   Thomas Kunze   collie: add batte...
376
  	free_irq(gpio_to_irq(COLLIE_GPIO_CO), &collie_bat_main);
297d716f6   Krzysztof Kozlowski   power_supply: Cha...
377
378
  	power_supply_unregister(collie_bat_bu.psy);
  	power_supply_unregister(collie_bat_main.psy);
f1fce597e   Thomas Kunze   collie: add batte...
379
380
  
  	/*
bc51e7ff5   Tejun Heo   power_supply: Don...
381
382
383
  	 * Now cancel the bat_work.  We won't get any more schedules,
  	 * since all sources (isr and external_power_changed) are
  	 * unregistered now.
f1fce597e   Thomas Kunze   collie: add batte...
384
  	 */
bc51e7ff5   Tejun Heo   power_supply: Don...
385
  	cancel_work_sync(&bat_work);
389cd203d   Axel Lin   collie_battery: C...
386
  	gpio_free_array(collie_batt_gpios, ARRAY_SIZE(collie_batt_gpios));
f1fce597e   Thomas Kunze   collie: add batte...
387
388
389
390
  }
  
  static struct ucb1x00_driver collie_bat_driver = {
  	.add		= collie_bat_probe,
28ea73f4c   Bill Pemberton   power: remove use...
391
  	.remove		= collie_bat_remove,
f1fce597e   Thomas Kunze   collie: add batte...
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
  	.suspend	= collie_bat_suspend,
  	.resume		= collie_bat_resume,
  };
  
  static int __init collie_bat_init(void)
  {
  	return ucb1x00_register_driver(&collie_bat_driver);
  }
  
  static void __exit collie_bat_exit(void)
  {
  	ucb1x00_unregister_driver(&collie_bat_driver);
  }
  
  module_init(collie_bat_init);
  module_exit(collie_bat_exit);
  
  MODULE_LICENSE("GPL");
  MODULE_AUTHOR("Thomas Kunze");
  MODULE_DESCRIPTION("Collie battery driver");