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
116 struct 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
dc_ti_battery_get_voltage_and_current_now(struct power_supply * psy,int * volt,int * curr)127 static 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, ®_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, ®_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, ®_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, ®_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, ®_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, ®_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, ®_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
222 out_err:
223 dev_err(chip->dev, "IO-error %d communicating with PMIC\n", ret);
224 return ret;
225 }
226
227 static 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
dc_ti_battery_hw_init(struct dc_ti_battery_chip * chip)236 static 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, ®_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, ®_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, ®_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
309 out_relock:
310 /* Re-lock the EEPROM Access */
311 regmap_write(chip->regmap, DC_TI_EEPROM_ACCESS_CONTROL, EEPROM_LOCK);
312 out:
313 if (ret)
314 dev_err(chip->dev, "IO-error %d initializing PMIC\n", ret);
315
316 return ret;
317 }
318
dc_ti_battery_probe(struct platform_device * pdev)319 static 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
376 static DEFINE_RUNTIME_DEV_PM_OPS(dc_ti_battery_pm_ops, adc_battery_helper_suspend,
377 adc_battery_helper_resume, NULL);
378
379 static 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 };
386 module_platform_driver(dc_ti_battery_driver);
387
388 MODULE_ALIAS("platform:" DEV_NAME);
389 MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
390 MODULE_DESCRIPTION("Intel Dollar Cove (TI) battery driver");
391 MODULE_LICENSE("GPL");
392