| /* |
| * Copyright (c) 2014-2016, Fuzhou Rockchip Electronics Co., Ltd |
| * Caesar Wang <wxt@rock-chips.com> |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms and conditions of the GNU General Public License, |
| * version 2, as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope 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/clk.h> |
| #include <linux/delay.h> |
| #include <linux/interrupt.h> |
| #include <linux/io.h> |
| #include <linux/module.h> |
| #include <linux/of.h> |
| #include <linux/of_address.h> |
| #include <linux/of_irq.h> |
| #include <linux/platform_device.h> |
| #include <linux/regmap.h> |
| #include <linux/reset.h> |
| #include <linux/thermal.h> |
| #include <linux/mfd/syscon.h> |
| #include <linux/pinctrl/consumer.h> |
| |
| /** |
| * 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 system has two Temperature Sensors. |
| * sensor0 is for CPU, and sensor1 is for GPU. |
| */ |
| enum sensor_id { |
| SENSOR_CPU = 0, |
| SENSOR_GPU, |
| }; |
| |
| /** |
| * 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, |
| }; |
| |
| /** |
| * The max sensors is two in rockchip SoCs. |
| * Two sensors: CPU and GPU sensor. |
| */ |
| #define SOC_MAX_SENSORS 2 |
| |
| /** |
| * 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 { |
| const struct tsadc_table *id; |
| unsigned int length; |
| u32 data_mask; |
| enum adc_sort_mode mode; |
| }; |
| |
| /** |
| * struct rockchip_tsadc_chip - hold the private data of tsadc chip |
| * @chn_id[SOC_MAX_SENSORS]: the sensor id of chip correspond to the channel |
| * @chn_num: the channel number of tsadc chip |
| * @tshut_temp: the hardware-controlled shutdown temperature value |
| * @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 |
| * @get_temp: get the temperature |
| * @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 |
| * @table: the chip-specific conversion table |
| */ |
| struct rockchip_tsadc_chip { |
| /* The sensor id of chip correspond to the ADC channel */ |
| int chn_id[SOC_MAX_SENSORS]; |
| int chn_num; |
| |
| /* The hardware-controlled tshut property */ |
| int tshut_temp; |
| enum tshut_mode tshut_mode; |
| enum tshut_polarity tshut_polarity; |
| |
| /* Chip-wide methods */ |
| void (*initialize)(struct regmap *grf, |
| void __iomem *reg, enum tshut_polarity p); |
| void (*irq_ack)(void __iomem *reg); |
| void (*control)(void __iomem *reg, bool on); |
| |
| /* Per-sensor methods */ |
| int (*get_temp)(struct chip_tsadc_table table, |
| int chn, void __iomem *reg, int *temp); |
| void (*set_alarm_temp)(struct chip_tsadc_table table, |
| int chn, void __iomem *reg, int temp); |
| void (*set_tshut_temp)(struct chip_tsadc_table table, |
| int chn, void __iomem *reg, int temp); |
| void (*set_tshut_mode)(int chn, void __iomem *reg, enum tshut_mode m); |
| |
| /* Per-table methods */ |
| struct chip_tsadc_table table; |
| }; |
| |
| /** |
| * struct rockchip_thermal_sensor - hold the information of thermal sensor |
| * @thermal: pointer to the platform/configuration data |
| * @tzd: pointer to a thermal zone |
| * @id: identifier of the thermal sensor |
| */ |
| struct rockchip_thermal_sensor { |
| struct rockchip_thermal_data *thermal; |
| struct thermal_zone_device *tzd; |
| int id; |
| }; |
| |
| /** |
| * 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[SOC_MAX_SENSORS]: the thermal sensor |
| * @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 |
| * @tshut_temp: the hardware-controlled shutdown temperature value |
| * @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 { |
| const struct rockchip_tsadc_chip *chip; |
| struct platform_device *pdev; |
| struct reset_control *reset; |
| |
| struct rockchip_thermal_sensor sensors[SOC_MAX_SENSORS]; |
| |
| struct clk *clk; |
| struct clk *pclk; |
| |
| struct regmap *grf; |
| void __iomem *regs; |
| |
| int tshut_temp; |
| enum tshut_mode tshut_mode; |
| enum tshut_polarity tshut_polarity; |
| }; |
| |
| /** |
| * TSADC Sensor Register description: |
| * |
| * TSADCV2_* are used for RK3288 SoCs, the other chips can reuse it. |
| * TSADCV3_* are used for newer SoCs than RK3288. (e.g: RK3228, RK3399) |
| * |
| */ |
| #define TSADCV2_USER_CON 0x00 |
| #define TSADCV2_AUTO_CON 0x04 |
| #define TSADCV2_INT_EN 0x08 |
| #define TSADCV2_INT_PD 0x0c |
| #define TSADCV2_DATA(chn) (0x20 + (chn) * 0x04) |
| #define TSADCV2_COMP_INT(chn) (0x30 + (chn) * 0x04) |
| #define TSADCV2_COMP_SHUT(chn) (0x40 + (chn) * 0x04) |
| #define TSADCV2_HIGHT_INT_DEBOUNCE 0x60 |
| #define TSADCV2_HIGHT_TSHUT_DEBOUNCE 0x64 |
| #define TSADCV2_AUTO_PERIOD 0x68 |
| #define TSADCV2_AUTO_PERIOD_HT 0x6c |
| |
| #define TSADCV2_AUTO_EN BIT(0) |
| #define TSADCV2_AUTO_SRC_EN(chn) BIT(4 + (chn)) |
| #define TSADCV2_AUTO_TSHUT_POLARITY_HIGH BIT(8) |
| |
| #define TSADCV3_AUTO_Q_SEL_EN BIT(1) |
| |
| #define TSADCV2_INT_SRC_EN(chn) BIT(chn) |
| #define TSADCV2_SHUT_2GPIO_SRC_EN(chn) BIT(4 + (chn)) |
| #define TSADCV2_SHUT_2CRU_SRC_EN(chn) BIT(8 + (chn)) |
| |
| #define TSADCV2_INT_PD_CLEAR_MASK ~BIT(8) |
| #define TSADCV3_INT_PD_CLEAR_MASK ~BIT(16) |
| |
| #define TSADCV2_DATA_MASK 0xfff |
| #define TSADCV3_DATA_MASK 0x3ff |
| |
| #define TSADCV2_HIGHT_INT_DEBOUNCE_COUNT 4 |
| #define TSADCV2_HIGHT_TSHUT_DEBOUNCE_COUNT 4 |
| #define TSADCV2_AUTO_PERIOD_TIME 250 /* 250ms */ |
| #define TSADCV2_AUTO_PERIOD_HT_TIME 50 /* 50ms */ |
| #define TSADCV3_AUTO_PERIOD_TIME 1875 /* 2.5ms */ |
| #define TSADCV3_AUTO_PERIOD_HT_TIME 1875 /* 2.5ms */ |
| |
| #define TSADCV2_USER_INTER_PD_SOC 0x340 /* 13 clocks */ |
| |
| #define GRF_SARADC_TESTBIT 0x0e644 |
| #define GRF_TSADC_TESTBIT_L 0x0e648 |
| #define GRF_TSADC_TESTBIT_H 0x0e64c |
| |
| #define GRF_SARADC_TESTBIT_ON (0x10001 << 2) |
| #define GRF_TSADC_TESTBIT_H_ON (0x10001 << 2) |
| #define GRF_TSADC_VCM_EN_L (0x10001 << 7) |
| #define GRF_TSADC_VCM_EN_H (0x10001 << 7) |
| |
| /** |
| * 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; |
| }; |
| |
| static const struct tsadc_table rk3228_code_table[] = { |
| {0, -40000}, |
| {588, -40000}, |
| {593, -35000}, |
| {598, -30000}, |
| {603, -25000}, |
| {608, -20000}, |
| {613, -15000}, |
| {618, -10000}, |
| {623, -5000}, |
| {629, 0}, |
| {634, 5000}, |
| {639, 10000}, |
| {644, 15000}, |
| {649, 20000}, |
| {654, 25000}, |
| {660, 30000}, |
| {665, 35000}, |
| {670, 40000}, |
| {675, 45000}, |
| {681, 50000}, |
| {686, 55000}, |
| {691, 60000}, |
| {696, 65000}, |
| {702, 70000}, |
| {707, 75000}, |
| {712, 80000}, |
| {717, 85000}, |
| {723, 90000}, |
| {728, 95000}, |
| {733, 100000}, |
| {738, 105000}, |
| {744, 110000}, |
| {749, 115000}, |
| {754, 120000}, |
| {760, 125000}, |
| {TSADCV2_DATA_MASK, 125000}, |
| }; |
| |
| static const struct tsadc_table rk3288_code_table[] = { |
| {TSADCV2_DATA_MASK, -40000}, |
| {3800, -40000}, |
| {3792, -35000}, |
| {3783, -30000}, |
| {3774, -25000}, |
| {3765, -20000}, |
| {3756, -15000}, |
| {3747, -10000}, |
| {3737, -5000}, |
| {3728, 0}, |
| {3718, 5000}, |
| {3708, 10000}, |
| {3698, 15000}, |
| {3688, 20000}, |
| {3678, 25000}, |
| {3667, 30000}, |
| {3656, 35000}, |
| {3645, 40000}, |
| {3634, 45000}, |
| {3623, 50000}, |
| {3611, 55000}, |
| {3600, 60000}, |
| {3588, 65000}, |
| {3575, 70000}, |
| {3563, 75000}, |
| {3550, 80000}, |
| {3537, 85000}, |
| {3524, 90000}, |
| {3510, 95000}, |
| {3496, 100000}, |
| {3482, 105000}, |
| {3467, 110000}, |
| {3452, 115000}, |
| {3437, 120000}, |
| {3421, 125000}, |
| }; |
| |
| static const struct tsadc_table rk3368_code_table[] = { |
| {0, -40000}, |
| {106, -40000}, |
| {108, -35000}, |
| {110, -30000}, |
| {112, -25000}, |
| {114, -20000}, |
| {116, -15000}, |
| {118, -10000}, |
| {120, -5000}, |
| {122, 0}, |
| {124, 5000}, |
| {126, 10000}, |
| {128, 15000}, |
| {130, 20000}, |
| {132, 25000}, |
| {134, 30000}, |
| {136, 35000}, |
| {138, 40000}, |
| {140, 45000}, |
| {142, 50000}, |
| {144, 55000}, |
| {146, 60000}, |
| {148, 65000}, |
| {150, 70000}, |
| {152, 75000}, |
| {154, 80000}, |
| {156, 85000}, |
| {158, 90000}, |
| {160, 95000}, |
| {162, 100000}, |
| {163, 105000}, |
| {165, 110000}, |
| {167, 115000}, |
| {169, 120000}, |
| {171, 125000}, |
| {TSADCV3_DATA_MASK, 125000}, |
| }; |
| |
| static const struct tsadc_table rk3399_code_table[] = { |
| {0, -40000}, |
| {402, -40000}, |
| {410, -35000}, |
| {419, -30000}, |
| {427, -25000}, |
| {436, -20000}, |
| {444, -15000}, |
| {453, -10000}, |
| {461, -5000}, |
| {470, 0}, |
| {478, 5000}, |
| {487, 10000}, |
| {496, 15000}, |
| {504, 20000}, |
| {513, 25000}, |
| {521, 30000}, |
| {530, 35000}, |
| {538, 40000}, |
| {547, 45000}, |
| {555, 50000}, |
| {564, 55000}, |
| {573, 60000}, |
| {581, 65000}, |
| {590, 70000}, |
| {599, 75000}, |
| {607, 80000}, |
| {616, 85000}, |
| {624, 90000}, |
| {633, 95000}, |
| {642, 100000}, |
| {650, 105000}, |
| {659, 110000}, |
| {668, 115000}, |
| {677, 120000}, |
| {685, 125000}, |
| {TSADCV3_DATA_MASK, 125000}, |
| }; |
| |
| static u32 rk_tsadcv2_temp_to_code(struct chip_tsadc_table table, |
| int temp) |
| { |
| int high, low, mid; |
| u32 error = 0; |
| |
| low = 0; |
| high = table.length - 1; |
| mid = (high + low) / 2; |
| |
| /* Return mask code data when the temp is over table range */ |
| if (temp < table.id[low].temp || temp > table.id[high].temp) { |
| error = table.data_mask; |
| goto exit; |
| } |
| |
| while (low <= high) { |
| if (temp == table.id[mid].temp) |
| return table.id[mid].code; |
| else if (temp < table.id[mid].temp) |
| high = mid - 1; |
| else |
| low = mid + 1; |
| mid = (low + high) / 2; |
| } |
| |
| exit: |
| pr_err("Invalid the conversion, error=%d\n", error); |
| return error; |
| } |
| |
| static int rk_tsadcv2_code_to_temp(struct chip_tsadc_table table, u32 code, |
| int *temp) |
| { |
| unsigned int low = 1; |
| unsigned int high = table.length - 1; |
| unsigned int mid = (low + high) / 2; |
| unsigned int num; |
| unsigned long denom; |
| |
| WARN_ON(table.length < 2); |
| |
| switch (table.mode) { |
| case ADC_DECREMENT: |
| code &= table.data_mask; |
| if (code < table.id[high].code) |
| return -EAGAIN; /* Incorrect reading */ |
| |
| while (low <= high) { |
| if (code >= table.id[mid].code && |
| code < table.id[mid - 1].code) |
| break; |
| else if (code < table.id[mid].code) |
| low = mid + 1; |
| else |
| high = mid - 1; |
| |
| mid = (low + high) / 2; |
| } |
| break; |
| case ADC_INCREMENT: |
| code &= table.data_mask; |
| if (code < table.id[low].code) |
| return -EAGAIN; /* Incorrect reading */ |
| |
| while (low <= high) { |
| if (code <= table.id[mid].code && |
| code > table.id[mid - 1].code) |
| break; |
| else if (code > table.id[mid].code) |
| low = mid + 1; |
| else |
| high = mid - 1; |
| |
| mid = (low + high) / 2; |
| } |
| break; |
| default: |
| pr_err("Invalid the conversion table\n"); |
| } |
| |
| /* |
| * 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. |
| * |
| * (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. |
| */ |
| static void 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); |
| |
| writel_relaxed(TSADCV2_AUTO_PERIOD_TIME, regs + TSADCV2_AUTO_PERIOD); |
| writel_relaxed(TSADCV2_HIGHT_INT_DEBOUNCE_COUNT, |
| regs + TSADCV2_HIGHT_INT_DEBOUNCE); |
| writel_relaxed(TSADCV2_AUTO_PERIOD_HT_TIME, |
| regs + TSADCV2_AUTO_PERIOD_HT); |
| writel_relaxed(TSADCV2_HIGHT_TSHUT_DEBOUNCE_COUNT, |
| regs + TSADCV2_HIGHT_TSHUT_DEBOUNCE); |
| } |
| |
| /** |
| * rk_tsadcv3_initialize - initialize TASDC Controller. |
| * |
| * (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. |
| */ |
| static void 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); |
| |
| writel_relaxed(TSADCV2_AUTO_PERIOD_TIME, |
| regs + TSADCV2_AUTO_PERIOD); |
| writel_relaxed(TSADCV2_HIGHT_INT_DEBOUNCE_COUNT, |
| regs + TSADCV2_HIGHT_INT_DEBOUNCE); |
| writel_relaxed(TSADCV2_AUTO_PERIOD_HT_TIME, |
| regs + TSADCV2_AUTO_PERIOD_HT); |
| writel_relaxed(TSADCV2_HIGHT_TSHUT_DEBOUNCE_COUNT, |
| regs + TSADCV2_HIGHT_TSHUT_DEBOUNCE); |
| |
| } 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 */ |
| |
| writel_relaxed(TSADCV3_AUTO_PERIOD_TIME, |
| regs + TSADCV2_AUTO_PERIOD); |
| writel_relaxed(TSADCV2_HIGHT_INT_DEBOUNCE_COUNT, |
| regs + TSADCV2_HIGHT_INT_DEBOUNCE); |
| writel_relaxed(TSADCV3_AUTO_PERIOD_HT_TIME, |
| regs + TSADCV2_AUTO_PERIOD_HT); |
| writel_relaxed(TSADCV2_HIGHT_TSHUT_DEBOUNCE_COUNT, |
| regs + TSADCV2_HIGHT_TSHUT_DEBOUNCE); |
| } |
| |
| 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); |
| } |
| |
| static void rk_tsadcv2_irq_ack(void __iomem *regs) |
| { |
| u32 val; |
| |
| val = readl_relaxed(regs + TSADCV2_INT_PD); |
| writel_relaxed(val & TSADCV2_INT_PD_CLEAR_MASK, regs + TSADCV2_INT_PD); |
| } |
| |
| static void rk_tsadcv3_irq_ack(void __iomem *regs) |
| { |
| u32 val; |
| |
| val = readl_relaxed(regs + TSADCV2_INT_PD); |
| writel_relaxed(val & TSADCV3_INT_PD_CLEAR_MASK, regs + TSADCV2_INT_PD); |
| } |
| |
| static void rk_tsadcv2_control(void __iomem *regs, bool enable) |
| { |
| u32 val; |
| |
| 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. |
| * |
| * 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. |
| */ |
| static void 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; |
| |
| writel_relaxed(val, regs + TSADCV2_AUTO_CON); |
| } |
| |
| static int rk_tsadcv2_get_temp(struct chip_tsadc_table table, |
| int chn, void __iomem *regs, int *temp) |
| { |
| u32 val; |
| |
| val = readl_relaxed(regs + TSADCV2_DATA(chn)); |
| |
| return rk_tsadcv2_code_to_temp(table, val, temp); |
| } |
| |
| static void rk_tsadcv2_alarm_temp(struct chip_tsadc_table table, |
| int chn, void __iomem *regs, int temp) |
| { |
| u32 alarm_value, int_en; |
| |
| /* Make sure the value is valid */ |
| alarm_value = rk_tsadcv2_temp_to_code(table, temp); |
| if (alarm_value == table.data_mask) |
| return; |
| |
| writel_relaxed(alarm_value & table.data_mask, |
| regs + TSADCV2_COMP_INT(chn)); |
| |
| int_en = readl_relaxed(regs + TSADCV2_INT_EN); |
| int_en |= TSADCV2_INT_SRC_EN(chn); |
| writel_relaxed(int_en, regs + TSADCV2_INT_EN); |
| } |
| |
| static void rk_tsadcv2_tshut_temp(struct 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; |
| |
| writel_relaxed(tshut_value, regs + TSADCV2_COMP_SHUT(chn)); |
| |
| /* TSHUT will be valid */ |
| val = readl_relaxed(regs + TSADCV2_AUTO_CON); |
| writel_relaxed(val | TSADCV2_AUTO_SRC_EN(chn), regs + TSADCV2_AUTO_CON); |
| } |
| |
| static void rk_tsadcv2_tshut_mode(int chn, void __iomem *regs, |
| enum tshut_mode mode) |
| { |
| u32 val; |
| |
| 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); |
| } |
| |
| writel_relaxed(val, regs + TSADCV2_INT_EN); |
| } |
| |
| static const struct rockchip_tsadc_chip rk3228_tsadc_data = { |
| .chn_id[SENSOR_CPU] = 0, /* cpu sensor is channel 0 */ |
| .chn_num = 1, /* one channel for tsadc */ |
| |
| .tshut_mode = TSHUT_MODE_GPIO, /* default TSHUT via GPIO give PMIC */ |
| .tshut_polarity = TSHUT_LOW_ACTIVE, /* default TSHUT LOW ACTIVE */ |
| .tshut_temp = 95000, |
| |
| .initialize = rk_tsadcv2_initialize, |
| .irq_ack = rk_tsadcv3_irq_ack, |
| .control = rk_tsadcv3_control, |
| .get_temp = rk_tsadcv2_get_temp, |
| .set_alarm_temp = rk_tsadcv2_alarm_temp, |
| .set_tshut_temp = rk_tsadcv2_tshut_temp, |
| .set_tshut_mode = rk_tsadcv2_tshut_mode, |
| |
| .table = { |
| .id = rk3228_code_table, |
| .length = ARRAY_SIZE(rk3228_code_table), |
| .data_mask = TSADCV3_DATA_MASK, |
| .mode = ADC_INCREMENT, |
| }, |
| }; |
| |
| static const struct rockchip_tsadc_chip rk3288_tsadc_data = { |
| .chn_id[SENSOR_CPU] = 1, /* cpu sensor is channel 1 */ |
| .chn_id[SENSOR_GPU] = 2, /* gpu sensor is channel 2 */ |
| .chn_num = 2, /* two channels for tsadc */ |
| |
| .tshut_mode = TSHUT_MODE_GPIO, /* default TSHUT via GPIO give PMIC */ |
| .tshut_polarity = TSHUT_LOW_ACTIVE, /* default TSHUT LOW ACTIVE */ |
| .tshut_temp = 95000, |
| |
| .initialize = rk_tsadcv2_initialize, |
| .irq_ack = rk_tsadcv2_irq_ack, |
| .control = rk_tsadcv2_control, |
| .get_temp = rk_tsadcv2_get_temp, |
| .set_alarm_temp = rk_tsadcv2_alarm_temp, |
| .set_tshut_temp = rk_tsadcv2_tshut_temp, |
| .set_tshut_mode = rk_tsadcv2_tshut_mode, |
| |
| .table = { |
| .id = rk3288_code_table, |
| .length = ARRAY_SIZE(rk3288_code_table), |
| .data_mask = TSADCV2_DATA_MASK, |
| .mode = ADC_DECREMENT, |
| }, |
| }; |
| |
| static const struct rockchip_tsadc_chip rk3366_tsadc_data = { |
| .chn_id[SENSOR_CPU] = 0, /* cpu sensor is channel 0 */ |
| .chn_id[SENSOR_GPU] = 1, /* gpu sensor is channel 1 */ |
| .chn_num = 2, /* two channels for tsadc */ |
| |
| .tshut_mode = TSHUT_MODE_GPIO, /* default TSHUT via GPIO give PMIC */ |
| .tshut_polarity = TSHUT_LOW_ACTIVE, /* default TSHUT LOW ACTIVE */ |
| .tshut_temp = 95000, |
| |
| .initialize = rk_tsadcv3_initialize, |
| .irq_ack = rk_tsadcv3_irq_ack, |
| .control = rk_tsadcv3_control, |
| .get_temp = rk_tsadcv2_get_temp, |
| .set_alarm_temp = rk_tsadcv2_alarm_temp, |
| .set_tshut_temp = rk_tsadcv2_tshut_temp, |
| .set_tshut_mode = rk_tsadcv2_tshut_mode, |
| |
| .table = { |
| .id = rk3228_code_table, |
| .length = ARRAY_SIZE(rk3228_code_table), |
| .data_mask = TSADCV3_DATA_MASK, |
| .mode = ADC_INCREMENT, |
| }, |
| }; |
| |
| static const struct rockchip_tsadc_chip rk3368_tsadc_data = { |
| .chn_id[SENSOR_CPU] = 0, /* cpu sensor is channel 0 */ |
| .chn_id[SENSOR_GPU] = 1, /* gpu sensor is channel 1 */ |
| .chn_num = 2, /* two channels for tsadc */ |
| |
| .tshut_mode = TSHUT_MODE_GPIO, /* default TSHUT via GPIO give PMIC */ |
| .tshut_polarity = TSHUT_LOW_ACTIVE, /* default TSHUT LOW ACTIVE */ |
| .tshut_temp = 95000, |
| |
| .initialize = rk_tsadcv2_initialize, |
| .irq_ack = rk_tsadcv2_irq_ack, |
| .control = rk_tsadcv2_control, |
| .get_temp = rk_tsadcv2_get_temp, |
| .set_alarm_temp = rk_tsadcv2_alarm_temp, |
| .set_tshut_temp = rk_tsadcv2_tshut_temp, |
| .set_tshut_mode = rk_tsadcv2_tshut_mode, |
| |
| .table = { |
| .id = rk3368_code_table, |
| .length = ARRAY_SIZE(rk3368_code_table), |
| .data_mask = TSADCV3_DATA_MASK, |
| .mode = ADC_INCREMENT, |
| }, |
| }; |
| |
| static const struct rockchip_tsadc_chip rk3399_tsadc_data = { |
| .chn_id[SENSOR_CPU] = 0, /* cpu sensor is channel 0 */ |
| .chn_id[SENSOR_GPU] = 1, /* gpu sensor is channel 1 */ |
| .chn_num = 2, /* two channels for tsadc */ |
| |
| .tshut_mode = TSHUT_MODE_GPIO, /* default TSHUT via GPIO give PMIC */ |
| .tshut_polarity = TSHUT_LOW_ACTIVE, /* default TSHUT LOW ACTIVE */ |
| .tshut_temp = 95000, |
| |
| .initialize = rk_tsadcv3_initialize, |
| .irq_ack = rk_tsadcv3_irq_ack, |
| .control = rk_tsadcv3_control, |
| .get_temp = rk_tsadcv2_get_temp, |
| .set_alarm_temp = rk_tsadcv2_alarm_temp, |
| .set_tshut_temp = rk_tsadcv2_tshut_temp, |
| .set_tshut_mode = rk_tsadcv2_tshut_mode, |
| |
| .table = { |
| .id = rk3399_code_table, |
| .length = ARRAY_SIZE(rk3399_code_table), |
| .data_mask = TSADCV3_DATA_MASK, |
| .mode = ADC_INCREMENT, |
| }, |
| }; |
| |
| static const struct of_device_id of_rockchip_thermal_match[] = { |
| { |
| .compatible = "rockchip,rk3228-tsadc", |
| .data = (void *)&rk3228_tsadc_data, |
| }, |
| { |
| .compatible = "rockchip,rk3288-tsadc", |
| .data = (void *)&rk3288_tsadc_data, |
| }, |
| { |
| .compatible = "rockchip,rk3366-tsadc", |
| .data = (void *)&rk3366_tsadc_data, |
| }, |
| { |
| .compatible = "rockchip,rk3368-tsadc", |
| .data = (void *)&rk3368_tsadc_data, |
| }, |
| { |
| .compatible = "rockchip,rk3399-tsadc", |
| .data = (void *)&rk3399_tsadc_data, |
| }, |
| { /* end */ }, |
| }; |
| MODULE_DEVICE_TABLE(of, of_rockchip_thermal_match); |
| |
| static void |
| rockchip_thermal_toggle_sensor(struct rockchip_thermal_sensor *sensor, bool on) |
| { |
| struct thermal_zone_device *tzd = sensor->tzd; |
| |
| tzd->ops->set_mode(tzd, |
| on ? THERMAL_DEVICE_ENABLED : THERMAL_DEVICE_DISABLED); |
| } |
| |
| static irqreturn_t rockchip_thermal_alarm_irq_thread(int irq, void *dev) |
| { |
| struct rockchip_thermal_data *thermal = dev; |
| int i; |
| |
| dev_dbg(&thermal->pdev->dev, "thermal alarm\n"); |
| |
| thermal->chip->irq_ack(thermal->regs); |
| |
| for (i = 0; i < thermal->chip->chn_num; i++) |
| thermal_zone_device_update(thermal->sensors[i].tzd, |
| THERMAL_EVENT_UNSPECIFIED); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static int rockchip_thermal_set_trips(void *_sensor, int low, int high) |
| { |
| struct rockchip_thermal_sensor *sensor = _sensor; |
| struct rockchip_thermal_data *thermal = sensor->thermal; |
| const struct rockchip_tsadc_chip *tsadc = thermal->chip; |
| |
| dev_dbg(&thermal->pdev->dev, "%s: sensor %d: low: %d, high %d\n", |
| __func__, sensor->id, low, high); |
| |
| tsadc->set_alarm_temp(tsadc->table, |
| sensor->id, thermal->regs, high); |
| |
| return 0; |
| } |
| |
| static int rockchip_thermal_get_temp(void *_sensor, int *out_temp) |
| { |
| struct rockchip_thermal_sensor *sensor = _sensor; |
| struct rockchip_thermal_data *thermal = sensor->thermal; |
| const struct rockchip_tsadc_chip *tsadc = sensor->thermal->chip; |
| int retval; |
| |
| retval = tsadc->get_temp(tsadc->table, |
| sensor->id, thermal->regs, out_temp); |
| dev_dbg(&thermal->pdev->dev, "sensor %d - temp: %d, retval: %d\n", |
| sensor->id, *out_temp, retval); |
| |
| return retval; |
| } |
| |
| static const struct thermal_zone_of_device_ops rockchip_of_thermal_ops = { |
| .get_temp = rockchip_thermal_get_temp, |
| .set_trips = rockchip_thermal_set_trips, |
| }; |
| |
| static int rockchip_configure_from_dt(struct device *dev, |
| struct device_node *np, |
| struct rockchip_thermal_data *thermal) |
| { |
| u32 shut_temp, tshut_mode, tshut_polarity; |
| |
| if (of_property_read_u32(np, "rockchip,hw-tshut-temp", &shut_temp)) { |
| dev_warn(dev, |
| "Missing tshut temp property, using default %d\n", |
| thermal->chip->tshut_temp); |
| thermal->tshut_temp = thermal->chip->tshut_temp; |
| } else { |
| if (shut_temp > INT_MAX) { |
| dev_err(dev, "Invalid tshut temperature specified: %d\n", |
| shut_temp); |
| return -ERANGE; |
| } |
| thermal->tshut_temp = shut_temp; |
| } |
| |
| if (of_property_read_u32(np, "rockchip,hw-tshut-mode", &tshut_mode)) { |
| dev_warn(dev, |
| "Missing tshut mode property, using default (%s)\n", |
| thermal->chip->tshut_mode == TSHUT_MODE_GPIO ? |
| "gpio" : "cru"); |
| thermal->tshut_mode = thermal->chip->tshut_mode; |
| } else { |
| thermal->tshut_mode = tshut_mode; |
| } |
| |
| if (thermal->tshut_mode > 1) { |
| dev_err(dev, "Invalid tshut mode specified: %d\n", |
| thermal->tshut_mode); |
| return -EINVAL; |
| } |
| |
| if (of_property_read_u32(np, "rockchip,hw-tshut-polarity", |
| &tshut_polarity)) { |
| dev_warn(dev, |
| "Missing tshut-polarity property, using default (%s)\n", |
| thermal->chip->tshut_polarity == TSHUT_LOW_ACTIVE ? |
| "low" : "high"); |
| thermal->tshut_polarity = thermal->chip->tshut_polarity; |
| } else { |
| thermal->tshut_polarity = tshut_polarity; |
| } |
| |
| if (thermal->tshut_polarity > 1) { |
| dev_err(dev, "Invalid tshut-polarity specified: %d\n", |
| thermal->tshut_polarity); |
| return -EINVAL; |
| } |
| |
| /* 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"); |
| |
| return 0; |
| } |
| |
| static int |
| rockchip_thermal_register_sensor(struct platform_device *pdev, |
| struct rockchip_thermal_data *thermal, |
| struct rockchip_thermal_sensor *sensor, |
| int id) |
| { |
| const struct rockchip_tsadc_chip *tsadc = thermal->chip; |
| int error; |
| |
| tsadc->set_tshut_mode(id, thermal->regs, thermal->tshut_mode); |
| tsadc->set_tshut_temp(tsadc->table, id, thermal->regs, |
| thermal->tshut_temp); |
| |
| sensor->thermal = thermal; |
| sensor->id = id; |
| sensor->tzd = devm_thermal_zone_of_sensor_register(&pdev->dev, id, |
| sensor, &rockchip_of_thermal_ops); |
| if (IS_ERR(sensor->tzd)) { |
| error = PTR_ERR(sensor->tzd); |
| dev_err(&pdev->dev, "failed to register sensor %d: %d\n", |
| id, error); |
| return error; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Reset TSADC Controller, reset all tsadc registers. |
| */ |
| static void rockchip_thermal_reset_controller(struct reset_control *reset) |
| { |
| reset_control_assert(reset); |
| usleep_range(10, 20); |
| reset_control_deassert(reset); |
| } |
| |
| static int rockchip_thermal_probe(struct platform_device *pdev) |
| { |
| struct device_node *np = pdev->dev.of_node; |
| struct rockchip_thermal_data *thermal; |
| const struct of_device_id *match; |
| struct resource *res; |
| int irq; |
| int i; |
| int error; |
| |
| match = of_match_node(of_rockchip_thermal_match, np); |
| if (!match) |
| return -ENXIO; |
| |
| irq = platform_get_irq(pdev, 0); |
| if (irq < 0) { |
| dev_err(&pdev->dev, "no irq resource?\n"); |
| return -EINVAL; |
| } |
| |
| thermal = devm_kzalloc(&pdev->dev, sizeof(struct rockchip_thermal_data), |
| GFP_KERNEL); |
| if (!thermal) |
| return -ENOMEM; |
| |
| thermal->pdev = pdev; |
| |
| thermal->chip = (const struct rockchip_tsadc_chip *)match->data; |
| if (!thermal->chip) |
| return -EINVAL; |
| |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| thermal->regs = devm_ioremap_resource(&pdev->dev, res); |
| if (IS_ERR(thermal->regs)) |
| return PTR_ERR(thermal->regs); |
| |
| thermal->reset = devm_reset_control_get(&pdev->dev, "tsadc-apb"); |
| if (IS_ERR(thermal->reset)) { |
| error = PTR_ERR(thermal->reset); |
| dev_err(&pdev->dev, "failed to get tsadc reset: %d\n", error); |
| return error; |
| } |
| |
| thermal->clk = devm_clk_get(&pdev->dev, "tsadc"); |
| if (IS_ERR(thermal->clk)) { |
| error = PTR_ERR(thermal->clk); |
| dev_err(&pdev->dev, "failed to get tsadc clock: %d\n", error); |
| return error; |
| } |
| |
| thermal->pclk = devm_clk_get(&pdev->dev, "apb_pclk"); |
| if (IS_ERR(thermal->pclk)) { |
| error = PTR_ERR(thermal->pclk); |
| dev_err(&pdev->dev, "failed to get apb_pclk clock: %d\n", |
| error); |
| return error; |
| } |
| |
| error = clk_prepare_enable(thermal->clk); |
| if (error) { |
| dev_err(&pdev->dev, "failed to enable converter clock: %d\n", |
| error); |
| return error; |
| } |
| |
| error = clk_prepare_enable(thermal->pclk); |
| if (error) { |
| dev_err(&pdev->dev, "failed to enable pclk: %d\n", error); |
| goto err_disable_clk; |
| } |
| |
| rockchip_thermal_reset_controller(thermal->reset); |
| |
| error = rockchip_configure_from_dt(&pdev->dev, np, thermal); |
| if (error) { |
| dev_err(&pdev->dev, "failed to parse device tree data: %d\n", |
| error); |
| goto err_disable_pclk; |
| } |
| |
| thermal->chip->initialize(thermal->grf, thermal->regs, |
| thermal->tshut_polarity); |
| |
| for (i = 0; i < thermal->chip->chn_num; i++) { |
| error = rockchip_thermal_register_sensor(pdev, thermal, |
| &thermal->sensors[i], |
| thermal->chip->chn_id[i]); |
| if (error) { |
| dev_err(&pdev->dev, |
| "failed to register sensor[%d] : error = %d\n", |
| i, error); |
| goto err_disable_pclk; |
| } |
| } |
| |
| error = devm_request_threaded_irq(&pdev->dev, irq, NULL, |
| &rockchip_thermal_alarm_irq_thread, |
| IRQF_ONESHOT, |
| "rockchip_thermal", thermal); |
| if (error) { |
| dev_err(&pdev->dev, |
| "failed to request tsadc irq: %d\n", error); |
| goto err_disable_pclk; |
| } |
| |
| thermal->chip->control(thermal->regs, true); |
| |
| for (i = 0; i < thermal->chip->chn_num; i++) |
| rockchip_thermal_toggle_sensor(&thermal->sensors[i], true); |
| |
| platform_set_drvdata(pdev, thermal); |
| |
| return 0; |
| |
| err_disable_pclk: |
| clk_disable_unprepare(thermal->pclk); |
| err_disable_clk: |
| clk_disable_unprepare(thermal->clk); |
| |
| return error; |
| } |
| |
| static int rockchip_thermal_remove(struct platform_device *pdev) |
| { |
| struct rockchip_thermal_data *thermal = platform_get_drvdata(pdev); |
| int i; |
| |
| for (i = 0; i < thermal->chip->chn_num; i++) { |
| struct rockchip_thermal_sensor *sensor = &thermal->sensors[i]; |
| |
| rockchip_thermal_toggle_sensor(sensor, false); |
| } |
| |
| thermal->chip->control(thermal->regs, false); |
| |
| clk_disable_unprepare(thermal->pclk); |
| clk_disable_unprepare(thermal->clk); |
| |
| return 0; |
| } |
| |
| static int __maybe_unused rockchip_thermal_suspend(struct device *dev) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct rockchip_thermal_data *thermal = platform_get_drvdata(pdev); |
| int i; |
| |
| for (i = 0; i < thermal->chip->chn_num; i++) |
| rockchip_thermal_toggle_sensor(&thermal->sensors[i], false); |
| |
| thermal->chip->control(thermal->regs, false); |
| |
| clk_disable(thermal->pclk); |
| clk_disable(thermal->clk); |
| |
| pinctrl_pm_select_sleep_state(dev); |
| |
| return 0; |
| } |
| |
| static int __maybe_unused rockchip_thermal_resume(struct device *dev) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct rockchip_thermal_data *thermal = platform_get_drvdata(pdev); |
| int i; |
| int error; |
| |
| error = clk_enable(thermal->clk); |
| if (error) |
| return error; |
| |
| error = clk_enable(thermal->pclk); |
| if (error) { |
| clk_disable(thermal->clk); |
| return error; |
| } |
| |
| rockchip_thermal_reset_controller(thermal->reset); |
| |
| thermal->chip->initialize(thermal->grf, thermal->regs, |
| thermal->tshut_polarity); |
| |
| for (i = 0; i < thermal->chip->chn_num; i++) { |
| int id = thermal->sensors[i].id; |
| |
| thermal->chip->set_tshut_mode(id, thermal->regs, |
| thermal->tshut_mode); |
| thermal->chip->set_tshut_temp(thermal->chip->table, |
| id, thermal->regs, |
| thermal->tshut_temp); |
| } |
| |
| thermal->chip->control(thermal->regs, true); |
| |
| for (i = 0; i < thermal->chip->chn_num; i++) |
| rockchip_thermal_toggle_sensor(&thermal->sensors[i], true); |
| |
| pinctrl_pm_select_default_state(dev); |
| |
| return 0; |
| } |
| |
| static SIMPLE_DEV_PM_OPS(rockchip_thermal_pm_ops, |
| rockchip_thermal_suspend, rockchip_thermal_resume); |
| |
| static struct platform_driver rockchip_thermal_driver = { |
| .driver = { |
| .name = "rockchip-thermal", |
| .pm = &rockchip_thermal_pm_ops, |
| .of_match_table = of_rockchip_thermal_match, |
| }, |
| .probe = rockchip_thermal_probe, |
| .remove = rockchip_thermal_remove, |
| }; |
| |
| module_platform_driver(rockchip_thermal_driver); |
| |
| MODULE_DESCRIPTION("ROCKCHIP THERMAL Driver"); |
| MODULE_AUTHOR("Rockchip, Inc."); |
| MODULE_LICENSE("GPL v2"); |
| MODULE_ALIAS("platform:rockchip-thermal"); |