18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Battery measurement code for Zipit Z2
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2009 Peter Edwards <sweetlilmre@gmail.com>
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/module.h>
98c2ecf20Sopenharmony_ci#include <linux/gpio.h>
108c2ecf20Sopenharmony_ci#include <linux/i2c.h>
118c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
128c2ecf20Sopenharmony_ci#include <linux/irq.h>
138c2ecf20Sopenharmony_ci#include <linux/power_supply.h>
148c2ecf20Sopenharmony_ci#include <linux/slab.h>
158c2ecf20Sopenharmony_ci#include <linux/z2_battery.h>
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ci#define	Z2_DEFAULT_NAME	"Z2"
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_cistruct z2_charger {
208c2ecf20Sopenharmony_ci	struct z2_battery_info		*info;
218c2ecf20Sopenharmony_ci	int				bat_status;
228c2ecf20Sopenharmony_ci	struct i2c_client		*client;
238c2ecf20Sopenharmony_ci	struct power_supply		*batt_ps;
248c2ecf20Sopenharmony_ci	struct power_supply_desc	batt_ps_desc;
258c2ecf20Sopenharmony_ci	struct mutex			work_lock;
268c2ecf20Sopenharmony_ci	struct work_struct		bat_work;
278c2ecf20Sopenharmony_ci};
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_cistatic unsigned long z2_read_bat(struct z2_charger *charger)
308c2ecf20Sopenharmony_ci{
318c2ecf20Sopenharmony_ci	int data;
328c2ecf20Sopenharmony_ci	data = i2c_smbus_read_byte_data(charger->client,
338c2ecf20Sopenharmony_ci					charger->info->batt_I2C_reg);
348c2ecf20Sopenharmony_ci	if (data < 0)
358c2ecf20Sopenharmony_ci		return 0;
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_ci	return data * charger->info->batt_mult / charger->info->batt_div;
388c2ecf20Sopenharmony_ci}
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_cistatic int z2_batt_get_property(struct power_supply *batt_ps,
418c2ecf20Sopenharmony_ci			    enum power_supply_property psp,
428c2ecf20Sopenharmony_ci			    union power_supply_propval *val)
438c2ecf20Sopenharmony_ci{
448c2ecf20Sopenharmony_ci	struct z2_charger *charger = power_supply_get_drvdata(batt_ps);
458c2ecf20Sopenharmony_ci	struct z2_battery_info *info = charger->info;
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci	switch (psp) {
488c2ecf20Sopenharmony_ci	case POWER_SUPPLY_PROP_STATUS:
498c2ecf20Sopenharmony_ci		val->intval = charger->bat_status;
508c2ecf20Sopenharmony_ci		break;
518c2ecf20Sopenharmony_ci	case POWER_SUPPLY_PROP_TECHNOLOGY:
528c2ecf20Sopenharmony_ci		val->intval = info->batt_tech;
538c2ecf20Sopenharmony_ci		break;
548c2ecf20Sopenharmony_ci	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
558c2ecf20Sopenharmony_ci		if (info->batt_I2C_reg >= 0)
568c2ecf20Sopenharmony_ci			val->intval = z2_read_bat(charger);
578c2ecf20Sopenharmony_ci		else
588c2ecf20Sopenharmony_ci			return -EINVAL;
598c2ecf20Sopenharmony_ci		break;
608c2ecf20Sopenharmony_ci	case POWER_SUPPLY_PROP_VOLTAGE_MAX:
618c2ecf20Sopenharmony_ci		if (info->max_voltage >= 0)
628c2ecf20Sopenharmony_ci			val->intval = info->max_voltage;
638c2ecf20Sopenharmony_ci		else
648c2ecf20Sopenharmony_ci			return -EINVAL;
658c2ecf20Sopenharmony_ci		break;
668c2ecf20Sopenharmony_ci	case POWER_SUPPLY_PROP_VOLTAGE_MIN:
678c2ecf20Sopenharmony_ci		if (info->min_voltage >= 0)
688c2ecf20Sopenharmony_ci			val->intval = info->min_voltage;
698c2ecf20Sopenharmony_ci		else
708c2ecf20Sopenharmony_ci			return -EINVAL;
718c2ecf20Sopenharmony_ci		break;
728c2ecf20Sopenharmony_ci	case POWER_SUPPLY_PROP_PRESENT:
738c2ecf20Sopenharmony_ci		val->intval = 1;
748c2ecf20Sopenharmony_ci		break;
758c2ecf20Sopenharmony_ci	default:
768c2ecf20Sopenharmony_ci		return -EINVAL;
778c2ecf20Sopenharmony_ci	}
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci	return 0;
808c2ecf20Sopenharmony_ci}
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_cistatic void z2_batt_ext_power_changed(struct power_supply *batt_ps)
838c2ecf20Sopenharmony_ci{
848c2ecf20Sopenharmony_ci	struct z2_charger *charger = power_supply_get_drvdata(batt_ps);
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	schedule_work(&charger->bat_work);
878c2ecf20Sopenharmony_ci}
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_cistatic void z2_batt_update(struct z2_charger *charger)
908c2ecf20Sopenharmony_ci{
918c2ecf20Sopenharmony_ci	int old_status = charger->bat_status;
928c2ecf20Sopenharmony_ci	struct z2_battery_info *info;
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci	info = charger->info;
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci	mutex_lock(&charger->work_lock);
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci	charger->bat_status = (info->charge_gpio >= 0) ?
998c2ecf20Sopenharmony_ci		(gpio_get_value(info->charge_gpio) ?
1008c2ecf20Sopenharmony_ci		POWER_SUPPLY_STATUS_CHARGING :
1018c2ecf20Sopenharmony_ci		POWER_SUPPLY_STATUS_DISCHARGING) :
1028c2ecf20Sopenharmony_ci		POWER_SUPPLY_STATUS_UNKNOWN;
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_ci	if (old_status != charger->bat_status) {
1058c2ecf20Sopenharmony_ci		pr_debug("%s: %i -> %i\n", charger->batt_ps->desc->name,
1068c2ecf20Sopenharmony_ci				old_status,
1078c2ecf20Sopenharmony_ci				charger->bat_status);
1088c2ecf20Sopenharmony_ci		power_supply_changed(charger->batt_ps);
1098c2ecf20Sopenharmony_ci	}
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci	mutex_unlock(&charger->work_lock);
1128c2ecf20Sopenharmony_ci}
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_cistatic void z2_batt_work(struct work_struct *work)
1158c2ecf20Sopenharmony_ci{
1168c2ecf20Sopenharmony_ci	struct z2_charger *charger;
1178c2ecf20Sopenharmony_ci	charger = container_of(work, struct z2_charger, bat_work);
1188c2ecf20Sopenharmony_ci	z2_batt_update(charger);
1198c2ecf20Sopenharmony_ci}
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_cistatic irqreturn_t z2_charge_switch_irq(int irq, void *devid)
1228c2ecf20Sopenharmony_ci{
1238c2ecf20Sopenharmony_ci	struct z2_charger *charger = devid;
1248c2ecf20Sopenharmony_ci	schedule_work(&charger->bat_work);
1258c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
1268c2ecf20Sopenharmony_ci}
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_cistatic int z2_batt_ps_init(struct z2_charger *charger, int props)
1298c2ecf20Sopenharmony_ci{
1308c2ecf20Sopenharmony_ci	int i = 0;
1318c2ecf20Sopenharmony_ci	enum power_supply_property *prop;
1328c2ecf20Sopenharmony_ci	struct z2_battery_info *info = charger->info;
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	if (info->charge_gpio >= 0)
1358c2ecf20Sopenharmony_ci		props++;	/* POWER_SUPPLY_PROP_STATUS */
1368c2ecf20Sopenharmony_ci	if (info->batt_tech >= 0)
1378c2ecf20Sopenharmony_ci		props++;	/* POWER_SUPPLY_PROP_TECHNOLOGY */
1388c2ecf20Sopenharmony_ci	if (info->batt_I2C_reg >= 0)
1398c2ecf20Sopenharmony_ci		props++;	/* POWER_SUPPLY_PROP_VOLTAGE_NOW */
1408c2ecf20Sopenharmony_ci	if (info->max_voltage >= 0)
1418c2ecf20Sopenharmony_ci		props++;	/* POWER_SUPPLY_PROP_VOLTAGE_MAX */
1428c2ecf20Sopenharmony_ci	if (info->min_voltage >= 0)
1438c2ecf20Sopenharmony_ci		props++;	/* POWER_SUPPLY_PROP_VOLTAGE_MIN */
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_ci	prop = kcalloc(props, sizeof(*prop), GFP_KERNEL);
1468c2ecf20Sopenharmony_ci	if (!prop)
1478c2ecf20Sopenharmony_ci		return -ENOMEM;
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	prop[i++] = POWER_SUPPLY_PROP_PRESENT;
1508c2ecf20Sopenharmony_ci	if (info->charge_gpio >= 0)
1518c2ecf20Sopenharmony_ci		prop[i++] = POWER_SUPPLY_PROP_STATUS;
1528c2ecf20Sopenharmony_ci	if (info->batt_tech >= 0)
1538c2ecf20Sopenharmony_ci		prop[i++] = POWER_SUPPLY_PROP_TECHNOLOGY;
1548c2ecf20Sopenharmony_ci	if (info->batt_I2C_reg >= 0)
1558c2ecf20Sopenharmony_ci		prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_NOW;
1568c2ecf20Sopenharmony_ci	if (info->max_voltage >= 0)
1578c2ecf20Sopenharmony_ci		prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MAX;
1588c2ecf20Sopenharmony_ci	if (info->min_voltage >= 0)
1598c2ecf20Sopenharmony_ci		prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MIN;
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci	if (!info->batt_name) {
1628c2ecf20Sopenharmony_ci		dev_info(&charger->client->dev,
1638c2ecf20Sopenharmony_ci				"Please consider setting proper battery "
1648c2ecf20Sopenharmony_ci				"name in platform definition file, falling "
1658c2ecf20Sopenharmony_ci				"back to name \" Z2_DEFAULT_NAME \"\n");
1668c2ecf20Sopenharmony_ci		charger->batt_ps_desc.name = Z2_DEFAULT_NAME;
1678c2ecf20Sopenharmony_ci	} else
1688c2ecf20Sopenharmony_ci		charger->batt_ps_desc.name = info->batt_name;
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci	charger->batt_ps_desc.properties	= prop;
1718c2ecf20Sopenharmony_ci	charger->batt_ps_desc.num_properties	= props;
1728c2ecf20Sopenharmony_ci	charger->batt_ps_desc.type		= POWER_SUPPLY_TYPE_BATTERY;
1738c2ecf20Sopenharmony_ci	charger->batt_ps_desc.get_property	= z2_batt_get_property;
1748c2ecf20Sopenharmony_ci	charger->batt_ps_desc.external_power_changed =
1758c2ecf20Sopenharmony_ci						z2_batt_ext_power_changed;
1768c2ecf20Sopenharmony_ci	charger->batt_ps_desc.use_for_apm	= 1;
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ci	return 0;
1798c2ecf20Sopenharmony_ci}
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_cistatic int z2_batt_probe(struct i2c_client *client,
1828c2ecf20Sopenharmony_ci				const struct i2c_device_id *id)
1838c2ecf20Sopenharmony_ci{
1848c2ecf20Sopenharmony_ci	int ret = 0;
1858c2ecf20Sopenharmony_ci	int props = 1;	/* POWER_SUPPLY_PROP_PRESENT */
1868c2ecf20Sopenharmony_ci	struct z2_charger *charger;
1878c2ecf20Sopenharmony_ci	struct z2_battery_info *info = client->dev.platform_data;
1888c2ecf20Sopenharmony_ci	struct power_supply_config psy_cfg = {};
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_ci	if (info == NULL) {
1918c2ecf20Sopenharmony_ci		dev_err(&client->dev,
1928c2ecf20Sopenharmony_ci			"Please set platform device platform_data"
1938c2ecf20Sopenharmony_ci			" to a valid z2_battery_info pointer!\n");
1948c2ecf20Sopenharmony_ci		return -EINVAL;
1958c2ecf20Sopenharmony_ci	}
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_ci	charger = kzalloc(sizeof(*charger), GFP_KERNEL);
1988c2ecf20Sopenharmony_ci	if (charger == NULL)
1998c2ecf20Sopenharmony_ci		return -ENOMEM;
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci	charger->bat_status = POWER_SUPPLY_STATUS_UNKNOWN;
2028c2ecf20Sopenharmony_ci	charger->info = info;
2038c2ecf20Sopenharmony_ci	charger->client = client;
2048c2ecf20Sopenharmony_ci	i2c_set_clientdata(client, charger);
2058c2ecf20Sopenharmony_ci	psy_cfg.drv_data = charger;
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_ci	mutex_init(&charger->work_lock);
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_ci	if (info->charge_gpio >= 0 && gpio_is_valid(info->charge_gpio)) {
2108c2ecf20Sopenharmony_ci		ret = gpio_request(info->charge_gpio, "BATT CHRG");
2118c2ecf20Sopenharmony_ci		if (ret)
2128c2ecf20Sopenharmony_ci			goto err;
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_ci		ret = gpio_direction_input(info->charge_gpio);
2158c2ecf20Sopenharmony_ci		if (ret)
2168c2ecf20Sopenharmony_ci			goto err2;
2178c2ecf20Sopenharmony_ci
2188c2ecf20Sopenharmony_ci		irq_set_irq_type(gpio_to_irq(info->charge_gpio),
2198c2ecf20Sopenharmony_ci				 IRQ_TYPE_EDGE_BOTH);
2208c2ecf20Sopenharmony_ci		ret = request_irq(gpio_to_irq(info->charge_gpio),
2218c2ecf20Sopenharmony_ci				z2_charge_switch_irq, 0,
2228c2ecf20Sopenharmony_ci				"AC Detect", charger);
2238c2ecf20Sopenharmony_ci		if (ret)
2248c2ecf20Sopenharmony_ci			goto err3;
2258c2ecf20Sopenharmony_ci	}
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_ci	ret = z2_batt_ps_init(charger, props);
2288c2ecf20Sopenharmony_ci	if (ret)
2298c2ecf20Sopenharmony_ci		goto err3;
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci	INIT_WORK(&charger->bat_work, z2_batt_work);
2328c2ecf20Sopenharmony_ci
2338c2ecf20Sopenharmony_ci	charger->batt_ps = power_supply_register(&client->dev,
2348c2ecf20Sopenharmony_ci						 &charger->batt_ps_desc,
2358c2ecf20Sopenharmony_ci						 &psy_cfg);
2368c2ecf20Sopenharmony_ci	if (IS_ERR(charger->batt_ps)) {
2378c2ecf20Sopenharmony_ci		ret = PTR_ERR(charger->batt_ps);
2388c2ecf20Sopenharmony_ci		goto err4;
2398c2ecf20Sopenharmony_ci	}
2408c2ecf20Sopenharmony_ci
2418c2ecf20Sopenharmony_ci	schedule_work(&charger->bat_work);
2428c2ecf20Sopenharmony_ci
2438c2ecf20Sopenharmony_ci	return 0;
2448c2ecf20Sopenharmony_ci
2458c2ecf20Sopenharmony_cierr4:
2468c2ecf20Sopenharmony_ci	kfree(charger->batt_ps_desc.properties);
2478c2ecf20Sopenharmony_cierr3:
2488c2ecf20Sopenharmony_ci	if (info->charge_gpio >= 0 && gpio_is_valid(info->charge_gpio))
2498c2ecf20Sopenharmony_ci		free_irq(gpio_to_irq(info->charge_gpio), charger);
2508c2ecf20Sopenharmony_cierr2:
2518c2ecf20Sopenharmony_ci	if (info->charge_gpio >= 0 && gpio_is_valid(info->charge_gpio))
2528c2ecf20Sopenharmony_ci		gpio_free(info->charge_gpio);
2538c2ecf20Sopenharmony_cierr:
2548c2ecf20Sopenharmony_ci	kfree(charger);
2558c2ecf20Sopenharmony_ci	return ret;
2568c2ecf20Sopenharmony_ci}
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_cistatic int z2_batt_remove(struct i2c_client *client)
2598c2ecf20Sopenharmony_ci{
2608c2ecf20Sopenharmony_ci	struct z2_charger *charger = i2c_get_clientdata(client);
2618c2ecf20Sopenharmony_ci	struct z2_battery_info *info = charger->info;
2628c2ecf20Sopenharmony_ci
2638c2ecf20Sopenharmony_ci	cancel_work_sync(&charger->bat_work);
2648c2ecf20Sopenharmony_ci	power_supply_unregister(charger->batt_ps);
2658c2ecf20Sopenharmony_ci
2668c2ecf20Sopenharmony_ci	kfree(charger->batt_ps_desc.properties);
2678c2ecf20Sopenharmony_ci	if (info->charge_gpio >= 0 && gpio_is_valid(info->charge_gpio)) {
2688c2ecf20Sopenharmony_ci		free_irq(gpio_to_irq(info->charge_gpio), charger);
2698c2ecf20Sopenharmony_ci		gpio_free(info->charge_gpio);
2708c2ecf20Sopenharmony_ci	}
2718c2ecf20Sopenharmony_ci
2728c2ecf20Sopenharmony_ci	kfree(charger);
2738c2ecf20Sopenharmony_ci
2748c2ecf20Sopenharmony_ci	return 0;
2758c2ecf20Sopenharmony_ci}
2768c2ecf20Sopenharmony_ci
2778c2ecf20Sopenharmony_ci#ifdef CONFIG_PM
2788c2ecf20Sopenharmony_cistatic int z2_batt_suspend(struct device *dev)
2798c2ecf20Sopenharmony_ci{
2808c2ecf20Sopenharmony_ci	struct i2c_client *client = to_i2c_client(dev);
2818c2ecf20Sopenharmony_ci	struct z2_charger *charger = i2c_get_clientdata(client);
2828c2ecf20Sopenharmony_ci
2838c2ecf20Sopenharmony_ci	flush_work(&charger->bat_work);
2848c2ecf20Sopenharmony_ci	return 0;
2858c2ecf20Sopenharmony_ci}
2868c2ecf20Sopenharmony_ci
2878c2ecf20Sopenharmony_cistatic int z2_batt_resume(struct device *dev)
2888c2ecf20Sopenharmony_ci{
2898c2ecf20Sopenharmony_ci	struct i2c_client *client = to_i2c_client(dev);
2908c2ecf20Sopenharmony_ci	struct z2_charger *charger = i2c_get_clientdata(client);
2918c2ecf20Sopenharmony_ci
2928c2ecf20Sopenharmony_ci	schedule_work(&charger->bat_work);
2938c2ecf20Sopenharmony_ci	return 0;
2948c2ecf20Sopenharmony_ci}
2958c2ecf20Sopenharmony_ci
2968c2ecf20Sopenharmony_cistatic const struct dev_pm_ops z2_battery_pm_ops = {
2978c2ecf20Sopenharmony_ci	.suspend	= z2_batt_suspend,
2988c2ecf20Sopenharmony_ci	.resume		= z2_batt_resume,
2998c2ecf20Sopenharmony_ci};
3008c2ecf20Sopenharmony_ci
3018c2ecf20Sopenharmony_ci#define	Z2_BATTERY_PM_OPS	(&z2_battery_pm_ops)
3028c2ecf20Sopenharmony_ci
3038c2ecf20Sopenharmony_ci#else
3048c2ecf20Sopenharmony_ci#define	Z2_BATTERY_PM_OPS	(NULL)
3058c2ecf20Sopenharmony_ci#endif
3068c2ecf20Sopenharmony_ci
3078c2ecf20Sopenharmony_cistatic const struct i2c_device_id z2_batt_id[] = {
3088c2ecf20Sopenharmony_ci	{ "aer915", 0 },
3098c2ecf20Sopenharmony_ci	{ }
3108c2ecf20Sopenharmony_ci};
3118c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, z2_batt_id);
3128c2ecf20Sopenharmony_ci
3138c2ecf20Sopenharmony_cistatic struct i2c_driver z2_batt_driver = {
3148c2ecf20Sopenharmony_ci	.driver	= {
3158c2ecf20Sopenharmony_ci		.name	= "z2-battery",
3168c2ecf20Sopenharmony_ci		.pm	= Z2_BATTERY_PM_OPS
3178c2ecf20Sopenharmony_ci	},
3188c2ecf20Sopenharmony_ci	.probe		= z2_batt_probe,
3198c2ecf20Sopenharmony_ci	.remove		= z2_batt_remove,
3208c2ecf20Sopenharmony_ci	.id_table	= z2_batt_id,
3218c2ecf20Sopenharmony_ci};
3228c2ecf20Sopenharmony_cimodule_i2c_driver(z2_batt_driver);
3238c2ecf20Sopenharmony_ci
3248c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
3258c2ecf20Sopenharmony_ciMODULE_AUTHOR("Peter Edwards <sweetlilmre@gmail.com>");
3268c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Zipit Z2 battery driver");
327