Merge "ARM: dts: msm: Add ADC_TM nodes for PM855, PM855B and PM855l"

This commit is contained in:
qctecmdr Service 2018-04-23 16:02:21 -07:00 committed by Gerrit - the friendly Code Review server
commit 2e670e7b5c
13 changed files with 1787 additions and 0 deletions

View File

@ -0,0 +1,145 @@
Qualcomm Technologies, Inc. PMIC thermal monitor ADC driver (ADC_TM)
PMIC thermal monitoring (TM) provides interface to thermal clients
to set temperature thresholds and receive notification when the thresholds
are crossed. A 15 bit ADC is used for measurements. The driver is part
of the sysfs thermal framework that provides support to read the trip
points, set threshold for the trip points and enable the trip points.
ADC_TM node
- compatible:
Usage: required
Value type: <string>
Definition: Should contain "qcom,adc-tm5" for PMIC5 ADC TM driver.
- reg:
Usage: required
Value type: <prop-encoded-array>
Definition: ADC_TM base address and length in the SPMI PMIC register map.
- #address-cells:
Usage: required
Value type: <u32>
Definition: Must be one. Child node 'reg' property should define ADC
channel number.
- #size-cells:
Usage: required
Value type: <u32>
Definition: Must be zero.
- interrupts:
Usage: required
Value type: <prop-encoded-array>
Definition: End of conversion interrupt.
- interrupt-names:
Usage: required
Value type: <string>
Definition: Should contain "thr-int-en" for PMIC5 ADC TM driver.
- qcom,decimation:
Usage: optional
Value type: <u32>
Definition: This parameter is used to decrease ADC sampling rate.
Quicker measurements can be made by reducing decimation ratio.
For PMIC5 ADC, combined two step decimation values are 250, 420 and 840.
If property is not found, default value of 840 will be used.
- qcom,avg-samples:
Usage: optional
Value type: <u32>
Definition: Number of samples to be used for measurement.
Averaging provides the option to obtain a single measurement
from the ADC that is an average of multiple samples. The value
selected is 2^(value).
Valid values are: 1, 2, 4, 8, 16
If property is not found, 1 sample will be used.
- #thermal-sensor-cells:
Usage: optional
Value type: <u32>
Definition: Should be 1. See thermal.txt for a description.
- io-channels:
Usage: Required
Value type: <phandle u32>
Definition: The phandle of the iio provider.
Channel node properties:
- reg:
Usage: required
Value type: <u32>
Definition: ADC channel number.
See include/dt-bindings/iio/qcom,spmi-vadc.h
- qcom,pre-scaling:
Usage: optional
Value type: <u32 array>
Definition: Used for scaling the channel input signal before the signal is
fed to VADC. The configuration for this node is to know the
pre-determined ratio and use it for post scaling. Select one from
the following options.
<1 1>, <1 3>, <1 4>, <1 6>, <1 20>, <1 8>, <10 81>, <1 10>
If property is not found default value depending on chip will be used.
- qcom,ratiometric:
Usage: optional
Value type: <empty>
Definition: Channel calibration type. If this property is specified
VADC will use the VDD reference (1.875V) and GND for channel
calibration. If property is not found, channel will be
calibrated with 0V and 1.25V reference channels, also
known as absolute calibration.
- qcom,hw-settle-time:
Usage: optional
Value type: <u32>
Definition: Time between AMUX getting configured and the ADC starting
conversion.
For PMIC5, delay = 15us for value 0,
100us * (value) for values 0 < value < 11, and
2ms * (value - 10) otherwise.
Valid values are: 15, 100, 200, 300, 400, 500, 600, 700, 1,
2, 4, 8, 16, 32, 64, 128 ms
If property is not found, channel will use 15us.
Example:
/* ADC_TM node */
pmic_adc_tm: adc_tm@3500 {
compatible = "qcom,adc-tm5";
reg = <0x3500 0x100>;
interrupts = <0x0 0x35 0x0 IRQ_TYPE_EDGE_RISING>;
interrupt-names = "thr-int-en";
#address-cells = <1>;
#size-cells = <0>;
#thermal-sensor-cells = <1>;
io-channels = <&pmic_vadc ADC_AMUX_THM2_PU2>;
/* Channel node */
skin_msm_therm {
reg = <ADC_AMUX_THM2_PU2>;
qcom,ratiometric;
qcom,hw-settle-time = <200>;
};
};
/* Adding thermal zone to register with of_thermal */
&thermal_zones {
wp-therm {
polling-delay-passive = <0>;
polling-delay = <0>;
thermal-governor = "user_space";
thermal-sensors = <&pmic_adc_tm ADC_AMUX_THM2_PU2>;
trips {
active-config0 {
temperature = <125000>;
hysteresis = <1000>;
type = "passive";
};
};
};
};

View File

@ -134,6 +134,17 @@
qcom,pre-scaling = <1 1>; qcom,pre-scaling = <1 1>;
}; };
}; };
pm855_adc_tm: adc_tm@3500 {
compatible = "qcom,adc-tm5";
reg = <0x3500 0x100>;
interrupts = <0x0 0x35 0x0 IRQ_TYPE_EDGE_RISING>;
interrupt-names = "thr-int-en";
#address-cells = <1>;
#size-cells = <0>;
#thermal-sensor-cells = <1>;
io-channels = <&pm855_vadc ADC_XO_THERM_PU2>;
};
}; };
qcom,pm855@1 { qcom,pm855@1 {

View File

@ -142,6 +142,17 @@
}; };
}; };
pm855b_adc_tm: adc_tm@3500 {
compatible = "qcom,adc-tm5";
reg = <0x3500 0x100>;
interrupts = <0x2 0x35 0x0 IRQ_TYPE_EDGE_RISING>;
interrupt-names = "thr-int-en";
#address-cells = <1>;
#size-cells = <0>;
#thermal-sensor-cells = <1>;
io-channels = <&pm855b_vadc ADC_AMUX_THM2_PU2>;
};
pm855b_charger: qcom,qpnp-smb5 { pm855b_charger: qcom,qpnp-smb5 {
compatible = "qcom,qpnp-smb5"; compatible = "qcom,qpnp-smb5";
#address-cells = <1>; #address-cells = <1>;
@ -472,6 +483,20 @@
}; };
&thermal_zones { &thermal_zones {
pm855b-wp-therm {
polling-delay-passive = <0>;
polling-delay = <0>;
thermal-governor = "user_space";
thermal-sensors = <&pm855b_adc_tm ADC_AMUX_THM2_PU2>;
trips {
active-config0 {
temperature = <125000>;
hysteresis = <1000>;
type = "passive";
};
};
};
pm855b_temp_alarm: pm855b_tz { pm855b_temp_alarm: pm855b_tz {
polling-delay-passive = <0>; polling-delay-passive = <0>;
polling-delay = <0>; polling-delay = <0>;

View File

@ -117,6 +117,19 @@
"bcl-vbat-lvl2"; "bcl-vbat-lvl2";
#thermal-sensor-cells = <1>; #thermal-sensor-cells = <1>;
}; };
pm855l_adc_tm: adc_tm@3500 {
compatible = "qcom,adc-tm5";
reg = <0x3500 0x100>;
interrupts = <0x4 0x35 0x0 IRQ_TYPE_EDGE_RISING>;
interrupt-names = "thr-int-en";
#address-cells = <1>;
#size-cells = <0>;
#thermal-sensor-cells = <1>;
io-channels = <&pm855l_vadc ADC_AMUX_THM1_PU2>,
<&pm855l_vadc ADC_AMUX_THM2_PU2>,
<&pm855l_vadc ADC_AMUX_THM3_PU2>;
};
}; };
qcom,pm855l@5 { qcom,pm855l@5 {

View File

@ -410,3 +410,111 @@
mhi,fw-name = "debug.mbn"; mhi,fw-name = "debug.mbn";
status = "okay"; status = "okay";
}; };
&pm855b_adc_tm {
wp_therm {
reg = <ADC_AMUX_THM2_PU2>;
qcom,ratiometric;
qcom,hw-settle-time = <200>;
};
};
&pm855_adc_tm {
xo_therm {
reg = <ADC_XO_THERM_PU2>;
qcom,ratiometric;
qcom,hw-settle-time = <200>;
};
};
&pm855l_adc_tm {
camera_flash_therm {
reg = <ADC_AMUX_THM1_PU2>;
qcom,ratiometric;
qcom,hw-settle-time = <200>;
};
skin_msm_therm {
reg = <ADC_AMUX_THM2_PU2>;
qcom,ratiometric;
qcom,hw-settle-time = <200>;
};
pa_therm2 {
reg = <ADC_AMUX_THM3_PU2>;
qcom,ratiometric;
qcom,hw-settle-time = <200>;
};
};
&thermal_zones {
wp-therm {
polling-delay-passive = <0>;
polling-delay = <0>;
thermal-governor = "user_space";
thermal-sensors = <&pm855b_adc_tm ADC_AMUX_THM2_PU2>;
trips {
active-config0 {
temperature = <125000>;
hysteresis = <1000>;
type = "passive";
};
};
};
xo-therm {
polling-delay-passive = <0>;
polling-delay = <0>;
thermal-governor = "user_space";
thermal-sensors = <&pm855_adc_tm ADC_XO_THERM_PU2>;
trips {
active-config0 {
temperature = <125000>;
hysteresis = <1000>;
type = "passive";
};
};
};
camera-flash-therm {
polling-delay-passive = <0>;
polling-delay = <0>;
thermal-governor = "user_space";
thermal-sensors = <&pm855l_adc_tm ADC_AMUX_THM1_PU2>;
trips {
active-config0 {
temperature = <125000>;
hysteresis = <1000>;
type = "passive";
};
};
};
skin-msm-therm {
polling-delay-passive = <0>;
polling-delay = <0>;
thermal-governor = "user_space";
thermal-sensors = <&pm855l_adc_tm ADC_AMUX_THM2_PU2>;
trips {
active-config0 {
temperature = <125000>;
hysteresis = <1000>;
type = "passive";
};
};
};
pa-therm2 {
polling-delay-passive = <0>;
polling-delay = <0>;
thermal-governor = "user_space";
thermal-sensors = <&pm855l_adc_tm ADC_AMUX_THM3_PU2>;
trips {
active-config0 {
temperature = <125000>;
hysteresis = <1000>;
type = "passive";
};
};
};
};

View File

@ -414,3 +414,111 @@
mhi,fw-name = "debug.mbn"; mhi,fw-name = "debug.mbn";
status = "okay"; status = "okay";
}; };
&pm855b_adc_tm {
wp_therm {
reg = <ADC_AMUX_THM2_PU2>;
qcom,ratiometric;
qcom,hw-settle-time = <200>;
};
};
&pm855_adc_tm {
xo_therm {
reg = <ADC_XO_THERM_PU2>;
qcom,ratiometric;
qcom,hw-settle-time = <200>;
};
};
&pm855l_adc_tm {
camera_flash_therm {
reg = <ADC_AMUX_THM1_PU2>;
qcom,ratiometric;
qcom,hw-settle-time = <200>;
};
skin_msm_therm {
reg = <ADC_AMUX_THM2_PU2>;
qcom,ratiometric;
qcom,hw-settle-time = <200>;
};
pa_therm2 {
reg = <ADC_AMUX_THM3_PU2>;
qcom,ratiometric;
qcom,hw-settle-time = <200>;
};
};
&thermal_zones {
wp-therm {
polling-delay-passive = <0>;
polling-delay = <0>;
thermal-governor = "user_space";
thermal-sensors = <&pm855b_adc_tm ADC_AMUX_THM2_PU2>;
trips {
active-config0 {
temperature = <125000>;
hysteresis = <1000>;
type = "passive";
};
};
};
xo-therm {
polling-delay-passive = <0>;
polling-delay = <0>;
thermal-governor = "user_space";
thermal-sensors = <&pm855_adc_tm ADC_XO_THERM_PU2>;
trips {
active-config0 {
temperature = <125000>;
hysteresis = <1000>;
type = "passive";
};
};
};
camera-flash-therm {
polling-delay-passive = <0>;
polling-delay = <0>;
thermal-governor = "user_space";
thermal-sensors = <&pm855l_adc_tm ADC_AMUX_THM1_PU2>;
trips {
active-config0 {
temperature = <125000>;
hysteresis = <1000>;
type = "passive";
};
};
};
skin-msm-therm {
polling-delay-passive = <0>;
polling-delay = <0>;
thermal-governor = "user_space";
thermal-sensors = <&pm855l_adc_tm ADC_AMUX_THM2_PU2>;
trips {
active-config0 {
temperature = <125000>;
hysteresis = <1000>;
type = "passive";
};
};
};
pa-therm2 {
polling-delay-passive = <0>;
polling-delay = <0>;
thermal-governor = "user_space";
thermal-sensors = <&pm855l_adc_tm ADC_AMUX_THM3_PU2>;
trips {
active-config0 {
temperature = <125000>;
hysteresis = <1000>;
type = "passive";
};
};
};
};

View File

@ -397,3 +397,111 @@
&wil6210 { &wil6210 {
status = "ok"; status = "ok";
}; };
&pm855b_adc_tm {
wp_therm {
reg = <ADC_AMUX_THM2_PU2>;
qcom,ratiometric;
qcom,hw-settle-time = <200>;
};
};
&pm855_adc_tm {
xo_therm {
reg = <ADC_XO_THERM_PU2>;
qcom,ratiometric;
qcom,hw-settle-time = <200>;
};
};
&pm855l_adc_tm {
camera_flash_therm {
reg = <ADC_AMUX_THM1_PU2>;
qcom,ratiometric;
qcom,hw-settle-time = <200>;
};
skin_msm_therm {
reg = <ADC_AMUX_THM2_PU2>;
qcom,ratiometric;
qcom,hw-settle-time = <200>;
};
pa_therm2 {
reg = <ADC_AMUX_THM3_PU2>;
qcom,ratiometric;
qcom,hw-settle-time = <200>;
};
};
&thermal_zones {
wp-therm {
polling-delay-passive = <0>;
polling-delay = <0>;
thermal-governor = "user_space";
thermal-sensors = <&pm855b_adc_tm ADC_AMUX_THM2_PU2>;
trips {
active-config0 {
temperature = <125000>;
hysteresis = <1000>;
type = "passive";
};
};
};
xo-therm {
polling-delay-passive = <0>;
polling-delay = <0>;
thermal-governor = "user_space";
thermal-sensors = <&pm855_adc_tm ADC_XO_THERM_PU2>;
trips {
active-config0 {
temperature = <125000>;
hysteresis = <1000>;
type = "passive";
};
};
};
camera-flash-therm {
polling-delay-passive = <0>;
polling-delay = <0>;
thermal-governor = "user_space";
thermal-sensors = <&pm855l_adc_tm ADC_AMUX_THM1_PU2>;
trips {
active-config0 {
temperature = <125000>;
hysteresis = <1000>;
type = "passive";
};
};
};
skin-msm-therm {
polling-delay-passive = <0>;
polling-delay = <0>;
thermal-governor = "user_space";
thermal-sensors = <&pm855l_adc_tm ADC_AMUX_THM2_PU2>;
trips {
active-config0 {
temperature = <125000>;
hysteresis = <1000>;
type = "passive";
};
};
};
pa-therm2 {
polling-delay-passive = <0>;
polling-delay = <0>;
thermal-governor = "user_space";
thermal-sensors = <&pm855l_adc_tm ADC_AMUX_THM3_PU2>;
trips {
active-config0 {
temperature = <125000>;
hysteresis = <1000>;
type = "passive";
};
};
};
};

View File

@ -93,3 +93,13 @@ config QTI_BCL_SOC_DRIVER
threshold and notify the thermal framework. threshold and notify the thermal framework.
If you want this support, you should say Y here. If you want this support, you should say Y here.
config QTI_ADC_TM
tristate "Qualcomm Technologies Inc. Thermal Monitor ADC Driver"
depends on SPMI && THERMAL
depends on QCOM_SPMI_ADC5
help
This enables the thermal Sysfs driver for the ADC thermal monitoring
device. It shows up in Sysfs as a thermal zone with multiple trip points.
Thermal client sets threshold temperature for both warm and cool
and gets updated when a threshold is reached.

View File

@ -8,3 +8,4 @@ obj-$(CONFIG_QTI_QMI_COOLING_DEVICE) += thermal_mitigation_device_service_v01.o
obj-$(CONFIG_MSM_BCL_PERIPHERAL_CTL) += bcl_peripheral.o obj-$(CONFIG_MSM_BCL_PERIPHERAL_CTL) += bcl_peripheral.o
obj-$(CONFIG_QTI_BCL_PMIC5) += bcl_pmic5.o obj-$(CONFIG_QTI_BCL_PMIC5) += bcl_pmic5.o
obj-$(CONFIG_QTI_BCL_SOC_DRIVER) += bcl_soc.o obj-$(CONFIG_QTI_BCL_SOC_DRIVER) += bcl_soc.o
obj-$(CONFIG_QTI_ADC_TM) += adc-tm.o adc-tm-common.o adc-tm5.o

View File

@ -0,0 +1,133 @@
/*
* Copyright (c) 2018, 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.
*
*/
#include <linux/module.h>
#include "adc-tm.h"
/*
* Voltage to temperature table for NTCG104EF104 thermistor with
* 1.875V reference and 100k pull-up.
*/
static const struct adc_tm_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 void adc_tm_map_temp_voltage(const struct adc_tm_map_pt *pts,
size_t tablesize, int input, int64_t *output)
{
bool descending = true;
unsigned int i = 0;
/* Check if table is descending or ascending */
if (tablesize > 1) {
if (pts[0].y < pts[1].y)
descending = 0;
}
while (i < tablesize) {
if (descending && (pts[i].y < input)) {
/*
* Table entry is less than measured value.
* Table is descending, stop.
*/
break;
} else if (!descending && (pts[i].y > input)) {
/*
* Table entry is greater than measured value.
* Table is ascending, stop.
*/
break;
}
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);
}
}
void adc_tm_scale_therm_voltage_100k(struct adc_tm_config *param,
const struct adc_tm_data *data)
{
uint32_t adc_hc_vdd_ref_mv = 1875;
/* High temperature maps to lower threshold voltage */
adc_tm_map_temp_voltage(
adcmap_100k_104ef_104fb_1875_vref,
ARRAY_SIZE(adcmap_100k_104ef_104fb_1875_vref),
param->high_thr_temp, &param->low_thr_voltage);
param->low_thr_voltage *= data->full_scale_code_volt;
param->low_thr_voltage = div64_s64(param->low_thr_voltage,
adc_hc_vdd_ref_mv);
/* Low temperature maps to higher threshold voltage */
adc_tm_map_temp_voltage(
adcmap_100k_104ef_104fb_1875_vref,
ARRAY_SIZE(adcmap_100k_104ef_104fb_1875_vref),
param->low_thr_temp, &param->high_thr_voltage);
param->high_thr_voltage *= data->full_scale_code_volt;
param->high_thr_voltage = div64_s64(param->high_thr_voltage,
adc_hc_vdd_ref_mv);
}
EXPORT_SYMBOL(adc_tm_scale_therm_voltage_100k);
MODULE_DESCRIPTION("Qualcomm Technologies Inc. PMIC ADC_TM common driver");
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,334 @@
/*
* Copyright (c) 2018, 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.
*
*/
#include <linux/err.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/regmap.h>
#include <linux/thermal.h>
#include <linux/iio/iio.h>
#include "adc-tm.h"
LIST_HEAD(adc_tm_device_list);
static int adc_tm_get_temp(void *data, int *temp)
{
struct adc_tm_sensor *s = data;
struct adc_tm_chip *adc_tm = s->chip;
return adc_tm->ops->get_temp(s, temp);
}
static int adc_tm_set_trip_temp(void *data, int low_temp, int high_temp)
{
struct adc_tm_sensor *s = data;
struct adc_tm_chip *adc_tm = s->chip;
if (adc_tm->ops->set_trips)
return adc_tm->ops->set_trips(s, low_temp, high_temp);
return 0;
}
static int adc_tm_register_interrupts(struct adc_tm_chip *adc_tm)
{
if (adc_tm->ops->interrupts_reg)
return adc_tm->ops->interrupts_reg(adc_tm);
return 0;
}
static int adc_tm_init(struct adc_tm_chip *adc_tm, uint32_t dt_chans)
{
if (adc_tm->ops->init)
return adc_tm->ops->init(adc_tm, dt_chans);
return 0;
}
static struct thermal_zone_of_device_ops adc_tm_ops = {
.get_temp = adc_tm_get_temp,
.set_trips = adc_tm_set_trip_temp,
};
static int adc_tm_register_tzd(struct adc_tm_chip *adc_tm, int dt_chan_num)
{
unsigned int i;
struct thermal_zone_device *tzd;
for (i = 0; i < dt_chan_num; i++) {
adc_tm->sensor[i].chip = adc_tm;
tzd = devm_thermal_zone_of_sensor_register(adc_tm->dev,
adc_tm->sensor[i].adc_ch,
&adc_tm->sensor[i],
&adc_tm_ops);
if (IS_ERR(tzd)) {
pr_err("Error registering TZ zone:%d for dt_ch:%d\n",
PTR_ERR(tzd), adc_tm->sensor[i].adc_ch);
continue;
}
adc_tm->sensor[i].tzd = tzd;
}
return 0;
}
static int adc_tm_avg_samples_from_dt(u32 value)
{
if (!is_power_of_2(value) || value > ADC_TM_AVG_SAMPLES_MAX)
return -EINVAL;
return __ffs64(value);
}
static int adc_tm_hw_settle_time_from_dt(u32 value,
const unsigned int *hw_settle)
{
unsigned int i;
for (i = 0; i < ADC_TM_HW_SETTLE_SAMPLES_MAX; i++) {
if (value == hw_settle[i])
return i;
}
return -EINVAL;
}
static int adc_tm_decimation_from_dt(u32 value, const unsigned int *decimation)
{
unsigned int i;
for (i = 0; i < ADC_TM_DECIMATION_SAMPLES_MAX; i++) {
if (value == decimation[i])
return i;
}
return -EINVAL;
}
static const struct of_device_id adc_tm_match_table[] = {
{
.compatible = "qcom,adc-tm5",
.data = &data_adc_tm5,
},
{}
};
static int adc_tm_get_dt_data(struct platform_device *pdev,
struct adc_tm_chip *adc_tm,
struct iio_channel *chan,
uint32_t dt_chan_num)
{
struct device_node *child, *node = pdev->dev.of_node;
struct device *dev = &pdev->dev;
const struct of_device_id *id;
const struct adc_tm_data *data;
int ret, idx = 0;
if (!node)
return -EINVAL;
id = of_match_node(adc_tm_match_table, node);
if (id)
data = id->data;
else
data = &data_adc_tm5;
adc_tm->data = data;
adc_tm->ops = data->ops;
ret = of_property_read_u32(node, "qcom,decimation",
&adc_tm->prop.decimation);
if (!ret) {
ret = adc_tm_decimation_from_dt(adc_tm->prop.decimation,
data->decimation);
if (ret < 0) {
dev_err(dev, "Invalid decimation value\n");
return ret;
}
adc_tm->prop.decimation = ret;
} else {
adc_tm->prop.decimation = ADC_TM_DECIMATION_DEFAULT;
}
ret = of_property_read_u32(node, "qcom,avg-samples",
&adc_tm->prop.fast_avg_samples);
if (!ret) {
ret = adc_tm_avg_samples_from_dt(adc_tm->prop.fast_avg_samples);
if (ret < 0) {
dev_err(dev, "Invalid fast average with%d\n", ret);
return -EINVAL;
}
} else {
adc_tm->prop.fast_avg_samples = ADC_TM_DEF_AVG_SAMPLES;
}
adc_tm->prop.timer1 = ADC_TM_TIMER1;
adc_tm->prop.timer2 = ADC_TM_TIMER2;
adc_tm->prop.timer3 = ADC_TM_TIMER3;
for_each_child_of_node(node, child) {
int channel_num, i = 0;
int calib_type = 0, ret, hw_settle_time = 0;
struct iio_channel *chan_adc;
ret = of_property_read_u32(child, "reg", &channel_num);
if (ret) {
dev_err(dev, "Invalid channel num\n");
return -EINVAL;
}
ret = of_property_read_u32(child, "qcom,hw-settle-time",
&hw_settle_time);
if (!ret) {
ret = adc_tm_hw_settle_time_from_dt(hw_settle_time,
data->hw_settle);
if (ret < 0) {
pr_err("Invalid channel hw settle time property\n");
return ret;
}
hw_settle_time = ret;
} else {
hw_settle_time = ADC_TM_DEF_HW_SETTLE_TIME;
}
if (of_property_read_bool(child, "qcom,ratiometric"))
calib_type = ADC_RATIO_CAL;
else
calib_type = ADC_ABS_CAL;
/* Individual channel properties */
adc_tm->sensor[idx].adc_ch = channel_num;
adc_tm->sensor[idx].cal_sel = calib_type;
/* Default to 1 second timer select */
adc_tm->sensor[idx].timer_select = ADC_TIMER_SEL_2;
adc_tm->sensor[idx].hw_settle_time = hw_settle_time;
while (i < dt_chan_num) {
chan_adc = &chan[i];
if (chan_adc->channel->channel == channel_num)
adc_tm->sensor[idx].adc = chan_adc;
i++;
}
idx++;
}
return 0;
}
static int adc_tm_probe(struct platform_device *pdev)
{
struct device_node *child, *node = pdev->dev.of_node;
struct device *dev = &pdev->dev;
struct adc_tm_chip *adc_tm;
struct regmap *regmap;
struct iio_channel *channels;
int ret, dt_chan_num = 0, indio_chan_count = 0;
u32 reg;
if (!node)
return -EINVAL;
for_each_child_of_node(node, child)
dt_chan_num++;
if (!dt_chan_num) {
dev_err(dev, "No channel listing\n");
return -EINVAL;
}
channels = iio_channel_get_all(dev);
if (IS_ERR(channels))
return PTR_ERR(channels);
while (channels[indio_chan_count].indio_dev)
indio_chan_count++;
if (indio_chan_count != dt_chan_num) {
dev_err(dev, "VADC IIO channel missing in main node\n");
return -EINVAL;
}
regmap = dev_get_regmap(dev->parent, NULL);
if (!regmap)
return -ENODEV;
ret = of_property_read_u32(node, "reg", &reg);
if (ret < 0)
return ret;
adc_tm = devm_kzalloc(&pdev->dev,
sizeof(struct adc_tm_chip) + (dt_chan_num *
(sizeof(struct adc_tm_sensor))), GFP_KERNEL);
if (!adc_tm)
return -ENOMEM;
adc_tm->regmap = regmap;
adc_tm->dev = dev;
adc_tm->base = reg;
adc_tm->dt_channels = dt_chan_num;
ret = adc_tm_get_dt_data(pdev, adc_tm, channels, dt_chan_num);
if (ret) {
dev_err(dev, "adc-tm get dt data failed\n");
return ret;
}
ret = adc_tm_init(adc_tm, dt_chan_num);
if (ret) {
dev_err(dev, "adc-tm init failed\n");
return ret;
}
ret = adc_tm_register_tzd(adc_tm, dt_chan_num);
if (ret) {
dev_err(dev, "adc-tm failed to register with of thermal\n");
return ret;
}
ret = adc_tm_register_interrupts(adc_tm);
if (ret) {
pr_err("adc-tm register interrupts failed:%d\n", ret);
return ret;
}
list_add_tail(&adc_tm->list, &adc_tm_device_list);
platform_set_drvdata(pdev, adc_tm);
return 0;
}
static int adc_tm_remove(struct platform_device *pdev)
{
struct adc_tm_chip *adc_tm = platform_get_drvdata(pdev);
if (adc_tm->ops->shutdown)
adc_tm->ops->shutdown(adc_tm);
return 0;
}
static struct platform_driver adc_tm_driver = {
.driver = {
.name = "qcom,adc-tm",
.of_match_table = adc_tm_match_table,
},
.probe = adc_tm_probe,
.remove = adc_tm_remove,
};
module_platform_driver(adc_tm_driver);
MODULE_DESCRIPTION("Qualcomm Technologies Inc. PMIC ADC_TM driver");
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,218 @@
/*
* Copyright (c) 2018, The Linux Foundation. All rights reserved.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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.
*/
#ifndef __QCOM_ADC_TM_H__
#define __QCOM_ADC_TM_H__
#include <linux/kernel.h>
#include <linux/thermal.h>
#include <linux/interrupt.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/iio/consumer.h>
struct adc_tm_chip;
#define ADC_TM_DECIMATION_DEFAULT 840
#define ADC_TM_DECIMATION_SAMPLES_MAX 3
#define ADC_TM_DEF_AVG_SAMPLES 0 /* 1 sample */
#define ADC_TM_DEF_HW_SETTLE_TIME 0 /* 15 us */
#define ADC_TM_HW_SETTLE_SAMPLES_MAX 16
#define ADC_TM_AVG_SAMPLES_MAX 16
#define ADC_TM_TIMER1 3 /* 3.9ms */
#define ADC_TM_TIMER2 10 /* 1 second */
#define ADC_TM_TIMER3 4 /* 4 second */
enum adc_cal_method {
ADC_NO_CAL = 0,
ADC_RATIO_CAL = 1,
ADC_ABS_CAL = 2,
ADC_CAL_SEL_NONE,
};
enum adc_cal_val {
ADC_TIMER_CAL = 0,
ADC_NEW_CAL,
ADC_CAL_VAL_NONE,
};
enum adc_timer_select {
ADC_TIMER_SEL_1 = 0,
ADC_TIMER_SEL_2,
ADC_TIMER_SEL_3,
ADC_TIMER_SEL_NONE,
};
struct adc_tm_sensor {
struct adc_tm_chip *chip;
struct thermal_zone_device *tzd;
enum adc_cal_val cal_val;
enum adc_cal_method cal_sel;
unsigned int hw_settle_time;
unsigned int adc_ch;
unsigned int btm_ch;
unsigned int prescaling;
unsigned int timer_select;
struct iio_channel *adc;
};
struct adc_tm_cmn_prop {
unsigned int decimation;
unsigned int fast_avg_samples;
unsigned int timer1;
unsigned int timer2;
unsigned int timer3;
};
struct adc_tm_ops {
int (*get_temp)(struct adc_tm_sensor *, int *);
int (*init)(struct adc_tm_chip *, uint32_t);
int (*set_trips)(struct adc_tm_sensor *, int, int);
int (*interrupts_reg)(struct adc_tm_chip *);
int (*shutdown)(struct adc_tm_chip *);
};
struct adc_tm_chip {
struct device *dev;
struct list_head list;
struct regmap *regmap;
u16 base;
struct adc_tm_cmn_prop prop;
spinlock_t adc_tm_lock;
const struct adc_tm_ops *ops;
const struct adc_tm_data *data;
unsigned int dt_channels;
struct adc_tm_sensor sensor[0];
};
struct adc_tm_data {
const struct adc_tm_ops *ops;
const u32 full_scale_code_volt;
unsigned int *decimation;
unsigned int *hw_settle;
};
extern const struct adc_tm_data data_adc_tm5;
/**
* Channel index for the corresponding index to adc_tm_channel_select
*/
enum adc_tm_channel_num {
ADC_TM_CHAN0 = 0,
ADC_TM_CHAN1,
ADC_TM_CHAN2,
ADC_TM_CHAN3,
ADC_TM_CHAN4,
ADC_TM_CHAN5,
ADC_TM_CHAN6,
ADC_TM_CHAN7,
ADC_TM_CHAN_NONE
};
/**
* Channel selection registers for each of the configurable measurements
* Channels allotment is set at device config for a channel.
*/
enum adc_tm_channel_sel {
ADC_TM_M0_ADC_CH_SEL_CTL = 0x60,
ADC_TM_M1_ADC_CH_SEL_CTL = 0x68,
ADC_TM_M2_ADC_CH_SEL_CTL = 0x70,
ADC_TM_M3_ADC_CH_SEL_CTL = 0x78,
ADC_TM_M4_ADC_CH_SEL_CTL = 0x80,
ADC_TM_M5_ADC_CH_SEL_CTL = 0x88,
ADC_TM_M6_ADC_CH_SEL_CTL = 0x90,
ADC_TM_M7_ADC_CH_SEL_CTL = 0x98,
ADC_TM_CH_SELECT_NONE
};
/**
* enum adc_tm_fast_avg_ctl - Provides ability to obtain single result
* from the ADC that is an average of multiple measurement
* samples. Select number of samples for use in fast
* average mode (i.e. 2 ^ value).
* %ADC_FAST_AVG_SAMPLE_1: 0x0 = 1
* %ADC_FAST_AVG_SAMPLE_2: 0x1 = 2
* %ADC_FAST_AVG_SAMPLE_4: 0x2 = 4
* %ADC_FAST_AVG_SAMPLE_8: 0x3 = 8
* %ADC_FAST_AVG_SAMPLE_16: 0x4 = 16
*/
enum qpnp_adc_fast_avg_ctl {
ADC_FAST_AVG_SAMPLE_1 = 0,
ADC_FAST_AVG_SAMPLE_2,
ADC_FAST_AVG_SAMPLE_4,
ADC_FAST_AVG_SAMPLE_8,
ADC_FAST_AVG_SAMPLE_16,
ADC_FAST_AVG_SAMPLE_NONE,
};
struct adc_tm_trip_reg_type {
enum adc_tm_channel_sel btm_amux_ch;
uint16_t low_thr_lsb_addr;
uint16_t low_thr_msb_addr;
uint16_t high_thr_lsb_addr;
uint16_t high_thr_msb_addr;
u8 multi_meas_en;
u8 low_thr_int_chan_en;
u8 high_thr_int_chan_en;
u8 meas_interval_ctl;
};
/**
* struct adc_tm_config - Represent ADC Thermal Monitor configuration.
* @channel: ADC channel for which thermal monitoring is requested.
* @adc_code: The pre-calibrated digital output of a given ADC releative to the
* ADC reference.
* @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 channel;
int adc_code;
int high_thr_temp;
int low_thr_temp;
int64_t high_thr_voltage;
int64_t low_thr_voltage;
};
/**
* struct adc_map_pt - Map the graph representation for ADC channel
* @x: Represent the ADC digitized code.
* @y: Represent the physical data which can be temperature, voltage,
* resistance.
*/
struct adc_tm_map_pt {
int32_t x;
int32_t y;
};
/**
* struct adc_linear_graph - Represent ADC characteristics.
* @dy: numerator slope to calculate the gain.
* @dx: denominator slope to calculate the gain.
* @gnd: A/D word of the ground reference used for the channel.
*
* Each ADC device has different offset and gain parameters which are
* computed to calibrate the device.
*/
struct adc_tm_linear_graph {
s32 dy;
s32 dx;
s32 gnd;
};
void adc_tm_scale_therm_voltage_100k(struct adc_tm_config *param,
const struct adc_tm_data *data);
#endif /* __QCOM_ADC_TM_H__ */

View File

@ -0,0 +1,573 @@
/*
* Copyright (c) 2018, 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.
*
*/
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/of.h>
#include <linux/err.h>
#include <linux/spmi.h>
#include <linux/platform_device.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <linux/iio/consumer.h>
#include "adc-tm.h"
#include "../thermal_core.h"
#define ADC_TM_STATUS2 0x09
#define ADC_TM_STATUS_LOW 0x0a
#define ADC_TM_STATUS_HIGH 0x0b
#define ADC_TM_NUM_BTM 0x0f
#define ADC_TM_ADC_DIG_PARAM 0x42
#define ADC_TM_FAST_AVG_CTL 0x43
#define ADC_TM_FAST_AVG_EN BIT(7)
#define ADC_TM_MEAS_INTERVAL_CTL 0x44
#define ADC_TM_MEAS_INTERVAL_CTL2 0x45
#define ADC_TM_MEAS_INTERVAL_CTL2_SHIFT 0x4
#define ADC_TM_MEAS_INTERVAL_CTL2_MASK 0xf0
#define ADC_TM_MEAS_INTERVAL_CTL3_MASK 0xf
#define ADC_TM_EN_CTL1 0x46
#define ADC_TM_EN BIT(7)
#define ADC_TM_CONV_REQ 0x47
#define ADC_TM_CONV_REQ_EN BIT(7)
#define ADC_TM_Mn_ADC_CH_SEL_CTL(n) ((n * 8) + 0x60)
#define ADC_TM_Mn_LOW_THR0(n) ((n * 8) + 0x61)
#define ADC_TM_Mn_LOW_THR1(n) ((n * 8) + 0x62)
#define ADC_TM_Mn_HIGH_THR0(n) ((n * 8) + 0x63)
#define ADC_TM_Mn_HIGH_THR1(n) ((n * 8) + 0x64)
#define ADC_TM_Mn_MEAS_INTERVAL_CTL(n) ((n * 8) + 0x65)
#define ADC_TM_Mn_CTL(n) ((n * 8) + 0x66)
#define ADC_TM_CTL_HW_SETTLE_DELAY_MASK 0xf
#define ADC_TM_CTL_CAL_SEL 0x30
#define ADC_TM_CTL_CAL_SEL_MASK_SHIFT 4
#define ADC_TM_CTL_CAL_VAL 0x40
#define ADC_TM_Mn_EN(n) ((n * 8) + 0x67)
#define ADC_TM_Mn_MEAS_EN BIT(7)
#define ADC_TM_Mn_HIGH_THR_INT_EN BIT(1)
#define ADC_TM_Mn_LOW_THR_INT_EN BIT(0)
#define ADC_TM_LOWER_MASK(n) ((n) & 0x000000ff)
#define ADC_TM_UPPER_MASK(n) (((n) & 0xffffff00) >> 8)
static struct adc_tm_trip_reg_type adc_tm_ch_data[] = {
[ADC_TM_CHAN0] = {ADC_TM_M0_ADC_CH_SEL_CTL},
[ADC_TM_CHAN1] = {ADC_TM_M1_ADC_CH_SEL_CTL},
[ADC_TM_CHAN2] = {ADC_TM_M2_ADC_CH_SEL_CTL},
[ADC_TM_CHAN3] = {ADC_TM_M3_ADC_CH_SEL_CTL},
[ADC_TM_CHAN4] = {ADC_TM_M4_ADC_CH_SEL_CTL},
[ADC_TM_CHAN5] = {ADC_TM_M5_ADC_CH_SEL_CTL},
[ADC_TM_CHAN6] = {ADC_TM_M6_ADC_CH_SEL_CTL},
[ADC_TM_CHAN7] = {ADC_TM_M7_ADC_CH_SEL_CTL},
};
static int adc_tm5_get_temp(struct adc_tm_sensor *sensor, int *temp)
{
int ret, milli_celsius;
if (!sensor || !sensor->adc)
return -EINVAL;
ret = iio_read_channel_processed(sensor->adc, &milli_celsius);
if (ret < 0)
return ret;
*temp = milli_celsius;
return 0;
}
static int32_t adc_tm5_read_reg(struct adc_tm_chip *chip,
int16_t reg, u8 *data, int len)
{
int ret;
ret = regmap_bulk_read(chip->regmap, (chip->base + reg), data, len);
if (ret < 0)
pr_err("adc-tm read reg %d failed with %d\n", reg, ret);
return ret;
}
static int32_t adc_tm5_write_reg(struct adc_tm_chip *chip,
int16_t reg, u8 *data, int len)
{
int ret;
ret = regmap_bulk_write(chip->regmap, (chip->base + reg), data, len);
if (ret < 0)
pr_err("adc-tm write reg %d failed with %d\n", reg, ret);
return ret;
}
static int32_t adc_tm5_reg_update(struct adc_tm_chip *chip,
uint16_t addr, u8 mask, bool state)
{
u8 reg_value = 0;
int ret;
ret = adc_tm5_read_reg(chip, addr, &reg_value, 1);
if (ret < 0) {
pr_err("read failed for addr:0x%x\n", addr);
return ret;
}
reg_value = reg_value & ~mask;
if (state)
reg_value |= mask;
pr_debug("state:%d, reg:0x%x with bits:0x%x and mask:0x%x\n",
state, addr, reg_value, ~mask);
ret = adc_tm5_write_reg(chip, addr, &reg_value, 1);
if (ret < 0) {
pr_err("write failed for addr:%x\n", addr);
return ret;
}
return ret;
}
static int32_t adc_tm5_get_btm_idx(struct adc_tm_chip *chip,
uint32_t btm_chan, uint32_t *btm_chan_idx)
{
int i;
for (i = 0; i < ADC_TM_CHAN_NONE; i++) {
if (adc_tm_ch_data[i].btm_amux_ch == btm_chan) {
*btm_chan_idx = i;
return 0;
}
}
return -EINVAL;
}
static int32_t adc_tm5_enable(struct adc_tm_chip *chip)
{
int rc = 0;
u8 data = 0;
data = ADC_TM_EN;
rc = adc_tm5_write_reg(chip, ADC_TM_EN_CTL1, &data, 1);
if (rc < 0) {
pr_err("adc-tm enable failed\n");
return rc;
}
data = ADC_TM_CONV_REQ_EN;
rc = adc_tm5_write_reg(chip, ADC_TM_CONV_REQ, &data, 1);
if (rc < 0) {
pr_err("adc-tm request conversion failed\n");
return rc;
}
return rc;
}
static int adc_tm5_configure(struct adc_tm_sensor *sensor,
uint32_t btm_chan_idx)
{
struct adc_tm_chip *chip = sensor->chip;
u8 buf[8], cal_sel;
int ret = 0;
ret = adc_tm5_read_reg(chip,
ADC_TM_Mn_ADC_CH_SEL_CTL(btm_chan_idx), buf, 8);
if (ret < 0) {
pr_err("adc-tm block read failed with %d\n", ret);
return ret;
}
/* Update ADC channel select */
buf[0] = sensor->adc_ch;
/* Update timer select */
buf[5] = sensor->timer_select;
/* Set calibration select, hw_settle delay */
cal_sel |= (u8) (sensor->cal_sel << ADC_TM_CTL_CAL_SEL_MASK_SHIFT);
buf[6] &= (u8) ~ADC_TM_CTL_HW_SETTLE_DELAY_MASK;
buf[6] |= (u8) sensor->hw_settle_time;
buf[6] &= (u8) ~ADC_TM_CTL_CAL_SEL;
buf[6] |= (u8) cal_sel;
buf[7] |= ADC_TM_Mn_MEAS_EN;
ret = adc_tm5_write_reg(chip,
ADC_TM_Mn_ADC_CH_SEL_CTL(btm_chan_idx), buf, 8);
if (ret < 0) {
pr_err("adc-tm block write failed with %d\n", ret);
return ret;
}
return 0;
}
static int adc_tm5_set_mode(struct adc_tm_sensor *sensor,
enum thermal_device_mode mode)
{
struct adc_tm_chip *chip = sensor->chip;
int ret = 0;
uint32_t btm_chan_idx = 0;
ret = adc_tm5_get_btm_idx(chip, sensor->btm_ch, &btm_chan_idx);
if (ret < 0) {
pr_err("Invalid btm channel idx with %d\n", ret);
return ret;
}
if (mode == THERMAL_DEVICE_ENABLED) {
ret = adc_tm5_configure(sensor, btm_chan_idx);
if (ret < 0) {
pr_err("Error during adc-tm configure:%d\n", ret);
return ret;
}
ret = adc_tm5_enable(chip);
if (ret < 0)
pr_err("Error enabling adc-tm with %d\n", ret);
} else if (mode == THERMAL_DEVICE_DISABLED) {
ret = adc_tm5_reg_update(chip,
ADC_TM_Mn_EN(btm_chan_idx),
ADC_TM_Mn_MEAS_EN, false);
if (ret < 0)
pr_err("Disable failed for ch:%d\n", btm_chan_idx);
}
return ret;
}
static int adc_tm5_activate_trip_type(struct adc_tm_sensor *adc_tm,
int trip, enum thermal_device_mode mode)
{
struct adc_tm_chip *chip = adc_tm->chip;
int ret = 0;
bool state = false;
uint32_t btm_chan_idx = 0, btm_chan = 0;
if (mode == THERMAL_DEVICE_ENABLED)
state = true;
btm_chan = adc_tm->btm_ch;
ret = adc_tm5_get_btm_idx(chip, btm_chan, &btm_chan_idx);
if (ret < 0) {
pr_err("Invalid btm channel idx\n");
return ret;
}
switch (trip) {
case THERMAL_TRIP_CONFIGURABLE_HI:
/* low_thr (lower voltage) for higher temp */
ret = adc_tm5_reg_update(chip,
ADC_TM_Mn_EN(btm_chan_idx),
ADC_TM_Mn_LOW_THR_INT_EN, state);
if (ret)
pr_err("channel:%x failed\n", btm_chan);
break;
case THERMAL_TRIP_CONFIGURABLE_LOW:
/* high_thr (higher voltage) for cooler temp */
ret = adc_tm5_reg_update(chip,
ADC_TM_Mn_EN(btm_chan_idx),
ADC_TM_Mn_HIGH_THR_INT_EN, state);
if (ret)
pr_err("channel:%x failed\n", btm_chan);
break;
default:
return -EINVAL;
}
return ret;
}
static int adc_tm5_set_trip_temp(struct adc_tm_sensor *sensor,
int low_temp, int high_temp)
{
struct adc_tm_chip *chip = sensor->chip;
struct adc_tm_config tm_config;
u8 trip_low_thr[2], trip_high_thr[2];
uint16_t reg_low_thr_lsb, reg_high_thr_lsb;
int ret;
uint32_t btm_chan = 0, btm_chan_idx = 0, mask = 0;
unsigned long flags;
if (!sensor)
return -EINVAL;
pr_debug("%s:low_temp(mdegC):%d, high_temp(mdegC):%d\n", __func__,
low_temp, high_temp);
tm_config.channel = sensor->adc_ch;
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(&tm_config, chip->data);
/* Cool temperature corresponds to high voltage threshold */
mask = lower_32_bits(tm_config.high_thr_voltage);
trip_high_thr[0] = ADC_TM_LOWER_MASK(mask);
trip_high_thr[1] = ADC_TM_UPPER_MASK(mask);
/* Warm temperature corresponds to low voltage threshold */
mask = lower_32_bits(tm_config.low_thr_voltage);
trip_low_thr[0] = ADC_TM_LOWER_MASK(mask);
trip_low_thr[1] = ADC_TM_UPPER_MASK(mask);
pr_debug("high_thr:0x%llx, low_thr:0x%llx\n",
tm_config.high_thr_voltage, tm_config.low_thr_voltage);
btm_chan = sensor->btm_ch;
ret = adc_tm5_get_btm_idx(chip, btm_chan, &btm_chan_idx);
if (ret < 0) {
pr_err("Invalid btm channel idx\n");
return ret;
}
spin_lock_irqsave(&chip->adc_tm_lock, flags);
reg_low_thr_lsb = ADC_TM_Mn_LOW_THR0(btm_chan_idx);
reg_high_thr_lsb = ADC_TM_Mn_HIGH_THR0(btm_chan_idx);
if (high_temp != INT_MAX) {
ret = adc_tm5_write_reg(chip, reg_low_thr_lsb,
trip_low_thr, 2);
if (ret) {
pr_err("Warm set threshold err\n");
goto fail;
}
ret = adc_tm5_activate_trip_type(sensor,
THERMAL_TRIP_CONFIGURABLE_HI,
THERMAL_DEVICE_ENABLED);
if (ret) {
pr_err("adc-tm warm activation failed\n");
goto fail;
}
} else {
ret = adc_tm5_activate_trip_type(sensor,
THERMAL_TRIP_CONFIGURABLE_HI,
THERMAL_DEVICE_DISABLED);
if (ret) {
pr_err("adc-tm warm deactivation failed\n");
goto fail;
}
}
if (low_temp != INT_MIN) {
ret = adc_tm5_write_reg(chip, reg_high_thr_lsb,
trip_high_thr, 2);
if (ret) {
pr_err("adc-tm cool temp set threshold err\n");
goto fail;
}
ret = adc_tm5_activate_trip_type(sensor,
THERMAL_TRIP_CONFIGURABLE_LOW,
THERMAL_DEVICE_ENABLED);
if (ret) {
pr_err("adc-tm cool activation failed\n");
goto fail;
}
} else {
ret = adc_tm5_activate_trip_type(sensor,
THERMAL_TRIP_CONFIGURABLE_LOW,
THERMAL_DEVICE_DISABLED);
if (ret) {
pr_err("adc-tm cool deactivation failed\n");
goto fail;
}
}
if ((high_temp != INT_MAX) || (low_temp != INT_MIN)) {
ret = adc_tm5_set_mode(sensor, THERMAL_DEVICE_ENABLED);
if (ret)
pr_err("sensor enabled failed\n");
} else {
ret = adc_tm5_set_mode(sensor, THERMAL_DEVICE_DISABLED);
if (ret)
pr_err("sensor disable failed\n");
}
fail:
spin_unlock_irqrestore(&chip->adc_tm_lock, flags);
return ret;
}
static irqreturn_t adc_tm5_handler(int irq, void *data)
{
struct adc_tm_chip *chip = data;
u8 status_low, status_high, ctl;
int ret, i = 0;
unsigned long flags;
ret = adc_tm5_read_reg(chip, ADC_TM_STATUS_LOW, &status_low, 1);
if (ret < 0) {
pr_err("adc-tm-tm read status low failed with %d\n", ret);
return IRQ_HANDLED;
}
ret = adc_tm5_read_reg(chip, ADC_TM_STATUS_HIGH, &status_high, 1);
if (ret < 0) {
pr_err("adc-tm-tm read status high failed with %d\n", ret);
return IRQ_HANDLED;
}
while (i < chip->dt_channels) {
bool upper_set = false, lower_set = false;
int temp;
if (IS_ERR(chip->sensor[i].tzd))
continue;
ret = adc_tm5_get_temp(&chip->sensor[i], &temp);
if (ret < 0)
continue;
spin_lock_irqsave(&chip->adc_tm_lock, flags);
ret = adc_tm5_read_reg(chip, ADC_TM_Mn_EN(i), &ctl, 1);
if (ret) {
pr_err("ctl read failed with %d\n", ret);
goto fail;
}
if ((status_low & 0x1) && (ctl & ADC_TM_Mn_MEAS_EN)
&& (ctl & ADC_TM_Mn_LOW_THR_INT_EN))
lower_set = true;
if ((status_high & 0x1) && (ctl & ADC_TM_Mn_MEAS_EN) &&
(ctl & ADC_TM_Mn_HIGH_THR_INT_EN))
upper_set = true;
fail:
status_low >>= 1;
status_high >>= 1;
spin_unlock_irqrestore(&chip->adc_tm_lock, flags);
if (upper_set || lower_set) {
/*
* Expected behavior is while notifying of_thermal,
* thermal core will call set_trips with new thresholds
* and activate/disable the appropriate trips.
*/
pr_debug("notifying of_thermal\n");
of_thermal_handle_trip(chip->sensor[i].tzd);
}
i++;
}
return IRQ_HANDLED;
}
static int adc_tm5_register_interrupts(struct adc_tm_chip *chip)
{
struct platform_device *pdev;
int ret, irq;
if (!chip)
return -EINVAL;
pdev = to_platform_device(chip->dev);
irq = platform_get_irq_byname(pdev, "thr-int-en");
if (irq < 0) {
dev_err(&pdev->dev, "failed to get irq %s\n",
"thr-int-en");
return irq;
}
ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
adc_tm5_handler,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"thr-int-en", chip);
if (ret) {
dev_err(&pdev->dev, "failed to get irq %s\n",
"thr-int-en");
return ret;
}
enable_irq_wake(irq);
return ret;
}
static int adc_tm5_init(struct adc_tm_chip *chip, uint32_t dt_chans)
{
u8 buf[4], channels_available;
int ret, i;
ret = adc_tm5_read_reg(chip, ADC_TM_NUM_BTM, &channels_available, 1);
if (ret < 0) {
pr_err("read failed for BTM channels\n");
return ret;
}
if (dt_chans > channels_available) {
pr_err("Number of nodes greater than channels supported:%d\n",
channels_available);
return -EINVAL;
}
/* Select decimation */
buf[0] = chip->prop.decimation;
/* Select number of samples in fast average mode */
buf[1] |= chip->prop.fast_avg_samples | ADC_TM_FAST_AVG_EN;
/* Select timer1 */
buf[2] = chip->prop.timer1;
/* Select timer2 and timer2 */
buf[3] |= chip->prop.timer2 << ADC_TM_MEAS_INTERVAL_CTL2_SHIFT;
buf[3] |= chip->prop.timer3;
ret = adc_tm5_write_reg(chip,
ADC_TM_ADC_DIG_PARAM, buf, 4);
if (ret < 0)
pr_err("adc-tm block write failed with %d\n", ret);
spin_lock_init(&chip->adc_tm_lock);
for (i = 0; i < dt_chans; i++)
chip->sensor[i].btm_ch = adc_tm_ch_data[i].btm_amux_ch;
return ret;
}
static const struct adc_tm_ops ops_adc_tm5 = {
.init = adc_tm5_init,
.set_trips = adc_tm5_set_trip_temp,
.interrupts_reg = adc_tm5_register_interrupts,
.get_temp = adc_tm5_get_temp,
};
const struct adc_tm_data data_adc_tm5 = {
.ops = &ops_adc_tm5,
.full_scale_code_volt = 0x70e4,
.decimation = (unsigned int []) {250, 420, 840},
.hw_settle = (unsigned int []) {15, 100, 200, 300, 400, 500, 600, 700,
1, 2, 4, 8, 16, 32, 64, 128},
};