#include <linux/acpi.h>
#include <linux/bits.h>
#include <linux/bitfield.h>
#include <linux/cleanup.h>
#include <linux/err.h>
#include <linux/gpio/consumer.h>
#include <linux/iio/consumer.h>
#include <linux/mfd/intel_soc_pmic.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/power_supply.h>
#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/timekeeping.h>
#include "adc-battery-helper.h"
#define DC_TI_PMIC_VERSION_REG 0x00
#define PMIC_VERSION_A0 0xC0
#define PMIC_VERSION_A1 0xC1
#define DC_TI_CC_CNTL_REG 0x60
#define CC_CNTL_CC_CTR_EN BIT(0)
#define CC_CNTL_CC_CLR_EN BIT(1)
#define CC_CNTL_CC_CAL_EN BIT(2)
#define CC_CNTL_CC_OFFSET_EN BIT(3)
#define CC_CNTL_SMPL_INTVL GENMASK(5, 4)
#define CC_CNTL_SMPL_INTVL_15MS FIELD_PREP(CC_CNTL_SMPL_INTVL, 0)
#define CC_CNTL_SMPL_INTVL_62MS FIELD_PREP(CC_CNTL_SMPL_INTVL, 1)
#define CC_CNTL_SMPL_INTVL_125MS FIELD_PREP(CC_CNTL_SMPL_INTVL, 2)
#define CC_CNTL_SMPL_INTVL_250MS FIELD_PREP(CC_CNTL_SMPL_INTVL, 3)
#define DC_TI_SMPL_CTR0_REG 0x69
#define DC_TI_SMPL_CTR1_REG 0x68
#define DC_TI_SMPL_CTR2_REG 0x67
#define DC_TI_CC_OFFSET_HI_REG 0x61
#define CC_OFFSET_HI_MASK 0x3F
#define DC_TI_CC_OFFSET_LO_REG 0x62
#define DC_TI_SW_OFFSET_REG 0x6C
#define DC_TI_CC_ACC3_REG 0x63
#define DC_TI_CC_ACC2_REG 0x64
#define DC_TI_CC_ACC1_REG 0x65
#define DC_TI_CC_ACC0_REG 0x66
#define DC_TI_CC_INTG1_REG 0x6A
#define DC_TI_CC_INTG1_MASK 0x3F
#define DC_TI_CC_INTG0_REG 0x6B
#define DC_TI_EEPROM_ACCESS_CONTROL 0x88
#define EEPROM_UNLOCK 0xDA
#define EEPROM_LOCK 0x00
#define DC_TI_EEPROM_CC_GAIN_REG 0xF4
#define CC_TRIM_REVISION GENMASK(3, 0)
#define CC_GAIN_CORRECTION GENMASK(7, 4)
#define PMIC_VERSION_A0_TRIM_REV 3
#define PMIC_VERSION_A1_MIN_TRIM_REV 1
#define DC_TI_EEPROM_CC_OFFSET_REG 0xFD
#define DC_TI_EEPROM_CTRL 0xFE
#define EEPROM_BANK0_SEL 0x01
#define EEPROM_BANK1_SEL 0x02
#define SMPL_INTVL_US 15000
#define SMPL_INTVL_MS (SMPL_INTVL_US / USEC_PER_MSEC)
#define CALIBRATION_TIME_US (10 * SMPL_INTVL_US)
#define SLEEP_SLACK_US 2500
#define CC_GAIN_STEP 25
#define CC_GAIN_DIV 10000
#define CC_OFFSET_DIV 2
#define CC_OFFSET_SMPL_INTVL_MS 250
#define CC_ACC_TO_UA(acc, smpl_ctr) \
((acc) * (3662 * MSEC_PER_SEC / 10) / ((smpl_ctr) * SMPL_INTVL_MS))
#define DEV_NAME "chtdc_ti_battery"
struct dc_ti_battery_chip {
struct adc_battery_helper helper;
struct device *dev;
struct regmap *regmap;
struct iio_channel *vbat_channel;
struct power_supply *psy;
int cc_gain;
int cc_offset;
};
static int dc_ti_battery_get_voltage_and_current_now(struct power_supply *psy, int *volt, int *curr)
{
struct dc_ti_battery_chip *chip = power_supply_get_drvdata(psy);
ktime_t ktime;
s64 sleep_usec;
unsigned int reg_val;
s32 acc, smpl_ctr;
int ret;
ret = regmap_write(chip->regmap, DC_TI_CC_CNTL_REG,
CC_CNTL_SMPL_INTVL_15MS | CC_CNTL_CC_OFFSET_EN | CC_CNTL_CC_CTR_EN);
if (ret)
goto out_err;
ktime = ktime_get();
ret = iio_read_channel_processed_scale(chip->vbat_channel, volt, 1000);
if (ret < 0)
goto out_err;
ktime = ktime_sub(ktime_get(), ktime);
sleep_usec = 3 * SMPL_INTVL_US + SLEEP_SLACK_US - ktime_to_us(ktime);
if (sleep_usec > 0 && sleep_usec < 1000000)
usleep_range(sleep_usec, sleep_usec + SLEEP_SLACK_US);
ret = regmap_read(chip->regmap, DC_TI_CC_ACC0_REG, ®_val);
if (ret)
goto out_err;
acc = reg_val;
ret = regmap_read(chip->regmap, DC_TI_CC_ACC1_REG, ®_val);
if (ret)
goto out_err;
acc |= reg_val << 8;
ret = regmap_read(chip->regmap, DC_TI_CC_ACC2_REG, ®_val);
if (ret)
goto out_err;
acc |= reg_val << 16;
ret = regmap_read(chip->regmap, DC_TI_CC_ACC3_REG, ®_val);
if (ret)
goto out_err;
acc |= reg_val << 24;
ret = regmap_read(chip->regmap, DC_TI_SMPL_CTR0_REG, ®_val);
if (ret)
goto out_err;
smpl_ctr = reg_val;
ret = regmap_read(chip->regmap, DC_TI_SMPL_CTR1_REG, ®_val);
if (ret)
goto out_err;
smpl_ctr |= reg_val << 8;
ret = regmap_read(chip->regmap, DC_TI_SMPL_CTR2_REG, ®_val);
if (ret)
goto out_err;
smpl_ctr |= reg_val << 16;
ret = regmap_write(chip->regmap, DC_TI_CC_CNTL_REG,
CC_CNTL_SMPL_INTVL_15MS | CC_CNTL_CC_OFFSET_EN);
if (ret)
goto out_err;
acc -= chip->cc_offset * smpl_ctr * SMPL_INTVL_MS /
(CC_OFFSET_SMPL_INTVL_MS * CC_OFFSET_DIV);
acc = acc * (CC_GAIN_DIV - chip->cc_gain * CC_GAIN_STEP) / CC_GAIN_DIV;
*curr = CC_ACC_TO_UA(acc, smpl_ctr);
return 0;
out_err:
dev_err(chip->dev, "IO-error %d communicating with PMIC\n", ret);
return ret;
}
static const struct power_supply_desc dc_ti_battery_psy_desc = {
.name = "intel_dc_ti_battery",
.type = POWER_SUPPLY_TYPE_BATTERY,
.get_property = adc_battery_helper_get_property,
.external_power_changed = adc_battery_helper_external_power_changed,
.properties = adc_battery_helper_properties,
.num_properties = ADC_HELPER_NUM_PROPERTIES,
};
static int dc_ti_battery_hw_init(struct dc_ti_battery_chip *chip)
{
u8 pmic_version, cc_trim_rev;
unsigned int reg_val;
int ret;
ret = regmap_write(chip->regmap, DC_TI_CC_CNTL_REG,
CC_CNTL_SMPL_INTVL_15MS | CC_CNTL_CC_OFFSET_EN |
CC_CNTL_CC_CAL_EN | CC_CNTL_CC_CTR_EN);
if (ret)
goto out;
fsleep(CALIBRATION_TIME_US);
ret = regmap_write(chip->regmap, DC_TI_CC_CNTL_REG,
CC_CNTL_SMPL_INTVL_15MS | CC_CNTL_CC_OFFSET_EN);
if (ret)
goto out;
ret = regmap_read(chip->regmap, DC_TI_PMIC_VERSION_REG, ®_val);
if (ret)
goto out;
pmic_version = reg_val;
ret = regmap_write(chip->regmap, DC_TI_EEPROM_ACCESS_CONTROL, EEPROM_UNLOCK);
if (ret)
goto out;
ret = regmap_write(chip->regmap, DC_TI_EEPROM_CTRL, EEPROM_BANK1_SEL);
if (ret)
goto out;
ret = regmap_read(chip->regmap, DC_TI_EEPROM_CC_GAIN_REG, ®_val);
if (ret)
goto out;
cc_trim_rev = FIELD_GET(CC_TRIM_REVISION, reg_val);
dev_dbg(chip->dev, "pmic-ver 0x%02x trim-rev %d\n", pmic_version, cc_trim_rev);
if (!(pmic_version == PMIC_VERSION_A0 && cc_trim_rev == PMIC_VERSION_A0_TRIM_REV) &&
!(pmic_version == PMIC_VERSION_A1 && cc_trim_rev >= PMIC_VERSION_A1_MIN_TRIM_REV)) {
dev_dbg(chip->dev, "unsupported trim-revision, using uncalibrated CC values\n");
goto out_relock;
}
chip->cc_gain = 1 - (int)FIELD_GET(CC_GAIN_CORRECTION, reg_val);
ret = regmap_write(chip->regmap, DC_TI_EEPROM_CTRL, EEPROM_BANK0_SEL);
if (ret)
goto out_relock;
ret = regmap_read(chip->regmap, DC_TI_EEPROM_CC_OFFSET_REG, ®_val);
if (ret)
goto out_relock;
chip->cc_offset = (s8)reg_val;
dev_dbg(chip->dev, "cc-offset %d cc-gain %d\n", chip->cc_offset, chip->cc_gain);
out_relock:
regmap_write(chip->regmap, DC_TI_EEPROM_ACCESS_CONTROL, EEPROM_LOCK);
out:
if (ret)
dev_err(chip->dev, "IO-error %d initializing PMIC\n", ret);
return ret;
}
static int dc_ti_battery_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct intel_soc_pmic *pmic = dev_get_drvdata(dev->parent);
struct power_supply_config psy_cfg = {};
struct fwnode_reference_args args;
struct gpio_desc *charge_finished;
struct dc_ti_battery_chip *chip;
int ret;
if (!acpi_quirk_skip_acpi_ac_and_battery())
return -ENODEV;
ret = fwnode_property_get_reference_args(dev_fwnode(dev), "monitored-battery",
NULL, 0, 0, &args);
if (ret) {
dev_dbg(dev, "fwnode_property_get_ref() ret %d\n", ret);
return dev_err_probe(dev, -EPROBE_DEFER, "Waiting for monitored-battery fwnode\n");
}
fwnode_handle_put(args.fwnode);
chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
chip->dev = dev;
chip->regmap = pmic->regmap;
chip->vbat_channel = devm_iio_channel_get(dev, "VBAT");
if (IS_ERR(chip->vbat_channel)) {
dev_dbg(dev, "devm_iio_channel_get() ret %ld\n", PTR_ERR(chip->vbat_channel));
return dev_err_probe(dev, -EPROBE_DEFER, "Waiting for VBAT IIO channel\n");
}
charge_finished = devm_gpiod_get_optional(dev, "charged", GPIOD_IN);
if (IS_ERR(charge_finished))
return dev_err_probe(dev, PTR_ERR(charge_finished), "Getting charged GPIO\n");
ret = dc_ti_battery_hw_init(chip);
if (ret)
return ret;
platform_set_drvdata(pdev, chip);
psy_cfg.drv_data = chip;
chip->psy = devm_power_supply_register(dev, &dc_ti_battery_psy_desc, &psy_cfg);
if (IS_ERR(chip->psy))
return PTR_ERR(chip->psy);
return adc_battery_helper_init(&chip->helper, chip->psy,
dc_ti_battery_get_voltage_and_current_now,
charge_finished);
}
static DEFINE_RUNTIME_DEV_PM_OPS(dc_ti_battery_pm_ops, adc_battery_helper_suspend,
adc_battery_helper_resume, NULL);
static struct platform_driver dc_ti_battery_driver = {
.driver = {
.name = DEV_NAME,
.pm = pm_sleep_ptr(&dc_ti_battery_pm_ops),
},
.probe = dc_ti_battery_probe,
};
module_platform_driver(dc_ti_battery_driver);
MODULE_ALIAS("platform:" DEV_NAME);
MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
MODULE_DESCRIPTION("Intel Dollar Cove (TI) battery driver");
MODULE_LICENSE("GPL");