| // 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} |
| }; |
| |
| static int qcom_vadc_map_voltage_temp(const struct vadc_map_pt *pts, |
| u32 tablesize, s32 input, s64 *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, result = 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); |
| if (ret) |
| return ret; |
| |
| result *= 1000; |
| *result_mdec = result; |
| |
| 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; |
| } |
| |
| 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_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"); |