linux/drivers/power/supply/lego_ev3_battery.c
<<
>>
Prefs
   1/*
   2 * Battery driver for LEGO MINDSTORMS EV3
   3 *
   4 * Copyright (C) 2017 David Lechner <david@lechnology.com>
   5 *
   6 * This program is free software; you can redistribute it and/or modify
   7 * it under the terms of the GNU General Public License version 2 as
   8 * published by the Free Software Foundation.
   9
  10 * This program is distributed "as is" WITHOUT ANY WARRANTY of any
  11 * kind, whether express or implied; without even the implied warranty
  12 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13 * GNU General Public License for more details.
  14 */
  15
  16#include <linux/delay.h>
  17#include <linux/err.h>
  18#include <linux/gpio/consumer.h>
  19#include <linux/iio/consumer.h>
  20#include <linux/iio/types.h>
  21#include <linux/kernel.h>
  22#include <linux/module.h>
  23#include <linux/of_device.h>
  24#include <linux/platform_device.h>
  25#include <linux/power_supply.h>
  26
  27struct lego_ev3_battery {
  28        struct iio_channel *iio_v;
  29        struct iio_channel *iio_i;
  30        struct gpio_desc *rechargeable_gpio;
  31        struct power_supply *psy;
  32        int technology;
  33        int v_max;
  34        int v_min;
  35};
  36
  37static int lego_ev3_battery_get_property(struct power_supply *psy,
  38                                         enum power_supply_property psp,
  39                                         union power_supply_propval *val)
  40{
  41        struct lego_ev3_battery *batt = power_supply_get_drvdata(psy);
  42        int val2;
  43
  44        switch (psp) {
  45        case POWER_SUPPLY_PROP_TECHNOLOGY:
  46                val->intval = batt->technology;
  47                break;
  48        case POWER_SUPPLY_PROP_VOLTAGE_NOW:
  49                /* battery voltage is iio channel * 2 + Vce of transistor */
  50                iio_read_channel_processed(batt->iio_v, &val->intval);
  51                val->intval *= 2000;
  52                val->intval += 200000;
  53                /* plus adjust for shunt resistor drop */
  54                iio_read_channel_processed(batt->iio_i, &val2);
  55                val2 *= 1000;
  56                val2 /= 15;
  57                val->intval += val2;
  58                break;
  59        case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
  60                val->intval = batt->v_max;
  61                break;
  62        case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
  63                val->intval = batt->v_min;
  64                break;
  65        case POWER_SUPPLY_PROP_CURRENT_NOW:
  66                /* battery current is iio channel / 15 / 0.05 ohms */
  67                iio_read_channel_processed(batt->iio_i, &val->intval);
  68                val->intval *= 20000;
  69                val->intval /= 15;
  70                break;
  71        case POWER_SUPPLY_PROP_SCOPE:
  72                val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
  73                break;
  74        default:
  75                return -EINVAL;
  76        }
  77
  78        return 0;
  79}
  80
  81static int lego_ev3_battery_set_property(struct power_supply *psy,
  82                                         enum power_supply_property psp,
  83                                         const union power_supply_propval *val)
  84{
  85        struct lego_ev3_battery *batt = power_supply_get_drvdata(psy);
  86
  87        switch (psp) {
  88        case POWER_SUPPLY_PROP_TECHNOLOGY:
  89                /*
  90                 * Only allow changing technology from Unknown to NiMH. Li-ion
  91                 * batteries are automatically detected and should not be
  92                 * overridden. Rechargeable AA batteries, on the other hand,
  93                 * cannot be automatically detected, and so must be manually
  94                 * specified. This should only be set once during system init,
  95                 * so there is no mechanism to go back to Unknown.
  96                 */
  97                if (batt->technology != POWER_SUPPLY_TECHNOLOGY_UNKNOWN)
  98                        return -EINVAL;
  99                switch (val->intval) {
 100                case POWER_SUPPLY_TECHNOLOGY_NiMH:
 101                        batt->technology = POWER_SUPPLY_TECHNOLOGY_NiMH;
 102                        batt->v_max = 7800000;
 103                        batt->v_min = 5400000;
 104                        break;
 105                default:
 106                        return -EINVAL;
 107                }
 108                break;
 109        default:
 110                return -EINVAL;
 111        }
 112
 113        return 0;
 114}
 115
 116static int lego_ev3_battery_property_is_writeable(struct power_supply *psy,
 117                                                  enum power_supply_property psp)
 118{
 119        struct lego_ev3_battery *batt = power_supply_get_drvdata(psy);
 120
 121        return psp == POWER_SUPPLY_PROP_TECHNOLOGY &&
 122                batt->technology == POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
 123}
 124
 125static enum power_supply_property lego_ev3_battery_props[] = {
 126        POWER_SUPPLY_PROP_TECHNOLOGY,
 127        POWER_SUPPLY_PROP_VOLTAGE_NOW,
 128        POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
 129        POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
 130        POWER_SUPPLY_PROP_CURRENT_NOW,
 131        POWER_SUPPLY_PROP_SCOPE,
 132};
 133
 134static const struct power_supply_desc lego_ev3_battery_desc = {
 135        .name                   = "lego-ev3-battery",
 136        .type                   = POWER_SUPPLY_TYPE_BATTERY,
 137        .properties             = lego_ev3_battery_props,
 138        .num_properties         = ARRAY_SIZE(lego_ev3_battery_props),
 139        .get_property           = lego_ev3_battery_get_property,
 140        .set_property           = lego_ev3_battery_set_property,
 141        .property_is_writeable  = lego_ev3_battery_property_is_writeable,
 142};
 143
 144static int lego_ev3_battery_probe(struct platform_device *pdev)
 145{
 146        struct device *dev = &pdev->dev;
 147        struct lego_ev3_battery *batt;
 148        struct power_supply_config psy_cfg = {};
 149        int err;
 150
 151        batt = devm_kzalloc(dev, sizeof(*batt), GFP_KERNEL);
 152        if (!batt)
 153                return -ENOMEM;
 154
 155        platform_set_drvdata(pdev, batt);
 156
 157        batt->iio_v = devm_iio_channel_get(dev, "voltage");
 158        err = PTR_ERR_OR_ZERO(batt->iio_v);
 159        if (err) {
 160                if (err != -EPROBE_DEFER)
 161                        dev_err(dev, "Failed to get voltage iio channel\n");
 162                return err;
 163        }
 164
 165        batt->iio_i = devm_iio_channel_get(dev, "current");
 166        err = PTR_ERR_OR_ZERO(batt->iio_i);
 167        if (err) {
 168                if (err != -EPROBE_DEFER)
 169                        dev_err(dev, "Failed to get current iio channel\n");
 170                return err;
 171        }
 172
 173        batt->rechargeable_gpio = devm_gpiod_get(dev, "rechargeable", GPIOD_IN);
 174        err = PTR_ERR_OR_ZERO(batt->rechargeable_gpio);
 175        if (err) {
 176                if (err != -EPROBE_DEFER)
 177                        dev_err(dev, "Failed to get rechargeable gpio\n");
 178                return err;
 179        }
 180
 181        /*
 182         * The rechargeable battery indication switch cannot be changed without
 183         * removing the battery, so we only need to read it once.
 184         */
 185        if (gpiod_get_value(batt->rechargeable_gpio)) {
 186                /* 2-cell Li-ion, 7.4V nominal */
 187                batt->technology = POWER_SUPPLY_TECHNOLOGY_LION;
 188                batt->v_max = 84000000;
 189                batt->v_min = 60000000;
 190        } else {
 191                /* 6x AA Alkaline, 9V nominal */
 192                batt->technology = POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
 193                batt->v_max = 90000000;
 194                batt->v_min = 48000000;
 195        }
 196
 197        psy_cfg.of_node = pdev->dev.of_node;
 198        psy_cfg.drv_data = batt;
 199
 200        batt->psy = devm_power_supply_register(dev, &lego_ev3_battery_desc,
 201                                               &psy_cfg);
 202        err = PTR_ERR_OR_ZERO(batt->psy);
 203        if (err) {
 204                dev_err(dev, "failed to register power supply\n");
 205                return err;
 206        }
 207
 208        return 0;
 209}
 210
 211static const struct of_device_id of_lego_ev3_battery_match[] = {
 212        { .compatible = "lego,ev3-battery", },
 213        { }
 214};
 215MODULE_DEVICE_TABLE(of, of_lego_ev3_battery_match);
 216
 217static struct platform_driver lego_ev3_battery_driver = {
 218        .driver = {
 219                .name           = "lego-ev3-battery",
 220                .of_match_table = of_lego_ev3_battery_match,
 221        },
 222        .probe  = lego_ev3_battery_probe,
 223};
 224module_platform_driver(lego_ev3_battery_driver);
 225
 226MODULE_LICENSE("GPL");
 227MODULE_AUTHOR("David Lechner <david@lechnology.com>");
 228MODULE_DESCRIPTION("LEGO MINDSTORMS EV3 Battery Driver");
 229