Back to home page

OSCL-LXR

 
 

    


0001 /*
0002  * Battery driver for LEGO MINDSTORMS EV3
0003  *
0004  * Copyright (C) 2017 David Lechner <david@lechnology.com>
0005  *
0006  * This program is free software; you can redistribute it and/or modify
0007  * it under the terms of the GNU General Public License version 2 as
0008  * published by the Free Software Foundation.
0009 
0010  * This program is distributed "as is" WITHOUT ANY WARRANTY of any
0011  * kind, whether express or implied; without even the implied warranty
0012  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013  * GNU General Public License for more details.
0014  */
0015 
0016 #include <linux/delay.h>
0017 #include <linux/err.h>
0018 #include <linux/gpio/consumer.h>
0019 #include <linux/iio/consumer.h>
0020 #include <linux/iio/types.h>
0021 #include <linux/kernel.h>
0022 #include <linux/module.h>
0023 #include <linux/of_device.h>
0024 #include <linux/platform_device.h>
0025 #include <linux/power_supply.h>
0026 
0027 struct lego_ev3_battery {
0028     struct iio_channel *iio_v;
0029     struct iio_channel *iio_i;
0030     struct gpio_desc *rechargeable_gpio;
0031     struct power_supply *psy;
0032     int technology;
0033     int v_max;
0034     int v_min;
0035 };
0036 
0037 static int lego_ev3_battery_get_property(struct power_supply *psy,
0038                      enum power_supply_property psp,
0039                      union power_supply_propval *val)
0040 {
0041     struct lego_ev3_battery *batt = power_supply_get_drvdata(psy);
0042     int ret, val2;
0043 
0044     switch (psp) {
0045     case POWER_SUPPLY_PROP_TECHNOLOGY:
0046         val->intval = batt->technology;
0047         break;
0048     case POWER_SUPPLY_PROP_VOLTAGE_NOW:
0049         /* battery voltage is iio channel * 2 + Vce of transistor */
0050         ret = iio_read_channel_processed(batt->iio_v, &val->intval);
0051         if (ret)
0052             return ret;
0053 
0054         val->intval *= 2000;
0055         val->intval += 50000;
0056 
0057         /* plus adjust for shunt resistor drop */
0058         ret = iio_read_channel_processed(batt->iio_i, &val2);
0059         if (ret)
0060             return ret;
0061 
0062         val2 *= 1000;
0063         val2 /= 15;
0064         val->intval += val2;
0065         break;
0066     case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
0067         val->intval = batt->v_max;
0068         break;
0069     case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
0070         val->intval = batt->v_min;
0071         break;
0072     case POWER_SUPPLY_PROP_CURRENT_NOW:
0073         /* battery current is iio channel / 15 / 0.05 ohms */
0074         ret = iio_read_channel_processed(batt->iio_i, &val->intval);
0075         if (ret)
0076             return ret;
0077 
0078         val->intval *= 20000;
0079         val->intval /= 15;
0080         break;
0081     case POWER_SUPPLY_PROP_SCOPE:
0082         val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
0083         break;
0084     default:
0085         return -EINVAL;
0086     }
0087 
0088     return 0;
0089 }
0090 
0091 static int lego_ev3_battery_set_property(struct power_supply *psy,
0092                      enum power_supply_property psp,
0093                      const union power_supply_propval *val)
0094 {
0095     struct lego_ev3_battery *batt = power_supply_get_drvdata(psy);
0096 
0097     switch (psp) {
0098     case POWER_SUPPLY_PROP_TECHNOLOGY:
0099         /*
0100          * Only allow changing technology from Unknown to NiMH. Li-ion
0101          * batteries are automatically detected and should not be
0102          * overridden. Rechargeable AA batteries, on the other hand,
0103          * cannot be automatically detected, and so must be manually
0104          * specified. This should only be set once during system init,
0105          * so there is no mechanism to go back to Unknown.
0106          */
0107         if (batt->technology != POWER_SUPPLY_TECHNOLOGY_UNKNOWN)
0108             return -EINVAL;
0109         switch (val->intval) {
0110         case POWER_SUPPLY_TECHNOLOGY_NiMH:
0111             batt->technology = POWER_SUPPLY_TECHNOLOGY_NiMH;
0112             batt->v_max = 7800000;
0113             batt->v_min = 5400000;
0114             break;
0115         default:
0116             return -EINVAL;
0117         }
0118         break;
0119     default:
0120         return -EINVAL;
0121     }
0122 
0123     return 0;
0124 }
0125 
0126 static int lego_ev3_battery_property_is_writeable(struct power_supply *psy,
0127                           enum power_supply_property psp)
0128 {
0129     struct lego_ev3_battery *batt = power_supply_get_drvdata(psy);
0130 
0131     return psp == POWER_SUPPLY_PROP_TECHNOLOGY &&
0132         batt->technology == POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
0133 }
0134 
0135 static enum power_supply_property lego_ev3_battery_props[] = {
0136     POWER_SUPPLY_PROP_TECHNOLOGY,
0137     POWER_SUPPLY_PROP_VOLTAGE_NOW,
0138     POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
0139     POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
0140     POWER_SUPPLY_PROP_CURRENT_NOW,
0141     POWER_SUPPLY_PROP_SCOPE,
0142 };
0143 
0144 static const struct power_supply_desc lego_ev3_battery_desc = {
0145     .name           = "lego-ev3-battery",
0146     .type           = POWER_SUPPLY_TYPE_BATTERY,
0147     .properties     = lego_ev3_battery_props,
0148     .num_properties     = ARRAY_SIZE(lego_ev3_battery_props),
0149     .get_property       = lego_ev3_battery_get_property,
0150     .set_property       = lego_ev3_battery_set_property,
0151     .property_is_writeable  = lego_ev3_battery_property_is_writeable,
0152 };
0153 
0154 static int lego_ev3_battery_probe(struct platform_device *pdev)
0155 {
0156     struct device *dev = &pdev->dev;
0157     struct lego_ev3_battery *batt;
0158     struct power_supply_config psy_cfg = {};
0159     int err;
0160 
0161     batt = devm_kzalloc(dev, sizeof(*batt), GFP_KERNEL);
0162     if (!batt)
0163         return -ENOMEM;
0164 
0165     platform_set_drvdata(pdev, batt);
0166 
0167     batt->iio_v = devm_iio_channel_get(dev, "voltage");
0168     err = PTR_ERR_OR_ZERO(batt->iio_v);
0169     if (err)
0170         return dev_err_probe(dev, err,
0171                      "Failed to get voltage iio channel\n");
0172 
0173     batt->iio_i = devm_iio_channel_get(dev, "current");
0174     err = PTR_ERR_OR_ZERO(batt->iio_i);
0175     if (err)
0176         return dev_err_probe(dev, err,
0177                      "Failed to get current iio channel\n");
0178 
0179     batt->rechargeable_gpio = devm_gpiod_get(dev, "rechargeable", GPIOD_IN);
0180     err = PTR_ERR_OR_ZERO(batt->rechargeable_gpio);
0181     if (err)
0182         return dev_err_probe(dev, err,
0183                      "Failed to get rechargeable gpio\n");
0184 
0185     /*
0186      * The rechargeable battery indication switch cannot be changed without
0187      * removing the battery, so we only need to read it once.
0188      */
0189     if (gpiod_get_value(batt->rechargeable_gpio)) {
0190         /* 2-cell Li-ion, 7.4V nominal */
0191         batt->technology = POWER_SUPPLY_TECHNOLOGY_LION;
0192         batt->v_max = 84000000;
0193         batt->v_min = 60000000;
0194     } else {
0195         /* 6x AA Alkaline, 9V nominal */
0196         batt->technology = POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
0197         batt->v_max = 90000000;
0198         batt->v_min = 48000000;
0199     }
0200 
0201     psy_cfg.of_node = pdev->dev.of_node;
0202     psy_cfg.drv_data = batt;
0203 
0204     batt->psy = devm_power_supply_register(dev, &lego_ev3_battery_desc,
0205                            &psy_cfg);
0206     err = PTR_ERR_OR_ZERO(batt->psy);
0207     if (err) {
0208         dev_err(dev, "failed to register power supply\n");
0209         return err;
0210     }
0211 
0212     return 0;
0213 }
0214 
0215 static const struct of_device_id of_lego_ev3_battery_match[] = {
0216     { .compatible = "lego,ev3-battery", },
0217     { }
0218 };
0219 MODULE_DEVICE_TABLE(of, of_lego_ev3_battery_match);
0220 
0221 static struct platform_driver lego_ev3_battery_driver = {
0222     .driver = {
0223         .name       = "lego-ev3-battery",
0224         .of_match_table = of_lego_ev3_battery_match,
0225     },
0226     .probe  = lego_ev3_battery_probe,
0227 };
0228 module_platform_driver(lego_ev3_battery_driver);
0229 
0230 MODULE_LICENSE("GPL");
0231 MODULE_AUTHOR("David Lechner <david@lechnology.com>");
0232 MODULE_DESCRIPTION("LEGO MINDSTORMS EV3 Battery Driver");