lego_ev3_battery.c (6463B)
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 ret, 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 ret = iio_read_channel_processed(batt->iio_v, &val->intval); 51 if (ret) 52 return ret; 53 54 val->intval *= 2000; 55 val->intval += 50000; 56 57 /* plus adjust for shunt resistor drop */ 58 ret = iio_read_channel_processed(batt->iio_i, &val2); 59 if (ret) 60 return ret; 61 62 val2 *= 1000; 63 val2 /= 15; 64 val->intval += val2; 65 break; 66 case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: 67 val->intval = batt->v_max; 68 break; 69 case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: 70 val->intval = batt->v_min; 71 break; 72 case POWER_SUPPLY_PROP_CURRENT_NOW: 73 /* battery current is iio channel / 15 / 0.05 ohms */ 74 ret = iio_read_channel_processed(batt->iio_i, &val->intval); 75 if (ret) 76 return ret; 77 78 val->intval *= 20000; 79 val->intval /= 15; 80 break; 81 case POWER_SUPPLY_PROP_SCOPE: 82 val->intval = POWER_SUPPLY_SCOPE_SYSTEM; 83 break; 84 default: 85 return -EINVAL; 86 } 87 88 return 0; 89} 90 91static int lego_ev3_battery_set_property(struct power_supply *psy, 92 enum power_supply_property psp, 93 const union power_supply_propval *val) 94{ 95 struct lego_ev3_battery *batt = power_supply_get_drvdata(psy); 96 97 switch (psp) { 98 case POWER_SUPPLY_PROP_TECHNOLOGY: 99 /* 100 * Only allow changing technology from Unknown to NiMH. Li-ion 101 * batteries are automatically detected and should not be 102 * overridden. Rechargeable AA batteries, on the other hand, 103 * cannot be automatically detected, and so must be manually 104 * specified. This should only be set once during system init, 105 * so there is no mechanism to go back to Unknown. 106 */ 107 if (batt->technology != POWER_SUPPLY_TECHNOLOGY_UNKNOWN) 108 return -EINVAL; 109 switch (val->intval) { 110 case POWER_SUPPLY_TECHNOLOGY_NiMH: 111 batt->technology = POWER_SUPPLY_TECHNOLOGY_NiMH; 112 batt->v_max = 7800000; 113 batt->v_min = 5400000; 114 break; 115 default: 116 return -EINVAL; 117 } 118 break; 119 default: 120 return -EINVAL; 121 } 122 123 return 0; 124} 125 126static int lego_ev3_battery_property_is_writeable(struct power_supply *psy, 127 enum power_supply_property psp) 128{ 129 struct lego_ev3_battery *batt = power_supply_get_drvdata(psy); 130 131 return psp == POWER_SUPPLY_PROP_TECHNOLOGY && 132 batt->technology == POWER_SUPPLY_TECHNOLOGY_UNKNOWN; 133} 134 135static enum power_supply_property lego_ev3_battery_props[] = { 136 POWER_SUPPLY_PROP_TECHNOLOGY, 137 POWER_SUPPLY_PROP_VOLTAGE_NOW, 138 POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, 139 POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, 140 POWER_SUPPLY_PROP_CURRENT_NOW, 141 POWER_SUPPLY_PROP_SCOPE, 142}; 143 144static const struct power_supply_desc lego_ev3_battery_desc = { 145 .name = "lego-ev3-battery", 146 .type = POWER_SUPPLY_TYPE_BATTERY, 147 .properties = lego_ev3_battery_props, 148 .num_properties = ARRAY_SIZE(lego_ev3_battery_props), 149 .get_property = lego_ev3_battery_get_property, 150 .set_property = lego_ev3_battery_set_property, 151 .property_is_writeable = lego_ev3_battery_property_is_writeable, 152}; 153 154static int lego_ev3_battery_probe(struct platform_device *pdev) 155{ 156 struct device *dev = &pdev->dev; 157 struct lego_ev3_battery *batt; 158 struct power_supply_config psy_cfg = {}; 159 int err; 160 161 batt = devm_kzalloc(dev, sizeof(*batt), GFP_KERNEL); 162 if (!batt) 163 return -ENOMEM; 164 165 platform_set_drvdata(pdev, batt); 166 167 batt->iio_v = devm_iio_channel_get(dev, "voltage"); 168 err = PTR_ERR_OR_ZERO(batt->iio_v); 169 if (err) 170 return dev_err_probe(dev, err, 171 "Failed to get voltage iio channel\n"); 172 173 batt->iio_i = devm_iio_channel_get(dev, "current"); 174 err = PTR_ERR_OR_ZERO(batt->iio_i); 175 if (err) 176 return dev_err_probe(dev, err, 177 "Failed to get current iio channel\n"); 178 179 batt->rechargeable_gpio = devm_gpiod_get(dev, "rechargeable", GPIOD_IN); 180 err = PTR_ERR_OR_ZERO(batt->rechargeable_gpio); 181 if (err) 182 return dev_err_probe(dev, err, 183 "Failed to get rechargeable gpio\n"); 184 185 /* 186 * The rechargeable battery indication switch cannot be changed without 187 * removing the battery, so we only need to read it once. 188 */ 189 if (gpiod_get_value(batt->rechargeable_gpio)) { 190 /* 2-cell Li-ion, 7.4V nominal */ 191 batt->technology = POWER_SUPPLY_TECHNOLOGY_LION; 192 batt->v_max = 84000000; 193 batt->v_min = 60000000; 194 } else { 195 /* 6x AA Alkaline, 9V nominal */ 196 batt->technology = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; 197 batt->v_max = 90000000; 198 batt->v_min = 48000000; 199 } 200 201 psy_cfg.of_node = pdev->dev.of_node; 202 psy_cfg.drv_data = batt; 203 204 batt->psy = devm_power_supply_register(dev, &lego_ev3_battery_desc, 205 &psy_cfg); 206 err = PTR_ERR_OR_ZERO(batt->psy); 207 if (err) { 208 dev_err(dev, "failed to register power supply\n"); 209 return err; 210 } 211 212 return 0; 213} 214 215static const struct of_device_id of_lego_ev3_battery_match[] = { 216 { .compatible = "lego,ev3-battery", }, 217 { } 218}; 219MODULE_DEVICE_TABLE(of, of_lego_ev3_battery_match); 220 221static struct platform_driver lego_ev3_battery_driver = { 222 .driver = { 223 .name = "lego-ev3-battery", 224 .of_match_table = of_lego_ev3_battery_match, 225 }, 226 .probe = lego_ev3_battery_probe, 227}; 228module_platform_driver(lego_ev3_battery_driver); 229 230MODULE_LICENSE("GPL"); 231MODULE_AUTHOR("David Lechner <david@lechnology.com>"); 232MODULE_DESCRIPTION("LEGO MINDSTORMS EV3 Battery Driver");