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/of_device.h> 21 #include <linux/platform_device.h> 22 #include <linux/power_supply.h> 23 #include <linux/regmap.h> 24 #include <linux/slab.h> 25 #include <linux/iio/consumer.h> 26 27 #define DRVNAME "axp20x-usb-power-supply" 28 29 #define AXP20X_PWR_STATUS_VBUS_PRESENT BIT(5) 30 #define AXP20X_PWR_STATUS_VBUS_USED BIT(4) 31 32 #define AXP20X_USB_STATUS_VBUS_VALID BIT(2) 33 34 #define AXP20X_VBUS_VHOLD_uV(b) (4000000 + (((b) >> 3) & 7) * 100000) 35 #define AXP20X_VBUS_VHOLD_MASK GENMASK(5, 3) 36 #define AXP20X_VBUS_VHOLD_OFFSET 3 37 #define AXP20X_VBUS_CLIMIT_MASK 3 38 #define AXP20X_VBUC_CLIMIT_900mA 0 39 #define AXP20X_VBUC_CLIMIT_500mA 1 40 #define AXP20X_VBUC_CLIMIT_100mA 2 41 #define AXP20X_VBUC_CLIMIT_NONE 3 42 43 #define AXP20X_ADC_EN1_VBUS_CURR BIT(2) 44 #define AXP20X_ADC_EN1_VBUS_VOLT BIT(3) 45 46 #define AXP20X_VBUS_MON_VBUS_VALID BIT(3) 47 48 struct axp20x_usb_power { 49 struct device_node *np; 50 struct regmap *regmap; 51 struct power_supply *supply; 52 enum axp20x_variants axp20x_id; 53 struct iio_channel *vbus_v; 54 struct iio_channel *vbus_i; 55 }; 56 57 static irqreturn_t axp20x_usb_power_irq(int irq, void *devid) 58 { 59 struct axp20x_usb_power *power = devid; 60 61 power_supply_changed(power->supply); 62 63 return IRQ_HANDLED; 64 } 65 66 static int axp20x_usb_power_get_property(struct power_supply *psy, 67 enum power_supply_property psp, union power_supply_propval *val) 68 { 69 struct axp20x_usb_power *power = power_supply_get_drvdata(psy); 70 unsigned int input, v; 71 int ret; 72 73 switch (psp) { 74 case POWER_SUPPLY_PROP_VOLTAGE_MIN: 75 ret = regmap_read(power->regmap, AXP20X_VBUS_IPSOUT_MGMT, &v); 76 if (ret) 77 return ret; 78 79 val->intval = AXP20X_VBUS_VHOLD_uV(v); 80 return 0; 81 case POWER_SUPPLY_PROP_VOLTAGE_NOW: 82 if (IS_ENABLED(CONFIG_AXP20X_ADC)) { 83 ret = iio_read_channel_processed(power->vbus_v, 84 &val->intval); 85 if (ret) 86 return ret; 87 88 /* 89 * IIO framework gives mV but Power Supply framework 90 * gives uV. 91 */ 92 val->intval *= 1000; 93 return 0; 94 } 95 96 ret = axp20x_read_variable_width(power->regmap, 97 AXP20X_VBUS_V_ADC_H, 12); 98 if (ret < 0) 99 return ret; 100 101 val->intval = ret * 1700; /* 1 step = 1.7 mV */ 102 return 0; 103 case POWER_SUPPLY_PROP_CURRENT_MAX: 104 ret = regmap_read(power->regmap, AXP20X_VBUS_IPSOUT_MGMT, &v); 105 if (ret) 106 return ret; 107 108 switch (v & AXP20X_VBUS_CLIMIT_MASK) { 109 case AXP20X_VBUC_CLIMIT_100mA: 110 if (power->axp20x_id == AXP221_ID) 111 val->intval = -1; /* No 100mA limit */ 112 else 113 val->intval = 100000; 114 break; 115 case AXP20X_VBUC_CLIMIT_500mA: 116 val->intval = 500000; 117 break; 118 case AXP20X_VBUC_CLIMIT_900mA: 119 val->intval = 900000; 120 break; 121 case AXP20X_VBUC_CLIMIT_NONE: 122 val->intval = -1; 123 break; 124 } 125 return 0; 126 case POWER_SUPPLY_PROP_CURRENT_NOW: 127 if (IS_ENABLED(CONFIG_AXP20X_ADC)) { 128 ret = iio_read_channel_processed(power->vbus_i, 129 &val->intval); 130 if (ret) 131 return ret; 132 133 /* 134 * IIO framework gives mA but Power Supply framework 135 * gives uA. 136 */ 137 val->intval *= 1000; 138 return 0; 139 } 140 141 ret = axp20x_read_variable_width(power->regmap, 142 AXP20X_VBUS_I_ADC_H, 12); 143 if (ret < 0) 144 return ret; 145 146 val->intval = ret * 375; /* 1 step = 0.375 mA */ 147 return 0; 148 default: 149 break; 150 } 151 152 /* All the properties below need the input-status reg value */ 153 ret = regmap_read(power->regmap, AXP20X_PWR_INPUT_STATUS, &input); 154 if (ret) 155 return ret; 156 157 switch (psp) { 158 case POWER_SUPPLY_PROP_HEALTH: 159 if (!(input & AXP20X_PWR_STATUS_VBUS_PRESENT)) { 160 val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; 161 break; 162 } 163 164 val->intval = POWER_SUPPLY_HEALTH_GOOD; 165 166 if (power->axp20x_id == AXP202_ID) { 167 ret = regmap_read(power->regmap, 168 AXP20X_USB_OTG_STATUS, &v); 169 if (ret) 170 return ret; 171 172 if (!(v & AXP20X_USB_STATUS_VBUS_VALID)) 173 val->intval = 174 POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; 175 } 176 break; 177 case POWER_SUPPLY_PROP_PRESENT: 178 val->intval = !!(input & AXP20X_PWR_STATUS_VBUS_PRESENT); 179 break; 180 case POWER_SUPPLY_PROP_ONLINE: 181 val->intval = !!(input & AXP20X_PWR_STATUS_VBUS_USED); 182 break; 183 default: 184 return -EINVAL; 185 } 186 187 return 0; 188 } 189 190 static int axp20x_usb_power_set_voltage_min(struct axp20x_usb_power *power, 191 int intval) 192 { 193 int val; 194 195 switch (intval) { 196 case 4000000: 197 case 4100000: 198 case 4200000: 199 case 4300000: 200 case 4400000: 201 case 4500000: 202 case 4600000: 203 case 4700000: 204 val = (intval - 4000000) / 100000; 205 return regmap_update_bits(power->regmap, 206 AXP20X_VBUS_IPSOUT_MGMT, 207 AXP20X_VBUS_VHOLD_MASK, 208 val << AXP20X_VBUS_VHOLD_OFFSET); 209 default: 210 return -EINVAL; 211 } 212 213 return -EINVAL; 214 } 215 216 static int axp20x_usb_power_set_current_max(struct axp20x_usb_power *power, 217 int intval) 218 { 219 int val; 220 221 switch (intval) { 222 case 100000: 223 if (power->axp20x_id == AXP221_ID) 224 return -EINVAL; 225 case 500000: 226 case 900000: 227 val = (900000 - intval) / 400000; 228 return regmap_update_bits(power->regmap, 229 AXP20X_VBUS_IPSOUT_MGMT, 230 AXP20X_VBUS_CLIMIT_MASK, val); 231 default: 232 return -EINVAL; 233 } 234 235 return -EINVAL; 236 } 237 238 static int axp20x_usb_power_set_property(struct power_supply *psy, 239 enum power_supply_property psp, 240 const union power_supply_propval *val) 241 { 242 struct axp20x_usb_power *power = power_supply_get_drvdata(psy); 243 244 switch (psp) { 245 case POWER_SUPPLY_PROP_VOLTAGE_MIN: 246 return axp20x_usb_power_set_voltage_min(power, val->intval); 247 248 case POWER_SUPPLY_PROP_CURRENT_MAX: 249 return axp20x_usb_power_set_current_max(power, val->intval); 250 251 default: 252 return -EINVAL; 253 } 254 255 return -EINVAL; 256 } 257 258 static int axp20x_usb_power_prop_writeable(struct power_supply *psy, 259 enum power_supply_property psp) 260 { 261 return psp == POWER_SUPPLY_PROP_VOLTAGE_MIN || 262 psp == POWER_SUPPLY_PROP_CURRENT_MAX; 263 } 264 265 static enum power_supply_property axp20x_usb_power_properties[] = { 266 POWER_SUPPLY_PROP_HEALTH, 267 POWER_SUPPLY_PROP_PRESENT, 268 POWER_SUPPLY_PROP_ONLINE, 269 POWER_SUPPLY_PROP_VOLTAGE_MIN, 270 POWER_SUPPLY_PROP_VOLTAGE_NOW, 271 POWER_SUPPLY_PROP_CURRENT_MAX, 272 POWER_SUPPLY_PROP_CURRENT_NOW, 273 }; 274 275 static enum power_supply_property axp22x_usb_power_properties[] = { 276 POWER_SUPPLY_PROP_HEALTH, 277 POWER_SUPPLY_PROP_PRESENT, 278 POWER_SUPPLY_PROP_ONLINE, 279 POWER_SUPPLY_PROP_VOLTAGE_MIN, 280 POWER_SUPPLY_PROP_CURRENT_MAX, 281 }; 282 283 static const struct power_supply_desc axp20x_usb_power_desc = { 284 .name = "axp20x-usb", 285 .type = POWER_SUPPLY_TYPE_USB, 286 .properties = axp20x_usb_power_properties, 287 .num_properties = ARRAY_SIZE(axp20x_usb_power_properties), 288 .property_is_writeable = axp20x_usb_power_prop_writeable, 289 .get_property = axp20x_usb_power_get_property, 290 .set_property = axp20x_usb_power_set_property, 291 }; 292 293 static const struct power_supply_desc axp22x_usb_power_desc = { 294 .name = "axp20x-usb", 295 .type = POWER_SUPPLY_TYPE_USB, 296 .properties = axp22x_usb_power_properties, 297 .num_properties = ARRAY_SIZE(axp22x_usb_power_properties), 298 .property_is_writeable = axp20x_usb_power_prop_writeable, 299 .get_property = axp20x_usb_power_get_property, 300 .set_property = axp20x_usb_power_set_property, 301 }; 302 303 static int configure_iio_channels(struct platform_device *pdev, 304 struct axp20x_usb_power *power) 305 { 306 power->vbus_v = devm_iio_channel_get(&pdev->dev, "vbus_v"); 307 if (IS_ERR(power->vbus_v)) { 308 if (PTR_ERR(power->vbus_v) == -ENODEV) 309 return -EPROBE_DEFER; 310 return PTR_ERR(power->vbus_v); 311 } 312 313 power->vbus_i = devm_iio_channel_get(&pdev->dev, "vbus_i"); 314 if (IS_ERR(power->vbus_i)) { 315 if (PTR_ERR(power->vbus_i) == -ENODEV) 316 return -EPROBE_DEFER; 317 return PTR_ERR(power->vbus_i); 318 } 319 320 return 0; 321 } 322 323 static int configure_adc_registers(struct axp20x_usb_power *power) 324 { 325 /* Enable vbus voltage and current measurement */ 326 return regmap_update_bits(power->regmap, AXP20X_ADC_EN1, 327 AXP20X_ADC_EN1_VBUS_CURR | 328 AXP20X_ADC_EN1_VBUS_VOLT, 329 AXP20X_ADC_EN1_VBUS_CURR | 330 AXP20X_ADC_EN1_VBUS_VOLT); 331 } 332 333 static int axp20x_usb_power_probe(struct platform_device *pdev) 334 { 335 struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent); 336 struct power_supply_config psy_cfg = {}; 337 struct axp20x_usb_power *power; 338 static const char * const axp20x_irq_names[] = { "VBUS_PLUGIN", 339 "VBUS_REMOVAL", "VBUS_VALID", "VBUS_NOT_VALID", NULL }; 340 static const char * const axp22x_irq_names[] = { 341 "VBUS_PLUGIN", "VBUS_REMOVAL", NULL }; 342 static const char * const *irq_names; 343 const struct power_supply_desc *usb_power_desc; 344 int i, irq, ret; 345 346 if (!of_device_is_available(pdev->dev.of_node)) 347 return -ENODEV; 348 349 if (!axp20x) { 350 dev_err(&pdev->dev, "Parent drvdata not set\n"); 351 return -EINVAL; 352 } 353 354 power = devm_kzalloc(&pdev->dev, sizeof(*power), GFP_KERNEL); 355 if (!power) 356 return -ENOMEM; 357 358 power->axp20x_id = (enum axp20x_variants)of_device_get_match_data( 359 &pdev->dev); 360 361 power->np = pdev->dev.of_node; 362 power->regmap = axp20x->regmap; 363 364 if (power->axp20x_id == AXP202_ID) { 365 /* Enable vbus valid checking */ 366 ret = regmap_update_bits(power->regmap, AXP20X_VBUS_MON, 367 AXP20X_VBUS_MON_VBUS_VALID, 368 AXP20X_VBUS_MON_VBUS_VALID); 369 if (ret) 370 return ret; 371 372 if (IS_ENABLED(CONFIG_AXP20X_ADC)) 373 ret = configure_iio_channels(pdev, power); 374 else 375 ret = configure_adc_registers(power); 376 377 if (ret) 378 return ret; 379 380 usb_power_desc = &axp20x_usb_power_desc; 381 irq_names = axp20x_irq_names; 382 } else if (power->axp20x_id == AXP221_ID || 383 power->axp20x_id == AXP223_ID) { 384 usb_power_desc = &axp22x_usb_power_desc; 385 irq_names = axp22x_irq_names; 386 } else { 387 dev_err(&pdev->dev, "Unsupported AXP variant: %ld\n", 388 axp20x->variant); 389 return -EINVAL; 390 } 391 392 psy_cfg.of_node = pdev->dev.of_node; 393 psy_cfg.drv_data = power; 394 395 power->supply = devm_power_supply_register(&pdev->dev, usb_power_desc, 396 &psy_cfg); 397 if (IS_ERR(power->supply)) 398 return PTR_ERR(power->supply); 399 400 /* Request irqs after registering, as irqs may trigger immediately */ 401 for (i = 0; irq_names[i]; i++) { 402 irq = platform_get_irq_byname(pdev, irq_names[i]); 403 if (irq < 0) { 404 dev_warn(&pdev->dev, "No IRQ for %s: %d\n", 405 irq_names[i], irq); 406 continue; 407 } 408 irq = regmap_irq_get_virq(axp20x->regmap_irqc, irq); 409 ret = devm_request_any_context_irq(&pdev->dev, irq, 410 axp20x_usb_power_irq, 0, DRVNAME, power); 411 if (ret < 0) 412 dev_warn(&pdev->dev, "Error requesting %s IRQ: %d\n", 413 irq_names[i], ret); 414 } 415 416 return 0; 417 } 418 419 static const struct of_device_id axp20x_usb_power_match[] = { 420 { 421 .compatible = "x-powers,axp202-usb-power-supply", 422 .data = (void *)AXP202_ID, 423 }, { 424 .compatible = "x-powers,axp221-usb-power-supply", 425 .data = (void *)AXP221_ID, 426 }, { 427 .compatible = "x-powers,axp223-usb-power-supply", 428 .data = (void *)AXP223_ID, 429 }, { /* sentinel */ } 430 }; 431 MODULE_DEVICE_TABLE(of, axp20x_usb_power_match); 432 433 static struct platform_driver axp20x_usb_power_driver = { 434 .probe = axp20x_usb_power_probe, 435 .driver = { 436 .name = DRVNAME, 437 .of_match_table = axp20x_usb_power_match, 438 }, 439 }; 440 441 module_platform_driver(axp20x_usb_power_driver); 442 443 MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); 444 MODULE_DESCRIPTION("AXP20x PMIC USB power supply status driver"); 445 MODULE_LICENSE("GPL"); 446