linux/drivers/power/supply/axp20x_ac_power.c
<<
>>
Prefs
   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
  32struct 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
  39static 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
  48static 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, &reg);
  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, &reg);
  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, &reg);
  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
 112static 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
 120static 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
 126static 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
 134static 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
 142struct axp_data {
 143        const struct power_supply_desc  *power_desc;
 144        bool                            acin_adc;
 145};
 146
 147static const struct axp_data axp20x_data = {
 148        .power_desc = &axp20x_ac_power_desc,
 149        .acin_adc = true,
 150};
 151
 152static const struct axp_data axp22x_data = {
 153        .power_desc = &axp22x_ac_power_desc,
 154        .acin_adc = false,
 155};
 156
 157static 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
 230static 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};
 239MODULE_DEVICE_TABLE(of, axp20x_ac_power_match);
 240
 241static 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
 249module_platform_driver(axp20x_ac_power_driver);
 250
 251MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>");
 252MODULE_DESCRIPTION("AXP20X and AXP22X PMICs' AC power supply driver");
 253MODULE_LICENSE("GPL");
 254