1 /* 2 * AXP20x PMIC USB power supply status driver 3 * 4 * Copyright (C) 2015 Hans de Goede <hdegoede@redhat.com> 5 * Copyright (C) 2014 Bruno Prémont <bonbons@linux-vserver.org> 6 * 7 * This program is free software; you can redistribute it and/or modify it 8 * under the terms of the GNU General Public License as published by the 9 * Free Software Foundation; either version 2 of the License, or (at your 10 * option) any later version. 11 */ 12 13 #include <linux/device.h> 14 #include <linux/init.h> 15 #include <linux/interrupt.h> 16 #include <linux/kernel.h> 17 #include <linux/mfd/axp20x.h> 18 #include <linux/module.h> 19 #include <linux/of.h> 20 #include <linux/platform_device.h> 21 #include <linux/power_supply.h> 22 #include <linux/regmap.h> 23 #include <linux/slab.h> 24 25 #define DRVNAME "axp20x-usb-power-supply" 26 27 #define AXP20X_PWR_STATUS_VBUS_PRESENT BIT(5) 28 #define AXP20X_PWR_STATUS_VBUS_USED BIT(4) 29 30 #define AXP20X_USB_STATUS_VBUS_VALID BIT(2) 31 32 #define AXP20X_VBUS_VHOLD_uV(b) (4000000 + (((b) >> 3) & 7) * 100000) 33 #define AXP20X_VBUS_CLIMIT_MASK 3 34 #define AXP20X_VBUC_CLIMIT_900mA 0 35 #define AXP20X_VBUC_CLIMIT_500mA 1 36 #define AXP20X_VBUC_CLIMIT_100mA 2 37 #define AXP20X_VBUC_CLIMIT_NONE 3 38 39 #define AXP20X_ADC_EN1_VBUS_CURR BIT(2) 40 #define AXP20X_ADC_EN1_VBUS_VOLT BIT(3) 41 42 #define AXP20X_VBUS_MON_VBUS_VALID BIT(3) 43 44 struct axp20x_usb_power { 45 struct device_node *np; 46 struct regmap *regmap; 47 struct power_supply *supply; 48 }; 49 50 static irqreturn_t axp20x_usb_power_irq(int irq, void *devid) 51 { 52 struct axp20x_usb_power *power = devid; 53 54 power_supply_changed(power->supply); 55 56 return IRQ_HANDLED; 57 } 58 59 static int axp20x_usb_power_get_property(struct power_supply *psy, 60 enum power_supply_property psp, union power_supply_propval *val) 61 { 62 struct axp20x_usb_power *power = power_supply_get_drvdata(psy); 63 unsigned int input, v; 64 int ret; 65 66 switch (psp) { 67 case POWER_SUPPLY_PROP_VOLTAGE_MIN: 68 ret = regmap_read(power->regmap, AXP20X_VBUS_IPSOUT_MGMT, &v); 69 if (ret) 70 return ret; 71 72 val->intval = AXP20X_VBUS_VHOLD_uV(v); 73 return 0; 74 case POWER_SUPPLY_PROP_VOLTAGE_NOW: 75 ret = axp20x_read_variable_width(power->regmap, 76 AXP20X_VBUS_V_ADC_H, 12); 77 if (ret < 0) 78 return ret; 79 80 val->intval = ret * 1700; /* 1 step = 1.7 mV */ 81 return 0; 82 case POWER_SUPPLY_PROP_CURRENT_MAX: 83 ret = regmap_read(power->regmap, AXP20X_VBUS_IPSOUT_MGMT, &v); 84 if (ret) 85 return ret; 86 87 switch (v & AXP20X_VBUS_CLIMIT_MASK) { 88 case AXP20X_VBUC_CLIMIT_100mA: 89 if (of_device_is_compatible(power->np, 90 "x-powers,axp202-usb-power-supply")) { 91 val->intval = 100000; 92 } else { 93 val->intval = -1; /* No 100mA limit */ 94 } 95 break; 96 case AXP20X_VBUC_CLIMIT_500mA: 97 val->intval = 500000; 98 break; 99 case AXP20X_VBUC_CLIMIT_900mA: 100 val->intval = 900000; 101 break; 102 case AXP20X_VBUC_CLIMIT_NONE: 103 val->intval = -1; 104 break; 105 } 106 return 0; 107 case POWER_SUPPLY_PROP_CURRENT_NOW: 108 ret = axp20x_read_variable_width(power->regmap, 109 AXP20X_VBUS_I_ADC_H, 12); 110 if (ret < 0) 111 return ret; 112 113 val->intval = ret * 375; /* 1 step = 0.375 mA */ 114 return 0; 115 default: 116 break; 117 } 118 119 /* All the properties below need the input-status reg value */ 120 ret = regmap_read(power->regmap, AXP20X_PWR_INPUT_STATUS, &input); 121 if (ret) 122 return ret; 123 124 switch (psp) { 125 case POWER_SUPPLY_PROP_HEALTH: 126 if (!(input & AXP20X_PWR_STATUS_VBUS_PRESENT)) { 127 val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; 128 break; 129 } 130 131 val->intval = POWER_SUPPLY_HEALTH_GOOD; 132 133 if (of_device_is_compatible(power->np, 134 "x-powers,axp202-usb-power-supply")) { 135 ret = regmap_read(power->regmap, 136 AXP20X_USB_OTG_STATUS, &v); 137 if (ret) 138 return ret; 139 140 if (!(v & AXP20X_USB_STATUS_VBUS_VALID)) 141 val->intval = 142 POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; 143 } 144 break; 145 case POWER_SUPPLY_PROP_PRESENT: 146 val->intval = !!(input & AXP20X_PWR_STATUS_VBUS_PRESENT); 147 break; 148 case POWER_SUPPLY_PROP_ONLINE: 149 val->intval = !!(input & AXP20X_PWR_STATUS_VBUS_USED); 150 break; 151 default: 152 return -EINVAL; 153 } 154 155 return 0; 156 } 157 158 static enum power_supply_property axp20x_usb_power_properties[] = { 159 POWER_SUPPLY_PROP_HEALTH, 160 POWER_SUPPLY_PROP_PRESENT, 161 POWER_SUPPLY_PROP_ONLINE, 162 POWER_SUPPLY_PROP_VOLTAGE_MIN, 163 POWER_SUPPLY_PROP_VOLTAGE_NOW, 164 POWER_SUPPLY_PROP_CURRENT_MAX, 165 POWER_SUPPLY_PROP_CURRENT_NOW, 166 }; 167 168 static enum power_supply_property axp22x_usb_power_properties[] = { 169 POWER_SUPPLY_PROP_HEALTH, 170 POWER_SUPPLY_PROP_PRESENT, 171 POWER_SUPPLY_PROP_ONLINE, 172 POWER_SUPPLY_PROP_VOLTAGE_MIN, 173 POWER_SUPPLY_PROP_CURRENT_MAX, 174 }; 175 176 static const struct power_supply_desc axp20x_usb_power_desc = { 177 .name = "axp20x-usb", 178 .type = POWER_SUPPLY_TYPE_USB, 179 .properties = axp20x_usb_power_properties, 180 .num_properties = ARRAY_SIZE(axp20x_usb_power_properties), 181 .get_property = axp20x_usb_power_get_property, 182 }; 183 184 static const struct power_supply_desc axp22x_usb_power_desc = { 185 .name = "axp20x-usb", 186 .type = POWER_SUPPLY_TYPE_USB, 187 .properties = axp22x_usb_power_properties, 188 .num_properties = ARRAY_SIZE(axp22x_usb_power_properties), 189 .get_property = axp20x_usb_power_get_property, 190 }; 191 192 static int axp20x_usb_power_probe(struct platform_device *pdev) 193 { 194 struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent); 195 struct power_supply_config psy_cfg = {}; 196 struct axp20x_usb_power *power; 197 static const char * const axp20x_irq_names[] = { "VBUS_PLUGIN", 198 "VBUS_REMOVAL", "VBUS_VALID", "VBUS_NOT_VALID", NULL }; 199 static const char * const axp22x_irq_names[] = { 200 "VBUS_PLUGIN", "VBUS_REMOVAL", NULL }; 201 static const char * const *irq_names; 202 const struct power_supply_desc *usb_power_desc; 203 int i, irq, ret; 204 205 if (!of_device_is_available(pdev->dev.of_node)) 206 return -ENODEV; 207 208 if (!axp20x) { 209 dev_err(&pdev->dev, "Parent drvdata not set\n"); 210 return -EINVAL; 211 } 212 213 power = devm_kzalloc(&pdev->dev, sizeof(*power), GFP_KERNEL); 214 if (!power) 215 return -ENOMEM; 216 217 power->np = pdev->dev.of_node; 218 power->regmap = axp20x->regmap; 219 220 if (of_device_is_compatible(power->np, 221 "x-powers,axp202-usb-power-supply")) { 222 /* Enable vbus valid checking */ 223 ret = regmap_update_bits(power->regmap, AXP20X_VBUS_MON, 224 AXP20X_VBUS_MON_VBUS_VALID, 225 AXP20X_VBUS_MON_VBUS_VALID); 226 if (ret) 227 return ret; 228 229 /* Enable vbus voltage and current measurement */ 230 ret = regmap_update_bits(power->regmap, AXP20X_ADC_EN1, 231 AXP20X_ADC_EN1_VBUS_CURR | AXP20X_ADC_EN1_VBUS_VOLT, 232 AXP20X_ADC_EN1_VBUS_CURR | AXP20X_ADC_EN1_VBUS_VOLT); 233 if (ret) 234 return ret; 235 236 usb_power_desc = &axp20x_usb_power_desc; 237 irq_names = axp20x_irq_names; 238 } else if (of_device_is_compatible(power->np, 239 "x-powers,axp221-usb-power-supply")) { 240 usb_power_desc = &axp22x_usb_power_desc; 241 irq_names = axp22x_irq_names; 242 } else { 243 dev_err(&pdev->dev, "Unsupported AXP variant: %ld\n", 244 axp20x->variant); 245 return -EINVAL; 246 } 247 248 psy_cfg.of_node = pdev->dev.of_node; 249 psy_cfg.drv_data = power; 250 251 power->supply = devm_power_supply_register(&pdev->dev, usb_power_desc, 252 &psy_cfg); 253 if (IS_ERR(power->supply)) 254 return PTR_ERR(power->supply); 255 256 /* Request irqs after registering, as irqs may trigger immediately */ 257 for (i = 0; irq_names[i]; i++) { 258 irq = platform_get_irq_byname(pdev, irq_names[i]); 259 if (irq < 0) { 260 dev_warn(&pdev->dev, "No IRQ for %s: %d\n", 261 irq_names[i], irq); 262 continue; 263 } 264 irq = regmap_irq_get_virq(axp20x->regmap_irqc, irq); 265 ret = devm_request_any_context_irq(&pdev->dev, irq, 266 axp20x_usb_power_irq, 0, DRVNAME, power); 267 if (ret < 0) 268 dev_warn(&pdev->dev, "Error requesting %s IRQ: %d\n", 269 irq_names[i], ret); 270 } 271 272 return 0; 273 } 274 275 static const struct of_device_id axp20x_usb_power_match[] = { 276 { .compatible = "x-powers,axp202-usb-power-supply" }, 277 { .compatible = "x-powers,axp221-usb-power-supply" }, 278 { } 279 }; 280 MODULE_DEVICE_TABLE(of, axp20x_usb_power_match); 281 282 static struct platform_driver axp20x_usb_power_driver = { 283 .probe = axp20x_usb_power_probe, 284 .driver = { 285 .name = DRVNAME, 286 .of_match_table = axp20x_usb_power_match, 287 }, 288 }; 289 290 module_platform_driver(axp20x_usb_power_driver); 291 292 MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); 293 MODULE_DESCRIPTION("AXP20x PMIC USB power supply status driver"); 294 MODULE_LICENSE("GPL"); 295