|
16 | 16 | #include <linux/of.h>
|
17 | 17 | #include <linux/of_device.h>
|
18 | 18 | #include <linux/platform_device.h>
|
| 19 | +#include <linux/pm.h> |
19 | 20 | #include <linux/power_supply.h>
|
20 | 21 | #include <linux/regmap.h>
|
21 | 22 | #include <linux/slab.h>
|
@@ -67,6 +68,8 @@ struct axp20x_usb_power {
|
67 | 68 | struct iio_channel *vbus_i;
|
68 | 69 | struct delayed_work vbus_detect;
|
69 | 70 | unsigned int old_status;
|
| 71 | + unsigned int num_irqs; |
| 72 | + unsigned int irqs[]; |
70 | 73 | };
|
71 | 74 |
|
72 | 75 | static irqreturn_t axp20x_usb_power_irq(int irq, void *devid)
|
@@ -440,45 +443,85 @@ static const char * const axp20x_irq_names[] = {
|
440 | 443 | "VBUS_REMOVAL",
|
441 | 444 | "VBUS_VALID",
|
442 | 445 | "VBUS_NOT_VALID",
|
443 |
| - NULL |
444 | 446 | };
|
445 | 447 |
|
446 | 448 | static const char * const axp22x_irq_names[] = {
|
447 | 449 | "VBUS_PLUGIN",
|
448 | 450 | "VBUS_REMOVAL",
|
449 |
| - NULL |
450 | 451 | };
|
451 | 452 |
|
452 | 453 | struct axp_data {
|
453 | 454 | const struct power_supply_desc *power_desc;
|
454 | 455 | const char * const *irq_names;
|
| 456 | + unsigned int num_irq_names; |
455 | 457 | enum axp20x_variants axp20x_id;
|
456 | 458 | };
|
457 | 459 |
|
458 | 460 | static const struct axp_data axp202_data = {
|
459 | 461 | .power_desc = &axp20x_usb_power_desc,
|
460 | 462 | .irq_names = axp20x_irq_names,
|
| 463 | + .num_irq_names = ARRAY_SIZE(axp20x_irq_names), |
461 | 464 | .axp20x_id = AXP202_ID,
|
462 | 465 | };
|
463 | 466 |
|
464 | 467 | static const struct axp_data axp221_data = {
|
465 | 468 | .power_desc = &axp22x_usb_power_desc,
|
466 | 469 | .irq_names = axp22x_irq_names,
|
| 470 | + .num_irq_names = ARRAY_SIZE(axp22x_irq_names), |
467 | 471 | .axp20x_id = AXP221_ID,
|
468 | 472 | };
|
469 | 473 |
|
470 | 474 | static const struct axp_data axp223_data = {
|
471 | 475 | .power_desc = &axp22x_usb_power_desc,
|
472 | 476 | .irq_names = axp22x_irq_names,
|
| 477 | + .num_irq_names = ARRAY_SIZE(axp22x_irq_names), |
473 | 478 | .axp20x_id = AXP223_ID,
|
474 | 479 | };
|
475 | 480 |
|
476 | 481 | static const struct axp_data axp813_data = {
|
477 | 482 | .power_desc = &axp22x_usb_power_desc,
|
478 | 483 | .irq_names = axp22x_irq_names,
|
| 484 | + .num_irq_names = ARRAY_SIZE(axp22x_irq_names), |
479 | 485 | .axp20x_id = AXP813_ID,
|
480 | 486 | };
|
481 | 487 |
|
| 488 | +#ifdef CONFIG_PM_SLEEP |
| 489 | +static int axp20x_usb_power_suspend(struct device *dev) |
| 490 | +{ |
| 491 | + struct axp20x_usb_power *power = dev_get_drvdata(dev); |
| 492 | + int i = 0; |
| 493 | + |
| 494 | + /* |
| 495 | + * Allow wake via VBUS_PLUGIN only. |
| 496 | + * |
| 497 | + * As nested threaded IRQs are not automatically disabled during |
| 498 | + * suspend, we must explicitly disable the remainder of the IRQs. |
| 499 | + */ |
| 500 | + if (device_may_wakeup(&power->supply->dev)) |
| 501 | + enable_irq_wake(power->irqs[i++]); |
| 502 | + while (i < power->num_irqs) |
| 503 | + disable_irq(power->irqs[i++]); |
| 504 | + |
| 505 | + return 0; |
| 506 | +} |
| 507 | + |
| 508 | +static int axp20x_usb_power_resume(struct device *dev) |
| 509 | +{ |
| 510 | + struct axp20x_usb_power *power = dev_get_drvdata(dev); |
| 511 | + int i = 0; |
| 512 | + |
| 513 | + if (device_may_wakeup(&power->supply->dev)) |
| 514 | + disable_irq_wake(power->irqs[i++]); |
| 515 | + while (i < power->num_irqs) |
| 516 | + enable_irq(power->irqs[i++]); |
| 517 | + |
| 518 | + return 0; |
| 519 | +} |
| 520 | +#endif |
| 521 | + |
| 522 | +static SIMPLE_DEV_PM_OPS(axp20x_usb_power_pm_ops, axp20x_usb_power_suspend, |
| 523 | + axp20x_usb_power_resume); |
| 524 | + |
482 | 525 | static int configure_iio_channels(struct platform_device *pdev,
|
483 | 526 | struct axp20x_usb_power *power)
|
484 | 527 | {
|
@@ -525,15 +568,19 @@ static int axp20x_usb_power_probe(struct platform_device *pdev)
|
525 | 568 | return -EINVAL;
|
526 | 569 | }
|
527 | 570 |
|
528 |
| - power = devm_kzalloc(&pdev->dev, sizeof(*power), GFP_KERNEL); |
| 571 | + axp_data = of_device_get_match_data(&pdev->dev); |
| 572 | + |
| 573 | + power = devm_kzalloc(&pdev->dev, |
| 574 | + struct_size(power, irqs, axp_data->num_irq_names), |
| 575 | + GFP_KERNEL); |
529 | 576 | if (!power)
|
530 | 577 | return -ENOMEM;
|
531 | 578 |
|
532 | 579 | platform_set_drvdata(pdev, power);
|
533 | 580 |
|
534 |
| - axp_data = of_device_get_match_data(&pdev->dev); |
535 | 581 | power->axp20x_id = axp_data->axp20x_id;
|
536 | 582 | power->regmap = axp20x->regmap;
|
| 583 | + power->num_irqs = axp_data->num_irq_names; |
537 | 584 |
|
538 | 585 | if (power->axp20x_id == AXP202_ID) {
|
539 | 586 | /* Enable vbus valid checking */
|
@@ -568,19 +615,22 @@ static int axp20x_usb_power_probe(struct platform_device *pdev)
|
568 | 615 | return PTR_ERR(power->supply);
|
569 | 616 |
|
570 | 617 | /* Request irqs after registering, as irqs may trigger immediately */
|
571 |
| - for (i = 0; axp_data->irq_names[i]; i++) { |
| 618 | + for (i = 0; i < axp_data->num_irq_names; i++) { |
572 | 619 | irq = platform_get_irq_byname(pdev, axp_data->irq_names[i]);
|
573 | 620 | if (irq < 0) {
|
574 |
| - dev_warn(&pdev->dev, "No IRQ for %s: %d\n", |
575 |
| - axp_data->irq_names[i], irq); |
576 |
| - continue; |
| 621 | + dev_err(&pdev->dev, "No IRQ for %s: %d\n", |
| 622 | + axp_data->irq_names[i], irq); |
| 623 | + return irq; |
| 624 | + } |
| 625 | + power->irqs[i] = regmap_irq_get_virq(axp20x->regmap_irqc, irq); |
| 626 | + ret = devm_request_any_context_irq(&pdev->dev, power->irqs[i], |
| 627 | + axp20x_usb_power_irq, 0, |
| 628 | + DRVNAME, power); |
| 629 | + if (ret < 0) { |
| 630 | + dev_err(&pdev->dev, "Error requesting %s IRQ: %d\n", |
| 631 | + axp_data->irq_names[i], ret); |
| 632 | + return ret; |
577 | 633 | }
|
578 |
| - irq = regmap_irq_get_virq(axp20x->regmap_irqc, irq); |
579 |
| - ret = devm_request_any_context_irq(&pdev->dev, irq, |
580 |
| - axp20x_usb_power_irq, 0, DRVNAME, power); |
581 |
| - if (ret < 0) |
582 |
| - dev_warn(&pdev->dev, "Error requesting %s IRQ: %d\n", |
583 |
| - axp_data->irq_names[i], ret); |
584 | 634 | }
|
585 | 635 |
|
586 | 636 | INIT_DELAYED_WORK(&power->vbus_detect, axp20x_usb_power_poll_vbus);
|
@@ -620,8 +670,9 @@ static struct platform_driver axp20x_usb_power_driver = {
|
620 | 670 | .probe = axp20x_usb_power_probe,
|
621 | 671 | .remove = axp20x_usb_power_remove,
|
622 | 672 | .driver = {
|
623 |
| - .name = DRVNAME, |
624 |
| - .of_match_table = axp20x_usb_power_match, |
| 673 | + .name = DRVNAME, |
| 674 | + .of_match_table = axp20x_usb_power_match, |
| 675 | + .pm = &axp20x_usb_power_pm_ops, |
625 | 676 | },
|
626 | 677 | };
|
627 | 678 |
|
|
0 commit comments