1 /* 2 * AXP20X and AXP22X PMICs' ACIN power supply driver 3 * 4 * Copyright (C) 2016 Free Electrons 5 * Quentin Schulz <quentin.schulz@free-electrons.com> 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 AXP20X_PWR_STATUS_ACIN_PRESENT BIT(7) 28 #define AXP20X_PWR_STATUS_ACIN_AVAIL BIT(6) 29 30 #define DRVNAME "axp20x-ac-power-supply" 31 32 struct axp20x_ac_power { 33 struct regmap *regmap; 34 struct power_supply *supply; 35 struct iio_channel *acin_v; 36 struct iio_channel *acin_i; 37 }; 38 39 static irqreturn_t axp20x_ac_power_irq(int irq, void *devid) 40 { 41 struct axp20x_ac_power *power = devid; 42 43 power_supply_changed(power->supply); 44 45 return IRQ_HANDLED; 46 } 47 48 static int axp20x_ac_power_get_property(struct power_supply *psy, 49 enum power_supply_property psp, 50 union power_supply_propval *val) 51 { 52 struct axp20x_ac_power *power = power_supply_get_drvdata(psy); 53 int ret, reg; 54 55 switch (psp) { 56 case POWER_SUPPLY_PROP_HEALTH: 57 ret = regmap_read(power->regmap, AXP20X_PWR_INPUT_STATUS, ®); 58 if (ret) 59 return ret; 60 61 if (reg & AXP20X_PWR_STATUS_ACIN_PRESENT) { 62 val->intval = POWER_SUPPLY_HEALTH_GOOD; 63 return 0; 64 } 65 66 val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; 67 return 0; 68 69 case POWER_SUPPLY_PROP_PRESENT: 70 ret = regmap_read(power->regmap, AXP20X_PWR_INPUT_STATUS, ®); 71 if (ret) 72 return ret; 73 74 val->intval = !!(reg & AXP20X_PWR_STATUS_ACIN_PRESENT); 75 return 0; 76 77 case POWER_SUPPLY_PROP_ONLINE: 78 ret = regmap_read(power->regmap, AXP20X_PWR_INPUT_STATUS, ®); 79 if (ret) 80 return ret; 81 82 val->intval = !!(reg & AXP20X_PWR_STATUS_ACIN_AVAIL); 83 return 0; 84 85 case POWER_SUPPLY_PROP_VOLTAGE_NOW: 86 ret = iio_read_channel_processed(power->acin_v, &val->intval); 87 if (ret) 88 return ret; 89 90 /* IIO framework gives mV but Power Supply framework gives uV */ 91 val->intval *= 1000; 92 93 return 0; 94 95 case POWER_SUPPLY_PROP_CURRENT_NOW: 96 ret = iio_read_channel_processed(power->acin_i, &val->intval); 97 if (ret) 98 return ret; 99 100 /* IIO framework gives mA but Power Supply framework gives uA */ 101 val->intval *= 1000; 102 103 return 0; 104 105 default: 106 return -EINVAL; 107 } 108 109 return -EINVAL; 110 } 111 112 static enum power_supply_property axp20x_ac_power_properties[] = { 113 POWER_SUPPLY_PROP_HEALTH, 114 POWER_SUPPLY_PROP_PRESENT, 115 POWER_SUPPLY_PROP_ONLINE, 116 POWER_SUPPLY_PROP_VOLTAGE_NOW, 117 POWER_SUPPLY_PROP_CURRENT_NOW, 118 }; 119 120 static enum power_supply_property axp22x_ac_power_properties[] = { 121 POWER_SUPPLY_PROP_HEALTH, 122 POWER_SUPPLY_PROP_PRESENT, 123 POWER_SUPPLY_PROP_ONLINE, 124 }; 125 126 static const struct power_supply_desc axp20x_ac_power_desc = { 127 .name = "axp20x-ac", 128 .type = POWER_SUPPLY_TYPE_MAINS, 129 .properties = axp20x_ac_power_properties, 130 .num_properties = ARRAY_SIZE(axp20x_ac_power_properties), 131 .get_property = axp20x_ac_power_get_property, 132 }; 133 134 static const struct power_supply_desc axp22x_ac_power_desc = { 135 .name = "axp22x-ac", 136 .type = POWER_SUPPLY_TYPE_MAINS, 137 .properties = axp22x_ac_power_properties, 138 .num_properties = ARRAY_SIZE(axp22x_ac_power_properties), 139 .get_property = axp20x_ac_power_get_property, 140 }; 141 142 struct axp_data { 143 const struct power_supply_desc *power_desc; 144 bool acin_adc; 145 }; 146 147 static const struct axp_data axp20x_data = { 148 .power_desc = &axp20x_ac_power_desc, 149 .acin_adc = true, 150 }; 151 152 static const struct axp_data axp22x_data = { 153 .power_desc = &axp22x_ac_power_desc, 154 .acin_adc = false, 155 }; 156 157 static int axp20x_ac_power_probe(struct platform_device *pdev) 158 { 159 struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent); 160 struct power_supply_config psy_cfg = {}; 161 struct axp20x_ac_power *power; 162 struct axp_data *axp_data; 163 static const char * const irq_names[] = { "ACIN_PLUGIN", "ACIN_REMOVAL", 164 NULL }; 165 int i, irq, ret; 166 167 if (!of_device_is_available(pdev->dev.of_node)) 168 return -ENODEV; 169 170 if (!axp20x) { 171 dev_err(&pdev->dev, "Parent drvdata not set\n"); 172 return -EINVAL; 173 } 174 175 power = devm_kzalloc(&pdev->dev, sizeof(*power), GFP_KERNEL); 176 if (!power) 177 return -ENOMEM; 178 179 axp_data = (struct axp_data *)of_device_get_match_data(&pdev->dev); 180 181 if (axp_data->acin_adc) { 182 power->acin_v = devm_iio_channel_get(&pdev->dev, "acin_v"); 183 if (IS_ERR(power->acin_v)) { 184 if (PTR_ERR(power->acin_v) == -ENODEV) 185 return -EPROBE_DEFER; 186 return PTR_ERR(power->acin_v); 187 } 188 189 power->acin_i = devm_iio_channel_get(&pdev->dev, "acin_i"); 190 if (IS_ERR(power->acin_i)) { 191 if (PTR_ERR(power->acin_i) == -ENODEV) 192 return -EPROBE_DEFER; 193 return PTR_ERR(power->acin_i); 194 } 195 } 196 197 power->regmap = dev_get_regmap(pdev->dev.parent, NULL); 198 199 platform_set_drvdata(pdev, power); 200 201 psy_cfg.of_node = pdev->dev.of_node; 202 psy_cfg.drv_data = power; 203 204 power->supply = devm_power_supply_register(&pdev->dev, 205 axp_data->power_desc, 206 &psy_cfg); 207 if (IS_ERR(power->supply)) 208 return PTR_ERR(power->supply); 209 210 /* Request irqs after registering, as irqs may trigger immediately */ 211 for (i = 0; irq_names[i]; i++) { 212 irq = platform_get_irq_byname(pdev, irq_names[i]); 213 if (irq < 0) { 214 dev_warn(&pdev->dev, "No IRQ for %s: %d\n", 215 irq_names[i], irq); 216 continue; 217 } 218 irq = regmap_irq_get_virq(axp20x->regmap_irqc, irq); 219 ret = devm_request_any_context_irq(&pdev->dev, irq, 220 axp20x_ac_power_irq, 0, 221 DRVNAME, power); 222 if (ret < 0) 223 dev_warn(&pdev->dev, "Error requesting %s IRQ: %d\n", 224 irq_names[i], ret); 225 } 226 227 return 0; 228 } 229 230 static const struct of_device_id axp20x_ac_power_match[] = { 231 { 232 .compatible = "x-powers,axp202-ac-power-supply", 233 .data = (void *)&axp20x_data, 234 }, { 235 .compatible = "x-powers,axp221-ac-power-supply", 236 .data = (void *)&axp22x_data, 237 }, { /* sentinel */ } 238 }; 239 MODULE_DEVICE_TABLE(of, axp20x_ac_power_match); 240 241 static struct platform_driver axp20x_ac_power_driver = { 242 .probe = axp20x_ac_power_probe, 243 .driver = { 244 .name = DRVNAME, 245 .of_match_table = axp20x_ac_power_match, 246 }, 247 }; 248 249 module_platform_driver(axp20x_ac_power_driver); 250 251 MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>"); 252 MODULE_DESCRIPTION("AXP20X and AXP22X PMICs' AC power supply driver"); 253 MODULE_LICENSE("GPL"); 254