From 7aac9f426d957544b4fc0fdaff4c7e8ed43b4023 Mon Sep 17 00:00:00 2001 From: Jishnu Prakash Date: Fri, 10 Jan 2020 16:42:59 +0530 Subject: [PATCH 01/22] iio: adc: Add support to read parallel charger current channel Add support to read ADC channel 0xA5 for parallel charger current. Some PMICs (PMI632) the max supported current is 5A, hence define a new config data and compatible string for it. Change-Id: I973dfdf81ee955d322da7e2c328d8b1472d33013 Signed-off-by: Jishnu Prakash Signed-off-by: Anirudh Ghayal --- drivers/iio/adc/qcom-spmi-adc5.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/iio/adc/qcom-spmi-adc5.c b/drivers/iio/adc/qcom-spmi-adc5.c index e910eb2ef076..c94a56057f0f 100644 --- a/drivers/iio/adc/qcom-spmi-adc5.c +++ b/drivers/iio/adc/qcom-spmi-adc5.c @@ -602,6 +602,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", 1, + SCALE_HW_CALIB_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, @@ -842,6 +844,16 @@ static const struct adc5_data adc7_data_pmic = { 64000, 128000}, }; +static const struct adc5_data adc5_data_pmic5_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_2 = (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 = { .full_scale_code_volt = 0x4000, .full_scale_code_cur = 0x1800, @@ -870,6 +882,10 @@ static const struct of_device_id adc5_match_table[] = { .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); From 50f6bca49018e8f85a9875e545ebf35d0a6e84a1 Mon Sep 17 00:00:00 2001 From: Jishnu Prakash Date: Mon, 20 Jul 2020 15:15:37 +0530 Subject: [PATCH 02/22] iio: adc: spmi-adc5: Add support for PM2250 S3 die temp channel Add support for reading S3 die_temp channel on PM2250 by specifying the scaling function. Change-Id: I1e8fcba0058851a540bc39adc7606d4f0ad8f6db Signed-off-by: Jishnu Prakash [quic_subbaram@quicinc.com: Fixed merge conflicts in qcom-vadc-common.h due to location change] Signed-off-by: Subbaraman Narayanamurthy [quic_collinsd@quicinc.com: changed struct vadc_prescale_ratio to struct u32_fract and changed prescale "num" and "den" to "numerator" and "denominator" respectively] Signed-off-by: David Collins --- drivers/iio/adc/qcom-vadc-common.c | 34 ++++++++++++++++++++++++ include/linux/iio/adc/qcom-vadc-common.h | 6 +++++ 2 files changed, 40 insertions(+) diff --git a/drivers/iio/adc/qcom-vadc-common.c b/drivers/iio/adc/qcom-vadc-common.c index 6c47d028a50c..ed5d91cf9134 100644 --- a/drivers/iio/adc/qcom-vadc-common.c +++ b/drivers/iio/adc/qcom-vadc-common.c @@ -582,6 +582,10 @@ 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_vadc_scale_hw_chg5_temp( const struct u32_fract *prescale, const struct adc5_data *data, @@ -619,6 +623,7 @@ 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_PM7_SMB_TEMP] = {qcom_vadc_scale_hw_pm7_smb_temp}, [SCALE_HW_CALIB_PM7_CHG_TEMP] = {qcom_vadc_scale_hw_pm7_chg_temp}, }; @@ -1059,6 +1064,35 @@ 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_vadc_scale_hw_chg5_temp( const struct u32_fract *prescale, const struct adc5_data *data, diff --git a/include/linux/iio/adc/qcom-vadc-common.h b/include/linux/iio/adc/qcom-vadc-common.h index 3ed870a1eb6b..6d90caa91b45 100644 --- a/include/linux/iio/adc/qcom-vadc-common.h +++ b/include/linux/iio/adc/qcom-vadc-common.h @@ -47,6 +47,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 @@ -126,6 +129,8 @@ 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. */ enum vadc_scale_fn_type { SCALE_DEFAULT = 0, @@ -149,6 +154,7 @@ 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_INVALID, }; From 6da5d51aa7c47405df0af34947ae8e298e022b88 Mon Sep 17 00:00:00 2001 From: Jishnu Prakash Date: Thu, 26 Nov 2020 11:56:45 +0530 Subject: [PATCH 03/22] iio: adc: Add support to read ADC5 current channels Add scaling function required to read PMIC5 current ADC channels so that channel 0xA5 (ADC5_PARALLEL_ISENSE) can use that. Change-Id: I55ede0c736c6f350df8ac92ea8661db4129da13c Signed-off-by: Jishnu Prakash [quic_subbaram@quicinc.com: Fixed merge conflicts in qcom-vadc-common.h due to location change] Signed-off-by: Subbaraman Narayanamurthy [quic_collinsd@quicinc.com: changed struct vadc_prescale_ratio to struct u32_fract and changed prescale "num" and "den" to "numerator" and "denominator" respectively] Signed-off-by: David Collins --- drivers/iio/adc/qcom-spmi-adc5.c | 4 +-- drivers/iio/adc/qcom-vadc-common.c | 32 ++++++++++++++++++++++++ include/linux/iio/adc/qcom-vadc-common.h | 3 +++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/drivers/iio/adc/qcom-spmi-adc5.c b/drivers/iio/adc/qcom-spmi-adc5.c index c94a56057f0f..7941d6804b7f 100644 --- a/drivers/iio/adc/qcom-spmi-adc5.c +++ b/drivers/iio/adc/qcom-spmi-adc5.c @@ -602,8 +602,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", 1, - SCALE_HW_CALIB_CUR) + [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, diff --git a/drivers/iio/adc/qcom-vadc-common.c b/drivers/iio/adc/qcom-vadc-common.c index ed5d91cf9134..41e587cad3bd 100644 --- a/drivers/iio/adc/qcom-vadc-common.c +++ b/drivers/iio/adc/qcom-vadc-common.c @@ -542,14 +542,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, @@ -607,6 +614,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}, @@ -880,6 +888,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, diff --git a/include/linux/iio/adc/qcom-vadc-common.h b/include/linux/iio/adc/qcom-vadc-common.h index 6d90caa91b45..55a17d81a83c 100644 --- a/include/linux/iio/adc/qcom-vadc-common.h +++ b/include/linux/iio/adc/qcom-vadc-common.h @@ -131,6 +131,8 @@ struct vadc_linear_graph { * 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. */ enum vadc_scale_fn_type { SCALE_DEFAULT = 0, @@ -155,6 +157,7 @@ enum vadc_scale_fn_type { SCALE_HW_CALIB_CUR, SCALE_HW_CALIB_CUR_RAW, SCALE_HW_CALIB_PM2250_S3_DIE_TEMP, + SCALE_HW_CALIB_PM5_CUR, SCALE_HW_CALIB_INVALID, }; From 49a1fd68dbbfc4832d738ae62a0e37f290d32374 Mon Sep 17 00:00:00 2001 From: Subbaraman Narayanamurthy Date: Tue, 24 Nov 2020 12:45:12 -0800 Subject: [PATCH 04/22] iio: adc: qcom-spmi-adc5: add an unique irq name Currently, interrupts of qcom-spmi-adc5 driver are registered with name "pm-adc5". However on platforms with multiple PMICs that can have different ADC types (adc5 or adc7), this naming is not user friendly when looking at interrupt data. Add a name that looks unique for different adc types so that they can be interpreted easily. Change-Id: Iac1080dc8e02d278b30cff0d61eca40e081bc306 Signed-off-by: Subbaraman Narayanamurthy [quic_subbaram@quicinc.com: Fixed merge conflicts in qcom-vadc-common.h due to location change] Signed-off-by: Subbaraman Narayanamurthy --- drivers/iio/adc/qcom-spmi-adc5.c | 11 ++++++++++- include/linux/iio/adc/qcom-vadc-common.h | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/iio/adc/qcom-spmi-adc5.c b/drivers/iio/adc/qcom-spmi-adc5.c index 7941d6804b7f..571a13fe1baf 100644 --- a/drivers/iio/adc/qcom-spmi-adc5.c +++ b/drivers/iio/adc/qcom-spmi-adc5.c @@ -818,6 +818,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, @@ -833,6 +834,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, @@ -845,6 +847,7 @@ static const struct adc5_data adc7_data_pmic = { }; 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, @@ -855,6 +858,7 @@ static const struct adc5_data adc5_data_pmic5_lite = { }; 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, @@ -952,6 +956,7 @@ static int adc5_probe(struct platform_device *pdev) struct iio_dev *indio_dev; struct adc5_chip *adc; struct regmap *regmap; + const char *irq_name; int ret, irq_eoc; u32 reg; @@ -987,8 +992,12 @@ static int adc5_probe(struct platform_device *pdev) return irq_eoc; adc->poll_eoc = true; } else { + irq_name = "pm-adc5"; + if (adc->data->name) + irq_name = adc->data->name; + ret = devm_request_irq(dev, irq_eoc, adc5_isr, 0, - "pm-adc5", adc); + irq_name, adc); if (ret) return ret; } diff --git a/include/linux/iio/adc/qcom-vadc-common.h b/include/linux/iio/adc/qcom-vadc-common.h index 55a17d81a83c..c58870892a30 100644 --- a/include/linux/iio/adc/qcom-vadc-common.h +++ b/include/linux/iio/adc/qcom-vadc-common.h @@ -162,6 +162,7 @@ enum vadc_scale_fn_type { }; struct adc5_data { + const char *name; const u32 full_scale_code_volt; const u32 full_scale_code_cur; const struct adc5_channels *adc_chans; From a0fc6a0c067d0c0b66342b24d0ff5fe41eca262e Mon Sep 17 00:00:00 2001 From: Shyam Kumar Thella Date: Wed, 23 Dec 2020 12:12:28 +0530 Subject: [PATCH 05/22] iio: qcom-spmi-adc5: Fix HW settle time table naming for ADC5 lite Correct the hw_settling times array defined for ADC5 lite. Change-Id: I38eee9b3f76cb40e286f9e7e3fbf296b3f3ccfe5 Signed-off-by: Shyam Kumar Thella --- drivers/iio/adc/qcom-spmi-adc5.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/adc/qcom-spmi-adc5.c b/drivers/iio/adc/qcom-spmi-adc5.c index 571a13fe1baf..ea9d602f2185 100644 --- a/drivers/iio/adc/qcom-spmi-adc5.c +++ b/drivers/iio/adc/qcom-spmi-adc5.c @@ -853,7 +853,7 @@ static const struct adc5_data adc5_data_pmic5_lite = { .full_scale_code_cur = 5000, .adc_chans = adc5_chans_pmic, .decimation = (unsigned int []) {250, 420, 840}, - .hw_settle_2 = (unsigned int []) {15, 100, 200, 300, 400, 500, 600, 700, + .hw_settle_1 = (unsigned int []) {15, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1, 2, 4, 6, 8, 10}, }; From 640fa8d8d94983a94350a152dd907f1ea943fcfb Mon Sep 17 00:00:00 2001 From: Jishnu Prakash Date: Tue, 2 Mar 2021 09:26:31 +0530 Subject: [PATCH 06/22] iio: qcom-spmi-adc5: update scaling ratios for SMB_ICHG and SMB_IIN Update scaling ratios for SMB_ICHG and SMB_IIN ADC channels, following HW recommendation. Update default prescaling values used for SMB_IIN and ICHG_FB channels to match changes made in the prescaling array. Change-Id: I2b016e021d3c9e8b76cd5400c6790030da088093 Signed-off-by: Jishnu Prakash [quic_subbaram@quicinc.com: Fixed merge conflicts in qcom-spmi-adc5.c because common code moved to qcom-vadc-common.c] Signed-off-by: Subbaraman Narayanamurthy [quic_collinsd@quicinc.com: changed prescale "num" and "den" to "numerator" and "denominator" respectively] Signed-off-by: David Collins --- drivers/iio/adc/qcom-spmi-adc5.c | 6 +++--- drivers/iio/adc/qcom-vadc-common.c | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/iio/adc/qcom-spmi-adc5.c b/drivers/iio/adc/qcom-spmi-adc5.c index ea9d602f2185..97fbc8e35f7a 100644 --- a/drivers/iio/adc/qcom-spmi-adc5.c +++ b/drivers/iio/adc/qcom-spmi-adc5.c @@ -629,11 +629,11 @@ static const struct adc5_channels adc7_chans_pmic[ADC5_MAX_CHANNEL] = { SCALE_HW_CALIB_PM7_CHG_TEMP) [ADC7_IIN_FB] = ADC5_CHAN_CUR("iin_fb", 9, SCALE_HW_CALIB_CUR) + [ADC7_IIN_SMB] = ADC5_CHAN_CUR("iin_smb", 9, + SCALE_HW_CALIB_CUR) [ADC7_ICHG_SMB] = ADC5_CHAN_CUR("ichg_smb", 10, SCALE_HW_CALIB_CUR) - [ADC7_IIN_SMB] = ADC5_CHAN_CUR("iin_smb", 11, - SCALE_HW_CALIB_CUR) - [ADC7_ICHG_FB] = ADC5_CHAN_CUR("ichg_fb", 12, + [ADC7_ICHG_FB] = ADC5_CHAN_CUR("ichg_fb", 11, SCALE_HW_CALIB_CUR_RAW) [ADC7_DIE_TEMP] = ADC5_CHAN_TEMP("die_temp", 0, SCALE_HW_CALIB_PMIC_THERM_PM7) diff --git a/drivers/iio/adc/qcom-vadc-common.c b/drivers/iio/adc/qcom-vadc-common.c index 41e587cad3bd..e23d735d761d 100644 --- a/drivers/iio/adc/qcom-vadc-common.c +++ b/drivers/iio/adc/qcom-vadc-common.c @@ -531,9 +531,8 @@ static const struct u32_fract adc5_prescale_ratios[] = { { .numerator = 1, .denominator = 10 }, { .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 = 32, .denominator = 100 }, /* IIN_FB, IIN_SMB */ + { .numerator = 16, .denominator = 100 }, /* ICHG_SMB */ { .numerator = 1000, .denominator = 305185 }, /* ICHG_FB */ { .numerator = 1000, .denominator = 610370 }, /* ICHG_FB_2X */ }; From b2a8924fb154f68c084d253952ef962f01a0abd8 Mon Sep 17 00:00:00 2001 From: Subbaraman Narayanamurthy Date: Tue, 6 Apr 2021 12:28:33 -0700 Subject: [PATCH 07/22] iio: qcom-spmi-adc5: add ADC7_USB_IN_V_16 to adc7_chans_pmic list Add ADC7_USB_IN_V_16 to adc7_chans_pmic list to support reading USB VBUS voltage directly from ADC channel if needed during testing. Change-Id: Id27cd50c65a549c1361f0b0185afa4048b66423c Signed-off-by: Subbaraman Narayanamurthy --- drivers/iio/adc/qcom-spmi-adc5.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/iio/adc/qcom-spmi-adc5.c b/drivers/iio/adc/qcom-spmi-adc5.c index 97fbc8e35f7a..cfa32c2859fd 100644 --- a/drivers/iio/adc/qcom-spmi-adc5.c +++ b/drivers/iio/adc/qcom-spmi-adc5.c @@ -623,6 +623,8 @@ static const struct adc5_channels adc7_chans_pmic[ADC5_MAX_CHANNEL] = { SCALE_HW_CALIB_DEFAULT) [ADC7_VBAT_SNS] = ADC5_CHAN_VOLT("vbat_sns", 3, SCALE_HW_CALIB_DEFAULT) + [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", 0, SCALE_HW_CALIB_PM7_SMB_TEMP) [ADC7_CHG_TEMP] = ADC5_CHAN_TEMP("chg_temp", 0, From 6a3569670ed456c576cf3fc5a38cf8bdfac41606 Mon Sep 17 00:00:00 2001 From: Subbaraman Narayanamurthy Date: Wed, 7 Jul 2021 17:00:20 -0700 Subject: [PATCH 08/22] dt-bindings: iio: qcom,spmi-vadc: add ADC_SCALE_* definitions Some device tree files refers these definitions when "qcom,scale-fn-type" is specified. Add them. Change-Id: I559de48eff20666194b01e32320de5c647afccae Signed-off-by: Subbaraman Narayanamurthy --- include/dt-bindings/iio/qcom,spmi-vadc.h | 27 +++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/include/dt-bindings/iio/qcom,spmi-vadc.h b/include/dt-bindings/iio/qcom,spmi-vadc.h index 1cfd3669a665..7cfa7108a126 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,29 @@ #define ADC7_SBUx 0x94 #define ADC7_VBAT_2S_MID 0x96 +/* 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 + #endif /* _DT_BINDINGS_QCOM_SPMI_VADC_H */ From fa248b6161e01612ac7ac4cb2a0ebec436ef1bb9 Mon Sep 17 00:00:00 2001 From: Subbaraman Narayanamurthy Date: Wed, 14 Jul 2021 19:10:20 -0700 Subject: [PATCH 09/22] iio: adc: qcom-spmi-adc5: Update ratios for SMB_* channels for ADC7 As per the hardware recommendation, add a ratio of 41/40 (i.e. numerator 40 and denominator 41 based on how it's used in our calculations) for the ADC channels that use SMB_TEMP pin because of the voltage divider circuit with a 1 K and 40 K in parallel. IIN_FB channel should be using the same ratio as that of IIN_SMB before. Hence, add a separate scaling factor for it. Change-Id: I4c3d76a068edf7c964a71c5082da54b115e7eb04 Signed-off-by: Subbaraman Narayanamurthy [quic_subbaram@quicinc.com: Fixed merge conflicts in qcom-spmi-adc5.c because common code moved to qcom-vadc-common.c] Signed-off-by: Subbaraman Narayanamurthy [quic_collinsd@quicinc.com: changed prescale "num" and "den" to "numerator" and "denominator" respectively] Signed-off-by: David Collins --- drivers/iio/adc/qcom-spmi-adc5.c | 10 +++++----- drivers/iio/adc/qcom-vadc-common.c | 3 +++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/drivers/iio/adc/qcom-spmi-adc5.c b/drivers/iio/adc/qcom-spmi-adc5.c index cfa32c2859fd..8a50edd69345 100644 --- a/drivers/iio/adc/qcom-spmi-adc5.c +++ b/drivers/iio/adc/qcom-spmi-adc5.c @@ -625,17 +625,17 @@ static const struct adc5_channels adc7_chans_pmic[ADC5_MAX_CHANNEL] = { SCALE_HW_CALIB_DEFAULT) [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", 0, + [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_IIN_SMB] = ADC5_CHAN_CUR("iin_smb", 9, + [ADC7_IIN_SMB] = ADC5_CHAN_CUR("iin_smb", 12, SCALE_HW_CALIB_CUR) - [ADC7_ICHG_SMB] = ADC5_CHAN_CUR("ichg_smb", 10, + [ADC7_ICHG_SMB] = ADC5_CHAN_CUR("ichg_smb", 13, SCALE_HW_CALIB_CUR) - [ADC7_ICHG_FB] = ADC5_CHAN_CUR("ichg_fb", 11, + [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) diff --git a/drivers/iio/adc/qcom-vadc-common.c b/drivers/iio/adc/qcom-vadc-common.c index e23d735d761d..3e81aebfb018 100644 --- a/drivers/iio/adc/qcom-vadc-common.c +++ b/drivers/iio/adc/qcom-vadc-common.c @@ -530,9 +530,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, 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 */ }; From 9e5e68e310b183633d0d259857a8fa9f4a0e2048 Mon Sep 17 00:00:00 2001 From: Jishnu Prakash Date: Thu, 22 Apr 2021 16:10:37 +0530 Subject: [PATCH 10/22] iio: qcom-spmi-adc5: Add AMUX2_GPIO channel to rev2 channel list Add ADC5_GPIO2_100K_PU channel to the list of rev2 ADC channels. Change-Id: I684c4e5124f37345e378fa552cad789d24be6a69 Signed-off-by: Jishnu Prakash --- drivers/iio/adc/qcom-spmi-adc5.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/iio/adc/qcom-spmi-adc5.c b/drivers/iio/adc/qcom-spmi-adc5.c index 8a50edd69345..0f2b983bdbce 100644 --- a/drivers/iio/adc/qcom-spmi-adc5.c +++ b/drivers/iio/adc/qcom-spmi-adc5.c @@ -686,6 +686,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, From 7945098ab39a3c6dac726374ee8f1184b8440df5 Mon Sep 17 00:00:00 2001 From: Jishnu Prakash Date: Mon, 22 Mar 2021 11:52:39 +0530 Subject: [PATCH 11/22] dt-bindings: iio: Add PMIC5 GEN3 ADC support Add definitions for ADC channels for PMIC5 GEN3 ADC driver. Add virtual channel definitions for PM5100, to be used by ADC clients for PMIC5 GEN3. Change-Id: I1180553c3ee697f994324c60ebfb4e996221108e Signed-off-by: Jishnu Prakash --- .../iio/qcom,spmi-adc5-gen3-pm5100.h | 83 +++++++++++++++++++ include/dt-bindings/iio/qcom,spmi-vadc.h | 77 +++++++++++++++++ 2 files changed, 160 insertions(+) create mode 100644 include/dt-bindings/iio/qcom,spmi-adc5-gen3-pm5100.h 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..ad9a1e53b883 --- /dev/null +++ b/include/dt-bindings/iio/qcom,spmi-adc5-gen3-pm5100.h @@ -0,0 +1,83 @@ +/* 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_AMUX2_THM (PM5100_SID << 8 | 0x05) +#define PM5100_ADC5_GEN3_AMUX3_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_IIN_FB (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_AMUX2_THM_30K_PU (PM5100_SID << 8 | 0x25) +#define PM5100_ADC5_GEN3_AMUX3_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) + + +#define ADC5_GEN3_AMUX2_GPIO_100K_PU 0x4b + + +/* 100k pull-up2 */ +#define PM5100_ADC5_GEN3_AMUX1_THM_100K_PU (PM5100_SID << 8 | 0x44) +#define PM5100_ADC5_GEN3_AMUX2_THM_100K_PU (PM5100_SID << 8 | 0x45) +#define PM5100_ADC5_GEN3_AMUX3_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_AMUX2_THM_400K_PU (PM5100_SID << 8 | 0x65) +#define PM5100_ADC5_GEN3_AMUX3_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 7cfa7108a126..0d2b7fec125b 100644 --- a/include/dt-bindings/iio/qcom,spmi-vadc.h +++ b/include/dt-bindings/iio/qcom,spmi-vadc.h @@ -300,6 +300,83 @@ #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 From 9d4f6e0a3a93e8a821b213f803cff4472164bbca Mon Sep 17 00:00:00 2001 From: Jishnu Prakash Date: Tue, 2 Mar 2021 11:12:21 +0530 Subject: [PATCH 12/22] iio: adc: Add QCOM SPMI PMIC5 GEN3 ADC driver Add support for QCOM SPMI PMIC5 GEN3 ADC driver that supports hardware based offset and gain compensation. The ADC peripheral can measure both voltage and current channels whose input signal is connected to the PMIC ADC AMUX. The PMIC5 GEN3 ADC peripheral uses registers defined in SDAM. The register set and configuration have been refreshed compared to the prior QCOM PMIC7 ADC family. Change-Id: I3512c3c067d9102406aa92c389d66283f0b12103 Signed-off-by: Jishnu Prakash [quic_subbaram@quicinc.com: Fixed merge conflicts in qcom-vadc-common.h due to location change; Made changes in qcom-vadc-common.c to re-use existing qcom_vadc_map_temp_voltage(); Added entire include/linux/adc-tm-clients.h as it doesn't exist anymore but needed to compile qcom-spmi-adc5-gen3.c and removed the APIs used when CONFIG_QTI_ADC_TM is enabled.] Signed-off-by: Subbaraman Narayanamurthy [quic_collinsd@quicinc.com: changed struct vadc_prescale_ratio to struct u32_fract and changed prescale "num" and "den" to "numerator" and "denominator" respectively] Signed-off-by: David Collins --- drivers/iio/adc/Kconfig | 20 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/qcom-spmi-adc5-gen3.c | 1737 ++++++++++++++++++++++ drivers/iio/adc/qcom-vadc-common.c | 89 ++ include/linux/adc-tm-clients.h | 102 ++ include/linux/iio/adc/qcom-vadc-common.h | 55 + 6 files changed, 2004 insertions(+) create mode 100644 drivers/iio/adc/qcom-spmi-adc5-gen3.c create mode 100644 include/linux/adc-tm-clients.h diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 71ab0a06aa82..0d3dafb83b2c 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -872,6 +872,26 @@ config QCOM_SPMI_ADC5 To compile this driver as a module, choose M here: the module will be called qcom-spmi-adc5. +config QCOM_SPMI_ADC5_GEN3 + tristate "Qualcomm Technologies Inc. SPMI PMIC5 GEN3 ADC" + depends on SPMI + select REGMAP_SPMI + select QCOM_VADC_COMMON + help + This is the IIO Voltage PMIC5 Gen3 ADC driver for Qualcomm Technologies Inc. + + The driver supports multiple channels read. The ADC is a 16-bit + sigma-delta ADC. The hardware supports calibrated results for + conversion requests and clients include reading voltage phone + power, on board system thermistors connected to the PMIC ADC, + PMIC die temperature, charger temperature, battery current, USB voltage + input, voltage signals connected to supported PMIC GPIO inputs. The + hardware supports internal pull-up for thermistors and can choose between + a 100k, 30k and 400k pull up using the ADC channels. + + To compile this driver as a module, choose M here: the module will + be called qcom-spmi-adc5-gen3. + config RCAR_GYRO_ADC tristate "Renesas R-Car GyroADC driver" depends on ARCH_RCAR_GEN2 || COMPILE_TEST diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 39d806f6d457..073f81002995 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -77,6 +77,7 @@ obj-$(CONFIG_NAU7802) += nau7802.o obj-$(CONFIG_NPCM_ADC) += npcm_adc.o obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o obj-$(CONFIG_QCOM_SPMI_ADC5) += qcom-spmi-adc5.o +obj-$(CONFIG_QCOM_SPMI_ADC5_GEN3) += qcom-spmi-adc5-gen3.o obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o obj-$(CONFIG_QCOM_VADC_COMMON) += qcom-vadc-common.o obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o diff --git a/drivers/iio/adc/qcom-spmi-adc5-gen3.c b/drivers/iio/adc/qcom-spmi-adc5-gen3.c new file mode 100644 index 000000000000..f25b1f4fa751 --- /dev/null +++ b/drivers/iio/adc/qcom-spmi-adc5-gen3.c @@ -0,0 +1,1737 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static LIST_HEAD(adc_tm_device_list); + +#define ADC5_GEN3_HS 0x45 +#define ADC5_GEN3_HS_BUSY BIT(7) +#define ADC5_GEN3_HS_READY BIT(0) + +#define ADC5_GEN3_STATUS1 0x46 +#define ADC5_GEN3_STATUS1_CONV_FAULT BIT(7) +#define ADC5_GEN3_STATUS1_THR_CROSS BIT(6) +#define ADC5_GEN3_STATUS1_EOC BIT(1) + +#define ADC5_GEN3_TM_EN_STS 0x47 + +#define ADC5_GEN3_TM_HIGH_STS 0x48 + +#define ADC5_GEN3_TM_LOW_STS 0x49 + +#define ADC5_GEN3_EOC_STS 0x4a + +#define ADC5_GEN3_EOC_CLR 0x4b + +#define ADC5_GEN3_TM_HIGH_STS_CLR 0x4c + +#define ADC5_GEN3_TM_LOW_STS_CLR 0x4d + +#define ADC5_GEN3_SID 0x4f +#define ADC5_GEN3_SID_MASK 0xf + +#define ADC5_GEN3_PERPH_CH 0x50 +#define ADC5_GEN3_CHAN_CONV_REQ BIT(7) + +#define ADC5_GEN3_TIMER_SEL 0x51 + +#define ADC5_GEN3_DIG_PARAM 0x52 +#define ADC5_GEN3_DIG_PARAM_CAL_SEL_MASK GENMASK(5, 4) +#define ADC5_GEN3_DIG_PARAM_CAL_SEL_SHIFT 4 +#define ADC5_GEN3_DIG_PARAM_DEC_RATIO_SEL_MASK GENMASK(3, 2) +#define ADC5_GEN3_DIG_PARAM_DEC_RATIO_SEL_SHIFT 2 + +#define ADC5_GEN3_FAST_AVG 0x53 +#define ADC5_GEN3_FAST_AVG_CTL_EN BIT(7) +#define ADC5_GEN3_FAST_AVG_CTL_SAMPLES_MASK GENMASK(2, 0) + +#define ADC5_GEN3_ADC_CH_SEL_CTL 0x54 + +#define ADC5_GEN3_DELAY_CTL 0x55 +#define ADC5_GEN3_HW_SETTLE_DELAY_MASK 0xf + +#define ADC5_GEN3_CH_EN 0x56 + +#define ADC5_GEN3_LOW_THR0 0x57 + +#define ADC5_GEN3_LOW_THR1 0x58 + +#define ADC5_GEN3_HIGH_THR0 0x59 + +#define ADC5_GEN3_HIGH_THR1 0x5a + +#define ADC5_GEN3_CH0_DATA0 0x5c +#define ADC5_GEN3_CH0_DATA1 0x5d +#define ADC5_GEN3_CH1_DATA0 0x5e +#define ADC5_GEN3_CH1_DATA1 0x5f +#define ADC5_GEN3_CH2_DATA0 0x60 +#define ADC5_GEN3_CH2_DATA1 0x61 +#define ADC5_GEN3_CH3_DATA0 0x62 +#define ADC5_GEN3_CH3_DATA1 0x63 +#define ADC5_GEN3_CH4_DATA0 0x64 +#define ADC5_GEN3_CH4_DATA1 0x65 +#define ADC5_GEN3_CH5_DATA0 0x66 +#define ADC5_GEN3_CH5_DATA1 0x67 +#define ADC5_GEN3_CH6_DATA0 0x68 +#define ADC5_GEN3_CH6_DATA1 0x69 +#define ADC5_GEN3_CH7_DATA0 0x6a +#define ADC5_GEN3_CH7_DATA1 0x6b + +#define ADC5_GEN3_CONV_REQ 0xe5 +#define ADC5_GEN3_CONV_REQ_REQ BIT(0) + +#define ADC5_GEN3_SID_OFFSET 0x8 +#define ADC5_GEN3_CHANNEL_MASK 0xff +#define V_CHAN(x) (((x).sid << ADC5_GEN3_SID_OFFSET) | (x).channel) + +#define ADC_TM5_GEN3_LOWER_MASK(n) ((n) & GENMASK(7, 0)) +#define ADC_TM5_GEN3_UPPER_MASK(n) (((n) & GENMASK(15, 8)) >> 8) + +#define ADC_TM5_GEN3_CHANS_MAX 7 + +enum adc5_cal_method { + ADC5_NO_CAL = 0, + ADC5_RATIOMETRIC_CAL, + ADC5_ABSOLUTE_CAL +}; + +enum adc_time_select { + MEAS_INT_DISABLE = 0, + MEAS_INT_IMMEDIATE, + MEAS_INT_50MS, + MEAS_INT_100MS, + MEAS_INT_1S, + MEAS_INT_NONE, +}; + +enum adc_tm_type { + ADC_TM_NONE = 0, + ADC_TM, + ADC_TM_IIO, + ADC_TM_NON_THERMAL, + ADC_TM_INVALID, +}; + +static struct adc_tm_reverse_scale_fn adc_tm_rscale_fn[] = { + [SCALE_R_ABSOLUTE] = {adc_tm_absolute_rthr_gen3}, +}; + +/** + * struct adc5_channel_prop - ADC channel property. + * @channel: channel number, refer to the channel list. + * @cal_method: calibration method. + * @decimation: sampling rate supported for the channel. + * @sid: slave id of PMIC owning the channel, for PMIC5 Gen2 and above. + * @prescale: channel scaling performed on the input signal. + * @hw_settle_time: the time between AMUX being configured and the + * start of conversion. + * @avg_samples: ability to provide single result from the ADC + * that is an average of multiple measurements. + * @scale_fn_type: Represents the scaling function to convert voltage + * physical units desired by the client for the channel. + * @datasheet_name: Channel name used in device tree. + * @chip: pointer to top-level ADC device structure. + * @adc_tm: indicates TM type if the channel is used for TM measurements. + * @tm_chan_index: TM channel number used (ranging from 1-7). + * @timer: time period of recurring TM measurement. + * @tzd: pointer to thermal device corresponding to TM channel. + * @high_thr_en: TM high threshold crossing detection enabled. + * @low_thr_en: TM low threshold crossing detection enabled. + * @high_thr_triggered: indicates if high TM threshold has been triggered. + * @low_thr_triggered: indicates if low TM threshold has been triggered. + * @high_thr_voltage: upper threshold voltage for TM. + * @low_thr_voltage: lower threshold voltage for TM. + * @last_temp: last temperature that caused threshold violation, + * or a thermal TM channel. + * @last_temp_set: indicates if last_temp is stored. + * @req_wq: workqueue holding queued notification tasks for a non-thermal + * TM channel. + * @work: scheduled work for handling non-thermal TM client notification. + * @thr_list: list of client thresholds configured for non-thermal TM channel. + * @adc_rscale_fn: reverse scaling function to convert voltage to raw code + * for non-thermal TM channels. + */ +struct adc5_channel_prop { + unsigned int channel; + enum adc5_cal_method cal_method; + unsigned int decimation; + unsigned int sid; + unsigned int prescale; + unsigned int hw_settle_time; + unsigned int avg_samples; + + enum vadc_scale_fn_type scale_fn_type; + const char *datasheet_name; + + struct adc5_chip *chip; + /* TM properties */ + int adc_tm; + unsigned int tm_chan_index; + unsigned int timer; + struct thermal_zone_device *tzd; + int high_thr_en; + int low_thr_en; + bool high_thr_triggered; + bool low_thr_triggered; + int64_t high_thr_voltage; + int64_t low_thr_voltage; + int last_temp; + bool last_temp_set; + struct workqueue_struct *req_wq; + struct work_struct work; + struct list_head thr_list; + enum adc_tm_rscale_fn_type adc_rscale_fn; +}; + +/** + * struct adc5_chip - ADC private structure. + * @regmap: SPMI ADC5 peripheral register map field. + * @dev: SPMI ADC5 device. + * @base: base address for the ADC peripheral. + * @debug_base: base address for the reserved ADC peripheral, + * to dump for debug purposes alone. + * @nchannels: number of ADC channels. + * @chan_props: array of ADC channel properties. + * @iio_chans: array of IIO channels specification. + * @complete: ADC result notification after interrupt is received. + * @lock: ADC lock for access to the peripheral. + * @data: software configuration data. + * @list: list item, used to add this device to gloal list of ADC_TM devices. + * @device_list: pointer to list of ADC_TM devices. + * @n_tm_channels: number of ADC channels used for TM measurements. + * @tm_handler_work: scheduled work for handling TM threshold violation. + */ +struct adc5_chip { + struct regmap *regmap; + struct device *dev; + u16 base; + u16 debug_base; + unsigned int nchannels; + struct adc5_channel_prop *chan_props; + struct iio_chan_spec *iio_chans; + struct completion complete; + struct mutex lock; + const struct adc5_data *data; + /* TM properties */ + unsigned int n_tm_channels; + struct list_head list; + struct list_head *device_list; + struct work_struct tm_handler_work; +}; + +static const struct u32_fract adc5_prescale_ratios[] = { + { .numerator = 1, .denominator = 1 }, + { .numerator = 1, .denominator = 3 }, + { .numerator = 1, .denominator = 6 }, + { .numerator = 1, .denominator = 16 }, + /* Prescale ratios for current channels below */ + { .numerator = 32, .denominator = 100 }, /* IIN_FB */ + { .numerator = 14, .denominator = 100 }, /* ICHG_SMB */ + { .numerator = 28, .denominator = 100 }, /* IIN_SMB */ + { .numerator = 1000, .denominator = 305185 }, /* ICHG_FB */ +}; + +static int adc5_read(struct adc5_chip *adc, u16 offset, u8 *data, int len) +{ + int ret; + + ret = regmap_bulk_read(adc->regmap, adc->base + offset, data, len); + if (ret < 0) + pr_err("adc read to register 0x%x of length:%d failed, ret=%d\n", offset, len, ret); + + return ret; +} + +static int adc5_write(struct adc5_chip *adc, u16 offset, u8 *data, int len) +{ + int ret; + + ret = regmap_bulk_write(adc->regmap, adc->base + offset, data, len); + if (ret < 0) + pr_err("adc write to register 0x%x of length:%d failed, ret=%d\n", offset, len, + ret); + + return ret; +} + +static int adc5_prescaling_from_dt(u32 numerator, u32 denominator) +{ + unsigned int pre; + + for (pre = 0; pre < ARRAY_SIZE(adc5_prescale_ratios); pre++) { + if (adc5_prescale_ratios[pre].numerator == numerator && + adc5_prescale_ratios[pre].denominator == denominator) + break; + } + + if (pre == ARRAY_SIZE(adc5_prescale_ratios)) + return -ENOENT; + + return pre; +} + +static int adc5_hw_settle_time_from_dt(u32 value, + const unsigned int *hw_settle) +{ + unsigned int i; + + for (i = 0; i < VADC_HW_SETTLE_SAMPLES_MAX; i++) { + if (value == hw_settle[i]) + return i; + } + + return -ENOENT; +} + +static int adc5_avg_samples_from_dt(u32 value) +{ + if (!is_power_of_2(value) || value > ADC5_AVG_SAMPLES_MAX) + return -EINVAL; + + return __ffs(value); +} + +static int adc5_decimation_from_dt(u32 value, + const unsigned int *decimation) +{ + unsigned int i; + + for (i = 0; i < ADC5_DECIMATION_SAMPLES_MAX; i++) { + if (value == decimation[i]) + return i; + } + + return -ENOENT; +} + +static int adc5_gen3_read_voltage_data(struct adc5_chip *adc, u16 *data) +{ + int ret; + u8 rslt[2]; + + ret = adc5_read(adc, ADC5_GEN3_CH0_DATA0, rslt, 2); + if (ret < 0) + return ret; + + *data = (rslt[1] << 8) | rslt[0]; + + if (*data == ADC5_USR_DATA_CHECK) { + pr_err("Invalid data:%#x\n", *data); + return -EINVAL; + } + + pr_debug("voltage raw code:0x%x\n", *data); + + return 0; +} + +static void adc5_gen3_update_dig_param(struct adc5_chip *adc, + struct adc5_channel_prop *prop, u8 *data) +{ + /* Update calibration select */ + *data &= ~ADC5_GEN3_DIG_PARAM_CAL_SEL_MASK; + *data |= (prop->cal_method << ADC5_GEN3_DIG_PARAM_CAL_SEL_SHIFT); + + /* Update decimation ratio select */ + *data &= ~ADC5_GEN3_DIG_PARAM_DEC_RATIO_SEL_MASK; + *data |= (prop->decimation << ADC5_GEN3_DIG_PARAM_DEC_RATIO_SEL_SHIFT); +} + +static int adc5_gen3_configure(struct adc5_chip *adc, + struct adc5_channel_prop *prop) +{ + int ret; + u8 conv_req = 0, buf[7]; + + ret = adc5_read(adc, ADC5_GEN3_SID, buf, sizeof(buf)); + if (ret < 0) + return ret; + + /* Write SID */ + buf[0] &= (u8) ~ADC5_GEN3_SID_MASK; + buf[0] &= prop->sid; + + /* Use channel 0 by default for immediate conversion */ + buf[1] = 0; + + buf[2] = prop->timer; + + /* Digital param selection */ + adc5_gen3_update_dig_param(adc, prop, &buf[3]); + + /* Update fast average sample value */ + buf[4] &= (u8) ~ADC5_GEN3_FAST_AVG_CTL_SAMPLES_MASK; + buf[4] |= prop->avg_samples; + + /* + * Select ADC channel and indicate there is an actual + * conversion request + */ + buf[5] = ADC5_GEN3_CHAN_CONV_REQ | prop->channel; + + /* Select HW settle delay for channel */ + buf[6] &= (u8) ~ADC5_GEN3_HW_SETTLE_DELAY_MASK; + buf[6] |= prop->hw_settle_time; + + reinit_completion(&adc->complete); + + ret = adc5_write(adc, ADC5_GEN3_SID, buf, sizeof(buf)); + if (ret < 0) + return ret; + + conv_req = ADC5_GEN3_CONV_REQ_REQ; + ret = adc5_write(adc, ADC5_GEN3_CONV_REQ, &conv_req, 1); + + return ret; +} + +#define ADC5_GEN3_HS_DELAY_MIN_US 100 +#define ADC5_GEN3_HS_DELAY_MAX_US 110 +#define ADC5_GEN3_HS_RETRY_COUNT 20 + +static int adc5_gen3_poll_wait_hs(struct adc5_chip *adc) +{ + int ret, count; + u8 status = 0; + + for (count = 0; count < ADC5_GEN3_HS_RETRY_COUNT; count++) { + ret = adc5_read(adc, ADC5_GEN3_HS, &status, 1); + if (ret < 0) + return ret; + + if (status == ADC5_GEN3_HS_READY) + break; + + usleep_range(ADC5_GEN3_HS_DELAY_MIN_US, + ADC5_GEN3_HS_DELAY_MAX_US); + } + + if (count == ADC5_GEN3_HS_RETRY_COUNT) { + pr_err("Setting HS ready bit timed out, status:%#x\n", status); + return -ETIMEDOUT; + } + + return 0; +} + +#define ADC5_GEN3_CONV_TIMEOUT_MS 501 + +static int adc5_gen3_do_conversion(struct adc5_chip *adc, + struct adc5_channel_prop *prop, + u16 *data_volt) +{ + int ret; + unsigned long rc; + unsigned int time_pending_ms; + u8 val; + + mutex_lock(&adc->lock); + ret = adc5_gen3_poll_wait_hs(adc); + if (ret < 0) + goto unlock; + + ret = adc5_gen3_configure(adc, prop); + if (ret < 0) { + pr_err("ADC configure failed with %d\n", ret); + goto unlock; + } + + /* No support for polling mode at present*/ + rc = wait_for_completion_timeout(&adc->complete, + msecs_to_jiffies(ADC5_GEN3_CONV_TIMEOUT_MS)); + if (!rc) { + pr_err("Reading ADC channel %s timed out\n", + prop->datasheet_name); + ret = -ETIMEDOUT; + goto unlock; + } + + time_pending_ms = jiffies_to_msecs(rc); + pr_debug("ADC channel %s EOC took %u ms\n", prop->datasheet_name, + ADC5_GEN3_CONV_TIMEOUT_MS - time_pending_ms); + + ret = adc5_gen3_read_voltage_data(adc, data_volt); + if (ret < 0) + goto unlock; + + val = BIT(0); + ret = adc5_write(adc, ADC5_GEN3_EOC_CLR, &val, 1); + if (ret < 0) + goto unlock; + + /* To indicate conversion request is only to clear a status */ + val = ~ADC5_GEN3_CHAN_CONV_REQ | prop->channel; + ret = adc5_write(adc, ADC5_GEN3_PERPH_CH, &val, 1); + if (ret < 0) + goto unlock; + + val = ADC5_GEN3_CONV_REQ_REQ; + ret = adc5_write(adc, ADC5_GEN3_CONV_REQ, &val, 1); + +unlock: + mutex_unlock(&adc->lock); + + return ret; +} + +#define ADC_OFFSET_DUMP 8 +#define ADC_SDAM_REG_DUMP 32 +static void adc5_gen3_dump_regs_debug(struct adc5_chip *adc) +{ + int rc = 0, i = 0, j = 0, offset; + u8 buf[8]; + + for (j = 0; j < 2; j++) { + if (!j) { + offset = 0; + pr_debug("ADC SDAM DUMP\n"); + } else { + if (adc->debug_base) + offset = adc->debug_base; + else + break; + pr_debug("SDAM 20 DUMP\n"); + } + + for (i = 0; i < ADC_SDAM_REG_DUMP; i++) { + rc = adc5_read(adc, offset, buf, sizeof(buf)); + if (rc < 0) { + pr_err("debug register dump failed\n"); + return; + } + offset += ADC_OFFSET_DUMP; + pr_debug("Buf[%d]: %*ph\n", i, sizeof(buf), buf); + } + } +} + +static irqreturn_t adc5_gen3_isr(int irq, void *dev_id) +{ + struct adc5_chip *adc = dev_id; + u8 status, tm_status[2]; + int ret; + + ret = adc5_read(adc, ADC5_GEN3_STATUS1, &status, 1); + if (ret < 0) { + pr_err("adc read status failed with %d\n", ret); + goto handler_end; + } + + if (status & ADC5_GEN3_STATUS1_EOC) + complete(&adc->complete); + + ret = adc5_read(adc, ADC5_GEN3_TM_HIGH_STS, tm_status, 2); + if (ret < 0) { + pr_err("adc read TM status failed with %d\n", ret); + goto handler_end; + } + + if (tm_status[0] || tm_status[1]) + schedule_work(&adc->tm_handler_work); + + pr_debug("Interrupt status:%#x, high:%#x, low:%#x\n", + status, tm_status[0], tm_status[1]); + + if (status & ADC5_GEN3_STATUS1_CONV_FAULT) { + pr_err("Unexpected conversion fault\n"); + adc5_gen3_dump_regs_debug(adc); + } + +handler_end: + return IRQ_HANDLED; +} + +static void tm_handler_work(struct work_struct *work) +{ + struct adc5_chip *adc = container_of(work, struct adc5_chip, + tm_handler_work); + struct adc5_channel_prop *chan_prop; + u8 tm_status[2], buf[14], val; + int ret, i; + + mutex_lock(&adc->lock); + + ret = adc5_read(adc, ADC5_GEN3_TM_HIGH_STS, tm_status, 2); + if (ret < 0) { + pr_err("adc read TM status failed with %d\n", ret); + goto work_unlock; + } + + ret = adc5_write(adc, ADC5_GEN3_TM_HIGH_STS_CLR, tm_status, 2); + if (ret < 0) { + pr_err("adc write TM status failed with %d\n", ret); + goto work_unlock; + } + + /* To indicate conversion request is only to clear a status */ + val = (u8) ~ADC5_GEN3_CHAN_CONV_REQ; + ret = adc5_write(adc, ADC5_GEN3_PERPH_CH, &val, 1); + + val = ADC5_GEN3_CONV_REQ_REQ; + ret = adc5_write(adc, ADC5_GEN3_CONV_REQ, &val, 1); + if (ret < 0) { + pr_err("adc write conv_req failed with %d\n", ret); + goto work_unlock; + } + + ret = adc5_read(adc, ADC5_GEN3_CH1_DATA0, buf, sizeof(buf)); + if (ret < 0) { + pr_err("adc read data failed with %d\n", ret); + goto work_unlock; + } + + mutex_unlock(&adc->lock); + + for (i = 0; i < adc->nchannels; i++) { + bool upper_set = false, lower_set = false; + u8 data_low = 0, data_high = 0; + u16 code = 0; + int temp; + + chan_prop = &adc->chan_props[i]; + if (!chan_prop->adc_tm) + continue; + + mutex_lock(&adc->lock); + if ((tm_status[0] & 0x1) && (chan_prop->high_thr_en)) + upper_set = true; + + if ((tm_status[1] & 0x1) && (chan_prop->low_thr_en)) + lower_set = true; + + tm_status[0] >>= 1; + tm_status[1] >>= 1; + mutex_unlock(&adc->lock); + + if (!(upper_set || lower_set)) + continue; + + data_low = buf[2 * i]; + data_high = buf[2 * i + 1]; + code = ((data_high << 8) | data_low); + pr_debug("ADC_TM threshold code:0x%x\n", code); + + if (chan_prop->adc_tm == ADC_TM_NON_THERMAL) { + if (lower_set) { + mutex_lock(&adc->lock); + chan_prop->low_thr_en = 0; + chan_prop->low_thr_triggered = true; + mutex_unlock(&adc->lock); + queue_work(chan_prop->req_wq, + &chan_prop->work); + } + + if (upper_set) { + mutex_lock(&adc->lock); + chan_prop->high_thr_en = 0; + chan_prop->high_thr_triggered = true; + mutex_unlock(&adc->lock); + queue_work(chan_prop->req_wq, + &chan_prop->work); + } + } else { + ret = qcom_adc5_hw_scale(chan_prop->scale_fn_type, + &adc5_prescale_ratios[chan_prop->prescale], + adc->data, code, &temp); + + if (ret < 0) { + pr_err("Invalid temperature reading, ret=%d, code=0x%x\n", + ret, code); + continue; + } + pr_debug("notifying thermal, temp:%d\n", temp); + chan_prop->last_temp = temp; + chan_prop->last_temp_set = true; + thermal_zone_device_update(chan_prop->tzd, THERMAL_TRIP_VIOLATED); + } + } + + return; + +work_unlock: + mutex_unlock(&adc->lock); +} + +static int adc5_gen3_of_xlate(struct iio_dev *indio_dev, + const struct of_phandle_args *iiospec) +{ + struct adc5_chip *adc = iio_priv(indio_dev); + int i, v_channel; + + for (i = 0; i < adc->nchannels; i++) { + v_channel = V_CHAN(adc->chan_props[i]); + if (v_channel == iiospec->args[0]) + return i; + } + + return -ENOENT; +} + +static int adc5_gen3_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, + long mask) +{ + struct adc5_chip *adc = iio_priv(indio_dev); + struct adc5_channel_prop *prop; + u16 adc_code_volt; + int ret; + + prop = &adc->chan_props[chan->address]; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + ret = adc5_gen3_do_conversion(adc, prop, + &adc_code_volt); + if (ret < 0) + return ret; + + ret = qcom_adc5_hw_scale(prop->scale_fn_type, + &adc5_prescale_ratios[prop->prescale], + adc->data, + adc_code_volt, val); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_RAW: + ret = adc5_gen3_do_conversion(adc, prop, + &adc_code_volt); + if (ret < 0) + return ret; + + *val = (int)adc_code_volt; + return IIO_VAL_INT; + + default: + return -EINVAL; + } + + return 0; +} + +static const struct iio_info adc5_gen3_info = { + .read_raw = adc5_gen3_read_raw, + .of_xlate = adc5_gen3_of_xlate, +}; + +/* Used by thermal clients to read ADC channel temperature */ +int adc_tm_gen3_get_temp(void *data, int *temp) +{ + int ret; + struct adc5_channel_prop *prop = data; + struct adc5_chip *adc; + u16 adc_code_volt; + + if (!prop || !prop->chip) + return -EINVAL; + + adc = prop->chip; + + if (prop->last_temp_set) { + pr_debug("last_temp: %d\n", prop->last_temp); + prop->last_temp_set = false; + *temp = prop->last_temp; + return 0; + } + + ret = adc5_gen3_do_conversion(adc, prop, &adc_code_volt); + if (ret < 0) + return ret; + + return qcom_adc5_hw_scale(prop->scale_fn_type, + &adc5_prescale_ratios[prop->prescale], + adc->data, + adc_code_volt, temp); +} + +static int adc_tm5_gen3_configure(struct adc5_channel_prop *prop) +{ + int ret; + u8 conv_req = 0, buf[12]; + u32 mask = 0; + struct adc5_chip *adc = prop->chip; + + mutex_lock(&adc->lock); + ret = adc5_gen3_poll_wait_hs(adc); + if (ret < 0) + goto tm_config_unlock; + + ret = adc5_read(adc, ADC5_GEN3_SID, buf, sizeof(buf)); + if (ret < 0) + goto tm_config_unlock; + + /* Write SID */ + buf[0] &= (u8) ~ADC5_GEN3_SID_MASK; + buf[0] &= prop->sid; + + buf[1] = prop->tm_chan_index; + + buf[2] = prop->timer; + + /* Digital param selection */ + adc5_gen3_update_dig_param(adc, prop, &buf[3]); + + /* Update fast average sample value */ + buf[4] &= (u8) ~ADC5_GEN3_FAST_AVG_CTL_SAMPLES_MASK; + buf[4] |= prop->avg_samples; + + /* + * Select ADC channel and indicate there is an actual + * conversion request + */ + buf[5] = ADC5_GEN3_CHAN_CONV_REQ | prop->channel; + + /* Select HW settle delay for channel */ + buf[6] &= (u8) ~ADC5_GEN3_HW_SETTLE_DELAY_MASK; + buf[6] |= prop->hw_settle_time; + + buf[7] = prop->high_thr_en << 1 | prop->low_thr_en; + + mask = lower_32_bits(prop->low_thr_voltage); + buf[8] = ADC_TM5_GEN3_LOWER_MASK(mask); + buf[9] = ADC_TM5_GEN3_UPPER_MASK(mask); + + mask = lower_32_bits(prop->high_thr_voltage); + buf[10] = ADC_TM5_GEN3_LOWER_MASK(mask); + buf[11] = ADC_TM5_GEN3_UPPER_MASK(mask); + + ret = adc5_write(adc, ADC5_GEN3_SID, buf, sizeof(buf)); + if (ret < 0) + goto tm_config_unlock; + + conv_req = ADC5_GEN3_CONV_REQ_REQ; + ret = adc5_write(adc, ADC5_GEN3_CONV_REQ, &conv_req, 1); + +tm_config_unlock: + mutex_unlock(&adc->lock); + + return ret; +} + +static int adc_tm5_gen3_set_trip_temp(void *data, + int low_temp, int high_temp) +{ + struct adc5_channel_prop *prop = data; + struct adc5_chip *adc = prop->chip; + struct adc_tm_config tm_config; + int ret; + + if (!prop) + return -EINVAL; + + pr_debug("channel:%s :low_temp(mdegC):%d, high_temp(mdegC):%d\n", + prop->datasheet_name, low_temp, high_temp); + + adc = prop->chip; + tm_config.high_thr_temp = tm_config.low_thr_temp = 0; + if (high_temp != INT_MAX) + tm_config.high_thr_temp = high_temp; + if (low_temp != INT_MIN) + tm_config.low_thr_temp = low_temp; + + if ((high_temp == INT_MAX) && (low_temp == INT_MIN)) { + pr_err("No trips to set\n"); + return -EINVAL; + } + + pr_debug("requested a low temp- %d and high temp- %d\n", + tm_config.low_thr_temp, tm_config.high_thr_temp); + + adc_tm_scale_therm_voltage_100k_gen3(&tm_config); + + /* + * Thresholds are forward scaled to confirm their + * temperatures will actually cause a violation, before + * being written. + */ + pr_debug("high_thr:0x%llx, low_thr:0x%llx\n", + tm_config.high_thr_voltage, tm_config.low_thr_voltage); + + mutex_lock(&adc->lock); + + if (high_temp != INT_MAX) { + prop->low_thr_voltage = tm_config.low_thr_voltage; + prop->low_thr_en = 1; + } else { + prop->low_thr_en = 0; + } + + if (low_temp > -INT_MAX) { + prop->high_thr_voltage = tm_config.high_thr_voltage; + prop->high_thr_en = 1; + } else { + prop->high_thr_en = 0; + } + + ret = adc_tm5_gen3_configure(prop); + if (ret < 0) + pr_err("Error during adc-tm configure:%d\n", ret); + + mutex_unlock(&adc->lock); + + return ret; +} + +#define MAX_PROP_NAME_LEN 32 +struct adc5_chip *get_adc_tm_gen3(struct device *dev, const char *name) +{ + struct platform_device *pdev; + struct adc5_chip *chip; + struct device_node *node = NULL; + char prop_name[MAX_PROP_NAME_LEN]; + + scnprintf(prop_name, MAX_PROP_NAME_LEN, "qcom,%s-adc_tm", name); + + node = of_parse_phandle(dev->of_node, prop_name, 0); + if (node == NULL) + return ERR_PTR(-ENODEV); + + list_for_each_entry(chip, &adc_tm_device_list, list) { + pdev = to_platform_device(chip->dev); + if (pdev->dev.of_node == node) { + of_node_put(node); + return chip; + } + } + of_node_put(node); + return ERR_PTR(-EPROBE_DEFER); +} +EXPORT_SYMBOL(get_adc_tm_gen3); + +static int32_t adc_tm_add_to_list(struct adc5_chip *chip, + uint32_t dt_index, + struct adc_tm_param *param) +{ + struct adc_tm_client_info *client_info = NULL; + bool client_info_exists = false; + + list_for_each_entry(client_info, + &chip->chan_props[dt_index].thr_list, list) { + if (client_info->param->id == param->id) { + client_info->low_thr_requested = param->low_thr; + client_info->high_thr_requested = param->high_thr; + client_info->state_request = param->state_request; + client_info->notify_low_thr = false; + client_info->notify_high_thr = false; + client_info_exists = true; + } + } + + if (!client_info_exists) { + client_info = devm_kzalloc(chip->dev, + sizeof(struct adc_tm_client_info), GFP_KERNEL); + if (!client_info) + return -ENOMEM; + + client_info->param->id = (uintptr_t) client_info; + client_info->low_thr_requested = param->low_thr; + client_info->high_thr_requested = param->high_thr; + client_info->state_request = param->state_request; + + list_add_tail(&client_info->list, + &chip->chan_props[dt_index].thr_list); + } + return 0; +} + +static int32_t adc_tm_gen3_manage_thresholds(struct adc5_channel_prop *prop) +{ + int high_thr = INT_MAX, low_thr = INT_MIN; + struct adc_tm_client_info *client_info = NULL; + struct list_head *thr_list; + uint32_t scale_type = 0; + struct adc_tm_config tm_config; + + prop->high_thr_en = 0; + prop->low_thr_en = 0; + + /* + * Reset the high_thr_set and low_thr_set of all + * clients since the thresholds will be recomputed. + */ + list_for_each(thr_list, &prop->thr_list) { + client_info = list_entry(thr_list, + struct adc_tm_client_info, list); + client_info->high_thr_set = false; + client_info->low_thr_set = false; + } + + /* Find the min of high_thr and max of low_thr */ + list_for_each(thr_list, &prop->thr_list) { + client_info = list_entry(thr_list, + struct adc_tm_client_info, list); + + if ((client_info->state_request == ADC_TM_HIGH_THR_ENABLE) || + (client_info->state_request == + ADC_TM_HIGH_LOW_THR_ENABLE)) + if (client_info->high_thr_requested < high_thr) + high_thr = client_info->high_thr_requested; + + if ((client_info->state_request == ADC_TM_LOW_THR_ENABLE) || + (client_info->state_request == + ADC_TM_HIGH_LOW_THR_ENABLE)) + if (client_info->low_thr_requested > low_thr) + low_thr = client_info->low_thr_requested; + + pr_debug("threshold compared is high:%d and low:%d\n", + client_info->high_thr_requested, + client_info->low_thr_requested); + pr_debug("current threshold is high:%d and low:%d\n", + high_thr, low_thr); + } + + /* Check which of the high_thr and low_thr got set */ + list_for_each(thr_list, &prop->thr_list) { + client_info = list_entry(thr_list, + struct adc_tm_client_info, list); + + if ((client_info->state_request == ADC_TM_HIGH_THR_ENABLE) || + (client_info->state_request == + ADC_TM_HIGH_LOW_THR_ENABLE)) + if (high_thr == client_info->high_thr_requested) { + prop->high_thr_en = 1; + client_info->high_thr_set = true; + } + + if ((client_info->state_request == ADC_TM_LOW_THR_ENABLE) || + (client_info->state_request == + ADC_TM_HIGH_LOW_THR_ENABLE)) + if (low_thr == client_info->low_thr_requested) { + prop->low_thr_en = 1; + client_info->low_thr_set = true; + } + } + + tm_config.high_thr_voltage = (int64_t)high_thr; + tm_config.low_thr_voltage = (int64_t)low_thr; + + scale_type = prop->adc_rscale_fn; + if (scale_type >= SCALE_RSCALE_NONE) + return -EBADF; + + adc_tm_rscale_fn[scale_type].chan(&tm_config); + + prop->low_thr_voltage = tm_config.low_thr_voltage; + prop->high_thr_voltage = tm_config.high_thr_voltage; + + pr_debug("threshold written is high:%d and low:%d\n", + high_thr, low_thr); + + return 0; +} + +/* Used to notify non-thermal clients of threshold crossing */ +void notify_adc_tm_fn(struct work_struct *work) +{ + struct adc5_channel_prop *prop = container_of(work, + struct adc5_channel_prop, work); + struct adc_tm_client_info *client_info = NULL; + struct adc5_chip *chip; + struct list_head *thr_list; + int ret; + + chip = prop->chip; + + mutex_lock(&chip->lock); + if (prop->low_thr_triggered) { + /* adjust thr, calling manage_thr */ + list_for_each(thr_list, &prop->thr_list) { + client_info = list_entry(thr_list, + struct adc_tm_client_info, list); + if (client_info->low_thr_set) { + client_info->low_thr_set = false; + client_info->notify_low_thr = true; + if (client_info->state_request == + ADC_TM_HIGH_LOW_THR_ENABLE) + client_info->state_request = + ADC_TM_HIGH_THR_ENABLE; + else + client_info->state_request = + ADC_TM_LOW_THR_DISABLE; + } + } + prop->low_thr_triggered = false; + } + + if (prop->high_thr_triggered) { + /* adjust thr, calling manage_thr */ + list_for_each(thr_list, &prop->thr_list) { + client_info = list_entry(thr_list, + struct adc_tm_client_info, list); + if (client_info->high_thr_set) { + client_info->high_thr_set = false; + client_info->notify_high_thr = true; + if (client_info->state_request == + ADC_TM_HIGH_LOW_THR_ENABLE) + client_info->state_request = + ADC_TM_LOW_THR_ENABLE; + else + client_info->state_request = + ADC_TM_HIGH_THR_DISABLE; + } + } + prop->high_thr_triggered = false; + } + ret = adc_tm_gen3_manage_thresholds(prop); + if (ret < 0) + pr_err("Error in reverse scaling:%d\n", ret); + + ret = adc_tm5_gen3_configure(prop); + if (ret < 0) + pr_err("Error during adc-tm configure:%d\n", ret); + + mutex_unlock(&chip->lock); + + list_for_each_entry(client_info, &prop->thr_list, list) { + if (client_info->notify_low_thr && + client_info->param->threshold_notification != NULL) { + pr_debug("notify kernel with low state for channel 0x%x\n", + prop->channel); + client_info->param->threshold_notification( + ADC_TM_LOW_STATE, + client_info->param->btm_ctx); + client_info->notify_low_thr = false; + } + + if (client_info->notify_high_thr && + client_info->param->threshold_notification != NULL) { + pr_debug("notify kernel with high state for channel 0x%x\n", + prop->channel); + client_info->param->threshold_notification( + ADC_TM_HIGH_STATE, + client_info->param->btm_ctx); + client_info->notify_high_thr = false; + } + } +} + +/* Used by non-thermal clients to configure an ADC_TM channel */ +int32_t adc_tm_channel_measure_gen3(struct adc5_chip *chip, + struct adc_tm_param *param) + +{ + int ret, i; + uint32_t v_channel, dt_index = 0; + bool chan_found = false; + + if (param == NULL) + return -EINVAL; + + if (param->threshold_notification == NULL) { + pr_debug("No notification for high/low temp\n"); + return -EINVAL; + } + + for (i = 0; i < chip->nchannels; i++) { + v_channel = V_CHAN(chip->chan_props[i]); + if (v_channel == param->channel) { + dt_index = i; + chan_found = true; + break; + } + } + + if (!chan_found) { + pr_err("not a valid ADC_TM channel\n"); + return -EINVAL; + } + + mutex_lock(&chip->lock); + + /* add channel client to channel list */ + adc_tm_add_to_list(chip, dt_index, param); + + /* set right thresholds for the sensor */ + ret = adc_tm_gen3_manage_thresholds(&chip->chan_props[dt_index]); + if (ret < 0) + pr_err("Error in reverse scaling:%d\n", ret); + + /* configure channel */ + ret = adc_tm5_gen3_configure(&chip->chan_props[dt_index]); + if (ret < 0) { + pr_err("Error during adc-tm configure:%d\n", ret); + goto fail_unlock; + } + +fail_unlock: + mutex_unlock(&chip->lock); + return ret; +} +EXPORT_SYMBOL(adc_tm_channel_measure_gen3); + +/* Used by non-thermal clients to release an ADC_TM channel */ +int32_t adc_tm_disable_chan_meas_gen3(struct adc5_chip *chip, + struct adc_tm_param *param) +{ + int ret, i; + uint32_t dt_index = 0, v_channel; + struct adc_tm_client_info *client_info = NULL; + + if (param == NULL) + return -EINVAL; + + for (i = 0; i < chip->nchannels; i++) { + v_channel = V_CHAN(chip->chan_props[i]); + if (v_channel == param->channel) { + dt_index = i; + break; + } + } + + if (i == chip->nchannels) { + pr_err("not a valid ADC_TM channel\n"); + return -EINVAL; + } + + mutex_lock(&chip->lock); + list_for_each_entry(client_info, + &chip->chan_props[i].thr_list, list) { + if (client_info->param->id == param->id) { + client_info->state_request = + ADC_TM_HIGH_LOW_THR_DISABLE; + ret = adc_tm_gen3_manage_thresholds(&chip->chan_props[i]); + if (ret < 0) { + pr_err("Error in reverse scaling:%d\n", + ret); + goto fail; + } + ret = adc_tm5_gen3_configure(&chip->chan_props[i]); + if (ret < 0) { + pr_err("Error during adc-tm configure:%d\n", + ret); + goto fail; + } + } + } + +fail: + mutex_unlock(&chip->lock); + return ret; +} +EXPORT_SYMBOL(adc_tm_disable_chan_meas_gen3); + +static struct thermal_zone_of_device_ops adc_tm_ops = { + .get_temp = adc_tm_gen3_get_temp, + .set_trips = adc_tm5_gen3_set_trip_temp, +}; + +static struct thermal_zone_of_device_ops adc_tm_ops_iio = { + .get_temp = adc_tm_gen3_get_temp, +}; + +static int adc_tm_register_tzd(struct adc5_chip *adc) +{ + unsigned int i, channel; + struct thermal_zone_device *tzd; + + for (i = 0; i < adc->nchannels; i++) { + channel = V_CHAN(adc->chan_props[i]); + + switch (adc->chan_props[i].adc_tm) { + case ADC_TM_NONE: + continue; + case ADC_TM: + tzd = devm_thermal_zone_of_sensor_register( + adc->dev, channel, + &adc->chan_props[i], &adc_tm_ops); + break; + case ADC_TM_IIO: + tzd = devm_thermal_zone_of_sensor_register( + adc->dev, channel, + &adc->chan_props[i], &adc_tm_ops_iio); + break; + case ADC_TM_NON_THERMAL: + tzd = NULL; + break; + default: + pr_err("Invalid ADC_TM type:%d for dt_ch:%d\n", + adc->chan_props[i].adc_tm, adc->chan_props[i].channel); + continue; + } + + if (IS_ERR(tzd)) { + pr_err("Error registering TZ zone:%ld for dt_ch:%d\n", + PTR_ERR(tzd), adc->chan_props[i].channel); + continue; + } + adc->chan_props[i].tzd = tzd; + } + + return 0; +} + +struct adc5_channels { + const char *datasheet_name; + unsigned int prescale_index; + enum iio_chan_type type; + long info_mask; + enum vadc_scale_fn_type scale_fn_type; +}; + +/* In these definitions, _pre refers to an index into adc5_prescale_ratios. */ +#define ADC5_CHAN(_dname, _type, _mask, _pre, _scale) \ + { \ + .datasheet_name = _dname, \ + .prescale_index = _pre, \ + .type = _type, \ + .info_mask = _mask, \ + .scale_fn_type = _scale, \ + }, \ + +#define ADC5_CHAN_TEMP(_dname, _pre, _scale) \ + ADC5_CHAN(_dname, IIO_TEMP, \ + BIT(IIO_CHAN_INFO_PROCESSED), \ + _pre, _scale) \ + +#define ADC5_CHAN_VOLT(_dname, _pre, _scale) \ + ADC5_CHAN(_dname, IIO_VOLTAGE, \ + BIT(IIO_CHAN_INFO_PROCESSED), \ + _pre, _scale) \ + +#define ADC5_CHAN_CUR(_dname, _pre, _scale) \ + ADC5_CHAN(_dname, IIO_CURRENT, \ + BIT(IIO_CHAN_INFO_PROCESSED), \ + _pre, _scale) \ + +static const struct adc5_channels adc5_chans_pmic[ADC5_MAX_CHANNEL] = { + [ADC5_GEN3_OFFSET_REF] = ADC5_CHAN_VOLT("ref_gnd", 0, + SCALE_HW_CALIB_DEFAULT) + [ADC5_GEN3_1P25VREF] = ADC5_CHAN_VOLT("vref_1p25", 0, + SCALE_HW_CALIB_DEFAULT) + [ADC5_GEN3_VPH_PWR] = ADC5_CHAN_VOLT("vph_pwr", 1, + SCALE_HW_CALIB_DEFAULT) + [ADC5_GEN3_VBAT_SNS_QBG] = ADC5_CHAN_VOLT("vbat_sns", 3, + SCALE_HW_CALIB_DEFAULT) + [ADC5_GEN3_AMUX3_THM] = ADC5_CHAN_TEMP("smb_temp", 0, + SCALE_HW_CALIB_PM7_SMB_TEMP) + [ADC5_GEN3_CHG_TEMP] = ADC5_CHAN_TEMP("chg_temp", 0, + SCALE_HW_CALIB_PM7_CHG_TEMP) + [ADC5_GEN3_IIN_FB] = ADC5_CHAN_CUR("iin_fb", 4, + SCALE_HW_CALIB_CUR) + [ADC5_GEN3_ICHG_SMB] = ADC5_CHAN_CUR("ichg_smb", 5, + SCALE_HW_CALIB_CUR) + [ADC5_GEN3_IIN_SMB] = ADC5_CHAN_CUR("iin_smb", 6, + SCALE_HW_CALIB_CUR) + [ADC5_GEN3_ICHG_FB] = ADC5_CHAN_CUR("ichg_fb", 7, + SCALE_HW_CALIB_CUR_RAW) + [ADC5_GEN3_DIE_TEMP] = ADC5_CHAN_TEMP("die_temp", 0, + SCALE_HW_CALIB_PMIC_THERM_PM7) + [ADC5_GEN3_AMUX1_THM_100K_PU] = ADC5_CHAN_TEMP("amux_thm1_pu2", 0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) + [ADC5_GEN3_AMUX2_THM_100K_PU] = ADC5_CHAN_TEMP("amux_thm2_pu2", 0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) + [ADC5_GEN3_AMUX3_THM_100K_PU] = ADC5_CHAN_TEMP("amux_thm3_pu2", 0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) + [ADC5_GEN3_AMUX4_THM_100K_PU] = ADC5_CHAN_TEMP("amux_thm4_pu2", 0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) + [ADC5_GEN3_AMUX5_THM_100K_PU] = ADC5_CHAN_TEMP("amux_thm5_pu2", 0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) + [ADC5_GEN3_AMUX6_THM_100K_PU] = ADC5_CHAN_TEMP("amux_thm6_pu2", 0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) + [ADC5_GEN3_AMUX1_GPIO_100K_PU] = ADC5_CHAN_TEMP("amux1_gpio_pu2", 0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) + [ADC5_GEN3_AMUX2_GPIO_100K_PU] = ADC5_CHAN_TEMP("amux2_gpio_pu2", 0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) + [ADC5_GEN3_AMUX3_GPIO_100K_PU] = ADC5_CHAN_TEMP("amux3_gpio_pu2", 0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) + [ADC5_GEN3_AMUX4_GPIO_100K_PU] = ADC5_CHAN_TEMP("amux4_gpio_pu2", 0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) +}; + +static int adc5_get_dt_channel_data(struct adc5_chip *adc, + struct adc5_channel_prop *prop, + struct device_node *node, + const struct adc5_data *data) +{ + const char *name = node->name, *channel_name; + u32 chan, value, varr[2]; + u32 sid = 0; + int ret; + struct device *dev = adc->dev; + + ret = of_property_read_u32(node, "reg", &chan); + if (ret < 0) { + dev_err(dev, "invalid channel number %s\n", name); + return ret; + } + + /* + * Value read from "reg" is virtual channel number + * virtual channel number = (sid << 8 | channel number). + */ + sid = (chan >> ADC5_GEN3_SID_OFFSET); + chan = (chan & ADC5_GEN3_CHANNEL_MASK); + + if (chan > ADC5_OFFSET_EXT2 || + !data->adc_chans[chan].datasheet_name) { + dev_err(dev, "%s invalid channel number %d\n", name, chan); + return -EINVAL; + } + + /* the channel has DT description */ + prop->channel = chan; + prop->sid = sid; + + channel_name = of_get_property(node, + "label", NULL) ? : node->name; + if (!channel_name) { + pr_err("Invalid channel name\n"); + return -EINVAL; + } + prop->datasheet_name = channel_name; + + ret = of_property_read_u32(node, "qcom,decimation", &value); + if (!ret) { + ret = adc5_decimation_from_dt(value, data->decimation); + if (ret < 0) { + dev_err(dev, "%02x invalid decimation %d\n", + chan, value); + return ret; + } + prop->decimation = ret; + } else { + prop->decimation = ADC5_DECIMATION_DEFAULT; + } + + ret = of_property_read_u32_array(node, "qcom,pre-scaling", varr, 2); + if (!ret) { + ret = adc5_prescaling_from_dt(varr[0], varr[1]); + if (ret < 0) { + dev_err(dev, "%02x invalid pre-scaling <%d %d>\n", + chan, varr[0], varr[1]); + return ret; + } + prop->prescale = ret; + } else { + prop->prescale = + adc->data->adc_chans[prop->channel].prescale_index; + } + + ret = of_property_read_u32(node, "qcom,hw-settle-time", &value); + if (!ret) { + ret = adc5_hw_settle_time_from_dt(value, + data->hw_settle_1); + if (ret < 0) { + dev_err(dev, "%02x invalid hw-settle-time %d us\n", + chan, value); + return ret; + } + prop->hw_settle_time = ret; + } else { + prop->hw_settle_time = VADC_DEF_HW_SETTLE_TIME; + } + + ret = of_property_read_u32(node, "qcom,avg-samples", &value); + if (!ret) { + ret = adc5_avg_samples_from_dt(value); + if (ret < 0) { + dev_err(dev, "%02x invalid avg-samples %d\n", + chan, value); + return ret; + } + prop->avg_samples = ret; + } else { + prop->avg_samples = VADC_DEF_AVG_SAMPLES; + } + + prop->scale_fn_type = -EINVAL; + ret = of_property_read_u32(node, "qcom,scale-fn-type", &value); + if (!ret && value < SCALE_HW_CALIB_INVALID) + prop->scale_fn_type = value; + + if (of_property_read_bool(node, "qcom,ratiometric")) + prop->cal_method = ADC5_RATIOMETRIC_CAL; + else + prop->cal_method = ADC5_ABSOLUTE_CAL; + + prop->timer = MEAS_INT_IMMEDIATE; + prop->adc_tm = ADC_TM_NONE; + + ret = of_property_read_u32(node, "qcom,adc-tm-type", &value); + if (!ret && value < ADC_TM_INVALID) + prop->adc_tm = value; + + if (prop->adc_tm == ADC_TM_NON_THERMAL) { + ret = of_property_read_u32(node, "qcom,rscale-type", + &prop->adc_rscale_fn); + if (ret < 0) + prop->adc_rscale_fn = SCALE_RSCALE_NONE; + } + + if (prop->adc_tm && prop->adc_tm != ADC_TM_IIO) { + adc->n_tm_channels++; + if (adc->n_tm_channels > ADC_TM5_GEN3_CHANS_MAX) { + pr_err("Number of TM nodes greater than channels supported:%d\n", + adc->n_tm_channels); + return -EINVAL; + } + prop->tm_chan_index = adc->n_tm_channels; + prop->timer = MEAS_INT_1S; + + if (prop->adc_tm == ADC_TM_NON_THERMAL) { + prop->req_wq = alloc_workqueue( + "adc_tm_notify_wq", WQ_HIGHPRI, 0); + if (!prop->req_wq) { + pr_err("Requesting priority wq failed\n"); + return -ENOMEM; + } + INIT_WORK(&prop->work, notify_adc_tm_fn); + } + INIT_LIST_HEAD(&prop->thr_list); + } + + dev_dbg(dev, "%02x name %s\n", chan, name); + + return 0; +} + +static const struct adc5_data adc5_gen3_data_pmic = { + .name = "pm-adc5-gen3", + .full_scale_code_volt = 0x70e4, + .full_scale_code_cur = 0x2710, + .adc_chans = adc5_chans_pmic, + .decimation = (unsigned int [ADC5_DECIMATION_SAMPLES_MAX]) + {85, 340, 1360}, + .hw_settle_1 = (unsigned int [VADC_HW_SETTLE_SAMPLES_MAX]) + {15, 100, 200, 300, 400, 500, 600, 700, + 1000, 2000, 4000, 8000, 16000, 32000, + 64000, 128000}, +}; + +static const struct of_device_id adc5_match_table[] = { + { + .compatible = "qcom,spmi-adc5-gen3", + .data = &adc5_gen3_data_pmic, + }, + { } +}; +MODULE_DEVICE_TABLE(of, adc5_match_table); + +static int adc5_get_dt_data(struct adc5_chip *adc, struct device_node *node) +{ + const struct adc5_channels *adc_chan; + struct iio_chan_spec *iio_chan; + struct adc5_channel_prop prop, *chan_props; + struct device_node *child; + unsigned int index = 0; + const struct of_device_id *id; + const struct adc5_data *data; + int ret; + + adc->nchannels = of_get_available_child_count(node); + if (!adc->nchannels) + return -EINVAL; + + adc->iio_chans = devm_kcalloc(adc->dev, adc->nchannels, + sizeof(*adc->iio_chans), GFP_KERNEL); + if (!adc->iio_chans) + return -ENOMEM; + + adc->chan_props = devm_kcalloc(adc->dev, adc->nchannels, + sizeof(*adc->chan_props), GFP_KERNEL); + if (!adc->chan_props) + return -ENOMEM; + + chan_props = adc->chan_props; + adc->n_tm_channels = 0; + iio_chan = adc->iio_chans; + id = of_match_node(adc5_match_table, node); + if (id) + data = id->data; + else + data = &adc5_gen3_data_pmic; + adc->data = data; + + for_each_available_child_of_node(node, child) { + ret = adc5_get_dt_channel_data(adc, &prop, child, data); + if (ret < 0) { + of_node_put(child); + return ret; + } + + prop.chip = adc; + if (prop.scale_fn_type == -EINVAL) + prop.scale_fn_type = + data->adc_chans[prop.channel].scale_fn_type; + *chan_props = prop; + adc_chan = &data->adc_chans[prop.channel]; + + iio_chan->channel = prop.channel; + iio_chan->datasheet_name = prop.datasheet_name; + iio_chan->extend_name = prop.datasheet_name; + iio_chan->info_mask_separate = adc_chan->info_mask; + iio_chan->type = adc_chan->type; + iio_chan->address = index; + iio_chan++; + chan_props++; + index++; + } + + return 0; +} + +static int adc5_gen3_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct adc5_chip *adc; + struct regmap *regmap; + const char *irq_name; + const __be32 *prop_addr; + int ret, irq_eoc, i; + u32 reg; + + regmap = dev_get_regmap(dev->parent, NULL); + if (!regmap) + return -ENODEV; + + ret = of_property_read_u32(node, "reg", ®); + if (ret < 0) + return ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + adc->regmap = regmap; + adc->dev = dev; + + prop_addr = of_get_address(dev->of_node, 0, NULL, NULL); + if (!prop_addr) { + pr_err("invalid IO resource\n"); + return -EINVAL; + } + adc->base = be32_to_cpu(*prop_addr); + + prop_addr = of_get_address(dev->of_node, 1, NULL, NULL); + if (!prop_addr) + pr_debug("invalid debug resource\n"); + else + adc->debug_base = be32_to_cpu(*prop_addr); + + platform_set_drvdata(pdev, adc); + + indio_dev->info = &adc5_gen3_info; + + init_completion(&adc->complete); + mutex_init(&adc->lock); + + ret = adc5_get_dt_data(adc, node); + if (ret < 0) { + pr_err("adc get dt data failed\n"); + goto fail; + } + + adc_tm_register_tzd(adc); + + irq_eoc = platform_get_irq(pdev, 0); + irq_name = "pm-adc5"; + if (adc->data->name) + irq_name = adc->data->name; + + ret = devm_request_irq(dev, irq_eoc, adc5_gen3_isr, 0, + irq_name, adc); + if (ret < 0) + goto fail; + + if (adc->n_tm_channels) + INIT_WORK(&adc->tm_handler_work, tm_handler_work); + + indio_dev->dev.parent = dev; + indio_dev->dev.of_node = node; + indio_dev->name = pdev->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = adc->iio_chans; + indio_dev->num_channels = adc->nchannels; + + list_add_tail(&adc->list, &adc_tm_device_list); + adc->device_list = &adc_tm_device_list; + + return devm_iio_device_register(dev, indio_dev); + +fail: + i = 0; + while (i < adc->nchannels) { + if (adc->chan_props[i].req_wq) + destroy_workqueue(adc->chan_props[i].req_wq); + i++; + } + return ret; +} + +static int adc5_gen3_exit(struct platform_device *pdev) +{ + struct adc5_chip *adc = platform_get_drvdata(pdev); + u8 data = 0; + int i; + + mutex_lock(&adc->lock); + for (i = 0; i < adc->nchannels; i++) { + if (adc->chan_props[i].req_wq) + destroy_workqueue(adc->chan_props[i].req_wq); + adc->chan_props[i].timer = MEAS_INT_DISABLE; + } + + /* Disable all available channels */ + for (i = 0; i < 8; i++) { + data = MEAS_INT_DISABLE; + adc5_write(adc, ADC5_GEN3_TIMER_SEL, &data, 1); + + /* To indicate there is an actual conversion request */ + data = ADC5_GEN3_CHAN_CONV_REQ | i; + adc5_write(adc, ADC5_GEN3_PERPH_CH, &data, 1); + + data = ADC5_GEN3_CONV_REQ_REQ; + adc5_write(adc, ADC5_GEN3_CONV_REQ, &data, 1); + } + + mutex_unlock(&adc->lock); + + if (adc->n_tm_channels) + cancel_work_sync(&adc->tm_handler_work); + + mutex_destroy(&adc->lock); + + list_del(&adc->list); + + return 0; +} + +static struct platform_driver adc5_gen3_driver = { + .driver = { + .name = "qcom-spmi-adc5-gen3.c", + .of_match_table = adc5_match_table, + }, + .probe = adc5_gen3_probe, + .remove = adc5_gen3_exit, +}; +module_platform_driver(adc5_gen3_driver); + +MODULE_ALIAS("platform:qcom-spmi-adc5-gen3"); +MODULE_DESCRIPTION("Qualcomm Technologies Inc. PMIC5 Gen3 ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/qcom-vadc-common.c b/drivers/iio/adc/qcom-vadc-common.c index 3e81aebfb018..d88e530021f7 100644 --- a/drivers/iio/adc/qcom-vadc-common.c +++ b/drivers/iio/adc/qcom-vadc-common.c @@ -1139,6 +1139,95 @@ static int qcom_vadc_scale_hw_chg5_temp( return 0; } +void adc_tm_scale_therm_voltage_100k_gen3(struct adc_tm_config *param) +{ + int temp, ret; + int64_t resistance = 0; + + /* + * High temperature maps to lower threshold voltage. + * Same API can be used for resistance-temperature table + */ + resistance = qcom_vadc_map_temp_voltage(adcmap7_100k, + ARRAY_SIZE(adcmap7_100k), + param->high_thr_temp); + + param->low_thr_voltage = resistance * RATIO_MAX_ADC7; + param->low_thr_voltage = div64_s64(param->low_thr_voltage, + (resistance + R_PU_100K)); + + /* + * low_thr_voltage is ADC raw code corresponding to upper temperature + * threshold. + * Instead of returning the ADC raw code obtained at this point,we first + * do a forward conversion on the (low voltage / high temperature) threshold code, + * to temperature, to check if that code, when read by TM, would translate to + * a temperature greater than or equal to the upper temperature limit (which is + * expected). If it is instead lower than the upper limit (not expected for correct + * TM functionality), we lower the raw code of the threshold written by 1 + * to ensure TM does see a violation when it reads raw code corresponding + * to the upper limit temperature specified. + */ + ret = qcom_vadc7_scale_hw_calib_therm(NULL, NULL, param->low_thr_voltage, &temp); + if (ret < 0) + return; + + if (temp < param->high_thr_temp) + param->low_thr_voltage--; + + /* + * Low temperature maps to higher threshold voltage + * Same API can be used for resistance-temperature table + */ + resistance = qcom_vadc_map_temp_voltage(adcmap7_100k, + ARRAY_SIZE(adcmap7_100k), + param->low_thr_temp); + + param->high_thr_voltage = resistance * RATIO_MAX_ADC7; + param->high_thr_voltage = div64_s64(param->high_thr_voltage, + (resistance + R_PU_100K)); + + /* + * high_thr_voltage is ADC raw code corresponding to lower temperature + * threshold. + * Similar to what is done above for low_thr voltage, we first + * do a forward conversion on the (high voltage / low temperature)threshold code, + * to temperature, to check if that code, when read by TM, would translate to a + * temperature less than or equal to the lower temperature limit (which is expected). + * If it is instead greater than the lower limit (not expected for correct + * TM functionality), we increase the raw code of the threshold written by 1 + * to ensure TM does see a violation when it reads raw code corresponding + * to the lower limit temperature specified. + */ + ret = qcom_vadc7_scale_hw_calib_therm(NULL, NULL, param->high_thr_voltage, &temp); + if (ret < 0) + return; + + if (temp > param->low_thr_temp) + param->high_thr_voltage++; +} +EXPORT_SYMBOL(adc_tm_scale_therm_voltage_100k_gen3); + +int32_t adc_tm_absolute_rthr_gen3(struct adc_tm_config *tm_config) +{ + int64_t low_thr = 0, high_thr = 0; + + low_thr = tm_config->low_thr_voltage; + low_thr *= ADC5_FULL_SCALE_CODE; + + low_thr = div64_s64(low_thr, ADC_VDD_REF); + tm_config->low_thr_voltage = low_thr; + + high_thr = tm_config->high_thr_voltage; + high_thr *= ADC5_FULL_SCALE_CODE; + + high_thr = div64_s64(high_thr, ADC_VDD_REF); + tm_config->high_thr_voltage = high_thr; + + return 0; +} +EXPORT_SYMBOL(adc_tm_absolute_rthr_gen3); + int qcom_vadc_scale(enum vadc_scale_fn_type scaletype, const struct vadc_linear_graph *calib_graph, const struct u32_fract *prescale, diff --git a/include/linux/adc-tm-clients.h b/include/linux/adc-tm-clients.h new file mode 100644 index 000000000000..3a84cea95e8b --- /dev/null +++ b/include/linux/adc-tm-clients.h @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2012-2021, The Linux Foundation. All rights reserved. + */ + +#ifndef __QCOM_ADC_TM_H_CLIENTS__ +#define __QCOM_ADC_TM_H_CLIENTS__ + +#include +#include + +struct adc_tm_chip; +struct adc5_chip; + +/** + * enum adc_tm_state - This lets the client know whether the threshold + * that was crossed was high/low. + * %ADC_TM_HIGH_STATE: Client is notified of crossing the requested high + * voltage threshold. + * %ADC_TM_COOL_STATE: Client is notified of crossing the requested cool + * temperature threshold. + * %ADC_TM_LOW_STATE: Client is notified of crossing the requested low + * voltage threshold. + * %ADC_TM_WARM_STATE: Client is notified of crossing the requested high + * temperature threshold. + */ +enum adc_tm_state { + ADC_TM_HIGH_STATE = 0, + ADC_TM_COOL_STATE = ADC_TM_HIGH_STATE, + ADC_TM_LOW_STATE, + ADC_TM_WARM_STATE = ADC_TM_LOW_STATE, + ADC_TM_STATE_NUM, +}; + +/** + * enum adc_tm_state_request - Request to enable/disable the corresponding + * high/low voltage/temperature thresholds. + * %ADC_TM_HIGH_THR_ENABLE: Enable high voltage threshold. + * %ADC_TM_COOL_THR_ENABLE = Enables cool temperature threshold. + * %ADC_TM_LOW_THR_ENABLE: Enable low voltage/temperature threshold. + * %ADC_TM_WARM_THR_ENABLE = Enables warm temperature threshold. + * %ADC_TM_HIGH_LOW_THR_ENABLE: Enable high and low voltage/temperature + * threshold. + * %ADC_TM_HIGH_THR_DISABLE: Disable high voltage/temperature threshold. + * %ADC_TM_COOL_THR_ENABLE = Disables cool temperature threshold. + * %ADC_TM_LOW_THR_DISABLE: Disable low voltage/temperature threshold. + * %ADC_TM_WARM_THR_ENABLE = Disables warm temperature threshold. + * %ADC_TM_HIGH_THR_DISABLE: Disable high and low voltage/temperature + * threshold. + */ +enum adc_tm_state_request { + ADC_TM_HIGH_THR_ENABLE = 0, + ADC_TM_COOL_THR_ENABLE = ADC_TM_HIGH_THR_ENABLE, + ADC_TM_LOW_THR_ENABLE, + ADC_TM_WARM_THR_ENABLE = ADC_TM_LOW_THR_ENABLE, + ADC_TM_HIGH_LOW_THR_ENABLE, + ADC_TM_HIGH_THR_DISABLE, + ADC_TM_COOL_THR_DISABLE = ADC_TM_HIGH_THR_DISABLE, + ADC_TM_LOW_THR_DISABLE, + ADC_TM_WARM_THR_DISABLE = ADC_TM_LOW_THR_DISABLE, + ADC_TM_HIGH_LOW_THR_DISABLE, + ADC_TM_THR_NUM, +}; + +struct adc_tm_param { + unsigned long id; + int low_thr; + int high_thr; + uint32_t channel; + enum adc_tm_state_request state_request; + void *btm_ctx; + void (*threshold_notification)(enum adc_tm_state state, + void *ctx); +}; + +struct device; + +/* Public API */ + +#if IS_ENABLED(CONFIG_QCOM_SPMI_ADC5_GEN3) +struct adc5_chip *get_adc_tm_gen3(struct device *dev, const char *name); +int32_t adc_tm_channel_measure_gen3(struct adc5_chip *chip, + struct adc_tm_param *param); +int32_t adc_tm_disable_chan_meas_gen3(struct adc5_chip *chip, + struct adc_tm_param *param); +#else +static inline struct adc5_chip *get_adc_tm_gen3( + struct device *dev, const char *name) +{ return ERR_PTR(-ENXIO); } + +static inline int32_t adc_tm_channel_measure_gen3( + struct adc5_chip *chip, + struct adc_tm_param *param) +{ return -ENXIO; } +static inline int32_t adc_tm_disable_chan_meas_gen3( + struct adc5_chip *chip, + struct adc_tm_param *param) +{ return -ENXIO; } + +#endif + +#endif /* __QCOM_ADC_TM_H_CLIENTS__ */ diff --git a/include/linux/iio/adc/qcom-vadc-common.h b/include/linux/iio/adc/qcom-vadc-common.h index c58870892a30..6c5f7ed4cf64 100644 --- a/include/linux/iio/adc/qcom-vadc-common.h +++ b/include/linux/iio/adc/qcom-vadc-common.h @@ -6,6 +6,7 @@ #ifndef QCOM_VADC_COMMON_H #define QCOM_VADC_COMMON_H +#include #include #include @@ -60,6 +61,8 @@ #define R_PU_100K 100000 #define RATIO_MAX_ADC7 BIT(14) +#define ADC_VDD_REF 1875000 + /* * VADC_CALIB_ABSOLUTE: uses the 625mV and 1.25V as reference channels. * VADC_CALIB_RATIOMETRIC: uses the reference voltage (1.8V) and GND for @@ -85,6 +88,54 @@ struct vadc_linear_graph { s32 gnd; }; +/** + * enum adc_tm_rscale_fn_type - Scaling function used to convert the + * channels input voltage/temperature to corresponding ADC code that is + * applied for thresholds. Check the corresponding channels scaling to + * determine the appropriate temperature/voltage units that are passed + * to the scaling function. Example battery follows the power supply + * framework that needs its units to be in decidegreesC so it passes + * deci-degreesC. PA_THERM clients pass the temperature in degrees. + * The order below should match the one in the driver for + * adc_tm_rscale_fn[]. + */ +enum adc_tm_rscale_fn_type { + SCALE_R_ABSOLUTE = 0, + SCALE_RSCALE_NONE, +}; + +/** + * struct adc_tm_config - Represent ADC Thermal Monitor configuration. + * @high_thr_temp: Temperature at which high threshold notification is required. + * @low_thr_temp: Temperature at which low threshold notification is required. + * @low_thr_voltage : Low threshold voltage ADC code used for reverse + * calibration. + * @high_thr_voltage: High threshold voltage ADC code used for reverse + * calibration. + */ +struct adc_tm_config { + int high_thr_temp; + int low_thr_temp; + int64_t high_thr_voltage; + int64_t low_thr_voltage; +}; + +struct adc_tm_reverse_scale_fn { + int32_t (*chan)(struct adc_tm_config *tm_config); +}; + +struct adc_tm_client_info { + struct list_head list; + struct adc_tm_param *param; + int32_t low_thr_requested; + int32_t high_thr_requested; + bool notify_low_thr; + bool notify_high_thr; + bool high_thr_set; + bool low_thr_set; + enum adc_tm_state_request state_request; +}; + /** * enum vadc_scale_fn_type - Scaling function to convert ADC code to * physical scaled units for the channel. @@ -201,4 +252,8 @@ int qcom_adc5_decimation_from_dt(u32 value, const unsigned int *decimation); int qcom_vadc_decimation_from_dt(u32 value); +void adc_tm_scale_therm_voltage_100k_gen3(struct adc_tm_config *param); + +int32_t adc_tm_absolute_rthr_gen3(struct adc_tm_config *tm_config); + #endif /* QCOM_VADC_COMMON_H */ From 56477ca16f7d7386c9525d96660cce4e805abf8d Mon Sep 17 00:00:00 2001 From: Jishnu Prakash Date: Mon, 10 May 2021 15:21:35 +0530 Subject: [PATCH 13/22] dt-bindings: iio: Update support for PM5100 ADC channels Add scaling function for batt_therm, batt_id and usb_in_i and update channel macros for PM5100. Change-Id: I45d6e8b5788276dd939b60034ae7804ca8d1b73a Signed-off-by: Jishnu Prakash [quic_subbaram@quicinc.com: Fixed trivial merge conflict in include/dt-bindings/iio/qcom,spmi-vadc.h] Signed-off-by: Subbaraman Narayanamurthy --- .../iio/qcom,spmi-adc5-gen3-pm5100.h | 24 ++++++++----------- include/dt-bindings/iio/qcom,spmi-vadc.h | 3 +++ 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/include/dt-bindings/iio/qcom,spmi-adc5-gen3-pm5100.h b/include/dt-bindings/iio/qcom,spmi-adc5-gen3-pm5100.h index ad9a1e53b883..13749464bff2 100644 --- a/include/dt-bindings/iio/qcom,spmi-adc5-gen3-pm5100.h +++ b/include/dt-bindings/iio/qcom,spmi-adc5-gen3-pm5100.h @@ -17,8 +17,8 @@ #define PM5100_ADC5_GEN3_DIE_TEMP (PM5100_SID << 8 | 0x03) #define PM5100_ADC5_GEN3_AMUX1_THM (PM5100_SID << 8 | 0x04) -#define PM5100_ADC5_GEN3_AMUX2_THM (PM5100_SID << 8 | 0x05) -#define PM5100_ADC5_GEN3_AMUX3_THM (PM5100_SID << 8 | 0x06) +#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) @@ -28,15 +28,15 @@ #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_USB_SNS_V_16 (PM5100_SID << 8 | 0x11) #define PM5100_ADC5_GEN3_VIN_DIV16_MUX (PM5100_SID << 8 | 0x12) -#define PM5100_ADC5_GEN3_IIN_FB (PM5100_SID << 8 | 0x17) +#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_AMUX2_THM_30K_PU (PM5100_SID << 8 | 0x25) -#define PM5100_ADC5_GEN3_AMUX3_THM_30K_PU (PM5100_SID << 8 | 0x26) +#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) @@ -45,14 +45,10 @@ #define PM5100_ADC5_GEN3_AMUX3_GPIO_30K_PU (PM5100_SID << 8 | 0x2c) #define PM5100_ADC5_GEN3_AMUX4_GPIO_30K_PU (PM5100_SID << 8 | 0x2d) - -#define ADC5_GEN3_AMUX2_GPIO_100K_PU 0x4b - - /* 100k pull-up2 */ #define PM5100_ADC5_GEN3_AMUX1_THM_100K_PU (PM5100_SID << 8 | 0x44) -#define PM5100_ADC5_GEN3_AMUX2_THM_100K_PU (PM5100_SID << 8 | 0x45) -#define PM5100_ADC5_GEN3_AMUX3_THM_100K_PU (PM5100_SID << 8 | 0x46) +#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) @@ -63,8 +59,8 @@ /* 400k pull-up3 */ #define PM5100_ADC5_GEN3_AMUX1_THM_400K_PU (PM5100_SID << 8 | 0x64) -#define PM5100_ADC5_GEN3_AMUX2_THM_400K_PU (PM5100_SID << 8 | 0x65) -#define PM5100_ADC5_GEN3_AMUX3_THM_400K_PU (PM5100_SID << 8 | 0x66) +#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) diff --git a/include/dt-bindings/iio/qcom,spmi-vadc.h b/include/dt-bindings/iio/qcom,spmi-vadc.h index 0d2b7fec125b..f04ab1a7e131 100644 --- a/include/dt-bindings/iio/qcom,spmi-vadc.h +++ b/include/dt-bindings/iio/qcom,spmi-vadc.h @@ -401,5 +401,8 @@ #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 */ From f8f8647cb846d1d864f4b07fb090b97a31aac2f9 Mon Sep 17 00:00:00 2001 From: Jishnu Prakash Date: Mon, 10 May 2021 15:16:21 +0530 Subject: [PATCH 14/22] iio: qcom-spmi-adc5: Update ADC5 Gen3 support for PM5100 Add scaling functions for batt_therm, batt_id and usb_in_i ADC channels for PM5100. Also add usb_sns_v_div_16 and vin_div_16 channels to list of supported channels and correct default prescaling value for vbat_sns channel and full scale ADC current. Change-Id: I86b86c319bff6df583efab8cc4c877218a79fe56 Signed-off-by: Jishnu Prakash [quic_subbaram@quicinc.com: Fixed merge conflicts in qcom-vadc-common.h due to location change and in qcom-vadc-common.c] Signed-off-by: Subbaraman Narayanamurthy [quic_collinsd@quicinc.com: changed struct vadc_prescale_ratio to struct u32_fract and changed prescale "num" and "den" to "numerator" and "denominator" respectively] Signed-off-by: David Collins --- drivers/iio/adc/qcom-spmi-adc5-gen3.c | 8 +- drivers/iio/adc/qcom-vadc-common.c | 160 +++++++++++++++++++++++ include/linux/iio/adc/qcom-vadc-common.h | 18 ++- 3 files changed, 181 insertions(+), 5 deletions(-) diff --git a/drivers/iio/adc/qcom-spmi-adc5-gen3.c b/drivers/iio/adc/qcom-spmi-adc5-gen3.c index f25b1f4fa751..310f802e1abc 100644 --- a/drivers/iio/adc/qcom-spmi-adc5-gen3.c +++ b/drivers/iio/adc/qcom-spmi-adc5-gen3.c @@ -1323,12 +1323,16 @@ static const struct adc5_channels adc5_chans_pmic[ADC5_MAX_CHANNEL] = { SCALE_HW_CALIB_DEFAULT) [ADC5_GEN3_VPH_PWR] = ADC5_CHAN_VOLT("vph_pwr", 1, SCALE_HW_CALIB_DEFAULT) - [ADC5_GEN3_VBAT_SNS_QBG] = ADC5_CHAN_VOLT("vbat_sns", 3, + [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, @@ -1511,7 +1515,7 @@ static int adc5_get_dt_channel_data(struct adc5_chip *adc, static const struct adc5_data adc5_gen3_data_pmic = { .name = "pm-adc5-gen3", .full_scale_code_volt = 0x70e4, - .full_scale_code_cur = 0x2710, + .full_scale_code_cur = 0x2ee0, .adc_chans = adc5_chans_pmic, .decimation = (unsigned int [ADC5_DECIMATION_SAMPLES_MAX]) {85, 340, 1360}, diff --git a/drivers/iio/adc/qcom-vadc-common.c b/drivers/iio/adc/qcom-vadc-common.c index d88e530021f7..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 }, @@ -595,6 +671,18 @@ 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, @@ -634,6 +722,9 @@ static struct qcom_adc5_scale_type scale_adc5_fn[] = { [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}, }; @@ -1127,6 +1218,75 @@ static int qcom_vadc_scale_hw_pm2250_s3_die_temp( 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, diff --git a/include/linux/iio/adc/qcom-vadc-common.h b/include/linux/iio/adc/qcom-vadc-common.h index 6c5f7ed4cf64..cd11b5aba4bc 100644 --- a/include/linux/iio/adc/qcom-vadc-common.h +++ b/include/linux/iio/adc/qcom-vadc-common.h @@ -61,6 +61,8 @@ #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 /* @@ -161,13 +163,13 @@ struct adc_tm_client_info { * 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 @@ -184,6 +186,13 @@ struct adc_tm_client_info { * 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, @@ -209,6 +218,9 @@ enum vadc_scale_fn_type { 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, }; From 50f3f5d30de45d596c4d941c3ea9c16c6c62f993 Mon Sep 17 00:00:00 2001 From: Jishnu Prakash Date: Tue, 25 May 2021 14:41:14 +0530 Subject: [PATCH 15/22] iio: qcom-spmi-adc5: Update ADC5 Gen3 conversion request logic 1.Remove mutexes used in TM configure API to avoid deadlock. 2.Update register writes in immediate and TM configure APIs. 3.Correct register and bit to check for end of conversion for immediate conversion in IRQ handler. Change-Id: I68d0bc4d6aeef089939d0d9ae9161b6122bf110e Signed-off-by: Jishnu Prakash --- drivers/iio/adc/qcom-spmi-adc5-gen3.c | 71 ++++++++++++++------------- 1 file changed, 37 insertions(+), 34 deletions(-) diff --git a/drivers/iio/adc/qcom-spmi-adc5-gen3.c b/drivers/iio/adc/qcom-spmi-adc5-gen3.c index 310f802e1abc..af50371b3de6 100644 --- a/drivers/iio/adc/qcom-spmi-adc5-gen3.c +++ b/drivers/iio/adc/qcom-spmi-adc5-gen3.c @@ -33,7 +33,7 @@ static LIST_HEAD(adc_tm_device_list); #define ADC5_GEN3_STATUS1 0x46 #define ADC5_GEN3_STATUS1_CONV_FAULT BIT(7) #define ADC5_GEN3_STATUS1_THR_CROSS BIT(6) -#define ADC5_GEN3_STATUS1_EOC BIT(1) +#define ADC5_GEN3_STATUS1_EOC BIT(0) #define ADC5_GEN3_TM_EN_STS 0x47 @@ -42,6 +42,7 @@ static LIST_HEAD(adc_tm_device_list); #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 @@ -373,8 +374,11 @@ static int adc5_gen3_configure(struct adc5_chip *adc, buf[0] &= (u8) ~ADC5_GEN3_SID_MASK; buf[0] &= prop->sid; - /* Use channel 0 by default for immediate conversion */ - buf[1] = 0; + /* + * 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] = prop->timer; @@ -383,13 +387,10 @@ static int adc5_gen3_configure(struct adc5_chip *adc, /* Update fast average sample value */ buf[4] &= (u8) ~ADC5_GEN3_FAST_AVG_CTL_SAMPLES_MASK; - buf[4] |= prop->avg_samples; + buf[4] |= prop->avg_samples | ADC5_GEN3_FAST_AVG_CTL_EN; - /* - * Select ADC channel and indicate there is an actual - * conversion request - */ - buf[5] = ADC5_GEN3_CHAN_CONV_REQ | prop->channel; + /* Select ADC channel */ + buf[5] = prop->channel; /* Select HW settle delay for channel */ buf[6] &= (u8) ~ADC5_GEN3_HW_SETTLE_DELAY_MASK; @@ -482,7 +483,7 @@ static int adc5_gen3_do_conversion(struct adc5_chip *adc, goto unlock; /* To indicate conversion request is only to clear a status */ - val = ~ADC5_GEN3_CHAN_CONV_REQ | prop->channel; + val = 0; ret = adc5_write(adc, ADC5_GEN3_PERPH_CH, &val, 1); if (ret < 0) goto unlock; @@ -530,16 +531,17 @@ static void adc5_gen3_dump_regs_debug(struct adc5_chip *adc) static irqreturn_t adc5_gen3_isr(int irq, void *dev_id) { struct adc5_chip *adc = dev_id; - u8 status, tm_status[2]; + u8 status, tm_status[2], eoc_status; int ret; - ret = adc5_read(adc, ADC5_GEN3_STATUS1, &status, 1); + ret = adc5_read(adc, ADC5_GEN3_EOC_STS, &eoc_status, 1); if (ret < 0) { - pr_err("adc read status failed with %d\n", ret); + pr_err("adc read eoc status failed with %d\n", ret); goto handler_end; } - if (status & ADC5_GEN3_STATUS1_EOC) + /* 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); @@ -551,8 +553,14 @@ static irqreturn_t adc5_gen3_isr(int irq, void *dev_id) if (tm_status[0] || tm_status[1]) schedule_work(&adc->tm_handler_work); - pr_debug("Interrupt status:%#x, high:%#x, low:%#x\n", - status, tm_status[0], tm_status[1]); + 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"); @@ -586,7 +594,7 @@ static void tm_handler_work(struct work_struct *work) } /* To indicate conversion request is only to clear a status */ - val = (u8) ~ADC5_GEN3_CHAN_CONV_REQ; + val = 0; ret = adc5_write(adc, ADC5_GEN3_PERPH_CH, &val, 1); val = ADC5_GEN3_CONV_REQ_REQ; @@ -774,20 +782,23 @@ static int adc_tm5_gen3_configure(struct adc5_channel_prop *prop) u32 mask = 0; struct adc5_chip *adc = prop->chip; - mutex_lock(&adc->lock); ret = adc5_gen3_poll_wait_hs(adc); if (ret < 0) - goto tm_config_unlock; + return ret; ret = adc5_read(adc, ADC5_GEN3_SID, buf, sizeof(buf)); if (ret < 0) - goto tm_config_unlock; + return ret; /* Write SID */ buf[0] &= (u8) ~ADC5_GEN3_SID_MASK; buf[0] &= prop->sid; - buf[1] = prop->tm_chan_index; + /* + * 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; @@ -796,13 +807,10 @@ static int adc_tm5_gen3_configure(struct adc5_channel_prop *prop) /* Update fast average sample value */ buf[4] &= (u8) ~ADC5_GEN3_FAST_AVG_CTL_SAMPLES_MASK; - buf[4] |= prop->avg_samples; + buf[4] |= prop->avg_samples | ADC5_GEN3_FAST_AVG_CTL_EN; - /* - * Select ADC channel and indicate there is an actual - * conversion request - */ - buf[5] = ADC5_GEN3_CHAN_CONV_REQ | prop->channel; + /* Select ADC channel */ + buf[5] = prop->channel; /* Select HW settle delay for channel */ buf[6] &= (u8) ~ADC5_GEN3_HW_SETTLE_DELAY_MASK; @@ -820,15 +828,10 @@ static int adc_tm5_gen3_configure(struct adc5_channel_prop *prop) ret = adc5_write(adc, ADC5_GEN3_SID, buf, sizeof(buf)); if (ret < 0) - goto tm_config_unlock; + return ret; conv_req = ADC5_GEN3_CONV_REQ_REQ; - ret = adc5_write(adc, ADC5_GEN3_CONV_REQ, &conv_req, 1); - -tm_config_unlock: - mutex_unlock(&adc->lock); - - return ret; + return adc5_write(adc, ADC5_GEN3_CONV_REQ, &conv_req, 1); } static int adc_tm5_gen3_set_trip_temp(void *data, From 558f4d2b64be7ef551973e203268251daeaf823e Mon Sep 17 00:00:00 2001 From: Jishnu Prakash Date: Fri, 28 May 2021 15:01:40 +0530 Subject: [PATCH 16/22] iio: qcom-spmi-adc5-gen3 : Update conversion and IRQ handling logic Fix timer value for immediate conversion to the right constant to avoid TM conversions for immediate conversion requests. Correct TM channel offsets checked from status and data registers in TM interrupt handler. Change-Id: I43a9a5cf33d31029fe306e61c4bf2e80966b9b7e Signed-off-by: Jishnu Prakash --- drivers/iio/adc/qcom-spmi-adc5-gen3.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/iio/adc/qcom-spmi-adc5-gen3.c b/drivers/iio/adc/qcom-spmi-adc5-gen3.c index af50371b3de6..c07826b071d3 100644 --- a/drivers/iio/adc/qcom-spmi-adc5-gen3.c +++ b/drivers/iio/adc/qcom-spmi-adc5-gen3.c @@ -57,6 +57,7 @@ static LIST_HEAD(adc_tm_device_list); #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) @@ -380,7 +381,7 @@ static int adc5_gen3_configure(struct adc5_chip *adc, */ buf[1] = ADC5_GEN3_CHAN_CONV_REQ | 0; - buf[2] = prop->timer; + buf[2] = ADC5_GEN3_TIME_IMMEDIATE; /* Digital param selection */ adc5_gen3_update_dig_param(adc, prop, &buf[3]); @@ -576,7 +577,7 @@ static void tm_handler_work(struct work_struct *work) struct adc5_chip *adc = container_of(work, struct adc5_chip, tm_handler_work); struct adc5_channel_prop *chan_prop; - u8 tm_status[2], buf[14], val; + u8 tm_status[2], buf[16], val; int ret, i; mutex_lock(&adc->lock); @@ -604,7 +605,7 @@ static void tm_handler_work(struct work_struct *work) goto work_unlock; } - ret = adc5_read(adc, ADC5_GEN3_CH1_DATA0, buf, sizeof(buf)); + 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; @@ -616,28 +617,27 @@ static void tm_handler_work(struct work_struct *work) bool upper_set = false, lower_set = false; u8 data_low = 0, data_high = 0; u16 code = 0; - int temp; + 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] & 0x1) && (chan_prop->high_thr_en)) + if ((tm_status[0] & BIT(offset)) && (chan_prop->high_thr_en)) upper_set = true; - if ((tm_status[1] & 0x1) && (chan_prop->low_thr_en)) + if ((tm_status[1] & BIT(offset)) && (chan_prop->low_thr_en)) lower_set = true; - tm_status[0] >>= 1; - tm_status[1] >>= 1; mutex_unlock(&adc->lock); if (!(upper_set || lower_set)) continue; - data_low = buf[2 * i]; - data_high = buf[2 * i + 1]; + 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); From ee48149cfa3770aa564b949fc528adbcec6a869b Mon Sep 17 00:00:00 2001 From: Jishnu Prakash Date: Sun, 13 Jun 2021 15:46:40 +0530 Subject: [PATCH 17/22] iio: qcom-spmi-adc5-gen3 : update register dump API Correct base offset and register read API used in register dump API called for conversion faults. Change-Id: I274d40fe29f9f32238b9baa5e42b23d4df64cbf2 Signed-off-by: Jishnu Prakash --- drivers/iio/adc/qcom-spmi-adc5-gen3.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/iio/adc/qcom-spmi-adc5-gen3.c b/drivers/iio/adc/qcom-spmi-adc5-gen3.c index c07826b071d3..1833f2630e8d 100644 --- a/drivers/iio/adc/qcom-spmi-adc5-gen3.c +++ b/drivers/iio/adc/qcom-spmi-adc5-gen3.c @@ -507,7 +507,7 @@ static void adc5_gen3_dump_regs_debug(struct adc5_chip *adc) for (j = 0; j < 2; j++) { if (!j) { - offset = 0; + offset = adc->base; pr_debug("ADC SDAM DUMP\n"); } else { if (adc->debug_base) @@ -518,7 +518,7 @@ static void adc5_gen3_dump_regs_debug(struct adc5_chip *adc) } for (i = 0; i < ADC_SDAM_REG_DUMP; i++) { - rc = adc5_read(adc, offset, buf, sizeof(buf)); + rc = regmap_bulk_read(adc->regmap, offset, buf, sizeof(buf)); if (rc < 0) { pr_err("debug register dump failed\n"); return; From bb4024cac6fb38570fc1f8b7ba6db233c1903595 Mon Sep 17 00:00:00 2001 From: Rama Krishna Phani A Date: Wed, 13 Feb 2019 20:24:50 +0530 Subject: [PATCH 18/22] drivers: iio: adc: Add support for suspend to disk Add support to adc driver for suspend to disk feature. Free interrupts during hibernation and re-register them during resume. Change-Id: I71d2aed51f0697e712db59dcc1ef4af0fa98aae4 Signed-off-by: Rama Krishna Phani A Signed-off-by: Veera Vegivada [quic_subbaram@quicinc.com: Fixed merge conflict] Signed-off-by: Subbaraman Narayanamurthy --- drivers/iio/adc/qcom-spmi-adc5.c | 44 +++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/drivers/iio/adc/qcom-spmi-adc5.c b/drivers/iio/adc/qcom-spmi-adc5.c index 0f2b983bdbce..ad435c2be706 100644 --- a/drivers/iio/adc/qcom-spmi-adc5.c +++ b/drivers/iio/adc/qcom-spmi-adc5.c @@ -154,6 +154,7 @@ 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) @@ -961,7 +962,7 @@ static int adc5_probe(struct platform_device *pdev) struct adc5_chip *adc; struct regmap *regmap; const char *irq_name; - int ret, irq_eoc; + int ret; u32 reg; regmap = dev_get_regmap(dev->parent, NULL); @@ -981,6 +982,8 @@ static int adc5_probe(struct platform_device *pdev) adc->dev = dev; adc->base = reg; + dev_set_drvdata(&pdev->dev, adc); + init_completion(&adc->complete); mutex_init(&adc->lock); @@ -990,17 +993,17 @@ 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 { irq_name = "pm-adc5"; if (adc->data->name) irq_name = adc->data->name; - ret = devm_request_irq(dev, irq_eoc, adc5_isr, 0, + ret = devm_request_irq(dev, adc->irq_eoc, adc5_isr, 0, irq_name, adc); if (ret) return ret; @@ -1015,10 +1018,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, }; From 7722611a9276ea91b652aaeeeaf07fb3342be3c1 Mon Sep 17 00:00:00 2001 From: Jishnu Prakash Date: Sun, 20 Jun 2021 21:21:29 +0530 Subject: [PATCH 19/22] iio: qcom-spmi-adc5: Add support for SW calibrated ADC Add support for reading ADC channels on a PMIC7 ADC directly from the ADC_PBS peripheral on that PMIC instead of through PMK8350 ADC peripheral using PBS, for targets where PMK8350 does not run in master mode. The conversion request sequence for using ADC_PBS is similar to the existing one for ADC7, with some differences in register writes. In addition, SW now performs calibration in place of PBS, for every conversion, by reading the three calibration channels, calculating the reference values and entering them in the calibration registers. Change-Id: I9e47ed69e75ee2580a37195ea9063f24ffb8aac5 Signed-off-by: Jishnu Prakash [quic_subbaram@quicinc.com: Fixed merge conflicts and modified to compile with upstream driver version.] Signed-off-by: Subbaraman Narayanamurthy --- drivers/iio/adc/qcom-spmi-adc5.c | 338 ++++++++++++++++++++++++++++++- 1 file changed, 331 insertions(+), 7 deletions(-) diff --git a/drivers/iio/adc/qcom-spmi-adc5.c b/drivers/iio/adc/qcom-spmi-adc5.c index ad435c2be706..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; @@ -159,17 +180,50 @@ struct adc5_chip { 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) @@ -177,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; @@ -318,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, @@ -428,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, @@ -442,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) { @@ -518,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, @@ -528,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; @@ -620,6 +905,8 @@ 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, @@ -808,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; @@ -887,6 +1176,10 @@ 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, @@ -962,8 +1255,10 @@ static int adc5_probe(struct platform_device *pdev) struct adc5_chip *adc; struct regmap *regmap; const char *irq_name; + const __be32 *prop_addr; int ret; u32 reg; + u8 val; regmap = dev_get_regmap(dev->parent, NULL); if (!regmap) @@ -980,9 +1275,27 @@ static int adc5_probe(struct platform_device *pdev) adc = iio_priv(indio_dev); adc->regmap = regmap; adc->dev = dev; - adc->base = reg; - dev_set_drvdata(&pdev->dev, adc); + 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); @@ -1009,6 +1322,17 @@ static int adc5_probe(struct platform_device *pdev) 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; From 3a2a5ea5ee5cb3e8c2766adbd59ee0660a57303c Mon Sep 17 00:00:00 2001 From: Jishnu Prakash Date: Mon, 23 Aug 2021 22:50:02 +0530 Subject: [PATCH 20/22] iio: qcom-spmi-adc5-gen3 : add support to clear conversion fault When an ADC conversion results in a conversion fault, the conversion fault bit in the status register is set, but never cleared, by PBS, causing an error print for conversion fault to appear for all future conversions. Update interrupt handler to clear conversion fault bit if it is found set. Change-Id: I83b36ce585ab5d20639dbf5050f91e8850cc2b75 Signed-off-by: Jishnu Prakash --- drivers/iio/adc/qcom-spmi-adc5-gen3.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/drivers/iio/adc/qcom-spmi-adc5-gen3.c b/drivers/iio/adc/qcom-spmi-adc5-gen3.c index 1833f2630e8d..6a3ca36eec0b 100644 --- a/drivers/iio/adc/qcom-spmi-adc5-gen3.c +++ b/drivers/iio/adc/qcom-spmi-adc5-gen3.c @@ -50,6 +50,9 @@ static LIST_HEAD(adc_tm_device_list); #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 @@ -532,7 +535,7 @@ static void adc5_gen3_dump_regs_debug(struct adc5_chip *adc) static irqreturn_t adc5_gen3_isr(int irq, void *dev_id) { struct adc5_chip *adc = dev_id; - u8 status, tm_status[2], eoc_status; + u8 status, tm_status[2], eoc_status, val; int ret; ret = adc5_read(adc, ADC5_GEN3_EOC_STS, &eoc_status, 1); @@ -566,6 +569,22 @@ static irqreturn_t adc5_gen3_isr(int irq, void *dev_id) 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: @@ -597,6 +616,10 @@ static void tm_handler_work(struct work_struct *work) /* 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); From 22fcbb9e512e299fb0c9bc9970a113945a79c7ae Mon Sep 17 00:00:00 2001 From: Subbaraman Narayanamurthy Date: Wed, 1 Dec 2021 19:16:50 -0800 Subject: [PATCH 21/22] iio: adc: qcom-spmi-adc5-gen3: Fix driver name Fix the typo for driver name. Change-Id: Ie65784e7c2920b7bdb61213524bbddb462d271be Signed-off-by: Subbaraman Narayanamurthy --- drivers/iio/adc/qcom-spmi-adc5-gen3.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/iio/adc/qcom-spmi-adc5-gen3.c b/drivers/iio/adc/qcom-spmi-adc5-gen3.c index 6a3ca36eec0b..14f2c94f98cb 100644 --- a/drivers/iio/adc/qcom-spmi-adc5-gen3.c +++ b/drivers/iio/adc/qcom-spmi-adc5-gen3.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2021, The Linux Foundation. All rights reserved. - * Copyright (c) 2022, Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved. */ #include @@ -1754,7 +1754,7 @@ static int adc5_gen3_exit(struct platform_device *pdev) static struct platform_driver adc5_gen3_driver = { .driver = { - .name = "qcom-spmi-adc5-gen3.c", + .name = "qcom-spmi-adc5-gen3", .of_match_table = adc5_match_table, }, .probe = adc5_gen3_probe, From 4a630f55ec9455fbe23c9078bf7efa97e3ded53b Mon Sep 17 00:00:00 2001 From: Anjelique Melendez Date: Fri, 17 Dec 2021 10:18:39 -0800 Subject: [PATCH 22/22] iio: adc: qcom-spmi-adc5-gen3: Fix calls to qcom_adc5_hw_scale() Currently, calls to qcom_adc5_hw_scale() are passing in a prescale pointer. The qcom_adc5_hw_scale() function signature asks for the prescale int and interanally will get the prescale pointer. Fix this. Change-Id: I4cf630cf47f0b69678b69aecd5058e510bde6b3c Signed-off-by: Anjelique Melendez --- drivers/iio/adc/qcom-spmi-adc5-gen3.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/iio/adc/qcom-spmi-adc5-gen3.c b/drivers/iio/adc/qcom-spmi-adc5-gen3.c index 14f2c94f98cb..6418373578c1 100644 --- a/drivers/iio/adc/qcom-spmi-adc5-gen3.c +++ b/drivers/iio/adc/qcom-spmi-adc5-gen3.c @@ -684,8 +684,7 @@ static void tm_handler_work(struct work_struct *work) } } else { ret = qcom_adc5_hw_scale(chan_prop->scale_fn_type, - &adc5_prescale_ratios[chan_prop->prescale], - adc->data, code, &temp); + chan_prop->prescale, adc->data, code, &temp); if (ret < 0) { pr_err("Invalid temperature reading, ret=%d, code=0x%x\n", @@ -739,8 +738,7 @@ static int adc5_gen3_read_raw(struct iio_dev *indio_dev, return ret; ret = qcom_adc5_hw_scale(prop->scale_fn_type, - &adc5_prescale_ratios[prop->prescale], - adc->data, + prop->prescale, adc->data, adc_code_volt, val); if (ret < 0) return ret; @@ -793,8 +791,7 @@ int adc_tm_gen3_get_temp(void *data, int *temp) return ret; return qcom_adc5_hw_scale(prop->scale_fn_type, - &adc5_prescale_ratios[prop->prescale], - adc->data, + prop->prescale, adc->data, adc_code_volt, temp); }