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 /* fall through */ 226 case 500000: 227 case 900000: 228 val = (900000 - intval) / 400000; 229 return regmap_update_bits(power->regmap, 230 AXP20X_VBUS_IPSOUT_MGMT, 231 AXP20X_VBUS_CLIMIT_MASK, val); 232 default: 233 return -EINVAL; 234 } 235 236 return -EINVAL; 237 } 238 239 static int axp20x_usb_power_set_property(struct power_supply *psy, 240 enum power_supply_property psp, 241 const union power_supply_propval *val) 242 { 243 struct axp20x_usb_power *power = power_supply_get_drvdata(psy); 244 245 switch (psp) { 246 case POWER_SUPPLY_PROP_VOLTAGE_MIN: 247 return axp20x_usb_power_set_voltage_min(power, val->intval); 248 249 case POWER_SUPPLY_PROP_CURRENT_MAX: 250 return axp20x_usb_power_set_current_max(power, val->intval); 251 252 default: 253 return -EINVAL; 254 } 255 256 return -EINVAL; 257 } 258 259 static int axp20x_usb_power_prop_writeable(struct power_supply *psy, 260 enum power_supply_property psp) 261 { 262 return psp == POWER_SUPPLY_PROP_VOLTAGE_MIN || 263 psp == POWER_SUPPLY_PROP_CURRENT_MAX; 264 } 265 266 static enum power_supply_property axp20x_usb_power_properties[] = { 267 POWER_SUPPLY_PROP_HEALTH, 268 POWER_SUPPLY_PROP_PRESENT, 269 POWER_SUPPLY_PROP_ONLINE, 270 POWER_SUPPLY_PROP_VOLTAGE_MIN, 271 POWER_SUPPLY_PROP_VOLTAGE_NOW, 272 POWER_SUPPLY_PROP_CURRENT_MAX, 273 POWER_SUPPLY_PROP_CURRENT_NOW, 274 }; 275 276 static enum power_supply_property axp22x_usb_power_properties[] = { 277 POWER_SUPPLY_PROP_HEALTH, 278 POWER_SUPPLY_PROP_PRESENT, 279 POWER_SUPPLY_PROP_ONLINE, 280 POWER_SUPPLY_PROP_VOLTAGE_MIN, 281 POWER_SUPPLY_PROP_CURRENT_MAX, 282 }; 283 284 static const struct power_supply_desc axp20x_usb_power_desc = { 285 .name = "axp20x-usb", 286 .type = POWER_SUPPLY_TYPE_USB, 287 .properties = axp20x_usb_power_properties, 288 .num_properties = ARRAY_SIZE(axp20x_usb_power_properties), 289 .property_is_writeable = axp20x_usb_power_prop_writeable, 290 .get_property = axp20x_usb_power_get_property, 291 .set_property = axp20x_usb_power_set_property, 292 }; 293 294 static const struct power_supply_desc axp22x_usb_power_desc = { 295 .name = "axp20x-usb", 296 .type = POWER_SUPPLY_TYPE_USB, 297 .properties = axp22x_usb_power_properties, 298 .num_properties = ARRAY_SIZE(axp22x_usb_power_properties), 299 .property_is_writeable = axp20x_usb_power_prop_writeable, 300 .get_property = axp20x_usb_power_get_property, 301 .set_property = axp20x_usb_power_set_property, 302 }; 303 304 static int configure_iio_channels(struct platform_device *pdev, 305 struct axp20x_usb_power *power) 306 { 307 power->vbus_v = devm_iio_channel_get(&pdev->dev, "vbus_v"); 308 if (IS_ERR(power->vbus_v)) { 309 if (PTR_ERR(power->vbus_v) == -ENODEV) 310 return -EPROBE_DEFER; 311 return PTR_ERR(power->vbus_v); 312 } 313 314 power->vbus_i = devm_iio_channel_get(&pdev->dev, "vbus_i"); 315 if (IS_ERR(power->vbus_i)) { 316 if (PTR_ERR(power->vbus_i) == -ENODEV) 317 return -EPROBE_DEFER; 318 return PTR_ERR(power->vbus_i); 319 } 320 321 return 0; 322 } 323 324 static int configure_adc_registers(struct axp20x_usb_power *power) 325 { 326 /* Enable vbus voltage and current measurement */ 327 return regmap_update_bits(power->regmap, AXP20X_ADC_EN1, 328 AXP20X_ADC_EN1_VBUS_CURR | 329 AXP20X_ADC_EN1_VBUS_VOLT, 330 AXP20X_ADC_EN1_VBUS_CURR | 331 AXP20X_ADC_EN1_VBUS_VOLT); 332 } 333 334 static int axp20x_usb_power_probe(struct platform_device *pdev) 335 { 336 struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent); 337 struct power_supply_config psy_cfg = {}; 338 struct axp20x_usb_power *power; 339 static const char * const axp20x_irq_names[] = { "VBUS_PLUGIN", 340 "VBUS_REMOVAL", "VBUS_VALID", "VBUS_NOT_VALID", NULL }; 341 static const char * const axp22x_irq_names[] = { 342 "VBUS_PLUGIN", "VBUS_REMOVAL", NULL }; 343 const char * const *irq_names; 344 const struct power_supply_desc *usb_power_desc; 345 int i, irq, ret; 346 347 if (!of_device_is_available(pdev->dev.of_node)) 348 return -ENODEV; 349 350 if (!axp20x) { 351 dev_err(&pdev->dev, "Parent drvdata not set\n"); 352 return -EINVAL; 353 } 354 355 power = devm_kzalloc(&pdev->dev, sizeof(*power), GFP_KERNEL); 356 if (!power) 357 return -ENOMEM; 358 359 power->axp20x_id = (enum axp20x_variants)of_device_get_match_data( 360 &pdev->dev); 361 362 power->np = pdev->dev.of_node; 363 power->regmap = axp20x->regmap; 364 365 if (power->axp20x_id == AXP202_ID) { 366 /* Enable vbus valid checking */ 367 ret = regmap_update_bits(power->regmap, AXP20X_VBUS_MON, 368 AXP20X_VBUS_MON_VBUS_VALID, 369 AXP20X_VBUS_MON_VBUS_VALID); 370 if (ret) 371 return ret; 372 373 if (IS_ENABLED(CONFIG_AXP20X_ADC)) 374 ret = configure_iio_channels(pdev, power); 375 else 376 ret = configure_adc_registers(power); 377 378 if (ret) 379 return ret; 380 381 usb_power_desc = &axp20x_usb_power_desc; 382 irq_names = axp20x_irq_names; 383 } else if (power->axp20x_id == AXP221_ID || 384 power->axp20x_id == AXP223_ID) { 385 usb_power_desc = &axp22x_usb_power_desc; 386 irq_names = axp22x_irq_names; 387 } else { 388 dev_err(&pdev->dev, "Unsupported AXP variant: %ld\n", 389 axp20x->variant); 390 return -EINVAL; 391 } 392 393 psy_cfg.of_node = pdev->dev.of_node; 394 psy_cfg.drv_data = power; 395 396 power->supply = devm_power_supply_register(&pdev->dev, usb_power_desc, 397 &psy_cfg); 398 if (IS_ERR(power->supply)) 399 return PTR_ERR(power->supply); 400 401 /* Request irqs after registering, as irqs may trigger immediately */ 402 for (i = 0; irq_names[i]; i++) { 403 irq = platform_get_irq_byname(pdev, irq_names[i]); 404 if (irq < 0) { 405 dev_warn(&pdev->dev, "No IRQ for %s: %d\n", 406 irq_names[i], irq); 407 continue; 408 } 409 irq = regmap_irq_get_virq(axp20x->regmap_irqc, irq); 410 ret = devm_request_any_context_irq(&pdev->dev, irq, 411 axp20x_usb_power_irq, 0, DRVNAME, power); 412 if (ret < 0) 413 dev_warn(&pdev->dev, "Error requesting %s IRQ: %d\n", 414 irq_names[i], ret); 415 } 416 417 return 0; 418 } 419 420 static const struct of_device_id axp20x_usb_power_match[] = { 421 { 422 .compatible = "x-powers,axp202-usb-power-supply", 423 .data = (void *)AXP202_ID, 424 }, { 425 .compatible = "x-powers,axp221-usb-power-supply", 426 .data = (void *)AXP221_ID, 427 }, { 428 .compatible = "x-powers,axp223-usb-power-supply", 429 .data = (void *)AXP223_ID, 430 }, { /* sentinel */ } 431 }; 432 MODULE_DEVICE_TABLE(of, axp20x_usb_power_match); 433 434 static struct platform_driver axp20x_usb_power_driver = { 435 .probe = axp20x_usb_power_probe, 436 .driver = { 437 .name = DRVNAME, 438 .of_match_table = axp20x_usb_power_match, 439 }, 440 }; 441 442 module_platform_driver(axp20x_usb_power_driver); 443 444 MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); 445 MODULE_DESCRIPTION("AXP20x PMIC USB power supply status driver"); 446 MODULE_LICENSE("GPL"); 447