Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5b27f076 authored by Siddartha Mohanadoss's avatar Siddartha Mohanadoss
Browse files

thermal: adc-tm: Add ADC_TM driver



ADC_TM driver is used by clients to set temperature thresholds
on channels supported by VADC peripheral. Clients can receive
notification once set thresholds are crossed. ADC_TM driver is
used by thermal driver and registers with of_thermal to let
thermal core set trip thresholds and read temperature for thermistors.

Change-Id: I49f57e3c6108b7976ad36a51f2c2f7a8e9eb5f2e
Signed-off-by: default avatarSiddartha Mohanadoss <smohanad@codeaurora.org>
parent 75958773
Loading
Loading
Loading
Loading
+145 −0
Original line number Diff line number Diff line
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";
                                };
                        };
                };
        };
+10 −0
Original line number Diff line number Diff line
@@ -93,3 +93,13 @@ config QTI_BCL_SOC_DRIVER
	  threshold and notify the thermal framework.

	  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.
+1 −0
Original line number Diff line number Diff line
@@ -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_QTI_BCL_PMIC5) += bcl_pmic5.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
+133 −0
Original line number Diff line number Diff line
/*
 * 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");
+334 −0
Original line number Diff line number Diff line
/*
 * 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");
Loading