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..6418373578c1 --- /dev/null +++ b/drivers/iio/adc/qcom-spmi-adc5-gen3.c @@ -0,0 +1,1764 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2021-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(0) + +#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_CHAN_0 BIT(0) + +#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_CONV_ERR_CLR 0x4e +#define ADC5_GEN3_CONV_ERR_CLR_REQ BIT(0) + +#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_TIME_IMMEDIATE 0x1 + +#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 and + * to indicate there is an actual conversion request + */ + buf[1] = ADC5_GEN3_CHAN_CONV_REQ | 0; + + buf[2] = ADC5_GEN3_TIME_IMMEDIATE; + + /* 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 | ADC5_GEN3_FAST_AVG_CTL_EN; + + /* Select ADC channel */ + buf[5] = 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 = 0; + 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 = adc->base; + 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 = regmap_bulk_read(adc->regmap, 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], eoc_status, val; + int ret; + + ret = adc5_read(adc, ADC5_GEN3_EOC_STS, &eoc_status, 1); + if (ret < 0) { + pr_err("adc read eoc status failed with %d\n", ret); + goto handler_end; + } + + /* CHAN0 is the preconfigured channel for immediate conversion */ + if (eoc_status & ADC5_GEN3_EOC_CHAN_0) + 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); + + ret = adc5_read(adc, ADC5_GEN3_STATUS1, &status, 1); + if (ret < 0) { + pr_err("adc read status1 failed with %d\n", ret); + goto handler_end; + } + + pr_debug("Interrupt status:%#x, EOC status:%#x, high:%#x, low:%#x\n", + status, eoc_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); + + val = ADC5_GEN3_CONV_ERR_CLR_REQ; + ret = adc5_write(adc, ADC5_GEN3_CONV_ERR_CLR, &val, 1); + if (ret < 0) + goto handler_end; + + /* To indicate conversion request is only to clear a status */ + val = 0; + ret = adc5_write(adc, ADC5_GEN3_PERPH_CH, &val, 1); + if (ret < 0) + goto handler_end; + + val = ADC5_GEN3_CONV_REQ_REQ; + ret = adc5_write(adc, ADC5_GEN3_CONV_REQ, &val, 1); + if (ret < 0) + goto handler_end; + } + +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[16], 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 = 0; + ret = adc5_write(adc, ADC5_GEN3_PERPH_CH, &val, 1); + if (ret < 0) { + pr_err("adc write status clear conv_req failed with %d\n", ret); + goto work_unlock; + } + + 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_CH0_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, offset; + + chan_prop = &adc->chan_props[i]; + offset = chan_prop->tm_chan_index; + if (!chan_prop->adc_tm) + continue; + + mutex_lock(&adc->lock); + if ((tm_status[0] & BIT(offset)) && (chan_prop->high_thr_en)) + upper_set = true; + + if ((tm_status[1] & BIT(offset)) && (chan_prop->low_thr_en)) + lower_set = true; + + mutex_unlock(&adc->lock); + + if (!(upper_set || lower_set)) + continue; + + data_low = buf[2 * offset]; + data_high = buf[2 * offset + 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, + 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, + 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, + 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; + + ret = adc5_gen3_poll_wait_hs(adc); + if (ret < 0) + return ret; + + 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; + + /* + * Select TM channel and indicate there is an actual + * conversion request + */ + buf[1] = ADC5_GEN3_CHAN_CONV_REQ | 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 | ADC5_GEN3_FAST_AVG_CTL_EN; + + /* Select ADC channel */ + buf[5] = 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) + return ret; + + conv_req = ADC5_GEN3_CONV_REQ_REQ; + return adc5_write(adc, ADC5_GEN3_CONV_REQ, &conv_req, 1); +} + +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", 1, + 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_USB_SNS_V_16] = ADC5_CHAN_TEMP("usb_sns_v_div_16", 3, + SCALE_HW_CALIB_DEFAULT) + [ADC5_GEN3_VIN_DIV16_MUX] = ADC5_CHAN_TEMP("vin_div_16", 3, + SCALE_HW_CALIB_DEFAULT) + [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 = 0x2ee0, + .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", + .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-spmi-adc5.c b/drivers/iio/adc/qcom-spmi-adc5.c index e910eb2ef076..4c9d60f6d785 100644 --- a/drivers/iio/adc/qcom-spmi-adc5.c +++ b/drivers/iio/adc/qcom-spmi-adc5.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -90,6 +91,23 @@ #define ADC_APP_SID_MASK GENMASK(3, 0) #define ADC7_CONV_TIMEOUT_MS 501 +/* For ADC_PBS on PMIC7 with SW calibration */ +#define ADC7_SW_CALIB_PBS_CALREF_FLAG 0x57 +#define ADC7_SW_CALIB_PBS_CALREF_RDY BIT(7) + +#define ADC7_SW_CALIB_PBS_GND_REF_D0 0x58 +#define ADC7_SW_CALIB_PBS_GND_REF_D1 0x59 +#define ADC7_SW_CALIB_PBS_VREF_VADC_DELTA_D0 0x5a +#define ADC7_SW_CALIB_PBS_VREF_VADC_DELTA_D1 0x5b +#define ADC7_SW_CALIB_PBS_VREF_MBG_DELTA_D0 0x5c +#define ADC7_SW_CALIB_PBS_VREF_MBG_DELTA_D1 0x5d + +/* For ADC_CMN on PMIC7 with SW calibration */ +#define ADC7_SW_CALIB_CMN_PBUS_WRITE_SYNC_CTL 0x4e +#define ADC7_PBUS_WRITE_SYNC_SW_CLK_REQ BIT(2) +#define ADC7_PBUS_WRITE_SYNC_SW_CLK_REQ_MODE BIT(1) +#define ADC7_PBUS_WRITE_SYNC_BYPASS BIT(0) + enum adc5_cal_method { ADC5_NO_CAL = 0, ADC5_RATIOMETRIC_CAL, @@ -135,6 +153,8 @@ struct adc5_channel_prop { * @regmap: SPMI ADC5 peripheral register map field. * @dev: SPMI ADC5 device. * @base: base address for the ADC peripheral. + * @cmn_base: base address for the ADC_CMN peripheral, needed + * for SW calibrated ADC. * @nchannels: number of ADC channels. * @chan_props: array of ADC channel properties. * @iio_chans: array of IIO channels specification. @@ -147,6 +167,7 @@ struct adc5_chip { struct regmap *regmap; struct device *dev; u16 base; + u16 cmn_base; unsigned int nchannels; struct adc5_channel_prop *chan_props; struct iio_chan_spec *iio_chans; @@ -154,21 +175,55 @@ struct adc5_chip { struct completion complete; struct mutex lock; const struct adc5_data *data; + int irq_eoc; }; static int adc5_read(struct adc5_chip *adc, u16 offset, u8 *data, int len) { - return regmap_bulk_read(adc->regmap, adc->base + offset, data, len); + int ret; + + ret = regmap_bulk_read(adc->regmap, adc->base + offset, data, len); + if (ret) + pr_err("adc read to register %#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) { - return regmap_bulk_write(adc->regmap, adc->base + offset, data, len); + int ret; + + ret = regmap_bulk_write(adc->regmap, adc->base + offset, data, len); + if (ret) + pr_err("adc write to register %#x of length:%d failed, ret=%d\n", + offset, len, ret); + + return ret; } static int adc5_masked_write(struct adc5_chip *adc, u16 offset, u8 mask, u8 val) { - return regmap_update_bits(adc->regmap, adc->base + offset, mask, val); + int ret; + + ret = regmap_update_bits(adc->regmap, adc->base + offset, mask, val); + if (ret) + pr_err("adc masked write to register %#x with mask:0x%x failed, ret=%d\n", + offset, mask, ret); + + return ret; +} + +static int adc5_cmn_write(struct adc5_chip *adc, u16 offset, u8 *data, int len) +{ + int ret; + + ret = regmap_bulk_write(adc->regmap, adc->cmn_base + offset, data, len); + if (ret) + pr_err("adc_cmn write to register %#x of length:%d failed, ret=%d\n", + offset, len, ret); + + return ret; } static int adc5_read_voltage_data(struct adc5_chip *adc, u16 *data) @@ -176,11 +231,11 @@ static int adc5_read_voltage_data(struct adc5_chip *adc, u16 *data) int ret; u8 rslt_lsb, rslt_msb; - ret = adc5_read(adc, ADC5_USR_DATA0, &rslt_lsb, sizeof(rslt_lsb)); + ret = adc5_read(adc, ADC5_USR_DATA0, &rslt_lsb, 1); if (ret) return ret; - ret = adc5_read(adc, ADC5_USR_DATA1, &rslt_msb, sizeof(rslt_lsb)); + ret = adc5_read(adc, ADC5_USR_DATA1, &rslt_msb, 1); if (ret) return ret; @@ -317,6 +372,52 @@ static int adc7_configure(struct adc5_chip *adc, return adc5_write(adc, ADC5_USR_CONV_REQ, &conv_req, 1); } +static int adc7_sw_calib_configure(struct adc5_chip *adc, + struct adc5_channel_prop *prop) +{ + int ret; + u8 buf[5], val = 0; + + /* Read registers 0x42 through 0x46 */ + ret = adc5_read(adc, ADC5_USR_DIG_PARAM, buf, sizeof(buf)); + if (ret < 0) + return ret; + + /* Digital param selection */ + adc5_update_dig_param(adc, prop, &buf[0]); + + /* Update fast average sample value */ + buf[1] &= (u8) ~ADC5_USR_FAST_AVG_CTL_SAMPLES_MASK; + buf[1] |= prop->avg_samples | ADC5_USR_FAST_AVG_CTL_EN; + + /* Select ADC channel */ + buf[2] = prop->channel; + + /* Select HW settle delay for channel */ + buf[3] &= (u8) ~ADC5_USR_HW_SETTLE_DELAY_MASK; + buf[3] |= prop->hw_settle_time; + + /* Select ADC enable */ + buf[4] |= ADC5_USR_EN_CTL1_ADC_EN; + + if (!adc->poll_eoc) + reinit_completion(&adc->complete); + + ret = adc5_write(adc, ADC5_USR_DIG_PARAM, buf, sizeof(buf)); + if (ret < 0) + return ret; + + val = ADC7_PBUS_WRITE_SYNC_SW_CLK_REQ | ADC7_PBUS_WRITE_SYNC_SW_CLK_REQ_MODE; + + ret = adc5_cmn_write(adc, ADC7_SW_CALIB_CMN_PBUS_WRITE_SYNC_CTL, &val, 1); + if (ret < 0) + return ret; + + /* Select CONV request */ + val = ADC5_USR_CONV_REQ_REQ; + return adc5_write(adc, ADC5_USR_CONV_REQ, &val, 1); +} + static int adc5_do_conversion(struct adc5_chip *adc, struct adc5_channel_prop *prop, struct iio_chan_spec const *chan, @@ -427,6 +528,59 @@ static int adc7_do_conversion(struct adc5_chip *adc, return ret; } +#define ADC7_SW_CALIB_CONV_TIMEOUT_MS 150 +static int adc7_sw_calib_do_conversion(struct adc5_chip *adc, + struct adc5_channel_prop *prop, u16 *adc_code_volt) +{ + int ret; + unsigned long rc; + u8 status = 0, val; + + mutex_lock(&adc->lock); + + ret = adc7_sw_calib_configure(adc, prop); + if (ret) { + 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(ADC7_SW_CALIB_CONV_TIMEOUT_MS)); + if (!rc) { + pr_err("Reading ADC channel %s timed out\n", + prop->datasheet_name); + ret = -ETIMEDOUT; + goto unlock; + } + + ret = adc5_read(adc, ADC5_USR_STATUS1, &status, 1); + if (ret < 0) + goto unlock; + + if (!(status & ADC5_USR_STATUS1_EOC)) { + pr_err("ADC channel %s EOC bit not set, status=%#x\n", + prop->datasheet_name, status); + ret = -EIO; + goto unlock; + } + + ret = adc5_read_voltage_data(adc, adc_code_volt); + if (ret < 0) + goto unlock; + + val = 0; + ret = adc5_write(adc, ADC5_USR_EN_CTL1, &val, 1); + if (ret < 0) + goto unlock; + + ret = adc5_cmn_write(adc, ADC7_SW_CALIB_CMN_PBUS_WRITE_SYNC_CTL, &val, 1); +unlock: + mutex_unlock(&adc->lock); + + return ret; +} + typedef int (*adc_do_conversion)(struct adc5_chip *adc, struct adc5_channel_prop *prop, struct iio_chan_spec const *chan, @@ -441,6 +595,21 @@ static irqreturn_t adc5_isr(int irq, void *dev_id) return IRQ_HANDLED; } +static struct adc5_channel_prop *adc7_get_channel(struct adc5_chip *adc, + unsigned int num) +{ + unsigned int i; + + for (i = 0; i < adc->nchannels; i++) { + if (adc->chan_props[i].channel == num) + return &adc->chan_props[i]; + } + + pr_err("Invalid channel %02x\n", num); + + return NULL; +} + static int adc5_of_xlate(struct iio_dev *indio_dev, const struct of_phandle_args *iiospec) { @@ -517,6 +686,118 @@ static int adc7_read_raw(struct iio_dev *indio_dev, mask, adc7_do_conversion); } +static int adc7_calib(struct adc5_chip *adc) +{ + int ret = 0; + u16 gnd, vref_1p25, vref_vdd; + u8 buf[2]; + struct adc5_channel_prop *gnd_prop, *vref_1p25_prop, *vref_vdd_prop; + + /* These channels are mandatory, they are used as reference points */ + gnd_prop = adc7_get_channel(adc, ADC7_REF_GND); + if (!gnd_prop) { + dev_err(adc->dev, "GND channel not defined for SW calibration\n"); + return -ENODEV; + } + + vref_1p25_prop = adc7_get_channel(adc, ADC7_1P25VREF); + if (!vref_1p25_prop) { + dev_err(adc->dev, "1.25VREF channel not defined for SW calibration\n"); + return -ENODEV; + } + + vref_vdd_prop = adc7_get_channel(adc, ADC7_VREF_VADC); + if (!vref_vdd_prop) { + dev_err(adc->dev, "VDD channel not defined for SW calibration\n"); + return -ENODEV; + } + + ret = adc7_sw_calib_do_conversion(adc, gnd_prop, &gnd); + if (ret) { + dev_err(adc->dev, "Failed to read GND channel, ret = %d\n", ret); + return ret; + } + + ret = adc7_sw_calib_do_conversion(adc, vref_1p25_prop, &vref_1p25); + if (ret) { + dev_err(adc->dev, "Failed to read 1.25VREF channel, ret = %d\n", ret); + return ret; + } + + ret = adc7_sw_calib_do_conversion(adc, vref_vdd_prop, &vref_vdd); + if (ret) { + dev_err(adc->dev, "Failed to read VDD channel, ret = %d\n", ret); + return ret; + } + + buf[0] = gnd & 0xff; + buf[1] = gnd >> 8; + ret = adc5_write(adc, ADC7_SW_CALIB_PBS_GND_REF_D0, buf, sizeof(buf)); + if (ret) + return ret; + + vref_vdd -= gnd; + buf[0] = vref_vdd & 0xff; + buf[1] = vref_vdd >> 8; + ret = adc5_write(adc, ADC7_SW_CALIB_PBS_VREF_VADC_DELTA_D0, buf, sizeof(buf)); + if (ret) + return ret; + + vref_1p25 -= gnd; + buf[0] = vref_1p25 & 0xff; + buf[1] = vref_1p25 >> 8; + ret = adc5_write(adc, ADC7_SW_CALIB_PBS_VREF_MBG_DELTA_D0, buf, sizeof(buf)); + + if (!ret) + dev_dbg(adc->dev, "SW calibration done, gnd:0x%x vref_vdd:0x%x vref_1p25:0x%x\n", + gnd, vref_vdd, vref_1p25); + + return ret; +} + +static int adc7_sw_calib_conv(struct adc5_chip *adc, struct adc5_channel_prop *prop, int *val) +{ + int ret = 0; + u16 adc_code_volt; + + ret = adc7_calib(adc); + if (ret) + return ret; + + ret = adc7_sw_calib_do_conversion(adc, prop, &adc_code_volt); + if (ret) + return ret; + + return qcom_adc5_hw_scale(prop->scale_fn_type, + prop->prescale, + adc->data, + adc_code_volt, val); +} + +static int adc7_sw_calib_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; + int ret; + + prop = &adc->chan_props[chan->address]; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + ret = adc7_sw_calib_conv(adc, prop, val); + if (ret) + return ret; + + return IIO_VAL_INT; + default: + return -EINVAL; + } + + return 0; +} + static const struct iio_info adc5_info = { .read_raw = adc5_read_raw, .of_xlate = adc5_of_xlate, @@ -527,6 +808,11 @@ static const struct iio_info adc7_info = { .of_xlate = adc7_of_xlate, }; +static const struct iio_info adc7_sw_calib_info = { + .read_raw = adc7_sw_calib_read_raw, + .of_xlate = adc5_of_xlate, +}; + struct adc5_channels { const char *datasheet_name; unsigned int prescale_index; @@ -602,6 +888,8 @@ static const struct adc5_channels adc5_chans_pmic[ADC5_MAX_CHANNEL] = { SCALE_HW_CALIB_THERM_100K_PULLUP) [ADC5_AMUX_THM2] = ADC5_CHAN_TEMP("amux_thm2", 0, SCALE_HW_CALIB_PM5_SMB_TEMP) + [ADC5_PARALLEL_ISENSE] = ADC5_CHAN_VOLT("parallel_isense", 0, + SCALE_HW_CALIB_PM5_CUR) [ADC5_GPIO1_100K_PU] = ADC5_CHAN_TEMP("gpio1_100k_pu", 0, SCALE_HW_CALIB_THERM_100K_PULLUP) [ADC5_GPIO2_100K_PU] = ADC5_CHAN_TEMP("gpio2_100k_pu", 0, @@ -617,21 +905,25 @@ static const struct adc5_channels adc7_chans_pmic[ADC5_MAX_CHANNEL] = { SCALE_HW_CALIB_DEFAULT) [ADC7_1P25VREF] = ADC5_CHAN_VOLT("vref_1p25", 0, SCALE_HW_CALIB_DEFAULT) + [ADC7_VREF_VADC] = ADC5_CHAN_VOLT("vref_vadc", 0, + SCALE_HW_CALIB_DEFAULT) [ADC7_VPH_PWR] = ADC5_CHAN_VOLT("vph_pwr", 1, SCALE_HW_CALIB_DEFAULT) [ADC7_VBAT_SNS] = ADC5_CHAN_VOLT("vbat_sns", 3, SCALE_HW_CALIB_DEFAULT) - [ADC7_AMUX_THM3] = ADC5_CHAN_TEMP("smb_temp", 0, + [ADC7_USB_IN_V_16] = ADC5_CHAN_VOLT("usb_in_v_div_16", 8, + SCALE_HW_CALIB_DEFAULT) + [ADC7_AMUX_THM3] = ADC5_CHAN_TEMP("smb_temp", 9, SCALE_HW_CALIB_PM7_SMB_TEMP) [ADC7_CHG_TEMP] = ADC5_CHAN_TEMP("chg_temp", 0, SCALE_HW_CALIB_PM7_CHG_TEMP) - [ADC7_IIN_FB] = ADC5_CHAN_CUR("iin_fb", 9, + [ADC7_IIN_FB] = ADC5_CHAN_CUR("iin_fb", 10, SCALE_HW_CALIB_CUR) - [ADC7_ICHG_SMB] = ADC5_CHAN_CUR("ichg_smb", 10, + [ADC7_IIN_SMB] = ADC5_CHAN_CUR("iin_smb", 12, SCALE_HW_CALIB_CUR) - [ADC7_IIN_SMB] = ADC5_CHAN_CUR("iin_smb", 11, + [ADC7_ICHG_SMB] = ADC5_CHAN_CUR("ichg_smb", 13, SCALE_HW_CALIB_CUR) - [ADC7_ICHG_FB] = ADC5_CHAN_CUR("ichg_fb", 12, + [ADC7_ICHG_FB] = ADC5_CHAN_CUR("ichg_fb", 14, SCALE_HW_CALIB_CUR_RAW) [ADC7_DIE_TEMP] = ADC5_CHAN_TEMP("die_temp", 0, SCALE_HW_CALIB_PMIC_THERM_PM7) @@ -682,6 +974,8 @@ static const struct adc5_channels adc5_chans_rev2[ADC5_MAX_CHANNEL] = { SCALE_HW_CALIB_THERM_100K_PULLUP) [ADC5_XO_THERM_100K_PU] = ADC5_CHAN_TEMP("xo_therm_100k_pu", 0, SCALE_HW_CALIB_THERM_100K_PULLUP) + [ADC5_GPIO2_100K_PU] = ADC5_CHAN_TEMP("gpio2_100k_pu", 0, + SCALE_HW_CALIB_THERM_100K_PULLUP) }; static int adc5_get_dt_channel_data(struct adc5_chip *adc, @@ -801,6 +1095,8 @@ static int adc5_get_dt_channel_data(struct adc5_chip *adc, if (of_property_read_bool(node, "qcom,ratiometric")) prop->cal_method = ADC5_RATIOMETRIC_CAL; + else if (of_property_read_bool(node, "qcom,no-cal")) + prop->cal_method = ADC5_NO_CAL; else prop->cal_method = ADC5_ABSOLUTE_CAL; @@ -816,6 +1112,7 @@ static int adc5_get_dt_channel_data(struct adc5_chip *adc, } static const struct adc5_data adc5_data_pmic = { + .name = "pm-adc5", .full_scale_code_volt = 0x70e4, .full_scale_code_cur = 0x2710, .adc_chans = adc5_chans_pmic, @@ -831,6 +1128,7 @@ static const struct adc5_data adc5_data_pmic = { }; static const struct adc5_data adc7_data_pmic = { + .name = "pm-adc7", .full_scale_code_volt = 0x70e4, .adc_chans = adc7_chans_pmic, .info = &adc7_info, @@ -842,7 +1140,19 @@ static const struct adc5_data adc7_data_pmic = { 64000, 128000}, }; +static const struct adc5_data adc5_data_pmic5_lite = { + .name = "pm-adc5-lite", + .full_scale_code_volt = 0x70e4, + /* On PMI632, IBAT LSB = 5A/32767 */ + .full_scale_code_cur = 5000, + .adc_chans = adc5_chans_pmic, + .decimation = (unsigned int []) {250, 420, 840}, + .hw_settle_1 = (unsigned int []) {15, 100, 200, 300, 400, 500, 600, 700, + 800, 900, 1, 2, 4, 6, 8, 10}, +}; + static const struct adc5_data adc5_data_pmic_rev2 = { + .name = "pm-adc4-rev2", .full_scale_code_volt = 0x4000, .full_scale_code_cur = 0x1800, .adc_chans = adc5_chans_rev2, @@ -866,10 +1176,18 @@ static const struct of_device_id adc5_match_table[] = { .compatible = "qcom,spmi-adc7", .data = &adc7_data_pmic, }, + { + .compatible = "qcom,spmi-adc7-sw-calib", + .data = &adc7_data_pmic, + }, { .compatible = "qcom,spmi-adc-rev2", .data = &adc5_data_pmic_rev2, }, + { + .compatible = "qcom,spmi-adc5-lite", + .data = &adc5_data_pmic5_lite, + }, { } }; MODULE_DEVICE_TABLE(of, adc5_match_table); @@ -936,8 +1254,11 @@ static int adc5_probe(struct platform_device *pdev) struct iio_dev *indio_dev; struct adc5_chip *adc; struct regmap *regmap; - int ret, irq_eoc; + const char *irq_name; + const __be32 *prop_addr; + int ret; u32 reg; + u8 val; regmap = dev_get_regmap(dev->parent, NULL); if (!regmap) @@ -954,7 +1275,27 @@ static int adc5_probe(struct platform_device *pdev) adc = iio_priv(indio_dev); adc->regmap = regmap; adc->dev = dev; - adc->base = reg; + + 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 cmn resource\n"); + else + adc->cmn_base = be32_to_cpu(*prop_addr); + + + if (of_device_is_compatible(node, "qcom,spmi-adc7-sw-calib")) { + if (!adc->cmn_base) { + pr_err("ADC_CMN undefined\n"); + return -ENODEV; + } + } init_completion(&adc->complete); mutex_init(&adc->lock); @@ -965,18 +1306,33 @@ static int adc5_probe(struct platform_device *pdev) return ret; } - irq_eoc = platform_get_irq(pdev, 0); - if (irq_eoc < 0) { - if (irq_eoc == -EPROBE_DEFER || irq_eoc == -EINVAL) - return irq_eoc; + adc->irq_eoc = platform_get_irq(pdev, 0); + if (adc->irq_eoc < 0) { + if (adc->irq_eoc == -EPROBE_DEFER || adc->irq_eoc == -EINVAL) + return adc->irq_eoc; adc->poll_eoc = true; } else { - ret = devm_request_irq(dev, irq_eoc, adc5_isr, 0, - "pm-adc5", adc); + irq_name = "pm-adc5"; + if (adc->data->name) + irq_name = adc->data->name; + + ret = devm_request_irq(dev, adc->irq_eoc, adc5_isr, 0, + irq_name, adc); if (ret) return ret; } + if (of_device_is_compatible(node, "qcom,spmi-adc7-sw-calib")) { + ret = adc7_calib(adc); + if (ret) + return ret; + + val = ADC7_SW_CALIB_PBS_CALREF_RDY; + ret = adc5_write(adc, ADC7_SW_CALIB_PBS_CALREF_FLAG, &val, 1); + if (ret < 0) + return ret; + } + indio_dev->name = pdev->name; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->info = adc->data->info; @@ -986,10 +1342,39 @@ static int adc5_probe(struct platform_device *pdev) return devm_iio_device_register(dev, indio_dev); } +static int adc_restore(struct device *dev) +{ + int ret = 0; + struct adc5_chip *adc = dev_get_drvdata(dev); + + if (adc->irq_eoc > 0) + ret = devm_request_irq(dev, adc->irq_eoc, adc5_isr, 0, + "pm-adc5", adc); + + return ret; +} + +static int adc_freeze(struct device *dev) +{ + struct adc5_chip *adc = dev_get_drvdata(dev); + + if (adc->irq_eoc > 0) + devm_free_irq(dev, adc->irq_eoc, adc); + + return 0; +} + +static const struct dev_pm_ops adc_pm_ops = { + .freeze = adc_freeze, + .thaw = adc_restore, + .restore = adc_restore, +}; + static struct platform_driver adc5_driver = { .driver = { .name = "qcom-spmi-adc5", .of_match_table = adc5_match_table, + .pm = &adc_pm_ops, }, .probe = adc5_probe, }; diff --git a/drivers/iio/adc/qcom-vadc-common.c b/drivers/iio/adc/qcom-vadc-common.c index 6c47d028a50c..75af6bb3d49b 100644 --- a/drivers/iio/adc/qcom-vadc-common.c +++ b/drivers/iio/adc/qcom-vadc-common.c @@ -520,6 +520,82 @@ static const struct vadc_map_pt adcmap7_100k[] = { { 2420, 130048 } }; +/* + * Resistance to temperature table for batt_therm. + */ +static const struct vadc_map_pt adcmap_gen3_batt_therm_100k[] = { + { 5319890, -400 }, + { 4555860, -380 }, + { 3911780, -360 }, + { 3367320, -340 }, + { 2905860, -320 }, + { 2513730, -300 }, + { 2179660, -280 }, + { 1894360, -260 }, + { 1650110, -240 }, + { 1440520, -220 }, + { 1260250, -200 }, + { 1104850, -180 }, + { 970600, -160 }, + { 854370, -140 }, + { 753530, -120 }, + { 665860, -100 }, + { 589490, -80 }, + { 522830, -60 }, + { 464540, -40 }, + { 413470, -20 }, + { 368640, 0 }, + { 329220, 20 }, + { 294490, 40 }, + { 263850, 60 }, + { 236770, 80 }, + { 212790, 100 }, + { 191530, 120 }, + { 172640, 140 }, + { 155840, 160 }, + { 140880, 180 }, + { 127520, 200 }, + { 115590, 220 }, + { 104910, 240 }, + { 95350, 260 }, + { 86760, 280 }, + { 79050, 300 }, + { 72110, 320 }, + { 65860, 340 }, + { 60220, 360 }, + { 55130, 380 }, + { 50520, 400 }, + { 46350, 420 }, + { 42570, 440 }, + { 39140, 460 }, + { 36030, 480 }, + { 33190, 500 }, + { 30620, 520 }, + { 28260, 540 }, + { 26120, 560 }, + { 24160, 580 }, + { 22370, 600 }, + { 20730, 620 }, + { 19230, 640 }, + { 17850, 660 }, + { 16580, 680 }, + { 15420, 700 }, + { 14350, 720 }, + { 13370, 740 }, + { 12470, 760 }, + { 11630, 780 }, + { 10860, 800 }, + { 10150, 820 }, + { 9490, 840 }, + { 8880, 860 }, + { 8320, 880 }, + { 7800, 900 }, + { 7310, 920 }, + { 6860, 940 }, + { 6450, 960 }, + { 6060, 980 } +}; + static const struct u32_fract adc5_prescale_ratios[] = { { .numerator = 1, .denominator = 1 }, { .numerator = 1, .denominator = 3 }, @@ -530,10 +606,12 @@ static const struct u32_fract adc5_prescale_ratios[] = { { .numerator = 10, .denominator = 81 }, { .numerator = 1, .denominator = 10 }, { .numerator = 1, .denominator = 16 }, + { .numerator = 40, .denominator = 41 }, /* PM7_SMB_TEMP */ /* 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 = 32, .denominator = 100 }, /* IIN_FB, IIN_SMB */ + { .numerator = 16, .denominator = 100 }, /* ICHG_SMB */ + { .numerator = 1280, .denominator = 4100 }, /* IIN_SMB_new */ + { .numerator = 640, .denominator = 4100 }, /* ICHG_SMB_new */ { .numerator = 1000, .denominator = 305185 }, /* ICHG_FB */ { .numerator = 1000, .denominator = 610370 }, /* ICHG_FB_2X */ }; @@ -542,14 +620,21 @@ static int qcom_vadc_scale_hw_calib_volt( const struct u32_fract *prescale, const struct adc5_data *data, u16 adc_code, int *result_uv); +/* Current scaling for PMIC7 */ static int qcom_vadc_scale_hw_calib_current( const struct u32_fract *prescale, const struct adc5_data *data, u16 adc_code, int *result_ua); +/* Raw current for PMIC7 */ static int qcom_vadc_scale_hw_calib_current_raw( const struct u32_fract *prescale, const struct adc5_data *data, u16 adc_code, int *result_ua); +/* Current scaling for PMIC5 */ +static int qcom_vadc5_scale_hw_calib_current( + const struct u32_fract *prescale, + const struct adc5_data *data, + u16 adc_code, int *result_ua); static int qcom_vadc_scale_hw_calib_therm( const struct u32_fract *prescale, const struct adc5_data *data, @@ -582,6 +667,22 @@ static int qcom_vadc_scale_hw_smb1398_temp( const struct u32_fract *prescale, const struct adc5_data *data, u16 adc_code, int *result_mdec); +static int qcom_vadc_scale_hw_pm2250_s3_die_temp( + const struct u32_fract *prescale, + const struct adc5_data *data, + u16 adc_code, int *result_mdec); +static int qcom_adc5_gen3_scale_hw_calib_batt_therm_100( + const struct u32_fract *prescale, + const struct adc5_data *data, + u16 adc_code, int *result_mdec); +static int qcom_adc5_gen3_scale_hw_calib_batt_id_100( + const struct u32_fract *prescale, + const struct adc5_data *data, + u16 adc_code, int *result_mdec); +static int qcom_adc5_gen3_scale_hw_calib_usb_in_current( + const struct u32_fract *prescale, + const struct adc5_data *data, + u16 adc_code, int *result_mdec); static int qcom_vadc_scale_hw_chg5_temp( const struct u32_fract *prescale, const struct adc5_data *data, @@ -603,6 +704,7 @@ static struct qcom_adc5_scale_type scale_adc5_fn[] = { [SCALE_HW_CALIB_DEFAULT] = {qcom_vadc_scale_hw_calib_volt}, [SCALE_HW_CALIB_CUR] = {qcom_vadc_scale_hw_calib_current}, [SCALE_HW_CALIB_CUR_RAW] = {qcom_vadc_scale_hw_calib_current_raw}, + [SCALE_HW_CALIB_PM5_CUR] = {qcom_vadc5_scale_hw_calib_current}, [SCALE_HW_CALIB_THERM_100K_PULLUP] = {qcom_vadc_scale_hw_calib_therm}, [SCALE_HW_CALIB_BATT_THERM_100K] = { qcom_vadc_scale_hw_calib_batt_therm_100}, @@ -619,6 +721,10 @@ static struct qcom_adc5_scale_type scale_adc5_fn[] = { [SCALE_HW_CALIB_PM5_CHG_TEMP] = {qcom_vadc_scale_hw_chg5_temp}, [SCALE_HW_CALIB_PM5_SMB_TEMP] = {qcom_vadc_scale_hw_smb_temp}, [SCALE_HW_CALIB_PM5_SMB1398_TEMP] = {qcom_vadc_scale_hw_smb1398_temp}, + [SCALE_HW_CALIB_PM2250_S3_DIE_TEMP] = {qcom_vadc_scale_hw_pm2250_s3_die_temp}, + [SCALE_HW_CALIB_PM5_GEN3_BATT_THERM_100K] = {qcom_adc5_gen3_scale_hw_calib_batt_therm_100}, + [SCALE_HW_CALIB_PM5_GEN3_BATT_ID_100K] = {qcom_adc5_gen3_scale_hw_calib_batt_id_100}, + [SCALE_HW_CALIB_PM5_GEN3_USB_IN_I] = {qcom_adc5_gen3_scale_hw_calib_usb_in_current}, [SCALE_HW_CALIB_PM7_SMB_TEMP] = {qcom_vadc_scale_hw_pm7_smb_temp}, [SCALE_HW_CALIB_PM7_CHG_TEMP] = {qcom_vadc_scale_hw_pm7_chg_temp}, }; @@ -875,6 +981,30 @@ static int qcom_vadc_scale_hw_calib_current( return 0; } +static int qcom_vadc5_scale_hw_calib_current( + const struct u32_fract *prescale, + const struct adc5_data *data, + u16 adc_code, int *result_ua) +{ + s64 voltage = 0, result = 0; + bool positive = true; + + if (adc_code & ADC5_USR_DATA_CHECK) { + adc_code = ~adc_code + 1; + positive = false; + } + + voltage = (s64)(s16) adc_code * data->full_scale_code_cur * 1000; + voltage = div64_s64(voltage, VADC5_MAX_CODE); + result = div64_s64(voltage * prescale->denominator, prescale->numerator); + *result_ua = result; + + if (!positive) + *result_ua = -result; + + return 0; +} + static int qcom_vadc_scale_hw_calib_volt( const struct u32_fract *prescale, const struct adc5_data *data, @@ -1059,6 +1189,104 @@ static int qcom_vadc_scale_hw_smb1398_temp( return 0; } +static int qcom_vadc_scale_hw_pm2250_s3_die_temp( + const struct u32_fract *prescale, + const struct adc5_data *data, + u16 adc_code, int *result_mdec) +{ + s64 voltage = 0, adc_vdd_ref_mv = 1875; + + if (adc_code > VADC5_MAX_CODE) + adc_code = 0; + + /* (ADC code * vref_vadc (1.875V)) / full_scale_code */ + voltage = (s64) adc_code * adc_vdd_ref_mv * 1000; + voltage = div64_s64(voltage, data->full_scale_code_volt); + if (voltage > 0) { + voltage *= prescale->denominator; + voltage = div64_s64(voltage, prescale->numerator); + } else { + voltage = 0; + } + + voltage = PMIC5_PM2250_S3_DIE_TEMP_CONSTANT - voltage; + voltage *= 100000; + voltage = div64_s64(voltage, PMIC5_PM2250_S3_DIE_TEMP_SCALE_FACTOR); + + *result_mdec = voltage; + + return 0; +} + +static int qcom_adc5_gen3_scale_hw_calib_batt_therm_100( + const struct u32_fract *prescale, + const struct adc5_data *data, + u16 adc_code, int *result_mdec) +{ + s64 resistance = 0; + int ret, result = 0; + + if (adc_code >= RATIO_MAX_ADC7) + return -EINVAL; + + /* (ADC code * R_PULLUP (100Kohm)) / (full_scale_code - ADC code)*/ + resistance = (s64) adc_code * R_PU_100K; + resistance = div64_s64(resistance, (RATIO_MAX_ADC7 - adc_code)); + + ret = qcom_vadc_map_voltage_temp(adcmap_gen3_batt_therm_100k, + ARRAY_SIZE(adcmap_gen3_batt_therm_100k), + resistance, &result); + if (ret) + return ret; + + *result_mdec = result; + + return 0; +} + +static int qcom_adc5_gen3_scale_hw_calib_batt_id_100( + const struct u32_fract *prescale, + const struct adc5_data *data, + u16 adc_code, int *result_mdec) +{ + s64 resistance = 0; + + if (adc_code >= RATIO_MAX_ADC7) + return -EINVAL; + + /* (ADC code * R_PULLUP (100Kohm)) / (full_scale_code - ADC code)*/ + resistance = (s64) adc_code * R_PU_100K; + resistance = div64_s64(resistance, (RATIO_MAX_ADC7 - adc_code)); + + *result_mdec = (int)resistance; + + return 0; +}; + +static int qcom_adc5_gen3_scale_hw_calib_usb_in_current( + const struct u32_fract *prescale, + const struct adc5_data *data, + u16 adc_code, int *result_ua) +{ + s64 voltage = 0, result = 0; + bool positive = true; + + if (adc_code & ADC5_USR_DATA_CHECK) { + adc_code = ~adc_code + 1; + positive = false; + } + + voltage = (s64)(s16) adc_code * 1000000; + voltage = div64_s64(voltage, PMIC5_GEN3_USB_IN_I_SCALE_FACTOR); + result = div64_s64(voltage * prescale->denominator, prescale->numerator); + *result_ua = (int)result; + + if (!positive) + *result_ua = -(int)result; + + return 0; +}; + static int qcom_vadc_scale_hw_chg5_temp( const struct u32_fract *prescale, const struct adc5_data *data, @@ -1071,6 +1299,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/dt-bindings/iio/qcom,spmi-adc5-gen3-pm5100.h b/include/dt-bindings/iio/qcom,spmi-adc5-gen3-pm5100.h new file mode 100644 index 000000000000..13749464bff2 --- /dev/null +++ b/include/dt-bindings/iio/qcom,spmi-adc5-gen3-pm5100.h @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2021 The Linux Foundation. All rights reserved. + */ + +#ifndef _DT_BINDINGS_QCOM_SPMI_VADC_PM5100_H +#define _DT_BINDINGS_QCOM_SPMI_VADC_PM5100_H + +#ifndef PM5100_SID +#define PM5100_SID 0 +#endif + +/* ADC channels for PM5100_ADC for PMIC5 Gen3 */ +#define PM5100_ADC5_GEN3_OFFSET_REF (PM5100_SID << 8 | 0x0) +#define PM5100_ADC5_GEN3_1P25VREF (PM5100_SID << 8 | 0x01) +#define PM5100_ADC5_GEN3_VREF_VADC (PM5100_SID << 8 | 0x02) +#define PM5100_ADC5_GEN3_DIE_TEMP (PM5100_SID << 8 | 0x03) + +#define PM5100_ADC5_GEN3_AMUX1_THM (PM5100_SID << 8 | 0x04) +#define PM5100_ADC5_GEN3_BAT_ID (PM5100_SID << 8 | 0x05) +#define PM5100_ADC5_GEN3_BATT_THM (PM5100_SID << 8 | 0x06) +#define PM5100_ADC5_GEN3_AMUX4_THM (PM5100_SID << 8 | 0x07) +#define PM5100_ADC5_GEN3_AMUX5_THM (PM5100_SID << 8 | 0x08) +#define PM5100_ADC5_GEN3_AMUX6_THM (PM5100_SID << 8 | 0x09) +#define PM5100_ADC5_GEN3_AMUX1_GPIO10 (PM5100_SID << 8 | 0x0a) +#define PM5100_ADC5_GEN3_AMUX2_GPIO11 (PM5100_SID << 8 | 0x0b) +#define PM5100_ADC5_GEN3_AMUX3_GPIO (PM5100_SID << 8 | 0x0c) +#define PM5100_ADC5_GEN3_AMUX4_GPIO (PM5100_SID << 8 | 0x0d) + +#define PM5100_ADC5_GEN3_CHG_TEMP (PM5100_SID << 8 | 0x10) +#define PM5100_ADC5_GEN3_USB_SNS_V_16 (PM5100_SID << 8 | 0x11) +#define PM5100_ADC5_GEN3_VIN_DIV16_MUX (PM5100_SID << 8 | 0x12) +#define PM5100_ADC5_GEN3_USB_IN_I (PM5100_SID << 8 | 0x17) +#define PM5100_ADC5_GEN3_ICHG_FB (PM5100_SID << 8 | 0xa1) + +/* 30k pull-up1 */ +#define PM5100_ADC5_GEN3_AMUX1_THM_30K_PU (PM5100_SID << 8 | 0x24) +#define PM5100_ADC5_GEN3_BAT_ID_30K_PU (PM5100_SID << 8 | 0x25) +#define PM5100_ADC5_GEN3_BATT_THM_30K_PU (PM5100_SID << 8 | 0x26) +#define PM5100_ADC5_GEN3_AMUX4_THM_30K_PU (PM5100_SID << 8 | 0x27) +#define PM5100_ADC5_GEN3_AMUX5_THM_30K_PU (PM5100_SID << 8 | 0x28) +#define PM5100_ADC5_GEN3_AMUX6_THM_30K_PU (PM5100_SID << 8 | 0x29) +#define PM5100_ADC5_GEN3_AMUX1_GPIO10_30K_PU (PM5100_SID << 8 | 0x2a) +#define PM5100_ADC5_GEN3_AMUX2_GPIO11_30K_PU (PM5100_SID << 8 | 0x2b) +#define PM5100_ADC5_GEN3_AMUX3_GPIO_30K_PU (PM5100_SID << 8 | 0x2c) +#define PM5100_ADC5_GEN3_AMUX4_GPIO_30K_PU (PM5100_SID << 8 | 0x2d) + +/* 100k pull-up2 */ +#define PM5100_ADC5_GEN3_AMUX1_THM_100K_PU (PM5100_SID << 8 | 0x44) +#define PM5100_ADC5_GEN3_BAT_ID_100K_PU (PM5100_SID << 8 | 0x45) +#define PM5100_ADC5_GEN3_BATT_THM_100K_PU (PM5100_SID << 8 | 0x46) +#define PM5100_ADC5_GEN3_AMUX4_THM_100K_PU (PM5100_SID << 8 | 0x47) +#define PM5100_ADC5_GEN3_AMUX5_THM_100K_PU (PM5100_SID << 8 | 0x48) +#define PM5100_ADC5_GEN3_AMUX6_THM_100K_PU (PM5100_SID << 8 | 0x49) +#define PM5100_ADC5_GEN3_AMUX1_GPIO10_100K_PU (PM5100_SID << 8 | 0x4a) +#define PM5100_ADC5_GEN3_AMUX2_GPIO11_100K_PU (PM5100_SID << 8 | 0x4b) +#define PM5100_ADC5_GEN3_AMUX3_GPIO_100K_PU (PM5100_SID << 8 | 0x4c) +#define PM5100_ADC5_GEN3_AMUX4_GPIO_100K_PU (PM5100_SID << 8 | 0x4d) + +/* 400k pull-up3 */ +#define PM5100_ADC5_GEN3_AMUX1_THM_400K_PU (PM5100_SID << 8 | 0x64) +#define PM5100_ADC5_GEN3_BAT_ID_400K_PU (PM5100_SID << 8 | 0x65) +#define PM5100_ADC5_GEN3_BATT_THM_400K_PU (PM5100_SID << 8 | 0x66) +#define PM5100_ADC5_GEN3_AMUX4_THM_400K_PU (PM5100_SID << 8 | 0x67) +#define PM5100_ADC5_GEN3_AMUX5_THM_400K_PU (PM5100_SID << 8 | 0x68) +#define PM5100_ADC5_GEN3_AMUX6_THM_400K_PU (PM5100_SID << 8 | 0x69) +#define PM5100_ADC5_GEN3_AMUX1_GPIO10_400K_PU (PM5100_SID << 8 | 0x6a) +#define PM5100_ADC5_GEN3_AMUX2_GPIO11_400K_PU (PM5100_SID << 8 | 0x6b) +#define PM5100_ADC5_GEN3_AMUX3_GPIO_400K_PU (PM5100_SID << 8 | 0x6c) +#define PM5100_ADC5_GEN3_AMUX4_GPIO_400K_PU (PM5100_SID << 8 | 0x6d) + +/* 1/3 Divider */ +#define PM5100_ADC5_GEN3_GPIO10_DIV3 (PM5100_SID << 8 | 0x8a) +#define PM5100_ADC5_GEN3_GPIO11_DIV3 (PM5100_SID << 8 | 0x8b) + +#define PM5100_ADC5_GEN3_VPH_PWR (PM5100_SID << 8 | 0x8e) +#define PM5100_ADC5_GEN3_VBAT_SNS_QBG (PM5100_SID << 8 | 0x8f) + +#endif /* _DT_BINDINGS_QCOM_SPMI_VADC_PM5100_H */ diff --git a/include/dt-bindings/iio/qcom,spmi-vadc.h b/include/dt-bindings/iio/qcom,spmi-vadc.h index 1cfd3669a665..f04ab1a7e131 100644 --- a/include/dt-bindings/iio/qcom,spmi-vadc.h +++ b/include/dt-bindings/iio/qcom,spmi-vadc.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* - * Copyright (c) 2012-2014,2018-2020 The Linux Foundation. All rights reserved. + * Copyright (c) 2012-2014,2018-2021 The Linux Foundation. All rights reserved. */ #ifndef _DT_BINDINGS_QCOM_SPMI_VADC_H @@ -300,4 +300,109 @@ #define ADC7_SBUx 0x94 #define ADC7_VBAT_2S_MID 0x96 +/* ADC channels for PMIC5 Gen3 */ + +#define ADC5_GEN3_OFFSET_REF 0x00 +#define ADC5_GEN3_1P25VREF 0x01 +#define ADC5_GEN3_VREF_VADC 0x02 +#define ADC5_GEN3_DIE_TEMP 0x03 + +#define ADC5_GEN3_AMUX1_THM 0x04 +#define ADC5_GEN3_AMUX2_THM 0x05 +#define ADC5_GEN3_AMUX3_THM 0x06 +#define ADC5_GEN3_AMUX4_THM 0x07 +#define ADC5_GEN3_AMUX5_THM 0x08 +#define ADC5_GEN3_AMUX6_THM 0x09 +#define ADC5_GEN3_AMUX1_GPIO 0x0a +#define ADC5_GEN3_AMUX2_GPIO 0x0b +#define ADC5_GEN3_AMUX3_GPIO 0x0c +#define ADC5_GEN3_AMUX4_GPIO 0x0d + +#define ADC5_GEN3_CHG_TEMP 0x10 +#define ADC5_GEN3_USB_SNS_V_16 0x11 +#define ADC5_GEN3_VIN_DIV16_MUX 0x12 +#define ADC5_GEN3_VREF_BAT_THERM 0x15 +#define ADC5_GEN3_IIN_FB 0x17 +#define ADC5_GEN3_ICHG_SMB 0x18 +#define ADC5_GEN3_IIN_SMB 0x19 +#define ADC5_GEN3_ICHG_FB 0xa1 + +/* 30k pull-up1 */ +#define ADC5_GEN3_AMUX1_THM_30K_PU 0x24 +#define ADC5_GEN3_AMUX2_THM_30K_PU 0x25 +#define ADC5_GEN3_AMUX3_THM_30K_PU 0x26 +#define ADC5_GEN3_AMUX4_THM_30K_PU 0x27 +#define ADC5_GEN3_AMUX5_THM_30K_PU 0x28 +#define ADC5_GEN3_AMUX6_THM_30K_PU 0x29 +#define ADC5_GEN3_AMUX1_GPIO_30K_PU 0x2a +#define ADC5_GEN3_AMUX2_GPIO_30K_PU 0x2b +#define ADC5_GEN3_AMUX3_GPIO_30K_PU 0x2c +#define ADC5_GEN3_AMUX4_GPIO_30K_PU 0x2d + +/* 100k pull-up2 */ +#define ADC5_GEN3_AMUX1_THM_100K_PU 0x44 +#define ADC5_GEN3_AMUX2_THM_100K_PU 0x45 +#define ADC5_GEN3_AMUX3_THM_100K_PU 0x46 +#define ADC5_GEN3_AMUX4_THM_100K_PU 0x47 +#define ADC5_GEN3_AMUX5_THM_100K_PU 0x48 +#define ADC5_GEN3_AMUX6_THM_100K_PU 0x49 +#define ADC5_GEN3_AMUX1_GPIO_100K_PU 0x4a +#define ADC5_GEN3_AMUX2_GPIO_100K_PU 0x4b +#define ADC5_GEN3_AMUX3_GPIO_100K_PU 0x4c +#define ADC5_GEN3_AMUX4_GPIO_100K_PU 0x4d + +/* 400k pull-up3 */ +#define ADC5_GEN3_AMUX1_THM_400K_PU 0x64 +#define ADC5_GEN3_AMUX2_THM_400K_PU 0x65 +#define ADC5_GEN3_AMUX3_THM_400K_PU 0x66 +#define ADC5_GEN3_AMUX4_THM_400K_PU 0x67 +#define ADC5_GEN3_AMUX5_THM_400K_PU 0x68 +#define ADC5_GEN3_AMUX6_THM_400K_PU 0x69 +#define ADC5_GEN3_AMUX1_GPIO_400K_PU 0x6a +#define ADC5_GEN3_AMUX2_GPIO_400K_PU 0x6b +#define ADC5_GEN3_AMUX3_GPIO_400K_PU 0x6c +#define ADC5_GEN3_AMUX4_GPIO_400K_PU 0x6d + +/* 1/3 Divider */ +#define ADC5_GEN3_AMUX1_GPIO_DIV3 0x8a +#define ADC5_GEN3_AMUX2_GPIO_DIV3 0x8b +#define ADC5_GEN3_AMUX3_GPIO_DIV3 0x8c + +#define ADC5_GEN3_VPH_PWR 0x8e +#define ADC5_GEN3_VBAT_SNS_QBG 0x8f + +#define ADC5_GEN3_VBAT_SNS_CHGR 0x94 +#define ADC5_GEN3_VBAT_2S_MID_QBG 0x96 +#define ADC5_GEN3_VBAT_2S_MID_CHGR 0x9d + +#define ADC5_OFFSET_EXT2 0xf8 + +/* VADC scale function index */ +#define ADC_SCALE_DEFAULT 0 +#define ADC_SCALE_THERM_100K_PULLUP 1 +#define ADC_SCALE_PMIC_THERM 2 +#define ADC_SCALE_XOTHERM 3 +#define ADC_SCALE_PMI_CHG_TEMP 4 +#define ADC_SCALE_HW_CALIB_DEFAULT 5 +#define ADC_SCALE_HW_CALIB_THERM_100K_PULLUP 6 +#define ADC_SCALE_HW_CALIB_XOTHERM 7 +#define ADC_SCALE_HW_CALIB_THERM_100K_PU_PM7 8 +#define ADC_SCALE_HW_CALIB_PMIC_THERM 9 +#define ADC_SCALE_HW_CALIB_PMIC_THERM_PM7 10 +#define ADC_SCALE_HW_CALIB_PM5_CHG_TEMP 11 +#define ADC_SCALE_HW_CALIB_PM5_SMB_TEMP 12 +#define ADC_SCALE_HW_CALIB_BATT_THERM_100K 13 +#define ADC_SCALE_HW_CALIB_BATT_THERM_30K 14 +#define ADC_SCALE_HW_CALIB_BATT_THERM_400K 15 +#define ADC_SCALE_HW_CALIB_PM5_SMB1398_TEMP 16 +#define ADC_SCALE_HW_CALIB_PM7_SMB_TEMP 17 +#define ADC_SCALE_HW_CALIB_PM7_CHG_TEMP 18 +#define ADC_SCALE_HW_CALIB_CUR 19 +#define ADC_SCALE_HW_CALIB_CUR_RAW 20 +#define ADC_SCALE_HW_CALIB_PM2250_S3_DIE_TEMP 21 +#define ADC_SCALE_HW_CALIB_PM5_CUR 22 +#define ADC_SCALE_HW_CALIB_PM5_GEN3_BATT_THERM_100K 23 +#define ADC_SCALE_HW_CALIB_PM5_GEN3_BATT_ID_100K 24 +#define ADC_SCALE_HW_CALIB_PM5_GEN3_USB_IN_I 25 + #endif /* _DT_BINDINGS_QCOM_SPMI_VADC_H */ 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 3ed870a1eb6b..cd11b5aba4bc 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 @@ -47,6 +48,9 @@ #define PMIC5_SMB1398_TEMP_CONSTANT 268235 #define PMIC5_SMB1398_TEMP_SCALE_FACTOR 340 +#define PMIC5_PM2250_S3_DIE_TEMP_SCALE_FACTOR 187263 +#define PMIC5_PM2250_S3_DIE_TEMP_CONSTANT 720100 + #define PMI_CHG_SCALE_1 -138890 #define PMI_CHG_SCALE_2 391750000000LL @@ -57,6 +61,10 @@ #define R_PU_100K 100000 #define RATIO_MAX_ADC7 BIT(14) +#define PMIC5_GEN3_USB_IN_I_SCALE_FACTOR 9248 + +#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 @@ -82,6 +90,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. @@ -107,13 +163,13 @@ struct vadc_linear_graph { * charger temperature. * SCALE_HW_CALIB_PM5_SMB_TEMP: Returns result in millidegrees for PMIC5 * SMB1390 temperature. - * SCALE_HW_CALIB_BATT_THERM_100K: Returns battery thermistor voltage in + * SCALE_HW_CALIB_BATT_THERM_100K: Returns battery thermistor temperature in * decidegC using 100k pullup. The hardware applies offset/slope to adc * code. - * SCALE_HW_CALIB_BATT_THERM_30K: Returns battery thermistor voltage in + * SCALE_HW_CALIB_BATT_THERM_30K: Returns battery thermistor temperature in * decidegC using 30k pullup. The hardware applies offset/slope to adc * code. - * SCALE_HW_CALIB_BATT_THERM_400K: Returns battery thermistor voltage in + * SCALE_HW_CALIB_BATT_THERM_400K: Returns battery thermistor temperature in * decidegC using 400k pullup. The hardware applies offset/slope to adc * code. * SCALE_HW_CALIB_PM5_SMB1398_TEMP: Returns result in millidegrees for PMIC5 @@ -126,6 +182,17 @@ struct vadc_linear_graph { * use voltage scaling. * SCALE_HW_CALIB_CUR_RAW: Returns result in microamperes for PMIC7 channels * that use raw ADC code. + * SCALE_HW_CALIB_PM2250_S3_DIE_TEMP: Returns result in millidegrees for + * S3 die temperature channel on PM2250. + * SCALE_HW_CALIB_PM5_CUR: Returns result in microamperes for PMIC5 channels + * that use voltage scaling. + * SCALE_HW_CALIB_PM5_GEN3_BATT_THERM_100K: Returns battery thermistor + * temperature in decidegC using 100k pullup. The hardware applies + * offset/slope to adc code. + * SCALE_HW_CALIB_PM5_GEN3_BATT_ID_100K: Returns battery ID resistance + * in ohms using 100k pullup. The hardware applies offset/slope to + * adc code. + * SCALE_HW_CALIB_PM5_GEN3_USB_IN_I: Returns USB input current in microamperes. */ enum vadc_scale_fn_type { SCALE_DEFAULT = 0, @@ -149,10 +216,16 @@ enum vadc_scale_fn_type { SCALE_HW_CALIB_PM7_CHG_TEMP, SCALE_HW_CALIB_CUR, SCALE_HW_CALIB_CUR_RAW, + SCALE_HW_CALIB_PM2250_S3_DIE_TEMP, + SCALE_HW_CALIB_PM5_CUR, + SCALE_HW_CALIB_PM5_GEN3_BATT_THERM_100K, + SCALE_HW_CALIB_PM5_GEN3_BATT_ID_100K, + SCALE_HW_CALIB_PM5_GEN3_USB_IN_I, SCALE_HW_CALIB_INVALID, }; struct adc5_data { + const char *name; const u32 full_scale_code_volt; const u32 full_scale_code_cur; const struct adc5_channels *adc_chans; @@ -191,4 +264,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 */