File Index
Files and directories
Sign in
FILE

chagall-battery.c

File preview with syntax highlighting.

linux-6.19.14/drivers/power/supply/chagall-battery.c C
  1// SPDX-License-Identifier: GPL-2.0-or-later
  2
  3#include <linux/array_size.h>
  4#include <linux/delay.h>
  5#include <linux/devm-helpers.h>
  6#include <linux/err.h>
  7#include <linux/i2c.h>
  8#include <linux/leds.h>
  9#include <linux/mod_devicetable.h>
 10#include <linux/module.h>
 11#include <linux/power_supply.h>
 12#include <linux/regmap.h>
 13
 14#define CHAGALL_REG_LED_AMBER				0x60
 15#define CHAGALL_REG_LED_WHITE				0x70
 16#define CHAGALL_REG_BATTERY_TEMPERATURE			0xa2
 17#define CHAGALL_REG_BATTERY_VOLTAGE			0xa4
 18#define CHAGALL_REG_BATTERY_CURRENT			0xa6
 19#define CHAGALL_REG_BATTERY_CAPACITY			0xa8
 20#define CHAGALL_REG_BATTERY_CHARGING_CURRENT		0xaa
 21#define CHAGALL_REG_BATTERY_CHARGING_VOLTAGE		0xac
 22#define CHAGALL_REG_BATTERY_STATUS			0xae
 23#define   BATTERY_DISCHARGING				BIT(6)
 24#define   BATTERY_FULL_CHARGED				BIT(5)
 25#define   BATTERY_FULL_DISCHARGED			BIT(4)
 26#define CHAGALL_REG_BATTERY_REMAIN_CAPACITY		0xb0
 27#define CHAGALL_REG_BATTERY_FULL_CAPACITY		0xb2
 28#define CHAGALL_REG_MAX_COUNT				0xb4
 29
 30#define CHAGALL_BATTERY_DATA_REFRESH			5000
 31#define TEMP_CELSIUS_OFFSET				2731
 32
 33static const struct regmap_config chagall_battery_regmap_config = {
 34	.reg_bits = 8,
 35	.val_bits = 8,
 36	.max_register = CHAGALL_REG_MAX_COUNT,
 37	.reg_format_endian = REGMAP_ENDIAN_LITTLE,
 38	.val_format_endian = REGMAP_ENDIAN_LITTLE,
 39};
 40
 41struct chagall_battery_data {
 42	struct regmap *regmap;
 43	struct led_classdev amber_led;
 44	struct led_classdev white_led;
 45	struct power_supply *battery;
 46	struct delayed_work poll_work;
 47	u16 last_state;
 48};
 49
 50static void chagall_led_set_brightness_amber(struct led_classdev *led,
 51					     enum led_brightness brightness)
 52{
 53	struct chagall_battery_data *cg =
 54		container_of(led, struct chagall_battery_data, amber_led);
 55
 56	regmap_write(cg->regmap, CHAGALL_REG_LED_AMBER, brightness);
 57}
 58
 59static void chagall_led_set_brightness_white(struct led_classdev *led,
 60					     enum led_brightness brightness)
 61{
 62	struct chagall_battery_data *cg =
 63		container_of(led, struct chagall_battery_data, white_led);
 64
 65	regmap_write(cg->regmap, CHAGALL_REG_LED_WHITE, brightness);
 66}
 67
 68static const enum power_supply_property chagall_battery_properties[] = {
 69	POWER_SUPPLY_PROP_STATUS,
 70	POWER_SUPPLY_PROP_PRESENT,
 71	POWER_SUPPLY_PROP_VOLTAGE_NOW,
 72	POWER_SUPPLY_PROP_VOLTAGE_MAX,
 73	POWER_SUPPLY_PROP_CURRENT_NOW,
 74	POWER_SUPPLY_PROP_CURRENT_MAX,
 75	POWER_SUPPLY_PROP_CAPACITY,
 76	POWER_SUPPLY_PROP_TEMP,
 77	POWER_SUPPLY_PROP_CHARGE_FULL,
 78	POWER_SUPPLY_PROP_CHARGE_NOW,
 79};
 80
 81static const unsigned int chagall_battery_prop_offs[] = {
 82	[POWER_SUPPLY_PROP_STATUS] = CHAGALL_REG_BATTERY_STATUS,
 83	[POWER_SUPPLY_PROP_VOLTAGE_NOW] = CHAGALL_REG_BATTERY_VOLTAGE,
 84	[POWER_SUPPLY_PROP_VOLTAGE_MAX] = CHAGALL_REG_BATTERY_CHARGING_VOLTAGE,
 85	[POWER_SUPPLY_PROP_CURRENT_NOW] = CHAGALL_REG_BATTERY_CURRENT,
 86	[POWER_SUPPLY_PROP_CURRENT_MAX] = CHAGALL_REG_BATTERY_CHARGING_CURRENT,
 87	[POWER_SUPPLY_PROP_CAPACITY] = CHAGALL_REG_BATTERY_CAPACITY,
 88	[POWER_SUPPLY_PROP_TEMP] = CHAGALL_REG_BATTERY_TEMPERATURE,
 89	[POWER_SUPPLY_PROP_CHARGE_FULL] = CHAGALL_REG_BATTERY_FULL_CAPACITY,
 90	[POWER_SUPPLY_PROP_CHARGE_NOW] = CHAGALL_REG_BATTERY_REMAIN_CAPACITY,
 91};
 92
 93static int chagall_battery_get_value(struct chagall_battery_data *cg,
 94				     enum power_supply_property psp, u32 *val)
 95{
 96	if (psp >= ARRAY_SIZE(chagall_battery_prop_offs))
 97		return -EINVAL;
 98	if (!chagall_battery_prop_offs[psp])
 99		return -EINVAL;
100
101	/* Battery data is stored in 2 consecutive registers with little-endian */
102	return regmap_bulk_read(cg->regmap, chagall_battery_prop_offs[psp], val, 2);
103}
104
105static int chagall_battery_get_status(u32 status_reg)
106{
107	if (status_reg & BATTERY_FULL_CHARGED)
108		return POWER_SUPPLY_STATUS_FULL;
109	else if (status_reg & BATTERY_DISCHARGING)
110		return POWER_SUPPLY_STATUS_DISCHARGING;
111	else
112		return POWER_SUPPLY_STATUS_CHARGING;
113}
114
115static int chagall_battery_get_property(struct power_supply *psy,
116					enum power_supply_property psp,
117					union power_supply_propval *val)
118{
119	struct chagall_battery_data *cg = power_supply_get_drvdata(psy);
120	int ret;
121
122	switch (psp) {
123	case POWER_SUPPLY_PROP_PRESENT:
124		val->intval = 1;
125		break;
126
127	default:
128		ret = chagall_battery_get_value(cg, psp, &val->intval);
129		if (ret)
130			return ret;
131
132		switch (psp) {
133		case POWER_SUPPLY_PROP_TEMP:
134			val->intval -= TEMP_CELSIUS_OFFSET;
135			break;
136
137		case POWER_SUPPLY_PROP_VOLTAGE_MAX:
138		case POWER_SUPPLY_PROP_VOLTAGE_NOW:
139		case POWER_SUPPLY_PROP_CURRENT_MAX:
140		case POWER_SUPPLY_PROP_CURRENT_NOW:
141		case POWER_SUPPLY_PROP_CHARGE_FULL:
142		case POWER_SUPPLY_PROP_CHARGE_NOW:
143			val->intval *= 1000;
144			break;
145
146		case POWER_SUPPLY_PROP_STATUS:
147			val->intval = chagall_battery_get_status(val->intval);
148			break;
149
150		default:
151			break;
152		}
153
154		break;
155	}
156
157	return 0;
158}
159
160static void chagall_battery_poll_work(struct work_struct *work)
161{
162	struct chagall_battery_data *cg =
163		container_of(work, struct chagall_battery_data, poll_work.work);
164	u32 state;
165	int ret;
166
167	ret = chagall_battery_get_value(cg, POWER_SUPPLY_PROP_STATUS, &state);
168	if (ret)
169		return;
170
171	state = chagall_battery_get_status(state);
172
173	if (cg->last_state != state) {
174		cg->last_state = state;
175		power_supply_changed(cg->battery);
176	}
177
178	/* continuously send uevent notification */
179	schedule_delayed_work(&cg->poll_work,
180			      msecs_to_jiffies(CHAGALL_BATTERY_DATA_REFRESH));
181}
182
183static const struct power_supply_desc chagall_battery_desc = {
184	.name = "chagall-battery",
185	.type = POWER_SUPPLY_TYPE_BATTERY,
186	.properties = chagall_battery_properties,
187	.num_properties = ARRAY_SIZE(chagall_battery_properties),
188	.get_property = chagall_battery_get_property,
189	.external_power_changed = power_supply_changed,
190};
191
192static int chagall_battery_probe(struct i2c_client *client)
193{
194	struct chagall_battery_data *cg;
195	struct device *dev = &client->dev;
196	struct power_supply_config cfg = { };
197	int ret;
198
199	cg = devm_kzalloc(dev, sizeof(*cg), GFP_KERNEL);
200	if (!cg)
201		return -ENOMEM;
202
203	cfg.drv_data = cg;
204	cfg.fwnode = dev_fwnode(dev);
205
206	i2c_set_clientdata(client, cg);
207
208	cg->regmap = devm_regmap_init_i2c(client, &chagall_battery_regmap_config);
209	if (IS_ERR(cg->regmap))
210		return dev_err_probe(dev, PTR_ERR(cg->regmap), "cannot allocate regmap\n");
211
212	cg->last_state = POWER_SUPPLY_STATUS_UNKNOWN;
213	cg->battery = devm_power_supply_register(dev, &chagall_battery_desc, &cfg);
214	if (IS_ERR(cg->battery))
215		return dev_err_probe(dev, PTR_ERR(cg->battery),
216				     "failed to register power supply\n");
217
218	cg->amber_led.name = "power::amber";
219	cg->amber_led.max_brightness = 1;
220	cg->amber_led.flags = LED_CORE_SUSPENDRESUME;
221	cg->amber_led.brightness_set = chagall_led_set_brightness_amber;
222	cg->amber_led.default_trigger = "chagall-battery-charging";
223
224	ret = devm_led_classdev_register(dev, &cg->amber_led);
225	if (ret)
226		return dev_err_probe(dev, ret, "failed to register amber LED\n");
227
228	cg->white_led.name = "power::white";
229	cg->white_led.max_brightness = 1;
230	cg->white_led.flags = LED_CORE_SUSPENDRESUME;
231	cg->white_led.brightness_set = chagall_led_set_brightness_white;
232	cg->white_led.default_trigger = "chagall-battery-full";
233
234	ret = devm_led_classdev_register(dev, &cg->white_led);
235	if (ret)
236		return dev_err_probe(dev, ret, "failed to register white LED\n");
237
238	led_set_brightness(&cg->amber_led, LED_OFF);
239	led_set_brightness(&cg->white_led, LED_OFF);
240
241	ret = devm_delayed_work_autocancel(dev, &cg->poll_work, chagall_battery_poll_work);
242	if (ret)
243		return ret;
244
245	schedule_delayed_work(&cg->poll_work, msecs_to_jiffies(CHAGALL_BATTERY_DATA_REFRESH));
246
247	return 0;
248}
249
250static int __maybe_unused chagall_battery_suspend(struct device *dev)
251{
252	struct i2c_client *client = to_i2c_client(dev);
253	struct chagall_battery_data *cg = i2c_get_clientdata(client);
254
255	cancel_delayed_work_sync(&cg->poll_work);
256
257	return 0;
258}
259
260static int __maybe_unused chagall_battery_resume(struct device *dev)
261{
262	struct i2c_client *client = to_i2c_client(dev);
263	struct chagall_battery_data *cg = i2c_get_clientdata(client);
264
265	schedule_delayed_work(&cg->poll_work, msecs_to_jiffies(CHAGALL_BATTERY_DATA_REFRESH));
266
267	return 0;
268}
269
270static SIMPLE_DEV_PM_OPS(chagall_battery_pm_ops,
271			 chagall_battery_suspend, chagall_battery_resume);
272
273static const struct of_device_id chagall_of_match[] = {
274	{ .compatible = "pegatron,chagall-ec" },
275	{ }
276};
277MODULE_DEVICE_TABLE(of, chagall_of_match);
278
279static struct i2c_driver chagall_battery_driver = {
280	.driver = {
281		.name = "chagall-battery",
282		.pm = &chagall_battery_pm_ops,
283		.of_match_table = chagall_of_match,
284	},
285	.probe = chagall_battery_probe,
286};
287module_i2c_driver(chagall_battery_driver);
288
289MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>");
290MODULE_DESCRIPTION("Pegatron Chagall fuel gauge driver");
291MODULE_LICENSE("GPL");
~/ Read-only file preview.