]> git.proxmox.com Git - mirror_ubuntu-eoan-kernel.git/blobdiff - drivers/power/supply/axp20x_usb_power.c
treewide: Replace GPLv2 boilerplate/reference with SPDX - rule 152
[mirror_ubuntu-eoan-kernel.git] / drivers / power / supply / axp20x_usb_power.c
index cd9b90d798394b3bcbb3aecacd969847103cc026..dc4c316eff81b5e26954c751f194bd42520a4206 100644 (file)
@@ -1,13 +1,9 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
 /*
  * AXP20x PMIC USB power supply status driver
  *
  * Copyright (C) 2015 Hans de Goede <hdegoede@redhat.com>
  * Copyright (C) 2014 Bruno PrĂ©mont <bonbons@linux-vserver.org>
- *
- * This program is free software; you can redistribute it and/or modify it
- * under  the terms of the GNU General  Public License as published by the
- * Free Software Foundation;  either version 2 of the License, or (at your
- * option) any later version.
  */
 
 #include <linux/bitops.h>
@@ -24,6 +20,7 @@
 #include <linux/regmap.h>
 #include <linux/slab.h>
 #include <linux/iio/consumer.h>
+#include <linux/workqueue.h>
 
 #define DRVNAME "axp20x-usb-power-supply"
 
 #define AXP20X_VBUS_CLIMIT_100mA       2
 #define AXP20X_VBUS_CLIMIT_NONE                3
 
+#define AXP813_VBUS_CLIMIT_900mA       0
+#define AXP813_VBUS_CLIMIT_1500mA      1
+#define AXP813_VBUS_CLIMIT_2000mA      2
+#define AXP813_VBUS_CLIMIT_2500mA      3
+
 #define AXP20X_ADC_EN1_VBUS_CURR       BIT(2)
 #define AXP20X_ADC_EN1_VBUS_VOLT       BIT(3)
 
 #define AXP20X_VBUS_MON_VBUS_VALID     BIT(3)
 
+/*
+ * Note do not raise the debounce time, we must report Vusb high within
+ * 100ms otherwise we get Vbus errors in musb.
+ */
+#define DEBOUNCE_TIME                  msecs_to_jiffies(50)
+
 struct axp20x_usb_power {
        struct device_node *np;
        struct regmap *regmap;
@@ -53,6 +61,8 @@ struct axp20x_usb_power {
        enum axp20x_variants axp20x_id;
        struct iio_channel *vbus_v;
        struct iio_channel *vbus_i;
+       struct delayed_work vbus_detect;
+       unsigned int old_status;
 };
 
 static irqreturn_t axp20x_usb_power_irq(int irq, void *devid)
@@ -64,6 +74,89 @@ static irqreturn_t axp20x_usb_power_irq(int irq, void *devid)
        return IRQ_HANDLED;
 }
 
+static void axp20x_usb_power_poll_vbus(struct work_struct *work)
+{
+       struct axp20x_usb_power *power =
+               container_of(work, struct axp20x_usb_power, vbus_detect.work);
+       unsigned int val;
+       int ret;
+
+       ret = regmap_read(power->regmap, AXP20X_PWR_INPUT_STATUS, &val);
+       if (ret)
+               goto out;
+
+       val &= (AXP20X_PWR_STATUS_VBUS_PRESENT | AXP20X_PWR_STATUS_VBUS_USED);
+       if (val != power->old_status)
+               power_supply_changed(power->supply);
+
+       power->old_status = val;
+
+out:
+       mod_delayed_work(system_wq, &power->vbus_detect, DEBOUNCE_TIME);
+}
+
+static bool axp20x_usb_vbus_needs_polling(struct axp20x_usb_power *power)
+{
+       if (power->axp20x_id >= AXP221_ID)
+               return true;
+
+       return false;
+}
+
+static int axp20x_get_current_max(struct axp20x_usb_power *power, int *val)
+{
+       unsigned int v;
+       int ret = regmap_read(power->regmap, AXP20X_VBUS_IPSOUT_MGMT, &v);
+
+       if (ret)
+               return ret;
+
+       switch (v & AXP20X_VBUS_CLIMIT_MASK) {
+       case AXP20X_VBUS_CLIMIT_100mA:
+               if (power->axp20x_id == AXP221_ID)
+                       *val = -1; /* No 100mA limit */
+               else
+                       *val = 100000;
+               break;
+       case AXP20X_VBUS_CLIMIT_500mA:
+               *val = 500000;
+               break;
+       case AXP20X_VBUS_CLIMIT_900mA:
+               *val = 900000;
+               break;
+       case AXP20X_VBUS_CLIMIT_NONE:
+               *val = -1;
+               break;
+       }
+
+       return 0;
+}
+
+static int axp813_get_current_max(struct axp20x_usb_power *power, int *val)
+{
+       unsigned int v;
+       int ret = regmap_read(power->regmap, AXP20X_VBUS_IPSOUT_MGMT, &v);
+
+       if (ret)
+               return ret;
+
+       switch (v & AXP20X_VBUS_CLIMIT_MASK) {
+       case AXP813_VBUS_CLIMIT_900mA:
+               *val = 900000;
+               break;
+       case AXP813_VBUS_CLIMIT_1500mA:
+               *val = 1500000;
+               break;
+       case AXP813_VBUS_CLIMIT_2000mA:
+               *val = 2000000;
+               break;
+       case AXP813_VBUS_CLIMIT_2500mA:
+               *val = 2500000;
+               break;
+       }
+       return 0;
+}
+
 static int axp20x_usb_power_get_property(struct power_supply *psy,
        enum power_supply_property psp, union power_supply_propval *val)
 {
@@ -102,28 +195,9 @@ static int axp20x_usb_power_get_property(struct power_supply *psy,
                val->intval = ret * 1700; /* 1 step = 1.7 mV */
                return 0;
        case POWER_SUPPLY_PROP_CURRENT_MAX:
-               ret = regmap_read(power->regmap, AXP20X_VBUS_IPSOUT_MGMT, &v);
-               if (ret)
-                       return ret;
-
-               switch (v & AXP20X_VBUS_CLIMIT_MASK) {
-               case AXP20X_VBUS_CLIMIT_100mA:
-                       if (power->axp20x_id == AXP221_ID)
-                               val->intval = -1; /* No 100mA limit */
-                       else
-                               val->intval = 100000;
-                       break;
-               case AXP20X_VBUS_CLIMIT_500mA:
-                       val->intval = 500000;
-                       break;
-               case AXP20X_VBUS_CLIMIT_900mA:
-                       val->intval = 900000;
-                       break;
-               case AXP20X_VBUS_CLIMIT_NONE:
-                       val->intval = -1;
-                       break;
-               }
-               return 0;
+               if (power->axp20x_id == AXP813_ID)
+                       return axp813_get_current_max(power, &val->intval);
+               return axp20x_get_current_max(power, &val->intval);
        case POWER_SUPPLY_PROP_CURRENT_NOW:
                if (IS_ENABLED(CONFIG_AXP20X_ADC)) {
                        ret = iio_read_channel_processed(power->vbus_i,
@@ -214,6 +288,31 @@ static int axp20x_usb_power_set_voltage_min(struct axp20x_usb_power *power,
        return -EINVAL;
 }
 
+static int axp813_usb_power_set_current_max(struct axp20x_usb_power *power,
+                                           int intval)
+{
+       int val;
+
+       switch (intval) {
+       case 900000:
+               return regmap_update_bits(power->regmap,
+                                         AXP20X_VBUS_IPSOUT_MGMT,
+                                         AXP20X_VBUS_CLIMIT_MASK,
+                                         AXP813_VBUS_CLIMIT_900mA);
+       case 1500000:
+       case 2000000:
+       case 2500000:
+               val = (intval - 1000000) / 500000;
+               return regmap_update_bits(power->regmap,
+                                         AXP20X_VBUS_IPSOUT_MGMT,
+                                         AXP20X_VBUS_CLIMIT_MASK, val);
+       default:
+               return -EINVAL;
+       }
+
+       return -EINVAL;
+}
+
 static int axp20x_usb_power_set_current_max(struct axp20x_usb_power *power,
                                            int intval)
 {
@@ -248,6 +347,9 @@ static int axp20x_usb_power_set_property(struct power_supply *psy,
                return axp20x_usb_power_set_voltage_min(power, val->intval);
 
        case POWER_SUPPLY_PROP_CURRENT_MAX:
+               if (power->axp20x_id == AXP813_ID)
+                       return axp813_usb_power_set_current_max(power,
+                                                               val->intval);
                return axp20x_usb_power_set_current_max(power, val->intval);
 
        default:
@@ -357,6 +459,7 @@ static int axp20x_usb_power_probe(struct platform_device *pdev)
        if (!power)
                return -ENOMEM;
 
+       platform_set_drvdata(pdev, power);
        power->axp20x_id = (enum axp20x_variants)of_device_get_match_data(
                                                                &pdev->dev);
 
@@ -382,7 +485,8 @@ static int axp20x_usb_power_probe(struct platform_device *pdev)
                usb_power_desc = &axp20x_usb_power_desc;
                irq_names = axp20x_irq_names;
        } else if (power->axp20x_id == AXP221_ID ||
-                  power->axp20x_id == AXP223_ID) {
+                  power->axp20x_id == AXP223_ID ||
+                  power->axp20x_id == AXP813_ID) {
                usb_power_desc = &axp22x_usb_power_desc;
                irq_names = axp22x_irq_names;
        } else {
@@ -415,6 +519,19 @@ static int axp20x_usb_power_probe(struct platform_device *pdev)
                                 irq_names[i], ret);
        }
 
+       INIT_DELAYED_WORK(&power->vbus_detect, axp20x_usb_power_poll_vbus);
+       if (axp20x_usb_vbus_needs_polling(power))
+               queue_delayed_work(system_wq, &power->vbus_detect, 0);
+
+       return 0;
+}
+
+static int axp20x_usb_power_remove(struct platform_device *pdev)
+{
+       struct axp20x_usb_power *power = platform_get_drvdata(pdev);
+
+       cancel_delayed_work_sync(&power->vbus_detect);
+
        return 0;
 }
 
@@ -428,12 +545,16 @@ static const struct of_device_id axp20x_usb_power_match[] = {
        }, {
                .compatible = "x-powers,axp223-usb-power-supply",
                .data = (void *)AXP223_ID,
+       }, {
+               .compatible = "x-powers,axp813-usb-power-supply",
+               .data = (void *)AXP813_ID,
        }, { /* sentinel */ }
 };
 MODULE_DEVICE_TABLE(of, axp20x_usb_power_match);
 
 static struct platform_driver axp20x_usb_power_driver = {
        .probe = axp20x_usb_power_probe,
+       .remove = axp20x_usb_power_remove,
        .driver = {
                .name = DRVNAME,
                .of_match_table = axp20x_usb_power_match,