pda_power.c 11.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/*
 * Common power driver for PDAs and phones with one or two external
 * power supplies (AC/USB) connected to main and backup batteries,
 * and optional builtin charger.
 *
 * Copyright © 2007 Anton Vorontsov <cbou@mail.ru>
 *
 * 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/module.h>
#include <linux/platform_device.h>
15
#include <linux/err.h>
16
#include <linux/interrupt.h>
17
#include <linux/notifier.h>
18 19
#include <linux/power_supply.h>
#include <linux/pda_power.h>
20
#include <linux/regulator/consumer.h>
21 22
#include <linux/timer.h>
#include <linux/jiffies.h>
23
#include <linux/usb/otg.h>
24 25 26

static inline unsigned int get_irq_flags(struct resource *res)
{
27
	unsigned int flags = IRQF_SAMPLE_RANDOM | IRQF_SHARED;
28 29 30 31 32 33 34 35 36 37 38

	flags |= res->flags & IRQF_TRIGGER_MASK;

	return flags;
}

static struct device *dev;
static struct pda_power_pdata *pdata;
static struct resource *ac_irq, *usb_irq;
static struct timer_list charger_timer;
static struct timer_list supply_timer;
A
Anton Vorontsov 已提交
39 40
static struct timer_list polling_timer;
static int polling;
41

42
#ifdef CONFIG_USB_OTG_UTILS
43
static struct usb_phy *transceiver;
44
static struct notifier_block otg_nb;
45 46
#endif

47 48
static struct regulator *ac_draw;

A
Anton Vorontsov 已提交
49 50 51 52 53 54 55 56 57 58
enum {
	PDA_PSY_OFFLINE = 0,
	PDA_PSY_ONLINE = 1,
	PDA_PSY_TO_CHANGE,
};
static int new_ac_status = -1;
static int new_usb_status = -1;
static int ac_status = -1;
static int usb_status = -1;

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
static int pda_power_get_property(struct power_supply *psy,
				  enum power_supply_property psp,
				  union power_supply_propval *val)
{
	switch (psp) {
	case POWER_SUPPLY_PROP_ONLINE:
		if (psy->type == POWER_SUPPLY_TYPE_MAINS)
			val->intval = pdata->is_ac_online ?
				      pdata->is_ac_online() : 0;
		else
			val->intval = pdata->is_usb_online ?
				      pdata->is_usb_online() : 0;
		break;
	default:
		return -EINVAL;
	}
	return 0;
}

static enum power_supply_property pda_power_props[] = {
	POWER_SUPPLY_PROP_ONLINE,
};

static char *pda_power_supplied_to[] = {
	"main-battery",
	"backup-battery",
};

A
Anton Vorontsov 已提交
87 88 89 90 91 92 93 94
static struct power_supply pda_psy_ac = {
	.name = "ac",
	.type = POWER_SUPPLY_TYPE_MAINS,
	.supplied_to = pda_power_supplied_to,
	.num_supplicants = ARRAY_SIZE(pda_power_supplied_to),
	.properties = pda_power_props,
	.num_properties = ARRAY_SIZE(pda_power_props),
	.get_property = pda_power_get_property,
95 96
};

A
Anton Vorontsov 已提交
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
static struct power_supply pda_psy_usb = {
	.name = "usb",
	.type = POWER_SUPPLY_TYPE_USB,
	.supplied_to = pda_power_supplied_to,
	.num_supplicants = ARRAY_SIZE(pda_power_supplied_to),
	.properties = pda_power_props,
	.num_properties = ARRAY_SIZE(pda_power_props),
	.get_property = pda_power_get_property,
};

static void update_status(void)
{
	if (pdata->is_ac_online)
		new_ac_status = !!pdata->is_ac_online();

	if (pdata->is_usb_online)
		new_usb_status = !!pdata->is_usb_online();
}

116 117
static void update_charger(void)
{
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
	static int regulator_enabled;
	int max_uA = pdata->ac_max_uA;

	if (pdata->set_charge) {
		if (new_ac_status > 0) {
			dev_dbg(dev, "charger on (AC)\n");
			pdata->set_charge(PDA_POWER_CHARGE_AC);
		} else if (new_usb_status > 0) {
			dev_dbg(dev, "charger on (USB)\n");
			pdata->set_charge(PDA_POWER_CHARGE_USB);
		} else {
			dev_dbg(dev, "charger off\n");
			pdata->set_charge(0);
		}
	} else if (ac_draw) {
		if (new_ac_status > 0) {
			regulator_set_current_limit(ac_draw, max_uA, max_uA);
			if (!regulator_enabled) {
				dev_dbg(dev, "charger on (AC)\n");
				regulator_enable(ac_draw);
				regulator_enabled = 1;
			}
		} else {
			if (regulator_enabled) {
				dev_dbg(dev, "charger off\n");
				regulator_disable(ac_draw);
				regulator_enabled = 0;
			}
		}
147 148 149
	}
}

A
Anton Vorontsov 已提交
150
static void supply_timer_func(unsigned long unused)
151
{
A
Anton Vorontsov 已提交
152 153 154 155
	if (ac_status == PDA_PSY_TO_CHANGE) {
		ac_status = new_ac_status;
		power_supply_changed(&pda_psy_ac);
	}
J
Jeff Garzik 已提交
156

A
Anton Vorontsov 已提交
157 158 159 160
	if (usb_status == PDA_PSY_TO_CHANGE) {
		usb_status = new_usb_status;
		power_supply_changed(&pda_psy_usb);
	}
161 162
}

A
Anton Vorontsov 已提交
163
static void psy_changed(void)
164 165 166
{
	update_charger();

A
Anton Vorontsov 已提交
167 168 169 170
	/*
	 * Okay, charger set. Now wait a bit before notifying supplicants,
	 * charge power should stabilize.
	 */
171 172 173 174
	mod_timer(&supply_timer,
		  jiffies + msecs_to_jiffies(pdata->wait_for_charger));
}

A
Anton Vorontsov 已提交
175 176 177 178 179 180
static void charger_timer_func(unsigned long unused)
{
	update_status();
	psy_changed();
}

J
Jeff Garzik 已提交
181
static irqreturn_t power_changed_isr(int irq, void *power_supply)
182
{
A
Anton Vorontsov 已提交
183 184 185 186 187 188 189 190 191 192 193
	if (power_supply == &pda_psy_ac)
		ac_status = PDA_PSY_TO_CHANGE;
	else if (power_supply == &pda_psy_usb)
		usb_status = PDA_PSY_TO_CHANGE;
	else
		return IRQ_NONE;

	/*
	 * Wait a bit before reading ac/usb line status and setting charger,
	 * because ac/usb status readings may lag from irq.
	 */
194 195
	mod_timer(&charger_timer,
		  jiffies + msecs_to_jiffies(pdata->wait_for_status));
A
Anton Vorontsov 已提交
196

197 198 199
	return IRQ_HANDLED;
}

A
Anton Vorontsov 已提交
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
static void polling_timer_func(unsigned long unused)
{
	int changed = 0;

	dev_dbg(dev, "polling...\n");

	update_status();

	if (!ac_irq && new_ac_status != ac_status) {
		ac_status = PDA_PSY_TO_CHANGE;
		changed = 1;
	}

	if (!usb_irq && new_usb_status != usb_status) {
		usb_status = PDA_PSY_TO_CHANGE;
		changed = 1;
	}

	if (changed)
		psy_changed();

	mod_timer(&polling_timer,
		  jiffies + msecs_to_jiffies(pdata->polling_interval));
}

225 226 227
#ifdef CONFIG_USB_OTG_UTILS
static int otg_is_usb_online(void)
{
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263
	return (transceiver->last_event == USB_EVENT_VBUS ||
		transceiver->last_event == USB_EVENT_ENUMERATED);
}

static int otg_is_ac_online(void)
{
	return (transceiver->last_event == USB_EVENT_CHARGER);
}

static int otg_handle_notification(struct notifier_block *nb,
		unsigned long event, void *unused)
{
	switch (event) {
	case USB_EVENT_CHARGER:
		ac_status = PDA_PSY_TO_CHANGE;
		break;
	case USB_EVENT_VBUS:
	case USB_EVENT_ENUMERATED:
		usb_status = PDA_PSY_TO_CHANGE;
		break;
	case USB_EVENT_NONE:
		ac_status = PDA_PSY_TO_CHANGE;
		usb_status = PDA_PSY_TO_CHANGE;
		break;
	default:
		return NOTIFY_OK;
	}

	/*
	 * Wait a bit before reading ac/usb line status and setting charger,
	 * because ac/usb status readings may lag from irq.
	 */
	mod_timer(&charger_timer,
		  jiffies + msecs_to_jiffies(pdata->wait_for_status));

	return NOTIFY_OK;
264 265 266
}
#endif

267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
static int pda_power_probe(struct platform_device *pdev)
{
	int ret = 0;

	dev = &pdev->dev;

	if (pdev->id != -1) {
		dev_err(dev, "it's meaningless to register several "
			"pda_powers; use id = -1\n");
		ret = -EINVAL;
		goto wrongid;
	}

	pdata = pdev->dev.platform_data;

282 283 284 285 286 287
	if (pdata->init) {
		ret = pdata->init(dev);
		if (ret < 0)
			goto init_failed;
	}

A
Anton Vorontsov 已提交
288
	update_status();
289 290 291 292 293 294 295 296
	update_charger();

	if (!pdata->wait_for_status)
		pdata->wait_for_status = 500;

	if (!pdata->wait_for_charger)
		pdata->wait_for_charger = 500;

A
Anton Vorontsov 已提交
297 298 299
	if (!pdata->polling_interval)
		pdata->polling_interval = 2000;

300 301 302
	if (!pdata->ac_max_uA)
		pdata->ac_max_uA = 500000;

303 304 305 306 307 308 309
	setup_timer(&charger_timer, charger_timer_func, 0);
	setup_timer(&supply_timer, supply_timer_func, 0);

	ac_irq = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "ac");
	usb_irq = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "usb");

	if (pdata->supplied_to) {
A
Anton Vorontsov 已提交
310 311 312 313
		pda_psy_ac.supplied_to = pdata->supplied_to;
		pda_psy_ac.num_supplicants = pdata->num_supplicants;
		pda_psy_usb.supplied_to = pdata->supplied_to;
		pda_psy_usb.num_supplicants = pdata->num_supplicants;
314 315
	}

316 317 318 319 320 321 322
	ac_draw = regulator_get(dev, "ac_draw");
	if (IS_ERR(ac_draw)) {
		dev_dbg(dev, "couldn't get ac_draw regulator\n");
		ac_draw = NULL;
		ret = PTR_ERR(ac_draw);
	}

323
#ifdef CONFIG_USB_OTG_UTILS
324
	transceiver = usb_get_phy(USB_PHY_TYPE_USB2);
325 326 327 328 329 330
	if (transceiver && !pdata->is_usb_online) {
		pdata->is_usb_online = otg_is_usb_online;
	}
	if (transceiver && !pdata->is_ac_online) {
		pdata->is_ac_online = otg_is_ac_online;
	}
331
#endif
332

333
	if (pdata->is_ac_online) {
A
Anton Vorontsov 已提交
334
		ret = power_supply_register(&pdev->dev, &pda_psy_ac);
335 336
		if (ret) {
			dev_err(dev, "failed to register %s power supply\n",
A
Anton Vorontsov 已提交
337
				pda_psy_ac.name);
338 339
			goto ac_supply_failed;
		}
340

341 342 343
		if (ac_irq) {
			ret = request_irq(ac_irq->start, power_changed_isr,
					  get_irq_flags(ac_irq), ac_irq->name,
A
Anton Vorontsov 已提交
344
					  &pda_psy_ac);
345 346 347 348
			if (ret) {
				dev_err(dev, "request ac irq failed\n");
				goto ac_irq_failed;
			}
A
Anton Vorontsov 已提交
349 350
		} else {
			polling = 1;
351
		}
352 353
	}

354
	if (pdata->is_usb_online) {
A
Anton Vorontsov 已提交
355
		ret = power_supply_register(&pdev->dev, &pda_psy_usb);
356
		if (ret) {
357
			dev_err(dev, "failed to register %s power supply\n",
A
Anton Vorontsov 已提交
358
				pda_psy_usb.name);
359
			goto usb_supply_failed;
360 361
		}

362 363 364
		if (usb_irq) {
			ret = request_irq(usb_irq->start, power_changed_isr,
					  get_irq_flags(usb_irq),
A
Anton Vorontsov 已提交
365
					  usb_irq->name, &pda_psy_usb);
366 367 368 369
			if (ret) {
				dev_err(dev, "request usb irq failed\n");
				goto usb_irq_failed;
			}
A
Anton Vorontsov 已提交
370 371
		} else {
			polling = 1;
372 373 374
		}
	}

375
#ifdef CONFIG_USB_OTG_UTILS
376 377
	if (transceiver && pdata->use_otg_notifier) {
		otg_nb.notifier_call = otg_handle_notification;
378
		ret = usb_register_notifier(transceiver, &otg_nb);
379 380 381 382 383 384
		if (ret) {
			dev_err(dev, "failure to register otg notifier\n");
			goto otg_reg_notifier_failed;
		}
		polling = 0;
	}
385
#endif
386

A
Anton Vorontsov 已提交
387 388 389 390 391 392 393 394 395
	if (polling) {
		dev_dbg(dev, "will poll for status\n");
		setup_timer(&polling_timer, polling_timer_func, 0);
		mod_timer(&polling_timer,
			  jiffies + msecs_to_jiffies(pdata->polling_interval));
	}

	if (ac_irq || usb_irq)
		device_init_wakeup(&pdev->dev, 1);
396

397
	return 0;
398

399
#ifdef CONFIG_USB_OTG_UTILS
400 401 402
otg_reg_notifier_failed:
	if (pdata->is_usb_online && usb_irq)
		free_irq(usb_irq->start, &pda_psy_usb);
403
#endif
404
usb_irq_failed:
405
	if (pdata->is_usb_online)
A
Anton Vorontsov 已提交
406
		power_supply_unregister(&pda_psy_usb);
407 408
usb_supply_failed:
	if (pdata->is_ac_online && ac_irq)
A
Anton Vorontsov 已提交
409
		free_irq(ac_irq->start, &pda_psy_ac);
410
#ifdef CONFIG_USB_OTG_UTILS
411
	if (transceiver)
412
		usb_put_phy(transceiver);
413
#endif
414
ac_irq_failed:
415
	if (pdata->is_ac_online)
A
Anton Vorontsov 已提交
416
		power_supply_unregister(&pda_psy_ac);
417
ac_supply_failed:
418 419 420 421
	if (ac_draw) {
		regulator_put(ac_draw);
		ac_draw = NULL;
	}
422 423 424
	if (pdata->exit)
		pdata->exit(dev);
init_failed:
425 426 427 428 429 430
wrongid:
	return ret;
}

static int pda_power_remove(struct platform_device *pdev)
{
431
	if (pdata->is_usb_online && usb_irq)
A
Anton Vorontsov 已提交
432
		free_irq(usb_irq->start, &pda_psy_usb);
433
	if (pdata->is_ac_online && ac_irq)
A
Anton Vorontsov 已提交
434 435
		free_irq(ac_irq->start, &pda_psy_ac);

A
Anton Vorontsov 已提交
436 437
	if (polling)
		del_timer_sync(&polling_timer);
438 439
	del_timer_sync(&charger_timer);
	del_timer_sync(&supply_timer);
A
Anton Vorontsov 已提交
440

441
	if (pdata->is_usb_online)
A
Anton Vorontsov 已提交
442
		power_supply_unregister(&pda_psy_usb);
443
	if (pdata->is_ac_online)
A
Anton Vorontsov 已提交
444
		power_supply_unregister(&pda_psy_ac);
445 446
#ifdef CONFIG_USB_OTG_UTILS
	if (transceiver)
447
		usb_put_phy(transceiver);
448 449 450 451 452
#endif
	if (ac_draw) {
		regulator_put(ac_draw);
		ac_draw = NULL;
	}
453 454
	if (pdata->exit)
		pdata->exit(dev);
A
Anton Vorontsov 已提交
455

456 457 458
	return 0;
}

459
#ifdef CONFIG_PM
460 461 462
static int ac_wakeup_enabled;
static int usb_wakeup_enabled;

463 464
static int pda_power_suspend(struct platform_device *pdev, pm_message_t state)
{
465 466 467 468 469 470 471
	if (pdata->suspend) {
		int ret = pdata->suspend(state);

		if (ret)
			return ret;
	}

472 473
	if (device_may_wakeup(&pdev->dev)) {
		if (ac_irq)
474
			ac_wakeup_enabled = !enable_irq_wake(ac_irq->start);
475
		if (usb_irq)
476
			usb_wakeup_enabled = !enable_irq_wake(usb_irq->start);
477 478 479 480 481 482 483 484
	}

	return 0;
}

static int pda_power_resume(struct platform_device *pdev)
{
	if (device_may_wakeup(&pdev->dev)) {
485
		if (usb_irq && usb_wakeup_enabled)
486
			disable_irq_wake(usb_irq->start);
487
		if (ac_irq && ac_wakeup_enabled)
488 489 490
			disable_irq_wake(ac_irq->start);
	}

491 492 493
	if (pdata->resume)
		return pdata->resume();

494 495 496 497 498 499 500
	return 0;
}
#else
#define pda_power_suspend NULL
#define pda_power_resume NULL
#endif /* CONFIG_PM */

501 502 503 504 505 506
static struct platform_driver pda_power_pdrv = {
	.driver = {
		.name = "pda-power",
	},
	.probe = pda_power_probe,
	.remove = pda_power_remove,
507 508
	.suspend = pda_power_suspend,
	.resume = pda_power_resume,
509 510
};

511
module_platform_driver(pda_power_pdrv);
512 513 514

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Anton Vorontsov <cbou@mail.ru>");
515
MODULE_ALIAS("platform:pda-power");