brintos

brintos / linux-shallow public Read only

0
0
Text · 7.0 KiB · ef5c419 Raw
296 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * Battery driver for Acer Iconia Tab A500.4 *5 * Copyright 2020 GRATE-driver project.6 *7 * Based on downstream driver from Acer Inc.8 * Based on NVIDIA Gas Gauge driver for SBS Compliant Batteries.9 *10 * Copyright (c) 2010, NVIDIA Corporation.11 */12 13#include <linux/module.h>14#include <linux/platform_device.h>15#include <linux/power_supply.h>16#include <linux/regmap.h>17#include <linux/sched.h>18#include <linux/slab.h>19#include <linux/workqueue.h>20 21enum {22	REG_CAPACITY,23	REG_VOLTAGE,24	REG_CURRENT,25	REG_DESIGN_CAPACITY,26	REG_TEMPERATURE,27};28 29#define EC_DATA(_reg, _psp) {			\30	.psp = POWER_SUPPLY_PROP_ ## _psp,	\31	.reg = _reg,				\32}33 34static const struct battery_register {35	enum power_supply_property psp;36	unsigned int reg;37} ec_data[] = {38	[REG_CAPACITY]		= EC_DATA(0x00, CAPACITY),39	[REG_VOLTAGE]		= EC_DATA(0x01, VOLTAGE_NOW),40	[REG_CURRENT]		= EC_DATA(0x03, CURRENT_NOW),41	[REG_DESIGN_CAPACITY]	= EC_DATA(0x08, CHARGE_FULL_DESIGN),42	[REG_TEMPERATURE]	= EC_DATA(0x0a, TEMP),43};44 45static const enum power_supply_property a500_battery_properties[] = {46	POWER_SUPPLY_PROP_CAPACITY,47	POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,48	POWER_SUPPLY_PROP_CURRENT_NOW,49	POWER_SUPPLY_PROP_PRESENT,50	POWER_SUPPLY_PROP_STATUS,51	POWER_SUPPLY_PROP_TECHNOLOGY,52	POWER_SUPPLY_PROP_TEMP,53	POWER_SUPPLY_PROP_VOLTAGE_NOW,54};55 56struct a500_battery {57	struct delayed_work poll_work;58	struct power_supply *psy;59	struct regmap *regmap;60	unsigned int capacity;61};62 63static bool a500_battery_update_capacity(struct a500_battery *bat)64{65	unsigned int capacity;66	int err;67 68	err = regmap_read(bat->regmap, ec_data[REG_CAPACITY].reg, &capacity);69	if (err)70		return false;71 72	/* capacity can be >100% even if max value is 100% */73	capacity = min(capacity, 100u);74 75	if (bat->capacity != capacity) {76		bat->capacity = capacity;77		return true;78	}79 80	return false;81}82 83static int a500_battery_get_status(struct a500_battery *bat)84{85	if (bat->capacity < 100) {86		if (power_supply_am_i_supplied(bat->psy))87			return POWER_SUPPLY_STATUS_CHARGING;88		else89			return POWER_SUPPLY_STATUS_DISCHARGING;90	}91 92	return POWER_SUPPLY_STATUS_FULL;93}94 95static void a500_battery_unit_adjustment(struct device *dev,96					 enum power_supply_property psp,97					 union power_supply_propval *val)98{99	const unsigned int base_unit_conversion = 1000;100	const unsigned int temp_kelvin_to_celsius = 2731;101 102	switch (psp) {103	case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:104	case POWER_SUPPLY_PROP_CURRENT_NOW:105	case POWER_SUPPLY_PROP_VOLTAGE_NOW:106		val->intval *= base_unit_conversion;107		break;108 109	case POWER_SUPPLY_PROP_TEMP:110		val->intval -= temp_kelvin_to_celsius;111		break;112 113	case POWER_SUPPLY_PROP_PRESENT:114		val->intval = !!val->intval;115		break;116 117	default:118		dev_dbg(dev,119			"%s: no need for unit conversion %d\n", __func__, psp);120	}121}122 123static int a500_battery_get_ec_data_index(struct device *dev,124					  enum power_supply_property psp)125{126	unsigned int i;127 128	/*129	 * DESIGN_CAPACITY register always returns a non-zero value if130	 * battery is connected and zero if disconnected, hence we'll use131	 * it for judging the battery presence.132	 */133	if (psp == POWER_SUPPLY_PROP_PRESENT)134		psp = POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN;135 136	for (i = 0; i < ARRAY_SIZE(ec_data); i++)137		if (psp == ec_data[i].psp)138			return i;139 140	dev_dbg(dev, "%s: invalid property %u\n", __func__, psp);141 142	return -EINVAL;143}144 145static int a500_battery_get_property(struct power_supply *psy,146				     enum power_supply_property psp,147				     union power_supply_propval *val)148{149	struct a500_battery *bat = power_supply_get_drvdata(psy);150	struct device *dev = psy->dev.parent;151	int ret = 0;152 153	switch (psp) {154	case POWER_SUPPLY_PROP_STATUS:155		val->intval = a500_battery_get_status(bat);156		break;157 158	case POWER_SUPPLY_PROP_TECHNOLOGY:159		val->intval = POWER_SUPPLY_TECHNOLOGY_LION;160		break;161 162	case POWER_SUPPLY_PROP_CAPACITY:163		a500_battery_update_capacity(bat);164		val->intval = bat->capacity;165		break;166 167	case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:168	case POWER_SUPPLY_PROP_CURRENT_NOW:169	case POWER_SUPPLY_PROP_VOLTAGE_NOW:170	case POWER_SUPPLY_PROP_PRESENT:171	case POWER_SUPPLY_PROP_TEMP:172		ret = a500_battery_get_ec_data_index(dev, psp);173		if (ret < 0)174			break;175 176		ret = regmap_read(bat->regmap, ec_data[ret].reg, &val->intval);177		break;178 179	default:180		dev_err(dev, "%s: invalid property %u\n", __func__, psp);181		return -EINVAL;182	}183 184	if (!ret) {185		/* convert units to match requirements of power supply class */186		a500_battery_unit_adjustment(dev, psp, val);187	}188 189	dev_dbg(dev, "%s: property = %d, value = %x\n",190		__func__, psp, val->intval);191 192	/* return NODATA for properties if battery not presents */193	if (ret)194		return -ENODATA;195 196	return 0;197}198 199static void a500_battery_poll_work(struct work_struct *work)200{201	struct a500_battery *bat;202	bool capacity_changed;203 204	bat = container_of(work, struct a500_battery, poll_work.work);205	capacity_changed = a500_battery_update_capacity(bat);206 207	if (capacity_changed)208		power_supply_changed(bat->psy);209 210	/* continuously send uevent notification */211	schedule_delayed_work(&bat->poll_work, 30 * HZ);212}213 214static const struct power_supply_desc a500_battery_desc = {215	.name = "ec-battery",216	.type = POWER_SUPPLY_TYPE_BATTERY,217	.properties = a500_battery_properties,218	.get_property = a500_battery_get_property,219	.num_properties = ARRAY_SIZE(a500_battery_properties),220	.external_power_changed = power_supply_changed,221};222 223static int a500_battery_probe(struct platform_device *pdev)224{225	struct power_supply_config psy_cfg = {};226	struct a500_battery *bat;227 228	bat = devm_kzalloc(&pdev->dev, sizeof(*bat), GFP_KERNEL);229	if (!bat)230		return -ENOMEM;231 232	platform_set_drvdata(pdev, bat);233 234	psy_cfg.of_node = pdev->dev.parent->of_node;235	psy_cfg.drv_data = bat;236 237	bat->regmap = dev_get_regmap(pdev->dev.parent, "KB930");238	if (!bat->regmap)239		return -EINVAL;240 241	bat->psy = devm_power_supply_register_no_ws(&pdev->dev,242						    &a500_battery_desc,243						    &psy_cfg);244	if (IS_ERR(bat->psy))245		return dev_err_probe(&pdev->dev, PTR_ERR(bat->psy),246				     "failed to register battery\n");247 248	INIT_DELAYED_WORK(&bat->poll_work, a500_battery_poll_work);249	schedule_delayed_work(&bat->poll_work, HZ);250 251	return 0;252}253 254static void a500_battery_remove(struct platform_device *pdev)255{256	struct a500_battery *bat = dev_get_drvdata(&pdev->dev);257 258	cancel_delayed_work_sync(&bat->poll_work);259}260 261static int __maybe_unused a500_battery_suspend(struct device *dev)262{263	struct a500_battery *bat = dev_get_drvdata(dev);264 265	cancel_delayed_work_sync(&bat->poll_work);266 267	return 0;268}269 270static int __maybe_unused a500_battery_resume(struct device *dev)271{272	struct a500_battery *bat = dev_get_drvdata(dev);273 274	schedule_delayed_work(&bat->poll_work, HZ);275 276	return 0;277}278 279static SIMPLE_DEV_PM_OPS(a500_battery_pm_ops,280			 a500_battery_suspend, a500_battery_resume);281 282static struct platform_driver a500_battery_driver = {283	.driver = {284		.name = "acer-a500-iconia-battery",285		.pm = &a500_battery_pm_ops,286	},287	.probe = a500_battery_probe,288	.remove_new = a500_battery_remove,289};290module_platform_driver(a500_battery_driver);291 292MODULE_DESCRIPTION("Battery gauge driver for Acer Iconia Tab A500");293MODULE_AUTHOR("Dmitry Osipenko <digetx@gmail.com>");294MODULE_ALIAS("platform:acer-a500-iconia-battery");295MODULE_LICENSE("GPL");296