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