diff options
Diffstat (limited to 'drivers/iio')
-rw-r--r-- | drivers/iio/adc/Kconfig | 27 | ||||
-rw-r--r-- | drivers/iio/adc/Makefile | 2 | ||||
-rw-r--r-- | drivers/iio/adc/qcom-rradc.c | 1109 | ||||
-rw-r--r-- | drivers/iio/adc/qcom-tadc.c | 1324 | ||||
-rw-r--r-- | drivers/iio/inkern.c | 18 |
5 files changed, 2480 insertions, 0 deletions
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index bda6bbe4479c..51cb6d8cffda 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -303,6 +303,33 @@ config QCOM_SPMI_VADC To compile this driver as a module, choose M here: the module will be called qcom-spmi-vadc. +config QCOM_RRADC + tristate "Qualcomm Technologies Inc. PMIC Round robin ADC" + depends on SPMI + select REGMAP_SPMI + help + This is the PMIC Round Robin ADC driver. + + The driver supports multiple channels read used for telemetry + and supports clients to read batt_id, batt_therm, PMIC die + temperature, USB_IN and DC_IN voltage and current. + The RRADC is a 10-bit ADC. + + To compile this driver as a module, choose M here: the module will + be called qcom-rradc. + +config QCOM_TADC + tristate "Qualcomm Technologies Inc. TADC driver" + depends on MFD_I2C_PMIC + help + Say yes here to support the Qualcomm Technologies Inc. telemetry ADC. + The TADC provides battery temperature, skin temperature, + die temperature, battery voltage, battery current, input voltage, + input current, and OTG current. + + The driver can also be built as a module. If so, the module will be + called qcom-tadc. + config ROCKCHIP_SARADC tristate "Rockchip SARADC driver" depends on ARCH_ROCKCHIP || (ARM && COMPILE_TEST) diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 99b37a963a1e..9124e49a5f65 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -29,6 +29,8 @@ obj-$(CONFIG_MEN_Z188_ADC) += men_z188_adc.o obj-$(CONFIG_NAU7802) += nau7802.o obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o +obj-$(CONFIG_QCOM_RRADC) += qcom-rradc.o +obj-$(CONFIG_QCOM_TADC) += qcom-tadc.o obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o obj-$(CONFIG_TI_ADC128S052) += ti-adc128s052.o diff --git a/drivers/iio/adc/qcom-rradc.c b/drivers/iio/adc/qcom-rradc.c new file mode 100644 index 000000000000..537cca877f66 --- /dev/null +++ b/drivers/iio/adc/qcom-rradc.c @@ -0,0 +1,1109 @@ +/* + * Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "RRADC: %s: " fmt, __func__ + +#include <linux/iio/iio.h> +#include <linux/kernel.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/delay.h> +#include <linux/qpnp/qpnp-revid.h> + +#define FG_ADC_RR_EN_CTL 0x46 +#define FG_ADC_RR_SKIN_TEMP_LSB 0x50 +#define FG_ADC_RR_SKIN_TEMP_MSB 0x51 +#define FG_ADC_RR_RR_ADC_CTL 0x52 +#define FG_ADC_RR_ADC_CTL_CONTINUOUS_SEL_MASK 0x8 +#define FG_ADC_RR_ADC_CTL_CONTINUOUS_SEL BIT(3) +#define FG_ADC_RR_ADC_LOG 0x53 +#define FG_ADC_RR_ADC_LOG_CLR_CTRL BIT(0) + +#define FG_ADC_RR_FAKE_BATT_LOW_LSB 0x58 +#define FG_ADC_RR_FAKE_BATT_LOW_MSB 0x59 +#define FG_ADC_RR_FAKE_BATT_HIGH_LSB 0x5A +#define FG_ADC_RR_FAKE_BATT_HIGH_MSB 0x5B + +#define FG_ADC_RR_BATT_ID_CTRL 0x60 +#define FG_ADC_RR_BATT_ID_CTRL_CHANNEL_CONV BIT(0) +#define FG_ADC_RR_BATT_ID_TRIGGER 0x61 +#define FG_ADC_RR_BATT_ID_TRIGGER_CTL BIT(0) +#define FG_ADC_RR_BATT_ID_STS 0x62 +#define FG_ADC_RR_BATT_ID_CFG 0x63 +#define FG_ADC_RR_BATT_ID_5_LSB 0x66 +#define FG_ADC_RR_BATT_ID_5_MSB 0x67 +#define FG_ADC_RR_BATT_ID_15_LSB 0x68 +#define FG_ADC_RR_BATT_ID_15_MSB 0x69 +#define FG_ADC_RR_BATT_ID_150_LSB 0x6A +#define FG_ADC_RR_BATT_ID_150_MSB 0x6B + +#define FG_ADC_RR_BATT_THERM_CTRL 0x70 +#define FG_ADC_RR_BATT_THERM_TRIGGER 0x71 +#define FG_ADC_RR_BATT_THERM_STS 0x72 +#define FG_ADC_RR_BATT_THERM_CFG 0x73 +#define FG_ADC_RR_BATT_THERM_LSB 0x74 +#define FG_ADC_RR_BATT_THERM_MSB 0x75 +#define FG_ADC_RR_BATT_THERM_FREQ 0x76 + +#define FG_ADC_RR_AUX_THERM_CTRL 0x80 +#define FG_ADC_RR_AUX_THERM_TRIGGER 0x81 +#define FG_ADC_RR_AUX_THERM_STS 0x82 +#define FG_ADC_RR_AUX_THERM_CFG 0x83 +#define FG_ADC_RR_AUX_THERM_LSB 0x84 +#define FG_ADC_RR_AUX_THERM_MSB 0x85 + +#define FG_ADC_RR_SKIN_HOT 0x86 +#define FG_ADC_RR_SKIN_TOO_HOT 0x87 + +#define FG_ADC_RR_AUX_THERM_C1 0x88 +#define FG_ADC_RR_AUX_THERM_C2 0x89 +#define FG_ADC_RR_AUX_THERM_C3 0x8A +#define FG_ADC_RR_AUX_THERM_HALF_RANGE 0x8B + +#define FG_ADC_RR_USB_IN_V_CTRL 0x90 +#define FG_ADC_RR_USB_IN_V_TRIGGER 0x91 +#define FG_ADC_RR_USB_IN_V_EVERY_CYCLE_MASK 0x80 +#define FG_ADC_RR_USB_IN_V_EVERY_CYCLE BIT(7) +#define FG_ADC_RR_USB_IN_V_STS 0x92 +#define FG_ADC_RR_USB_IN_V_LSB 0x94 +#define FG_ADC_RR_USB_IN_V_MSB 0x95 +#define FG_ADC_RR_USB_IN_I_CTRL 0x98 +#define FG_ADC_RR_USB_IN_I_TRIGGER 0x99 +#define FG_ADC_RR_USB_IN_I_STS 0x9A +#define FG_ADC_RR_USB_IN_I_LSB 0x9C +#define FG_ADC_RR_USB_IN_I_MSB 0x9D + +#define FG_ADC_RR_DC_IN_V_CTRL 0xA0 +#define FG_ADC_RR_DC_IN_V_TRIGGER 0xA1 +#define FG_ADC_RR_DC_IN_V_STS 0xA2 +#define FG_ADC_RR_DC_IN_V_LSB 0xA4 +#define FG_ADC_RR_DC_IN_V_MSB 0xA5 +#define FG_ADC_RR_DC_IN_I_CTRL 0xA8 +#define FG_ADC_RR_DC_IN_I_TRIGGER 0xA9 +#define FG_ADC_RR_DC_IN_I_STS 0xAA +#define FG_ADC_RR_DC_IN_I_LSB 0xAC +#define FG_ADC_RR_DC_IN_I_MSB 0xAD + +#define FG_ADC_RR_PMI_DIE_TEMP_CTRL 0xB0 +#define FG_ADC_RR_PMI_DIE_TEMP_TRIGGER 0xB1 +#define FG_ADC_RR_PMI_DIE_TEMP_STS 0xB2 +#define FG_ADC_RR_PMI_DIE_TEMP_CFG 0xB3 +#define FG_ADC_RR_PMI_DIE_TEMP_LSB 0xB4 +#define FG_ADC_RR_PMI_DIE_TEMP_MSB 0xB5 + +#define FG_ADC_RR_CHARGER_TEMP_CTRL 0xB8 +#define FG_ADC_RR_CHARGER_TEMP_TRIGGER 0xB9 +#define FG_ADC_RR_CHARGER_TEMP_STS 0xBA +#define FG_ADC_RR_CHARGER_TEMP_CFG 0xBB +#define FG_ADC_RR_CHARGER_TEMP_LSB 0xBC +#define FG_ADC_RR_CHARGER_TEMP_MSB 0xBD +#define FG_ADC_RR_CHARGER_HOT 0xBE +#define FG_ADC_RR_CHARGER_TOO_HOT 0xBF + +#define FG_ADC_RR_GPIO_CTRL 0xC0 +#define FG_ADC_RR_GPIO_TRIGGER 0xC1 +#define FG_ADC_RR_GPIO_STS 0xC2 +#define FG_ADC_RR_GPIO_LSB 0xC4 +#define FG_ADC_RR_GPIO_MSB 0xC5 + +#define FG_ADC_RR_ATEST_CTRL 0xC8 +#define FG_ADC_RR_ATEST_TRIGGER 0xC9 +#define FG_ADC_RR_ATEST_STS 0xCA +#define FG_ADC_RR_ATEST_LSB 0xCC +#define FG_ADC_RR_ATEST_MSB 0xCD +#define FG_ADC_RR_SEC_ACCESS 0xD0 + +#define FG_ADC_RR_PERPH_RESET_CTL2 0xD9 +#define FG_ADC_RR_PERPH_RESET_CTL3 0xDA +#define FG_ADC_RR_PERPH_RESET_CTL4 0xDB +#define FG_ADC_RR_INT_TEST1 0xE0 +#define FG_ADC_RR_INT_TEST_VAL 0xE1 + +#define FG_ADC_RR_TM_TRIGGER_CTRLS 0xE2 +#define FG_ADC_RR_TM_ADC_CTRLS 0xE3 +#define FG_ADC_RR_TM_CNL_CTRL 0xE4 +#define FG_ADC_RR_TM_BATT_ID_CTRL 0xE5 +#define FG_ADC_RR_TM_THERM_CTRL 0xE6 +#define FG_ADC_RR_TM_CONV_STS 0xE7 +#define FG_ADC_RR_TM_ADC_READ_LSB 0xE8 +#define FG_ADC_RR_TM_ADC_READ_MSB 0xE9 +#define FG_ADC_RR_TM_ATEST_MUX_1 0xEA +#define FG_ADC_RR_TM_ATEST_MUX_2 0xEB +#define FG_ADC_RR_TM_REFERENCES 0xED +#define FG_ADC_RR_TM_MISC_CTL 0xEE +#define FG_ADC_RR_TM_RR_CTRL 0xEF + +#define FG_ADC_RR_BATT_ID_5_MA 5 +#define FG_ADC_RR_BATT_ID_15_MA 15 +#define FG_ADC_RR_BATT_ID_150_MA 150 +#define FG_ADC_RR_BATT_ID_RANGE 820 + +#define FG_ADC_BITS 10 +#define FG_MAX_ADC_READINGS (1 << FG_ADC_BITS) +#define FG_ADC_RR_FS_VOLTAGE_MV 2500 + +/* BATT_THERM 0.25K/LSB */ +#define FG_ADC_RR_BATT_THERM_LSB_K 4 + +#define FG_ADC_RR_TEMP_FS_VOLTAGE_NUM 5000000 +#define FG_ADC_RR_TEMP_FS_VOLTAGE_DEN 3 +#define FG_ADC_RR_DIE_TEMP_OFFSET 601400 +#define FG_ADC_RR_DIE_TEMP_SLOPE 2 +#define FG_ADC_RR_DIE_TEMP_OFFSET_MILLI_DEGC 25000 + +#define FG_ADC_RR_CHG_TEMP_GF_OFFSET_UV 1303168 +#define FG_ADC_RR_CHG_TEMP_GF_SLOPE_UV_PER_C 3784 +#define FG_ADC_RR_CHG_TEMP_SMIC_OFFSET_UV 1338433 +#define FG_ADC_RR_CHG_TEMP_SMIC_SLOPE_UV_PER_C 3655 +#define FG_ADC_RR_CHG_TEMP_660_GF_OFFSET_UV 1309001 +#define FG_RR_CHG_TEMP_660_GF_SLOPE_UV_PER_C 3403 +#define FG_ADC_RR_CHG_TEMP_660_SMIC_OFFSET_UV 1295898 +#define FG_RR_CHG_TEMP_660_SMIC_SLOPE_UV_PER_C 3596 +#define FG_ADC_RR_CHG_TEMP_660_MGNA_OFFSET_UV 1314779 +#define FG_RR_CHG_TEMP_660_MGNA_SLOPE_UV_PER_C 3496 +#define FG_ADC_RR_CHG_TEMP_OFFSET_MILLI_DEGC 25000 +#define FG_ADC_RR_CHG_THRESHOLD_SCALE 4 + +#define FG_ADC_RR_VOLT_INPUT_FACTOR 8 +#define FG_ADC_RR_CURR_INPUT_FACTOR 2000 +#define FG_ADC_RR_CURR_USBIN_INPUT_FACTOR_MIL 1886 +#define FG_ADC_SCALE_MILLI_FACTOR 1000 +#define FG_ADC_KELVINMIL_CELSIUSMIL 273150 + +#define FG_ADC_RR_GPIO_FS_RANGE 5000 +#define FG_RR_ADC_COHERENT_CHECK_RETRY 5 +#define FG_RR_ADC_MAX_CONTINUOUS_BUFFER_LEN 16 +#define FG_RR_ADC_STS_CHANNEL_READING_MASK 0x3 +#define FG_RR_ADC_STS_CHANNEL_STS 0x2 + +#define FG_RR_CONV_CONTINUOUS_TIME_MIN_US 50000 +#define FG_RR_CONV_CONTINUOUS_TIME_MAX_US 51000 +#define FG_RR_CONV_MAX_RETRY_CNT 50 + +/* + * The channel number is not a physical index in hardware, + * rather it's a list of supported channels and an index to + * select the respective channel properties such as scaling + * the result. Add any new additional channels supported by + * the RR ADC before RR_ADC_MAX. + */ +enum rradc_channel_id { + RR_ADC_BATT_ID = 0, + RR_ADC_BATT_THERM, + RR_ADC_SKIN_TEMP, + RR_ADC_USBIN_I, + RR_ADC_USBIN_V, + RR_ADC_DCIN_I, + RR_ADC_DCIN_V, + RR_ADC_DIE_TEMP, + RR_ADC_CHG_TEMP, + RR_ADC_GPIO, + RR_ADC_CHG_HOT_TEMP, + RR_ADC_CHG_TOO_HOT_TEMP, + RR_ADC_SKIN_HOT_TEMP, + RR_ADC_SKIN_TOO_HOT_TEMP, + RR_ADC_MAX +}; + +struct rradc_chip { + struct device *dev; + struct mutex lock; + struct regmap *regmap; + u16 base; + struct iio_chan_spec *iio_chans; + unsigned int nchannels; + struct rradc_chan_prop *chan_props; + struct device_node *revid_dev_node; + struct pmic_revid_data *pmic_fab_id; +}; + +struct rradc_channels { + const char *datasheet_name; + enum iio_chan_type type; + long info_mask; + u8 lsb; + u8 msb; + u8 sts; + int (*scale)(struct rradc_chip *chip, struct rradc_chan_prop *prop, + u16 adc_code, int *result); +}; + +struct rradc_chan_prop { + enum rradc_channel_id channel; + uint32_t channel_data; + int (*scale)(struct rradc_chip *chip, struct rradc_chan_prop *prop, + u16 adc_code, int *result); +}; + +static int rradc_masked_write(struct rradc_chip *rr_adc, u16 offset, u8 mask, + u8 val) +{ + int rc; + + rc = regmap_update_bits(rr_adc->regmap, rr_adc->base + offset, + mask, val); + if (rc) { + pr_err("spmi write failed: addr=%03X, rc=%d\n", offset, rc); + return rc; + } + + return rc; +} + +static int rradc_read(struct rradc_chip *rr_adc, u16 offset, u8 *data, int len) +{ + int rc = 0, retry_cnt = 0, i = 0; + u8 data_check[FG_RR_ADC_MAX_CONTINUOUS_BUFFER_LEN]; + bool coherent_err = false; + + if (len > FG_RR_ADC_MAX_CONTINUOUS_BUFFER_LEN) { + pr_err("Increase the buffer length\n"); + return -EINVAL; + } + + while (retry_cnt < FG_RR_ADC_COHERENT_CHECK_RETRY) { + rc = regmap_bulk_read(rr_adc->regmap, rr_adc->base + offset, + data, len); + if (rc < 0) { + pr_err("rr_adc reg 0x%x failed :%d\n", offset, rc); + return rc; + } + + rc = regmap_bulk_read(rr_adc->regmap, rr_adc->base + offset, + data_check, len); + if (rc < 0) { + pr_err("rr_adc reg 0x%x failed :%d\n", offset, rc); + return rc; + } + + for (i = 0; i < len; i++) { + if (data[i] != data_check[i]) + coherent_err = true; + } + + if (coherent_err) { + retry_cnt++; + coherent_err = false; + pr_debug("retry_cnt:%d\n", retry_cnt); + } else { + break; + } + } + + if (retry_cnt == FG_RR_ADC_COHERENT_CHECK_RETRY) + pr_err("Retry exceeded for coherrency check\n"); + + return rc; +} + +static int rradc_post_process_batt_id(struct rradc_chip *chip, + struct rradc_chan_prop *prop, u16 adc_code, + int *result_ohms) +{ + uint32_t current_value; + int64_t r_id; + + current_value = prop->channel_data; + r_id = ((int64_t)adc_code * FG_ADC_RR_FS_VOLTAGE_MV); + r_id = div64_s64(r_id, (FG_MAX_ADC_READINGS * current_value)); + *result_ohms = (r_id * FG_ADC_SCALE_MILLI_FACTOR); + + return 0; +} + +static int rradc_post_process_therm(struct rradc_chip *chip, + struct rradc_chan_prop *prop, u16 adc_code, + int *result_millidegc) +{ + int64_t temp; + + /* K = code/4 */ + temp = div64_s64(adc_code, FG_ADC_RR_BATT_THERM_LSB_K); + temp *= FG_ADC_SCALE_MILLI_FACTOR; + *result_millidegc = temp - FG_ADC_KELVINMIL_CELSIUSMIL; + + return 0; +} + +static int rradc_post_process_volt(struct rradc_chip *chip, + struct rradc_chan_prop *prop, u16 adc_code, + int *result_uv) +{ + int64_t uv = 0; + + /* 8x input attenuation; 2.5V ADC full scale */ + uv = ((int64_t)adc_code * FG_ADC_RR_VOLT_INPUT_FACTOR); + uv *= (FG_ADC_RR_FS_VOLTAGE_MV * FG_ADC_SCALE_MILLI_FACTOR); + uv = div64_s64(uv, FG_MAX_ADC_READINGS); + *result_uv = uv; + + return 0; +} + +static int rradc_post_process_curr(struct rradc_chip *chip, + struct rradc_chan_prop *prop, u16 adc_code, + int *result_ua) +{ + int64_t ua = 0, scale = 0; + + if (!prop) + return -EINVAL; + + if (prop->channel == RR_ADC_USBIN_I) + scale = FG_ADC_RR_CURR_USBIN_INPUT_FACTOR_MIL; + else + scale = FG_ADC_RR_CURR_INPUT_FACTOR; + + /* scale * V/A; 2.5V ADC full scale */ + ua = ((int64_t)adc_code * scale); + ua *= (FG_ADC_RR_FS_VOLTAGE_MV * FG_ADC_SCALE_MILLI_FACTOR); + ua = div64_s64(ua, (FG_MAX_ADC_READINGS * 1000)); + *result_ua = ua; + + return 0; +} + +static int rradc_post_process_die_temp(struct rradc_chip *chip, + struct rradc_chan_prop *prop, u16 adc_code, + int *result_millidegc) +{ + int64_t temp = 0; + + temp = ((int64_t)adc_code * FG_ADC_RR_TEMP_FS_VOLTAGE_NUM); + temp = div64_s64(temp, (FG_ADC_RR_TEMP_FS_VOLTAGE_DEN * + FG_MAX_ADC_READINGS)); + temp -= FG_ADC_RR_DIE_TEMP_OFFSET; + temp = div64_s64(temp, FG_ADC_RR_DIE_TEMP_SLOPE); + temp += FG_ADC_RR_DIE_TEMP_OFFSET_MILLI_DEGC; + *result_millidegc = temp; + + return 0; +} + +static int rradc_get_660_fab_coeff(struct rradc_chip *chip, + int64_t *offset, int64_t *slope) +{ + switch (chip->pmic_fab_id->fab_id) { + case PM660_FAB_ID_GF: + *offset = FG_ADC_RR_CHG_TEMP_660_GF_OFFSET_UV; + *slope = FG_RR_CHG_TEMP_660_GF_SLOPE_UV_PER_C; + break; + case PM660_FAB_ID_TSMC: + *offset = FG_ADC_RR_CHG_TEMP_660_SMIC_OFFSET_UV; + *slope = FG_RR_CHG_TEMP_660_SMIC_SLOPE_UV_PER_C; + break; + default: + *offset = FG_ADC_RR_CHG_TEMP_660_MGNA_OFFSET_UV; + *slope = FG_RR_CHG_TEMP_660_MGNA_SLOPE_UV_PER_C; + } + + return 0; +} + +static int rradc_get_8998_fab_coeff(struct rradc_chip *chip, + int64_t *offset, int64_t *slope) +{ + switch (chip->pmic_fab_id->fab_id) { + case PMI8998_FAB_ID_GF: + *offset = FG_ADC_RR_CHG_TEMP_GF_OFFSET_UV; + *slope = FG_ADC_RR_CHG_TEMP_GF_SLOPE_UV_PER_C; + break; + case PMI8998_FAB_ID_SMIC: + *offset = FG_ADC_RR_CHG_TEMP_SMIC_OFFSET_UV; + *slope = FG_ADC_RR_CHG_TEMP_SMIC_SLOPE_UV_PER_C; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int rradc_post_process_chg_temp_hot(struct rradc_chip *chip, + struct rradc_chan_prop *prop, u16 adc_code, + int *result_millidegc) +{ + int64_t uv = 0, offset = 0, slope = 0; + int rc = 0; + + if (chip->revid_dev_node) { + switch (chip->pmic_fab_id->pmic_subtype) { + case PM660_SUBTYPE: + rc = rradc_get_660_fab_coeff(chip, &offset, &slope); + if (rc < 0) { + pr_err("Unable to get fab id coefficients\n"); + return -EINVAL; + } + break; + case PMI8998_SUBTYPE: + rc = rradc_get_8998_fab_coeff(chip, &offset, &slope); + if (rc < 0) { + pr_err("Unable to get fab id coefficients\n"); + return -EINVAL; + } + break; + default: + pr_err("No PMIC subtype found\n"); + return -EINVAL; + } + } else { + pr_err("No temperature scaling coefficients\n"); + return -EINVAL; + } + + uv = (int64_t) adc_code * FG_ADC_RR_CHG_THRESHOLD_SCALE; + uv = uv * FG_ADC_RR_TEMP_FS_VOLTAGE_NUM; + uv = div64_s64(uv, (FG_ADC_RR_TEMP_FS_VOLTAGE_DEN * + FG_MAX_ADC_READINGS)); + uv = offset - uv; + uv = div64_s64((uv * FG_ADC_SCALE_MILLI_FACTOR), slope); + uv = uv + FG_ADC_RR_CHG_TEMP_OFFSET_MILLI_DEGC; + *result_millidegc = uv; + + return 0; +} + +static int rradc_post_process_skin_temp_hot(struct rradc_chip *chip, + struct rradc_chan_prop *prop, u16 adc_code, + int *result_millidegc) +{ + int64_t temp = 0; + + temp = (int64_t) adc_code; + temp = div64_s64(temp, 2); + temp = temp - 30; + temp *= FG_ADC_SCALE_MILLI_FACTOR; + *result_millidegc = temp; + + return 0; +} + +static int rradc_post_process_chg_temp(struct rradc_chip *chip, + struct rradc_chan_prop *prop, u16 adc_code, + int *result_millidegc) +{ + int64_t uv = 0, offset = 0, slope = 0; + int rc = 0; + + if (chip->revid_dev_node) { + switch (chip->pmic_fab_id->pmic_subtype) { + case PM660_SUBTYPE: + rc = rradc_get_660_fab_coeff(chip, &offset, &slope); + if (rc < 0) { + pr_err("Unable to get fab id coefficients\n"); + return -EINVAL; + } + break; + case PMI8998_SUBTYPE: + rc = rradc_get_8998_fab_coeff(chip, &offset, &slope); + if (rc < 0) { + pr_err("Unable to get fab id coefficients\n"); + return -EINVAL; + } + break; + default: + pr_err("No PMIC subtype found\n"); + return -EINVAL; + } + } else { + pr_err("No temperature scaling coefficients\n"); + return -EINVAL; + } + + uv = ((int64_t) adc_code * FG_ADC_RR_TEMP_FS_VOLTAGE_NUM); + uv = div64_s64(uv, (FG_ADC_RR_TEMP_FS_VOLTAGE_DEN * + FG_MAX_ADC_READINGS)); + uv = offset - uv; + uv = div64_s64((uv * FG_ADC_SCALE_MILLI_FACTOR), slope); + uv += FG_ADC_RR_CHG_TEMP_OFFSET_MILLI_DEGC; + *result_millidegc = uv; + + return 0; +} + +static int rradc_post_process_gpio(struct rradc_chip *chip, + struct rradc_chan_prop *prop, u16 adc_code, + int *result_mv) +{ + int64_t mv = 0; + + /* 5V ADC full scale, 10 bit */ + mv = ((int64_t)adc_code * FG_ADC_RR_GPIO_FS_RANGE); + mv = div64_s64(mv, FG_MAX_ADC_READINGS); + *result_mv = mv; + + return 0; +} + +#define RR_ADC_CHAN(_dname, _type, _mask, _scale, _lsb, _msb, _sts) \ + { \ + .datasheet_name = (_dname), \ + .type = _type, \ + .info_mask = _mask, \ + .scale = _scale, \ + .lsb = _lsb, \ + .msb = _msb, \ + .sts = _sts, \ + }, \ + +#define RR_ADC_CHAN_TEMP(_dname, _scale, mask, _lsb, _msb, _sts) \ + RR_ADC_CHAN(_dname, IIO_TEMP, \ + mask, \ + _scale, _lsb, _msb, _sts) \ + +#define RR_ADC_CHAN_VOLT(_dname, _scale, _lsb, _msb, _sts) \ + RR_ADC_CHAN(_dname, IIO_VOLTAGE, \ + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED),\ + _scale, _lsb, _msb, _sts) \ + +#define RR_ADC_CHAN_CURRENT(_dname, _scale, _lsb, _msb, _sts) \ + RR_ADC_CHAN(_dname, IIO_CURRENT, \ + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED),\ + _scale, _lsb, _msb, _sts) \ + +#define RR_ADC_CHAN_RESISTANCE(_dname, _scale, _lsb, _msb, _sts) \ + RR_ADC_CHAN(_dname, IIO_RESISTANCE, \ + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED),\ + _scale, _lsb, _msb, _sts) \ + +static const struct rradc_channels rradc_chans[] = { + RR_ADC_CHAN_RESISTANCE("batt_id", rradc_post_process_batt_id, + FG_ADC_RR_BATT_ID_5_LSB, FG_ADC_RR_BATT_ID_5_MSB, + FG_ADC_RR_BATT_ID_STS) + RR_ADC_CHAN_TEMP("batt_therm", &rradc_post_process_therm, + BIT(IIO_CHAN_INFO_RAW), + FG_ADC_RR_BATT_THERM_LSB, FG_ADC_RR_BATT_THERM_MSB, + FG_ADC_RR_BATT_THERM_STS) + RR_ADC_CHAN_TEMP("skin_temp", &rradc_post_process_therm, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED), + FG_ADC_RR_SKIN_TEMP_LSB, FG_ADC_RR_SKIN_TEMP_MSB, + FG_ADC_RR_AUX_THERM_STS) + RR_ADC_CHAN_CURRENT("usbin_i", &rradc_post_process_curr, + FG_ADC_RR_USB_IN_I_LSB, FG_ADC_RR_USB_IN_I_MSB, + FG_ADC_RR_USB_IN_I_STS) + RR_ADC_CHAN_VOLT("usbin_v", &rradc_post_process_volt, + FG_ADC_RR_USB_IN_V_LSB, FG_ADC_RR_USB_IN_V_MSB, + FG_ADC_RR_USB_IN_V_STS) + RR_ADC_CHAN_CURRENT("dcin_i", &rradc_post_process_curr, + FG_ADC_RR_DC_IN_I_LSB, FG_ADC_RR_DC_IN_I_MSB, + FG_ADC_RR_DC_IN_I_STS) + RR_ADC_CHAN_VOLT("dcin_v", &rradc_post_process_volt, + FG_ADC_RR_DC_IN_V_LSB, FG_ADC_RR_DC_IN_V_MSB, + FG_ADC_RR_DC_IN_V_STS) + RR_ADC_CHAN_TEMP("die_temp", &rradc_post_process_die_temp, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED), + FG_ADC_RR_PMI_DIE_TEMP_LSB, FG_ADC_RR_PMI_DIE_TEMP_MSB, + FG_ADC_RR_PMI_DIE_TEMP_STS) + RR_ADC_CHAN_TEMP("chg_temp", &rradc_post_process_chg_temp, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED), + FG_ADC_RR_CHARGER_TEMP_LSB, FG_ADC_RR_CHARGER_TEMP_MSB, + FG_ADC_RR_CHARGER_TEMP_STS) + RR_ADC_CHAN_VOLT("gpio", &rradc_post_process_gpio, + FG_ADC_RR_GPIO_LSB, FG_ADC_RR_GPIO_MSB, + FG_ADC_RR_GPIO_STS) + RR_ADC_CHAN_TEMP("chg_temp_hot", &rradc_post_process_chg_temp_hot, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED), + FG_ADC_RR_CHARGER_HOT, FG_ADC_RR_CHARGER_HOT, + FG_ADC_RR_CHARGER_TEMP_STS) + RR_ADC_CHAN_TEMP("chg_temp_too_hot", &rradc_post_process_chg_temp_hot, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED), + FG_ADC_RR_CHARGER_TOO_HOT, FG_ADC_RR_CHARGER_TOO_HOT, + FG_ADC_RR_CHARGER_TEMP_STS) + RR_ADC_CHAN_TEMP("skin_temp_hot", &rradc_post_process_skin_temp_hot, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED), + FG_ADC_RR_SKIN_HOT, FG_ADC_RR_SKIN_HOT, + FG_ADC_RR_AUX_THERM_STS) + RR_ADC_CHAN_TEMP("skin_temp_too_hot", &rradc_post_process_skin_temp_hot, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED), + FG_ADC_RR_SKIN_TOO_HOT, FG_ADC_RR_SKIN_TOO_HOT, + FG_ADC_RR_AUX_THERM_STS) +}; + +static int rradc_enable_continuous_mode(struct rradc_chip *chip) +{ + int rc = 0; + + /* Clear channel log */ + rc = rradc_masked_write(chip, FG_ADC_RR_ADC_LOG, + FG_ADC_RR_ADC_LOG_CLR_CTRL, + FG_ADC_RR_ADC_LOG_CLR_CTRL); + if (rc < 0) { + pr_err("log ctrl update to clear failed:%d\n", rc); + return rc; + } + + rc = rradc_masked_write(chip, FG_ADC_RR_ADC_LOG, + FG_ADC_RR_ADC_LOG_CLR_CTRL, 0); + if (rc < 0) { + pr_err("log ctrl update to not clear failed:%d\n", rc); + return rc; + } + + /* Switch to continuous mode */ + rc = rradc_masked_write(chip, FG_ADC_RR_RR_ADC_CTL, + FG_ADC_RR_ADC_CTL_CONTINUOUS_SEL_MASK, + FG_ADC_RR_ADC_CTL_CONTINUOUS_SEL); + if (rc < 0) { + pr_err("Update to continuous mode failed:%d\n", rc); + return rc; + } + + return rc; +} + +static int rradc_disable_continuous_mode(struct rradc_chip *chip) +{ + int rc = 0; + + /* Switch to non continuous mode */ + rc = rradc_masked_write(chip, FG_ADC_RR_RR_ADC_CTL, + FG_ADC_RR_ADC_CTL_CONTINUOUS_SEL_MASK, 0); + if (rc < 0) { + pr_err("Update to non-continuous mode failed:%d\n", rc); + return rc; + } + + return rc; +} + +static int rradc_check_status_ready_with_retry(struct rradc_chip *chip, + struct rradc_chan_prop *prop, u8 *buf, u16 status) +{ + int rc = 0, retry_cnt = 0, mask = 0; + + switch (prop->channel) { + case RR_ADC_BATT_ID: + /* BATT_ID STS bit does not get set initially */ + mask = FG_RR_ADC_STS_CHANNEL_STS; + break; + default: + mask = FG_RR_ADC_STS_CHANNEL_READING_MASK; + break; + } + + while (((buf[0] & mask) != mask) && + (retry_cnt < FG_RR_CONV_MAX_RETRY_CNT)) { + pr_debug("%s is not ready; nothing to read:0x%x\n", + rradc_chans[prop->channel].datasheet_name, buf[0]); + usleep_range(FG_RR_CONV_CONTINUOUS_TIME_MIN_US, + FG_RR_CONV_CONTINUOUS_TIME_MAX_US); + retry_cnt++; + rc = rradc_read(chip, status, buf, 1); + if (rc < 0) { + pr_err("status read failed:%d\n", rc); + return rc; + } + } + + if (retry_cnt >= FG_RR_CONV_MAX_RETRY_CNT) + rc = -ENODATA; + + return rc; +} + +static int rradc_read_channel_with_continuous_mode(struct rradc_chip *chip, + struct rradc_chan_prop *prop, u8 *buf) +{ + int rc = 0; + u16 status = 0; + + rc = rradc_enable_continuous_mode(chip); + if (rc < 0) { + pr_err("Failed to switch to continuous mode\n"); + return rc; + } + + status = rradc_chans[prop->channel].sts; + rc = rradc_read(chip, status, buf, 1); + if (rc < 0) { + pr_err("status read failed:%d\n", rc); + return rc; + } + + rc = rradc_check_status_ready_with_retry(chip, prop, + buf, status); + if (rc < 0) { + pr_err("Status read failed:%d\n", rc); + return rc; + } + + rc = rradc_disable_continuous_mode(chip); + if (rc < 0) { + pr_err("Failed to switch to non continuous mode\n"); + return rc; + } + + return rc; +} + +static int rradc_enable_batt_id_channel(struct rradc_chip *chip, bool enable) +{ + int rc = 0; + + if (enable) { + rc = rradc_masked_write(chip, FG_ADC_RR_BATT_ID_CTRL, + FG_ADC_RR_BATT_ID_CTRL_CHANNEL_CONV, + FG_ADC_RR_BATT_ID_CTRL_CHANNEL_CONV); + if (rc < 0) { + pr_err("Enabling BATT ID channel failed:%d\n", rc); + return rc; + } + } else { + rc = rradc_masked_write(chip, FG_ADC_RR_BATT_ID_CTRL, + FG_ADC_RR_BATT_ID_CTRL_CHANNEL_CONV, 0); + if (rc < 0) { + pr_err("Disabling BATT ID channel failed:%d\n", rc); + return rc; + } + } + + return rc; +} + +static int rradc_do_batt_id_conversion(struct rradc_chip *chip, + struct rradc_chan_prop *prop, u16 *data, u8 *buf) +{ + int rc = 0, ret = 0; + + rc = rradc_enable_batt_id_channel(chip, true); + if (rc < 0) { + pr_err("Enabling BATT ID channel failed:%d\n", rc); + return rc; + } + + rc = rradc_masked_write(chip, FG_ADC_RR_BATT_ID_TRIGGER, + FG_ADC_RR_BATT_ID_TRIGGER_CTL, + FG_ADC_RR_BATT_ID_TRIGGER_CTL); + if (rc < 0) { + pr_err("BATT_ID trigger set failed:%d\n", rc); + ret = rc; + rc = rradc_enable_batt_id_channel(chip, false); + if (rc < 0) + pr_err("Disabling BATT ID channel failed:%d\n", rc); + return ret; + } + + rc = rradc_read_channel_with_continuous_mode(chip, prop, buf); + if (rc < 0) { + pr_err("Error reading in continuous mode:%d\n", rc); + ret = rc; + } + + rc = rradc_masked_write(chip, FG_ADC_RR_BATT_ID_TRIGGER, + FG_ADC_RR_BATT_ID_TRIGGER_CTL, 0); + if (rc < 0) { + pr_err("BATT_ID trigger re-set failed:%d\n", rc); + ret = rc; + } + + rc = rradc_enable_batt_id_channel(chip, false); + if (rc < 0) { + pr_err("Disabling BATT ID channel failed:%d\n", rc); + ret = rc; + } + + return ret; +} + +static int rradc_do_conversion(struct rradc_chip *chip, + struct rradc_chan_prop *prop, u16 *data) +{ + int rc = 0, bytes_to_read = 0; + u8 buf[6]; + u16 offset = 0, batt_id_5 = 0, batt_id_15 = 0, batt_id_150 = 0; + u16 status = 0; + + mutex_lock(&chip->lock); + + switch (prop->channel) { + case RR_ADC_BATT_ID: + rc = rradc_do_batt_id_conversion(chip, prop, data, buf); + if (rc < 0) { + pr_err("Battery ID conversion failed:%d\n", rc); + goto fail; + } + break; + case RR_ADC_USBIN_V: + /* Force conversion every cycle */ + rc = rradc_masked_write(chip, FG_ADC_RR_USB_IN_V_TRIGGER, + FG_ADC_RR_USB_IN_V_EVERY_CYCLE_MASK, + FG_ADC_RR_USB_IN_V_EVERY_CYCLE); + if (rc < 0) { + pr_err("Force every cycle update failed:%d\n", rc); + goto fail; + } + + rc = rradc_read_channel_with_continuous_mode(chip, prop, buf); + if (rc < 0) { + pr_err("Error reading in continuous mode:%d\n", rc); + goto fail; + } + + /* Restore usb_in trigger */ + rc = rradc_masked_write(chip, FG_ADC_RR_USB_IN_V_TRIGGER, + FG_ADC_RR_USB_IN_V_EVERY_CYCLE_MASK, 0); + if (rc < 0) { + pr_err("Restore every cycle update failed:%d\n", rc); + goto fail; + } + break; + case RR_ADC_CHG_HOT_TEMP: + case RR_ADC_CHG_TOO_HOT_TEMP: + case RR_ADC_SKIN_HOT_TEMP: + case RR_ADC_SKIN_TOO_HOT_TEMP: + pr_debug("Read only the data registers\n"); + break; + default: + status = rradc_chans[prop->channel].sts; + rc = rradc_read(chip, status, buf, 1); + if (rc < 0) { + pr_err("status read failed:%d\n", rc); + goto fail; + } + + rc = rradc_check_status_ready_with_retry(chip, prop, + buf, status); + if (rc < 0) { + pr_debug("Status read failed:%d\n", rc); + rc = -ENODATA; + goto fail; + } + break; + } + + offset = rradc_chans[prop->channel].lsb; + if (prop->channel == RR_ADC_BATT_ID) + bytes_to_read = 6; + else if ((prop->channel == RR_ADC_CHG_HOT_TEMP) || + (prop->channel == RR_ADC_CHG_TOO_HOT_TEMP) || + (prop->channel == RR_ADC_SKIN_HOT_TEMP) || + (prop->channel == RR_ADC_SKIN_TOO_HOT_TEMP)) + bytes_to_read = 1; + else + bytes_to_read = 2; + + buf[0] = 0; + rc = rradc_read(chip, offset, buf, bytes_to_read); + if (rc) { + pr_err("read data failed\n"); + goto fail; + } + + if (prop->channel == RR_ADC_BATT_ID) { + batt_id_150 = (buf[5] << 8) | buf[4]; + batt_id_15 = (buf[3] << 8) | buf[2]; + batt_id_5 = (buf[1] << 8) | buf[0]; + if ((!batt_id_150) && (!batt_id_15) && (!batt_id_5)) { + pr_err("Invalid batt_id values with all zeros\n"); + rc = -EINVAL; + goto fail; + } + + if (batt_id_150 <= FG_ADC_RR_BATT_ID_RANGE) { + pr_debug("Batt_id_150 is chosen\n"); + *data = batt_id_150; + prop->channel_data = FG_ADC_RR_BATT_ID_150_MA; + } else if (batt_id_15 <= FG_ADC_RR_BATT_ID_RANGE) { + pr_debug("Batt_id_15 is chosen\n"); + *data = batt_id_15; + prop->channel_data = FG_ADC_RR_BATT_ID_15_MA; + } else { + pr_debug("Batt_id_5 is chosen\n"); + *data = batt_id_5; + prop->channel_data = FG_ADC_RR_BATT_ID_5_MA; + } + } else if ((prop->channel == RR_ADC_CHG_HOT_TEMP) || + (prop->channel == RR_ADC_CHG_TOO_HOT_TEMP) || + (prop->channel == RR_ADC_SKIN_HOT_TEMP) || + (prop->channel == RR_ADC_SKIN_TOO_HOT_TEMP)) { + *data = buf[0]; + } else { + *data = (buf[1] << 8) | buf[0]; + } +fail: + mutex_unlock(&chip->lock); + + return rc; +} + +static int rradc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, + long mask) +{ + struct rradc_chip *chip = iio_priv(indio_dev); + struct rradc_chan_prop *prop; + u16 adc_code; + int rc = 0; + + if (chan->address >= RR_ADC_MAX) { + pr_err("Invalid channel index:%ld\n", chan->address); + return -EINVAL; + } + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + prop = &chip->chan_props[chan->address]; + rc = rradc_do_conversion(chip, prop, &adc_code); + if (rc) + break; + + prop->scale(chip, prop, adc_code, val); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_RAW: + prop = &chip->chan_props[chan->address]; + rc = rradc_do_conversion(chip, prop, &adc_code); + if (rc) + break; + + *val = (int) adc_code; + + return IIO_VAL_INT; + default: + rc = -EINVAL; + break; + } + + return rc; +} + +static const struct iio_info rradc_info = { + .read_raw = &rradc_read_raw, + .driver_module = THIS_MODULE, +}; + +static int rradc_get_dt_data(struct rradc_chip *chip, struct device_node *node) +{ + const struct rradc_channels *rradc_chan; + struct iio_chan_spec *iio_chan; + unsigned int i = 0, base; + int rc = 0; + struct rradc_chan_prop prop; + + chip->nchannels = RR_ADC_MAX; + chip->iio_chans = devm_kcalloc(chip->dev, chip->nchannels, + sizeof(*chip->iio_chans), GFP_KERNEL); + if (!chip->iio_chans) + return -ENOMEM; + + chip->chan_props = devm_kcalloc(chip->dev, chip->nchannels, + sizeof(*chip->chan_props), GFP_KERNEL); + if (!chip->chan_props) + return -ENOMEM; + + /* Get the peripheral address */ + rc = of_property_read_u32(node, "reg", &base); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't find reg in node = %s rc = %d\n", + node->name, rc); + return rc; + } + + chip->base = base; + chip->revid_dev_node = of_parse_phandle(node, "qcom,pmic-revid", 0); + if (chip->revid_dev_node) { + chip->pmic_fab_id = get_revid_data(chip->revid_dev_node); + if (IS_ERR(chip->pmic_fab_id)) { + rc = PTR_ERR(chip->pmic_fab_id); + if (rc != -EPROBE_DEFER) + pr_err("Unable to get pmic_revid rc=%d\n", rc); + return rc; + } + + if (chip->pmic_fab_id->fab_id == -EINVAL) { + rc = chip->pmic_fab_id->fab_id; + pr_debug("Unable to read fabid rc=%d\n", rc); + } + } + + iio_chan = chip->iio_chans; + + for (i = 0; i < RR_ADC_MAX; i++) { + prop.channel = i; + prop.scale = rradc_chans[i].scale; + /* Private channel data used for selecting batt_id */ + prop.channel_data = 0; + chip->chan_props[i] = prop; + + rradc_chan = &rradc_chans[i]; + + iio_chan->channel = prop.channel; + iio_chan->datasheet_name = rradc_chan->datasheet_name; + iio_chan->extend_name = rradc_chan->datasheet_name; + iio_chan->info_mask_separate = rradc_chan->info_mask; + iio_chan->type = rradc_chan->type; + iio_chan->address = i; + iio_chan++; + } + + return 0; +} + +static int rradc_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct rradc_chip *chip; + int rc = 0; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + chip->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!chip->regmap) { + dev_err(&pdev->dev, "Couldn't get parent's regmap\n"); + return -EINVAL; + } + + chip->dev = dev; + mutex_init(&chip->lock); + + rc = rradc_get_dt_data(chip, node); + if (rc) + return rc; + + indio_dev->dev.parent = dev; + indio_dev->dev.of_node = node; + indio_dev->name = pdev->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &rradc_info; + indio_dev->channels = chip->iio_chans; + indio_dev->num_channels = chip->nchannels; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id rradc_match_table[] = { + { .compatible = "qcom,rradc" }, + { } +}; +MODULE_DEVICE_TABLE(of, rradc_match_table); + +static struct platform_driver rradc_driver = { + .driver = { + .name = "qcom-rradc", + .of_match_table = rradc_match_table, + }, + .probe = rradc_probe, +}; +module_platform_driver(rradc_driver); + +MODULE_DESCRIPTION("QPNP PMIC RR ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/qcom-tadc.c b/drivers/iio/adc/qcom-tadc.c new file mode 100644 index 000000000000..05b1985ba378 --- /dev/null +++ b/drivers/iio/adc/qcom-tadc.c @@ -0,0 +1,1324 @@ +/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "TADC: %s: " fmt, __func__ + +#include <linux/iio/iio.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/power_supply.h> +#include <linux/pmic-voter.h> + +#define USB_PRESENT_VOTER "USB_PRESENT_VOTER" +#define SLEEP_VOTER "SLEEP_VOTER" +#define SHUTDOWN_VOTER "SHUTDOWN_VOTER" +#define TADC_REVISION1_REG 0x00 +#define TADC_REVISION2_REG 0x01 +#define TADC_REVISION3_REG 0x02 +#define TADC_REVISION4_REG 0x03 +#define TADC_PERPH_TYPE_REG 0x04 +#define TADC_PERPH_SUBTYPE_REG 0x05 + +/* TADC register definitions */ +#define TADC_SW_CH_CONV_REG(chip) (chip->tadc_base + 0x06) +#define TADC_MBG_ERR_REG(chip) (chip->tadc_base + 0x07) +#define TADC_EN_CTL_REG(chip) (chip->tadc_base + 0x46) +#define TADC_CONV_REQ_REG(chip) (chip->tadc_base + 0x51) +#define TADC_HWTRIG_CONV_CH_EN_REG(chip) (chip->tadc_base + 0x52) +#define TADC_HW_SETTLE_DELAY_REG(chip) (chip->tadc_base + 0x53) +#define TADC_LONG_HW_SETTLE_DLY_EN_REG(chip) (chip->tadc_base + 0x54) +#define TADC_LONG_HW_SETTLE_DLY_REG(chip) (chip->tadc_base + 0x55) +#define TADC_ADC_BUF_CH_REG(chip) (chip->tadc_base + 0x56) +#define TADC_ADC_AAF_CH_REG(chip) (chip->tadc_base + 0x57) +#define TADC_ADC_DATA_RDBK_REG(chip) (chip->tadc_base + 0x58) +#define TADC_CH1_ADC_LO_REG(chip) (chip->tadc_base + 0x60) +#define TADC_CH1_ADC_HI_REG(chip) (chip->tadc_base + 0x61) +#define TADC_CH2_ADC_LO_REG(chip) (chip->tadc_base + 0x62) +#define TADC_CH2_ADC_HI_REG(chip) (chip->tadc_base + 0x63) +#define TADC_CH3_ADC_LO_REG(chip) (chip->tadc_base + 0x64) +#define TADC_CH3_ADC_HI_REG(chip) (chip->tadc_base + 0x65) +#define TADC_CH4_ADC_LO_REG(chip) (chip->tadc_base + 0x66) +#define TADC_CH4_ADC_HI_REG(chip) (chip->tadc_base + 0x67) +#define TADC_CH5_ADC_LO_REG(chip) (chip->tadc_base + 0x68) +#define TADC_CH5_ADC_HI_REG(chip) (chip->tadc_base + 0x69) +#define TADC_CH6_ADC_LO_REG(chip) (chip->tadc_base + 0x70) +#define TADC_CH6_ADC_HI_REG(chip) (chip->tadc_base + 0x71) +#define TADC_CH7_ADC_LO_REG(chip) (chip->tadc_base + 0x72) +#define TADC_CH7_ADC_HI_REG(chip) (chip->tadc_base + 0x73) +#define TADC_CH8_ADC_LO_REG(chip) (chip->tadc_base + 0x74) +#define TADC_CH8_ADC_HI_REG(chip) (chip->tadc_base + 0x75) +#define TADC_ADC_DIRECT_TST(chip) (chip->tadc_base + 0xE7) + +/* TADC_CMP register definitions */ +#define TADC_CMP_THR1_CMP_REG(chip) (chip->tadc_cmp_base + 0x51) +#define TADC_CMP_THR1_CH1_CMP_LO_REG(chip) (chip->tadc_cmp_base + 0x52) +#define TADC_CMP_THR1_CH1_CMP_HI_REG(chip) (chip->tadc_cmp_base + 0x53) +#define TADC_CMP_THR1_CH2_CMP_LO_REG(chip) (chip->tadc_cmp_base + 0x54) +#define TADC_CMP_THR1_CH2_CMP_HI_REG(chip) (chip->tadc_cmp_base + 0x55) +#define TADC_CMP_THR1_CH3_CMP_LO_REG(chip) (chip->tadc_cmp_base + 0x56) +#define TADC_CMP_THR1_CH3_CMP_HI_REG(chip) (chip->tadc_cmp_base + 0x57) +#define TADC_CMP_THR2_CMP_REG(chip) (chip->tadc_cmp_base + 0x67) +#define TADC_CMP_THR2_CH1_CMP_LO_REG(chip) (chip->tadc_cmp_base + 0x68) +#define TADC_CMP_THR2_CH1_CMP_HI_REG(chip) (chip->tadc_cmp_base + 0x69) +#define TADC_CMP_THR2_CH2_CMP_LO_REG(chip) (chip->tadc_cmp_base + 0x6A) +#define TADC_CMP_THR2_CH2_CMP_HI_REG(chip) (chip->tadc_cmp_base + 0x6B) +#define TADC_CMP_THR2_CH3_CMP_LO_REG(chip) (chip->tadc_cmp_base + 0x6C) +#define TADC_CMP_THR2_CH3_CMP_HI_REG(chip) (chip->tadc_cmp_base + 0x6D) +#define TADC_CMP_THR3_CMP_REG(chip) (chip->tadc_cmp_base + 0x7D) +#define TADC_CMP_THR3_CH1_CMP_LO_REG(chip) (chip->tadc_cmp_base + 0x7E) +#define TADC_CMP_THR3_CH1_CMP_HI_REG(chip) (chip->tadc_cmp_base + 0x7F) +#define TADC_CMP_THR3_CH2_CMP_LO_REG(chip) (chip->tadc_cmp_base + 0x80) +#define TADC_CMP_THR3_CH2_CMP_HI_REG(chip) (chip->tadc_cmp_base + 0x81) +#define TADC_CMP_THR3_CH3_CMP_LO_REG(chip) (chip->tadc_cmp_base + 0x82) +#define TADC_CMP_THR3_CH3_CMP_HI_REG(chip) (chip->tadc_cmp_base + 0x83) +#define TADC_CMP_THR4_CMP_REG(chip) (chip->tadc_cmp_base + 0x93) +#define TADC_CMP_THR4_CH1_CMP_LO_REG(chip) (chip->tadc_cmp_base + 0x94) +#define TADC_CMP_THR4_CH1_CMP_HI_REG(chip) (chip->tadc_cmp_base + 0x95) +#define TADC_CMP_THR1_CH1_HYST_REG(chip) (chip->tadc_cmp_base + 0xB0) +#define TADC_CMP_THR2_CH1_HYST_REG(chip) (chip->tadc_cmp_base + 0xB1) +#define TADC_CMP_THR3_CH1_HYST_REG(chip) (chip->tadc_cmp_base + 0xB2) +#define TADC_CMP_THR4_CH1_HYST_REG(chip) (chip->tadc_cmp_base + 0xB3) + +/* 10 bits of resolution */ +#define TADC_RESOLUTION 1024 +/* number of hardware channels */ +#define TADC_NUM_CH 8 + +enum tadc_chan_id { + TADC_THERM1 = 0, + TADC_THERM2, + TADC_DIE_TEMP, + TADC_BATT_I, + TADC_BATT_V, + TADC_INPUT_I, + TADC_INPUT_V, + TADC_OTG_I, + /* virtual channels */ + TADC_BATT_P, + TADC_INPUT_P, + TADC_THERM1_THR1, + TADC_THERM1_THR2, + TADC_THERM1_THR3, + TADC_THERM1_THR4, + TADC_THERM2_THR1, + TADC_THERM2_THR2, + TADC_THERM2_THR3, + TADC_DIE_TEMP_THR1, + TADC_DIE_TEMP_THR2, + TADC_DIE_TEMP_THR3, + TADC_CHAN_ID_MAX, +}; + +#define TADC_CHAN(_name, _type, _channel, _info_mask) \ +{ \ + .type = _type, \ + .channel = _channel, \ + .info_mask_separate = _info_mask, \ + .extend_name = _name, \ +} + +#define TADC_THERM_CHAN(_name, _channel) \ +TADC_CHAN(_name, IIO_TEMP, _channel, \ + BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_PROCESSED)) + +#define TADC_TEMP_CHAN(_name, _channel) \ +TADC_CHAN(_name, IIO_TEMP, _channel, \ + BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_PROCESSED) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET)) + +#define TADC_CURRENT_CHAN(_name, _channel) \ +TADC_CHAN(_name, IIO_CURRENT, _channel, \ + BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_PROCESSED) | \ + BIT(IIO_CHAN_INFO_SCALE)) + + +#define TADC_VOLTAGE_CHAN(_name, _channel) \ +TADC_CHAN(_name, IIO_VOLTAGE, _channel, \ + BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_PROCESSED) | \ + BIT(IIO_CHAN_INFO_SCALE)) + +#define TADC_POWER_CHAN(_name, _channel) \ +TADC_CHAN(_name, IIO_POWER, _channel, \ + BIT(IIO_CHAN_INFO_PROCESSED)) + +static const struct iio_chan_spec tadc_iio_chans[] = { + [TADC_THERM1] = TADC_THERM_CHAN( + "batt", TADC_THERM1), + [TADC_THERM2] = TADC_THERM_CHAN( + "skin", TADC_THERM2), + [TADC_DIE_TEMP] = TADC_TEMP_CHAN( + "die", TADC_DIE_TEMP), + [TADC_BATT_I] = TADC_CURRENT_CHAN( + "batt", TADC_BATT_I), + [TADC_BATT_V] = TADC_VOLTAGE_CHAN( + "batt", TADC_BATT_V), + [TADC_INPUT_I] = TADC_CURRENT_CHAN( + "input", TADC_INPUT_I), + [TADC_INPUT_V] = TADC_VOLTAGE_CHAN( + "input", TADC_INPUT_V), + [TADC_OTG_I] = TADC_CURRENT_CHAN( + "otg", TADC_OTG_I), + [TADC_BATT_P] = TADC_POWER_CHAN( + "batt", TADC_BATT_P), + [TADC_INPUT_P] = TADC_POWER_CHAN( + "input", TADC_INPUT_P), + [TADC_THERM1_THR1] = TADC_THERM_CHAN( + "batt_warm", TADC_THERM1_THR1), + [TADC_THERM1_THR2] = TADC_THERM_CHAN( + "batt_cool", TADC_THERM1_THR2), + [TADC_THERM1_THR3] = TADC_THERM_CHAN( + "batt_cold", TADC_THERM1_THR3), + [TADC_THERM1_THR4] = TADC_THERM_CHAN( + "batt_hot", TADC_THERM1_THR4), + [TADC_THERM2_THR1] = TADC_THERM_CHAN( + "skin_lb", TADC_THERM2_THR1), + [TADC_THERM2_THR2] = TADC_THERM_CHAN( + "skin_ub", TADC_THERM2_THR2), + [TADC_THERM2_THR3] = TADC_THERM_CHAN( + "skin_rst", TADC_THERM2_THR3), + [TADC_DIE_TEMP_THR1] = TADC_THERM_CHAN( + "die_lb", TADC_DIE_TEMP_THR1), + [TADC_DIE_TEMP_THR2] = TADC_THERM_CHAN( + "die_ub", TADC_DIE_TEMP_THR2), + [TADC_DIE_TEMP_THR3] = TADC_THERM_CHAN( + "die_rst", TADC_DIE_TEMP_THR3), +}; + +struct tadc_therm_thr { + int addr_lo; + int addr_hi; +}; + +struct tadc_chan_data { + s32 scale; + s32 offset; + u32 rbias; + const struct tadc_pt *table; + size_t tablesize; + struct tadc_therm_thr thr[4]; +}; + +struct tadc_chip { + struct device *dev; + struct regmap *regmap; + u32 tadc_base; + u32 tadc_cmp_base; + struct tadc_chan_data chans[TADC_NUM_CH]; + struct completion eoc_complete; + struct mutex write_lock; + struct mutex conv_lock; + struct power_supply *usb_psy; + struct votable *tadc_disable_votable; + struct work_struct status_change_work; + struct notifier_block nb; + u8 hwtrig_conv; +}; + +struct tadc_pt { + s32 x; + s32 y; +}; + +/* + * Thermistor tables are generated by the B-parameter equation which is a + * simplifed version of the Steinhart-Hart equation. + * + * (1 / T) = (1 / T0) + (1 / B) * ln(R / R0) + * + * Where R0 is the resistance at temperature T0, and T0 is typically room + * temperature (25C). + */ +static const struct tadc_pt tadc_therm_3450b_68k[] = { + { 4151, 120000 }, + { 4648, 115000 }, + { 5220, 110000 }, + { 5880, 105000 }, + { 6644, 100000 }, + { 7533, 95000 }, + { 8571, 90000 }, + { 9786, 85000 }, + { 11216, 80000 }, + { 12906, 75000 }, + { 14910, 70000 }, + { 17300, 65000 }, + { 20163, 60000 }, + { 23609, 55000 }, + { 27780, 50000 }, + { 32855, 45000 }, + { 39065, 40000 }, + { 46712, 35000 }, + { 56185, 30000 }, + { 68000, 25000 }, + { 82837, 20000 }, + { 101604, 15000 }, + { 125525, 10000 }, + { 156261, 5000 }, + { 196090, 0 }, + { 248163, -5000 }, + { 316887, -10000 }, + { 408493, -15000 }, + { 531889, -20000 }, + { 699966, -25000 }, + { 931618, -30000 }, + { 1254910, -35000 }, + { 1712127, -40000 }, +}; + +static bool tadc_is_reg_locked(struct tadc_chip *chip, u16 reg) +{ + if ((reg & 0xFF00) == chip->tadc_cmp_base) + return true; + + if (reg >= TADC_HWTRIG_CONV_CH_EN_REG(chip)) + return true; + + return false; +} + +static int tadc_read(struct tadc_chip *chip, u16 reg, u8 *val, size_t count) +{ + int rc = 0; + + rc = regmap_bulk_read(chip->regmap, reg, val, count); + if (rc < 0) + pr_err("Couldn't read 0x%04x rc=%d\n", reg, rc); + + return rc; +} + +static int tadc_write(struct tadc_chip *chip, u16 reg, u8 data) +{ + int rc = 0; + + mutex_lock(&chip->write_lock); + if (tadc_is_reg_locked(chip, reg)) { + rc = regmap_write(chip->regmap, (reg & 0xFF00) | 0xD0, 0xA5); + if (rc < 0) { + pr_err("Couldn't unlock secure register rc=%d\n", rc); + goto unlock; + } + } + + rc = regmap_write(chip->regmap, reg, data); + if (rc < 0) { + pr_err("Couldn't write 0x%02x to 0x%04x rc=%d\n", + data, reg, rc); + goto unlock; + } + +unlock: + mutex_unlock(&chip->write_lock); + return rc; +} +static int tadc_bulk_write(struct tadc_chip *chip, u16 reg, u8 *data, + size_t count) +{ + int rc = 0, i; + + mutex_lock(&chip->write_lock); + for (i = 0; i < count; ++i, ++reg) { + if (tadc_is_reg_locked(chip, reg)) { + rc = regmap_write(chip->regmap, + (reg & 0xFF00) | 0xD0, 0xA5); + if (rc < 0) { + pr_err("Couldn't unlock secure register rc=%d\n", + rc); + goto unlock; + } + } + + rc = regmap_write(chip->regmap, reg, data[i]); + if (rc < 0) { + pr_err("Couldn't write 0x%02x to 0x%04x rc=%d\n", + data[i], reg, rc); + goto unlock; + } + } + +unlock: + mutex_unlock(&chip->write_lock); + return rc; +} + +static int tadc_masked_write(struct tadc_chip *chip, u16 reg, u8 mask, u8 data) +{ + int rc = 0; + + mutex_lock(&chip->write_lock); + if (tadc_is_reg_locked(chip, reg)) { + rc = regmap_write(chip->regmap, (reg & 0xFF00) | 0xD0, 0xA5); + if (rc < 0) { + pr_err("Couldn't unlock secure register rc=%d\n", rc); + goto unlock; + } + } + + rc = regmap_update_bits(chip->regmap, reg, mask, data); + +unlock: + mutex_unlock(&chip->write_lock); + return rc; +} + +static int tadc_lerp(const struct tadc_pt *pts, size_t size, bool inv, + s32 input, s32 *output) +{ + int i; + s64 temp; + bool ascending; + + if (pts == NULL) { + pr_err("Table is NULL\n"); + return -EINVAL; + } + + if (size < 1) { + pr_err("Table has no entries\n"); + return -ENOENT; + } + + if (size == 1) { + *output = inv ? pts[0].x : pts[0].y; + return 0; + } + + ascending = inv ? (pts[0].y < pts[1].y) : (pts[0].x < pts[1].x); + if (ascending ? (input <= (inv ? pts[0].y : pts[0].x)) : + (input >= (inv ? pts[0].y : pts[0].x))) { + *output = inv ? pts[0].x : pts[0].y; + return 0; + } + + if (ascending ? (input >= (inv ? pts[size - 1].y : pts[size - 1].x)) : + (input <= (inv ? pts[size - 1].y : pts[size - 1].x))) { + *output = inv ? pts[size - 1].x : pts[size - 1].y; + return 0; + } + + for (i = 1; i < size; i++) + if (ascending ? (input <= (inv ? pts[i].y : pts[i].x)) : + (input >= (inv ? pts[i].y : pts[i].x))) + break; + + if (inv) { + temp = (s64)(pts[i].x - pts[i - 1].x) * + (s64)(input - pts[i - 1].y); + temp = div_s64(temp, pts[i].y - pts[i - 1].y); + *output = temp + pts[i - 1].x; + } else { + temp = (s64)(pts[i].y - pts[i - 1].y) * + (s64)(input - pts[i - 1].x); + temp = div_s64(temp, pts[i].x - pts[i - 1].x); + *output = temp + pts[i - 1].y; + } + + return 0; +} + +/* + * Process the result of a thermistor reading. + * + * The voltage input to the ADC is a result of a voltage divider circuit. + * Vout = (Rtherm / (Rbias + Rtherm)) * Vbias + * + * The ADC value is based on the output voltage of the voltage divider, and the + * bias voltage. + * ADC = (Vin * 1024) / Vbias + * + * Combine these equations and solve for Rtherm + * Rtherm = (ADC * Rbias) / (1024 - ADC) + */ +static int tadc_get_processed_therm(const struct tadc_chan_data *chan_data, + s16 adc, s32 *result) +{ + s32 rtherm; + + rtherm = div_s64((s64)adc * chan_data->rbias, TADC_RESOLUTION - adc); + return tadc_lerp(chan_data->table, chan_data->tablesize, false, rtherm, + result); +} + +static int tadc_get_raw_therm(const struct tadc_chan_data *chan_data, + int mdegc, int *result) +{ + int rc; + s32 rtherm; + + rc = tadc_lerp(chan_data->table, chan_data->tablesize, true, mdegc, + &rtherm); + if (rc < 0) { + pr_err("Couldn't interpolate %d\n rc=%d", mdegc, rc); + return rc; + } + + *result = div64_s64((s64)rtherm * TADC_RESOLUTION, + (s64)chan_data->rbias + rtherm); + return 0; +} + +static int tadc_read_channel(struct tadc_chip *chip, u16 address, int *adc) +{ + u8 val[2]; + int rc; + + rc = tadc_read(chip, address, val, ARRAY_SIZE(val)); + if (rc < 0) { + pr_err("Couldn't read channel rc=%d\n", rc); + return rc; + } + + /* the 10th bit is the sign bit for all channels */ + *adc = sign_extend32(val[0] | val[1] << BITS_PER_BYTE, 10); + return rc; +} + +static int tadc_write_channel(struct tadc_chip *chip, u16 address, int adc) +{ + u8 val[2]; + int rc; + + /* the 10th bit is the sign bit for all channels */ + adc = sign_extend32(adc, 10); + val[0] = (u8)adc; + val[1] = (u8)(adc >> BITS_PER_BYTE); + rc = tadc_bulk_write(chip, address, val, 2); + if (rc < 0) { + pr_err("Couldn't write to channel rc=%d\n", rc); + return rc; + } + + return rc; +} + +#define CONVERSION_TIMEOUT_MS 100 +static int tadc_do_conversion(struct tadc_chip *chip, u8 channels, s16 *adc) +{ + unsigned long timeout, timeleft; + u8 val[TADC_NUM_CH * 2]; + int rc = 0, i; + + mutex_lock(&chip->conv_lock); + rc = tadc_read(chip, TADC_MBG_ERR_REG(chip), val, 1); + if (rc < 0) { + pr_err("Couldn't read mbg error status rc=%d\n", rc); + goto unlock; + } + + reinit_completion(&chip->eoc_complete); + + if (get_effective_result(chip->tadc_disable_votable)) { + /* leave it back in completed state */ + complete_all(&chip->eoc_complete); + rc = -ENODATA; + goto unlock; + } + + if (val[0] != 0) { + tadc_write(chip, TADC_EN_CTL_REG(chip), 0); + tadc_write(chip, TADC_EN_CTL_REG(chip), 0x80); + } + + rc = tadc_write(chip, TADC_CONV_REQ_REG(chip), channels); + if (rc < 0) { + pr_err("Couldn't write conversion request rc=%d\n", rc); + goto unlock; + } + + timeout = msecs_to_jiffies(CONVERSION_TIMEOUT_MS); + timeleft = wait_for_completion_timeout(&chip->eoc_complete, timeout); + + if (timeleft == 0) { + rc = tadc_read(chip, TADC_SW_CH_CONV_REG(chip), val, 1); + if (rc < 0) { + pr_err("Couldn't read conversion status rc=%d\n", rc); + goto unlock; + } + + /* + * check one last time if the channel we are requesting + * has completed conversion + */ + if (val[0] != channels) { + rc = -ETIMEDOUT; + goto unlock; + } + } + + rc = tadc_read(chip, TADC_CH1_ADC_LO_REG(chip), val, ARRAY_SIZE(val)); + if (rc < 0) { + pr_err("Couldn't read adc channels rc=%d\n", rc); + goto unlock; + } + + for (i = 0; i < TADC_NUM_CH; i++) + adc[i] = (s16)(val[i * 2] | (u16)val[i * 2 + 1] << 8); + + pr_debug("Conversion time for channels 0x%x = %dms\n", channels, + jiffies_to_msecs(timeout - timeleft)); + +unlock: + mutex_unlock(&chip->conv_lock); + return rc; +} + +static int tadc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, + long mask) +{ + struct tadc_chip *chip = iio_priv(indio_dev); + struct tadc_chan_data *chan_data = NULL; + int rc, offset = 0, scale, scale2, scale_type; + s16 adc[TADC_NUM_CH]; + + switch (chan->channel) { + case TADC_THERM1_THR1: + case TADC_THERM1_THR2: + case TADC_THERM1_THR3: + case TADC_THERM1_THR4: + chan_data = &chip->chans[TADC_THERM1]; + break; + case TADC_THERM2_THR1: + case TADC_THERM2_THR2: + case TADC_THERM2_THR3: + chan_data = &chip->chans[TADC_THERM2]; + break; + case TADC_DIE_TEMP_THR1: + case TADC_DIE_TEMP_THR2: + case TADC_DIE_TEMP_THR3: + chan_data = &chip->chans[TADC_DIE_TEMP]; + break; + default: + if (chan->channel >= ARRAY_SIZE(chip->chans)) { + pr_err("Channel %d is out of bounds\n", chan->channel); + return -EINVAL; + } + + chan_data = &chip->chans[chan->channel]; + break; + } + + if (!chan_data) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->channel) { + case TADC_THERM1_THR1: + case TADC_THERM2_THR1: + case TADC_DIE_TEMP_THR1: + rc = tadc_read_channel(chip, + chan_data->thr[0].addr_lo, val); + break; + case TADC_THERM1_THR2: + case TADC_THERM2_THR2: + case TADC_DIE_TEMP_THR2: + rc = tadc_read_channel(chip, + chan_data->thr[1].addr_lo, val); + break; + case TADC_THERM1_THR3: + case TADC_THERM2_THR3: + case TADC_DIE_TEMP_THR3: + rc = tadc_read_channel(chip, + chan_data->thr[2].addr_lo, val); + break; + case TADC_THERM1_THR4: + rc = tadc_read_channel(chip, + chan_data->thr[3].addr_lo, val); + break; + default: + rc = tadc_do_conversion(chip, BIT(chan->channel), adc); + if (rc < 0) { + if (rc != -ENODATA) + pr_err("Couldn't read battery current and voltage channels rc=%d\n", + rc); + return rc; + } + *val = adc[chan->channel]; + break; + } + + if (rc < 0 && rc != -ENODATA) { + pr_err("Couldn't read channel %d\n", chan->channel); + return rc; + } + + return IIO_VAL_INT; + case IIO_CHAN_INFO_PROCESSED: + switch (chan->channel) { + case TADC_THERM1: + case TADC_THERM2: + case TADC_THERM1_THR1: + case TADC_THERM1_THR2: + case TADC_THERM1_THR3: + case TADC_THERM1_THR4: + case TADC_THERM2_THR1: + case TADC_THERM2_THR2: + case TADC_THERM2_THR3: + rc = tadc_read_raw(indio_dev, chan, val, NULL, + IIO_CHAN_INFO_RAW); + if (rc < 0) + return rc; + + rc = tadc_get_processed_therm(chan_data, *val, val); + if (rc < 0) { + pr_err("Couldn't process 0x%04x from channel %d rc=%d\n", + *val, chan->channel, rc); + return rc; + } + break; + case TADC_BATT_P: + rc = tadc_do_conversion(chip, + BIT(TADC_BATT_I) | BIT(TADC_BATT_V), adc); + if (rc < 0 && rc != -ENODATA) { + pr_err("Couldn't read battery current and voltage channels rc=%d\n", + rc); + return rc; + } + + *val = adc[TADC_BATT_I] * adc[TADC_BATT_V]; + break; + case TADC_INPUT_P: + rc = tadc_do_conversion(chip, + BIT(TADC_INPUT_I) | BIT(TADC_INPUT_V), adc); + if (rc < 0 && rc != -ENODATA) { + pr_err("Couldn't read input current and voltage channels rc=%d\n", + rc); + return rc; + } + + *val = adc[TADC_INPUT_I] * adc[TADC_INPUT_V]; + break; + default: + rc = tadc_read_raw(indio_dev, chan, val, NULL, + IIO_CHAN_INFO_RAW); + if (rc < 0) + return rc; + + /* offset is optional */ + rc = tadc_read_raw(indio_dev, chan, &offset, NULL, + IIO_CHAN_INFO_OFFSET); + if (rc < 0) + return rc; + + scale_type = tadc_read_raw(indio_dev, chan, + &scale, &scale2, IIO_CHAN_INFO_SCALE); + switch (scale_type) { + case IIO_VAL_INT: + *val = *val * scale + offset; + break; + case IIO_VAL_FRACTIONAL: + *val = div_s64((s64)*val * scale + offset, + scale2); + break; + default: + return -EINVAL; + } + break; + } + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->channel) { + case TADC_DIE_TEMP: + case TADC_DIE_TEMP_THR1: + case TADC_DIE_TEMP_THR2: + case TADC_DIE_TEMP_THR3: + *val = chan_data->scale; + return IIO_VAL_INT; + case TADC_BATT_I: + case TADC_BATT_V: + case TADC_INPUT_I: + case TADC_INPUT_V: + case TADC_OTG_I: + *val = chan_data->scale; + *val2 = TADC_RESOLUTION; + return IIO_VAL_FRACTIONAL; + } + + return -EINVAL; + case IIO_CHAN_INFO_OFFSET: + *val = chan_data->offset; + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static int tadc_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, + long mask) +{ + struct tadc_chip *chip = iio_priv(indio_dev); + const struct tadc_chan_data *chan_data; + int rc, raw; + s32 rem; + + switch (chan->channel) { + case TADC_THERM1_THR1: + case TADC_THERM1_THR2: + case TADC_THERM1_THR3: + case TADC_THERM1_THR4: + chan_data = &chip->chans[TADC_THERM1]; + break; + case TADC_THERM2_THR1: + case TADC_THERM2_THR2: + case TADC_THERM2_THR3: + chan_data = &chip->chans[TADC_THERM2]; + break; + case TADC_DIE_TEMP_THR1: + case TADC_DIE_TEMP_THR2: + case TADC_DIE_TEMP_THR3: + chan_data = &chip->chans[TADC_DIE_TEMP]; + break; + default: + if (chan->channel >= ARRAY_SIZE(chip->chans)) { + pr_err("Channel %d is out of bounds\n", chan->channel); + return -EINVAL; + } + + chan_data = &chip->chans[chan->channel]; + break; + } + + if (!chan_data) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + switch (chan->channel) { + case TADC_THERM1_THR1: + case TADC_THERM1_THR2: + case TADC_THERM1_THR3: + case TADC_THERM1_THR4: + case TADC_THERM2_THR1: + case TADC_THERM2_THR2: + case TADC_THERM2_THR3: + rc = tadc_get_raw_therm(chan_data, val, &raw); + if (rc < 0) { + pr_err("Couldn't get raw value rc=%d\n", rc); + return rc; + } + break; + case TADC_DIE_TEMP_THR1: + case TADC_DIE_TEMP_THR2: + case TADC_DIE_TEMP_THR3: + /* DIV_ROUND_CLOSEST does not like negative numbers */ + raw = div_s64_rem(val - chan_data->offset, + chan_data->scale, &rem); + if (abs(rem) >= abs(chan_data->scale / 2)) + raw++; + break; + default: + return -EINVAL; + } + + rc = tadc_write_raw(indio_dev, chan, raw, 0, + IIO_CHAN_INFO_RAW); + if (rc < 0) { + pr_err("Couldn't write raw rc=%d\n", rc); + return rc; + } + + break; + case IIO_CHAN_INFO_RAW: + switch (chan->channel) { + case TADC_THERM1_THR1: + case TADC_THERM2_THR1: + case TADC_DIE_TEMP_THR1: + rc = tadc_write_channel(chip, + chan_data->thr[0].addr_lo, val); + break; + case TADC_THERM1_THR2: + case TADC_THERM2_THR2: + case TADC_DIE_TEMP_THR2: + rc = tadc_write_channel(chip, + chan_data->thr[1].addr_lo, val); + break; + case TADC_THERM1_THR3: + case TADC_THERM2_THR3: + case TADC_DIE_TEMP_THR3: + rc = tadc_write_channel(chip, + chan_data->thr[2].addr_lo, val); + break; + case TADC_THERM1_THR4: + rc = tadc_write_channel(chip, + chan_data->thr[3].addr_lo, val); + break; + default: + return -EINVAL; + } + + if (rc < 0) { + pr_err("Couldn't write channel %d\n", chan->channel); + return rc; + } + + break; + default: + return -EINVAL; + } + + return 0; +} + +static irqreturn_t handle_eoc(int irq, void *dev_id) +{ + struct tadc_chip *chip = dev_id; + + complete_all(&chip->eoc_complete); + return IRQ_HANDLED; +} + +static int tadc_disable_vote_callback(struct votable *votable, + void *data, int disable, const char *client) +{ + struct tadc_chip *chip = data; + int rc; + int timeout; + unsigned long timeleft; + + if (disable) { + timeout = msecs_to_jiffies(CONVERSION_TIMEOUT_MS); + timeleft = wait_for_completion_timeout(&chip->eoc_complete, + timeout); + if (timeleft == 0) + pr_err("Timed out waiting for eoc, disabling hw conversions regardless\n"); + + rc = tadc_read(chip, TADC_HWTRIG_CONV_CH_EN_REG(chip), + &chip->hwtrig_conv, 1); + if (rc < 0) { + pr_err("Couldn't save hw conversions rc=%d\n", rc); + return rc; + } + rc = tadc_write(chip, TADC_HWTRIG_CONV_CH_EN_REG(chip), 0x00); + if (rc < 0) { + pr_err("Couldn't disable hw conversions rc=%d\n", rc); + return rc; + } + rc = tadc_write(chip, TADC_ADC_DIRECT_TST(chip), 0x80); + if (rc < 0) { + pr_err("Couldn't enable direct test mode rc=%d\n", rc); + return rc; + } + } else { + rc = tadc_write(chip, TADC_ADC_DIRECT_TST(chip), 0x00); + if (rc < 0) { + pr_err("Couldn't disable direct test mode rc=%d\n", rc); + return rc; + } + rc = tadc_write(chip, TADC_HWTRIG_CONV_CH_EN_REG(chip), + chip->hwtrig_conv); + if (rc < 0) { + pr_err("Couldn't restore hw conversions rc=%d\n", rc); + return rc; + } + } + + pr_debug("client: %s disable: %d\n", client, disable); + return 0; +} + +static void status_change_work(struct work_struct *work) +{ + struct tadc_chip *chip = container_of(work, + struct tadc_chip, status_change_work); + union power_supply_propval pval = {0, }; + int rc; + + if (!chip->usb_psy) + chip->usb_psy = power_supply_get_by_name("usb"); + + if (!chip->usb_psy) { + /* treat usb is not present */ + vote(chip->tadc_disable_votable, USB_PRESENT_VOTER, true, 0); + return; + } + + rc = power_supply_get_property(chip->usb_psy, + POWER_SUPPLY_PROP_PRESENT, &pval); + if (rc < 0) { + pr_err("Couldn't get present status rc=%d\n", rc); + /* treat usb is not present */ + vote(chip->tadc_disable_votable, USB_PRESENT_VOTER, true, 0); + return; + } + + /* disable if usb is not present */ + vote(chip->tadc_disable_votable, USB_PRESENT_VOTER, !pval.intval, 0); +} + +static int tadc_notifier_call(struct notifier_block *nb, + unsigned long ev, void *v) +{ + struct power_supply *psy = v; + struct tadc_chip *chip = container_of(nb, struct tadc_chip, nb); + + if (ev != PSY_EVENT_PROP_CHANGED) + return NOTIFY_OK; + + if ((strcmp(psy->desc->name, "usb") == 0)) + schedule_work(&chip->status_change_work); + + return NOTIFY_OK; +} + +static int tadc_register_notifier(struct tadc_chip *chip) +{ + int rc; + + chip->nb.notifier_call = tadc_notifier_call; + rc = power_supply_reg_notifier(&chip->nb); + if (rc < 0) { + pr_err("Couldn't register psy notifier rc = %d\n", rc); + return rc; + } + + return 0; +} + +static int tadc_suspend(struct device *dev) +{ + struct tadc_chip *chip = dev_get_drvdata(dev); + + vote(chip->tadc_disable_votable, SLEEP_VOTER, true, 0); + return 0; +} + +static int tadc_resume(struct device *dev) +{ + struct tadc_chip *chip = dev_get_drvdata(dev); + + vote(chip->tadc_disable_votable, SLEEP_VOTER, false, 0); + return 0; +} + +static int tadc_set_therm_table(struct tadc_chan_data *chan_data, u32 beta, + u32 rtherm) +{ + if (beta == 3450 && rtherm == 68000) { + chan_data->table = tadc_therm_3450b_68k; + chan_data->tablesize = ARRAY_SIZE(tadc_therm_3450b_68k); + return 0; + } + + return -ENOENT; +} + +static int tadc_parse_dt(struct tadc_chip *chip) +{ + struct device_node *child, *node; + struct tadc_chan_data *chan_data; + u32 chan_id, rtherm, beta; + int rc = 0; + + node = chip->dev->of_node; + for_each_available_child_of_node(node, child) { + rc = of_property_read_u32(child, "reg", &chan_id); + if (rc < 0) { + pr_err("Couldn't find channel for %s rc=%d", + child->name, rc); + return rc; + } + + if (chan_id > TADC_NUM_CH - 1) { + pr_err("Channel %d is out of range [0, %d]\n", + chan_id, TADC_NUM_CH - 1); + return -EINVAL; + } + + chan_data = &chip->chans[chan_id]; + if (chan_id == TADC_THERM1 || chan_id == TADC_THERM2) { + rc = of_property_read_u32(child, + "qcom,rbias", &chan_data->rbias); + if (rc < 0) { + pr_err("Couldn't read qcom,rbias rc=%d\n", rc); + return rc; + } + + rc = of_property_read_u32(child, + "qcom,beta-coefficient", &beta); + if (rc < 0) { + pr_err("Couldn't read qcom,beta-coefficient rc=%d\n", + rc); + return rc; + } + + rc = of_property_read_u32(child, + "qcom,rtherm-at-25degc", &rtherm); + if (rc < 0) { + pr_err("Couldn't read qcom,rtherm-at-25degc rc=%d\n", + rc); + return rc; + } + + rc = tadc_set_therm_table(chan_data, beta, rtherm); + if (rc < 0) { + pr_err("Couldn't set therm table rc=%d\n", rc); + return rc; + } + } else { + rc = of_property_read_s32(child, "qcom,scale", + &chan_data->scale); + if (rc < 0) { + pr_err("Couldn't read scale rc=%d\n", rc); + return rc; + } + + of_property_read_s32(child, "qcom,offset", + &chan_data->offset); + } + } + + return rc; +} + +static int tadc_init_hw(struct tadc_chip *chip) +{ + int rc; + + chip->chans[TADC_THERM1].thr[0].addr_lo = + TADC_CMP_THR1_CH1_CMP_LO_REG(chip); + chip->chans[TADC_THERM1].thr[0].addr_hi = + TADC_CMP_THR1_CH1_CMP_HI_REG(chip); + chip->chans[TADC_THERM1].thr[1].addr_lo = + TADC_CMP_THR2_CH1_CMP_LO_REG(chip); + chip->chans[TADC_THERM1].thr[1].addr_hi = + TADC_CMP_THR2_CH1_CMP_HI_REG(chip); + chip->chans[TADC_THERM1].thr[2].addr_lo = + TADC_CMP_THR3_CH1_CMP_LO_REG(chip); + chip->chans[TADC_THERM1].thr[2].addr_hi = + TADC_CMP_THR3_CH1_CMP_HI_REG(chip); + chip->chans[TADC_THERM1].thr[3].addr_lo = + TADC_CMP_THR4_CH1_CMP_LO_REG(chip); + chip->chans[TADC_THERM1].thr[3].addr_hi = + TADC_CMP_THR4_CH1_CMP_HI_REG(chip); + + chip->chans[TADC_THERM2].thr[0].addr_lo = + TADC_CMP_THR1_CH2_CMP_LO_REG(chip); + chip->chans[TADC_THERM2].thr[0].addr_hi = + TADC_CMP_THR1_CH2_CMP_HI_REG(chip); + chip->chans[TADC_THERM2].thr[1].addr_lo = + TADC_CMP_THR2_CH2_CMP_LO_REG(chip); + chip->chans[TADC_THERM2].thr[1].addr_hi = + TADC_CMP_THR2_CH2_CMP_HI_REG(chip); + chip->chans[TADC_THERM2].thr[2].addr_lo = + TADC_CMP_THR3_CH2_CMP_LO_REG(chip); + chip->chans[TADC_THERM2].thr[2].addr_hi = + TADC_CMP_THR3_CH2_CMP_HI_REG(chip); + + chip->chans[TADC_DIE_TEMP].thr[0].addr_lo = + TADC_CMP_THR1_CH3_CMP_LO_REG(chip); + chip->chans[TADC_DIE_TEMP].thr[0].addr_hi = + TADC_CMP_THR1_CH3_CMP_HI_REG(chip); + chip->chans[TADC_DIE_TEMP].thr[1].addr_lo = + TADC_CMP_THR2_CH3_CMP_LO_REG(chip); + chip->chans[TADC_DIE_TEMP].thr[1].addr_hi = + TADC_CMP_THR2_CH3_CMP_HI_REG(chip); + chip->chans[TADC_DIE_TEMP].thr[2].addr_lo = + TADC_CMP_THR3_CH3_CMP_LO_REG(chip); + chip->chans[TADC_DIE_TEMP].thr[2].addr_hi = + TADC_CMP_THR3_CH3_CMP_HI_REG(chip); + + rc = tadc_write(chip, TADC_CMP_THR1_CMP_REG(chip), 0); + if (rc < 0) { + pr_err("Couldn't enable hardware triggers rc=%d\n", rc); + return rc; + } + + rc = tadc_write(chip, TADC_CMP_THR2_CMP_REG(chip), 0); + if (rc < 0) { + pr_err("Couldn't enable hardware triggers rc=%d\n", rc); + return rc; + } + + rc = tadc_write(chip, TADC_CMP_THR3_CMP_REG(chip), 0); + if (rc < 0) { + pr_err("Couldn't enable hardware triggers rc=%d\n", rc); + return rc; + } + + /* enable connector and die temp hardware triggers */ + rc = tadc_masked_write(chip, TADC_HWTRIG_CONV_CH_EN_REG(chip), + BIT(TADC_THERM2) | BIT(TADC_DIE_TEMP), + BIT(TADC_THERM2) | BIT(TADC_DIE_TEMP)); + if (rc < 0) { + pr_err("Couldn't enable hardware triggers rc=%d\n", rc); + return rc; + } + + /* save hw triggered conversion configuration */ + rc = tadc_read(chip, TADC_HWTRIG_CONV_CH_EN_REG(chip), + &chip->hwtrig_conv, 1); + if (rc < 0) { + pr_err("Couldn't save hw conversions rc=%d\n", rc); + return rc; + } + + return 0; +} + +static const struct iio_info tadc_info = { + .read_raw = &tadc_read_raw, + .write_raw = &tadc_write_raw, + .driver_module = THIS_MODULE, +}; + +static int tadc_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct iio_dev *indio_dev; + struct tadc_chip *chip; + int rc, irq; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + chip->dev = &pdev->dev; + init_completion(&chip->eoc_complete); + + /* + * set the completion in "completed" state so disable of the tadc + * can progress + */ + complete_all(&chip->eoc_complete); + + rc = of_property_read_u32(node, "reg", &chip->tadc_base); + if (rc < 0) { + pr_err("Couldn't read base address rc=%d\n", rc); + return rc; + } + chip->tadc_cmp_base = chip->tadc_base + 0x100; + + mutex_init(&chip->write_lock); + mutex_init(&chip->conv_lock); + INIT_WORK(&chip->status_change_work, status_change_work); + chip->regmap = dev_get_regmap(chip->dev->parent, NULL); + if (!chip->regmap) { + pr_err("Couldn't get regmap\n"); + return -ENODEV; + } + + rc = tadc_parse_dt(chip); + if (rc < 0) { + pr_err("Couldn't parse device tree rc=%d\n", rc); + return rc; + } + + rc = tadc_init_hw(chip); + if (rc < 0) { + pr_err("Couldn't initialize hardware rc=%d\n", rc); + return rc; + } + + chip->tadc_disable_votable = create_votable("SMB_TADC_DISABLE", + VOTE_SET_ANY, + tadc_disable_vote_callback, + chip); + if (IS_ERR(chip->tadc_disable_votable)) { + rc = PTR_ERR(chip->tadc_disable_votable); + return rc; + } + /* assume usb is not present */ + vote(chip->tadc_disable_votable, USB_PRESENT_VOTER, true, 0); + vote(chip->tadc_disable_votable, SHUTDOWN_VOTER, false, 0); + vote(chip->tadc_disable_votable, SLEEP_VOTER, false, 0); + + rc = tadc_register_notifier(chip); + if (rc < 0) { + pr_err("Couldn't register notifier=%d\n", rc); + goto destroy_votable; + } + + irq = of_irq_get_byname(node, "eoc"); + if (irq < 0) { + pr_err("Couldn't get eoc irq rc=%d\n", irq); + goto destroy_votable; + } + + rc = devm_request_threaded_irq(chip->dev, irq, NULL, handle_eoc, + IRQF_ONESHOT, "eoc", chip); + if (rc < 0) { + pr_err("Couldn't request irq %d rc=%d\n", irq, rc); + goto destroy_votable; + } + + indio_dev->dev.parent = chip->dev; + indio_dev->name = pdev->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &tadc_info; + indio_dev->channels = tadc_iio_chans; + indio_dev->num_channels = ARRAY_SIZE(tadc_iio_chans); + + rc = devm_iio_device_register(chip->dev, indio_dev); + if (rc < 0) { + pr_err("Couldn't register IIO device rc=%d\n", rc); + goto destroy_votable; + } + + platform_set_drvdata(pdev, chip); + return 0; + +destroy_votable: + destroy_votable(chip->tadc_disable_votable); + return rc; +} + +static int tadc_remove(struct platform_device *pdev) +{ + struct tadc_chip *chip = platform_get_drvdata(pdev); + + destroy_votable(chip->tadc_disable_votable); + return 0; +} + +static void tadc_shutdown(struct platform_device *pdev) +{ + struct tadc_chip *chip = platform_get_drvdata(pdev); + + vote(chip->tadc_disable_votable, SHUTDOWN_VOTER, true, 0); +} + +static const struct dev_pm_ops tadc_pm_ops = { + .resume = tadc_resume, + .suspend = tadc_suspend, +}; + +static const struct of_device_id tadc_match_table[] = { + { .compatible = "qcom,tadc" }, + { } +}; +MODULE_DEVICE_TABLE(of, tadc_match_table); + +static struct platform_driver tadc_driver = { + .driver = { + .name = "qcom-tadc", + .of_match_table = tadc_match_table, + .pm = &tadc_pm_ops, + }, + .probe = tadc_probe, + .remove = tadc_remove, + .shutdown = tadc_shutdown, +}; +module_platform_driver(tadc_driver); + +MODULE_DESCRIPTION("Qualcomm Technologies Inc. TADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c index 217e9306aa0f..407b2ef4d2e9 100644 --- a/drivers/iio/inkern.c +++ b/drivers/iio/inkern.c @@ -664,3 +664,21 @@ err_unlock: return ret; } EXPORT_SYMBOL_GPL(iio_write_channel_raw); + +int iio_write_channel_processed(struct iio_channel *chan, int val) +{ + int ret; + + mutex_lock(&chan->indio_dev->info_exist_lock); + if (chan->indio_dev->info == NULL) { + ret = -ENODEV; + goto err_unlock; + } + + ret = iio_channel_write(chan, val, 0, IIO_CHAN_INFO_PROCESSED); +err_unlock: + mutex_unlock(&chan->indio_dev->info_exist_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(iio_write_channel_processed); |