diff options
Diffstat (limited to 'drivers/power/supply/axp20x_usb_power.c')
-rw-r--r-- | drivers/power/supply/axp20x_usb_power.c | 179 |
1 files changed, 152 insertions, 27 deletions
diff --git a/drivers/power/supply/axp20x_usb_power.c b/drivers/power/supply/axp20x_usb_power.c index f52fe77edb6f..d2b1255ee1cc 100644 --- a/drivers/power/supply/axp20x_usb_power.c +++ b/drivers/power/supply/axp20x_usb_power.c @@ -24,6 +24,7 @@ #include <linux/regmap.h> #include <linux/slab.h> #include <linux/iio/consumer.h> +#include <linux/workqueue.h> #define DRVNAME "axp20x-usb-power-supply" @@ -36,16 +37,27 @@ #define AXP20X_VBUS_VHOLD_MASK GENMASK(5, 3) #define AXP20X_VBUS_VHOLD_OFFSET 3 #define AXP20X_VBUS_CLIMIT_MASK 3 -#define AXP20X_VBUC_CLIMIT_900mA 0 -#define AXP20X_VBUC_CLIMIT_500mA 1 -#define AXP20X_VBUC_CLIMIT_100mA 2 -#define AXP20X_VBUC_CLIMIT_NONE 3 +#define AXP20X_VBUS_CLIMIT_900mA 0 +#define AXP20X_VBUS_CLIMIT_500mA 1 +#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 +65,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 +78,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 +199,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_VBUC_CLIMIT_100mA: - if (power->axp20x_id == AXP221_ID) - val->intval = -1; /* No 100mA limit */ - else - val->intval = 100000; - break; - case AXP20X_VBUC_CLIMIT_500mA: - val->intval = 500000; - break; - case AXP20X_VBUC_CLIMIT_900mA: - val->intval = 900000; - break; - case AXP20X_VBUC_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 +292,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 +351,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 +463,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 +489,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 +523,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 +549,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, |