brintos

brintos / linux-shallow public Read only

0
0
Text · 16.3 KiB · a1ddc4b Raw
648 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * I2C client/driver for the Linear Technology LTC2941, LTC2942, LTC29434 * and LTC2944 Battery Gas Gauge IC5 *6 * Copyright (C) 2014 Topic Embedded Systems7 *8 * Author: Auryn Verwegen9 * Author: Mike Looijmans10 */11#include <linux/devm-helpers.h>12#include <linux/kernel.h>13#include <linux/module.h>14#include <linux/of.h>15#include <linux/types.h>16#include <linux/errno.h>17#include <linux/swab.h>18#include <linux/i2c.h>19#include <linux/delay.h>20#include <linux/power_supply.h>21#include <linux/slab.h>22 23#define I16_MSB(x)			((x >> 8) & 0xFF)24#define I16_LSB(x)			(x & 0xFF)25 26#define LTC294X_WORK_DELAY		10	/* Update delay in seconds */27 28#define LTC294X_MAX_VALUE		0xFFFF29#define LTC294X_MID_SUPPLY		0x7FFF30 31#define LTC2941_MAX_PRESCALER_EXP	732#define LTC2943_MAX_PRESCALER_EXP	633 34enum ltc294x_reg {35	LTC294X_REG_STATUS		= 0x00,36	LTC294X_REG_CONTROL		= 0x01,37	LTC294X_REG_ACC_CHARGE_MSB	= 0x02,38	LTC294X_REG_ACC_CHARGE_LSB	= 0x03,39	LTC294X_REG_CHARGE_THR_HIGH_MSB	= 0x04,40	LTC294X_REG_CHARGE_THR_HIGH_LSB	= 0x05,41	LTC294X_REG_CHARGE_THR_LOW_MSB	= 0x06,42	LTC294X_REG_CHARGE_THR_LOW_LSB	= 0x07,43	LTC294X_REG_VOLTAGE_MSB		= 0x08,44	LTC294X_REG_VOLTAGE_LSB		= 0x09,45	LTC2942_REG_TEMPERATURE_MSB	= 0x0C,46	LTC2942_REG_TEMPERATURE_LSB	= 0x0D,47	LTC2943_REG_CURRENT_MSB		= 0x0E,48	LTC2943_REG_CURRENT_LSB		= 0x0F,49	LTC2943_REG_TEMPERATURE_MSB	= 0x14,50	LTC2943_REG_TEMPERATURE_LSB	= 0x15,51};52 53enum ltc294x_id {54	LTC2941_ID,55	LTC2942_ID,56	LTC2943_ID,57	LTC2944_ID,58};59 60#define LTC2941_REG_STATUS_CHIP_ID	BIT(7)61 62#define LTC2942_REG_CONTROL_MODE_SCAN	(BIT(7) | BIT(6))63#define LTC2943_REG_CONTROL_MODE_SCAN	BIT(7)64#define LTC294X_REG_CONTROL_PRESCALER_MASK	(BIT(5) | BIT(4) | BIT(3))65#define LTC294X_REG_CONTROL_SHUTDOWN_MASK	(BIT(0))66#define LTC294X_REG_CONTROL_PRESCALER_SET(x) \67	((x << 3) & LTC294X_REG_CONTROL_PRESCALER_MASK)68#define LTC294X_REG_CONTROL_ALCC_CONFIG_DISABLED	069#define LTC294X_REG_CONTROL_ADC_DISABLE(x)	((x) & ~(BIT(7) | BIT(6)))70 71struct ltc294x_info {72	struct i2c_client *client;	/* I2C Client pointer */73	struct power_supply *supply;	/* Supply pointer */74	struct power_supply_desc supply_desc;	/* Supply description */75	struct delayed_work work;	/* Work scheduler */76	enum ltc294x_id id;		/* Chip type */77	int charge;	/* Last charge register content */78	int r_sense;	/* mOhm */79	int Qlsb;	/* nAh */80};81 82static inline int convert_bin_to_uAh(83	const struct ltc294x_info *info, int Q)84{85	return ((Q * (info->Qlsb / 10))) / 100;86}87 88static inline int convert_uAh_to_bin(89	const struct ltc294x_info *info, int uAh)90{91	int Q;92 93	Q = (uAh * 100) / (info->Qlsb/10);94	return (Q < LTC294X_MAX_VALUE) ? Q : LTC294X_MAX_VALUE;95}96 97static int ltc294x_read_regs(struct i2c_client *client,98	enum ltc294x_reg reg, u8 *buf, int num_regs)99{100	int ret;101	struct i2c_msg msgs[2] = { };102	u8 reg_start = reg;103 104	msgs[0].addr	= client->addr;105	msgs[0].len	= 1;106	msgs[0].buf	= &reg_start;107 108	msgs[1].addr	= client->addr;109	msgs[1].len	= num_regs;110	msgs[1].buf	= buf;111	msgs[1].flags	= I2C_M_RD;112 113	ret = i2c_transfer(client->adapter, &msgs[0], 2);114	if (ret < 0) {115		dev_err(&client->dev, "ltc2941 read_reg(0x%x[%d]) failed: %pe\n",116			reg, num_regs, ERR_PTR(ret));117		return ret;118	}119 120	dev_dbg(&client->dev, "%s (%#x, %d) -> %#x\n",121		__func__, reg, num_regs, *buf);122 123	return 0;124}125 126static int ltc294x_write_regs(struct i2c_client *client,127	enum ltc294x_reg reg, const u8 *buf, int num_regs)128{129	int ret;130	u8 reg_start = reg;131 132	ret = i2c_smbus_write_i2c_block_data(client, reg_start, num_regs, buf);133	if (ret < 0) {134		dev_err(&client->dev, "ltc2941 write_reg(0x%x[%d]) failed: %pe\n",135			reg, num_regs, ERR_PTR(ret));136		return ret;137	}138 139	dev_dbg(&client->dev, "%s (%#x, %d) -> %#x\n",140		__func__, reg, num_regs, *buf);141 142	return 0;143}144 145static int ltc294x_reset(const struct ltc294x_info *info, int prescaler_exp)146{147	int ret;148	u8 value;149	u8 control;150 151	/* Read status and control registers */152	ret = ltc294x_read_regs(info->client, LTC294X_REG_CONTROL, &value, 1);153	if (ret < 0)154		return ret;155 156	control = LTC294X_REG_CONTROL_PRESCALER_SET(prescaler_exp) |157				LTC294X_REG_CONTROL_ALCC_CONFIG_DISABLED;158	/* Put device into "monitor" mode */159	switch (info->id) {160	case LTC2942_ID:	/* 2942 measures every 2 sec */161		control |= LTC2942_REG_CONTROL_MODE_SCAN;162		break;163	case LTC2943_ID:164	case LTC2944_ID:	/* 2943 and 2944 measure every 10 sec */165		control |= LTC2943_REG_CONTROL_MODE_SCAN;166		break;167	default:168		break;169	}170 171	if (value != control) {172		ret = ltc294x_write_regs(info->client,173			LTC294X_REG_CONTROL, &control, 1);174		if (ret < 0)175			return ret;176	}177 178	return 0;179}180 181static int ltc294x_read_charge_register(const struct ltc294x_info *info,182					enum ltc294x_reg reg)183 {184	int ret;185	u8 datar[2];186 187	ret = ltc294x_read_regs(info->client, reg, &datar[0], 2);188	if (ret < 0)189		return ret;190	return (datar[0] << 8) + datar[1];191}192 193static int ltc294x_get_charge(const struct ltc294x_info *info,194				enum ltc294x_reg reg, int *val)195{196	int value = ltc294x_read_charge_register(info, reg);197 198	if (value < 0)199		return value;200	/* When r_sense < 0, this counts up when the battery discharges */201	if (info->Qlsb < 0)202		value -= 0xFFFF;203	*val = convert_bin_to_uAh(info, value);204	return 0;205}206 207static int ltc294x_set_charge_now(const struct ltc294x_info *info, int val)208{209	int ret;210	u8 dataw[2];211	u8 ctrl_reg;212	s32 value;213 214	value = convert_uAh_to_bin(info, val);215	/* Direction depends on how sense+/- were connected */216	if (info->Qlsb < 0)217		value += 0xFFFF;218	if ((value < 0) || (value > 0xFFFF)) /* input validation */219		return -EINVAL;220 221	/* Read control register */222	ret = ltc294x_read_regs(info->client,223		LTC294X_REG_CONTROL, &ctrl_reg, 1);224	if (ret < 0)225		return ret;226	/* Disable analog section */227	ctrl_reg |= LTC294X_REG_CONTROL_SHUTDOWN_MASK;228	ret = ltc294x_write_regs(info->client,229		LTC294X_REG_CONTROL, &ctrl_reg, 1);230	if (ret < 0)231		return ret;232	/* Set new charge value */233	dataw[0] = I16_MSB(value);234	dataw[1] = I16_LSB(value);235	ret = ltc294x_write_regs(info->client,236		LTC294X_REG_ACC_CHARGE_MSB, &dataw[0], 2);237	if (ret < 0)238		goto error_exit;239	/* Enable analog section */240error_exit:241	ctrl_reg &= ~LTC294X_REG_CONTROL_SHUTDOWN_MASK;242	ret = ltc294x_write_regs(info->client,243		LTC294X_REG_CONTROL, &ctrl_reg, 1);244 245	return ret < 0 ? ret : 0;246}247 248static int ltc294x_set_charge_thr(const struct ltc294x_info *info,249					enum ltc294x_reg reg, int val)250{251	u8 dataw[2];252	s32 value;253 254	value = convert_uAh_to_bin(info, val);255	/* Direction depends on how sense+/- were connected */256	if (info->Qlsb < 0)257		value += 0xFFFF;258	if ((value < 0) || (value > 0xFFFF)) /* input validation */259		return -EINVAL;260 261	/* Set new charge value */262	dataw[0] = I16_MSB(value);263	dataw[1] = I16_LSB(value);264	return ltc294x_write_regs(info->client, reg, &dataw[0], 2);265}266 267static int ltc294x_get_charge_counter(268	const struct ltc294x_info *info, int *val)269{270	int value = ltc294x_read_charge_register(info, LTC294X_REG_ACC_CHARGE_MSB);271 272	if (value < 0)273		return value;274	value -= LTC294X_MID_SUPPLY;275	*val = convert_bin_to_uAh(info, value);276	return 0;277}278 279static int ltc294x_get_voltage(const struct ltc294x_info *info, int *val)280{281	int ret;282	u8 datar[2];283	u32 value;284 285	ret = ltc294x_read_regs(info->client,286		LTC294X_REG_VOLTAGE_MSB, &datar[0], 2);287	value = (datar[0] << 8) | datar[1];288	switch (info->id) {289	case LTC2943_ID:290		value *= 23600 * 2;291		value /= 0xFFFF;292		value *= 1000 / 2;293		break;294	case LTC2944_ID:295		value *= 70800 / 5*4;296		value /= 0xFFFF;297		value *= 1000 * 5/4;298		break;299	default:300		value *= 6000 * 10;301		value /= 0xFFFF;302		value *= 1000 / 10;303		break;304	}305	*val = value;306	return ret;307}308 309static int ltc294x_get_current(const struct ltc294x_info *info, int *val)310{311	int ret;312	u8 datar[2];313	s32 value;314 315	ret = ltc294x_read_regs(info->client,316		LTC2943_REG_CURRENT_MSB, &datar[0], 2);317	value = (datar[0] << 8) | datar[1];318	value -= 0x7FFF;319	if (info->id == LTC2944_ID)320		value *= 64000;321	else322		value *= 60000;323	/* Value is in range -32k..+32k, r_sense is usually 10..50 mOhm,324	 * the formula below keeps everything in s32 range while preserving325	 * enough digits */326	*val = 1000 * (value / (info->r_sense * 0x7FFF)); /* in uA */327	return ret;328}329 330static int ltc294x_get_temperature(const struct ltc294x_info *info, int *val)331{332	enum ltc294x_reg reg;333	int ret;334	u8 datar[2];335	u32 value;336 337	if (info->id == LTC2942_ID) {338		reg = LTC2942_REG_TEMPERATURE_MSB;339		value = 6000;	/* Full-scale is 600 Kelvin */340	} else {341		reg = LTC2943_REG_TEMPERATURE_MSB;342		value = 5100;	/* Full-scale is 510 Kelvin */343	}344	ret = ltc294x_read_regs(info->client, reg, &datar[0], 2);345	value *= (datar[0] << 8) | datar[1];346	/* Convert to tenths of degree Celsius */347	*val = value / 0xFFFF - 2722;348	return ret;349}350 351static int ltc294x_get_property(struct power_supply *psy,352				enum power_supply_property prop,353				union power_supply_propval *val)354{355	struct ltc294x_info *info = power_supply_get_drvdata(psy);356 357	switch (prop) {358	case POWER_SUPPLY_PROP_CHARGE_FULL:359		return ltc294x_get_charge(info, LTC294X_REG_CHARGE_THR_HIGH_MSB,360						&val->intval);361	case POWER_SUPPLY_PROP_CHARGE_EMPTY:362		return ltc294x_get_charge(info, LTC294X_REG_CHARGE_THR_LOW_MSB,363						&val->intval);364	case POWER_SUPPLY_PROP_CHARGE_NOW:365		return ltc294x_get_charge(info, LTC294X_REG_ACC_CHARGE_MSB,366						&val->intval);367	case POWER_SUPPLY_PROP_CHARGE_COUNTER:368		return ltc294x_get_charge_counter(info, &val->intval);369	case POWER_SUPPLY_PROP_VOLTAGE_NOW:370		return ltc294x_get_voltage(info, &val->intval);371	case POWER_SUPPLY_PROP_CURRENT_NOW:372		return ltc294x_get_current(info, &val->intval);373	case POWER_SUPPLY_PROP_TEMP:374		return ltc294x_get_temperature(info, &val->intval);375	default:376		return -EINVAL;377	}378}379 380static int ltc294x_set_property(struct power_supply *psy,381	enum power_supply_property psp,382	const union power_supply_propval *val)383{384	struct ltc294x_info *info = power_supply_get_drvdata(psy);385 386	switch (psp) {387	case POWER_SUPPLY_PROP_CHARGE_FULL:388		return ltc294x_set_charge_thr(info,389			LTC294X_REG_CHARGE_THR_HIGH_MSB, val->intval);390	case POWER_SUPPLY_PROP_CHARGE_EMPTY:391		return ltc294x_set_charge_thr(info,392			LTC294X_REG_CHARGE_THR_LOW_MSB, val->intval);393	case POWER_SUPPLY_PROP_CHARGE_NOW:394		return ltc294x_set_charge_now(info, val->intval);395	default:396		return -EPERM;397	}398}399 400static int ltc294x_property_is_writeable(401	struct power_supply *psy, enum power_supply_property psp)402{403	switch (psp) {404	case POWER_SUPPLY_PROP_CHARGE_FULL:405	case POWER_SUPPLY_PROP_CHARGE_EMPTY:406	case POWER_SUPPLY_PROP_CHARGE_NOW:407		return 1;408	default:409		return 0;410	}411}412 413static void ltc294x_update(struct ltc294x_info *info)414{415	int charge = ltc294x_read_charge_register(info, LTC294X_REG_ACC_CHARGE_MSB);416 417	if (charge != info->charge) {418		info->charge = charge;419		power_supply_changed(info->supply);420	}421}422 423static void ltc294x_work(struct work_struct *work)424{425	struct ltc294x_info *info;426 427	info = container_of(work, struct ltc294x_info, work.work);428	ltc294x_update(info);429	schedule_delayed_work(&info->work, LTC294X_WORK_DELAY * HZ);430}431 432static enum power_supply_property ltc294x_properties[] = {433	POWER_SUPPLY_PROP_CHARGE_COUNTER,434	POWER_SUPPLY_PROP_CHARGE_FULL,435	POWER_SUPPLY_PROP_CHARGE_EMPTY,436	POWER_SUPPLY_PROP_CHARGE_NOW,437	POWER_SUPPLY_PROP_VOLTAGE_NOW,438	POWER_SUPPLY_PROP_TEMP,439	POWER_SUPPLY_PROP_CURRENT_NOW,440};441 442static int ltc294x_i2c_probe(struct i2c_client *client)443{444	struct power_supply_config psy_cfg = {};445	struct ltc294x_info *info;446	struct device_node *np;447	int ret;448	u32 prescaler_exp;449	s32 r_sense;450	u8 status;451 452	info = devm_kzalloc(&client->dev, sizeof(*info), GFP_KERNEL);453	if (info == NULL)454		return -ENOMEM;455 456	i2c_set_clientdata(client, info);457 458	np = of_node_get(client->dev.of_node);459 460	info->id = (enum ltc294x_id) (uintptr_t) of_device_get_match_data(461							&client->dev);462	info->supply_desc.name = np->name;463 464	/* r_sense can be negative, when sense+ is connected to the battery465	 * instead of the sense-. This results in reversed measurements. */466	ret = of_property_read_u32(np, "lltc,resistor-sense", &r_sense);467	if (ret < 0)468		return dev_err_probe(&client->dev, ret,469			"Could not find lltc,resistor-sense in devicetree\n");470	info->r_sense = r_sense;471 472	ret = of_property_read_u32(np, "lltc,prescaler-exponent",473		&prescaler_exp);474	if (ret < 0) {475		dev_warn(&client->dev,476			"lltc,prescaler-exponent not in devicetree\n");477		prescaler_exp = LTC2941_MAX_PRESCALER_EXP;478	}479 480	if (info->id == LTC2943_ID) {481		if (prescaler_exp > LTC2943_MAX_PRESCALER_EXP)482			prescaler_exp = LTC2943_MAX_PRESCALER_EXP;483		info->Qlsb = ((340 * 50000) / r_sense) >>484			     (12 - 2*prescaler_exp);485	} else {486		if (prescaler_exp > LTC2941_MAX_PRESCALER_EXP)487			prescaler_exp = LTC2941_MAX_PRESCALER_EXP;488		info->Qlsb = ((85 * 50000) / r_sense) >>489			     (7 - prescaler_exp);490	}491 492	/* Read status register to check for LTC2942 */493	if (info->id == LTC2941_ID || info->id == LTC2942_ID) {494		ret = ltc294x_read_regs(client, LTC294X_REG_STATUS, &status, 1);495		if (ret < 0)496			return dev_err_probe(&client->dev, ret,497				"Could not read status register\n");498		if (status & LTC2941_REG_STATUS_CHIP_ID)499			info->id = LTC2941_ID;500		else501			info->id = LTC2942_ID;502	}503 504	info->client = client;505	info->supply_desc.type = POWER_SUPPLY_TYPE_BATTERY;506	info->supply_desc.properties = ltc294x_properties;507	switch (info->id) {508	case LTC2944_ID:509	case LTC2943_ID:510		info->supply_desc.num_properties =511			ARRAY_SIZE(ltc294x_properties);512		break;513	case LTC2942_ID:514		info->supply_desc.num_properties =515			ARRAY_SIZE(ltc294x_properties) - 1;516		break;517	case LTC2941_ID:518	default:519		info->supply_desc.num_properties =520			ARRAY_SIZE(ltc294x_properties) - 3;521		break;522	}523	info->supply_desc.get_property = ltc294x_get_property;524	info->supply_desc.set_property = ltc294x_set_property;525	info->supply_desc.property_is_writeable = ltc294x_property_is_writeable;526	info->supply_desc.external_power_changed	= NULL;527 528	psy_cfg.drv_data = info;529 530	ret = devm_delayed_work_autocancel(&client->dev, &info->work,531					   ltc294x_work);532	if (ret)533		return ret;534 535	ret = ltc294x_reset(info, prescaler_exp);536	if (ret < 0)537		return dev_err_probe(&client->dev, ret,538			"Communication with chip failed\n");539 540	info->supply = devm_power_supply_register(&client->dev,541						  &info->supply_desc, &psy_cfg);542	if (IS_ERR(info->supply))543		return dev_err_probe(&client->dev, PTR_ERR(info->supply),544			"failed to register ltc2941\n");545 546	schedule_delayed_work(&info->work, LTC294X_WORK_DELAY * HZ);547 548	return 0;549}550 551static void ltc294x_i2c_shutdown(struct i2c_client *client)552{553	struct ltc294x_info *info = i2c_get_clientdata(client);554	int ret;555	u8 value;556	u8 control;557 558	/* The LTC2941 does not need any special handling */559	if (info->id == LTC2941_ID)560		return;561 562	/* Read control register */563	ret = ltc294x_read_regs(info->client, LTC294X_REG_CONTROL, &value, 1);564	if (ret < 0)565		return;566 567	/* Disable continuous ADC conversion as this drains the battery */568	control = LTC294X_REG_CONTROL_ADC_DISABLE(value);569	if (control != value)570		ltc294x_write_regs(info->client, LTC294X_REG_CONTROL,571			&control, 1);572}573 574#ifdef CONFIG_PM_SLEEP575 576static int ltc294x_suspend(struct device *dev)577{578	struct i2c_client *client = to_i2c_client(dev);579	struct ltc294x_info *info = i2c_get_clientdata(client);580 581	cancel_delayed_work(&info->work);582	return 0;583}584 585static int ltc294x_resume(struct device *dev)586{587	struct i2c_client *client = to_i2c_client(dev);588	struct ltc294x_info *info = i2c_get_clientdata(client);589 590	schedule_delayed_work(&info->work, LTC294X_WORK_DELAY * HZ);591	return 0;592}593 594static SIMPLE_DEV_PM_OPS(ltc294x_pm_ops, ltc294x_suspend, ltc294x_resume);595#define LTC294X_PM_OPS (&ltc294x_pm_ops)596 597#else598#define LTC294X_PM_OPS NULL599#endif /* CONFIG_PM_SLEEP */600 601 602static const struct i2c_device_id ltc294x_i2c_id[] = {603	{ "ltc2941", LTC2941_ID, },604	{ "ltc2942", LTC2942_ID, },605	{ "ltc2943", LTC2943_ID, },606	{ "ltc2944", LTC2944_ID, },607	{ },608};609MODULE_DEVICE_TABLE(i2c, ltc294x_i2c_id);610 611static const struct of_device_id ltc294x_i2c_of_match[] = {612	{613		.compatible = "lltc,ltc2941",614		.data = (void *)LTC2941_ID,615	},616	{617		.compatible = "lltc,ltc2942",618		.data = (void *)LTC2942_ID,619	},620	{621		.compatible = "lltc,ltc2943",622		.data = (void *)LTC2943_ID,623	},624	{625		.compatible = "lltc,ltc2944",626		.data = (void *)LTC2944_ID,627	},628	{ },629};630MODULE_DEVICE_TABLE(of, ltc294x_i2c_of_match);631 632static struct i2c_driver ltc294x_driver = {633	.driver = {634		.name	= "LTC2941",635		.of_match_table = ltc294x_i2c_of_match,636		.pm	= LTC294X_PM_OPS,637	},638	.probe		= ltc294x_i2c_probe,639	.shutdown	= ltc294x_i2c_shutdown,640	.id_table	= ltc294x_i2c_id,641};642module_i2c_driver(ltc294x_driver);643 644MODULE_AUTHOR("Auryn Verwegen, Topic Embedded Systems");645MODULE_AUTHOR("Mike Looijmans, Topic Embedded Products");646MODULE_DESCRIPTION("LTC2941/LTC2942/LTC2943/LTC2944 Battery Gas Gauge IC driver");647MODULE_LICENSE("GPL");648