File Index
Files and directories
Sign in
FILE

intel_dc_ti_battery.c

File preview with syntax highlighting.

linux-6.19.14/drivers/power/supply/intel_dc_ti_battery.c C
  1// SPDX-License-Identifier: GPL-2.0-only
  2/*
  3 * Battery driver for the coulomb-counter of the Intel Dollar Cove TI PMIC
  4 *
  5 * Note the Intel Dollar Cove TI PMIC coulomb-counter is not a full-featured
  6 * autonomous fuel-gauge. It is intended to work together with an always on
  7 * micro-controller monitoring it.
  8 *
  9 * Since Linux does not monitor coulomb-counter changes while the device
 10 * is off or suspended, voltage based capacity estimation from
 11 * the adc-battery-helper code is used.
 12 *
 13 * Copyright (C) 2024 Hans de Goede <hansg@kernel.org>
 14 *
 15 * Register definitions and calibration code was taken from
 16 * kernel/drivers/platform/x86/dc_ti_cc.c from the Acer A1-840 Android kernel
 17 * which has the following copyright header:
 18 *
 19 * Copyright (C) 2014 Intel Corporation
 20 * Author: Ramakrishna Pallala <ramakrishna.pallala@intel.com>
 21 *
 22 * dc_ti_cc.c is part of the Acer A1-840 Android kernel source-code archive
 23 * named: "App. Guide_Acer_20151221_A_A.zip"
 24 * which is distributed by Acer from the Acer A1-840 support page:
 25 * https://www.acer.com/us-en/support/product-support/A1-840/downloads
 26 */
 27
 28#include <linux/acpi.h>
 29#include <linux/bits.h>
 30#include <linux/bitfield.h>
 31#include <linux/cleanup.h>
 32#include <linux/err.h>
 33#include <linux/gpio/consumer.h>
 34#include <linux/iio/consumer.h>
 35#include <linux/mfd/intel_soc_pmic.h>
 36#include <linux/module.h>
 37#include <linux/platform_device.h>
 38#include <linux/pm_runtime.h>
 39#include <linux/power_supply.h>
 40#include <linux/property.h>
 41#include <linux/regmap.h>
 42#include <linux/timekeeping.h>
 43
 44#include "adc-battery-helper.h"
 45
 46#define DC_TI_PMIC_VERSION_REG		0x00
 47#define PMIC_VERSION_A0			0xC0
 48#define PMIC_VERSION_A1			0xC1
 49
 50#define DC_TI_CC_CNTL_REG		0x60
 51#define CC_CNTL_CC_CTR_EN		BIT(0)
 52#define CC_CNTL_CC_CLR_EN		BIT(1)
 53#define CC_CNTL_CC_CAL_EN		BIT(2)
 54#define CC_CNTL_CC_OFFSET_EN		BIT(3)
 55#define CC_CNTL_SMPL_INTVL		GENMASK(5, 4)
 56#define CC_CNTL_SMPL_INTVL_15MS		FIELD_PREP(CC_CNTL_SMPL_INTVL, 0)
 57#define CC_CNTL_SMPL_INTVL_62MS		FIELD_PREP(CC_CNTL_SMPL_INTVL, 1)
 58#define CC_CNTL_SMPL_INTVL_125MS	FIELD_PREP(CC_CNTL_SMPL_INTVL, 2)
 59#define CC_CNTL_SMPL_INTVL_250MS	FIELD_PREP(CC_CNTL_SMPL_INTVL, 3)
 60
 61#define DC_TI_SMPL_CTR0_REG		0x69
 62#define DC_TI_SMPL_CTR1_REG		0x68
 63#define DC_TI_SMPL_CTR2_REG		0x67
 64
 65#define DC_TI_CC_OFFSET_HI_REG		0x61
 66#define CC_OFFSET_HI_MASK		0x3F
 67#define DC_TI_CC_OFFSET_LO_REG		0x62
 68
 69#define DC_TI_SW_OFFSET_REG		0x6C
 70
 71#define DC_TI_CC_ACC3_REG		0x63
 72#define DC_TI_CC_ACC2_REG		0x64
 73#define DC_TI_CC_ACC1_REG		0x65
 74#define DC_TI_CC_ACC0_REG		0x66
 75
 76#define DC_TI_CC_INTG1_REG		0x6A
 77#define DC_TI_CC_INTG1_MASK		0x3F
 78#define DC_TI_CC_INTG0_REG		0x6B
 79
 80#define DC_TI_EEPROM_ACCESS_CONTROL	0x88
 81#define EEPROM_UNLOCK			0xDA
 82#define EEPROM_LOCK			0x00
 83
 84#define DC_TI_EEPROM_CC_GAIN_REG	0xF4
 85#define CC_TRIM_REVISION		GENMASK(3, 0)
 86#define CC_GAIN_CORRECTION		GENMASK(7, 4)
 87
 88#define PMIC_VERSION_A0_TRIM_REV	3
 89#define PMIC_VERSION_A1_MIN_TRIM_REV	1
 90
 91#define DC_TI_EEPROM_CC_OFFSET_REG	0xFD
 92
 93#define DC_TI_EEPROM_CTRL		0xFE
 94#define EEPROM_BANK0_SEL		0x01
 95#define EEPROM_BANK1_SEL		0x02
 96
 97#define SMPL_INTVL_US			15000
 98#define SMPL_INTVL_MS			(SMPL_INTVL_US / USEC_PER_MSEC)
 99#define CALIBRATION_TIME_US		(10 * SMPL_INTVL_US)
100#define SLEEP_SLACK_US			2500
101
102/* CC gain correction is in 0.0025 increments */
103#define CC_GAIN_STEP			25
104#define CC_GAIN_DIV			10000
105
106/* CC offset is in 0.5 units per 250ms (default sample interval) */
107#define CC_OFFSET_DIV			2
108#define CC_OFFSET_SMPL_INTVL_MS		250
109
110/* CC accumulator scale is 366.2 ųCoulumb / unit */
111#define CC_ACC_TO_UA(acc, smpl_ctr)	\
112	((acc) * (3662 * MSEC_PER_SEC / 10) / ((smpl_ctr) * SMPL_INTVL_MS))
113
114#define DEV_NAME			"chtdc_ti_battery"
115
116struct dc_ti_battery_chip {
117	/* Must be the first member see adc-battery-helper documentation */
118	struct adc_battery_helper helper;
119	struct device *dev;
120	struct regmap *regmap;
121	struct iio_channel *vbat_channel;
122	struct power_supply *psy;
123	int cc_gain;
124	int cc_offset;
125};
126
127static int dc_ti_battery_get_voltage_and_current_now(struct power_supply *psy, int *volt, int *curr)
128{
129	struct dc_ti_battery_chip *chip = power_supply_get_drvdata(psy);
130	ktime_t ktime;
131	s64 sleep_usec;
132	unsigned int reg_val;
133	s32 acc, smpl_ctr;
134	int ret;
135
136	/*
137	 * Enable coulomb-counter before reading Vbat from ADC, so that the CC
138	 * samples are from the same time period as the Vbat reading.
139	 */
140	ret = regmap_write(chip->regmap, DC_TI_CC_CNTL_REG,
141			   CC_CNTL_SMPL_INTVL_15MS | CC_CNTL_CC_OFFSET_EN | CC_CNTL_CC_CTR_EN);
142	if (ret)
143		goto out_err;
144
145	ktime = ktime_get();
146
147	/* Read Vbat, convert IIO mV to power-supply ųV */
148	ret = iio_read_channel_processed_scale(chip->vbat_channel, volt, 1000);
149	if (ret < 0)
150		goto out_err;
151
152	ktime = ktime_sub(ktime_get(), ktime);
153
154	/* Sleep at least 3 sample-times + slack to get 3+ CC samples */
155	sleep_usec = 3 * SMPL_INTVL_US + SLEEP_SLACK_US - ktime_to_us(ktime);
156	if (sleep_usec > 0 && sleep_usec < 1000000)
157		usleep_range(sleep_usec, sleep_usec + SLEEP_SLACK_US);
158
159	/*
160	 * The PMIC latches the coulomb- and sample-counters upon reading the
161	 * CC_ACC0 register. Reading multiple registers at once is not supported.
162	 *
163	 * Step 1: Read CC_ACC0 - CC_ACC3
164	 */
165	ret = regmap_read(chip->regmap, DC_TI_CC_ACC0_REG, &reg_val);
166	if (ret)
167		goto out_err;
168
169	acc = reg_val;
170
171	ret = regmap_read(chip->regmap, DC_TI_CC_ACC1_REG, &reg_val);
172	if (ret)
173		goto out_err;
174
175	acc |= reg_val << 8;
176
177	ret = regmap_read(chip->regmap, DC_TI_CC_ACC2_REG, &reg_val);
178	if (ret)
179		goto out_err;
180
181	acc |= reg_val << 16;
182
183	ret = regmap_read(chip->regmap, DC_TI_CC_ACC3_REG, &reg_val);
184	if (ret)
185		goto out_err;
186
187	acc |= reg_val << 24;
188
189	/* Step 2: Read SMPL_CTR0 - SMPL_CTR2 */
190	ret = regmap_read(chip->regmap, DC_TI_SMPL_CTR0_REG, &reg_val);
191	if (ret)
192		goto out_err;
193
194	smpl_ctr = reg_val;
195
196	ret = regmap_read(chip->regmap, DC_TI_SMPL_CTR1_REG, &reg_val);
197	if (ret)
198		goto out_err;
199
200	smpl_ctr |= reg_val << 8;
201
202	ret = regmap_read(chip->regmap, DC_TI_SMPL_CTR2_REG, &reg_val);
203	if (ret)
204		goto out_err;
205
206	smpl_ctr |= reg_val << 16;
207
208	/* Disable the coulumb-counter again */
209	ret = regmap_write(chip->regmap, DC_TI_CC_CNTL_REG,
210			   CC_CNTL_SMPL_INTVL_15MS | CC_CNTL_CC_OFFSET_EN);
211	if (ret)
212		goto out_err;
213
214	/* Apply calibration */
215	acc -= chip->cc_offset * smpl_ctr * SMPL_INTVL_MS /
216	       (CC_OFFSET_SMPL_INTVL_MS * CC_OFFSET_DIV);
217	acc = acc * (CC_GAIN_DIV - chip->cc_gain * CC_GAIN_STEP) / CC_GAIN_DIV;
218	*curr = CC_ACC_TO_UA(acc, smpl_ctr);
219
220	return 0;
221
222out_err:
223	dev_err(chip->dev, "IO-error %d communicating with PMIC\n", ret);
224	return ret;
225}
226
227static const struct power_supply_desc dc_ti_battery_psy_desc = {
228	.name		= "intel_dc_ti_battery",
229	.type		= POWER_SUPPLY_TYPE_BATTERY,
230	.get_property	= adc_battery_helper_get_property,
231	.external_power_changed	= adc_battery_helper_external_power_changed,
232	.properties	= adc_battery_helper_properties,
233	.num_properties	= ADC_HELPER_NUM_PROPERTIES,
234};
235
236static int dc_ti_battery_hw_init(struct dc_ti_battery_chip *chip)
237{
238	u8 pmic_version, cc_trim_rev;
239	unsigned int reg_val;
240	int ret;
241
242	/* Set sample rate to 15 ms and calibrate the coulomb-counter */
243	ret = regmap_write(chip->regmap, DC_TI_CC_CNTL_REG,
244			   CC_CNTL_SMPL_INTVL_15MS | CC_CNTL_CC_OFFSET_EN |
245			   CC_CNTL_CC_CAL_EN | CC_CNTL_CC_CTR_EN);
246	if (ret)
247		goto out;
248
249	fsleep(CALIBRATION_TIME_US);
250
251	/* Disable coulomb-counter it is only used while getting the current */
252	ret = regmap_write(chip->regmap, DC_TI_CC_CNTL_REG,
253			   CC_CNTL_SMPL_INTVL_15MS | CC_CNTL_CC_OFFSET_EN);
254	if (ret)
255		goto out;
256
257	ret = regmap_read(chip->regmap, DC_TI_PMIC_VERSION_REG, &reg_val);
258	if (ret)
259		goto out;
260
261	pmic_version = reg_val;
262
263	/*
264	 * As per the PMIC vendor (TI), the calibration offset and gain err
265	 * values are stored in EEPROM Bank 0 and Bank 1 of the PMIC.
266	 * We need to read the stored offset and gain margins and need
267	 * to apply the corrections to the raw coulomb counter value.
268	 */
269
270	/* Unlock the EEPROM Access */
271	ret = regmap_write(chip->regmap, DC_TI_EEPROM_ACCESS_CONTROL, EEPROM_UNLOCK);
272	if (ret)
273		goto out;
274
275	/* Select Bank 1 to read CC GAIN Err correction */
276	ret = regmap_write(chip->regmap, DC_TI_EEPROM_CTRL, EEPROM_BANK1_SEL);
277	if (ret)
278		goto out;
279
280	ret = regmap_read(chip->regmap, DC_TI_EEPROM_CC_GAIN_REG, &reg_val);
281	if (ret)
282		goto out;
283
284	cc_trim_rev = FIELD_GET(CC_TRIM_REVISION, reg_val);
285
286	dev_dbg(chip->dev, "pmic-ver 0x%02x trim-rev %d\n", pmic_version, cc_trim_rev);
287
288	if (!(pmic_version == PMIC_VERSION_A0 && cc_trim_rev == PMIC_VERSION_A0_TRIM_REV) &&
289	    !(pmic_version == PMIC_VERSION_A1 && cc_trim_rev >= PMIC_VERSION_A1_MIN_TRIM_REV)) {
290		dev_dbg(chip->dev, "unsupported trim-revision, using uncalibrated CC values\n");
291		goto out_relock;
292	}
293
294	chip->cc_gain = 1 - (int)FIELD_GET(CC_GAIN_CORRECTION, reg_val);
295
296	/* Select Bank 0 to read CC OFFSET Correction */
297	ret = regmap_write(chip->regmap, DC_TI_EEPROM_CTRL, EEPROM_BANK0_SEL);
298	if (ret)
299		goto out_relock;
300
301	ret = regmap_read(chip->regmap, DC_TI_EEPROM_CC_OFFSET_REG, &reg_val);
302	if (ret)
303		goto out_relock;
304
305	chip->cc_offset = (s8)reg_val;
306
307	dev_dbg(chip->dev, "cc-offset %d cc-gain %d\n", chip->cc_offset, chip->cc_gain);
308
309out_relock:
310	/* Re-lock the EEPROM Access */
311	regmap_write(chip->regmap, DC_TI_EEPROM_ACCESS_CONTROL, EEPROM_LOCK);
312out:
313	if (ret)
314		dev_err(chip->dev, "IO-error %d initializing PMIC\n", ret);
315
316	return ret;
317}
318
319static int dc_ti_battery_probe(struct platform_device *pdev)
320{
321	struct device *dev = &pdev->dev;
322	struct intel_soc_pmic *pmic = dev_get_drvdata(dev->parent);
323	struct power_supply_config psy_cfg = {};
324	struct fwnode_reference_args args;
325	struct gpio_desc *charge_finished;
326	struct dc_ti_battery_chip *chip;
327	int ret;
328
329	/* On most devices with a Dollar Cove TI the battery is handled by ACPI */
330	if (!acpi_quirk_skip_acpi_ac_and_battery())
331		return -ENODEV;
332
333	/* ACPI glue code adds a "monitored-battery" fwnode, wait for this */
334	ret = fwnode_property_get_reference_args(dev_fwnode(dev), "monitored-battery",
335						 NULL, 0, 0, &args);
336	if (ret) {
337		dev_dbg(dev, "fwnode_property_get_ref() ret %d\n", ret);
338		return dev_err_probe(dev, -EPROBE_DEFER, "Waiting for monitored-battery fwnode\n");
339	}
340
341	fwnode_handle_put(args.fwnode);
342
343	chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
344	if (!chip)
345		return -ENOMEM;
346
347	chip->dev = dev;
348	chip->regmap = pmic->regmap;
349
350	chip->vbat_channel = devm_iio_channel_get(dev, "VBAT");
351	if (IS_ERR(chip->vbat_channel)) {
352		dev_dbg(dev, "devm_iio_channel_get() ret %ld\n", PTR_ERR(chip->vbat_channel));
353		return dev_err_probe(dev, -EPROBE_DEFER, "Waiting for VBAT IIO channel\n");
354	}
355
356	charge_finished = devm_gpiod_get_optional(dev, "charged", GPIOD_IN);
357	if (IS_ERR(charge_finished))
358		return dev_err_probe(dev, PTR_ERR(charge_finished), "Getting charged GPIO\n");
359
360	ret = dc_ti_battery_hw_init(chip);
361	if (ret)
362		return ret;
363
364	platform_set_drvdata(pdev, chip);
365
366	psy_cfg.drv_data = chip;
367	chip->psy = devm_power_supply_register(dev, &dc_ti_battery_psy_desc, &psy_cfg);
368	if (IS_ERR(chip->psy))
369		return PTR_ERR(chip->psy);
370
371	return adc_battery_helper_init(&chip->helper, chip->psy,
372				       dc_ti_battery_get_voltage_and_current_now,
373				       charge_finished);
374}
375
376static DEFINE_RUNTIME_DEV_PM_OPS(dc_ti_battery_pm_ops, adc_battery_helper_suspend,
377				 adc_battery_helper_resume, NULL);
378
379static struct platform_driver dc_ti_battery_driver = {
380	.driver = {
381		.name = DEV_NAME,
382		.pm = pm_sleep_ptr(&dc_ti_battery_pm_ops),
383	},
384	.probe = dc_ti_battery_probe,
385};
386module_platform_driver(dc_ti_battery_driver);
387
388MODULE_ALIAS("platform:" DEV_NAME);
389MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
390MODULE_DESCRIPTION("Intel Dollar Cove (TI) battery driver");
391MODULE_LICENSE("GPL");
~/ Read-only file preview.