linux/drivers/thermal/qcom/tsens.c
Krzysztof Kozlowski bc55630c65 thermal/drivers/qcom-tsens: Simplify with dev_err_probe()
Error handling in probe() can be a bit simpler with dev_err_probe().

Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Reviewed-by: Bjorn Andersson <andersson@kernel.org>
Link: https://lore.kernel.org/r/20240709-thermal-probe-v1-10-241644e2b6e0@linaro.org
Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
2024-07-15 13:31:41 +02:00

1375 lines
34 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2015, The Linux Foundation. All rights reserved.
* Copyright (c) 2019, 2020, Linaro Ltd.
*/
#include <linux/debugfs.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/nvmem-consumer.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/mfd/syscon.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/suspend.h>
#include <linux/thermal.h>
#include "../thermal_hwmon.h"
#include "tsens.h"
/**
* struct tsens_irq_data - IRQ status and temperature violations
* @up_viol: upper threshold violated
* @up_thresh: upper threshold temperature value
* @up_irq_mask: mask register for upper threshold irqs
* @up_irq_clear: clear register for uppper threshold irqs
* @low_viol: lower threshold violated
* @low_thresh: lower threshold temperature value
* @low_irq_mask: mask register for lower threshold irqs
* @low_irq_clear: clear register for lower threshold irqs
* @crit_viol: critical threshold violated
* @crit_thresh: critical threshold temperature value
* @crit_irq_mask: mask register for critical threshold irqs
* @crit_irq_clear: clear register for critical threshold irqs
*
* Structure containing data about temperature threshold settings and
* irq status if they were violated.
*/
struct tsens_irq_data {
u32 up_viol;
int up_thresh;
u32 up_irq_mask;
u32 up_irq_clear;
u32 low_viol;
int low_thresh;
u32 low_irq_mask;
u32 low_irq_clear;
u32 crit_viol;
u32 crit_thresh;
u32 crit_irq_mask;
u32 crit_irq_clear;
};
char *qfprom_read(struct device *dev, const char *cname)
{
struct nvmem_cell *cell;
ssize_t data;
char *ret;
cell = nvmem_cell_get(dev, cname);
if (IS_ERR(cell))
return ERR_CAST(cell);
ret = nvmem_cell_read(cell, &data);
nvmem_cell_put(cell);
return ret;
}
int tsens_read_calibration(struct tsens_priv *priv, int shift, u32 *p1, u32 *p2, bool backup)
{
u32 mode;
u32 base1, base2;
char name[] = "sXX_pY_backup"; /* s10_p1_backup */
int i, ret;
if (priv->num_sensors > MAX_SENSORS)
return -EINVAL;
ret = snprintf(name, sizeof(name), "mode%s", backup ? "_backup" : "");
if (ret < 0)
return ret;
ret = nvmem_cell_read_variable_le_u32(priv->dev, name, &mode);
if (ret == -ENOENT)
dev_warn(priv->dev, "Please migrate to separate nvmem cells for calibration data\n");
if (ret < 0)
return ret;
dev_dbg(priv->dev, "calibration mode is %d\n", mode);
ret = snprintf(name, sizeof(name), "base1%s", backup ? "_backup" : "");
if (ret < 0)
return ret;
ret = nvmem_cell_read_variable_le_u32(priv->dev, name, &base1);
if (ret < 0)
return ret;
ret = snprintf(name, sizeof(name), "base2%s", backup ? "_backup" : "");
if (ret < 0)
return ret;
ret = nvmem_cell_read_variable_le_u32(priv->dev, name, &base2);
if (ret < 0)
return ret;
for (i = 0; i < priv->num_sensors; i++) {
ret = snprintf(name, sizeof(name), "s%d_p1%s", priv->sensor[i].hw_id,
backup ? "_backup" : "");
if (ret < 0)
return ret;
ret = nvmem_cell_read_variable_le_u32(priv->dev, name, &p1[i]);
if (ret)
return ret;
ret = snprintf(name, sizeof(name), "s%d_p2%s", priv->sensor[i].hw_id,
backup ? "_backup" : "");
if (ret < 0)
return ret;
ret = nvmem_cell_read_variable_le_u32(priv->dev, name, &p2[i]);
if (ret)
return ret;
}
switch (mode) {
case ONE_PT_CALIB:
for (i = 0; i < priv->num_sensors; i++)
p1[i] = p1[i] + (base1 << shift);
break;
case TWO_PT_CALIB:
case TWO_PT_CALIB_NO_OFFSET:
for (i = 0; i < priv->num_sensors; i++)
p2[i] = (p2[i] + base2) << shift;
fallthrough;
case ONE_PT_CALIB2:
case ONE_PT_CALIB2_NO_OFFSET:
for (i = 0; i < priv->num_sensors; i++)
p1[i] = (p1[i] + base1) << shift;
break;
default:
dev_dbg(priv->dev, "calibrationless mode\n");
for (i = 0; i < priv->num_sensors; i++) {
p1[i] = 500;
p2[i] = 780;
}
}
/* Apply calibration offset workaround except for _NO_OFFSET modes */
switch (mode) {
case TWO_PT_CALIB:
for (i = 0; i < priv->num_sensors; i++)
p2[i] += priv->sensor[i].p2_calib_offset;
fallthrough;
case ONE_PT_CALIB2:
for (i = 0; i < priv->num_sensors; i++)
p1[i] += priv->sensor[i].p1_calib_offset;
break;
}
return mode;
}
int tsens_calibrate_nvmem(struct tsens_priv *priv, int shift)
{
u32 p1[MAX_SENSORS], p2[MAX_SENSORS];
int mode;
mode = tsens_read_calibration(priv, shift, p1, p2, false);
if (mode < 0)
return mode;
compute_intercept_slope(priv, p1, p2, mode);
return 0;
}
int tsens_calibrate_common(struct tsens_priv *priv)
{
return tsens_calibrate_nvmem(priv, 2);
}
static u32 tsens_read_cell(const struct tsens_single_value *cell, u8 len, u32 *data0, u32 *data1)
{
u32 val;
u32 *data = cell->blob ? data1 : data0;
if (cell->shift + len <= 32) {
val = data[cell->idx] >> cell->shift;
} else {
u8 part = 32 - cell->shift;
val = data[cell->idx] >> cell->shift;
val |= data[cell->idx + 1] << part;
}
return val & ((1 << len) - 1);
}
int tsens_read_calibration_legacy(struct tsens_priv *priv,
const struct tsens_legacy_calibration_format *format,
u32 *p1, u32 *p2,
u32 *cdata0, u32 *cdata1)
{
u32 mode, invalid;
u32 base1, base2;
int i;
mode = tsens_read_cell(&format->mode, 2, cdata0, cdata1);
invalid = tsens_read_cell(&format->invalid, 1, cdata0, cdata1);
if (invalid)
mode = NO_PT_CALIB;
dev_dbg(priv->dev, "calibration mode is %d\n", mode);
base1 = tsens_read_cell(&format->base[0], format->base_len, cdata0, cdata1);
base2 = tsens_read_cell(&format->base[1], format->base_len, cdata0, cdata1);
for (i = 0; i < priv->num_sensors; i++) {
p1[i] = tsens_read_cell(&format->sp[i][0], format->sp_len, cdata0, cdata1);
p2[i] = tsens_read_cell(&format->sp[i][1], format->sp_len, cdata0, cdata1);
}
switch (mode) {
case ONE_PT_CALIB:
for (i = 0; i < priv->num_sensors; i++)
p1[i] = p1[i] + (base1 << format->base_shift);
break;
case TWO_PT_CALIB:
for (i = 0; i < priv->num_sensors; i++)
p2[i] = (p2[i] + base2) << format->base_shift;
fallthrough;
case ONE_PT_CALIB2:
for (i = 0; i < priv->num_sensors; i++)
p1[i] = (p1[i] + base1) << format->base_shift;
break;
default:
dev_dbg(priv->dev, "calibrationless mode\n");
for (i = 0; i < priv->num_sensors; i++) {
p1[i] = 500;
p2[i] = 780;
}
}
return mode;
}
/*
* Use this function on devices where slope and offset calculations
* depend on calibration data read from qfprom. On others the slope
* and offset values are derived from tz->tzp->slope and tz->tzp->offset
* resp.
*/
void compute_intercept_slope(struct tsens_priv *priv, u32 *p1,
u32 *p2, u32 mode)
{
int i;
int num, den;
for (i = 0; i < priv->num_sensors; i++) {
dev_dbg(priv->dev,
"%s: sensor%d - data_point1:%#x data_point2:%#x\n",
__func__, i, p1[i], p2 ? p2[i] : 0);
if (!priv->sensor[i].slope)
priv->sensor[i].slope = SLOPE_DEFAULT;
if (mode == TWO_PT_CALIB || mode == TWO_PT_CALIB_NO_OFFSET) {
/*
* slope (m) = adc_code2 - adc_code1 (y2 - y1)/
* temp_120_degc - temp_30_degc (x2 - x1)
*/
num = p2[i] - p1[i];
num *= SLOPE_FACTOR;
den = CAL_DEGC_PT2 - CAL_DEGC_PT1;
priv->sensor[i].slope = num / den;
}
priv->sensor[i].offset = (p1[i] * SLOPE_FACTOR) -
(CAL_DEGC_PT1 *
priv->sensor[i].slope);
dev_dbg(priv->dev, "%s: offset:%d\n", __func__,
priv->sensor[i].offset);
}
}
static inline u32 degc_to_code(int degc, const struct tsens_sensor *s)
{
u64 code = div_u64(((u64)degc * s->slope + s->offset), SLOPE_FACTOR);
pr_debug("%s: raw_code: 0x%llx, degc:%d\n", __func__, code, degc);
return clamp_val(code, THRESHOLD_MIN_ADC_CODE, THRESHOLD_MAX_ADC_CODE);
}
static inline int code_to_degc(u32 adc_code, const struct tsens_sensor *s)
{
int degc, num, den;
num = (adc_code * SLOPE_FACTOR) - s->offset;
den = s->slope;
if (num > 0)
degc = num + (den / 2);
else if (num < 0)
degc = num - (den / 2);
else
degc = num;
degc /= den;
return degc;
}
/**
* tsens_hw_to_mC - Return sign-extended temperature in mCelsius.
* @s: Pointer to sensor struct
* @field: Index into regmap_field array pointing to temperature data
*
* This function handles temperature returned in ADC code or deciCelsius
* depending on IP version.
*
* Return: Temperature in milliCelsius on success, a negative errno will
* be returned in error cases
*/
static int tsens_hw_to_mC(const struct tsens_sensor *s, int field)
{
struct tsens_priv *priv = s->priv;
u32 resolution;
u32 temp = 0;
int ret;
resolution = priv->fields[LAST_TEMP_0].msb -
priv->fields[LAST_TEMP_0].lsb;
ret = regmap_field_read(priv->rf[field], &temp);
if (ret)
return ret;
/* Convert temperature from ADC code to milliCelsius */
if (priv->feat->adc)
return code_to_degc(temp, s) * 1000;
/* deciCelsius -> milliCelsius along with sign extension */
return sign_extend32(temp, resolution) * 100;
}
/**
* tsens_mC_to_hw - Convert temperature to hardware register value
* @s: Pointer to sensor struct
* @temp: temperature in milliCelsius to be programmed to hardware
*
* This function outputs the value to be written to hardware in ADC code
* or deciCelsius depending on IP version.
*
* Return: ADC code or temperature in deciCelsius.
*/
static int tsens_mC_to_hw(const struct tsens_sensor *s, int temp)
{
struct tsens_priv *priv = s->priv;
/* milliC to adc code */
if (priv->feat->adc)
return degc_to_code(temp / 1000, s);
/* milliC to deciC */
return temp / 100;
}
static inline enum tsens_ver tsens_version(struct tsens_priv *priv)
{
return priv->feat->ver_major;
}
static void tsens_set_interrupt_v1(struct tsens_priv *priv, u32 hw_id,
enum tsens_irq_type irq_type, bool enable)
{
u32 index = 0;
switch (irq_type) {
case UPPER:
index = UP_INT_CLEAR_0 + hw_id;
break;
case LOWER:
index = LOW_INT_CLEAR_0 + hw_id;
break;
case CRITICAL:
/* No critical interrupts before v2 */
return;
}
regmap_field_write(priv->rf[index], enable ? 0 : 1);
}
static void tsens_set_interrupt_v2(struct tsens_priv *priv, u32 hw_id,
enum tsens_irq_type irq_type, bool enable)
{
u32 index_mask = 0, index_clear = 0;
/*
* To enable the interrupt flag for a sensor:
* - clear the mask bit
* To disable the interrupt flag for a sensor:
* - Mask further interrupts for this sensor
* - Write 1 followed by 0 to clear the interrupt
*/
switch (irq_type) {
case UPPER:
index_mask = UP_INT_MASK_0 + hw_id;
index_clear = UP_INT_CLEAR_0 + hw_id;
break;
case LOWER:
index_mask = LOW_INT_MASK_0 + hw_id;
index_clear = LOW_INT_CLEAR_0 + hw_id;
break;
case CRITICAL:
index_mask = CRIT_INT_MASK_0 + hw_id;
index_clear = CRIT_INT_CLEAR_0 + hw_id;
break;
}
if (enable) {
regmap_field_write(priv->rf[index_mask], 0);
} else {
regmap_field_write(priv->rf[index_mask], 1);
regmap_field_write(priv->rf[index_clear], 1);
regmap_field_write(priv->rf[index_clear], 0);
}
}
/**
* tsens_set_interrupt - Set state of an interrupt
* @priv: Pointer to tsens controller private data
* @hw_id: Hardware ID aka. sensor number
* @irq_type: irq_type from enum tsens_irq_type
* @enable: false = disable, true = enable
*
* Call IP-specific function to set state of an interrupt
*
* Return: void
*/
static void tsens_set_interrupt(struct tsens_priv *priv, u32 hw_id,
enum tsens_irq_type irq_type, bool enable)
{
dev_dbg(priv->dev, "[%u] %s: %s -> %s\n", hw_id, __func__,
irq_type ? ((irq_type == 1) ? "UP" : "CRITICAL") : "LOW",
enable ? "en" : "dis");
if (tsens_version(priv) > VER_1_X)
tsens_set_interrupt_v2(priv, hw_id, irq_type, enable);
else
tsens_set_interrupt_v1(priv, hw_id, irq_type, enable);
}
/**
* tsens_threshold_violated - Check if a sensor temperature violated a preset threshold
* @priv: Pointer to tsens controller private data
* @hw_id: Hardware ID aka. sensor number
* @d: Pointer to irq state data
*
* Return: 0 if threshold was not violated, 1 if it was violated and negative
* errno in case of errors
*/
static int tsens_threshold_violated(struct tsens_priv *priv, u32 hw_id,
struct tsens_irq_data *d)
{
int ret;
ret = regmap_field_read(priv->rf[UPPER_STATUS_0 + hw_id], &d->up_viol);
if (ret)
return ret;
ret = regmap_field_read(priv->rf[LOWER_STATUS_0 + hw_id], &d->low_viol);
if (ret)
return ret;
if (priv->feat->crit_int) {
ret = regmap_field_read(priv->rf[CRITICAL_STATUS_0 + hw_id],
&d->crit_viol);
if (ret)
return ret;
}
if (d->up_viol || d->low_viol || d->crit_viol)
return 1;
return 0;
}
static int tsens_read_irq_state(struct tsens_priv *priv, u32 hw_id,
const struct tsens_sensor *s,
struct tsens_irq_data *d)
{
int ret;
ret = regmap_field_read(priv->rf[UP_INT_CLEAR_0 + hw_id], &d->up_irq_clear);
if (ret)
return ret;
ret = regmap_field_read(priv->rf[LOW_INT_CLEAR_0 + hw_id], &d->low_irq_clear);
if (ret)
return ret;
if (tsens_version(priv) > VER_1_X) {
ret = regmap_field_read(priv->rf[UP_INT_MASK_0 + hw_id], &d->up_irq_mask);
if (ret)
return ret;
ret = regmap_field_read(priv->rf[LOW_INT_MASK_0 + hw_id], &d->low_irq_mask);
if (ret)
return ret;
ret = regmap_field_read(priv->rf[CRIT_INT_CLEAR_0 + hw_id],
&d->crit_irq_clear);
if (ret)
return ret;
ret = regmap_field_read(priv->rf[CRIT_INT_MASK_0 + hw_id],
&d->crit_irq_mask);
if (ret)
return ret;
d->crit_thresh = tsens_hw_to_mC(s, CRIT_THRESH_0 + hw_id);
} else {
/* No mask register on older TSENS */
d->up_irq_mask = 0;
d->low_irq_mask = 0;
d->crit_irq_clear = 0;
d->crit_irq_mask = 0;
d->crit_thresh = 0;
}
d->up_thresh = tsens_hw_to_mC(s, UP_THRESH_0 + hw_id);
d->low_thresh = tsens_hw_to_mC(s, LOW_THRESH_0 + hw_id);
dev_dbg(priv->dev, "[%u] %s%s: status(%u|%u|%u) | clr(%u|%u|%u) | mask(%u|%u|%u)\n",
hw_id, __func__,
(d->up_viol || d->low_viol || d->crit_viol) ? "(V)" : "",
d->low_viol, d->up_viol, d->crit_viol,
d->low_irq_clear, d->up_irq_clear, d->crit_irq_clear,
d->low_irq_mask, d->up_irq_mask, d->crit_irq_mask);
dev_dbg(priv->dev, "[%u] %s%s: thresh: (%d:%d:%d)\n", hw_id, __func__,
(d->up_viol || d->low_viol || d->crit_viol) ? "(V)" : "",
d->low_thresh, d->up_thresh, d->crit_thresh);
return 0;
}
static inline u32 masked_irq(u32 hw_id, u32 mask, enum tsens_ver ver)
{
if (ver > VER_1_X)
return mask & (1 << hw_id);
/* v1, v0.1 don't have a irq mask register */
return 0;
}
/**
* tsens_critical_irq_thread() - Threaded handler for critical interrupts
* @irq: irq number
* @data: tsens controller private data
*
* Check FSM watchdog bark status and clear if needed.
* Check all sensors to find ones that violated their critical threshold limits.
* Clear and then re-enable the interrupt.
*
* The level-triggered interrupt might deassert if the temperature returned to
* within the threshold limits by the time the handler got scheduled. We
* consider the irq to have been handled in that case.
*
* Return: IRQ_HANDLED
*/
static irqreturn_t tsens_critical_irq_thread(int irq, void *data)
{
struct tsens_priv *priv = data;
struct tsens_irq_data d;
int temp, ret, i;
u32 wdog_status, wdog_count;
if (priv->feat->has_watchdog) {
ret = regmap_field_read(priv->rf[WDOG_BARK_STATUS],
&wdog_status);
if (ret)
return ret;
if (wdog_status) {
/* Clear WDOG interrupt */
regmap_field_write(priv->rf[WDOG_BARK_CLEAR], 1);
regmap_field_write(priv->rf[WDOG_BARK_CLEAR], 0);
ret = regmap_field_read(priv->rf[WDOG_BARK_COUNT],
&wdog_count);
if (ret)
return ret;
if (wdog_count)
dev_dbg(priv->dev, "%s: watchdog count: %d\n",
__func__, wdog_count);
/* Fall through to handle critical interrupts if any */
}
}
for (i = 0; i < priv->num_sensors; i++) {
const struct tsens_sensor *s = &priv->sensor[i];
u32 hw_id = s->hw_id;
if (!s->tzd)
continue;
if (!tsens_threshold_violated(priv, hw_id, &d))
continue;
ret = get_temp_tsens_valid(s, &temp);
if (ret) {
dev_err(priv->dev, "[%u] %s: error reading sensor\n",
hw_id, __func__);
continue;
}
tsens_read_irq_state(priv, hw_id, s, &d);
if (d.crit_viol &&
!masked_irq(hw_id, d.crit_irq_mask, tsens_version(priv))) {
/* Mask critical interrupts, unused on Linux */
tsens_set_interrupt(priv, hw_id, CRITICAL, false);
}
}
return IRQ_HANDLED;
}
/**
* tsens_irq_thread - Threaded interrupt handler for uplow interrupts
* @irq: irq number
* @data: tsens controller private data
*
* Check all sensors to find ones that violated their threshold limits. If the
* temperature is still outside the limits, call thermal_zone_device_update() to
* update the thresholds, else re-enable the interrupts.
*
* The level-triggered interrupt might deassert if the temperature returned to
* within the threshold limits by the time the handler got scheduled. We
* consider the irq to have been handled in that case.
*
* Return: IRQ_HANDLED
*/
static irqreturn_t tsens_irq_thread(int irq, void *data)
{
struct tsens_priv *priv = data;
struct tsens_irq_data d;
int i;
for (i = 0; i < priv->num_sensors; i++) {
const struct tsens_sensor *s = &priv->sensor[i];
u32 hw_id = s->hw_id;
if (!s->tzd)
continue;
if (!tsens_threshold_violated(priv, hw_id, &d))
continue;
thermal_zone_device_update(s->tzd, THERMAL_EVENT_UNSPECIFIED);
if (tsens_version(priv) < VER_0_1) {
/* Constraint: There is only 1 interrupt control register for all
* 11 temperature sensor. So monitoring more than 1 sensor based
* on interrupts will yield inconsistent result. To overcome this
* issue we will monitor only sensor 0 which is the master sensor.
*/
break;
}
}
return IRQ_HANDLED;
}
/**
* tsens_combined_irq_thread() - Threaded interrupt handler for combined interrupts
* @irq: irq number
* @data: tsens controller private data
*
* Handle the combined interrupt as if it were 2 separate interrupts, so call the
* critical handler first and then the up/low one.
*
* Return: IRQ_HANDLED
*/
static irqreturn_t tsens_combined_irq_thread(int irq, void *data)
{
irqreturn_t ret;
ret = tsens_critical_irq_thread(irq, data);
if (ret != IRQ_HANDLED)
return ret;
return tsens_irq_thread(irq, data);
}
static int tsens_set_trips(struct thermal_zone_device *tz, int low, int high)
{
struct tsens_sensor *s = thermal_zone_device_priv(tz);
struct tsens_priv *priv = s->priv;
struct device *dev = priv->dev;
struct tsens_irq_data d;
unsigned long flags;
int high_val, low_val, cl_high, cl_low;
u32 hw_id = s->hw_id;
if (tsens_version(priv) < VER_0_1) {
/* Pre v0.1 IP had a single register for each type of interrupt
* and thresholds
*/
hw_id = 0;
}
dev_dbg(dev, "[%u] %s: proposed thresholds: (%d:%d)\n",
hw_id, __func__, low, high);
cl_high = clamp_val(high, priv->feat->trip_min_temp, priv->feat->trip_max_temp);
cl_low = clamp_val(low, priv->feat->trip_min_temp, priv->feat->trip_max_temp);
high_val = tsens_mC_to_hw(s, cl_high);
low_val = tsens_mC_to_hw(s, cl_low);
spin_lock_irqsave(&priv->ul_lock, flags);
tsens_read_irq_state(priv, hw_id, s, &d);
/* Write the new thresholds and clear the status */
regmap_field_write(priv->rf[LOW_THRESH_0 + hw_id], low_val);
regmap_field_write(priv->rf[UP_THRESH_0 + hw_id], high_val);
tsens_set_interrupt(priv, hw_id, LOWER, true);
tsens_set_interrupt(priv, hw_id, UPPER, true);
spin_unlock_irqrestore(&priv->ul_lock, flags);
dev_dbg(dev, "[%u] %s: (%d:%d)->(%d:%d)\n",
hw_id, __func__, d.low_thresh, d.up_thresh, cl_low, cl_high);
return 0;
}
static int tsens_enable_irq(struct tsens_priv *priv)
{
int ret;
int val = tsens_version(priv) > VER_1_X ? 7 : 1;
ret = regmap_field_write(priv->rf[INT_EN], val);
if (ret < 0)
dev_err(priv->dev, "%s: failed to enable interrupts\n",
__func__);
return ret;
}
static void tsens_disable_irq(struct tsens_priv *priv)
{
regmap_field_write(priv->rf[INT_EN], 0);
}
int get_temp_tsens_valid(const struct tsens_sensor *s, int *temp)
{
struct tsens_priv *priv = s->priv;
int hw_id = s->hw_id;
u32 temp_idx = LAST_TEMP_0 + hw_id;
u32 valid_idx = VALID_0 + hw_id;
u32 valid;
int ret;
/* VER_0 doesn't have VALID bit */
if (tsens_version(priv) == VER_0)
goto get_temp;
/* Valid bit is 0 for 6 AHB clock cycles.
* At 19.2MHz, 1 AHB clock is ~60ns.
* We should enter this loop very, very rarely.
* Wait 1 us since it's the min of poll_timeout macro.
* Old value was 400 ns.
*/
ret = regmap_field_read_poll_timeout(priv->rf[valid_idx], valid,
valid, 1, 20 * USEC_PER_MSEC);
if (ret)
return ret;
get_temp:
/* Valid bit is set, OK to read the temperature */
*temp = tsens_hw_to_mC(s, temp_idx);
return 0;
}
int get_temp_common(const struct tsens_sensor *s, int *temp)
{
struct tsens_priv *priv = s->priv;
int hw_id = s->hw_id;
int last_temp = 0, ret, trdy;
unsigned long timeout;
timeout = jiffies + usecs_to_jiffies(TIMEOUT_US);
do {
if (tsens_version(priv) == VER_0) {
ret = regmap_field_read(priv->rf[TRDY], &trdy);
if (ret)
return ret;
if (!trdy)
continue;
}
ret = regmap_field_read(priv->rf[LAST_TEMP_0 + hw_id], &last_temp);
if (ret)
return ret;
*temp = code_to_degc(last_temp, s) * 1000;
return 0;
} while (time_before(jiffies, timeout));
return -ETIMEDOUT;
}
#ifdef CONFIG_DEBUG_FS
static int dbg_sensors_show(struct seq_file *s, void *data)
{
struct platform_device *pdev = s->private;
struct tsens_priv *priv = platform_get_drvdata(pdev);
int i;
seq_printf(s, "max: %2d\nnum: %2d\n\n",
priv->feat->max_sensors, priv->num_sensors);
seq_puts(s, " id slope offset\n--------------------------\n");
for (i = 0; i < priv->num_sensors; i++) {
seq_printf(s, "%8d %8d %8d\n", priv->sensor[i].hw_id,
priv->sensor[i].slope, priv->sensor[i].offset);
}
return 0;
}
static int dbg_version_show(struct seq_file *s, void *data)
{
struct platform_device *pdev = s->private;
struct tsens_priv *priv = platform_get_drvdata(pdev);
u32 maj_ver, min_ver, step_ver;
int ret;
if (tsens_version(priv) > VER_0_1) {
ret = regmap_field_read(priv->rf[VER_MAJOR], &maj_ver);
if (ret)
return ret;
ret = regmap_field_read(priv->rf[VER_MINOR], &min_ver);
if (ret)
return ret;
ret = regmap_field_read(priv->rf[VER_STEP], &step_ver);
if (ret)
return ret;
seq_printf(s, "%d.%d.%d\n", maj_ver, min_ver, step_ver);
} else {
seq_printf(s, "0.%d.0\n", priv->feat->ver_major);
}
return 0;
}
DEFINE_SHOW_ATTRIBUTE(dbg_version);
DEFINE_SHOW_ATTRIBUTE(dbg_sensors);
static void tsens_debug_init(struct platform_device *pdev)
{
struct tsens_priv *priv = platform_get_drvdata(pdev);
priv->debug_root = debugfs_lookup("tsens", NULL);
if (!priv->debug_root)
priv->debug_root = debugfs_create_dir("tsens", NULL);
/* A directory for each instance of the TSENS IP */
priv->debug = debugfs_create_dir(dev_name(&pdev->dev), priv->debug_root);
debugfs_create_file("version", 0444, priv->debug, pdev, &dbg_version_fops);
debugfs_create_file("sensors", 0444, priv->debug, pdev, &dbg_sensors_fops);
}
#else
static inline void tsens_debug_init(struct platform_device *pdev) {}
#endif
static const struct regmap_config tsens_config = {
.name = "tm",
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
};
static const struct regmap_config tsens_srot_config = {
.name = "srot",
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
};
int __init init_common(struct tsens_priv *priv)
{
void __iomem *tm_base, *srot_base;
struct device *dev = priv->dev;
u32 ver_minor;
struct resource *res;
u32 enabled;
int ret, i, j;
struct platform_device *op = of_find_device_by_node(priv->dev->of_node);
if (!op)
return -EINVAL;
if (op->num_resources > 1) {
/* DT with separate SROT and TM address space */
priv->tm_offset = 0;
res = platform_get_resource(op, IORESOURCE_MEM, 1);
srot_base = devm_ioremap_resource(dev, res);
if (IS_ERR(srot_base)) {
ret = PTR_ERR(srot_base);
goto err_put_device;
}
priv->srot_map = devm_regmap_init_mmio(dev, srot_base,
&tsens_srot_config);
if (IS_ERR(priv->srot_map)) {
ret = PTR_ERR(priv->srot_map);
goto err_put_device;
}
} else {
/* old DTs where SROT and TM were in a contiguous 2K block */
priv->tm_offset = 0x1000;
}
if (tsens_version(priv) >= VER_0_1) {
res = platform_get_resource(op, IORESOURCE_MEM, 0);
tm_base = devm_ioremap_resource(dev, res);
if (IS_ERR(tm_base)) {
ret = PTR_ERR(tm_base);
goto err_put_device;
}
priv->tm_map = devm_regmap_init_mmio(dev, tm_base, &tsens_config);
} else { /* VER_0 share the same gcc regs using a syscon */
struct device *parent = priv->dev->parent;
if (parent)
priv->tm_map = syscon_node_to_regmap(parent->of_node);
}
if (IS_ERR_OR_NULL(priv->tm_map)) {
if (!priv->tm_map)
ret = -ENODEV;
else
ret = PTR_ERR(priv->tm_map);
goto err_put_device;
}
/* VER_0 have only tm_map */
if (!priv->srot_map)
priv->srot_map = priv->tm_map;
if (tsens_version(priv) > VER_0_1) {
for (i = VER_MAJOR; i <= VER_STEP; i++) {
priv->rf[i] = devm_regmap_field_alloc(dev, priv->srot_map,
priv->fields[i]);
if (IS_ERR(priv->rf[i])) {
ret = PTR_ERR(priv->rf[i]);
goto err_put_device;
}
}
ret = regmap_field_read(priv->rf[VER_MINOR], &ver_minor);
if (ret)
goto err_put_device;
}
priv->rf[TSENS_EN] = devm_regmap_field_alloc(dev, priv->srot_map,
priv->fields[TSENS_EN]);
if (IS_ERR(priv->rf[TSENS_EN])) {
ret = PTR_ERR(priv->rf[TSENS_EN]);
goto err_put_device;
}
/* in VER_0 TSENS need to be explicitly enabled */
if (tsens_version(priv) == VER_0)
regmap_field_write(priv->rf[TSENS_EN], 1);
ret = regmap_field_read(priv->rf[TSENS_EN], &enabled);
if (ret)
goto err_put_device;
if (!enabled) {
dev_err(dev, "%s: device not enabled\n", __func__);
ret = -ENODEV;
goto err_put_device;
}
priv->rf[SENSOR_EN] = devm_regmap_field_alloc(dev, priv->srot_map,
priv->fields[SENSOR_EN]);
if (IS_ERR(priv->rf[SENSOR_EN])) {
ret = PTR_ERR(priv->rf[SENSOR_EN]);
goto err_put_device;
}
priv->rf[INT_EN] = devm_regmap_field_alloc(dev, priv->tm_map,
priv->fields[INT_EN]);
if (IS_ERR(priv->rf[INT_EN])) {
ret = PTR_ERR(priv->rf[INT_EN]);
goto err_put_device;
}
priv->rf[TSENS_SW_RST] =
devm_regmap_field_alloc(dev, priv->srot_map, priv->fields[TSENS_SW_RST]);
if (IS_ERR(priv->rf[TSENS_SW_RST])) {
ret = PTR_ERR(priv->rf[TSENS_SW_RST]);
goto err_put_device;
}
priv->rf[TRDY] = devm_regmap_field_alloc(dev, priv->tm_map, priv->fields[TRDY]);
if (IS_ERR(priv->rf[TRDY])) {
ret = PTR_ERR(priv->rf[TRDY]);
goto err_put_device;
}
/* This loop might need changes if enum regfield_ids is reordered */
for (j = LAST_TEMP_0; j <= UP_THRESH_15; j += 16) {
for (i = 0; i < priv->feat->max_sensors; i++) {
int idx = j + i;
priv->rf[idx] = devm_regmap_field_alloc(dev,
priv->tm_map,
priv->fields[idx]);
if (IS_ERR(priv->rf[idx])) {
ret = PTR_ERR(priv->rf[idx]);
goto err_put_device;
}
}
}
if (priv->feat->crit_int || tsens_version(priv) < VER_0_1) {
/* Loop might need changes if enum regfield_ids is reordered */
for (j = CRITICAL_STATUS_0; j <= CRIT_THRESH_15; j += 16) {
for (i = 0; i < priv->feat->max_sensors; i++) {
int idx = j + i;
priv->rf[idx] =
devm_regmap_field_alloc(dev,
priv->tm_map,
priv->fields[idx]);
if (IS_ERR(priv->rf[idx])) {
ret = PTR_ERR(priv->rf[idx]);
goto err_put_device;
}
}
}
}
if (tsens_version(priv) > VER_1_X && ver_minor > 2) {
/* Watchdog is present only on v2.3+ */
priv->feat->has_watchdog = 1;
for (i = WDOG_BARK_STATUS; i <= CC_MON_MASK; i++) {
priv->rf[i] = devm_regmap_field_alloc(dev, priv->tm_map,
priv->fields[i]);
if (IS_ERR(priv->rf[i])) {
ret = PTR_ERR(priv->rf[i]);
goto err_put_device;
}
}
/*
* Watchdog is already enabled, unmask the bark.
* Disable cycle completion monitoring
*/
regmap_field_write(priv->rf[WDOG_BARK_MASK], 0);
regmap_field_write(priv->rf[CC_MON_MASK], 1);
}
spin_lock_init(&priv->ul_lock);
/* VER_0 interrupt doesn't need to be enabled */
if (tsens_version(priv) >= VER_0_1)
tsens_enable_irq(priv);
err_put_device:
put_device(&op->dev);
return ret;
}
static int tsens_get_temp(struct thermal_zone_device *tz, int *temp)
{
struct tsens_sensor *s = thermal_zone_device_priv(tz);
struct tsens_priv *priv = s->priv;
return priv->ops->get_temp(s, temp);
}
static int __maybe_unused tsens_suspend(struct device *dev)
{
struct tsens_priv *priv = dev_get_drvdata(dev);
if (priv->ops && priv->ops->suspend)
return priv->ops->suspend(priv);
return 0;
}
static int __maybe_unused tsens_resume(struct device *dev)
{
struct tsens_priv *priv = dev_get_drvdata(dev);
if (priv->ops && priv->ops->resume)
return priv->ops->resume(priv);
return 0;
}
static SIMPLE_DEV_PM_OPS(tsens_pm_ops, tsens_suspend, tsens_resume);
static const struct of_device_id tsens_table[] = {
{
.compatible = "qcom,ipq8064-tsens",
.data = &data_8960,
}, {
.compatible = "qcom,ipq8074-tsens",
.data = &data_ipq8074,
}, {
.compatible = "qcom,mdm9607-tsens",
.data = &data_9607,
}, {
.compatible = "qcom,msm8226-tsens",
.data = &data_8226,
}, {
.compatible = "qcom,msm8909-tsens",
.data = &data_8909,
}, {
.compatible = "qcom,msm8916-tsens",
.data = &data_8916,
}, {
.compatible = "qcom,msm8939-tsens",
.data = &data_8939,
}, {
.compatible = "qcom,msm8956-tsens",
.data = &data_8956,
}, {
.compatible = "qcom,msm8960-tsens",
.data = &data_8960,
}, {
.compatible = "qcom,msm8974-tsens",
.data = &data_8974,
}, {
.compatible = "qcom,msm8976-tsens",
.data = &data_8976,
}, {
.compatible = "qcom,msm8996-tsens",
.data = &data_8996,
}, {
.compatible = "qcom,tsens-v1",
.data = &data_tsens_v1,
}, {
.compatible = "qcom,tsens-v2",
.data = &data_tsens_v2,
},
{}
};
MODULE_DEVICE_TABLE(of, tsens_table);
static const struct thermal_zone_device_ops tsens_of_ops = {
.get_temp = tsens_get_temp,
.set_trips = tsens_set_trips,
};
static int tsens_register_irq(struct tsens_priv *priv, char *irqname,
irq_handler_t thread_fn)
{
struct platform_device *pdev;
int ret, irq;
pdev = of_find_device_by_node(priv->dev->of_node);
if (!pdev)
return -ENODEV;
irq = platform_get_irq_byname(pdev, irqname);
if (irq < 0) {
ret = irq;
/* For old DTs with no IRQ defined */
if (irq == -ENXIO)
ret = 0;
} else {
/* VER_0 interrupt is TRIGGER_RISING, VER_0_1 and up is ONESHOT */
if (tsens_version(priv) == VER_0)
ret = devm_request_threaded_irq(&pdev->dev, irq,
thread_fn, NULL,
IRQF_TRIGGER_RISING,
dev_name(&pdev->dev),
priv);
else
ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
thread_fn, IRQF_ONESHOT,
dev_name(&pdev->dev),
priv);
if (ret)
dev_err(&pdev->dev, "%s: failed to get irq\n",
__func__);
else
enable_irq_wake(irq);
}
put_device(&pdev->dev);
return ret;
}
#ifdef CONFIG_SUSPEND
static int tsens_reinit(struct tsens_priv *priv)
{
if (tsens_version(priv) >= VER_2_X) {
/*
* Re-enable the watchdog, unmask the bark.
* Disable cycle completion monitoring
*/
if (priv->feat->has_watchdog) {
regmap_field_write(priv->rf[WDOG_BARK_MASK], 0);
regmap_field_write(priv->rf[CC_MON_MASK], 1);
}
/* Re-enable interrupts */
tsens_enable_irq(priv);
}
return 0;
}
int tsens_resume_common(struct tsens_priv *priv)
{
if (pm_suspend_target_state == PM_SUSPEND_MEM)
tsens_reinit(priv);
return 0;
}
#endif /* !CONFIG_SUSPEND */
static int tsens_register(struct tsens_priv *priv)
{
int i, ret;
struct thermal_zone_device *tzd;
for (i = 0; i < priv->num_sensors; i++) {
priv->sensor[i].priv = priv;
tzd = devm_thermal_of_zone_register(priv->dev, priv->sensor[i].hw_id,
&priv->sensor[i],
&tsens_of_ops);
if (IS_ERR(tzd))
continue;
priv->sensor[i].tzd = tzd;
if (priv->ops->enable)
priv->ops->enable(priv, i);
devm_thermal_add_hwmon_sysfs(priv->dev, tzd);
}
/* VER_0 require to set MIN and MAX THRESH
* These 2 regs are set using the:
* - CRIT_THRESH_0 for MAX THRESH hardcoded to 120°C
* - CRIT_THRESH_1 for MIN THRESH hardcoded to 0°C
*/
if (tsens_version(priv) < VER_0_1) {
regmap_field_write(priv->rf[CRIT_THRESH_0],
tsens_mC_to_hw(priv->sensor, 120000));
regmap_field_write(priv->rf[CRIT_THRESH_1],
tsens_mC_to_hw(priv->sensor, 0));
}
if (priv->feat->combo_int) {
ret = tsens_register_irq(priv, "combined",
tsens_combined_irq_thread);
} else {
ret = tsens_register_irq(priv, "uplow", tsens_irq_thread);
if (ret < 0)
return ret;
if (priv->feat->crit_int)
ret = tsens_register_irq(priv, "critical",
tsens_critical_irq_thread);
}
return ret;
}
static int tsens_probe(struct platform_device *pdev)
{
int ret, i;
struct device *dev;
struct device_node *np;
struct tsens_priv *priv;
const struct tsens_plat_data *data;
const struct of_device_id *id;
u32 num_sensors;
if (pdev->dev.of_node)
dev = &pdev->dev;
else
dev = pdev->dev.parent;
np = dev->of_node;
id = of_match_node(tsens_table, np);
if (id)
data = id->data;
else
data = &data_8960;
num_sensors = data->num_sensors;
if (np)
of_property_read_u32(np, "#qcom,sensors", &num_sensors);
if (num_sensors <= 0) {
dev_err(dev, "%s: invalid number of sensors\n", __func__);
return -EINVAL;
}
priv = devm_kzalloc(dev,
struct_size(priv, sensor, num_sensors),
GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->dev = dev;
priv->num_sensors = num_sensors;
priv->ops = data->ops;
for (i = 0; i < priv->num_sensors; i++) {
if (data->hw_ids)
priv->sensor[i].hw_id = data->hw_ids[i];
else
priv->sensor[i].hw_id = i;
}
priv->feat = data->feat;
priv->fields = data->fields;
platform_set_drvdata(pdev, priv);
if (!priv->ops || !priv->ops->init || !priv->ops->get_temp)
return -EINVAL;
ret = priv->ops->init(priv);
if (ret < 0) {
dev_err(dev, "%s: init failed\n", __func__);
return ret;
}
if (priv->ops->calibrate) {
ret = priv->ops->calibrate(priv);
if (ret < 0)
return dev_err_probe(dev, ret, "%s: calibration failed\n",
__func__);
}
ret = tsens_register(priv);
if (!ret)
tsens_debug_init(pdev);
return ret;
}
static void tsens_remove(struct platform_device *pdev)
{
struct tsens_priv *priv = platform_get_drvdata(pdev);
debugfs_remove_recursive(priv->debug_root);
tsens_disable_irq(priv);
if (priv->ops->disable)
priv->ops->disable(priv);
}
static struct platform_driver tsens_driver = {
.probe = tsens_probe,
.remove_new = tsens_remove,
.driver = {
.name = "qcom-tsens",
.pm = &tsens_pm_ops,
.of_match_table = tsens_table,
},
};
module_platform_driver(tsens_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("QCOM Temperature Sensor driver");
MODULE_ALIAS("platform:qcom-tsens");