375 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Broadcom STB AVS TMON thermal sensor driver4 *5 * Copyright (c) 2015-2017 Broadcom6 */7 8#define DRV_NAME "brcmstb_thermal"9 10#define pr_fmt(fmt) DRV_NAME ": " fmt11 12#include <linux/bitops.h>13#include <linux/device.h>14#include <linux/err.h>15#include <linux/io.h>16#include <linux/irqreturn.h>17#include <linux/interrupt.h>18#include <linux/kernel.h>19#include <linux/module.h>20#include <linux/of.h>21#include <linux/platform_device.h>22#include <linux/thermal.h>23 24#define AVS_TMON_STATUS 0x0025 #define AVS_TMON_STATUS_valid_msk BIT(11)26 #define AVS_TMON_STATUS_data_msk GENMASK(10, 1)27 #define AVS_TMON_STATUS_data_shift 128 29#define AVS_TMON_EN_OVERTEMP_RESET 0x0430 #define AVS_TMON_EN_OVERTEMP_RESET_msk BIT(0)31 32#define AVS_TMON_RESET_THRESH 0x0833 #define AVS_TMON_RESET_THRESH_msk GENMASK(10, 1)34 #define AVS_TMON_RESET_THRESH_shift 135 36#define AVS_TMON_INT_IDLE_TIME 0x1037 38#define AVS_TMON_EN_TEMP_INT_SRCS 0x1439 #define AVS_TMON_EN_TEMP_INT_SRCS_high BIT(1)40 #define AVS_TMON_EN_TEMP_INT_SRCS_low BIT(0)41 42#define AVS_TMON_INT_THRESH 0x1843 #define AVS_TMON_INT_THRESH_high_msk GENMASK(26, 17)44 #define AVS_TMON_INT_THRESH_high_shift 1745 #define AVS_TMON_INT_THRESH_low_msk GENMASK(10, 1)46 #define AVS_TMON_INT_THRESH_low_shift 147 48#define AVS_TMON_TEMP_INT_CODE 0x1c49#define AVS_TMON_TP_TEST_ENABLE 0x2050 51/* Default coefficients */52#define AVS_TMON_TEMP_SLOPE 48753#define AVS_TMON_TEMP_OFFSET 41004054 55/* HW related temperature constants */56#define AVS_TMON_TEMP_MAX 0x3ff57#define AVS_TMON_TEMP_MIN -8816158#define AVS_TMON_TEMP_MASK AVS_TMON_TEMP_MAX59 60enum avs_tmon_trip_type {61 TMON_TRIP_TYPE_LOW = 0,62 TMON_TRIP_TYPE_HIGH,63 TMON_TRIP_TYPE_RESET,64 TMON_TRIP_TYPE_MAX,65};66 67struct avs_tmon_trip {68 /* HW bit to enable the trip */69 u32 enable_offs;70 u32 enable_mask;71 72 /* HW field to read the trip temperature */73 u32 reg_offs;74 u32 reg_msk;75 int reg_shift;76};77 78static struct avs_tmon_trip avs_tmon_trips[] = {79 /* Trips when temperature is below threshold */80 [TMON_TRIP_TYPE_LOW] = {81 .enable_offs = AVS_TMON_EN_TEMP_INT_SRCS,82 .enable_mask = AVS_TMON_EN_TEMP_INT_SRCS_low,83 .reg_offs = AVS_TMON_INT_THRESH,84 .reg_msk = AVS_TMON_INT_THRESH_low_msk,85 .reg_shift = AVS_TMON_INT_THRESH_low_shift,86 },87 /* Trips when temperature is above threshold */88 [TMON_TRIP_TYPE_HIGH] = {89 .enable_offs = AVS_TMON_EN_TEMP_INT_SRCS,90 .enable_mask = AVS_TMON_EN_TEMP_INT_SRCS_high,91 .reg_offs = AVS_TMON_INT_THRESH,92 .reg_msk = AVS_TMON_INT_THRESH_high_msk,93 .reg_shift = AVS_TMON_INT_THRESH_high_shift,94 },95 /* Automatically resets chip when above threshold */96 [TMON_TRIP_TYPE_RESET] = {97 .enable_offs = AVS_TMON_EN_OVERTEMP_RESET,98 .enable_mask = AVS_TMON_EN_OVERTEMP_RESET_msk,99 .reg_offs = AVS_TMON_RESET_THRESH,100 .reg_msk = AVS_TMON_RESET_THRESH_msk,101 .reg_shift = AVS_TMON_RESET_THRESH_shift,102 },103};104 105struct brcmstb_thermal_params {106 unsigned int offset;107 unsigned int mult;108 const struct thermal_zone_device_ops *of_ops;109};110 111struct brcmstb_thermal_priv {112 void __iomem *tmon_base;113 struct device *dev;114 struct thermal_zone_device *thermal;115 /* Process specific thermal parameters used for calculations */116 const struct brcmstb_thermal_params *temp_params;117};118 119/* Convert a HW code to a temperature reading (millidegree celsius) */120static inline int avs_tmon_code_to_temp(struct brcmstb_thermal_priv *priv,121 u32 code)122{123 int offset = priv->temp_params->offset;124 int mult = priv->temp_params->mult;125 126 return (offset - (int)((code & AVS_TMON_TEMP_MASK) * mult));127}128 129/*130 * Convert a temperature value (millidegree celsius) to a HW code131 *132 * @temp: temperature to convert133 * @low: if true, round toward the low side134 */135static inline u32 avs_tmon_temp_to_code(struct brcmstb_thermal_priv *priv,136 int temp, bool low)137{138 int offset = priv->temp_params->offset;139 int mult = priv->temp_params->mult;140 141 if (temp < AVS_TMON_TEMP_MIN)142 return AVS_TMON_TEMP_MAX; /* Maximum code value */143 144 if (temp >= offset)145 return 0; /* Minimum code value */146 147 if (low)148 return (u32)(DIV_ROUND_UP(offset - temp, mult));149 else150 return (u32)((offset - temp) / mult);151}152 153static int brcmstb_get_temp(struct thermal_zone_device *tz, int *temp)154{155 struct brcmstb_thermal_priv *priv = thermal_zone_device_priv(tz);156 u32 val;157 long t;158 159 val = __raw_readl(priv->tmon_base + AVS_TMON_STATUS);160 161 if (!(val & AVS_TMON_STATUS_valid_msk))162 return -EIO;163 164 val = (val & AVS_TMON_STATUS_data_msk) >> AVS_TMON_STATUS_data_shift;165 166 t = avs_tmon_code_to_temp(priv, val);167 if (t < 0)168 *temp = 0;169 else170 *temp = t;171 172 return 0;173}174 175static void avs_tmon_trip_enable(struct brcmstb_thermal_priv *priv,176 enum avs_tmon_trip_type type, int en)177{178 struct avs_tmon_trip *trip = &avs_tmon_trips[type];179 u32 val = __raw_readl(priv->tmon_base + trip->enable_offs);180 181 dev_dbg(priv->dev, "%sable trip, type %d\n", en ? "en" : "dis", type);182 183 if (en)184 val |= trip->enable_mask;185 else186 val &= ~trip->enable_mask;187 188 __raw_writel(val, priv->tmon_base + trip->enable_offs);189}190 191static int avs_tmon_get_trip_temp(struct brcmstb_thermal_priv *priv,192 enum avs_tmon_trip_type type)193{194 struct avs_tmon_trip *trip = &avs_tmon_trips[type];195 u32 val = __raw_readl(priv->tmon_base + trip->reg_offs);196 197 val &= trip->reg_msk;198 val >>= trip->reg_shift;199 200 return avs_tmon_code_to_temp(priv, val);201}202 203static void avs_tmon_set_trip_temp(struct brcmstb_thermal_priv *priv,204 enum avs_tmon_trip_type type,205 int temp)206{207 struct avs_tmon_trip *trip = &avs_tmon_trips[type];208 u32 val, orig;209 210 dev_dbg(priv->dev, "set temp %d to %d\n", type, temp);211 212 /* round toward low temp for the low interrupt */213 val = avs_tmon_temp_to_code(priv, temp,214 type == TMON_TRIP_TYPE_LOW);215 216 val <<= trip->reg_shift;217 val &= trip->reg_msk;218 219 orig = __raw_readl(priv->tmon_base + trip->reg_offs);220 orig &= ~trip->reg_msk;221 orig |= val;222 __raw_writel(orig, priv->tmon_base + trip->reg_offs);223}224 225static int avs_tmon_get_intr_temp(struct brcmstb_thermal_priv *priv)226{227 u32 val;228 229 val = __raw_readl(priv->tmon_base + AVS_TMON_TEMP_INT_CODE);230 return avs_tmon_code_to_temp(priv, val);231}232 233static irqreturn_t brcmstb_tmon_irq_thread(int irq, void *data)234{235 struct brcmstb_thermal_priv *priv = data;236 int low, high, intr;237 238 low = avs_tmon_get_trip_temp(priv, TMON_TRIP_TYPE_LOW);239 high = avs_tmon_get_trip_temp(priv, TMON_TRIP_TYPE_HIGH);240 intr = avs_tmon_get_intr_temp(priv);241 242 dev_dbg(priv->dev, "low/intr/high: %d/%d/%d\n",243 low, intr, high);244 245 /* Disable high-temp until next threshold shift */246 if (intr >= high)247 avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_HIGH, 0);248 /* Disable low-temp until next threshold shift */249 if (intr <= low)250 avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_LOW, 0);251 252 /*253 * Notify using the interrupt temperature, in case the temperature254 * changes before it can next be read out255 */256 thermal_zone_device_update(priv->thermal, intr);257 258 return IRQ_HANDLED;259}260 261static int brcmstb_set_trips(struct thermal_zone_device *tz, int low, int high)262{263 struct brcmstb_thermal_priv *priv = thermal_zone_device_priv(tz);264 265 dev_dbg(priv->dev, "set trips %d <--> %d\n", low, high);266 267 /*268 * Disable low-temp if "low" is too small. As per thermal framework269 * API, we use -INT_MAX rather than INT_MIN.270 */271 if (low <= -INT_MAX) {272 avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_LOW, 0);273 } else {274 avs_tmon_set_trip_temp(priv, TMON_TRIP_TYPE_LOW, low);275 avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_LOW, 1);276 }277 278 /* Disable high-temp if "high" is too big. */279 if (high == INT_MAX) {280 avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_HIGH, 0);281 } else {282 avs_tmon_set_trip_temp(priv, TMON_TRIP_TYPE_HIGH, high);283 avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_HIGH, 1);284 }285 286 return 0;287}288 289static const struct thermal_zone_device_ops brcmstb_16nm_of_ops = {290 .get_temp = brcmstb_get_temp,291};292 293static const struct brcmstb_thermal_params brcmstb_16nm_params = {294 .offset = 457829,295 .mult = 557,296 .of_ops = &brcmstb_16nm_of_ops,297};298 299static const struct thermal_zone_device_ops brcmstb_28nm_of_ops = {300 .get_temp = brcmstb_get_temp,301 .set_trips = brcmstb_set_trips,302};303 304static const struct brcmstb_thermal_params brcmstb_28nm_params = {305 .offset = 410040,306 .mult = 487,307 .of_ops = &brcmstb_28nm_of_ops,308};309 310static const struct of_device_id brcmstb_thermal_id_table[] = {311 { .compatible = "brcm,avs-tmon-bcm7216", .data = &brcmstb_16nm_params },312 { .compatible = "brcm,avs-tmon", .data = &brcmstb_28nm_params },313 {},314};315MODULE_DEVICE_TABLE(of, brcmstb_thermal_id_table);316 317static int brcmstb_thermal_probe(struct platform_device *pdev)318{319 const struct thermal_zone_device_ops *of_ops;320 struct thermal_zone_device *thermal;321 struct brcmstb_thermal_priv *priv;322 int irq, ret;323 324 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);325 if (!priv)326 return -ENOMEM;327 328 priv->temp_params = of_device_get_match_data(&pdev->dev);329 if (!priv->temp_params)330 return -EINVAL;331 332 priv->tmon_base = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);333 if (IS_ERR(priv->tmon_base))334 return PTR_ERR(priv->tmon_base);335 336 priv->dev = &pdev->dev;337 of_ops = priv->temp_params->of_ops;338 339 thermal = devm_thermal_of_zone_register(&pdev->dev, 0, priv,340 of_ops);341 if (IS_ERR(thermal))342 return dev_err_probe(&pdev->dev, PTR_ERR(thermal),343 "could not register sensor\n");344 345 priv->thermal = thermal;346 347 irq = platform_get_irq_optional(pdev, 0);348 if (irq >= 0) {349 ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,350 brcmstb_tmon_irq_thread,351 IRQF_ONESHOT,352 DRV_NAME, priv);353 if (ret < 0)354 return dev_err_probe(&pdev->dev, ret,355 "could not request IRQ\n");356 }357 358 dev_info(&pdev->dev, "registered AVS TMON of-sensor driver\n");359 360 return 0;361}362 363static struct platform_driver brcmstb_thermal_driver = {364 .probe = brcmstb_thermal_probe,365 .driver = {366 .name = DRV_NAME,367 .of_match_table = brcmstb_thermal_id_table,368 },369};370module_platform_driver(brcmstb_thermal_driver);371 372MODULE_LICENSE("GPL v2");373MODULE_AUTHOR("Brian Norris");374MODULE_DESCRIPTION("Broadcom STB AVS TMON thermal driver");375