| // SPDX-License-Identifier: GPL-2.0 |
| #include <linux/bug.h> |
| #include <linux/kernel.h> |
| #include <linux/bitops.h> |
| #include <linux/math64.h> |
| #include <linux/log2.h> |
| #include <linux/err.h> |
| #include <linux/module.h> |
| |
| #include "qcom-vadc-common.h" |
| |
| /* Voltage to temperature */ |
| static const struct vadc_map_pt adcmap_100k_104ef_104fb[] = { |
| {1758, -40}, |
| {1742, -35}, |
| {1719, -30}, |
| {1691, -25}, |
| {1654, -20}, |
| {1608, -15}, |
| {1551, -10}, |
| {1483, -5}, |
| {1404, 0}, |
| {1315, 5}, |
| {1218, 10}, |
| {1114, 15}, |
| {1007, 20}, |
| {900, 25}, |
| {795, 30}, |
| {696, 35}, |
| {605, 40}, |
| {522, 45}, |
| {448, 50}, |
| {383, 55}, |
| {327, 60}, |
| {278, 65}, |
| {237, 70}, |
| {202, 75}, |
| {172, 80}, |
| {146, 85}, |
| {125, 90}, |
| {107, 95}, |
| {92, 100}, |
| {79, 105}, |
| {68, 110}, |
| {59, 115}, |
| {51, 120}, |
| {44, 125} |
| }; |
| |
| /* |
| * Voltage to temperature table for 100k pull up for NTCG104EF104 with |
| * 1.875V reference. |
| */ |
| static const struct vadc_map_pt adcmap_100k_104ef_104fb_1875_vref[] = { |
| { 1831, -40000 }, |
| { 1814, -35000 }, |
| { 1791, -30000 }, |
| { 1761, -25000 }, |
| { 1723, -20000 }, |
| { 1675, -15000 }, |
| { 1616, -10000 }, |
| { 1545, -5000 }, |
| { 1463, 0 }, |
| { 1370, 5000 }, |
| { 1268, 10000 }, |
| { 1160, 15000 }, |
| { 1049, 20000 }, |
| { 937, 25000 }, |
| { 828, 30000 }, |
| { 726, 35000 }, |
| { 630, 40000 }, |
| { 544, 45000 }, |
| { 467, 50000 }, |
| { 399, 55000 }, |
| { 340, 60000 }, |
| { 290, 65000 }, |
| { 247, 70000 }, |
| { 209, 75000 }, |
| { 179, 80000 }, |
| { 153, 85000 }, |
| { 130, 90000 }, |
| { 112, 95000 }, |
| { 96, 100000 }, |
| { 82, 105000 }, |
| { 71, 110000 }, |
| { 62, 115000 }, |
| { 53, 120000 }, |
| { 46, 125000 }, |
| }; |
| |
| static int qcom_vadc_scale_hw_calib_volt( |
| const struct vadc_prescale_ratio *prescale, |
| const struct adc5_data *data, |
| u16 adc_code, int *result_uv); |
| static int qcom_vadc_scale_hw_calib_therm( |
| const struct vadc_prescale_ratio *prescale, |
| const struct adc5_data *data, |
| u16 adc_code, int *result_mdec); |
| static int qcom_vadc_scale_hw_smb_temp( |
| const struct vadc_prescale_ratio *prescale, |
| const struct adc5_data *data, |
| u16 adc_code, int *result_mdec); |
| static int qcom_vadc_scale_hw_chg5_temp( |
| const struct vadc_prescale_ratio *prescale, |
| const struct adc5_data *data, |
| u16 adc_code, int *result_mdec); |
| static int qcom_vadc_scale_hw_calib_die_temp( |
| const struct vadc_prescale_ratio *prescale, |
| const struct adc5_data *data, |
| u16 adc_code, int *result_mdec); |
| |
| static struct qcom_adc5_scale_type scale_adc5_fn[] = { |
| [SCALE_HW_CALIB_DEFAULT] = {qcom_vadc_scale_hw_calib_volt}, |
| [SCALE_HW_CALIB_THERM_100K_PULLUP] = {qcom_vadc_scale_hw_calib_therm}, |
| [SCALE_HW_CALIB_XOTHERM] = {qcom_vadc_scale_hw_calib_therm}, |
| [SCALE_HW_CALIB_PMIC_THERM] = {qcom_vadc_scale_hw_calib_die_temp}, |
| [SCALE_HW_CALIB_PM5_CHG_TEMP] = {qcom_vadc_scale_hw_chg5_temp}, |
| [SCALE_HW_CALIB_PM5_SMB_TEMP] = {qcom_vadc_scale_hw_smb_temp}, |
| }; |
| |
| static int qcom_vadc_map_voltage_temp(const struct vadc_map_pt *pts, |
| u32 tablesize, s32 input, int *output) |
| { |
| bool descending = 1; |
| u32 i = 0; |
| |
| if (!pts) |
| return -EINVAL; |
| |
| /* Check if table is descending or ascending */ |
| if (tablesize > 1) { |
| if (pts[0].x < pts[1].x) |
| descending = 0; |
| } |
| |
| while (i < tablesize) { |
| if ((descending) && (pts[i].x < input)) { |
| /* table entry is less than measured*/ |
| /* value and table is descending, stop */ |
| break; |
| } else if ((!descending) && |
| (pts[i].x > input)) { |
| /* table entry is greater than measured*/ |
| /*value and table is ascending, stop */ |
| break; |
| } |
| i++; |
| } |
| |
| if (i == 0) { |
| *output = pts[0].y; |
| } else if (i == tablesize) { |
| *output = pts[tablesize - 1].y; |
| } else { |
| /* result is between search_index and search_index-1 */ |
| /* interpolate linearly */ |
| *output = (((s32)((pts[i].y - pts[i - 1].y) * |
| (input - pts[i - 1].x)) / |
| (pts[i].x - pts[i - 1].x)) + |
| pts[i - 1].y); |
| } |
| |
| return 0; |
| } |
| |
| static void qcom_vadc_scale_calib(const struct vadc_linear_graph *calib_graph, |
| u16 adc_code, |
| bool absolute, |
| s64 *scale_voltage) |
| { |
| *scale_voltage = (adc_code - calib_graph->gnd); |
| *scale_voltage *= calib_graph->dx; |
| *scale_voltage = div64_s64(*scale_voltage, calib_graph->dy); |
| if (absolute) |
| *scale_voltage += calib_graph->dx; |
| |
| if (*scale_voltage < 0) |
| *scale_voltage = 0; |
| } |
| |
| static int qcom_vadc_scale_volt(const struct vadc_linear_graph *calib_graph, |
| const struct vadc_prescale_ratio *prescale, |
| bool absolute, u16 adc_code, |
| int *result_uv) |
| { |
| s64 voltage = 0, result = 0; |
| |
| qcom_vadc_scale_calib(calib_graph, adc_code, absolute, &voltage); |
| |
| voltage = voltage * prescale->den; |
| result = div64_s64(voltage, prescale->num); |
| *result_uv = result; |
| |
| return 0; |
| } |
| |
| static int qcom_vadc_scale_therm(const struct vadc_linear_graph *calib_graph, |
| const struct vadc_prescale_ratio *prescale, |
| bool absolute, u16 adc_code, |
| int *result_mdec) |
| { |
| s64 voltage = 0; |
| int ret; |
| |
| qcom_vadc_scale_calib(calib_graph, adc_code, absolute, &voltage); |
| |
| if (absolute) |
| voltage = div64_s64(voltage, 1000); |
| |
| ret = qcom_vadc_map_voltage_temp(adcmap_100k_104ef_104fb, |
| ARRAY_SIZE(adcmap_100k_104ef_104fb), |
| voltage, result_mdec); |
| if (ret) |
| return ret; |
| |
| *result_mdec *= 1000; |
| |
| return 0; |
| } |
| |
| static int qcom_vadc_scale_die_temp(const struct vadc_linear_graph *calib_graph, |
| const struct vadc_prescale_ratio *prescale, |
| bool absolute, |
| u16 adc_code, int *result_mdec) |
| { |
| s64 voltage = 0; |
| u64 temp; /* Temporary variable for do_div */ |
| |
| qcom_vadc_scale_calib(calib_graph, adc_code, absolute, &voltage); |
| |
| if (voltage > 0) { |
| temp = voltage * prescale->den; |
| do_div(temp, prescale->num * 2); |
| voltage = temp; |
| } else { |
| voltage = 0; |
| } |
| |
| voltage -= KELVINMIL_CELSIUSMIL; |
| *result_mdec = voltage; |
| |
| return 0; |
| } |
| |
| static int qcom_vadc_scale_chg_temp(const struct vadc_linear_graph *calib_graph, |
| const struct vadc_prescale_ratio *prescale, |
| bool absolute, |
| u16 adc_code, int *result_mdec) |
| { |
| s64 voltage = 0, result = 0; |
| |
| qcom_vadc_scale_calib(calib_graph, adc_code, absolute, &voltage); |
| |
| voltage = voltage * prescale->den; |
| voltage = div64_s64(voltage, prescale->num); |
| voltage = ((PMI_CHG_SCALE_1) * (voltage * 2)); |
| voltage = (voltage + PMI_CHG_SCALE_2); |
| result = div64_s64(voltage, 1000000); |
| *result_mdec = result; |
| |
| return 0; |
| } |
| |
| static int qcom_vadc_scale_code_voltage_factor(u16 adc_code, |
| const struct vadc_prescale_ratio *prescale, |
| const struct adc5_data *data, |
| unsigned int factor) |
| { |
| s64 voltage, temp, adc_vdd_ref_mv = 1875; |
| |
| /* |
| * The normal data range is between 0V to 1.875V. On cases where |
| * we read low voltage values, the ADC code can go beyond the |
| * range and the scale result is incorrect so we clamp the values |
| * for the cases where the code represents a value below 0V |
| */ |
| 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->den; |
| temp = prescale->num * factor; |
| voltage = div64_s64(voltage, temp); |
| } else { |
| voltage = 0; |
| } |
| |
| return (int) voltage; |
| } |
| |
| static int qcom_vadc_scale_hw_calib_volt( |
| const struct vadc_prescale_ratio *prescale, |
| const struct adc5_data *data, |
| u16 adc_code, int *result_uv) |
| { |
| *result_uv = qcom_vadc_scale_code_voltage_factor(adc_code, |
| prescale, data, 1); |
| |
| return 0; |
| } |
| |
| static int qcom_vadc_scale_hw_calib_therm( |
| const struct vadc_prescale_ratio *prescale, |
| const struct adc5_data *data, |
| u16 adc_code, int *result_mdec) |
| { |
| int voltage; |
| |
| voltage = qcom_vadc_scale_code_voltage_factor(adc_code, |
| prescale, data, 1000); |
| |
| /* Map voltage to temperature from look-up table */ |
| return qcom_vadc_map_voltage_temp(adcmap_100k_104ef_104fb_1875_vref, |
| ARRAY_SIZE(adcmap_100k_104ef_104fb_1875_vref), |
| voltage, result_mdec); |
| } |
| |
| static int qcom_vadc_scale_hw_calib_die_temp( |
| const struct vadc_prescale_ratio *prescale, |
| const struct adc5_data *data, |
| u16 adc_code, int *result_mdec) |
| { |
| *result_mdec = qcom_vadc_scale_code_voltage_factor(adc_code, |
| prescale, data, 2); |
| *result_mdec -= KELVINMIL_CELSIUSMIL; |
| |
| return 0; |
| } |
| |
| static int qcom_vadc_scale_hw_smb_temp( |
| const struct vadc_prescale_ratio *prescale, |
| const struct adc5_data *data, |
| u16 adc_code, int *result_mdec) |
| { |
| *result_mdec = qcom_vadc_scale_code_voltage_factor(adc_code * 100, |
| prescale, data, PMIC5_SMB_TEMP_SCALE_FACTOR); |
| *result_mdec = PMIC5_SMB_TEMP_CONSTANT - *result_mdec; |
| |
| return 0; |
| } |
| |
| static int qcom_vadc_scale_hw_chg5_temp( |
| const struct vadc_prescale_ratio *prescale, |
| const struct adc5_data *data, |
| u16 adc_code, int *result_mdec) |
| { |
| *result_mdec = qcom_vadc_scale_code_voltage_factor(adc_code, |
| prescale, data, 4); |
| *result_mdec = PMIC5_CHG_TEMP_SCALE_FACTOR - *result_mdec; |
| |
| return 0; |
| } |
| |
| int qcom_vadc_scale(enum vadc_scale_fn_type scaletype, |
| const struct vadc_linear_graph *calib_graph, |
| const struct vadc_prescale_ratio *prescale, |
| bool absolute, |
| u16 adc_code, int *result) |
| { |
| switch (scaletype) { |
| case SCALE_DEFAULT: |
| return qcom_vadc_scale_volt(calib_graph, prescale, |
| absolute, adc_code, |
| result); |
| case SCALE_THERM_100K_PULLUP: |
| case SCALE_XOTHERM: |
| return qcom_vadc_scale_therm(calib_graph, prescale, |
| absolute, adc_code, |
| result); |
| case SCALE_PMIC_THERM: |
| return qcom_vadc_scale_die_temp(calib_graph, prescale, |
| absolute, adc_code, |
| result); |
| case SCALE_PMI_CHG_TEMP: |
| return qcom_vadc_scale_chg_temp(calib_graph, prescale, |
| absolute, adc_code, |
| result); |
| default: |
| return -EINVAL; |
| } |
| } |
| EXPORT_SYMBOL(qcom_vadc_scale); |
| |
| int qcom_adc5_hw_scale(enum vadc_scale_fn_type scaletype, |
| const struct vadc_prescale_ratio *prescale, |
| const struct adc5_data *data, |
| u16 adc_code, int *result) |
| { |
| if (!(scaletype >= SCALE_HW_CALIB_DEFAULT && |
| scaletype < SCALE_HW_CALIB_INVALID)) { |
| pr_err("Invalid scale type %d\n", scaletype); |
| return -EINVAL; |
| } |
| |
| return scale_adc5_fn[scaletype].scale_fn(prescale, data, |
| adc_code, result); |
| } |
| EXPORT_SYMBOL(qcom_adc5_hw_scale); |
| |
| int qcom_vadc_decimation_from_dt(u32 value) |
| { |
| if (!is_power_of_2(value) || value < VADC_DECIMATION_MIN || |
| value > VADC_DECIMATION_MAX) |
| return -EINVAL; |
| |
| return __ffs64(value / VADC_DECIMATION_MIN); |
| } |
| EXPORT_SYMBOL(qcom_vadc_decimation_from_dt); |
| |
| MODULE_LICENSE("GPL v2"); |
| MODULE_DESCRIPTION("Qualcomm ADC common functionality"); |