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