18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Marvell EBU Armada SoCs thermal sensor driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2013 Marvell 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci#include <linux/device.h> 88c2ecf20Sopenharmony_ci#include <linux/err.h> 98c2ecf20Sopenharmony_ci#include <linux/io.h> 108c2ecf20Sopenharmony_ci#include <linux/kernel.h> 118c2ecf20Sopenharmony_ci#include <linux/of.h> 128c2ecf20Sopenharmony_ci#include <linux/module.h> 138c2ecf20Sopenharmony_ci#include <linux/delay.h> 148c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 158c2ecf20Sopenharmony_ci#include <linux/of_device.h> 168c2ecf20Sopenharmony_ci#include <linux/thermal.h> 178c2ecf20Sopenharmony_ci#include <linux/iopoll.h> 188c2ecf20Sopenharmony_ci#include <linux/mfd/syscon.h> 198c2ecf20Sopenharmony_ci#include <linux/regmap.h> 208c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci#include "thermal_core.h" 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci/* Thermal Manager Control and Status Register */ 258c2ecf20Sopenharmony_ci#define PMU_TDC0_SW_RST_MASK (0x1 << 1) 268c2ecf20Sopenharmony_ci#define PMU_TM_DISABLE_OFFS 0 278c2ecf20Sopenharmony_ci#define PMU_TM_DISABLE_MASK (0x1 << PMU_TM_DISABLE_OFFS) 288c2ecf20Sopenharmony_ci#define PMU_TDC0_REF_CAL_CNT_OFFS 11 298c2ecf20Sopenharmony_ci#define PMU_TDC0_REF_CAL_CNT_MASK (0x1ff << PMU_TDC0_REF_CAL_CNT_OFFS) 308c2ecf20Sopenharmony_ci#define PMU_TDC0_OTF_CAL_MASK (0x1 << 30) 318c2ecf20Sopenharmony_ci#define PMU_TDC0_START_CAL_MASK (0x1 << 25) 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci#define A375_UNIT_CONTROL_SHIFT 27 348c2ecf20Sopenharmony_ci#define A375_UNIT_CONTROL_MASK 0x7 358c2ecf20Sopenharmony_ci#define A375_READOUT_INVERT BIT(15) 368c2ecf20Sopenharmony_ci#define A375_HW_RESETn BIT(8) 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci/* Errata fields */ 398c2ecf20Sopenharmony_ci#define CONTROL0_TSEN_TC_TRIM_MASK 0x7 408c2ecf20Sopenharmony_ci#define CONTROL0_TSEN_TC_TRIM_VAL 0x3 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci#define CONTROL0_TSEN_START BIT(0) 438c2ecf20Sopenharmony_ci#define CONTROL0_TSEN_RESET BIT(1) 448c2ecf20Sopenharmony_ci#define CONTROL0_TSEN_ENABLE BIT(2) 458c2ecf20Sopenharmony_ci#define CONTROL0_TSEN_AVG_BYPASS BIT(6) 468c2ecf20Sopenharmony_ci#define CONTROL0_TSEN_CHAN_SHIFT 13 478c2ecf20Sopenharmony_ci#define CONTROL0_TSEN_CHAN_MASK 0xF 488c2ecf20Sopenharmony_ci#define CONTROL0_TSEN_OSR_SHIFT 24 498c2ecf20Sopenharmony_ci#define CONTROL0_TSEN_OSR_MAX 0x3 508c2ecf20Sopenharmony_ci#define CONTROL0_TSEN_MODE_SHIFT 30 518c2ecf20Sopenharmony_ci#define CONTROL0_TSEN_MODE_EXTERNAL 0x2 528c2ecf20Sopenharmony_ci#define CONTROL0_TSEN_MODE_MASK 0x3 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci#define CONTROL1_TSEN_AVG_MASK 0x7 558c2ecf20Sopenharmony_ci#define CONTROL1_EXT_TSEN_SW_RESET BIT(7) 568c2ecf20Sopenharmony_ci#define CONTROL1_EXT_TSEN_HW_RESETn BIT(8) 578c2ecf20Sopenharmony_ci#define CONTROL1_TSEN_INT_EN BIT(25) 588c2ecf20Sopenharmony_ci#define CONTROL1_TSEN_SELECT_OFF 21 598c2ecf20Sopenharmony_ci#define CONTROL1_TSEN_SELECT_MASK 0x3 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci#define STATUS_POLL_PERIOD_US 1000 628c2ecf20Sopenharmony_ci#define STATUS_POLL_TIMEOUT_US 100000 638c2ecf20Sopenharmony_ci#define OVERHEAT_INT_POLL_DELAY_MS 1000 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_cistruct armada_thermal_data; 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci/* Marvell EBU Thermal Sensor Dev Structure */ 688c2ecf20Sopenharmony_cistruct armada_thermal_priv { 698c2ecf20Sopenharmony_ci struct device *dev; 708c2ecf20Sopenharmony_ci struct regmap *syscon; 718c2ecf20Sopenharmony_ci char zone_name[THERMAL_NAME_LENGTH]; 728c2ecf20Sopenharmony_ci /* serialize temperature reads/updates */ 738c2ecf20Sopenharmony_ci struct mutex update_lock; 748c2ecf20Sopenharmony_ci struct armada_thermal_data *data; 758c2ecf20Sopenharmony_ci struct thermal_zone_device *overheat_sensor; 768c2ecf20Sopenharmony_ci int interrupt_source; 778c2ecf20Sopenharmony_ci int current_channel; 788c2ecf20Sopenharmony_ci long current_threshold; 798c2ecf20Sopenharmony_ci long current_hysteresis; 808c2ecf20Sopenharmony_ci}; 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_cistruct armada_thermal_data { 838c2ecf20Sopenharmony_ci /* Initialize the thermal IC */ 848c2ecf20Sopenharmony_ci void (*init)(struct platform_device *pdev, 858c2ecf20Sopenharmony_ci struct armada_thermal_priv *priv); 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci /* Formula coeficients: temp = (b - m * reg) / div */ 888c2ecf20Sopenharmony_ci s64 coef_b; 898c2ecf20Sopenharmony_ci s64 coef_m; 908c2ecf20Sopenharmony_ci u32 coef_div; 918c2ecf20Sopenharmony_ci bool inverted; 928c2ecf20Sopenharmony_ci bool signed_sample; 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci /* Register shift and mask to access the sensor temperature */ 958c2ecf20Sopenharmony_ci unsigned int temp_shift; 968c2ecf20Sopenharmony_ci unsigned int temp_mask; 978c2ecf20Sopenharmony_ci unsigned int thresh_shift; 988c2ecf20Sopenharmony_ci unsigned int hyst_shift; 998c2ecf20Sopenharmony_ci unsigned int hyst_mask; 1008c2ecf20Sopenharmony_ci u32 is_valid_bit; 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci /* Syscon access */ 1038c2ecf20Sopenharmony_ci unsigned int syscon_control0_off; 1048c2ecf20Sopenharmony_ci unsigned int syscon_control1_off; 1058c2ecf20Sopenharmony_ci unsigned int syscon_status_off; 1068c2ecf20Sopenharmony_ci unsigned int dfx_irq_cause_off; 1078c2ecf20Sopenharmony_ci unsigned int dfx_irq_mask_off; 1088c2ecf20Sopenharmony_ci unsigned int dfx_overheat_irq; 1098c2ecf20Sopenharmony_ci unsigned int dfx_server_irq_mask_off; 1108c2ecf20Sopenharmony_ci unsigned int dfx_server_irq_en; 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci /* One sensor is in the thermal IC, the others are in the CPUs if any */ 1138c2ecf20Sopenharmony_ci unsigned int cpu_nr; 1148c2ecf20Sopenharmony_ci}; 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_cistruct armada_drvdata { 1178c2ecf20Sopenharmony_ci enum drvtype { 1188c2ecf20Sopenharmony_ci LEGACY, 1198c2ecf20Sopenharmony_ci SYSCON 1208c2ecf20Sopenharmony_ci } type; 1218c2ecf20Sopenharmony_ci union { 1228c2ecf20Sopenharmony_ci struct armada_thermal_priv *priv; 1238c2ecf20Sopenharmony_ci struct thermal_zone_device *tz; 1248c2ecf20Sopenharmony_ci } data; 1258c2ecf20Sopenharmony_ci}; 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci/* 1288c2ecf20Sopenharmony_ci * struct armada_thermal_sensor - hold the information of one thermal sensor 1298c2ecf20Sopenharmony_ci * @thermal: pointer to the local private structure 1308c2ecf20Sopenharmony_ci * @tzd: pointer to the thermal zone device 1318c2ecf20Sopenharmony_ci * @id: identifier of the thermal sensor 1328c2ecf20Sopenharmony_ci */ 1338c2ecf20Sopenharmony_cistruct armada_thermal_sensor { 1348c2ecf20Sopenharmony_ci struct armada_thermal_priv *priv; 1358c2ecf20Sopenharmony_ci int id; 1368c2ecf20Sopenharmony_ci}; 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_cistatic void armadaxp_init(struct platform_device *pdev, 1398c2ecf20Sopenharmony_ci struct armada_thermal_priv *priv) 1408c2ecf20Sopenharmony_ci{ 1418c2ecf20Sopenharmony_ci struct armada_thermal_data *data = priv->data; 1428c2ecf20Sopenharmony_ci u32 reg; 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ci regmap_read(priv->syscon, data->syscon_control1_off, ®); 1458c2ecf20Sopenharmony_ci reg |= PMU_TDC0_OTF_CAL_MASK; 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci /* Reference calibration value */ 1488c2ecf20Sopenharmony_ci reg &= ~PMU_TDC0_REF_CAL_CNT_MASK; 1498c2ecf20Sopenharmony_ci reg |= (0xf1 << PMU_TDC0_REF_CAL_CNT_OFFS); 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci /* Reset the sensor */ 1528c2ecf20Sopenharmony_ci reg |= PMU_TDC0_SW_RST_MASK; 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci regmap_write(priv->syscon, data->syscon_control1_off, reg); 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci reg &= ~PMU_TDC0_SW_RST_MASK; 1578c2ecf20Sopenharmony_ci regmap_write(priv->syscon, data->syscon_control1_off, reg); 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci /* Enable the sensor */ 1608c2ecf20Sopenharmony_ci regmap_read(priv->syscon, data->syscon_status_off, ®); 1618c2ecf20Sopenharmony_ci reg &= ~PMU_TM_DISABLE_MASK; 1628c2ecf20Sopenharmony_ci regmap_write(priv->syscon, data->syscon_status_off, reg); 1638c2ecf20Sopenharmony_ci} 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_cistatic void armada370_init(struct platform_device *pdev, 1668c2ecf20Sopenharmony_ci struct armada_thermal_priv *priv) 1678c2ecf20Sopenharmony_ci{ 1688c2ecf20Sopenharmony_ci struct armada_thermal_data *data = priv->data; 1698c2ecf20Sopenharmony_ci u32 reg; 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci regmap_read(priv->syscon, data->syscon_control1_off, ®); 1728c2ecf20Sopenharmony_ci reg |= PMU_TDC0_OTF_CAL_MASK; 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci /* Reference calibration value */ 1758c2ecf20Sopenharmony_ci reg &= ~PMU_TDC0_REF_CAL_CNT_MASK; 1768c2ecf20Sopenharmony_ci reg |= (0xf1 << PMU_TDC0_REF_CAL_CNT_OFFS); 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci /* Reset the sensor */ 1798c2ecf20Sopenharmony_ci reg &= ~PMU_TDC0_START_CAL_MASK; 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci regmap_write(priv->syscon, data->syscon_control1_off, reg); 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci msleep(10); 1848c2ecf20Sopenharmony_ci} 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_cistatic void armada375_init(struct platform_device *pdev, 1878c2ecf20Sopenharmony_ci struct armada_thermal_priv *priv) 1888c2ecf20Sopenharmony_ci{ 1898c2ecf20Sopenharmony_ci struct armada_thermal_data *data = priv->data; 1908c2ecf20Sopenharmony_ci u32 reg; 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci regmap_read(priv->syscon, data->syscon_control1_off, ®); 1938c2ecf20Sopenharmony_ci reg &= ~(A375_UNIT_CONTROL_MASK << A375_UNIT_CONTROL_SHIFT); 1948c2ecf20Sopenharmony_ci reg &= ~A375_READOUT_INVERT; 1958c2ecf20Sopenharmony_ci reg &= ~A375_HW_RESETn; 1968c2ecf20Sopenharmony_ci regmap_write(priv->syscon, data->syscon_control1_off, reg); 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci msleep(20); 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci reg |= A375_HW_RESETn; 2018c2ecf20Sopenharmony_ci regmap_write(priv->syscon, data->syscon_control1_off, reg); 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci msleep(50); 2048c2ecf20Sopenharmony_ci} 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_cistatic int armada_wait_sensor_validity(struct armada_thermal_priv *priv) 2078c2ecf20Sopenharmony_ci{ 2088c2ecf20Sopenharmony_ci u32 reg; 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci return regmap_read_poll_timeout(priv->syscon, 2118c2ecf20Sopenharmony_ci priv->data->syscon_status_off, reg, 2128c2ecf20Sopenharmony_ci reg & priv->data->is_valid_bit, 2138c2ecf20Sopenharmony_ci STATUS_POLL_PERIOD_US, 2148c2ecf20Sopenharmony_ci STATUS_POLL_TIMEOUT_US); 2158c2ecf20Sopenharmony_ci} 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_cistatic void armada380_init(struct platform_device *pdev, 2188c2ecf20Sopenharmony_ci struct armada_thermal_priv *priv) 2198c2ecf20Sopenharmony_ci{ 2208c2ecf20Sopenharmony_ci struct armada_thermal_data *data = priv->data; 2218c2ecf20Sopenharmony_ci u32 reg; 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci /* Disable the HW/SW reset */ 2248c2ecf20Sopenharmony_ci regmap_read(priv->syscon, data->syscon_control1_off, ®); 2258c2ecf20Sopenharmony_ci reg |= CONTROL1_EXT_TSEN_HW_RESETn; 2268c2ecf20Sopenharmony_ci reg &= ~CONTROL1_EXT_TSEN_SW_RESET; 2278c2ecf20Sopenharmony_ci regmap_write(priv->syscon, data->syscon_control1_off, reg); 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci /* Set Tsen Tc Trim to correct default value (errata #132698) */ 2308c2ecf20Sopenharmony_ci regmap_read(priv->syscon, data->syscon_control0_off, ®); 2318c2ecf20Sopenharmony_ci reg &= ~CONTROL0_TSEN_TC_TRIM_MASK; 2328c2ecf20Sopenharmony_ci reg |= CONTROL0_TSEN_TC_TRIM_VAL; 2338c2ecf20Sopenharmony_ci regmap_write(priv->syscon, data->syscon_control0_off, reg); 2348c2ecf20Sopenharmony_ci} 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_cistatic void armada_ap806_init(struct platform_device *pdev, 2378c2ecf20Sopenharmony_ci struct armada_thermal_priv *priv) 2388c2ecf20Sopenharmony_ci{ 2398c2ecf20Sopenharmony_ci struct armada_thermal_data *data = priv->data; 2408c2ecf20Sopenharmony_ci u32 reg; 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_ci regmap_read(priv->syscon, data->syscon_control0_off, ®); 2438c2ecf20Sopenharmony_ci reg &= ~CONTROL0_TSEN_RESET; 2448c2ecf20Sopenharmony_ci reg |= CONTROL0_TSEN_START | CONTROL0_TSEN_ENABLE; 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ci /* Sample every ~2ms */ 2478c2ecf20Sopenharmony_ci reg |= CONTROL0_TSEN_OSR_MAX << CONTROL0_TSEN_OSR_SHIFT; 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci /* Enable average (2 samples by default) */ 2508c2ecf20Sopenharmony_ci reg &= ~CONTROL0_TSEN_AVG_BYPASS; 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_ci regmap_write(priv->syscon, data->syscon_control0_off, reg); 2538c2ecf20Sopenharmony_ci} 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_cistatic void armada_cp110_init(struct platform_device *pdev, 2568c2ecf20Sopenharmony_ci struct armada_thermal_priv *priv) 2578c2ecf20Sopenharmony_ci{ 2588c2ecf20Sopenharmony_ci struct armada_thermal_data *data = priv->data; 2598c2ecf20Sopenharmony_ci u32 reg; 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_ci armada380_init(pdev, priv); 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_ci /* Sample every ~2ms */ 2648c2ecf20Sopenharmony_ci regmap_read(priv->syscon, data->syscon_control0_off, ®); 2658c2ecf20Sopenharmony_ci reg |= CONTROL0_TSEN_OSR_MAX << CONTROL0_TSEN_OSR_SHIFT; 2668c2ecf20Sopenharmony_ci regmap_write(priv->syscon, data->syscon_control0_off, reg); 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_ci /* Average the output value over 2^1 = 2 samples */ 2698c2ecf20Sopenharmony_ci regmap_read(priv->syscon, data->syscon_control1_off, ®); 2708c2ecf20Sopenharmony_ci reg &= ~CONTROL1_TSEN_AVG_MASK; 2718c2ecf20Sopenharmony_ci reg |= 1; 2728c2ecf20Sopenharmony_ci regmap_write(priv->syscon, data->syscon_control1_off, reg); 2738c2ecf20Sopenharmony_ci} 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_cistatic bool armada_is_valid(struct armada_thermal_priv *priv) 2768c2ecf20Sopenharmony_ci{ 2778c2ecf20Sopenharmony_ci u32 reg; 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_ci if (!priv->data->is_valid_bit) 2808c2ecf20Sopenharmony_ci return true; 2818c2ecf20Sopenharmony_ci 2828c2ecf20Sopenharmony_ci regmap_read(priv->syscon, priv->data->syscon_status_off, ®); 2838c2ecf20Sopenharmony_ci 2848c2ecf20Sopenharmony_ci return reg & priv->data->is_valid_bit; 2858c2ecf20Sopenharmony_ci} 2868c2ecf20Sopenharmony_ci 2878c2ecf20Sopenharmony_cistatic void armada_enable_overheat_interrupt(struct armada_thermal_priv *priv) 2888c2ecf20Sopenharmony_ci{ 2898c2ecf20Sopenharmony_ci struct armada_thermal_data *data = priv->data; 2908c2ecf20Sopenharmony_ci u32 reg; 2918c2ecf20Sopenharmony_ci 2928c2ecf20Sopenharmony_ci /* Clear DFX temperature IRQ cause */ 2938c2ecf20Sopenharmony_ci regmap_read(priv->syscon, data->dfx_irq_cause_off, ®); 2948c2ecf20Sopenharmony_ci 2958c2ecf20Sopenharmony_ci /* Enable DFX Temperature IRQ */ 2968c2ecf20Sopenharmony_ci regmap_read(priv->syscon, data->dfx_irq_mask_off, ®); 2978c2ecf20Sopenharmony_ci reg |= data->dfx_overheat_irq; 2988c2ecf20Sopenharmony_ci regmap_write(priv->syscon, data->dfx_irq_mask_off, reg); 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_ci /* Enable DFX server IRQ */ 3018c2ecf20Sopenharmony_ci regmap_read(priv->syscon, data->dfx_server_irq_mask_off, ®); 3028c2ecf20Sopenharmony_ci reg |= data->dfx_server_irq_en; 3038c2ecf20Sopenharmony_ci regmap_write(priv->syscon, data->dfx_server_irq_mask_off, reg); 3048c2ecf20Sopenharmony_ci 3058c2ecf20Sopenharmony_ci /* Enable overheat interrupt */ 3068c2ecf20Sopenharmony_ci regmap_read(priv->syscon, data->syscon_control1_off, ®); 3078c2ecf20Sopenharmony_ci reg |= CONTROL1_TSEN_INT_EN; 3088c2ecf20Sopenharmony_ci regmap_write(priv->syscon, data->syscon_control1_off, reg); 3098c2ecf20Sopenharmony_ci} 3108c2ecf20Sopenharmony_ci 3118c2ecf20Sopenharmony_cistatic void __maybe_unused 3128c2ecf20Sopenharmony_ciarmada_disable_overheat_interrupt(struct armada_thermal_priv *priv) 3138c2ecf20Sopenharmony_ci{ 3148c2ecf20Sopenharmony_ci struct armada_thermal_data *data = priv->data; 3158c2ecf20Sopenharmony_ci u32 reg; 3168c2ecf20Sopenharmony_ci 3178c2ecf20Sopenharmony_ci regmap_read(priv->syscon, data->syscon_control1_off, ®); 3188c2ecf20Sopenharmony_ci reg &= ~CONTROL1_TSEN_INT_EN; 3198c2ecf20Sopenharmony_ci regmap_write(priv->syscon, data->syscon_control1_off, reg); 3208c2ecf20Sopenharmony_ci} 3218c2ecf20Sopenharmony_ci 3228c2ecf20Sopenharmony_ci/* There is currently no board with more than one sensor per channel */ 3238c2ecf20Sopenharmony_cistatic int armada_select_channel(struct armada_thermal_priv *priv, int channel) 3248c2ecf20Sopenharmony_ci{ 3258c2ecf20Sopenharmony_ci struct armada_thermal_data *data = priv->data; 3268c2ecf20Sopenharmony_ci u32 ctrl0; 3278c2ecf20Sopenharmony_ci 3288c2ecf20Sopenharmony_ci if (channel < 0 || channel > priv->data->cpu_nr) 3298c2ecf20Sopenharmony_ci return -EINVAL; 3308c2ecf20Sopenharmony_ci 3318c2ecf20Sopenharmony_ci if (priv->current_channel == channel) 3328c2ecf20Sopenharmony_ci return 0; 3338c2ecf20Sopenharmony_ci 3348c2ecf20Sopenharmony_ci /* Stop the measurements */ 3358c2ecf20Sopenharmony_ci regmap_read(priv->syscon, data->syscon_control0_off, &ctrl0); 3368c2ecf20Sopenharmony_ci ctrl0 &= ~CONTROL0_TSEN_START; 3378c2ecf20Sopenharmony_ci regmap_write(priv->syscon, data->syscon_control0_off, ctrl0); 3388c2ecf20Sopenharmony_ci 3398c2ecf20Sopenharmony_ci /* Reset the mode, internal sensor will be automatically selected */ 3408c2ecf20Sopenharmony_ci ctrl0 &= ~(CONTROL0_TSEN_MODE_MASK << CONTROL0_TSEN_MODE_SHIFT); 3418c2ecf20Sopenharmony_ci 3428c2ecf20Sopenharmony_ci /* Other channels are external and should be selected accordingly */ 3438c2ecf20Sopenharmony_ci if (channel) { 3448c2ecf20Sopenharmony_ci /* Change the mode to external */ 3458c2ecf20Sopenharmony_ci ctrl0 |= CONTROL0_TSEN_MODE_EXTERNAL << 3468c2ecf20Sopenharmony_ci CONTROL0_TSEN_MODE_SHIFT; 3478c2ecf20Sopenharmony_ci /* Select the sensor */ 3488c2ecf20Sopenharmony_ci ctrl0 &= ~(CONTROL0_TSEN_CHAN_MASK << CONTROL0_TSEN_CHAN_SHIFT); 3498c2ecf20Sopenharmony_ci ctrl0 |= (channel - 1) << CONTROL0_TSEN_CHAN_SHIFT; 3508c2ecf20Sopenharmony_ci } 3518c2ecf20Sopenharmony_ci 3528c2ecf20Sopenharmony_ci /* Actually set the mode/channel */ 3538c2ecf20Sopenharmony_ci regmap_write(priv->syscon, data->syscon_control0_off, ctrl0); 3548c2ecf20Sopenharmony_ci priv->current_channel = channel; 3558c2ecf20Sopenharmony_ci 3568c2ecf20Sopenharmony_ci /* Re-start the measurements */ 3578c2ecf20Sopenharmony_ci ctrl0 |= CONTROL0_TSEN_START; 3588c2ecf20Sopenharmony_ci regmap_write(priv->syscon, data->syscon_control0_off, ctrl0); 3598c2ecf20Sopenharmony_ci 3608c2ecf20Sopenharmony_ci /* 3618c2ecf20Sopenharmony_ci * The IP has a latency of ~15ms, so after updating the selected source, 3628c2ecf20Sopenharmony_ci * we must absolutely wait for the sensor validity bit to ensure we read 3638c2ecf20Sopenharmony_ci * actual data. 3648c2ecf20Sopenharmony_ci */ 3658c2ecf20Sopenharmony_ci if (armada_wait_sensor_validity(priv)) { 3668c2ecf20Sopenharmony_ci dev_err(priv->dev, 3678c2ecf20Sopenharmony_ci "Temperature sensor reading not valid\n"); 3688c2ecf20Sopenharmony_ci return -EIO; 3698c2ecf20Sopenharmony_ci } 3708c2ecf20Sopenharmony_ci 3718c2ecf20Sopenharmony_ci return 0; 3728c2ecf20Sopenharmony_ci} 3738c2ecf20Sopenharmony_ci 3748c2ecf20Sopenharmony_cistatic int armada_read_sensor(struct armada_thermal_priv *priv, int *temp) 3758c2ecf20Sopenharmony_ci{ 3768c2ecf20Sopenharmony_ci u32 reg, div; 3778c2ecf20Sopenharmony_ci s64 sample, b, m; 3788c2ecf20Sopenharmony_ci 3798c2ecf20Sopenharmony_ci regmap_read(priv->syscon, priv->data->syscon_status_off, ®); 3808c2ecf20Sopenharmony_ci reg = (reg >> priv->data->temp_shift) & priv->data->temp_mask; 3818c2ecf20Sopenharmony_ci if (priv->data->signed_sample) 3828c2ecf20Sopenharmony_ci /* The most significant bit is the sign bit */ 3838c2ecf20Sopenharmony_ci sample = sign_extend32(reg, fls(priv->data->temp_mask) - 1); 3848c2ecf20Sopenharmony_ci else 3858c2ecf20Sopenharmony_ci sample = reg; 3868c2ecf20Sopenharmony_ci 3878c2ecf20Sopenharmony_ci /* Get formula coeficients */ 3888c2ecf20Sopenharmony_ci b = priv->data->coef_b; 3898c2ecf20Sopenharmony_ci m = priv->data->coef_m; 3908c2ecf20Sopenharmony_ci div = priv->data->coef_div; 3918c2ecf20Sopenharmony_ci 3928c2ecf20Sopenharmony_ci if (priv->data->inverted) 3938c2ecf20Sopenharmony_ci *temp = div_s64((m * sample) - b, div); 3948c2ecf20Sopenharmony_ci else 3958c2ecf20Sopenharmony_ci *temp = div_s64(b - (m * sample), div); 3968c2ecf20Sopenharmony_ci 3978c2ecf20Sopenharmony_ci return 0; 3988c2ecf20Sopenharmony_ci} 3998c2ecf20Sopenharmony_ci 4008c2ecf20Sopenharmony_cistatic int armada_get_temp_legacy(struct thermal_zone_device *thermal, 4018c2ecf20Sopenharmony_ci int *temp) 4028c2ecf20Sopenharmony_ci{ 4038c2ecf20Sopenharmony_ci struct armada_thermal_priv *priv = thermal->devdata; 4048c2ecf20Sopenharmony_ci int ret; 4058c2ecf20Sopenharmony_ci 4068c2ecf20Sopenharmony_ci /* Valid check */ 4078c2ecf20Sopenharmony_ci if (!armada_is_valid(priv)) { 4088c2ecf20Sopenharmony_ci dev_err(priv->dev, 4098c2ecf20Sopenharmony_ci "Temperature sensor reading not valid\n"); 4108c2ecf20Sopenharmony_ci return -EIO; 4118c2ecf20Sopenharmony_ci } 4128c2ecf20Sopenharmony_ci 4138c2ecf20Sopenharmony_ci /* Do the actual reading */ 4148c2ecf20Sopenharmony_ci ret = armada_read_sensor(priv, temp); 4158c2ecf20Sopenharmony_ci 4168c2ecf20Sopenharmony_ci return ret; 4178c2ecf20Sopenharmony_ci} 4188c2ecf20Sopenharmony_ci 4198c2ecf20Sopenharmony_cistatic struct thermal_zone_device_ops legacy_ops = { 4208c2ecf20Sopenharmony_ci .get_temp = armada_get_temp_legacy, 4218c2ecf20Sopenharmony_ci}; 4228c2ecf20Sopenharmony_ci 4238c2ecf20Sopenharmony_cistatic int armada_get_temp(void *_sensor, int *temp) 4248c2ecf20Sopenharmony_ci{ 4258c2ecf20Sopenharmony_ci struct armada_thermal_sensor *sensor = _sensor; 4268c2ecf20Sopenharmony_ci struct armada_thermal_priv *priv = sensor->priv; 4278c2ecf20Sopenharmony_ci int ret; 4288c2ecf20Sopenharmony_ci 4298c2ecf20Sopenharmony_ci mutex_lock(&priv->update_lock); 4308c2ecf20Sopenharmony_ci 4318c2ecf20Sopenharmony_ci /* Select the desired channel */ 4328c2ecf20Sopenharmony_ci ret = armada_select_channel(priv, sensor->id); 4338c2ecf20Sopenharmony_ci if (ret) 4348c2ecf20Sopenharmony_ci goto unlock_mutex; 4358c2ecf20Sopenharmony_ci 4368c2ecf20Sopenharmony_ci /* Do the actual reading */ 4378c2ecf20Sopenharmony_ci ret = armada_read_sensor(priv, temp); 4388c2ecf20Sopenharmony_ci if (ret) 4398c2ecf20Sopenharmony_ci goto unlock_mutex; 4408c2ecf20Sopenharmony_ci 4418c2ecf20Sopenharmony_ci /* 4428c2ecf20Sopenharmony_ci * Select back the interrupt source channel from which a potential 4438c2ecf20Sopenharmony_ci * critical trip point has been set. 4448c2ecf20Sopenharmony_ci */ 4458c2ecf20Sopenharmony_ci ret = armada_select_channel(priv, priv->interrupt_source); 4468c2ecf20Sopenharmony_ci 4478c2ecf20Sopenharmony_ciunlock_mutex: 4488c2ecf20Sopenharmony_ci mutex_unlock(&priv->update_lock); 4498c2ecf20Sopenharmony_ci 4508c2ecf20Sopenharmony_ci return ret; 4518c2ecf20Sopenharmony_ci} 4528c2ecf20Sopenharmony_ci 4538c2ecf20Sopenharmony_cistatic const struct thermal_zone_of_device_ops of_ops = { 4548c2ecf20Sopenharmony_ci .get_temp = armada_get_temp, 4558c2ecf20Sopenharmony_ci}; 4568c2ecf20Sopenharmony_ci 4578c2ecf20Sopenharmony_cistatic unsigned int armada_mc_to_reg_temp(struct armada_thermal_data *data, 4588c2ecf20Sopenharmony_ci unsigned int temp_mc) 4598c2ecf20Sopenharmony_ci{ 4608c2ecf20Sopenharmony_ci s64 b = data->coef_b; 4618c2ecf20Sopenharmony_ci s64 m = data->coef_m; 4628c2ecf20Sopenharmony_ci s64 div = data->coef_div; 4638c2ecf20Sopenharmony_ci unsigned int sample; 4648c2ecf20Sopenharmony_ci 4658c2ecf20Sopenharmony_ci if (data->inverted) 4668c2ecf20Sopenharmony_ci sample = div_s64(((temp_mc * div) + b), m); 4678c2ecf20Sopenharmony_ci else 4688c2ecf20Sopenharmony_ci sample = div_s64((b - (temp_mc * div)), m); 4698c2ecf20Sopenharmony_ci 4708c2ecf20Sopenharmony_ci return sample & data->temp_mask; 4718c2ecf20Sopenharmony_ci} 4728c2ecf20Sopenharmony_ci 4738c2ecf20Sopenharmony_ci/* 4748c2ecf20Sopenharmony_ci * The documentation states: 4758c2ecf20Sopenharmony_ci * high/low watermark = threshold +/- 0.4761 * 2^(hysteresis + 2) 4768c2ecf20Sopenharmony_ci * which is the mathematical derivation for: 4778c2ecf20Sopenharmony_ci * 0x0 <=> 1.9°C, 0x1 <=> 3.8°C, 0x2 <=> 7.6°C, 0x3 <=> 15.2°C 4788c2ecf20Sopenharmony_ci */ 4798c2ecf20Sopenharmony_cistatic unsigned int hyst_levels_mc[] = {1900, 3800, 7600, 15200}; 4808c2ecf20Sopenharmony_ci 4818c2ecf20Sopenharmony_cistatic unsigned int armada_mc_to_reg_hyst(struct armada_thermal_data *data, 4828c2ecf20Sopenharmony_ci unsigned int hyst_mc) 4838c2ecf20Sopenharmony_ci{ 4848c2ecf20Sopenharmony_ci int i; 4858c2ecf20Sopenharmony_ci 4868c2ecf20Sopenharmony_ci /* 4878c2ecf20Sopenharmony_ci * We will always take the smallest possible hysteresis to avoid risking 4888c2ecf20Sopenharmony_ci * the hardware integrity by enlarging the threshold by +8°C in the 4898c2ecf20Sopenharmony_ci * worst case. 4908c2ecf20Sopenharmony_ci */ 4918c2ecf20Sopenharmony_ci for (i = ARRAY_SIZE(hyst_levels_mc) - 1; i > 0; i--) 4928c2ecf20Sopenharmony_ci if (hyst_mc >= hyst_levels_mc[i]) 4938c2ecf20Sopenharmony_ci break; 4948c2ecf20Sopenharmony_ci 4958c2ecf20Sopenharmony_ci return i & data->hyst_mask; 4968c2ecf20Sopenharmony_ci} 4978c2ecf20Sopenharmony_ci 4988c2ecf20Sopenharmony_cistatic void armada_set_overheat_thresholds(struct armada_thermal_priv *priv, 4998c2ecf20Sopenharmony_ci int thresh_mc, int hyst_mc) 5008c2ecf20Sopenharmony_ci{ 5018c2ecf20Sopenharmony_ci struct armada_thermal_data *data = priv->data; 5028c2ecf20Sopenharmony_ci unsigned int threshold = armada_mc_to_reg_temp(data, thresh_mc); 5038c2ecf20Sopenharmony_ci unsigned int hysteresis = armada_mc_to_reg_hyst(data, hyst_mc); 5048c2ecf20Sopenharmony_ci u32 ctrl1; 5058c2ecf20Sopenharmony_ci 5068c2ecf20Sopenharmony_ci regmap_read(priv->syscon, data->syscon_control1_off, &ctrl1); 5078c2ecf20Sopenharmony_ci 5088c2ecf20Sopenharmony_ci /* Set Threshold */ 5098c2ecf20Sopenharmony_ci if (thresh_mc >= 0) { 5108c2ecf20Sopenharmony_ci ctrl1 &= ~(data->temp_mask << data->thresh_shift); 5118c2ecf20Sopenharmony_ci ctrl1 |= threshold << data->thresh_shift; 5128c2ecf20Sopenharmony_ci priv->current_threshold = thresh_mc; 5138c2ecf20Sopenharmony_ci } 5148c2ecf20Sopenharmony_ci 5158c2ecf20Sopenharmony_ci /* Set Hysteresis */ 5168c2ecf20Sopenharmony_ci if (hyst_mc >= 0) { 5178c2ecf20Sopenharmony_ci ctrl1 &= ~(data->hyst_mask << data->hyst_shift); 5188c2ecf20Sopenharmony_ci ctrl1 |= hysteresis << data->hyst_shift; 5198c2ecf20Sopenharmony_ci priv->current_hysteresis = hyst_mc; 5208c2ecf20Sopenharmony_ci } 5218c2ecf20Sopenharmony_ci 5228c2ecf20Sopenharmony_ci regmap_write(priv->syscon, data->syscon_control1_off, ctrl1); 5238c2ecf20Sopenharmony_ci} 5248c2ecf20Sopenharmony_ci 5258c2ecf20Sopenharmony_cistatic irqreturn_t armada_overheat_isr(int irq, void *blob) 5268c2ecf20Sopenharmony_ci{ 5278c2ecf20Sopenharmony_ci /* 5288c2ecf20Sopenharmony_ci * Disable the IRQ and continue in thread context (thermal core 5298c2ecf20Sopenharmony_ci * notification and temperature monitoring). 5308c2ecf20Sopenharmony_ci */ 5318c2ecf20Sopenharmony_ci disable_irq_nosync(irq); 5328c2ecf20Sopenharmony_ci 5338c2ecf20Sopenharmony_ci return IRQ_WAKE_THREAD; 5348c2ecf20Sopenharmony_ci} 5358c2ecf20Sopenharmony_ci 5368c2ecf20Sopenharmony_cistatic irqreturn_t armada_overheat_isr_thread(int irq, void *blob) 5378c2ecf20Sopenharmony_ci{ 5388c2ecf20Sopenharmony_ci struct armada_thermal_priv *priv = blob; 5398c2ecf20Sopenharmony_ci int low_threshold = priv->current_threshold - priv->current_hysteresis; 5408c2ecf20Sopenharmony_ci int temperature; 5418c2ecf20Sopenharmony_ci u32 dummy; 5428c2ecf20Sopenharmony_ci int ret; 5438c2ecf20Sopenharmony_ci 5448c2ecf20Sopenharmony_ci /* Notify the core in thread context */ 5458c2ecf20Sopenharmony_ci thermal_zone_device_update(priv->overheat_sensor, 5468c2ecf20Sopenharmony_ci THERMAL_EVENT_UNSPECIFIED); 5478c2ecf20Sopenharmony_ci 5488c2ecf20Sopenharmony_ci /* 5498c2ecf20Sopenharmony_ci * The overheat interrupt must be cleared by reading the DFX interrupt 5508c2ecf20Sopenharmony_ci * cause _after_ the temperature has fallen down to the low threshold. 5518c2ecf20Sopenharmony_ci * Otherwise future interrupts might not be served. 5528c2ecf20Sopenharmony_ci */ 5538c2ecf20Sopenharmony_ci do { 5548c2ecf20Sopenharmony_ci msleep(OVERHEAT_INT_POLL_DELAY_MS); 5558c2ecf20Sopenharmony_ci mutex_lock(&priv->update_lock); 5568c2ecf20Sopenharmony_ci ret = armada_read_sensor(priv, &temperature); 5578c2ecf20Sopenharmony_ci mutex_unlock(&priv->update_lock); 5588c2ecf20Sopenharmony_ci if (ret) 5598c2ecf20Sopenharmony_ci goto enable_irq; 5608c2ecf20Sopenharmony_ci } while (temperature >= low_threshold); 5618c2ecf20Sopenharmony_ci 5628c2ecf20Sopenharmony_ci regmap_read(priv->syscon, priv->data->dfx_irq_cause_off, &dummy); 5638c2ecf20Sopenharmony_ci 5648c2ecf20Sopenharmony_ci /* Notify the thermal core that the temperature is acceptable again */ 5658c2ecf20Sopenharmony_ci thermal_zone_device_update(priv->overheat_sensor, 5668c2ecf20Sopenharmony_ci THERMAL_EVENT_UNSPECIFIED); 5678c2ecf20Sopenharmony_ci 5688c2ecf20Sopenharmony_cienable_irq: 5698c2ecf20Sopenharmony_ci enable_irq(irq); 5708c2ecf20Sopenharmony_ci 5718c2ecf20Sopenharmony_ci return IRQ_HANDLED; 5728c2ecf20Sopenharmony_ci} 5738c2ecf20Sopenharmony_ci 5748c2ecf20Sopenharmony_cistatic const struct armada_thermal_data armadaxp_data = { 5758c2ecf20Sopenharmony_ci .init = armadaxp_init, 5768c2ecf20Sopenharmony_ci .temp_shift = 10, 5778c2ecf20Sopenharmony_ci .temp_mask = 0x1ff, 5788c2ecf20Sopenharmony_ci .coef_b = 3153000000ULL, 5798c2ecf20Sopenharmony_ci .coef_m = 10000000ULL, 5808c2ecf20Sopenharmony_ci .coef_div = 13825, 5818c2ecf20Sopenharmony_ci .syscon_status_off = 0xb0, 5828c2ecf20Sopenharmony_ci .syscon_control1_off = 0x2d0, 5838c2ecf20Sopenharmony_ci}; 5848c2ecf20Sopenharmony_ci 5858c2ecf20Sopenharmony_cistatic const struct armada_thermal_data armada370_data = { 5868c2ecf20Sopenharmony_ci .init = armada370_init, 5878c2ecf20Sopenharmony_ci .is_valid_bit = BIT(9), 5888c2ecf20Sopenharmony_ci .temp_shift = 10, 5898c2ecf20Sopenharmony_ci .temp_mask = 0x1ff, 5908c2ecf20Sopenharmony_ci .coef_b = 3153000000ULL, 5918c2ecf20Sopenharmony_ci .coef_m = 10000000ULL, 5928c2ecf20Sopenharmony_ci .coef_div = 13825, 5938c2ecf20Sopenharmony_ci .syscon_status_off = 0x0, 5948c2ecf20Sopenharmony_ci .syscon_control1_off = 0x4, 5958c2ecf20Sopenharmony_ci}; 5968c2ecf20Sopenharmony_ci 5978c2ecf20Sopenharmony_cistatic const struct armada_thermal_data armada375_data = { 5988c2ecf20Sopenharmony_ci .init = armada375_init, 5998c2ecf20Sopenharmony_ci .is_valid_bit = BIT(10), 6008c2ecf20Sopenharmony_ci .temp_shift = 0, 6018c2ecf20Sopenharmony_ci .temp_mask = 0x1ff, 6028c2ecf20Sopenharmony_ci .coef_b = 3171900000ULL, 6038c2ecf20Sopenharmony_ci .coef_m = 10000000ULL, 6048c2ecf20Sopenharmony_ci .coef_div = 13616, 6058c2ecf20Sopenharmony_ci .syscon_status_off = 0x78, 6068c2ecf20Sopenharmony_ci .syscon_control0_off = 0x7c, 6078c2ecf20Sopenharmony_ci .syscon_control1_off = 0x80, 6088c2ecf20Sopenharmony_ci}; 6098c2ecf20Sopenharmony_ci 6108c2ecf20Sopenharmony_cistatic const struct armada_thermal_data armada380_data = { 6118c2ecf20Sopenharmony_ci .init = armada380_init, 6128c2ecf20Sopenharmony_ci .is_valid_bit = BIT(10), 6138c2ecf20Sopenharmony_ci .temp_shift = 0, 6148c2ecf20Sopenharmony_ci .temp_mask = 0x3ff, 6158c2ecf20Sopenharmony_ci .coef_b = 1172499100ULL, 6168c2ecf20Sopenharmony_ci .coef_m = 2000096ULL, 6178c2ecf20Sopenharmony_ci .coef_div = 4201, 6188c2ecf20Sopenharmony_ci .inverted = true, 6198c2ecf20Sopenharmony_ci .syscon_control0_off = 0x70, 6208c2ecf20Sopenharmony_ci .syscon_control1_off = 0x74, 6218c2ecf20Sopenharmony_ci .syscon_status_off = 0x78, 6228c2ecf20Sopenharmony_ci}; 6238c2ecf20Sopenharmony_ci 6248c2ecf20Sopenharmony_cistatic const struct armada_thermal_data armada_ap806_data = { 6258c2ecf20Sopenharmony_ci .init = armada_ap806_init, 6268c2ecf20Sopenharmony_ci .is_valid_bit = BIT(16), 6278c2ecf20Sopenharmony_ci .temp_shift = 0, 6288c2ecf20Sopenharmony_ci .temp_mask = 0x3ff, 6298c2ecf20Sopenharmony_ci .thresh_shift = 3, 6308c2ecf20Sopenharmony_ci .hyst_shift = 19, 6318c2ecf20Sopenharmony_ci .hyst_mask = 0x3, 6328c2ecf20Sopenharmony_ci .coef_b = -150000LL, 6338c2ecf20Sopenharmony_ci .coef_m = 423ULL, 6348c2ecf20Sopenharmony_ci .coef_div = 1, 6358c2ecf20Sopenharmony_ci .inverted = true, 6368c2ecf20Sopenharmony_ci .signed_sample = true, 6378c2ecf20Sopenharmony_ci .syscon_control0_off = 0x84, 6388c2ecf20Sopenharmony_ci .syscon_control1_off = 0x88, 6398c2ecf20Sopenharmony_ci .syscon_status_off = 0x8C, 6408c2ecf20Sopenharmony_ci .dfx_irq_cause_off = 0x108, 6418c2ecf20Sopenharmony_ci .dfx_irq_mask_off = 0x10C, 6428c2ecf20Sopenharmony_ci .dfx_overheat_irq = BIT(22), 6438c2ecf20Sopenharmony_ci .dfx_server_irq_mask_off = 0x104, 6448c2ecf20Sopenharmony_ci .dfx_server_irq_en = BIT(1), 6458c2ecf20Sopenharmony_ci .cpu_nr = 4, 6468c2ecf20Sopenharmony_ci}; 6478c2ecf20Sopenharmony_ci 6488c2ecf20Sopenharmony_cistatic const struct armada_thermal_data armada_cp110_data = { 6498c2ecf20Sopenharmony_ci .init = armada_cp110_init, 6508c2ecf20Sopenharmony_ci .is_valid_bit = BIT(10), 6518c2ecf20Sopenharmony_ci .temp_shift = 0, 6528c2ecf20Sopenharmony_ci .temp_mask = 0x3ff, 6538c2ecf20Sopenharmony_ci .thresh_shift = 16, 6548c2ecf20Sopenharmony_ci .hyst_shift = 26, 6558c2ecf20Sopenharmony_ci .hyst_mask = 0x3, 6568c2ecf20Sopenharmony_ci .coef_b = 1172499100ULL, 6578c2ecf20Sopenharmony_ci .coef_m = 2000096ULL, 6588c2ecf20Sopenharmony_ci .coef_div = 4201, 6598c2ecf20Sopenharmony_ci .inverted = true, 6608c2ecf20Sopenharmony_ci .syscon_control0_off = 0x70, 6618c2ecf20Sopenharmony_ci .syscon_control1_off = 0x74, 6628c2ecf20Sopenharmony_ci .syscon_status_off = 0x78, 6638c2ecf20Sopenharmony_ci .dfx_irq_cause_off = 0x108, 6648c2ecf20Sopenharmony_ci .dfx_irq_mask_off = 0x10C, 6658c2ecf20Sopenharmony_ci .dfx_overheat_irq = BIT(20), 6668c2ecf20Sopenharmony_ci .dfx_server_irq_mask_off = 0x104, 6678c2ecf20Sopenharmony_ci .dfx_server_irq_en = BIT(1), 6688c2ecf20Sopenharmony_ci}; 6698c2ecf20Sopenharmony_ci 6708c2ecf20Sopenharmony_cistatic const struct of_device_id armada_thermal_id_table[] = { 6718c2ecf20Sopenharmony_ci { 6728c2ecf20Sopenharmony_ci .compatible = "marvell,armadaxp-thermal", 6738c2ecf20Sopenharmony_ci .data = &armadaxp_data, 6748c2ecf20Sopenharmony_ci }, 6758c2ecf20Sopenharmony_ci { 6768c2ecf20Sopenharmony_ci .compatible = "marvell,armada370-thermal", 6778c2ecf20Sopenharmony_ci .data = &armada370_data, 6788c2ecf20Sopenharmony_ci }, 6798c2ecf20Sopenharmony_ci { 6808c2ecf20Sopenharmony_ci .compatible = "marvell,armada375-thermal", 6818c2ecf20Sopenharmony_ci .data = &armada375_data, 6828c2ecf20Sopenharmony_ci }, 6838c2ecf20Sopenharmony_ci { 6848c2ecf20Sopenharmony_ci .compatible = "marvell,armada380-thermal", 6858c2ecf20Sopenharmony_ci .data = &armada380_data, 6868c2ecf20Sopenharmony_ci }, 6878c2ecf20Sopenharmony_ci { 6888c2ecf20Sopenharmony_ci .compatible = "marvell,armada-ap806-thermal", 6898c2ecf20Sopenharmony_ci .data = &armada_ap806_data, 6908c2ecf20Sopenharmony_ci }, 6918c2ecf20Sopenharmony_ci { 6928c2ecf20Sopenharmony_ci .compatible = "marvell,armada-cp110-thermal", 6938c2ecf20Sopenharmony_ci .data = &armada_cp110_data, 6948c2ecf20Sopenharmony_ci }, 6958c2ecf20Sopenharmony_ci { 6968c2ecf20Sopenharmony_ci /* sentinel */ 6978c2ecf20Sopenharmony_ci }, 6988c2ecf20Sopenharmony_ci}; 6998c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, armada_thermal_id_table); 7008c2ecf20Sopenharmony_ci 7018c2ecf20Sopenharmony_cistatic const struct regmap_config armada_thermal_regmap_config = { 7028c2ecf20Sopenharmony_ci .reg_bits = 32, 7038c2ecf20Sopenharmony_ci .reg_stride = 4, 7048c2ecf20Sopenharmony_ci .val_bits = 32, 7058c2ecf20Sopenharmony_ci .fast_io = true, 7068c2ecf20Sopenharmony_ci}; 7078c2ecf20Sopenharmony_ci 7088c2ecf20Sopenharmony_cistatic int armada_thermal_probe_legacy(struct platform_device *pdev, 7098c2ecf20Sopenharmony_ci struct armada_thermal_priv *priv) 7108c2ecf20Sopenharmony_ci{ 7118c2ecf20Sopenharmony_ci struct armada_thermal_data *data = priv->data; 7128c2ecf20Sopenharmony_ci struct resource *res; 7138c2ecf20Sopenharmony_ci void __iomem *base; 7148c2ecf20Sopenharmony_ci 7158c2ecf20Sopenharmony_ci /* First memory region points towards the status register */ 7168c2ecf20Sopenharmony_ci res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 7178c2ecf20Sopenharmony_ci base = devm_ioremap_resource(&pdev->dev, res); 7188c2ecf20Sopenharmony_ci if (IS_ERR(base)) 7198c2ecf20Sopenharmony_ci return PTR_ERR(base); 7208c2ecf20Sopenharmony_ci 7218c2ecf20Sopenharmony_ci /* 7228c2ecf20Sopenharmony_ci * Fix up from the old individual DT register specification to 7238c2ecf20Sopenharmony_ci * cover all the registers. We do this by adjusting the ioremap() 7248c2ecf20Sopenharmony_ci * result, which should be fine as ioremap() deals with pages. 7258c2ecf20Sopenharmony_ci * However, validate that we do not cross a page boundary while 7268c2ecf20Sopenharmony_ci * making this adjustment. 7278c2ecf20Sopenharmony_ci */ 7288c2ecf20Sopenharmony_ci if (((unsigned long)base & ~PAGE_MASK) < data->syscon_status_off) 7298c2ecf20Sopenharmony_ci return -EINVAL; 7308c2ecf20Sopenharmony_ci base -= data->syscon_status_off; 7318c2ecf20Sopenharmony_ci 7328c2ecf20Sopenharmony_ci priv->syscon = devm_regmap_init_mmio(&pdev->dev, base, 7338c2ecf20Sopenharmony_ci &armada_thermal_regmap_config); 7348c2ecf20Sopenharmony_ci return PTR_ERR_OR_ZERO(priv->syscon); 7358c2ecf20Sopenharmony_ci} 7368c2ecf20Sopenharmony_ci 7378c2ecf20Sopenharmony_cistatic int armada_thermal_probe_syscon(struct platform_device *pdev, 7388c2ecf20Sopenharmony_ci struct armada_thermal_priv *priv) 7398c2ecf20Sopenharmony_ci{ 7408c2ecf20Sopenharmony_ci priv->syscon = syscon_node_to_regmap(pdev->dev.parent->of_node); 7418c2ecf20Sopenharmony_ci return PTR_ERR_OR_ZERO(priv->syscon); 7428c2ecf20Sopenharmony_ci} 7438c2ecf20Sopenharmony_ci 7448c2ecf20Sopenharmony_cistatic void armada_set_sane_name(struct platform_device *pdev, 7458c2ecf20Sopenharmony_ci struct armada_thermal_priv *priv) 7468c2ecf20Sopenharmony_ci{ 7478c2ecf20Sopenharmony_ci const char *name = dev_name(&pdev->dev); 7488c2ecf20Sopenharmony_ci char *insane_char; 7498c2ecf20Sopenharmony_ci 7508c2ecf20Sopenharmony_ci if (strlen(name) > THERMAL_NAME_LENGTH) { 7518c2ecf20Sopenharmony_ci /* 7528c2ecf20Sopenharmony_ci * When inside a system controller, the device name has the 7538c2ecf20Sopenharmony_ci * form: f06f8000.system-controller:ap-thermal so stripping 7548c2ecf20Sopenharmony_ci * after the ':' should give us a shorter but meaningful name. 7558c2ecf20Sopenharmony_ci */ 7568c2ecf20Sopenharmony_ci name = strrchr(name, ':'); 7578c2ecf20Sopenharmony_ci if (!name) 7588c2ecf20Sopenharmony_ci name = "armada_thermal"; 7598c2ecf20Sopenharmony_ci else 7608c2ecf20Sopenharmony_ci name++; 7618c2ecf20Sopenharmony_ci } 7628c2ecf20Sopenharmony_ci 7638c2ecf20Sopenharmony_ci /* Save the name locally */ 7648c2ecf20Sopenharmony_ci strncpy(priv->zone_name, name, THERMAL_NAME_LENGTH - 1); 7658c2ecf20Sopenharmony_ci priv->zone_name[THERMAL_NAME_LENGTH - 1] = '\0'; 7668c2ecf20Sopenharmony_ci 7678c2ecf20Sopenharmony_ci /* Then check there are no '-' or hwmon core will complain */ 7688c2ecf20Sopenharmony_ci do { 7698c2ecf20Sopenharmony_ci insane_char = strpbrk(priv->zone_name, "-"); 7708c2ecf20Sopenharmony_ci if (insane_char) 7718c2ecf20Sopenharmony_ci *insane_char = '_'; 7728c2ecf20Sopenharmony_ci } while (insane_char); 7738c2ecf20Sopenharmony_ci} 7748c2ecf20Sopenharmony_ci 7758c2ecf20Sopenharmony_ci/* 7768c2ecf20Sopenharmony_ci * The IP can manage to trigger interrupts on overheat situation from all the 7778c2ecf20Sopenharmony_ci * sensors. However, the interrupt source changes along with the last selected 7788c2ecf20Sopenharmony_ci * source (ie. the last read sensor), which is an inconsistent behavior. Avoid 7798c2ecf20Sopenharmony_ci * possible glitches by always selecting back only one channel (arbitrarily: the 7808c2ecf20Sopenharmony_ci * first in the DT which has a critical trip point). We also disable sensor 7818c2ecf20Sopenharmony_ci * switch during overheat situations. 7828c2ecf20Sopenharmony_ci */ 7838c2ecf20Sopenharmony_cistatic int armada_configure_overheat_int(struct armada_thermal_priv *priv, 7848c2ecf20Sopenharmony_ci struct thermal_zone_device *tz, 7858c2ecf20Sopenharmony_ci int sensor_id) 7868c2ecf20Sopenharmony_ci{ 7878c2ecf20Sopenharmony_ci /* Retrieve the critical trip point to enable the overheat interrupt */ 7888c2ecf20Sopenharmony_ci const struct thermal_trip *trips = of_thermal_get_trip_points(tz); 7898c2ecf20Sopenharmony_ci int ret; 7908c2ecf20Sopenharmony_ci int i; 7918c2ecf20Sopenharmony_ci 7928c2ecf20Sopenharmony_ci if (!trips) 7938c2ecf20Sopenharmony_ci return -EINVAL; 7948c2ecf20Sopenharmony_ci 7958c2ecf20Sopenharmony_ci for (i = 0; i < of_thermal_get_ntrips(tz); i++) 7968c2ecf20Sopenharmony_ci if (trips[i].type == THERMAL_TRIP_CRITICAL) 7978c2ecf20Sopenharmony_ci break; 7988c2ecf20Sopenharmony_ci 7998c2ecf20Sopenharmony_ci if (i == of_thermal_get_ntrips(tz)) 8008c2ecf20Sopenharmony_ci return -EINVAL; 8018c2ecf20Sopenharmony_ci 8028c2ecf20Sopenharmony_ci ret = armada_select_channel(priv, sensor_id); 8038c2ecf20Sopenharmony_ci if (ret) 8048c2ecf20Sopenharmony_ci return ret; 8058c2ecf20Sopenharmony_ci 8068c2ecf20Sopenharmony_ci armada_set_overheat_thresholds(priv, 8078c2ecf20Sopenharmony_ci trips[i].temperature, 8088c2ecf20Sopenharmony_ci trips[i].hysteresis); 8098c2ecf20Sopenharmony_ci priv->overheat_sensor = tz; 8108c2ecf20Sopenharmony_ci priv->interrupt_source = sensor_id; 8118c2ecf20Sopenharmony_ci 8128c2ecf20Sopenharmony_ci armada_enable_overheat_interrupt(priv); 8138c2ecf20Sopenharmony_ci 8148c2ecf20Sopenharmony_ci return 0; 8158c2ecf20Sopenharmony_ci} 8168c2ecf20Sopenharmony_ci 8178c2ecf20Sopenharmony_cistatic int armada_thermal_probe(struct platform_device *pdev) 8188c2ecf20Sopenharmony_ci{ 8198c2ecf20Sopenharmony_ci struct thermal_zone_device *tz; 8208c2ecf20Sopenharmony_ci struct armada_thermal_sensor *sensor; 8218c2ecf20Sopenharmony_ci struct armada_drvdata *drvdata; 8228c2ecf20Sopenharmony_ci const struct of_device_id *match; 8238c2ecf20Sopenharmony_ci struct armada_thermal_priv *priv; 8248c2ecf20Sopenharmony_ci int sensor_id, irq; 8258c2ecf20Sopenharmony_ci int ret; 8268c2ecf20Sopenharmony_ci 8278c2ecf20Sopenharmony_ci match = of_match_device(armada_thermal_id_table, &pdev->dev); 8288c2ecf20Sopenharmony_ci if (!match) 8298c2ecf20Sopenharmony_ci return -ENODEV; 8308c2ecf20Sopenharmony_ci 8318c2ecf20Sopenharmony_ci priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 8328c2ecf20Sopenharmony_ci if (!priv) 8338c2ecf20Sopenharmony_ci return -ENOMEM; 8348c2ecf20Sopenharmony_ci 8358c2ecf20Sopenharmony_ci drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL); 8368c2ecf20Sopenharmony_ci if (!drvdata) 8378c2ecf20Sopenharmony_ci return -ENOMEM; 8388c2ecf20Sopenharmony_ci 8398c2ecf20Sopenharmony_ci priv->dev = &pdev->dev; 8408c2ecf20Sopenharmony_ci priv->data = (struct armada_thermal_data *)match->data; 8418c2ecf20Sopenharmony_ci 8428c2ecf20Sopenharmony_ci mutex_init(&priv->update_lock); 8438c2ecf20Sopenharmony_ci 8448c2ecf20Sopenharmony_ci /* 8458c2ecf20Sopenharmony_ci * Legacy DT bindings only described "control1" register (also referred 8468c2ecf20Sopenharmony_ci * as "control MSB" on old documentation). Then, bindings moved to cover 8478c2ecf20Sopenharmony_ci * "control0/control LSB" and "control1/control MSB" registers within 8488c2ecf20Sopenharmony_ci * the same resource, which was then of size 8 instead of 4. 8498c2ecf20Sopenharmony_ci * 8508c2ecf20Sopenharmony_ci * The logic of defining sporadic registers is broken. For instance, it 8518c2ecf20Sopenharmony_ci * blocked the addition of the overheat interrupt feature that needed 8528c2ecf20Sopenharmony_ci * another resource somewhere else in the same memory area. One solution 8538c2ecf20Sopenharmony_ci * is to define an overall system controller and put the thermal node 8548c2ecf20Sopenharmony_ci * into it, which requires the use of regmaps across all the driver. 8558c2ecf20Sopenharmony_ci */ 8568c2ecf20Sopenharmony_ci if (IS_ERR(syscon_node_to_regmap(pdev->dev.parent->of_node))) { 8578c2ecf20Sopenharmony_ci /* Ensure device name is correct for the thermal core */ 8588c2ecf20Sopenharmony_ci armada_set_sane_name(pdev, priv); 8598c2ecf20Sopenharmony_ci 8608c2ecf20Sopenharmony_ci ret = armada_thermal_probe_legacy(pdev, priv); 8618c2ecf20Sopenharmony_ci if (ret) 8628c2ecf20Sopenharmony_ci return ret; 8638c2ecf20Sopenharmony_ci 8648c2ecf20Sopenharmony_ci priv->data->init(pdev, priv); 8658c2ecf20Sopenharmony_ci 8668c2ecf20Sopenharmony_ci /* Wait the sensors to be valid */ 8678c2ecf20Sopenharmony_ci armada_wait_sensor_validity(priv); 8688c2ecf20Sopenharmony_ci 8698c2ecf20Sopenharmony_ci tz = thermal_zone_device_register(priv->zone_name, 0, 0, priv, 8708c2ecf20Sopenharmony_ci &legacy_ops, NULL, 0, 0); 8718c2ecf20Sopenharmony_ci if (IS_ERR(tz)) { 8728c2ecf20Sopenharmony_ci dev_err(&pdev->dev, 8738c2ecf20Sopenharmony_ci "Failed to register thermal zone device\n"); 8748c2ecf20Sopenharmony_ci return PTR_ERR(tz); 8758c2ecf20Sopenharmony_ci } 8768c2ecf20Sopenharmony_ci 8778c2ecf20Sopenharmony_ci ret = thermal_zone_device_enable(tz); 8788c2ecf20Sopenharmony_ci if (ret) { 8798c2ecf20Sopenharmony_ci thermal_zone_device_unregister(tz); 8808c2ecf20Sopenharmony_ci return ret; 8818c2ecf20Sopenharmony_ci } 8828c2ecf20Sopenharmony_ci 8838c2ecf20Sopenharmony_ci drvdata->type = LEGACY; 8848c2ecf20Sopenharmony_ci drvdata->data.tz = tz; 8858c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, drvdata); 8868c2ecf20Sopenharmony_ci 8878c2ecf20Sopenharmony_ci return 0; 8888c2ecf20Sopenharmony_ci } 8898c2ecf20Sopenharmony_ci 8908c2ecf20Sopenharmony_ci ret = armada_thermal_probe_syscon(pdev, priv); 8918c2ecf20Sopenharmony_ci if (ret) 8928c2ecf20Sopenharmony_ci return ret; 8938c2ecf20Sopenharmony_ci 8948c2ecf20Sopenharmony_ci priv->current_channel = -1; 8958c2ecf20Sopenharmony_ci priv->data->init(pdev, priv); 8968c2ecf20Sopenharmony_ci drvdata->type = SYSCON; 8978c2ecf20Sopenharmony_ci drvdata->data.priv = priv; 8988c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, drvdata); 8998c2ecf20Sopenharmony_ci 9008c2ecf20Sopenharmony_ci irq = platform_get_irq(pdev, 0); 9018c2ecf20Sopenharmony_ci if (irq == -EPROBE_DEFER) 9028c2ecf20Sopenharmony_ci return irq; 9038c2ecf20Sopenharmony_ci 9048c2ecf20Sopenharmony_ci /* The overheat interrupt feature is not mandatory */ 9058c2ecf20Sopenharmony_ci if (irq > 0) { 9068c2ecf20Sopenharmony_ci ret = devm_request_threaded_irq(&pdev->dev, irq, 9078c2ecf20Sopenharmony_ci armada_overheat_isr, 9088c2ecf20Sopenharmony_ci armada_overheat_isr_thread, 9098c2ecf20Sopenharmony_ci 0, NULL, priv); 9108c2ecf20Sopenharmony_ci if (ret) { 9118c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Cannot request threaded IRQ %d\n", 9128c2ecf20Sopenharmony_ci irq); 9138c2ecf20Sopenharmony_ci return ret; 9148c2ecf20Sopenharmony_ci } 9158c2ecf20Sopenharmony_ci } 9168c2ecf20Sopenharmony_ci 9178c2ecf20Sopenharmony_ci /* 9188c2ecf20Sopenharmony_ci * There is one channel for the IC and one per CPU (if any), each 9198c2ecf20Sopenharmony_ci * channel has one sensor. 9208c2ecf20Sopenharmony_ci */ 9218c2ecf20Sopenharmony_ci for (sensor_id = 0; sensor_id <= priv->data->cpu_nr; sensor_id++) { 9228c2ecf20Sopenharmony_ci sensor = devm_kzalloc(&pdev->dev, 9238c2ecf20Sopenharmony_ci sizeof(struct armada_thermal_sensor), 9248c2ecf20Sopenharmony_ci GFP_KERNEL); 9258c2ecf20Sopenharmony_ci if (!sensor) 9268c2ecf20Sopenharmony_ci return -ENOMEM; 9278c2ecf20Sopenharmony_ci 9288c2ecf20Sopenharmony_ci /* Register the sensor */ 9298c2ecf20Sopenharmony_ci sensor->priv = priv; 9308c2ecf20Sopenharmony_ci sensor->id = sensor_id; 9318c2ecf20Sopenharmony_ci tz = devm_thermal_zone_of_sensor_register(&pdev->dev, 9328c2ecf20Sopenharmony_ci sensor->id, sensor, 9338c2ecf20Sopenharmony_ci &of_ops); 9348c2ecf20Sopenharmony_ci if (IS_ERR(tz)) { 9358c2ecf20Sopenharmony_ci dev_info(&pdev->dev, "Thermal sensor %d unavailable\n", 9368c2ecf20Sopenharmony_ci sensor_id); 9378c2ecf20Sopenharmony_ci devm_kfree(&pdev->dev, sensor); 9388c2ecf20Sopenharmony_ci continue; 9398c2ecf20Sopenharmony_ci } 9408c2ecf20Sopenharmony_ci 9418c2ecf20Sopenharmony_ci /* 9428c2ecf20Sopenharmony_ci * The first channel that has a critical trip point registered 9438c2ecf20Sopenharmony_ci * in the DT will serve as interrupt source. Others possible 9448c2ecf20Sopenharmony_ci * critical trip points will simply be ignored by the driver. 9458c2ecf20Sopenharmony_ci */ 9468c2ecf20Sopenharmony_ci if (irq > 0 && !priv->overheat_sensor) 9478c2ecf20Sopenharmony_ci armada_configure_overheat_int(priv, tz, sensor->id); 9488c2ecf20Sopenharmony_ci } 9498c2ecf20Sopenharmony_ci 9508c2ecf20Sopenharmony_ci /* Just complain if no overheat interrupt was set up */ 9518c2ecf20Sopenharmony_ci if (!priv->overheat_sensor) 9528c2ecf20Sopenharmony_ci dev_warn(&pdev->dev, "Overheat interrupt not available\n"); 9538c2ecf20Sopenharmony_ci 9548c2ecf20Sopenharmony_ci return 0; 9558c2ecf20Sopenharmony_ci} 9568c2ecf20Sopenharmony_ci 9578c2ecf20Sopenharmony_cistatic int armada_thermal_exit(struct platform_device *pdev) 9588c2ecf20Sopenharmony_ci{ 9598c2ecf20Sopenharmony_ci struct armada_drvdata *drvdata = platform_get_drvdata(pdev); 9608c2ecf20Sopenharmony_ci 9618c2ecf20Sopenharmony_ci if (drvdata->type == LEGACY) 9628c2ecf20Sopenharmony_ci thermal_zone_device_unregister(drvdata->data.tz); 9638c2ecf20Sopenharmony_ci 9648c2ecf20Sopenharmony_ci return 0; 9658c2ecf20Sopenharmony_ci} 9668c2ecf20Sopenharmony_ci 9678c2ecf20Sopenharmony_cistatic struct platform_driver armada_thermal_driver = { 9688c2ecf20Sopenharmony_ci .probe = armada_thermal_probe, 9698c2ecf20Sopenharmony_ci .remove = armada_thermal_exit, 9708c2ecf20Sopenharmony_ci .driver = { 9718c2ecf20Sopenharmony_ci .name = "armada_thermal", 9728c2ecf20Sopenharmony_ci .of_match_table = armada_thermal_id_table, 9738c2ecf20Sopenharmony_ci }, 9748c2ecf20Sopenharmony_ci}; 9758c2ecf20Sopenharmony_ci 9768c2ecf20Sopenharmony_cimodule_platform_driver(armada_thermal_driver); 9778c2ecf20Sopenharmony_ci 9788c2ecf20Sopenharmony_ciMODULE_AUTHOR("Ezequiel Garcia <ezequiel.garcia@free-electrons.com>"); 9798c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Marvell EBU Armada SoCs thermal driver"); 9808c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 981