162306a36Sopenharmony_ci/* 262306a36Sopenharmony_ci * Battery driver for LEGO MINDSTORMS EV3 362306a36Sopenharmony_ci * 462306a36Sopenharmony_ci * Copyright (C) 2017 David Lechner <david@lechnology.com> 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * This program is free software; you can redistribute it and/or modify 762306a36Sopenharmony_ci * it under the terms of the GNU General Public License version 2 as 862306a36Sopenharmony_ci * published by the Free Software Foundation. 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci * This program is distributed "as is" WITHOUT ANY WARRANTY of any 1162306a36Sopenharmony_ci * kind, whether express or implied; without even the implied warranty 1262306a36Sopenharmony_ci * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1362306a36Sopenharmony_ci * GNU General Public License for more details. 1462306a36Sopenharmony_ci */ 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci#include <linux/delay.h> 1762306a36Sopenharmony_ci#include <linux/err.h> 1862306a36Sopenharmony_ci#include <linux/gpio/consumer.h> 1962306a36Sopenharmony_ci#include <linux/iio/consumer.h> 2062306a36Sopenharmony_ci#include <linux/iio/types.h> 2162306a36Sopenharmony_ci#include <linux/kernel.h> 2262306a36Sopenharmony_ci#include <linux/module.h> 2362306a36Sopenharmony_ci#include <linux/mod_devicetable.h> 2462306a36Sopenharmony_ci#include <linux/platform_device.h> 2562306a36Sopenharmony_ci#include <linux/power_supply.h> 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_cistruct lego_ev3_battery { 2862306a36Sopenharmony_ci struct iio_channel *iio_v; 2962306a36Sopenharmony_ci struct iio_channel *iio_i; 3062306a36Sopenharmony_ci struct gpio_desc *rechargeable_gpio; 3162306a36Sopenharmony_ci struct power_supply *psy; 3262306a36Sopenharmony_ci int technology; 3362306a36Sopenharmony_ci int v_max; 3462306a36Sopenharmony_ci int v_min; 3562306a36Sopenharmony_ci}; 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_cistatic int lego_ev3_battery_get_property(struct power_supply *psy, 3862306a36Sopenharmony_ci enum power_supply_property psp, 3962306a36Sopenharmony_ci union power_supply_propval *val) 4062306a36Sopenharmony_ci{ 4162306a36Sopenharmony_ci struct lego_ev3_battery *batt = power_supply_get_drvdata(psy); 4262306a36Sopenharmony_ci int ret, val2; 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci switch (psp) { 4562306a36Sopenharmony_ci case POWER_SUPPLY_PROP_TECHNOLOGY: 4662306a36Sopenharmony_ci val->intval = batt->technology; 4762306a36Sopenharmony_ci break; 4862306a36Sopenharmony_ci case POWER_SUPPLY_PROP_VOLTAGE_NOW: 4962306a36Sopenharmony_ci /* battery voltage is iio channel * 2 + Vce of transistor */ 5062306a36Sopenharmony_ci ret = iio_read_channel_processed(batt->iio_v, &val->intval); 5162306a36Sopenharmony_ci if (ret) 5262306a36Sopenharmony_ci return ret; 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci val->intval *= 2000; 5562306a36Sopenharmony_ci val->intval += 50000; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci /* plus adjust for shunt resistor drop */ 5862306a36Sopenharmony_ci ret = iio_read_channel_processed(batt->iio_i, &val2); 5962306a36Sopenharmony_ci if (ret) 6062306a36Sopenharmony_ci return ret; 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci val2 *= 1000; 6362306a36Sopenharmony_ci val2 /= 15; 6462306a36Sopenharmony_ci val->intval += val2; 6562306a36Sopenharmony_ci break; 6662306a36Sopenharmony_ci case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: 6762306a36Sopenharmony_ci val->intval = batt->v_max; 6862306a36Sopenharmony_ci break; 6962306a36Sopenharmony_ci case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: 7062306a36Sopenharmony_ci val->intval = batt->v_min; 7162306a36Sopenharmony_ci break; 7262306a36Sopenharmony_ci case POWER_SUPPLY_PROP_CURRENT_NOW: 7362306a36Sopenharmony_ci /* battery current is iio channel / 15 / 0.05 ohms */ 7462306a36Sopenharmony_ci ret = iio_read_channel_processed(batt->iio_i, &val->intval); 7562306a36Sopenharmony_ci if (ret) 7662306a36Sopenharmony_ci return ret; 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci val->intval *= 20000; 7962306a36Sopenharmony_ci val->intval /= 15; 8062306a36Sopenharmony_ci break; 8162306a36Sopenharmony_ci case POWER_SUPPLY_PROP_SCOPE: 8262306a36Sopenharmony_ci val->intval = POWER_SUPPLY_SCOPE_SYSTEM; 8362306a36Sopenharmony_ci break; 8462306a36Sopenharmony_ci default: 8562306a36Sopenharmony_ci return -EINVAL; 8662306a36Sopenharmony_ci } 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci return 0; 8962306a36Sopenharmony_ci} 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_cistatic int lego_ev3_battery_set_property(struct power_supply *psy, 9262306a36Sopenharmony_ci enum power_supply_property psp, 9362306a36Sopenharmony_ci const union power_supply_propval *val) 9462306a36Sopenharmony_ci{ 9562306a36Sopenharmony_ci struct lego_ev3_battery *batt = power_supply_get_drvdata(psy); 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci switch (psp) { 9862306a36Sopenharmony_ci case POWER_SUPPLY_PROP_TECHNOLOGY: 9962306a36Sopenharmony_ci /* 10062306a36Sopenharmony_ci * Only allow changing technology from Unknown to NiMH. Li-ion 10162306a36Sopenharmony_ci * batteries are automatically detected and should not be 10262306a36Sopenharmony_ci * overridden. Rechargeable AA batteries, on the other hand, 10362306a36Sopenharmony_ci * cannot be automatically detected, and so must be manually 10462306a36Sopenharmony_ci * specified. This should only be set once during system init, 10562306a36Sopenharmony_ci * so there is no mechanism to go back to Unknown. 10662306a36Sopenharmony_ci */ 10762306a36Sopenharmony_ci if (batt->technology != POWER_SUPPLY_TECHNOLOGY_UNKNOWN) 10862306a36Sopenharmony_ci return -EINVAL; 10962306a36Sopenharmony_ci switch (val->intval) { 11062306a36Sopenharmony_ci case POWER_SUPPLY_TECHNOLOGY_NiMH: 11162306a36Sopenharmony_ci batt->technology = POWER_SUPPLY_TECHNOLOGY_NiMH; 11262306a36Sopenharmony_ci batt->v_max = 7800000; 11362306a36Sopenharmony_ci batt->v_min = 5400000; 11462306a36Sopenharmony_ci break; 11562306a36Sopenharmony_ci default: 11662306a36Sopenharmony_ci return -EINVAL; 11762306a36Sopenharmony_ci } 11862306a36Sopenharmony_ci break; 11962306a36Sopenharmony_ci default: 12062306a36Sopenharmony_ci return -EINVAL; 12162306a36Sopenharmony_ci } 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci return 0; 12462306a36Sopenharmony_ci} 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_cistatic int lego_ev3_battery_property_is_writeable(struct power_supply *psy, 12762306a36Sopenharmony_ci enum power_supply_property psp) 12862306a36Sopenharmony_ci{ 12962306a36Sopenharmony_ci struct lego_ev3_battery *batt = power_supply_get_drvdata(psy); 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci return psp == POWER_SUPPLY_PROP_TECHNOLOGY && 13262306a36Sopenharmony_ci batt->technology == POWER_SUPPLY_TECHNOLOGY_UNKNOWN; 13362306a36Sopenharmony_ci} 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_cistatic enum power_supply_property lego_ev3_battery_props[] = { 13662306a36Sopenharmony_ci POWER_SUPPLY_PROP_TECHNOLOGY, 13762306a36Sopenharmony_ci POWER_SUPPLY_PROP_VOLTAGE_NOW, 13862306a36Sopenharmony_ci POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, 13962306a36Sopenharmony_ci POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, 14062306a36Sopenharmony_ci POWER_SUPPLY_PROP_CURRENT_NOW, 14162306a36Sopenharmony_ci POWER_SUPPLY_PROP_SCOPE, 14262306a36Sopenharmony_ci}; 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_cistatic const struct power_supply_desc lego_ev3_battery_desc = { 14562306a36Sopenharmony_ci .name = "lego-ev3-battery", 14662306a36Sopenharmony_ci .type = POWER_SUPPLY_TYPE_BATTERY, 14762306a36Sopenharmony_ci .properties = lego_ev3_battery_props, 14862306a36Sopenharmony_ci .num_properties = ARRAY_SIZE(lego_ev3_battery_props), 14962306a36Sopenharmony_ci .get_property = lego_ev3_battery_get_property, 15062306a36Sopenharmony_ci .set_property = lego_ev3_battery_set_property, 15162306a36Sopenharmony_ci .property_is_writeable = lego_ev3_battery_property_is_writeable, 15262306a36Sopenharmony_ci}; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_cistatic int lego_ev3_battery_probe(struct platform_device *pdev) 15562306a36Sopenharmony_ci{ 15662306a36Sopenharmony_ci struct device *dev = &pdev->dev; 15762306a36Sopenharmony_ci struct lego_ev3_battery *batt; 15862306a36Sopenharmony_ci struct power_supply_config psy_cfg = {}; 15962306a36Sopenharmony_ci int err; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci batt = devm_kzalloc(dev, sizeof(*batt), GFP_KERNEL); 16262306a36Sopenharmony_ci if (!batt) 16362306a36Sopenharmony_ci return -ENOMEM; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci platform_set_drvdata(pdev, batt); 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci batt->iio_v = devm_iio_channel_get(dev, "voltage"); 16862306a36Sopenharmony_ci err = PTR_ERR_OR_ZERO(batt->iio_v); 16962306a36Sopenharmony_ci if (err) 17062306a36Sopenharmony_ci return dev_err_probe(dev, err, 17162306a36Sopenharmony_ci "Failed to get voltage iio channel\n"); 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci batt->iio_i = devm_iio_channel_get(dev, "current"); 17462306a36Sopenharmony_ci err = PTR_ERR_OR_ZERO(batt->iio_i); 17562306a36Sopenharmony_ci if (err) 17662306a36Sopenharmony_ci return dev_err_probe(dev, err, 17762306a36Sopenharmony_ci "Failed to get current iio channel\n"); 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci batt->rechargeable_gpio = devm_gpiod_get(dev, "rechargeable", GPIOD_IN); 18062306a36Sopenharmony_ci err = PTR_ERR_OR_ZERO(batt->rechargeable_gpio); 18162306a36Sopenharmony_ci if (err) 18262306a36Sopenharmony_ci return dev_err_probe(dev, err, 18362306a36Sopenharmony_ci "Failed to get rechargeable gpio\n"); 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci /* 18662306a36Sopenharmony_ci * The rechargeable battery indication switch cannot be changed without 18762306a36Sopenharmony_ci * removing the battery, so we only need to read it once. 18862306a36Sopenharmony_ci */ 18962306a36Sopenharmony_ci if (gpiod_get_value(batt->rechargeable_gpio)) { 19062306a36Sopenharmony_ci /* 2-cell Li-ion, 7.4V nominal */ 19162306a36Sopenharmony_ci batt->technology = POWER_SUPPLY_TECHNOLOGY_LION; 19262306a36Sopenharmony_ci batt->v_max = 84000000; 19362306a36Sopenharmony_ci batt->v_min = 60000000; 19462306a36Sopenharmony_ci } else { 19562306a36Sopenharmony_ci /* 6x AA Alkaline, 9V nominal */ 19662306a36Sopenharmony_ci batt->technology = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; 19762306a36Sopenharmony_ci batt->v_max = 90000000; 19862306a36Sopenharmony_ci batt->v_min = 48000000; 19962306a36Sopenharmony_ci } 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci psy_cfg.of_node = pdev->dev.of_node; 20262306a36Sopenharmony_ci psy_cfg.drv_data = batt; 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci batt->psy = devm_power_supply_register(dev, &lego_ev3_battery_desc, 20562306a36Sopenharmony_ci &psy_cfg); 20662306a36Sopenharmony_ci err = PTR_ERR_OR_ZERO(batt->psy); 20762306a36Sopenharmony_ci if (err) { 20862306a36Sopenharmony_ci dev_err(dev, "failed to register power supply\n"); 20962306a36Sopenharmony_ci return err; 21062306a36Sopenharmony_ci } 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci return 0; 21362306a36Sopenharmony_ci} 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_cistatic const struct of_device_id of_lego_ev3_battery_match[] = { 21662306a36Sopenharmony_ci { .compatible = "lego,ev3-battery", }, 21762306a36Sopenharmony_ci { } 21862306a36Sopenharmony_ci}; 21962306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, of_lego_ev3_battery_match); 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_cistatic struct platform_driver lego_ev3_battery_driver = { 22262306a36Sopenharmony_ci .driver = { 22362306a36Sopenharmony_ci .name = "lego-ev3-battery", 22462306a36Sopenharmony_ci .of_match_table = of_lego_ev3_battery_match, 22562306a36Sopenharmony_ci }, 22662306a36Sopenharmony_ci .probe = lego_ev3_battery_probe, 22762306a36Sopenharmony_ci}; 22862306a36Sopenharmony_cimodule_platform_driver(lego_ev3_battery_driver); 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 23162306a36Sopenharmony_ciMODULE_AUTHOR("David Lechner <david@lechnology.com>"); 23262306a36Sopenharmony_ciMODULE_DESCRIPTION("LEGO MINDSTORMS EV3 Battery Driver"); 233