From 9d4f6e0a3a93e8a821b213f803cff4472164bbca Mon Sep 17 00:00:00 2001 From: Jishnu Prakash Date: Tue, 2 Mar 2021 11:12:21 +0530 Subject: [PATCH] iio: adc: Add QCOM SPMI PMIC5 GEN3 ADC driver Add support for QCOM SPMI PMIC5 GEN3 ADC driver that supports hardware based offset and gain compensation. The ADC peripheral can measure both voltage and current channels whose input signal is connected to the PMIC ADC AMUX. The PMIC5 GEN3 ADC peripheral uses registers defined in SDAM. The register set and configuration have been refreshed compared to the prior QCOM PMIC7 ADC family. Change-Id: I3512c3c067d9102406aa92c389d66283f0b12103 Signed-off-by: Jishnu Prakash [quic_subbaram@quicinc.com: Fixed merge conflicts in qcom-vadc-common.h due to location change; Made changes in qcom-vadc-common.c to re-use existing qcom_vadc_map_temp_voltage(); Added entire include/linux/adc-tm-clients.h as it doesn't exist anymore but needed to compile qcom-spmi-adc5-gen3.c and removed the APIs used when CONFIG_QTI_ADC_TM is enabled.] Signed-off-by: Subbaraman Narayanamurthy [quic_collinsd@quicinc.com: changed struct vadc_prescale_ratio to struct u32_fract and changed prescale "num" and "den" to "numerator" and "denominator" respectively] Signed-off-by: David Collins --- drivers/iio/adc/Kconfig | 20 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/qcom-spmi-adc5-gen3.c | 1737 ++++++++++++++++++++++ drivers/iio/adc/qcom-vadc-common.c | 89 ++ include/linux/adc-tm-clients.h | 102 ++ include/linux/iio/adc/qcom-vadc-common.h | 55 + 6 files changed, 2004 insertions(+) create mode 100644 drivers/iio/adc/qcom-spmi-adc5-gen3.c create mode 100644 include/linux/adc-tm-clients.h diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 71ab0a06aa82..0d3dafb83b2c 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -872,6 +872,26 @@ config QCOM_SPMI_ADC5 To compile this driver as a module, choose M here: the module will be called qcom-spmi-adc5. +config QCOM_SPMI_ADC5_GEN3 + tristate "Qualcomm Technologies Inc. SPMI PMIC5 GEN3 ADC" + depends on SPMI + select REGMAP_SPMI + select QCOM_VADC_COMMON + help + This is the IIO Voltage PMIC5 Gen3 ADC driver for Qualcomm Technologies Inc. + + The driver supports multiple channels read. The ADC is a 16-bit + sigma-delta ADC. The hardware supports calibrated results for + conversion requests and clients include reading voltage phone + power, on board system thermistors connected to the PMIC ADC, + PMIC die temperature, charger temperature, battery current, USB voltage + input, voltage signals connected to supported PMIC GPIO inputs. The + hardware supports internal pull-up for thermistors and can choose between + a 100k, 30k and 400k pull up using the ADC channels. + + To compile this driver as a module, choose M here: the module will + be called qcom-spmi-adc5-gen3. + config RCAR_GYRO_ADC tristate "Renesas R-Car GyroADC driver" depends on ARCH_RCAR_GEN2 || COMPILE_TEST diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 39d806f6d457..073f81002995 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -77,6 +77,7 @@ obj-$(CONFIG_NAU7802) += nau7802.o obj-$(CONFIG_NPCM_ADC) += npcm_adc.o obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o obj-$(CONFIG_QCOM_SPMI_ADC5) += qcom-spmi-adc5.o +obj-$(CONFIG_QCOM_SPMI_ADC5_GEN3) += qcom-spmi-adc5-gen3.o obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o obj-$(CONFIG_QCOM_VADC_COMMON) += qcom-vadc-common.o obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o diff --git a/drivers/iio/adc/qcom-spmi-adc5-gen3.c b/drivers/iio/adc/qcom-spmi-adc5-gen3.c new file mode 100644 index 000000000000..f25b1f4fa751 --- /dev/null +++ b/drivers/iio/adc/qcom-spmi-adc5-gen3.c @@ -0,0 +1,1737 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static LIST_HEAD(adc_tm_device_list); + +#define ADC5_GEN3_HS 0x45 +#define ADC5_GEN3_HS_BUSY BIT(7) +#define ADC5_GEN3_HS_READY BIT(0) + +#define ADC5_GEN3_STATUS1 0x46 +#define ADC5_GEN3_STATUS1_CONV_FAULT BIT(7) +#define ADC5_GEN3_STATUS1_THR_CROSS BIT(6) +#define ADC5_GEN3_STATUS1_EOC BIT(1) + +#define ADC5_GEN3_TM_EN_STS 0x47 + +#define ADC5_GEN3_TM_HIGH_STS 0x48 + +#define ADC5_GEN3_TM_LOW_STS 0x49 + +#define ADC5_GEN3_EOC_STS 0x4a + +#define ADC5_GEN3_EOC_CLR 0x4b + +#define ADC5_GEN3_TM_HIGH_STS_CLR 0x4c + +#define ADC5_GEN3_TM_LOW_STS_CLR 0x4d + +#define ADC5_GEN3_SID 0x4f +#define ADC5_GEN3_SID_MASK 0xf + +#define ADC5_GEN3_PERPH_CH 0x50 +#define ADC5_GEN3_CHAN_CONV_REQ BIT(7) + +#define ADC5_GEN3_TIMER_SEL 0x51 + +#define ADC5_GEN3_DIG_PARAM 0x52 +#define ADC5_GEN3_DIG_PARAM_CAL_SEL_MASK GENMASK(5, 4) +#define ADC5_GEN3_DIG_PARAM_CAL_SEL_SHIFT 4 +#define ADC5_GEN3_DIG_PARAM_DEC_RATIO_SEL_MASK GENMASK(3, 2) +#define ADC5_GEN3_DIG_PARAM_DEC_RATIO_SEL_SHIFT 2 + +#define ADC5_GEN3_FAST_AVG 0x53 +#define ADC5_GEN3_FAST_AVG_CTL_EN BIT(7) +#define ADC5_GEN3_FAST_AVG_CTL_SAMPLES_MASK GENMASK(2, 0) + +#define ADC5_GEN3_ADC_CH_SEL_CTL 0x54 + +#define ADC5_GEN3_DELAY_CTL 0x55 +#define ADC5_GEN3_HW_SETTLE_DELAY_MASK 0xf + +#define ADC5_GEN3_CH_EN 0x56 + +#define ADC5_GEN3_LOW_THR0 0x57 + +#define ADC5_GEN3_LOW_THR1 0x58 + +#define ADC5_GEN3_HIGH_THR0 0x59 + +#define ADC5_GEN3_HIGH_THR1 0x5a + +#define ADC5_GEN3_CH0_DATA0 0x5c +#define ADC5_GEN3_CH0_DATA1 0x5d +#define ADC5_GEN3_CH1_DATA0 0x5e +#define ADC5_GEN3_CH1_DATA1 0x5f +#define ADC5_GEN3_CH2_DATA0 0x60 +#define ADC5_GEN3_CH2_DATA1 0x61 +#define ADC5_GEN3_CH3_DATA0 0x62 +#define ADC5_GEN3_CH3_DATA1 0x63 +#define ADC5_GEN3_CH4_DATA0 0x64 +#define ADC5_GEN3_CH4_DATA1 0x65 +#define ADC5_GEN3_CH5_DATA0 0x66 +#define ADC5_GEN3_CH5_DATA1 0x67 +#define ADC5_GEN3_CH6_DATA0 0x68 +#define ADC5_GEN3_CH6_DATA1 0x69 +#define ADC5_GEN3_CH7_DATA0 0x6a +#define ADC5_GEN3_CH7_DATA1 0x6b + +#define ADC5_GEN3_CONV_REQ 0xe5 +#define ADC5_GEN3_CONV_REQ_REQ BIT(0) + +#define ADC5_GEN3_SID_OFFSET 0x8 +#define ADC5_GEN3_CHANNEL_MASK 0xff +#define V_CHAN(x) (((x).sid << ADC5_GEN3_SID_OFFSET) | (x).channel) + +#define ADC_TM5_GEN3_LOWER_MASK(n) ((n) & GENMASK(7, 0)) +#define ADC_TM5_GEN3_UPPER_MASK(n) (((n) & GENMASK(15, 8)) >> 8) + +#define ADC_TM5_GEN3_CHANS_MAX 7 + +enum adc5_cal_method { + ADC5_NO_CAL = 0, + ADC5_RATIOMETRIC_CAL, + ADC5_ABSOLUTE_CAL +}; + +enum adc_time_select { + MEAS_INT_DISABLE = 0, + MEAS_INT_IMMEDIATE, + MEAS_INT_50MS, + MEAS_INT_100MS, + MEAS_INT_1S, + MEAS_INT_NONE, +}; + +enum adc_tm_type { + ADC_TM_NONE = 0, + ADC_TM, + ADC_TM_IIO, + ADC_TM_NON_THERMAL, + ADC_TM_INVALID, +}; + +static struct adc_tm_reverse_scale_fn adc_tm_rscale_fn[] = { + [SCALE_R_ABSOLUTE] = {adc_tm_absolute_rthr_gen3}, +}; + +/** + * struct adc5_channel_prop - ADC channel property. + * @channel: channel number, refer to the channel list. + * @cal_method: calibration method. + * @decimation: sampling rate supported for the channel. + * @sid: slave id of PMIC owning the channel, for PMIC5 Gen2 and above. + * @prescale: channel scaling performed on the input signal. + * @hw_settle_time: the time between AMUX being configured and the + * start of conversion. + * @avg_samples: ability to provide single result from the ADC + * that is an average of multiple measurements. + * @scale_fn_type: Represents the scaling function to convert voltage + * physical units desired by the client for the channel. + * @datasheet_name: Channel name used in device tree. + * @chip: pointer to top-level ADC device structure. + * @adc_tm: indicates TM type if the channel is used for TM measurements. + * @tm_chan_index: TM channel number used (ranging from 1-7). + * @timer: time period of recurring TM measurement. + * @tzd: pointer to thermal device corresponding to TM channel. + * @high_thr_en: TM high threshold crossing detection enabled. + * @low_thr_en: TM low threshold crossing detection enabled. + * @high_thr_triggered: indicates if high TM threshold has been triggered. + * @low_thr_triggered: indicates if low TM threshold has been triggered. + * @high_thr_voltage: upper threshold voltage for TM. + * @low_thr_voltage: lower threshold voltage for TM. + * @last_temp: last temperature that caused threshold violation, + * or a thermal TM channel. + * @last_temp_set: indicates if last_temp is stored. + * @req_wq: workqueue holding queued notification tasks for a non-thermal + * TM channel. + * @work: scheduled work for handling non-thermal TM client notification. + * @thr_list: list of client thresholds configured for non-thermal TM channel. + * @adc_rscale_fn: reverse scaling function to convert voltage to raw code + * for non-thermal TM channels. + */ +struct adc5_channel_prop { + unsigned int channel; + enum adc5_cal_method cal_method; + unsigned int decimation; + unsigned int sid; + unsigned int prescale; + unsigned int hw_settle_time; + unsigned int avg_samples; + + enum vadc_scale_fn_type scale_fn_type; + const char *datasheet_name; + + struct adc5_chip *chip; + /* TM properties */ + int adc_tm; + unsigned int tm_chan_index; + unsigned int timer; + struct thermal_zone_device *tzd; + int high_thr_en; + int low_thr_en; + bool high_thr_triggered; + bool low_thr_triggered; + int64_t high_thr_voltage; + int64_t low_thr_voltage; + int last_temp; + bool last_temp_set; + struct workqueue_struct *req_wq; + struct work_struct work; + struct list_head thr_list; + enum adc_tm_rscale_fn_type adc_rscale_fn; +}; + +/** + * struct adc5_chip - ADC private structure. + * @regmap: SPMI ADC5 peripheral register map field. + * @dev: SPMI ADC5 device. + * @base: base address for the ADC peripheral. + * @debug_base: base address for the reserved ADC peripheral, + * to dump for debug purposes alone. + * @nchannels: number of ADC channels. + * @chan_props: array of ADC channel properties. + * @iio_chans: array of IIO channels specification. + * @complete: ADC result notification after interrupt is received. + * @lock: ADC lock for access to the peripheral. + * @data: software configuration data. + * @list: list item, used to add this device to gloal list of ADC_TM devices. + * @device_list: pointer to list of ADC_TM devices. + * @n_tm_channels: number of ADC channels used for TM measurements. + * @tm_handler_work: scheduled work for handling TM threshold violation. + */ +struct adc5_chip { + struct regmap *regmap; + struct device *dev; + u16 base; + u16 debug_base; + unsigned int nchannels; + struct adc5_channel_prop *chan_props; + struct iio_chan_spec *iio_chans; + struct completion complete; + struct mutex lock; + const struct adc5_data *data; + /* TM properties */ + unsigned int n_tm_channels; + struct list_head list; + struct list_head *device_list; + struct work_struct tm_handler_work; +}; + +static const struct u32_fract adc5_prescale_ratios[] = { + { .numerator = 1, .denominator = 1 }, + { .numerator = 1, .denominator = 3 }, + { .numerator = 1, .denominator = 6 }, + { .numerator = 1, .denominator = 16 }, + /* Prescale ratios for current channels below */ + { .numerator = 32, .denominator = 100 }, /* IIN_FB */ + { .numerator = 14, .denominator = 100 }, /* ICHG_SMB */ + { .numerator = 28, .denominator = 100 }, /* IIN_SMB */ + { .numerator = 1000, .denominator = 305185 }, /* ICHG_FB */ +}; + +static int adc5_read(struct adc5_chip *adc, u16 offset, u8 *data, int len) +{ + int ret; + + ret = regmap_bulk_read(adc->regmap, adc->base + offset, data, len); + if (ret < 0) + pr_err("adc read to register 0x%x of length:%d failed, ret=%d\n", offset, len, ret); + + return ret; +} + +static int adc5_write(struct adc5_chip *adc, u16 offset, u8 *data, int len) +{ + int ret; + + ret = regmap_bulk_write(adc->regmap, adc->base + offset, data, len); + if (ret < 0) + pr_err("adc write to register 0x%x of length:%d failed, ret=%d\n", offset, len, + ret); + + return ret; +} + +static int adc5_prescaling_from_dt(u32 numerator, u32 denominator) +{ + unsigned int pre; + + for (pre = 0; pre < ARRAY_SIZE(adc5_prescale_ratios); pre++) { + if (adc5_prescale_ratios[pre].numerator == numerator && + adc5_prescale_ratios[pre].denominator == denominator) + break; + } + + if (pre == ARRAY_SIZE(adc5_prescale_ratios)) + return -ENOENT; + + return pre; +} + +static int adc5_hw_settle_time_from_dt(u32 value, + const unsigned int *hw_settle) +{ + unsigned int i; + + for (i = 0; i < VADC_HW_SETTLE_SAMPLES_MAX; i++) { + if (value == hw_settle[i]) + return i; + } + + return -ENOENT; +} + +static int adc5_avg_samples_from_dt(u32 value) +{ + if (!is_power_of_2(value) || value > ADC5_AVG_SAMPLES_MAX) + return -EINVAL; + + return __ffs(value); +} + +static int adc5_decimation_from_dt(u32 value, + const unsigned int *decimation) +{ + unsigned int i; + + for (i = 0; i < ADC5_DECIMATION_SAMPLES_MAX; i++) { + if (value == decimation[i]) + return i; + } + + return -ENOENT; +} + +static int adc5_gen3_read_voltage_data(struct adc5_chip *adc, u16 *data) +{ + int ret; + u8 rslt[2]; + + ret = adc5_read(adc, ADC5_GEN3_CH0_DATA0, rslt, 2); + if (ret < 0) + return ret; + + *data = (rslt[1] << 8) | rslt[0]; + + if (*data == ADC5_USR_DATA_CHECK) { + pr_err("Invalid data:%#x\n", *data); + return -EINVAL; + } + + pr_debug("voltage raw code:0x%x\n", *data); + + return 0; +} + +static void adc5_gen3_update_dig_param(struct adc5_chip *adc, + struct adc5_channel_prop *prop, u8 *data) +{ + /* Update calibration select */ + *data &= ~ADC5_GEN3_DIG_PARAM_CAL_SEL_MASK; + *data |= (prop->cal_method << ADC5_GEN3_DIG_PARAM_CAL_SEL_SHIFT); + + /* Update decimation ratio select */ + *data &= ~ADC5_GEN3_DIG_PARAM_DEC_RATIO_SEL_MASK; + *data |= (prop->decimation << ADC5_GEN3_DIG_PARAM_DEC_RATIO_SEL_SHIFT); +} + +static int adc5_gen3_configure(struct adc5_chip *adc, + struct adc5_channel_prop *prop) +{ + int ret; + u8 conv_req = 0, buf[7]; + + ret = adc5_read(adc, ADC5_GEN3_SID, buf, sizeof(buf)); + if (ret < 0) + return ret; + + /* Write SID */ + buf[0] &= (u8) ~ADC5_GEN3_SID_MASK; + buf[0] &= prop->sid; + + /* Use channel 0 by default for immediate conversion */ + buf[1] = 0; + + buf[2] = prop->timer; + + /* Digital param selection */ + adc5_gen3_update_dig_param(adc, prop, &buf[3]); + + /* Update fast average sample value */ + buf[4] &= (u8) ~ADC5_GEN3_FAST_AVG_CTL_SAMPLES_MASK; + buf[4] |= prop->avg_samples; + + /* + * Select ADC channel and indicate there is an actual + * conversion request + */ + buf[5] = ADC5_GEN3_CHAN_CONV_REQ | prop->channel; + + /* Select HW settle delay for channel */ + buf[6] &= (u8) ~ADC5_GEN3_HW_SETTLE_DELAY_MASK; + buf[6] |= prop->hw_settle_time; + + reinit_completion(&adc->complete); + + ret = adc5_write(adc, ADC5_GEN3_SID, buf, sizeof(buf)); + if (ret < 0) + return ret; + + conv_req = ADC5_GEN3_CONV_REQ_REQ; + ret = adc5_write(adc, ADC5_GEN3_CONV_REQ, &conv_req, 1); + + return ret; +} + +#define ADC5_GEN3_HS_DELAY_MIN_US 100 +#define ADC5_GEN3_HS_DELAY_MAX_US 110 +#define ADC5_GEN3_HS_RETRY_COUNT 20 + +static int adc5_gen3_poll_wait_hs(struct adc5_chip *adc) +{ + int ret, count; + u8 status = 0; + + for (count = 0; count < ADC5_GEN3_HS_RETRY_COUNT; count++) { + ret = adc5_read(adc, ADC5_GEN3_HS, &status, 1); + if (ret < 0) + return ret; + + if (status == ADC5_GEN3_HS_READY) + break; + + usleep_range(ADC5_GEN3_HS_DELAY_MIN_US, + ADC5_GEN3_HS_DELAY_MAX_US); + } + + if (count == ADC5_GEN3_HS_RETRY_COUNT) { + pr_err("Setting HS ready bit timed out, status:%#x\n", status); + return -ETIMEDOUT; + } + + return 0; +} + +#define ADC5_GEN3_CONV_TIMEOUT_MS 501 + +static int adc5_gen3_do_conversion(struct adc5_chip *adc, + struct adc5_channel_prop *prop, + u16 *data_volt) +{ + int ret; + unsigned long rc; + unsigned int time_pending_ms; + u8 val; + + mutex_lock(&adc->lock); + ret = adc5_gen3_poll_wait_hs(adc); + if (ret < 0) + goto unlock; + + ret = adc5_gen3_configure(adc, prop); + if (ret < 0) { + pr_err("ADC configure failed with %d\n", ret); + goto unlock; + } + + /* No support for polling mode at present*/ + rc = wait_for_completion_timeout(&adc->complete, + msecs_to_jiffies(ADC5_GEN3_CONV_TIMEOUT_MS)); + if (!rc) { + pr_err("Reading ADC channel %s timed out\n", + prop->datasheet_name); + ret = -ETIMEDOUT; + goto unlock; + } + + time_pending_ms = jiffies_to_msecs(rc); + pr_debug("ADC channel %s EOC took %u ms\n", prop->datasheet_name, + ADC5_GEN3_CONV_TIMEOUT_MS - time_pending_ms); + + ret = adc5_gen3_read_voltage_data(adc, data_volt); + if (ret < 0) + goto unlock; + + val = BIT(0); + ret = adc5_write(adc, ADC5_GEN3_EOC_CLR, &val, 1); + if (ret < 0) + goto unlock; + + /* To indicate conversion request is only to clear a status */ + val = ~ADC5_GEN3_CHAN_CONV_REQ | prop->channel; + ret = adc5_write(adc, ADC5_GEN3_PERPH_CH, &val, 1); + if (ret < 0) + goto unlock; + + val = ADC5_GEN3_CONV_REQ_REQ; + ret = adc5_write(adc, ADC5_GEN3_CONV_REQ, &val, 1); + +unlock: + mutex_unlock(&adc->lock); + + return ret; +} + +#define ADC_OFFSET_DUMP 8 +#define ADC_SDAM_REG_DUMP 32 +static void adc5_gen3_dump_regs_debug(struct adc5_chip *adc) +{ + int rc = 0, i = 0, j = 0, offset; + u8 buf[8]; + + for (j = 0; j < 2; j++) { + if (!j) { + offset = 0; + pr_debug("ADC SDAM DUMP\n"); + } else { + if (adc->debug_base) + offset = adc->debug_base; + else + break; + pr_debug("SDAM 20 DUMP\n"); + } + + for (i = 0; i < ADC_SDAM_REG_DUMP; i++) { + rc = adc5_read(adc, offset, buf, sizeof(buf)); + if (rc < 0) { + pr_err("debug register dump failed\n"); + return; + } + offset += ADC_OFFSET_DUMP; + pr_debug("Buf[%d]: %*ph\n", i, sizeof(buf), buf); + } + } +} + +static irqreturn_t adc5_gen3_isr(int irq, void *dev_id) +{ + struct adc5_chip *adc = dev_id; + u8 status, tm_status[2]; + int ret; + + ret = adc5_read(adc, ADC5_GEN3_STATUS1, &status, 1); + if (ret < 0) { + pr_err("adc read status failed with %d\n", ret); + goto handler_end; + } + + if (status & ADC5_GEN3_STATUS1_EOC) + complete(&adc->complete); + + ret = adc5_read(adc, ADC5_GEN3_TM_HIGH_STS, tm_status, 2); + if (ret < 0) { + pr_err("adc read TM status failed with %d\n", ret); + goto handler_end; + } + + if (tm_status[0] || tm_status[1]) + schedule_work(&adc->tm_handler_work); + + pr_debug("Interrupt status:%#x, high:%#x, low:%#x\n", + status, tm_status[0], tm_status[1]); + + if (status & ADC5_GEN3_STATUS1_CONV_FAULT) { + pr_err("Unexpected conversion fault\n"); + adc5_gen3_dump_regs_debug(adc); + } + +handler_end: + return IRQ_HANDLED; +} + +static void tm_handler_work(struct work_struct *work) +{ + struct adc5_chip *adc = container_of(work, struct adc5_chip, + tm_handler_work); + struct adc5_channel_prop *chan_prop; + u8 tm_status[2], buf[14], val; + int ret, i; + + mutex_lock(&adc->lock); + + ret = adc5_read(adc, ADC5_GEN3_TM_HIGH_STS, tm_status, 2); + if (ret < 0) { + pr_err("adc read TM status failed with %d\n", ret); + goto work_unlock; + } + + ret = adc5_write(adc, ADC5_GEN3_TM_HIGH_STS_CLR, tm_status, 2); + if (ret < 0) { + pr_err("adc write TM status failed with %d\n", ret); + goto work_unlock; + } + + /* To indicate conversion request is only to clear a status */ + val = (u8) ~ADC5_GEN3_CHAN_CONV_REQ; + ret = adc5_write(adc, ADC5_GEN3_PERPH_CH, &val, 1); + + val = ADC5_GEN3_CONV_REQ_REQ; + ret = adc5_write(adc, ADC5_GEN3_CONV_REQ, &val, 1); + if (ret < 0) { + pr_err("adc write conv_req failed with %d\n", ret); + goto work_unlock; + } + + ret = adc5_read(adc, ADC5_GEN3_CH1_DATA0, buf, sizeof(buf)); + if (ret < 0) { + pr_err("adc read data failed with %d\n", ret); + goto work_unlock; + } + + mutex_unlock(&adc->lock); + + for (i = 0; i < adc->nchannels; i++) { + bool upper_set = false, lower_set = false; + u8 data_low = 0, data_high = 0; + u16 code = 0; + int temp; + + chan_prop = &adc->chan_props[i]; + if (!chan_prop->adc_tm) + continue; + + mutex_lock(&adc->lock); + if ((tm_status[0] & 0x1) && (chan_prop->high_thr_en)) + upper_set = true; + + if ((tm_status[1] & 0x1) && (chan_prop->low_thr_en)) + lower_set = true; + + tm_status[0] >>= 1; + tm_status[1] >>= 1; + mutex_unlock(&adc->lock); + + if (!(upper_set || lower_set)) + continue; + + data_low = buf[2 * i]; + data_high = buf[2 * i + 1]; + code = ((data_high << 8) | data_low); + pr_debug("ADC_TM threshold code:0x%x\n", code); + + if (chan_prop->adc_tm == ADC_TM_NON_THERMAL) { + if (lower_set) { + mutex_lock(&adc->lock); + chan_prop->low_thr_en = 0; + chan_prop->low_thr_triggered = true; + mutex_unlock(&adc->lock); + queue_work(chan_prop->req_wq, + &chan_prop->work); + } + + if (upper_set) { + mutex_lock(&adc->lock); + chan_prop->high_thr_en = 0; + chan_prop->high_thr_triggered = true; + mutex_unlock(&adc->lock); + queue_work(chan_prop->req_wq, + &chan_prop->work); + } + } else { + ret = qcom_adc5_hw_scale(chan_prop->scale_fn_type, + &adc5_prescale_ratios[chan_prop->prescale], + adc->data, code, &temp); + + if (ret < 0) { + pr_err("Invalid temperature reading, ret=%d, code=0x%x\n", + ret, code); + continue; + } + pr_debug("notifying thermal, temp:%d\n", temp); + chan_prop->last_temp = temp; + chan_prop->last_temp_set = true; + thermal_zone_device_update(chan_prop->tzd, THERMAL_TRIP_VIOLATED); + } + } + + return; + +work_unlock: + mutex_unlock(&adc->lock); +} + +static int adc5_gen3_of_xlate(struct iio_dev *indio_dev, + const struct of_phandle_args *iiospec) +{ + struct adc5_chip *adc = iio_priv(indio_dev); + int i, v_channel; + + for (i = 0; i < adc->nchannels; i++) { + v_channel = V_CHAN(adc->chan_props[i]); + if (v_channel == iiospec->args[0]) + return i; + } + + return -ENOENT; +} + +static int adc5_gen3_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, + long mask) +{ + struct adc5_chip *adc = iio_priv(indio_dev); + struct adc5_channel_prop *prop; + u16 adc_code_volt; + int ret; + + prop = &adc->chan_props[chan->address]; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + ret = adc5_gen3_do_conversion(adc, prop, + &adc_code_volt); + if (ret < 0) + return ret; + + ret = qcom_adc5_hw_scale(prop->scale_fn_type, + &adc5_prescale_ratios[prop->prescale], + adc->data, + adc_code_volt, val); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_RAW: + ret = adc5_gen3_do_conversion(adc, prop, + &adc_code_volt); + if (ret < 0) + return ret; + + *val = (int)adc_code_volt; + return IIO_VAL_INT; + + default: + return -EINVAL; + } + + return 0; +} + +static const struct iio_info adc5_gen3_info = { + .read_raw = adc5_gen3_read_raw, + .of_xlate = adc5_gen3_of_xlate, +}; + +/* Used by thermal clients to read ADC channel temperature */ +int adc_tm_gen3_get_temp(void *data, int *temp) +{ + int ret; + struct adc5_channel_prop *prop = data; + struct adc5_chip *adc; + u16 adc_code_volt; + + if (!prop || !prop->chip) + return -EINVAL; + + adc = prop->chip; + + if (prop->last_temp_set) { + pr_debug("last_temp: %d\n", prop->last_temp); + prop->last_temp_set = false; + *temp = prop->last_temp; + return 0; + } + + ret = adc5_gen3_do_conversion(adc, prop, &adc_code_volt); + if (ret < 0) + return ret; + + return qcom_adc5_hw_scale(prop->scale_fn_type, + &adc5_prescale_ratios[prop->prescale], + adc->data, + adc_code_volt, temp); +} + +static int adc_tm5_gen3_configure(struct adc5_channel_prop *prop) +{ + int ret; + u8 conv_req = 0, buf[12]; + u32 mask = 0; + struct adc5_chip *adc = prop->chip; + + mutex_lock(&adc->lock); + ret = adc5_gen3_poll_wait_hs(adc); + if (ret < 0) + goto tm_config_unlock; + + ret = adc5_read(adc, ADC5_GEN3_SID, buf, sizeof(buf)); + if (ret < 0) + goto tm_config_unlock; + + /* Write SID */ + buf[0] &= (u8) ~ADC5_GEN3_SID_MASK; + buf[0] &= prop->sid; + + buf[1] = prop->tm_chan_index; + + buf[2] = prop->timer; + + /* Digital param selection */ + adc5_gen3_update_dig_param(adc, prop, &buf[3]); + + /* Update fast average sample value */ + buf[4] &= (u8) ~ADC5_GEN3_FAST_AVG_CTL_SAMPLES_MASK; + buf[4] |= prop->avg_samples; + + /* + * Select ADC channel and indicate there is an actual + * conversion request + */ + buf[5] = ADC5_GEN3_CHAN_CONV_REQ | prop->channel; + + /* Select HW settle delay for channel */ + buf[6] &= (u8) ~ADC5_GEN3_HW_SETTLE_DELAY_MASK; + buf[6] |= prop->hw_settle_time; + + buf[7] = prop->high_thr_en << 1 | prop->low_thr_en; + + mask = lower_32_bits(prop->low_thr_voltage); + buf[8] = ADC_TM5_GEN3_LOWER_MASK(mask); + buf[9] = ADC_TM5_GEN3_UPPER_MASK(mask); + + mask = lower_32_bits(prop->high_thr_voltage); + buf[10] = ADC_TM5_GEN3_LOWER_MASK(mask); + buf[11] = ADC_TM5_GEN3_UPPER_MASK(mask); + + ret = adc5_write(adc, ADC5_GEN3_SID, buf, sizeof(buf)); + if (ret < 0) + goto tm_config_unlock; + + conv_req = ADC5_GEN3_CONV_REQ_REQ; + ret = adc5_write(adc, ADC5_GEN3_CONV_REQ, &conv_req, 1); + +tm_config_unlock: + mutex_unlock(&adc->lock); + + return ret; +} + +static int adc_tm5_gen3_set_trip_temp(void *data, + int low_temp, int high_temp) +{ + struct adc5_channel_prop *prop = data; + struct adc5_chip *adc = prop->chip; + struct adc_tm_config tm_config; + int ret; + + if (!prop) + return -EINVAL; + + pr_debug("channel:%s :low_temp(mdegC):%d, high_temp(mdegC):%d\n", + prop->datasheet_name, low_temp, high_temp); + + adc = prop->chip; + tm_config.high_thr_temp = tm_config.low_thr_temp = 0; + if (high_temp != INT_MAX) + tm_config.high_thr_temp = high_temp; + if (low_temp != INT_MIN) + tm_config.low_thr_temp = low_temp; + + if ((high_temp == INT_MAX) && (low_temp == INT_MIN)) { + pr_err("No trips to set\n"); + return -EINVAL; + } + + pr_debug("requested a low temp- %d and high temp- %d\n", + tm_config.low_thr_temp, tm_config.high_thr_temp); + + adc_tm_scale_therm_voltage_100k_gen3(&tm_config); + + /* + * Thresholds are forward scaled to confirm their + * temperatures will actually cause a violation, before + * being written. + */ + pr_debug("high_thr:0x%llx, low_thr:0x%llx\n", + tm_config.high_thr_voltage, tm_config.low_thr_voltage); + + mutex_lock(&adc->lock); + + if (high_temp != INT_MAX) { + prop->low_thr_voltage = tm_config.low_thr_voltage; + prop->low_thr_en = 1; + } else { + prop->low_thr_en = 0; + } + + if (low_temp > -INT_MAX) { + prop->high_thr_voltage = tm_config.high_thr_voltage; + prop->high_thr_en = 1; + } else { + prop->high_thr_en = 0; + } + + ret = adc_tm5_gen3_configure(prop); + if (ret < 0) + pr_err("Error during adc-tm configure:%d\n", ret); + + mutex_unlock(&adc->lock); + + return ret; +} + +#define MAX_PROP_NAME_LEN 32 +struct adc5_chip *get_adc_tm_gen3(struct device *dev, const char *name) +{ + struct platform_device *pdev; + struct adc5_chip *chip; + struct device_node *node = NULL; + char prop_name[MAX_PROP_NAME_LEN]; + + scnprintf(prop_name, MAX_PROP_NAME_LEN, "qcom,%s-adc_tm", name); + + node = of_parse_phandle(dev->of_node, prop_name, 0); + if (node == NULL) + return ERR_PTR(-ENODEV); + + list_for_each_entry(chip, &adc_tm_device_list, list) { + pdev = to_platform_device(chip->dev); + if (pdev->dev.of_node == node) { + of_node_put(node); + return chip; + } + } + of_node_put(node); + return ERR_PTR(-EPROBE_DEFER); +} +EXPORT_SYMBOL(get_adc_tm_gen3); + +static int32_t adc_tm_add_to_list(struct adc5_chip *chip, + uint32_t dt_index, + struct adc_tm_param *param) +{ + struct adc_tm_client_info *client_info = NULL; + bool client_info_exists = false; + + list_for_each_entry(client_info, + &chip->chan_props[dt_index].thr_list, list) { + if (client_info->param->id == param->id) { + client_info->low_thr_requested = param->low_thr; + client_info->high_thr_requested = param->high_thr; + client_info->state_request = param->state_request; + client_info->notify_low_thr = false; + client_info->notify_high_thr = false; + client_info_exists = true; + } + } + + if (!client_info_exists) { + client_info = devm_kzalloc(chip->dev, + sizeof(struct adc_tm_client_info), GFP_KERNEL); + if (!client_info) + return -ENOMEM; + + client_info->param->id = (uintptr_t) client_info; + client_info->low_thr_requested = param->low_thr; + client_info->high_thr_requested = param->high_thr; + client_info->state_request = param->state_request; + + list_add_tail(&client_info->list, + &chip->chan_props[dt_index].thr_list); + } + return 0; +} + +static int32_t adc_tm_gen3_manage_thresholds(struct adc5_channel_prop *prop) +{ + int high_thr = INT_MAX, low_thr = INT_MIN; + struct adc_tm_client_info *client_info = NULL; + struct list_head *thr_list; + uint32_t scale_type = 0; + struct adc_tm_config tm_config; + + prop->high_thr_en = 0; + prop->low_thr_en = 0; + + /* + * Reset the high_thr_set and low_thr_set of all + * clients since the thresholds will be recomputed. + */ + list_for_each(thr_list, &prop->thr_list) { + client_info = list_entry(thr_list, + struct adc_tm_client_info, list); + client_info->high_thr_set = false; + client_info->low_thr_set = false; + } + + /* Find the min of high_thr and max of low_thr */ + list_for_each(thr_list, &prop->thr_list) { + client_info = list_entry(thr_list, + struct adc_tm_client_info, list); + + if ((client_info->state_request == ADC_TM_HIGH_THR_ENABLE) || + (client_info->state_request == + ADC_TM_HIGH_LOW_THR_ENABLE)) + if (client_info->high_thr_requested < high_thr) + high_thr = client_info->high_thr_requested; + + if ((client_info->state_request == ADC_TM_LOW_THR_ENABLE) || + (client_info->state_request == + ADC_TM_HIGH_LOW_THR_ENABLE)) + if (client_info->low_thr_requested > low_thr) + low_thr = client_info->low_thr_requested; + + pr_debug("threshold compared is high:%d and low:%d\n", + client_info->high_thr_requested, + client_info->low_thr_requested); + pr_debug("current threshold is high:%d and low:%d\n", + high_thr, low_thr); + } + + /* Check which of the high_thr and low_thr got set */ + list_for_each(thr_list, &prop->thr_list) { + client_info = list_entry(thr_list, + struct adc_tm_client_info, list); + + if ((client_info->state_request == ADC_TM_HIGH_THR_ENABLE) || + (client_info->state_request == + ADC_TM_HIGH_LOW_THR_ENABLE)) + if (high_thr == client_info->high_thr_requested) { + prop->high_thr_en = 1; + client_info->high_thr_set = true; + } + + if ((client_info->state_request == ADC_TM_LOW_THR_ENABLE) || + (client_info->state_request == + ADC_TM_HIGH_LOW_THR_ENABLE)) + if (low_thr == client_info->low_thr_requested) { + prop->low_thr_en = 1; + client_info->low_thr_set = true; + } + } + + tm_config.high_thr_voltage = (int64_t)high_thr; + tm_config.low_thr_voltage = (int64_t)low_thr; + + scale_type = prop->adc_rscale_fn; + if (scale_type >= SCALE_RSCALE_NONE) + return -EBADF; + + adc_tm_rscale_fn[scale_type].chan(&tm_config); + + prop->low_thr_voltage = tm_config.low_thr_voltage; + prop->high_thr_voltage = tm_config.high_thr_voltage; + + pr_debug("threshold written is high:%d and low:%d\n", + high_thr, low_thr); + + return 0; +} + +/* Used to notify non-thermal clients of threshold crossing */ +void notify_adc_tm_fn(struct work_struct *work) +{ + struct adc5_channel_prop *prop = container_of(work, + struct adc5_channel_prop, work); + struct adc_tm_client_info *client_info = NULL; + struct adc5_chip *chip; + struct list_head *thr_list; + int ret; + + chip = prop->chip; + + mutex_lock(&chip->lock); + if (prop->low_thr_triggered) { + /* adjust thr, calling manage_thr */ + list_for_each(thr_list, &prop->thr_list) { + client_info = list_entry(thr_list, + struct adc_tm_client_info, list); + if (client_info->low_thr_set) { + client_info->low_thr_set = false; + client_info->notify_low_thr = true; + if (client_info->state_request == + ADC_TM_HIGH_LOW_THR_ENABLE) + client_info->state_request = + ADC_TM_HIGH_THR_ENABLE; + else + client_info->state_request = + ADC_TM_LOW_THR_DISABLE; + } + } + prop->low_thr_triggered = false; + } + + if (prop->high_thr_triggered) { + /* adjust thr, calling manage_thr */ + list_for_each(thr_list, &prop->thr_list) { + client_info = list_entry(thr_list, + struct adc_tm_client_info, list); + if (client_info->high_thr_set) { + client_info->high_thr_set = false; + client_info->notify_high_thr = true; + if (client_info->state_request == + ADC_TM_HIGH_LOW_THR_ENABLE) + client_info->state_request = + ADC_TM_LOW_THR_ENABLE; + else + client_info->state_request = + ADC_TM_HIGH_THR_DISABLE; + } + } + prop->high_thr_triggered = false; + } + ret = adc_tm_gen3_manage_thresholds(prop); + if (ret < 0) + pr_err("Error in reverse scaling:%d\n", ret); + + ret = adc_tm5_gen3_configure(prop); + if (ret < 0) + pr_err("Error during adc-tm configure:%d\n", ret); + + mutex_unlock(&chip->lock); + + list_for_each_entry(client_info, &prop->thr_list, list) { + if (client_info->notify_low_thr && + client_info->param->threshold_notification != NULL) { + pr_debug("notify kernel with low state for channel 0x%x\n", + prop->channel); + client_info->param->threshold_notification( + ADC_TM_LOW_STATE, + client_info->param->btm_ctx); + client_info->notify_low_thr = false; + } + + if (client_info->notify_high_thr && + client_info->param->threshold_notification != NULL) { + pr_debug("notify kernel with high state for channel 0x%x\n", + prop->channel); + client_info->param->threshold_notification( + ADC_TM_HIGH_STATE, + client_info->param->btm_ctx); + client_info->notify_high_thr = false; + } + } +} + +/* Used by non-thermal clients to configure an ADC_TM channel */ +int32_t adc_tm_channel_measure_gen3(struct adc5_chip *chip, + struct adc_tm_param *param) + +{ + int ret, i; + uint32_t v_channel, dt_index = 0; + bool chan_found = false; + + if (param == NULL) + return -EINVAL; + + if (param->threshold_notification == NULL) { + pr_debug("No notification for high/low temp\n"); + return -EINVAL; + } + + for (i = 0; i < chip->nchannels; i++) { + v_channel = V_CHAN(chip->chan_props[i]); + if (v_channel == param->channel) { + dt_index = i; + chan_found = true; + break; + } + } + + if (!chan_found) { + pr_err("not a valid ADC_TM channel\n"); + return -EINVAL; + } + + mutex_lock(&chip->lock); + + /* add channel client to channel list */ + adc_tm_add_to_list(chip, dt_index, param); + + /* set right thresholds for the sensor */ + ret = adc_tm_gen3_manage_thresholds(&chip->chan_props[dt_index]); + if (ret < 0) + pr_err("Error in reverse scaling:%d\n", ret); + + /* configure channel */ + ret = adc_tm5_gen3_configure(&chip->chan_props[dt_index]); + if (ret < 0) { + pr_err("Error during adc-tm configure:%d\n", ret); + goto fail_unlock; + } + +fail_unlock: + mutex_unlock(&chip->lock); + return ret; +} +EXPORT_SYMBOL(adc_tm_channel_measure_gen3); + +/* Used by non-thermal clients to release an ADC_TM channel */ +int32_t adc_tm_disable_chan_meas_gen3(struct adc5_chip *chip, + struct adc_tm_param *param) +{ + int ret, i; + uint32_t dt_index = 0, v_channel; + struct adc_tm_client_info *client_info = NULL; + + if (param == NULL) + return -EINVAL; + + for (i = 0; i < chip->nchannels; i++) { + v_channel = V_CHAN(chip->chan_props[i]); + if (v_channel == param->channel) { + dt_index = i; + break; + } + } + + if (i == chip->nchannels) { + pr_err("not a valid ADC_TM channel\n"); + return -EINVAL; + } + + mutex_lock(&chip->lock); + list_for_each_entry(client_info, + &chip->chan_props[i].thr_list, list) { + if (client_info->param->id == param->id) { + client_info->state_request = + ADC_TM_HIGH_LOW_THR_DISABLE; + ret = adc_tm_gen3_manage_thresholds(&chip->chan_props[i]); + if (ret < 0) { + pr_err("Error in reverse scaling:%d\n", + ret); + goto fail; + } + ret = adc_tm5_gen3_configure(&chip->chan_props[i]); + if (ret < 0) { + pr_err("Error during adc-tm configure:%d\n", + ret); + goto fail; + } + } + } + +fail: + mutex_unlock(&chip->lock); + return ret; +} +EXPORT_SYMBOL(adc_tm_disable_chan_meas_gen3); + +static struct thermal_zone_of_device_ops adc_tm_ops = { + .get_temp = adc_tm_gen3_get_temp, + .set_trips = adc_tm5_gen3_set_trip_temp, +}; + +static struct thermal_zone_of_device_ops adc_tm_ops_iio = { + .get_temp = adc_tm_gen3_get_temp, +}; + +static int adc_tm_register_tzd(struct adc5_chip *adc) +{ + unsigned int i, channel; + struct thermal_zone_device *tzd; + + for (i = 0; i < adc->nchannels; i++) { + channel = V_CHAN(adc->chan_props[i]); + + switch (adc->chan_props[i].adc_tm) { + case ADC_TM_NONE: + continue; + case ADC_TM: + tzd = devm_thermal_zone_of_sensor_register( + adc->dev, channel, + &adc->chan_props[i], &adc_tm_ops); + break; + case ADC_TM_IIO: + tzd = devm_thermal_zone_of_sensor_register( + adc->dev, channel, + &adc->chan_props[i], &adc_tm_ops_iio); + break; + case ADC_TM_NON_THERMAL: + tzd = NULL; + break; + default: + pr_err("Invalid ADC_TM type:%d for dt_ch:%d\n", + adc->chan_props[i].adc_tm, adc->chan_props[i].channel); + continue; + } + + if (IS_ERR(tzd)) { + pr_err("Error registering TZ zone:%ld for dt_ch:%d\n", + PTR_ERR(tzd), adc->chan_props[i].channel); + continue; + } + adc->chan_props[i].tzd = tzd; + } + + return 0; +} + +struct adc5_channels { + const char *datasheet_name; + unsigned int prescale_index; + enum iio_chan_type type; + long info_mask; + enum vadc_scale_fn_type scale_fn_type; +}; + +/* In these definitions, _pre refers to an index into adc5_prescale_ratios. */ +#define ADC5_CHAN(_dname, _type, _mask, _pre, _scale) \ + { \ + .datasheet_name = _dname, \ + .prescale_index = _pre, \ + .type = _type, \ + .info_mask = _mask, \ + .scale_fn_type = _scale, \ + }, \ + +#define ADC5_CHAN_TEMP(_dname, _pre, _scale) \ + ADC5_CHAN(_dname, IIO_TEMP, \ + BIT(IIO_CHAN_INFO_PROCESSED), \ + _pre, _scale) \ + +#define ADC5_CHAN_VOLT(_dname, _pre, _scale) \ + ADC5_CHAN(_dname, IIO_VOLTAGE, \ + BIT(IIO_CHAN_INFO_PROCESSED), \ + _pre, _scale) \ + +#define ADC5_CHAN_CUR(_dname, _pre, _scale) \ + ADC5_CHAN(_dname, IIO_CURRENT, \ + BIT(IIO_CHAN_INFO_PROCESSED), \ + _pre, _scale) \ + +static const struct adc5_channels adc5_chans_pmic[ADC5_MAX_CHANNEL] = { + [ADC5_GEN3_OFFSET_REF] = ADC5_CHAN_VOLT("ref_gnd", 0, + SCALE_HW_CALIB_DEFAULT) + [ADC5_GEN3_1P25VREF] = ADC5_CHAN_VOLT("vref_1p25", 0, + SCALE_HW_CALIB_DEFAULT) + [ADC5_GEN3_VPH_PWR] = ADC5_CHAN_VOLT("vph_pwr", 1, + SCALE_HW_CALIB_DEFAULT) + [ADC5_GEN3_VBAT_SNS_QBG] = ADC5_CHAN_VOLT("vbat_sns", 3, + SCALE_HW_CALIB_DEFAULT) + [ADC5_GEN3_AMUX3_THM] = ADC5_CHAN_TEMP("smb_temp", 0, + SCALE_HW_CALIB_PM7_SMB_TEMP) + [ADC5_GEN3_CHG_TEMP] = ADC5_CHAN_TEMP("chg_temp", 0, + SCALE_HW_CALIB_PM7_CHG_TEMP) + [ADC5_GEN3_IIN_FB] = ADC5_CHAN_CUR("iin_fb", 4, + SCALE_HW_CALIB_CUR) + [ADC5_GEN3_ICHG_SMB] = ADC5_CHAN_CUR("ichg_smb", 5, + SCALE_HW_CALIB_CUR) + [ADC5_GEN3_IIN_SMB] = ADC5_CHAN_CUR("iin_smb", 6, + SCALE_HW_CALIB_CUR) + [ADC5_GEN3_ICHG_FB] = ADC5_CHAN_CUR("ichg_fb", 7, + SCALE_HW_CALIB_CUR_RAW) + [ADC5_GEN3_DIE_TEMP] = ADC5_CHAN_TEMP("die_temp", 0, + SCALE_HW_CALIB_PMIC_THERM_PM7) + [ADC5_GEN3_AMUX1_THM_100K_PU] = ADC5_CHAN_TEMP("amux_thm1_pu2", 0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) + [ADC5_GEN3_AMUX2_THM_100K_PU] = ADC5_CHAN_TEMP("amux_thm2_pu2", 0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) + [ADC5_GEN3_AMUX3_THM_100K_PU] = ADC5_CHAN_TEMP("amux_thm3_pu2", 0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) + [ADC5_GEN3_AMUX4_THM_100K_PU] = ADC5_CHAN_TEMP("amux_thm4_pu2", 0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) + [ADC5_GEN3_AMUX5_THM_100K_PU] = ADC5_CHAN_TEMP("amux_thm5_pu2", 0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) + [ADC5_GEN3_AMUX6_THM_100K_PU] = ADC5_CHAN_TEMP("amux_thm6_pu2", 0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) + [ADC5_GEN3_AMUX1_GPIO_100K_PU] = ADC5_CHAN_TEMP("amux1_gpio_pu2", 0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) + [ADC5_GEN3_AMUX2_GPIO_100K_PU] = ADC5_CHAN_TEMP("amux2_gpio_pu2", 0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) + [ADC5_GEN3_AMUX3_GPIO_100K_PU] = ADC5_CHAN_TEMP("amux3_gpio_pu2", 0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) + [ADC5_GEN3_AMUX4_GPIO_100K_PU] = ADC5_CHAN_TEMP("amux4_gpio_pu2", 0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) +}; + +static int adc5_get_dt_channel_data(struct adc5_chip *adc, + struct adc5_channel_prop *prop, + struct device_node *node, + const struct adc5_data *data) +{ + const char *name = node->name, *channel_name; + u32 chan, value, varr[2]; + u32 sid = 0; + int ret; + struct device *dev = adc->dev; + + ret = of_property_read_u32(node, "reg", &chan); + if (ret < 0) { + dev_err(dev, "invalid channel number %s\n", name); + return ret; + } + + /* + * Value read from "reg" is virtual channel number + * virtual channel number = (sid << 8 | channel number). + */ + sid = (chan >> ADC5_GEN3_SID_OFFSET); + chan = (chan & ADC5_GEN3_CHANNEL_MASK); + + if (chan > ADC5_OFFSET_EXT2 || + !data->adc_chans[chan].datasheet_name) { + dev_err(dev, "%s invalid channel number %d\n", name, chan); + return -EINVAL; + } + + /* the channel has DT description */ + prop->channel = chan; + prop->sid = sid; + + channel_name = of_get_property(node, + "label", NULL) ? : node->name; + if (!channel_name) { + pr_err("Invalid channel name\n"); + return -EINVAL; + } + prop->datasheet_name = channel_name; + + ret = of_property_read_u32(node, "qcom,decimation", &value); + if (!ret) { + ret = adc5_decimation_from_dt(value, data->decimation); + if (ret < 0) { + dev_err(dev, "%02x invalid decimation %d\n", + chan, value); + return ret; + } + prop->decimation = ret; + } else { + prop->decimation = ADC5_DECIMATION_DEFAULT; + } + + ret = of_property_read_u32_array(node, "qcom,pre-scaling", varr, 2); + if (!ret) { + ret = adc5_prescaling_from_dt(varr[0], varr[1]); + if (ret < 0) { + dev_err(dev, "%02x invalid pre-scaling <%d %d>\n", + chan, varr[0], varr[1]); + return ret; + } + prop->prescale = ret; + } else { + prop->prescale = + adc->data->adc_chans[prop->channel].prescale_index; + } + + ret = of_property_read_u32(node, "qcom,hw-settle-time", &value); + if (!ret) { + ret = adc5_hw_settle_time_from_dt(value, + data->hw_settle_1); + if (ret < 0) { + dev_err(dev, "%02x invalid hw-settle-time %d us\n", + chan, value); + return ret; + } + prop->hw_settle_time = ret; + } else { + prop->hw_settle_time = VADC_DEF_HW_SETTLE_TIME; + } + + ret = of_property_read_u32(node, "qcom,avg-samples", &value); + if (!ret) { + ret = adc5_avg_samples_from_dt(value); + if (ret < 0) { + dev_err(dev, "%02x invalid avg-samples %d\n", + chan, value); + return ret; + } + prop->avg_samples = ret; + } else { + prop->avg_samples = VADC_DEF_AVG_SAMPLES; + } + + prop->scale_fn_type = -EINVAL; + ret = of_property_read_u32(node, "qcom,scale-fn-type", &value); + if (!ret && value < SCALE_HW_CALIB_INVALID) + prop->scale_fn_type = value; + + if (of_property_read_bool(node, "qcom,ratiometric")) + prop->cal_method = ADC5_RATIOMETRIC_CAL; + else + prop->cal_method = ADC5_ABSOLUTE_CAL; + + prop->timer = MEAS_INT_IMMEDIATE; + prop->adc_tm = ADC_TM_NONE; + + ret = of_property_read_u32(node, "qcom,adc-tm-type", &value); + if (!ret && value < ADC_TM_INVALID) + prop->adc_tm = value; + + if (prop->adc_tm == ADC_TM_NON_THERMAL) { + ret = of_property_read_u32(node, "qcom,rscale-type", + &prop->adc_rscale_fn); + if (ret < 0) + prop->adc_rscale_fn = SCALE_RSCALE_NONE; + } + + if (prop->adc_tm && prop->adc_tm != ADC_TM_IIO) { + adc->n_tm_channels++; + if (adc->n_tm_channels > ADC_TM5_GEN3_CHANS_MAX) { + pr_err("Number of TM nodes greater than channels supported:%d\n", + adc->n_tm_channels); + return -EINVAL; + } + prop->tm_chan_index = adc->n_tm_channels; + prop->timer = MEAS_INT_1S; + + if (prop->adc_tm == ADC_TM_NON_THERMAL) { + prop->req_wq = alloc_workqueue( + "adc_tm_notify_wq", WQ_HIGHPRI, 0); + if (!prop->req_wq) { + pr_err("Requesting priority wq failed\n"); + return -ENOMEM; + } + INIT_WORK(&prop->work, notify_adc_tm_fn); + } + INIT_LIST_HEAD(&prop->thr_list); + } + + dev_dbg(dev, "%02x name %s\n", chan, name); + + return 0; +} + +static const struct adc5_data adc5_gen3_data_pmic = { + .name = "pm-adc5-gen3", + .full_scale_code_volt = 0x70e4, + .full_scale_code_cur = 0x2710, + .adc_chans = adc5_chans_pmic, + .decimation = (unsigned int [ADC5_DECIMATION_SAMPLES_MAX]) + {85, 340, 1360}, + .hw_settle_1 = (unsigned int [VADC_HW_SETTLE_SAMPLES_MAX]) + {15, 100, 200, 300, 400, 500, 600, 700, + 1000, 2000, 4000, 8000, 16000, 32000, + 64000, 128000}, +}; + +static const struct of_device_id adc5_match_table[] = { + { + .compatible = "qcom,spmi-adc5-gen3", + .data = &adc5_gen3_data_pmic, + }, + { } +}; +MODULE_DEVICE_TABLE(of, adc5_match_table); + +static int adc5_get_dt_data(struct adc5_chip *adc, struct device_node *node) +{ + const struct adc5_channels *adc_chan; + struct iio_chan_spec *iio_chan; + struct adc5_channel_prop prop, *chan_props; + struct device_node *child; + unsigned int index = 0; + const struct of_device_id *id; + const struct adc5_data *data; + int ret; + + adc->nchannels = of_get_available_child_count(node); + if (!adc->nchannels) + return -EINVAL; + + adc->iio_chans = devm_kcalloc(adc->dev, adc->nchannels, + sizeof(*adc->iio_chans), GFP_KERNEL); + if (!adc->iio_chans) + return -ENOMEM; + + adc->chan_props = devm_kcalloc(adc->dev, adc->nchannels, + sizeof(*adc->chan_props), GFP_KERNEL); + if (!adc->chan_props) + return -ENOMEM; + + chan_props = adc->chan_props; + adc->n_tm_channels = 0; + iio_chan = adc->iio_chans; + id = of_match_node(adc5_match_table, node); + if (id) + data = id->data; + else + data = &adc5_gen3_data_pmic; + adc->data = data; + + for_each_available_child_of_node(node, child) { + ret = adc5_get_dt_channel_data(adc, &prop, child, data); + if (ret < 0) { + of_node_put(child); + return ret; + } + + prop.chip = adc; + if (prop.scale_fn_type == -EINVAL) + prop.scale_fn_type = + data->adc_chans[prop.channel].scale_fn_type; + *chan_props = prop; + adc_chan = &data->adc_chans[prop.channel]; + + iio_chan->channel = prop.channel; + iio_chan->datasheet_name = prop.datasheet_name; + iio_chan->extend_name = prop.datasheet_name; + iio_chan->info_mask_separate = adc_chan->info_mask; + iio_chan->type = adc_chan->type; + iio_chan->address = index; + iio_chan++; + chan_props++; + index++; + } + + return 0; +} + +static int adc5_gen3_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct adc5_chip *adc; + struct regmap *regmap; + const char *irq_name; + const __be32 *prop_addr; + int ret, irq_eoc, i; + u32 reg; + + regmap = dev_get_regmap(dev->parent, NULL); + if (!regmap) + return -ENODEV; + + ret = of_property_read_u32(node, "reg", ®); + if (ret < 0) + return ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + adc->regmap = regmap; + adc->dev = dev; + + prop_addr = of_get_address(dev->of_node, 0, NULL, NULL); + if (!prop_addr) { + pr_err("invalid IO resource\n"); + return -EINVAL; + } + adc->base = be32_to_cpu(*prop_addr); + + prop_addr = of_get_address(dev->of_node, 1, NULL, NULL); + if (!prop_addr) + pr_debug("invalid debug resource\n"); + else + adc->debug_base = be32_to_cpu(*prop_addr); + + platform_set_drvdata(pdev, adc); + + indio_dev->info = &adc5_gen3_info; + + init_completion(&adc->complete); + mutex_init(&adc->lock); + + ret = adc5_get_dt_data(adc, node); + if (ret < 0) { + pr_err("adc get dt data failed\n"); + goto fail; + } + + adc_tm_register_tzd(adc); + + irq_eoc = platform_get_irq(pdev, 0); + irq_name = "pm-adc5"; + if (adc->data->name) + irq_name = adc->data->name; + + ret = devm_request_irq(dev, irq_eoc, adc5_gen3_isr, 0, + irq_name, adc); + if (ret < 0) + goto fail; + + if (adc->n_tm_channels) + INIT_WORK(&adc->tm_handler_work, tm_handler_work); + + indio_dev->dev.parent = dev; + indio_dev->dev.of_node = node; + indio_dev->name = pdev->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = adc->iio_chans; + indio_dev->num_channels = adc->nchannels; + + list_add_tail(&adc->list, &adc_tm_device_list); + adc->device_list = &adc_tm_device_list; + + return devm_iio_device_register(dev, indio_dev); + +fail: + i = 0; + while (i < adc->nchannels) { + if (adc->chan_props[i].req_wq) + destroy_workqueue(adc->chan_props[i].req_wq); + i++; + } + return ret; +} + +static int adc5_gen3_exit(struct platform_device *pdev) +{ + struct adc5_chip *adc = platform_get_drvdata(pdev); + u8 data = 0; + int i; + + mutex_lock(&adc->lock); + for (i = 0; i < adc->nchannels; i++) { + if (adc->chan_props[i].req_wq) + destroy_workqueue(adc->chan_props[i].req_wq); + adc->chan_props[i].timer = MEAS_INT_DISABLE; + } + + /* Disable all available channels */ + for (i = 0; i < 8; i++) { + data = MEAS_INT_DISABLE; + adc5_write(adc, ADC5_GEN3_TIMER_SEL, &data, 1); + + /* To indicate there is an actual conversion request */ + data = ADC5_GEN3_CHAN_CONV_REQ | i; + adc5_write(adc, ADC5_GEN3_PERPH_CH, &data, 1); + + data = ADC5_GEN3_CONV_REQ_REQ; + adc5_write(adc, ADC5_GEN3_CONV_REQ, &data, 1); + } + + mutex_unlock(&adc->lock); + + if (adc->n_tm_channels) + cancel_work_sync(&adc->tm_handler_work); + + mutex_destroy(&adc->lock); + + list_del(&adc->list); + + return 0; +} + +static struct platform_driver adc5_gen3_driver = { + .driver = { + .name = "qcom-spmi-adc5-gen3.c", + .of_match_table = adc5_match_table, + }, + .probe = adc5_gen3_probe, + .remove = adc5_gen3_exit, +}; +module_platform_driver(adc5_gen3_driver); + +MODULE_ALIAS("platform:qcom-spmi-adc5-gen3"); +MODULE_DESCRIPTION("Qualcomm Technologies Inc. PMIC5 Gen3 ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/qcom-vadc-common.c b/drivers/iio/adc/qcom-vadc-common.c index 3e81aebfb018..d88e530021f7 100644 --- a/drivers/iio/adc/qcom-vadc-common.c +++ b/drivers/iio/adc/qcom-vadc-common.c @@ -1139,6 +1139,95 @@ static int qcom_vadc_scale_hw_chg5_temp( return 0; } +void adc_tm_scale_therm_voltage_100k_gen3(struct adc_tm_config *param) +{ + int temp, ret; + int64_t resistance = 0; + + /* + * High temperature maps to lower threshold voltage. + * Same API can be used for resistance-temperature table + */ + resistance = qcom_vadc_map_temp_voltage(adcmap7_100k, + ARRAY_SIZE(adcmap7_100k), + param->high_thr_temp); + + param->low_thr_voltage = resistance * RATIO_MAX_ADC7; + param->low_thr_voltage = div64_s64(param->low_thr_voltage, + (resistance + R_PU_100K)); + + /* + * low_thr_voltage is ADC raw code corresponding to upper temperature + * threshold. + * Instead of returning the ADC raw code obtained at this point,we first + * do a forward conversion on the (low voltage / high temperature) threshold code, + * to temperature, to check if that code, when read by TM, would translate to + * a temperature greater than or equal to the upper temperature limit (which is + * expected). If it is instead lower than the upper limit (not expected for correct + * TM functionality), we lower the raw code of the threshold written by 1 + * to ensure TM does see a violation when it reads raw code corresponding + * to the upper limit temperature specified. + */ + ret = qcom_vadc7_scale_hw_calib_therm(NULL, NULL, param->low_thr_voltage, &temp); + if (ret < 0) + return; + + if (temp < param->high_thr_temp) + param->low_thr_voltage--; + + /* + * Low temperature maps to higher threshold voltage + * Same API can be used for resistance-temperature table + */ + resistance = qcom_vadc_map_temp_voltage(adcmap7_100k, + ARRAY_SIZE(adcmap7_100k), + param->low_thr_temp); + + param->high_thr_voltage = resistance * RATIO_MAX_ADC7; + param->high_thr_voltage = div64_s64(param->high_thr_voltage, + (resistance + R_PU_100K)); + + /* + * high_thr_voltage is ADC raw code corresponding to lower temperature + * threshold. + * Similar to what is done above for low_thr voltage, we first + * do a forward conversion on the (high voltage / low temperature)threshold code, + * to temperature, to check if that code, when read by TM, would translate to a + * temperature less than or equal to the lower temperature limit (which is expected). + * If it is instead greater than the lower limit (not expected for correct + * TM functionality), we increase the raw code of the threshold written by 1 + * to ensure TM does see a violation when it reads raw code corresponding + * to the lower limit temperature specified. + */ + ret = qcom_vadc7_scale_hw_calib_therm(NULL, NULL, param->high_thr_voltage, &temp); + if (ret < 0) + return; + + if (temp > param->low_thr_temp) + param->high_thr_voltage++; +} +EXPORT_SYMBOL(adc_tm_scale_therm_voltage_100k_gen3); + +int32_t adc_tm_absolute_rthr_gen3(struct adc_tm_config *tm_config) +{ + int64_t low_thr = 0, high_thr = 0; + + low_thr = tm_config->low_thr_voltage; + low_thr *= ADC5_FULL_SCALE_CODE; + + low_thr = div64_s64(low_thr, ADC_VDD_REF); + tm_config->low_thr_voltage = low_thr; + + high_thr = tm_config->high_thr_voltage; + high_thr *= ADC5_FULL_SCALE_CODE; + + high_thr = div64_s64(high_thr, ADC_VDD_REF); + tm_config->high_thr_voltage = high_thr; + + return 0; +} +EXPORT_SYMBOL(adc_tm_absolute_rthr_gen3); + int qcom_vadc_scale(enum vadc_scale_fn_type scaletype, const struct vadc_linear_graph *calib_graph, const struct u32_fract *prescale, diff --git a/include/linux/adc-tm-clients.h b/include/linux/adc-tm-clients.h new file mode 100644 index 000000000000..3a84cea95e8b --- /dev/null +++ b/include/linux/adc-tm-clients.h @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2012-2021, The Linux Foundation. All rights reserved. + */ + +#ifndef __QCOM_ADC_TM_H_CLIENTS__ +#define __QCOM_ADC_TM_H_CLIENTS__ + +#include +#include + +struct adc_tm_chip; +struct adc5_chip; + +/** + * enum adc_tm_state - This lets the client know whether the threshold + * that was crossed was high/low. + * %ADC_TM_HIGH_STATE: Client is notified of crossing the requested high + * voltage threshold. + * %ADC_TM_COOL_STATE: Client is notified of crossing the requested cool + * temperature threshold. + * %ADC_TM_LOW_STATE: Client is notified of crossing the requested low + * voltage threshold. + * %ADC_TM_WARM_STATE: Client is notified of crossing the requested high + * temperature threshold. + */ +enum adc_tm_state { + ADC_TM_HIGH_STATE = 0, + ADC_TM_COOL_STATE = ADC_TM_HIGH_STATE, + ADC_TM_LOW_STATE, + ADC_TM_WARM_STATE = ADC_TM_LOW_STATE, + ADC_TM_STATE_NUM, +}; + +/** + * enum adc_tm_state_request - Request to enable/disable the corresponding + * high/low voltage/temperature thresholds. + * %ADC_TM_HIGH_THR_ENABLE: Enable high voltage threshold. + * %ADC_TM_COOL_THR_ENABLE = Enables cool temperature threshold. + * %ADC_TM_LOW_THR_ENABLE: Enable low voltage/temperature threshold. + * %ADC_TM_WARM_THR_ENABLE = Enables warm temperature threshold. + * %ADC_TM_HIGH_LOW_THR_ENABLE: Enable high and low voltage/temperature + * threshold. + * %ADC_TM_HIGH_THR_DISABLE: Disable high voltage/temperature threshold. + * %ADC_TM_COOL_THR_ENABLE = Disables cool temperature threshold. + * %ADC_TM_LOW_THR_DISABLE: Disable low voltage/temperature threshold. + * %ADC_TM_WARM_THR_ENABLE = Disables warm temperature threshold. + * %ADC_TM_HIGH_THR_DISABLE: Disable high and low voltage/temperature + * threshold. + */ +enum adc_tm_state_request { + ADC_TM_HIGH_THR_ENABLE = 0, + ADC_TM_COOL_THR_ENABLE = ADC_TM_HIGH_THR_ENABLE, + ADC_TM_LOW_THR_ENABLE, + ADC_TM_WARM_THR_ENABLE = ADC_TM_LOW_THR_ENABLE, + ADC_TM_HIGH_LOW_THR_ENABLE, + ADC_TM_HIGH_THR_DISABLE, + ADC_TM_COOL_THR_DISABLE = ADC_TM_HIGH_THR_DISABLE, + ADC_TM_LOW_THR_DISABLE, + ADC_TM_WARM_THR_DISABLE = ADC_TM_LOW_THR_DISABLE, + ADC_TM_HIGH_LOW_THR_DISABLE, + ADC_TM_THR_NUM, +}; + +struct adc_tm_param { + unsigned long id; + int low_thr; + int high_thr; + uint32_t channel; + enum adc_tm_state_request state_request; + void *btm_ctx; + void (*threshold_notification)(enum adc_tm_state state, + void *ctx); +}; + +struct device; + +/* Public API */ + +#if IS_ENABLED(CONFIG_QCOM_SPMI_ADC5_GEN3) +struct adc5_chip *get_adc_tm_gen3(struct device *dev, const char *name); +int32_t adc_tm_channel_measure_gen3(struct adc5_chip *chip, + struct adc_tm_param *param); +int32_t adc_tm_disable_chan_meas_gen3(struct adc5_chip *chip, + struct adc_tm_param *param); +#else +static inline struct adc5_chip *get_adc_tm_gen3( + struct device *dev, const char *name) +{ return ERR_PTR(-ENXIO); } + +static inline int32_t adc_tm_channel_measure_gen3( + struct adc5_chip *chip, + struct adc_tm_param *param) +{ return -ENXIO; } +static inline int32_t adc_tm_disable_chan_meas_gen3( + struct adc5_chip *chip, + struct adc_tm_param *param) +{ return -ENXIO; } + +#endif + +#endif /* __QCOM_ADC_TM_H_CLIENTS__ */ diff --git a/include/linux/iio/adc/qcom-vadc-common.h b/include/linux/iio/adc/qcom-vadc-common.h index c58870892a30..6c5f7ed4cf64 100644 --- a/include/linux/iio/adc/qcom-vadc-common.h +++ b/include/linux/iio/adc/qcom-vadc-common.h @@ -6,6 +6,7 @@ #ifndef QCOM_VADC_COMMON_H #define QCOM_VADC_COMMON_H +#include #include #include @@ -60,6 +61,8 @@ #define R_PU_100K 100000 #define RATIO_MAX_ADC7 BIT(14) +#define ADC_VDD_REF 1875000 + /* * VADC_CALIB_ABSOLUTE: uses the 625mV and 1.25V as reference channels. * VADC_CALIB_RATIOMETRIC: uses the reference voltage (1.8V) and GND for @@ -85,6 +88,54 @@ struct vadc_linear_graph { s32 gnd; }; +/** + * enum adc_tm_rscale_fn_type - Scaling function used to convert the + * channels input voltage/temperature to corresponding ADC code that is + * applied for thresholds. Check the corresponding channels scaling to + * determine the appropriate temperature/voltage units that are passed + * to the scaling function. Example battery follows the power supply + * framework that needs its units to be in decidegreesC so it passes + * deci-degreesC. PA_THERM clients pass the temperature in degrees. + * The order below should match the one in the driver for + * adc_tm_rscale_fn[]. + */ +enum adc_tm_rscale_fn_type { + SCALE_R_ABSOLUTE = 0, + SCALE_RSCALE_NONE, +}; + +/** + * struct adc_tm_config - Represent ADC Thermal Monitor configuration. + * @high_thr_temp: Temperature at which high threshold notification is required. + * @low_thr_temp: Temperature at which low threshold notification is required. + * @low_thr_voltage : Low threshold voltage ADC code used for reverse + * calibration. + * @high_thr_voltage: High threshold voltage ADC code used for reverse + * calibration. + */ +struct adc_tm_config { + int high_thr_temp; + int low_thr_temp; + int64_t high_thr_voltage; + int64_t low_thr_voltage; +}; + +struct adc_tm_reverse_scale_fn { + int32_t (*chan)(struct adc_tm_config *tm_config); +}; + +struct adc_tm_client_info { + struct list_head list; + struct adc_tm_param *param; + int32_t low_thr_requested; + int32_t high_thr_requested; + bool notify_low_thr; + bool notify_high_thr; + bool high_thr_set; + bool low_thr_set; + enum adc_tm_state_request state_request; +}; + /** * enum vadc_scale_fn_type - Scaling function to convert ADC code to * physical scaled units for the channel. @@ -201,4 +252,8 @@ int qcom_adc5_decimation_from_dt(u32 value, const unsigned int *decimation); int qcom_vadc_decimation_from_dt(u32 value); +void adc_tm_scale_therm_voltage_100k_gen3(struct adc_tm_config *param); + +int32_t adc_tm_absolute_rthr_gen3(struct adc_tm_config *tm_config); + #endif /* QCOM_VADC_COMMON_H */