blob: bde18d44e902796cbf1cdb462dffb1b52a4086f2 [file] [log] [blame]
/* Copyright (c) 2012, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#define pr_fmt(fmt) "%s: " fmt, __func__
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/types.h>
#include <linux/hwmon.h>
#include <linux/module.h>
#include <linux/debugfs.h>
#include <linux/spmi.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/completion.h>
#include <linux/qpnp/qpnp-adc.h>
#include <linux/platform_device.h>
/* Min ADC code represets 0V */
#define QPNP_VADC_MIN_ADC_CODE 0x6000
/* Max ADC code represents full-scale range of 1.8V */
#define QPNP_VADC_MAX_ADC_CODE 0xA800
#define KELVINMIL_DEGMIL 273160
/* Units for temperature below (on x axis) is in 0.1DegC as
required by the battery driver. Note the resolution used
here to compute the table was done for DegC to milli-volts.
In consideration to limit the size of the table for the given
temperature range below, the result is linearly interpolated
and provided to the battery driver in the units desired for
their framework which is 0.1DegC. True resolution of 0.1DegC
will result in the below table size to increase by 10 times */
static const struct qpnp_vadc_map_pt adcmap_btm_threshold[] = {
{-300, 1642},
{-200, 1544},
{-100, 1414},
{0, 1260},
{10, 1244},
{20, 1228},
{30, 1212},
{40, 1195},
{50, 1179},
{60, 1162},
{70, 1146},
{80, 1129},
{90, 1113},
{100, 1097},
{110, 1080},
{120, 1064},
{130, 1048},
{140, 1032},
{150, 1016},
{160, 1000},
{170, 985},
{180, 969},
{190, 954},
{200, 939},
{210, 924},
{220, 909},
{230, 894},
{240, 880},
{250, 866},
{260, 852},
{270, 838},
{280, 824},
{290, 811},
{300, 798},
{310, 785},
{320, 773},
{330, 760},
{340, 748},
{350, 736},
{360, 725},
{370, 713},
{380, 702},
{390, 691},
{400, 681},
{410, 670},
{420, 660},
{430, 650},
{440, 640},
{450, 631},
{460, 622},
{470, 613},
{480, 604},
{490, 595},
{500, 587},
{510, 579},
{520, 571},
{530, 563},
{540, 556},
{550, 548},
{560, 541},
{570, 534},
{580, 527},
{590, 521},
{600, 514},
{610, 508},
{620, 502},
{630, 496},
{640, 490},
{650, 485},
{660, 281},
{670, 274},
{680, 267},
{690, 260},
{700, 254},
{710, 247},
{720, 241},
{730, 235},
{740, 229},
{750, 224},
{760, 218},
{770, 213},
{780, 208},
{790, 203}
};
/* Voltage to temperature */
static const struct qpnp_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 */
static const struct qpnp_vadc_map_pt adcmap_150k_104ef_104fb[] = {
{1738, -40},
{1714, -35},
{1682, -30},
{1641, -25},
{1589, -20},
{1526, -15},
{1451, -10},
{1363, -5},
{1266, 0},
{1159, 5},
{1048, 10},
{936, 15},
{825, 20},
{720, 25},
{622, 30},
{533, 35},
{454, 40},
{385, 45},
{326, 50},
{275, 55},
{232, 60},
{195, 65},
{165, 70},
{139, 75},
{118, 80},
{100, 85},
{85, 90},
{73, 95},
{62, 100},
{53, 105},
{46, 110},
{40, 115},
{34, 120},
{30, 125}
};
static int32_t qpnp_adc_map_voltage_temp(const struct qpnp_vadc_map_pt *pts,
uint32_t tablesize, int32_t input, int64_t *output)
{
bool descending = 1;
uint32_t i = 0;
if ((pts == NULL) || (output == NULL))
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 == 1) && (pts[i].x < input)) {
/* table entry is less than measured
value and table is descending, stop */
break;
} else if ((descending == 0) &&
(pts[i].x > input)) {
/* table entry is greater than measured
value and table is ascending, stop */
break;
} else {
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 = (((int32_t) ((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 int32_t qpnp_adc_map_temp_voltage(const struct qpnp_vadc_map_pt *pts,
uint32_t tablesize, int32_t input, int64_t *output)
{
bool descending = 1;
uint32_t i = 0;
if ((pts == NULL) || (output == NULL))
return -EINVAL;
/* Check if table is descending or ascending */
if (tablesize > 1) {
if (pts[0].y < pts[1].y)
descending = 0;
}
while (i < tablesize) {
if ((descending == 1) && (pts[i].y < input)) {
/* table entry is less than measured
value and table is descending, stop */
break;
} else if ((descending == 0) && (pts[i].y > input)) {
/* table entry is greater than measured
value and table is ascending, stop */
break;
} else {
i++;
}
}
if (i == 0) {
*output = pts[0].x;
} else if (i == tablesize) {
*output = pts[tablesize-1].x;
} else {
/* result is between search_index and search_index-1 */
/* interpolate linearly */
*output = (((int32_t) ((pts[i].x - pts[i-1].x)*
(input - pts[i-1].y))/
(pts[i].y - pts[i-1].y))+
pts[i-1].x);
}
return 0;
}
static int64_t qpnp_adc_scale_ratiometric_calib(int32_t adc_code,
const struct qpnp_adc_properties *adc_properties,
const struct qpnp_vadc_chan_properties *chan_properties)
{
int64_t adc_voltage = 0;
bool negative_offset = 0;
if (!chan_properties || !chan_properties->offset_gain_numerator ||
!chan_properties->offset_gain_denominator || !adc_properties)
return -EINVAL;
adc_voltage = (adc_code -
chan_properties->adc_graph[CALIB_RATIOMETRIC].adc_gnd)
* adc_properties->adc_vdd_reference;
if (adc_voltage < 0) {
negative_offset = 1;
adc_voltage = -adc_voltage;
}
do_div(adc_voltage,
chan_properties->adc_graph[CALIB_RATIOMETRIC].dy);
if (negative_offset)
adc_voltage = -adc_voltage;
return adc_voltage;
}
int32_t qpnp_adc_scale_pmic_therm(int32_t adc_code,
const struct qpnp_adc_properties *adc_properties,
const struct qpnp_vadc_chan_properties *chan_properties,
struct qpnp_vadc_result *adc_chan_result)
{
int64_t pmic_voltage = 0;
bool negative_offset = 0;
if (!chan_properties || !chan_properties->offset_gain_numerator ||
!chan_properties->offset_gain_denominator || !adc_properties
|| !adc_chan_result)
return -EINVAL;
pmic_voltage = (adc_code -
chan_properties->adc_graph[CALIB_ABSOLUTE].adc_gnd)
* chan_properties->adc_graph[CALIB_ABSOLUTE].dx;
if (pmic_voltage < 0) {
negative_offset = 1;
pmic_voltage = -pmic_voltage;
}
do_div(pmic_voltage,
chan_properties->adc_graph[CALIB_ABSOLUTE].dy);
if (negative_offset)
pmic_voltage = -pmic_voltage;
pmic_voltage += chan_properties->adc_graph[CALIB_ABSOLUTE].dx;
if (pmic_voltage > 0) {
/* 2mV/K */
adc_chan_result->measurement = pmic_voltage*
chan_properties->offset_gain_denominator;
do_div(adc_chan_result->measurement,
chan_properties->offset_gain_numerator * 2);
} else {
adc_chan_result->measurement = 0;
}
/* Change to .001 deg C */
adc_chan_result->measurement -= KELVINMIL_DEGMIL;
adc_chan_result->physical = (int32_t)adc_chan_result->measurement;
return 0;
}
EXPORT_SYMBOL_GPL(qpnp_adc_scale_pmic_therm);
/* Scales the ADC code to 0.001 degrees C using the map
* table for the XO thermistor.
*/
int32_t qpnp_adc_tdkntcg_therm(int32_t adc_code,
const struct qpnp_adc_properties *adc_properties,
const struct qpnp_vadc_chan_properties *chan_properties,
struct qpnp_vadc_result *adc_chan_result)
{
int64_t xo_thm = 0;
if (!chan_properties || !chan_properties->offset_gain_numerator ||
!chan_properties->offset_gain_denominator || !adc_properties
|| !adc_chan_result)
return -EINVAL;
xo_thm = qpnp_adc_scale_ratiometric_calib(adc_code,
adc_properties, chan_properties);
qpnp_adc_map_voltage_temp(adcmap_100k_104ef_104fb,
ARRAY_SIZE(adcmap_100k_104ef_104fb),
xo_thm, &adc_chan_result->physical);
return 0;
}
EXPORT_SYMBOL_GPL(qpnp_adc_tdkntcg_therm);
int32_t qpnp_adc_scale_batt_therm(int32_t adc_code,
const struct qpnp_adc_properties *adc_properties,
const struct qpnp_vadc_chan_properties *chan_properties,
struct qpnp_vadc_result *adc_chan_result)
{
int64_t bat_voltage = 0;
bat_voltage = qpnp_adc_scale_ratiometric_calib(adc_code,
adc_properties, chan_properties);
return qpnp_adc_map_temp_voltage(
adcmap_btm_threshold,
ARRAY_SIZE(adcmap_btm_threshold),
bat_voltage,
&adc_chan_result->physical);
}
EXPORT_SYMBOL_GPL(qpnp_adc_scale_batt_therm);
int32_t qpnp_adc_scale_therm_pu1(int32_t adc_code,
const struct qpnp_adc_properties *adc_properties,
const struct qpnp_vadc_chan_properties *chan_properties,
struct qpnp_vadc_result *adc_chan_result)
{
int64_t therm_voltage = 0;
therm_voltage = qpnp_adc_scale_ratiometric_calib(adc_code,
adc_properties, chan_properties);
qpnp_adc_map_voltage_temp(adcmap_150k_104ef_104fb,
ARRAY_SIZE(adcmap_150k_104ef_104fb),
therm_voltage, &adc_chan_result->physical);
return 0;
}
EXPORT_SYMBOL_GPL(qpnp_adc_scale_therm_pu1);
int32_t qpnp_adc_scale_therm_pu2(int32_t adc_code,
const struct qpnp_adc_properties *adc_properties,
const struct qpnp_vadc_chan_properties *chan_properties,
struct qpnp_vadc_result *adc_chan_result)
{
int64_t therm_voltage = 0;
therm_voltage = qpnp_adc_scale_ratiometric_calib(adc_code,
adc_properties, chan_properties);
qpnp_adc_map_voltage_temp(adcmap_100k_104ef_104fb,
ARRAY_SIZE(adcmap_100k_104ef_104fb),
therm_voltage, &adc_chan_result->physical);
return 0;
}
EXPORT_SYMBOL_GPL(qpnp_adc_scale_therm_pu2);
int32_t qpnp_adc_tm_scale_voltage_therm_pu2(uint32_t reg, int64_t *result)
{
int64_t adc_voltage = 0;
struct qpnp_vadc_linear_graph param1;
int negative_offset;
qpnp_get_vadc_gain_and_offset(&param1, CALIB_RATIOMETRIC);
adc_voltage = (reg - param1.adc_gnd) * param1.adc_vref;
if (adc_voltage < 0) {
negative_offset = 1;
adc_voltage = -adc_voltage;
}
do_div(adc_voltage, param1.dy);
qpnp_adc_map_voltage_temp(adcmap_100k_104ef_104fb,
ARRAY_SIZE(adcmap_100k_104ef_104fb),
adc_voltage, result);
if (negative_offset)
adc_voltage = -adc_voltage;
return 0;
}
EXPORT_SYMBOL_GPL(qpnp_adc_tm_scale_voltage_therm_pu2);
int32_t qpnp_adc_tm_scale_therm_voltage_pu2(struct qpnp_adc_tm_config *param)
{
struct qpnp_vadc_linear_graph param1;
int rc;
qpnp_get_vadc_gain_and_offset(&param1, CALIB_RATIOMETRIC);
rc = qpnp_adc_map_temp_voltage(adcmap_100k_104ef_104fb,
ARRAY_SIZE(adcmap_100k_104ef_104fb),
param->low_thr_temp, &param->low_thr_voltage);
if (rc)
return rc;
param->low_thr_voltage *= param1.dy;
do_div(param->low_thr_voltage, param1.adc_vref);
param->low_thr_voltage += param1.adc_gnd;
rc = qpnp_adc_map_temp_voltage(adcmap_100k_104ef_104fb,
ARRAY_SIZE(adcmap_100k_104ef_104fb),
param->high_thr_temp, &param->high_thr_voltage);
if (rc)
return rc;
param->high_thr_voltage *= param1.dy;
do_div(param->high_thr_voltage, param1.adc_vref);
param->high_thr_voltage += param1.adc_gnd;
return 0;
}
EXPORT_SYMBOL_GPL(qpnp_adc_tm_scale_therm_voltage_pu2);
int32_t qpnp_adc_scale_batt_id(int32_t adc_code,
const struct qpnp_adc_properties *adc_properties,
const struct qpnp_vadc_chan_properties *chan_properties,
struct qpnp_vadc_result *adc_chan_result)
{
int64_t batt_id_voltage = 0;
batt_id_voltage = qpnp_adc_scale_ratiometric_calib(adc_code,
adc_properties, chan_properties);
adc_chan_result->physical = batt_id_voltage;
adc_chan_result->physical = adc_chan_result->measurement;
return 0;
}
EXPORT_SYMBOL_GPL(qpnp_adc_scale_batt_id);
int32_t qpnp_adc_scale_default(int32_t adc_code,
const struct qpnp_adc_properties *adc_properties,
const struct qpnp_vadc_chan_properties *chan_properties,
struct qpnp_vadc_result *adc_chan_result)
{
bool negative_rawfromoffset = 0, negative_offset = 0;
int64_t scale_voltage = 0;
if (!chan_properties || !chan_properties->offset_gain_numerator ||
!chan_properties->offset_gain_denominator || !adc_properties
|| !adc_chan_result)
return -EINVAL;
scale_voltage = (adc_code -
chan_properties->adc_graph[CALIB_ABSOLUTE].adc_gnd)
* chan_properties->adc_graph[CALIB_ABSOLUTE].dx;
if (scale_voltage < 0) {
negative_offset = 1;
scale_voltage = -scale_voltage;
}
do_div(scale_voltage,
chan_properties->adc_graph[CALIB_ABSOLUTE].dy);
if (negative_offset)
scale_voltage = -scale_voltage;
scale_voltage += chan_properties->adc_graph[CALIB_ABSOLUTE].dx;
if (scale_voltage < 0) {
if (adc_properties->bipolar) {
scale_voltage = -scale_voltage;
negative_rawfromoffset = 1;
} else {
scale_voltage = 0;
}
}
adc_chan_result->measurement = scale_voltage *
chan_properties->offset_gain_denominator;
/* do_div only perform positive integer division! */
do_div(adc_chan_result->measurement,
chan_properties->offset_gain_numerator);
if (negative_rawfromoffset)
adc_chan_result->measurement = -adc_chan_result->measurement;
/*
* Note: adc_chan_result->measurement is in the unit of
* adc_properties.adc_reference. For generic channel processing,
* channel measurement is a scale/ratio relative to the adc
* reference input
*/
adc_chan_result->physical = adc_chan_result->measurement;
return 0;
}
EXPORT_SYMBOL_GPL(qpnp_adc_scale_default);
int32_t qpnp_adc_usb_scaler(struct qpnp_adc_tm_usbid_param *param,
uint32_t *low_threshold, uint32_t *high_threshold)
{
struct qpnp_vadc_linear_graph usb_param;
qpnp_get_vadc_gain_and_offset(&usb_param, CALIB_ABSOLUTE);
*low_threshold = param->low_thr * usb_param.dy;
do_div(*low_threshold, usb_param.adc_vref);
*low_threshold += usb_param.adc_gnd;
*high_threshold = param->high_thr * usb_param.dy;
do_div(*high_threshold, usb_param.adc_vref);
*high_threshold += usb_param.adc_gnd;
return 0;
}
EXPORT_SYMBOL_GPL(qpnp_adc_usb_scaler);
int32_t qpnp_adc_btm_scaler(struct qpnp_adc_tm_btm_param *param,
uint32_t *low_threshold, uint32_t *high_threshold)
{
struct qpnp_vadc_linear_graph btm_param;
int64_t *low_output = 0, *high_output = 0;
int rc = 0;
qpnp_get_vadc_gain_and_offset(&btm_param, CALIB_RATIOMETRIC);
rc = qpnp_adc_map_temp_voltage(
adcmap_btm_threshold,
ARRAY_SIZE(adcmap_btm_threshold),
(param->low_temp),
low_output);
if (rc)
return rc;
*low_output *= btm_param.dy;
do_div(*low_output, btm_param.adc_vref);
*low_output += btm_param.adc_gnd;
rc = qpnp_adc_map_temp_voltage(
adcmap_btm_threshold,
ARRAY_SIZE(adcmap_btm_threshold),
(param->high_temp),
high_output);
if (rc)
return rc;
*high_output *= btm_param.dy;
do_div(*high_output, btm_param.adc_vref);
*high_output += btm_param.adc_gnd;
low_threshold = (uint32_t *) low_output;
high_threshold = (uint32_t *) high_output;
return 0;
}
EXPORT_SYMBOL_GPL(qpnp_adc_btm_scaler);
int32_t qpnp_vadc_check_result(int32_t *data)
{
if (*data < QPNP_VADC_MIN_ADC_CODE)
*data = QPNP_VADC_MIN_ADC_CODE;
else if (*data > QPNP_VADC_MAX_ADC_CODE)
*data = QPNP_VADC_MAX_ADC_CODE;
return 0;
}
EXPORT_SYMBOL_GPL(qpnp_vadc_check_result);
int32_t qpnp_adc_get_devicetree_data(struct spmi_device *spmi,
struct qpnp_adc_drv *adc_qpnp)
{
struct device_node *node = spmi->dev.of_node;
struct resource *res;
struct device_node *child;
struct qpnp_adc_amux *adc_channel_list;
struct qpnp_adc_properties *adc_prop;
struct qpnp_adc_amux_properties *amux_prop;
int count_adc_channel_list = 0, decimation, rc = 0, i = 0;
if (!node)
return -EINVAL;
for_each_child_of_node(node, child)
count_adc_channel_list++;
if (!count_adc_channel_list) {
pr_err("No channel listing\n");
return -EINVAL;
}
adc_qpnp->spmi = spmi;
adc_prop = devm_kzalloc(&spmi->dev, sizeof(struct qpnp_adc_properties),
GFP_KERNEL);
if (!adc_prop) {
dev_err(&spmi->dev, "Unable to allocate memory\n");
return -ENOMEM;
}
adc_channel_list = devm_kzalloc(&spmi->dev,
((sizeof(struct qpnp_adc_amux)) * count_adc_channel_list),
GFP_KERNEL);
if (!adc_channel_list) {
dev_err(&spmi->dev, "Unable to allocate memory\n");
return -ENOMEM;
}
amux_prop = devm_kzalloc(&spmi->dev,
sizeof(struct qpnp_adc_amux_properties) +
sizeof(struct qpnp_vadc_chan_properties), GFP_KERNEL);
if (!amux_prop) {
dev_err(&spmi->dev, "Unable to allocate memory\n");
return -ENOMEM;
}
adc_qpnp->adc_channels = adc_channel_list;
adc_qpnp->amux_prop = amux_prop;
for_each_child_of_node(node, child) {
int channel_num, scaling, post_scaling, hw_settle_time;
int fast_avg_setup, calib_type, rc;
const char *calibration_param, *channel_name;
channel_name = of_get_property(child,
"label", NULL) ? : child->name;
if (!channel_name) {
pr_err("Invalid channel name\n");
return -EINVAL;
}
rc = of_property_read_u32(child, "reg", &channel_num);
if (rc) {
pr_err("Invalid channel num\n");
return -EINVAL;
}
rc = of_property_read_u32(child, "qcom,decimation",
&decimation);
if (rc) {
pr_err("Invalid channel decimation property\n");
return -EINVAL;
}
rc = of_property_read_u32(child,
"qcom,pre-div-channel-scaling", &scaling);
if (rc) {
pr_err("Invalid channel scaling property\n");
return -EINVAL;
}
rc = of_property_read_u32(child,
"qcom,scale-function", &post_scaling);
if (rc) {
pr_err("Invalid channel post scaling property\n");
return -EINVAL;
}
rc = of_property_read_u32(child,
"qcom,hw-settle-time", &hw_settle_time);
if (rc) {
pr_err("Invalid channel hw settle time property\n");
return -EINVAL;
}
rc = of_property_read_u32(child,
"qcom,fast-avg-setup", &fast_avg_setup);
if (rc) {
pr_err("Invalid channel fast average setup\n");
return -EINVAL;
}
calibration_param = of_get_property(child,
"qcom,calibration-type", NULL);
if (!strncmp(calibration_param, "absolute", 8))
calib_type = CALIB_ABSOLUTE;
else if (!strncmp(calibration_param, "ratiometric", 11))
calib_type = CALIB_RATIOMETRIC;
else {
pr_err("%s: Invalid calibration property\n", __func__);
return -EINVAL;
}
/* Individual channel properties */
adc_channel_list[i].name = (char *)channel_name;
adc_channel_list[i].channel_num = channel_num;
adc_channel_list[i].chan_path_prescaling = scaling;
adc_channel_list[i].adc_decimation = decimation;
adc_channel_list[i].adc_scale_fn = post_scaling;
adc_channel_list[i].hw_settle_time = hw_settle_time;
adc_channel_list[i].fast_avg_setup = fast_avg_setup;
i++;
}
/* Get the ADC VDD reference voltage and ADC bit resolution */
rc = of_property_read_u32(node, "qcom,adc-vdd-reference",
&adc_prop->adc_vdd_reference);
if (rc) {
pr_err("Invalid adc vdd reference property\n");
return -EINVAL;
}
rc = of_property_read_u32(node, "qcom,adc-bit-resolution",
&adc_prop->bitresolution);
if (rc) {
pr_err("Invalid adc bit resolution property\n");
return -EINVAL;
}
adc_qpnp->adc_prop = adc_prop;
/* Get the peripheral address */
res = spmi_get_resource(spmi, 0, IORESOURCE_MEM, 0);
if (!res) {
pr_err("No base address definition\n");
return -EINVAL;
}
adc_qpnp->slave = spmi->sid;
adc_qpnp->offset = res->start;
/* Register the ADC peripheral interrupt */
adc_qpnp->adc_irq_eoc = spmi_get_irq_byname(spmi, NULL,
"eoc-int-en-set");
if (adc_qpnp->adc_irq_eoc < 0) {
pr_err("Invalid irq\n");
return -ENXIO;
}
init_completion(&adc_qpnp->adc_rslt_completion);
mutex_init(&adc_qpnp->adc_lock);
return 0;
}
EXPORT_SYMBOL(qpnp_adc_get_devicetree_data);