18c2ecf20Sopenharmony_ci/*
28c2ecf20Sopenharmony_ci * Battery driver for LEGO MINDSTORMS EV3
38c2ecf20Sopenharmony_ci *
48c2ecf20Sopenharmony_ci * Copyright (C) 2017 David Lechner <david@lechnology.com>
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci * This program is free software; you can redistribute it and/or modify
78c2ecf20Sopenharmony_ci * it under the terms of the GNU General Public License version 2 as
88c2ecf20Sopenharmony_ci * published by the Free Software Foundation.
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci * This program is distributed "as is" WITHOUT ANY WARRANTY of any
118c2ecf20Sopenharmony_ci * kind, whether express or implied; without even the implied warranty
128c2ecf20Sopenharmony_ci * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
138c2ecf20Sopenharmony_ci * GNU General Public License for more details.
148c2ecf20Sopenharmony_ci */
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci#include <linux/delay.h>
178c2ecf20Sopenharmony_ci#include <linux/err.h>
188c2ecf20Sopenharmony_ci#include <linux/gpio/consumer.h>
198c2ecf20Sopenharmony_ci#include <linux/iio/consumer.h>
208c2ecf20Sopenharmony_ci#include <linux/iio/types.h>
218c2ecf20Sopenharmony_ci#include <linux/kernel.h>
228c2ecf20Sopenharmony_ci#include <linux/module.h>
238c2ecf20Sopenharmony_ci#include <linux/of_device.h>
248c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
258c2ecf20Sopenharmony_ci#include <linux/power_supply.h>
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_cistruct lego_ev3_battery {
288c2ecf20Sopenharmony_ci	struct iio_channel *iio_v;
298c2ecf20Sopenharmony_ci	struct iio_channel *iio_i;
308c2ecf20Sopenharmony_ci	struct gpio_desc *rechargeable_gpio;
318c2ecf20Sopenharmony_ci	struct power_supply *psy;
328c2ecf20Sopenharmony_ci	int technology;
338c2ecf20Sopenharmony_ci	int v_max;
348c2ecf20Sopenharmony_ci	int v_min;
358c2ecf20Sopenharmony_ci};
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_cistatic int lego_ev3_battery_get_property(struct power_supply *psy,
388c2ecf20Sopenharmony_ci					 enum power_supply_property psp,
398c2ecf20Sopenharmony_ci					 union power_supply_propval *val)
408c2ecf20Sopenharmony_ci{
418c2ecf20Sopenharmony_ci	struct lego_ev3_battery *batt = power_supply_get_drvdata(psy);
428c2ecf20Sopenharmony_ci	int ret, val2;
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci	switch (psp) {
458c2ecf20Sopenharmony_ci	case POWER_SUPPLY_PROP_TECHNOLOGY:
468c2ecf20Sopenharmony_ci		val->intval = batt->technology;
478c2ecf20Sopenharmony_ci		break;
488c2ecf20Sopenharmony_ci	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
498c2ecf20Sopenharmony_ci		/* battery voltage is iio channel * 2 + Vce of transistor */
508c2ecf20Sopenharmony_ci		ret = iio_read_channel_processed(batt->iio_v, &val->intval);
518c2ecf20Sopenharmony_ci		if (ret)
528c2ecf20Sopenharmony_ci			return ret;
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci		val->intval *= 2000;
558c2ecf20Sopenharmony_ci		val->intval += 50000;
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci		/* plus adjust for shunt resistor drop */
588c2ecf20Sopenharmony_ci		ret = iio_read_channel_processed(batt->iio_i, &val2);
598c2ecf20Sopenharmony_ci		if (ret)
608c2ecf20Sopenharmony_ci			return ret;
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci		val2 *= 1000;
638c2ecf20Sopenharmony_ci		val2 /= 15;
648c2ecf20Sopenharmony_ci		val->intval += val2;
658c2ecf20Sopenharmony_ci		break;
668c2ecf20Sopenharmony_ci	case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
678c2ecf20Sopenharmony_ci		val->intval = batt->v_max;
688c2ecf20Sopenharmony_ci		break;
698c2ecf20Sopenharmony_ci	case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
708c2ecf20Sopenharmony_ci		val->intval = batt->v_min;
718c2ecf20Sopenharmony_ci		break;
728c2ecf20Sopenharmony_ci	case POWER_SUPPLY_PROP_CURRENT_NOW:
738c2ecf20Sopenharmony_ci		/* battery current is iio channel / 15 / 0.05 ohms */
748c2ecf20Sopenharmony_ci		ret = iio_read_channel_processed(batt->iio_i, &val->intval);
758c2ecf20Sopenharmony_ci		if (ret)
768c2ecf20Sopenharmony_ci			return ret;
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_ci		val->intval *= 20000;
798c2ecf20Sopenharmony_ci		val->intval /= 15;
808c2ecf20Sopenharmony_ci		break;
818c2ecf20Sopenharmony_ci	case POWER_SUPPLY_PROP_SCOPE:
828c2ecf20Sopenharmony_ci		val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
838c2ecf20Sopenharmony_ci		break;
848c2ecf20Sopenharmony_ci	default:
858c2ecf20Sopenharmony_ci		return -EINVAL;
868c2ecf20Sopenharmony_ci	}
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci	return 0;
898c2ecf20Sopenharmony_ci}
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_cistatic int lego_ev3_battery_set_property(struct power_supply *psy,
928c2ecf20Sopenharmony_ci					 enum power_supply_property psp,
938c2ecf20Sopenharmony_ci					 const union power_supply_propval *val)
948c2ecf20Sopenharmony_ci{
958c2ecf20Sopenharmony_ci	struct lego_ev3_battery *batt = power_supply_get_drvdata(psy);
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci	switch (psp) {
988c2ecf20Sopenharmony_ci	case POWER_SUPPLY_PROP_TECHNOLOGY:
998c2ecf20Sopenharmony_ci		/*
1008c2ecf20Sopenharmony_ci		 * Only allow changing technology from Unknown to NiMH. Li-ion
1018c2ecf20Sopenharmony_ci		 * batteries are automatically detected and should not be
1028c2ecf20Sopenharmony_ci		 * overridden. Rechargeable AA batteries, on the other hand,
1038c2ecf20Sopenharmony_ci		 * cannot be automatically detected, and so must be manually
1048c2ecf20Sopenharmony_ci		 * specified. This should only be set once during system init,
1058c2ecf20Sopenharmony_ci		 * so there is no mechanism to go back to Unknown.
1068c2ecf20Sopenharmony_ci		 */
1078c2ecf20Sopenharmony_ci		if (batt->technology != POWER_SUPPLY_TECHNOLOGY_UNKNOWN)
1088c2ecf20Sopenharmony_ci			return -EINVAL;
1098c2ecf20Sopenharmony_ci		switch (val->intval) {
1108c2ecf20Sopenharmony_ci		case POWER_SUPPLY_TECHNOLOGY_NiMH:
1118c2ecf20Sopenharmony_ci			batt->technology = POWER_SUPPLY_TECHNOLOGY_NiMH;
1128c2ecf20Sopenharmony_ci			batt->v_max = 7800000;
1138c2ecf20Sopenharmony_ci			batt->v_min = 5400000;
1148c2ecf20Sopenharmony_ci			break;
1158c2ecf20Sopenharmony_ci		default:
1168c2ecf20Sopenharmony_ci			return -EINVAL;
1178c2ecf20Sopenharmony_ci		}
1188c2ecf20Sopenharmony_ci		break;
1198c2ecf20Sopenharmony_ci	default:
1208c2ecf20Sopenharmony_ci		return -EINVAL;
1218c2ecf20Sopenharmony_ci	}
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	return 0;
1248c2ecf20Sopenharmony_ci}
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_cistatic int lego_ev3_battery_property_is_writeable(struct power_supply *psy,
1278c2ecf20Sopenharmony_ci						  enum power_supply_property psp)
1288c2ecf20Sopenharmony_ci{
1298c2ecf20Sopenharmony_ci	struct lego_ev3_battery *batt = power_supply_get_drvdata(psy);
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci	return psp == POWER_SUPPLY_PROP_TECHNOLOGY &&
1328c2ecf20Sopenharmony_ci		batt->technology == POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
1338c2ecf20Sopenharmony_ci}
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_cistatic enum power_supply_property lego_ev3_battery_props[] = {
1368c2ecf20Sopenharmony_ci	POWER_SUPPLY_PROP_TECHNOLOGY,
1378c2ecf20Sopenharmony_ci	POWER_SUPPLY_PROP_VOLTAGE_NOW,
1388c2ecf20Sopenharmony_ci	POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
1398c2ecf20Sopenharmony_ci	POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
1408c2ecf20Sopenharmony_ci	POWER_SUPPLY_PROP_CURRENT_NOW,
1418c2ecf20Sopenharmony_ci	POWER_SUPPLY_PROP_SCOPE,
1428c2ecf20Sopenharmony_ci};
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_cistatic const struct power_supply_desc lego_ev3_battery_desc = {
1458c2ecf20Sopenharmony_ci	.name			= "lego-ev3-battery",
1468c2ecf20Sopenharmony_ci	.type			= POWER_SUPPLY_TYPE_BATTERY,
1478c2ecf20Sopenharmony_ci	.properties		= lego_ev3_battery_props,
1488c2ecf20Sopenharmony_ci	.num_properties		= ARRAY_SIZE(lego_ev3_battery_props),
1498c2ecf20Sopenharmony_ci	.get_property		= lego_ev3_battery_get_property,
1508c2ecf20Sopenharmony_ci	.set_property		= lego_ev3_battery_set_property,
1518c2ecf20Sopenharmony_ci	.property_is_writeable	= lego_ev3_battery_property_is_writeable,
1528c2ecf20Sopenharmony_ci};
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_cistatic int lego_ev3_battery_probe(struct platform_device *pdev)
1558c2ecf20Sopenharmony_ci{
1568c2ecf20Sopenharmony_ci	struct device *dev = &pdev->dev;
1578c2ecf20Sopenharmony_ci	struct lego_ev3_battery *batt;
1588c2ecf20Sopenharmony_ci	struct power_supply_config psy_cfg = {};
1598c2ecf20Sopenharmony_ci	int err;
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci	batt = devm_kzalloc(dev, sizeof(*batt), GFP_KERNEL);
1628c2ecf20Sopenharmony_ci	if (!batt)
1638c2ecf20Sopenharmony_ci		return -ENOMEM;
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, batt);
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	batt->iio_v = devm_iio_channel_get(dev, "voltage");
1688c2ecf20Sopenharmony_ci	err = PTR_ERR_OR_ZERO(batt->iio_v);
1698c2ecf20Sopenharmony_ci	if (err)
1708c2ecf20Sopenharmony_ci		return dev_err_probe(dev, err,
1718c2ecf20Sopenharmony_ci				     "Failed to get voltage iio channel\n");
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_ci	batt->iio_i = devm_iio_channel_get(dev, "current");
1748c2ecf20Sopenharmony_ci	err = PTR_ERR_OR_ZERO(batt->iio_i);
1758c2ecf20Sopenharmony_ci	if (err)
1768c2ecf20Sopenharmony_ci		return dev_err_probe(dev, err,
1778c2ecf20Sopenharmony_ci				     "Failed to get current iio channel\n");
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci	batt->rechargeable_gpio = devm_gpiod_get(dev, "rechargeable", GPIOD_IN);
1808c2ecf20Sopenharmony_ci	err = PTR_ERR_OR_ZERO(batt->rechargeable_gpio);
1818c2ecf20Sopenharmony_ci	if (err)
1828c2ecf20Sopenharmony_ci		return dev_err_probe(dev, err,
1838c2ecf20Sopenharmony_ci				     "Failed to get rechargeable gpio\n");
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_ci	/*
1868c2ecf20Sopenharmony_ci	 * The rechargeable battery indication switch cannot be changed without
1878c2ecf20Sopenharmony_ci	 * removing the battery, so we only need to read it once.
1888c2ecf20Sopenharmony_ci	 */
1898c2ecf20Sopenharmony_ci	if (gpiod_get_value(batt->rechargeable_gpio)) {
1908c2ecf20Sopenharmony_ci		/* 2-cell Li-ion, 7.4V nominal */
1918c2ecf20Sopenharmony_ci		batt->technology = POWER_SUPPLY_TECHNOLOGY_LION;
1928c2ecf20Sopenharmony_ci		batt->v_max = 84000000;
1938c2ecf20Sopenharmony_ci		batt->v_min = 60000000;
1948c2ecf20Sopenharmony_ci	} else {
1958c2ecf20Sopenharmony_ci		/* 6x AA Alkaline, 9V nominal */
1968c2ecf20Sopenharmony_ci		batt->technology = POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
1978c2ecf20Sopenharmony_ci		batt->v_max = 90000000;
1988c2ecf20Sopenharmony_ci		batt->v_min = 48000000;
1998c2ecf20Sopenharmony_ci	}
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci	psy_cfg.of_node = pdev->dev.of_node;
2028c2ecf20Sopenharmony_ci	psy_cfg.drv_data = batt;
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_ci	batt->psy = devm_power_supply_register(dev, &lego_ev3_battery_desc,
2058c2ecf20Sopenharmony_ci					       &psy_cfg);
2068c2ecf20Sopenharmony_ci	err = PTR_ERR_OR_ZERO(batt->psy);
2078c2ecf20Sopenharmony_ci	if (err) {
2088c2ecf20Sopenharmony_ci		dev_err(dev, "failed to register power supply\n");
2098c2ecf20Sopenharmony_ci		return err;
2108c2ecf20Sopenharmony_ci	}
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ci	return 0;
2138c2ecf20Sopenharmony_ci}
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_cistatic const struct of_device_id of_lego_ev3_battery_match[] = {
2168c2ecf20Sopenharmony_ci	{ .compatible = "lego,ev3-battery", },
2178c2ecf20Sopenharmony_ci	{ }
2188c2ecf20Sopenharmony_ci};
2198c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, of_lego_ev3_battery_match);
2208c2ecf20Sopenharmony_ci
2218c2ecf20Sopenharmony_cistatic struct platform_driver lego_ev3_battery_driver = {
2228c2ecf20Sopenharmony_ci	.driver	= {
2238c2ecf20Sopenharmony_ci		.name		= "lego-ev3-battery",
2248c2ecf20Sopenharmony_ci		.of_match_table = of_lego_ev3_battery_match,
2258c2ecf20Sopenharmony_ci	},
2268c2ecf20Sopenharmony_ci	.probe	= lego_ev3_battery_probe,
2278c2ecf20Sopenharmony_ci};
2288c2ecf20Sopenharmony_cimodule_platform_driver(lego_ev3_battery_driver);
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
2318c2ecf20Sopenharmony_ciMODULE_AUTHOR("David Lechner <david@lechnology.com>");
2328c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("LEGO MINDSTORMS EV3 Battery Driver");
233