/* * If the temperature over a period of time High, * the resulting TSHUT gave CRU module,let it reset the entire chip, * or via GPIO give PMIC.
*/ enum tshut_mode {
TSHUT_MODE_CRU = 0,
TSHUT_MODE_GPIO,
};
/* * The system Temperature Sensors tshut(tshut) polarity * the bit 8 is tshut polarity. * 0: low active, 1: high active
*/ enum tshut_polarity {
TSHUT_LOW_ACTIVE = 0,
TSHUT_HIGH_ACTIVE,
};
/* * The conversion table has the adc value and temperature. * ADC_DECREMENT: the adc value is of diminishing.(e.g. rk3288_code_table) * ADC_INCREMENT: the adc value is incremental.(e.g. rk3368_code_table)
*/ enum adc_sort_mode {
ADC_DECREMENT = 0,
ADC_INCREMENT,
};
#include"thermal_hwmon.h"
/** * struct chip_tsadc_table - hold information about chip-specific differences * @id: conversion table * @length: size of conversion table * @data_mask: mask to apply on data inputs * @mode: sort mode of this adc variant (incrementing or decrementing)
*/ struct chip_tsadc_table { conststruct tsadc_table *id; unsignedint length;
u32 data_mask; enum adc_sort_mode mode;
};
/** * struct rockchip_tsadc_chip - hold the private data of tsadc chip * @chn_offset: the channel offset of the first channel * @chn_num: the channel number of tsadc chip * @trim_slope: used to convert the trim code to a temperature in millicelsius * @tshut_temp: the hardware-controlled shutdown temperature value, with no trim * @tshut_mode: the hardware-controlled shutdown mode (0:CRU 1:GPIO) * @tshut_polarity: the hardware-controlled active polarity (0:LOW 1:HIGH) * @initialize: SoC special initialize tsadc controller method * @irq_ack: clear the interrupt * @control: enable/disable method for the tsadc controller * @get_temp: get the raw temperature, unadjusted by trim * @set_alarm_temp: set the high temperature interrupt * @set_tshut_temp: set the hardware-controlled shutdown temperature * @set_tshut_mode: set the hardware-controlled shutdown mode * @get_trim_code: convert a hardware temperature code to one adjusted for by trim * @table: the chip-specific conversion table
*/ struct rockchip_tsadc_chip { /* The sensor id of chip correspond to the ADC channel */ int chn_offset; int chn_num;
/* Used to convert trim code to trim temp */ int trim_slope;
/* The hardware-controlled tshut property */ int tshut_temp; enum tshut_mode tshut_mode; enum tshut_polarity tshut_polarity;
/* Per-sensor methods */ int (*get_temp)(conststruct chip_tsadc_table *table, int chn, void __iomem *reg, int *temp); int (*set_alarm_temp)(conststruct chip_tsadc_table *table, int chn, void __iomem *reg, int temp); int (*set_tshut_temp)(conststruct chip_tsadc_table *table, int chn, void __iomem *reg, int temp); void (*set_tshut_mode)(int chn, void __iomem *reg, enum tshut_mode m); int (*get_trim_code)(conststruct chip_tsadc_table *table, int code, int trim_base, int trim_base_frac);
/** * struct rockchip_thermal_sensor - hold the information of thermal sensor * @thermal: pointer to the platform/configuration data * @tzd: pointer to a thermal zone * @of_node: pointer to the device_node representing this sensor, if any * @id: identifier of the thermal sensor * @trim_temp: per-sensor trim temperature value
*/ struct rockchip_thermal_sensor { struct rockchip_thermal_data *thermal; struct thermal_zone_device *tzd; struct device_node *of_node; int id; int trim_temp;
};
/** * struct rockchip_thermal_data - hold the private data of thermal driver * @chip: pointer to the platform/configuration data * @pdev: platform device of thermal * @reset: the reset controller of tsadc * @sensors: array of thermal sensors * @clk: the controller clock is divided by the exteral 24MHz * @pclk: the advanced peripherals bus clock * @grf: the general register file will be used to do static set by software * @regs: the base address of tsadc controller * @trim_base: major component of sensor trim value, in Celsius * @trim_base_frac: minor component of sensor trim value, in Decicelsius * @trim: fallback thermal trim value for each channel * @tshut_temp: the hardware-controlled shutdown temperature value, with no trim * @trim_temp: the fallback trim temperature for the whole sensor * @tshut_mode: the hardware-controlled shutdown mode (0:CRU 1:GPIO) * @tshut_polarity: the hardware-controlled active polarity (0:LOW 1:HIGH)
*/ struct rockchip_thermal_data { conststruct rockchip_tsadc_chip *chip; struct platform_device *pdev; struct reset_control *reset;
struct rockchip_thermal_sensor *sensors;
struct clk *clk; struct clk *pclk;
struct regmap *grf; void __iomem *regs;
int trim_base; int trim_base_frac; int trim;
int tshut_temp; int trim_temp; enum tshut_mode tshut_mode; enum tshut_polarity tshut_polarity;
};
/** * struct tsadc_table - code to temperature conversion table * @code: the value of adc channel * @temp: the temperature * Note: * code to temperature mapping of the temperature sensor is a piece wise linear * curve.Any temperature, code faling between to 2 give temperatures can be * linearly interpolated. * Code to Temperature mapping should be updated based on manufacturer results.
*/ struct tsadc_table {
u32 code; int temp;
};
/* * The conversion code granularity provided by the table. Let's * assume that the relationship between temperature and * analog value between 2 table entries is linear and interpolate * to produce less granular result.
*/
num = abs(table->id[mid + 1].code - table->id[mid].code);
num *= temp - table->id[mid].temp;
denom = table->id[mid + 1].temp - table->id[mid].temp;
/* * The 5C granularity provided by the table is too much. Let's * assume that the relationship between sensor readings and * temperature between 2 table entries is linear and interpolate * to produce less granular result.
*/
num = table->id[mid].temp - table->id[mid - 1].temp;
num *= abs(table->id[mid - 1].code - code);
denom = abs(table->id[mid - 1].code - table->id[mid].code);
*temp = table->id[mid - 1].temp + (num / denom);
return 0;
}
/** * rk_tsadcv2_initialize - initialize TASDC Controller. * @grf: the general register file will be used to do static set by software * @regs: the base address of tsadc controller * @tshut_polarity: the hardware-controlled active polarity (0:LOW 1:HIGH) * * (1) Set TSADC_V2_AUTO_PERIOD: * Configure the interleave between every two accessing of * TSADC in normal operation. * * (2) Set TSADCV2_AUTO_PERIOD_HT: * Configure the interleave between every two accessing of * TSADC after the temperature is higher than COM_SHUT or COM_INT. * * (3) Set TSADCV2_HIGH_INT_DEBOUNCE and TSADC_HIGHT_TSHUT_DEBOUNCE: * If the temperature is higher than COMP_INT or COMP_SHUT for * "debounce" times, TSADC controller will generate interrupt or TSHUT.
*/ staticvoid rk_tsadcv2_initialize(struct regmap *grf, void __iomem *regs, enum tshut_polarity tshut_polarity)
{ if (tshut_polarity == TSHUT_HIGH_ACTIVE)
writel_relaxed(0U | TSADCV2_AUTO_TSHUT_POLARITY_HIGH,
regs + TSADCV2_AUTO_CON); else
writel_relaxed(0U & ~TSADCV2_AUTO_TSHUT_POLARITY_HIGH,
regs + TSADCV2_AUTO_CON);
/** * rk_tsadcv3_initialize - initialize TASDC Controller. * @grf: the general register file will be used to do static set by software * @regs: the base address of tsadc controller * @tshut_polarity: the hardware-controlled active polarity (0:LOW 1:HIGH) * * (1) The tsadc control power sequence. * * (2) Set TSADC_V2_AUTO_PERIOD: * Configure the interleave between every two accessing of * TSADC in normal operation. * * (2) Set TSADCV2_AUTO_PERIOD_HT: * Configure the interleave between every two accessing of * TSADC after the temperature is higher than COM_SHUT or COM_INT. * * (3) Set TSADCV2_HIGH_INT_DEBOUNCE and TSADC_HIGHT_TSHUT_DEBOUNCE: * If the temperature is higher than COMP_INT or COMP_SHUT for * "debounce" times, TSADC controller will generate interrupt or TSHUT.
*/ staticvoid rk_tsadcv3_initialize(struct regmap *grf, void __iomem *regs, enum tshut_polarity tshut_polarity)
{ /* The tsadc control power sequence */ if (IS_ERR(grf)) { /* Set interleave value to workround ic time sync issue */
writel_relaxed(TSADCV2_USER_INTER_PD_SOC, regs +
TSADCV2_USER_CON);
} else { /* Enable the voltage common mode feature */
regmap_write(grf, GRF_TSADC_TESTBIT_L, GRF_TSADC_VCM_EN_L);
regmap_write(grf, GRF_TSADC_TESTBIT_H, GRF_TSADC_VCM_EN_H);
usleep_range(15, 100); /* The spec note says at least 15 us */
regmap_write(grf, GRF_SARADC_TESTBIT, GRF_SARADC_TESTBIT_ON);
regmap_write(grf, GRF_TSADC_TESTBIT_H, GRF_TSADC_TESTBIT_H_ON);
usleep_range(90, 200); /* The spec note says at least 90 us */
/* * The general register file will is optional * and might not be available.
*/ if (!IS_ERR(grf)) {
regmap_write(grf, RK3568_GRF_TSADC_CON, RK3568_GRF_TSADC_TSEN); /* * RK3568 TRM, section 18.5. requires a delay no less * than 10us between the rising edge of tsadc_tsen_en * and the rising edge of tsadc_ana_reg_0/1/2.
*/
udelay(15);
regmap_write(grf, RK3568_GRF_TSADC_CON, RK3568_GRF_TSADC_ANA_REG0);
regmap_write(grf, RK3568_GRF_TSADC_CON, RK3568_GRF_TSADC_ANA_REG1);
regmap_write(grf, RK3568_GRF_TSADC_CON, RK3568_GRF_TSADC_ANA_REG2);
/* * RK3568 TRM, section 18.5. requires a delay no less * than 90us after the rising edge of tsadc_ana_reg_0/1/2.
*/
usleep_range(100, 200);
}
}
val = readl_relaxed(regs + TSADCV2_AUTO_CON); if (enable)
val |= TSADCV2_AUTO_EN; else
val &= ~TSADCV2_AUTO_EN;
writel_relaxed(val, regs + TSADCV2_AUTO_CON);
}
/** * rk_tsadcv3_control - the tsadc controller is enabled or disabled. * @regs: the base address of tsadc controller * @enable: boolean flag to enable the controller * * NOTE: TSADC controller works at auto mode, and some SoCs need set the * tsadc_q_sel bit on TSADCV2_AUTO_CON[1]. The (1024 - tsadc_q) as output * adc value if setting this bit to enable.
*/ staticvoid rk_tsadcv3_control(void __iomem *regs, bool enable)
{
u32 val;
val = readl_relaxed(regs + TSADCV2_AUTO_CON); if (enable)
val |= TSADCV2_AUTO_EN | TSADCV3_AUTO_Q_SEL_EN; else
val &= ~TSADCV2_AUTO_EN;
staticint rk_tsadcv2_alarm_temp(conststruct chip_tsadc_table *table, int chn, void __iomem *regs, int temp)
{
u32 alarm_value;
u32 int_en, int_clr;
/* * In some cases, some sensors didn't need the trip points, the * set_trips will pass {-INT_MAX, INT_MAX} to trigger tsadc alarm * in the end, ignore this case and disable the high temperature * interrupt.
*/ if (temp == INT_MAX) {
int_clr = readl_relaxed(regs + TSADCV2_INT_EN);
int_clr &= ~TSADCV2_INT_SRC_EN(chn);
writel_relaxed(int_clr, regs + TSADCV2_INT_EN); return 0;
}
/* Make sure the value is valid */
alarm_value = rk_tsadcv2_temp_to_code(table, temp); if (alarm_value == table->data_mask) return -ERANGE;
staticint rk_tsadcv3_alarm_temp(conststruct chip_tsadc_table *table, int chn, void __iomem *regs, int temp)
{
u32 alarm_value;
/* * In some cases, some sensors didn't need the trip points, the * set_trips will pass {-INT_MAX, INT_MAX} to trigger tsadc alarm * in the end, ignore this case and disable the high temperature * interrupt.
*/ if (temp == INT_MAX) {
writel_relaxed(TSADCV2_INT_SRC_EN_MASK(chn),
regs + TSADCV3_HT_INT_EN); return 0;
} /* Make sure the value is valid */
alarm_value = rk_tsadcv2_temp_to_code(table, temp); if (alarm_value == table->data_mask) return -ERANGE;
writel_relaxed(alarm_value & table->data_mask,
regs + TSADCV3_COMP_INT(chn));
writel_relaxed(TSADCV2_INT_SRC_EN(chn) | TSADCV2_INT_SRC_EN_MASK(chn),
regs + TSADCV3_HT_INT_EN); return 0;
}
staticint rk_tsadcv2_tshut_temp(conststruct chip_tsadc_table *table, int chn, void __iomem *regs, int temp)
{
u32 tshut_value, val;
/* Make sure the value is valid */
tshut_value = rk_tsadcv2_temp_to_code(table, temp); if (tshut_value == table->data_mask) return -ERANGE;
val = readl_relaxed(regs + TSADCV2_INT_EN); if (mode == TSHUT_MODE_GPIO) {
val &= ~TSADCV2_SHUT_2CRU_SRC_EN(chn);
val |= TSADCV2_SHUT_2GPIO_SRC_EN(chn);
} else {
val &= ~TSADCV2_SHUT_2GPIO_SRC_EN(chn);
val |= TSADCV2_SHUT_2CRU_SRC_EN(chn);
}
/** * rockchip_get_efuse_value - read an OTP cell from a device node * @np: pointer to the device node with the nvmem-cells property * @cell_name: name of cell that should be read * @value: pointer to where the read value will be placed * * Return: Negative errno on failure, during which *value will not be touched, * or 0 on success.
*/ staticint rockchip_get_efuse_value(struct device_node *np, constchar *cell_name, int *value)
{ struct nvmem_cell *cell; int ret = 0;
size_t len;
u8 *buf; int i;
cell = of_nvmem_cell_get(np, cell_name); if (IS_ERR(cell)) return PTR_ERR(cell);
buf = nvmem_cell_read(cell, &len);
nvmem_cell_put(cell);
if (IS_ERR(buf)) return PTR_ERR(buf);
if (len > sizeof(*value)) {
ret = -ERANGE; gotoexit;
}
/* Copy with implicit endian conversion */
*value = 0; for (i = 0; i < len; i++)
*value |= (int) buf[i] << (8 * i);
exit:
kfree(buf); return ret;
}
staticint rockchip_get_trim_configuration(struct device *dev, struct device_node *np, struct rockchip_thermal_data *thermal)
{ conststruct rockchip_tsadc_chip *tsadc = thermal->chip; int trim_base = 0, trim_base_frac = 0, trim = 0; int trim_code; int ret;
ret = rockchip_get_efuse_value(np, "trim_base", &trim_base); if (ret < 0) { if (ret == -ENOENT) {
trim_base = 30;
dev_dbg(dev, "trim_base is absent, defaulting to 30\n");
} else {
dev_err(dev, "failed reading nvmem value of trim_base: %pe\n",
ERR_PTR(ret)); return ret;
}
}
ret = rockchip_get_efuse_value(np, "trim_base_frac", &trim_base_frac); if (ret < 0) { if (ret == -ENOENT) {
dev_dbg(dev, "trim_base_frac is absent, defaulting to 0\n");
} else {
dev_err(dev, "failed reading nvmem value of trim_base_frac: %pe\n",
ERR_PTR(ret)); return ret;
}
}
thermal->trim_base = trim_base;
thermal->trim_base_frac = trim_base_frac;
/* * If the tsadc node contains the trim property, then it is used in the * absence of per-channel trim values
*/ if (!rockchip_get_efuse_value(np, "trim", &trim))
thermal->trim = trim; if (trim) {
trim_code = tsadc->get_trim_code(&tsadc->table, trim,
trim_base, trim_base_frac);
thermal->trim_temp = thermal->chip->trim_slope * trim_code;
}
/* The tsadc wont to handle the error in here since some SoCs didn't * need this property.
*/
thermal->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf"); if (IS_ERR(thermal->grf))
dev_warn(dev, "Missing rockchip,grf property\n");
/** * rockchip_thermal_reset_controller - Reset TSADC Controller, reset all tsadc registers. * @reset: the reset controller of tsadc
*/ staticvoid rockchip_thermal_reset_controller(struct reset_control *reset)
{
reset_control_assert(reset);
usleep_range(10, 20);
reset_control_deassert(reset);
}
staticint rockchip_thermal_probe(struct platform_device *pdev)
{ struct device_node *np = pdev->dev.of_node; struct rockchip_thermal_data *thermal; struct device_node *child; int irq; int i; int error;
u32 chn;
irq = platform_get_irq(pdev, 0); if (irq < 0) return -EINVAL;
thermal = devm_kzalloc(&pdev->dev, sizeof(struct rockchip_thermal_data),
GFP_KERNEL); if (!thermal) return -ENOMEM;
thermal->pdev = pdev;
thermal->chip = device_get_match_data(&pdev->dev); if (!thermal->chip) return -EINVAL;
thermal->sensors = devm_kcalloc(&pdev->dev, thermal->chip->chn_num, sizeof(*thermal->sensors), GFP_KERNEL); if (!thermal->sensors) return -ENOMEM;
thermal->regs = devm_platform_get_and_ioremap_resource(pdev, 0, NULL); if (IS_ERR(thermal->regs)) return PTR_ERR(thermal->regs);
thermal->reset = devm_reset_control_array_get_exclusive(&pdev->dev); if (IS_ERR(thermal->reset)) return dev_err_probe(&pdev->dev, PTR_ERR(thermal->reset), "failed to get tsadc reset.\n");
thermal->clk = devm_clk_get_enabled(&pdev->dev, "tsadc"); if (IS_ERR(thermal->clk)) return dev_err_probe(&pdev->dev, PTR_ERR(thermal->clk), "failed to get tsadc clock.\n");
thermal->pclk = devm_clk_get_enabled(&pdev->dev, "apb_pclk"); if (IS_ERR(thermal->pclk)) return dev_err_probe(&pdev->dev, PTR_ERR(thermal->pclk), "failed to get apb_pclk clock.\n");
for_each_available_child_of_node(np, child) { if (!of_property_read_u32(child, "reg", &chn)) { if (chn < thermal->chip->chn_num)
thermal->sensors[chn].of_node = child; else
dev_warn(&pdev->dev, "sensor address (%d) too large, ignoring its trim\n",
chn);
}
}
for (i = 0; i < thermal->chip->chn_num; i++) {
error = rockchip_thermal_register_sensor(pdev, thermal,
&thermal->sensors[i],
thermal->chip->chn_offset + i); if (error) return dev_err_probe(&pdev->dev, error, "failed to register sensor[%d].\n", i);
}
error = devm_request_threaded_irq(&pdev->dev, irq, NULL,
&rockchip_thermal_alarm_irq_thread,
IRQF_ONESHOT, "rockchip_thermal", thermal); if (error) return dev_err_probe(&pdev->dev, error, "failed to request tsadc irq.\n");
thermal->chip->control(thermal->regs, true);
for (i = 0; i < thermal->chip->chn_num; i++) {
rockchip_thermal_toggle_sensor(&thermal->sensors[i], true);
error = thermal_add_hwmon_sysfs(thermal->sensors[i].tzd); if (error)
dev_warn(&pdev->dev, "failed to register sensor %d with hwmon: %d\n",
i, error);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.