1 // SPDX-License-Identifier: GPL-2.0-only
3 * Broadcom STB AVS TMON thermal sensor driver
5 * Copyright (c) 2015-2017 Broadcom
8 #define DRV_NAME "brcmstb_thermal"
10 #define pr_fmt(fmt) DRV_NAME ": " fmt
12 #include <linux/bitops.h>
13 #include <linux/device.h>
14 #include <linux/err.h>
16 #include <linux/irqreturn.h>
17 #include <linux/interrupt.h>
18 #include <linux/kernel.h>
19 #include <linux/module.h>
20 #include <linux/platform_device.h>
21 #include <linux/of_device.h>
22 #include <linux/thermal.h>
24 #define AVS_TMON_STATUS 0x00
25 #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 1
29 #define AVS_TMON_EN_OVERTEMP_RESET 0x04
30 #define AVS_TMON_EN_OVERTEMP_RESET_msk BIT(0)
32 #define AVS_TMON_RESET_THRESH 0x08
33 #define AVS_TMON_RESET_THRESH_msk GENMASK(10, 1)
34 #define AVS_TMON_RESET_THRESH_shift 1
36 #define AVS_TMON_INT_IDLE_TIME 0x10
38 #define AVS_TMON_EN_TEMP_INT_SRCS 0x14
39 #define AVS_TMON_EN_TEMP_INT_SRCS_high BIT(1)
40 #define AVS_TMON_EN_TEMP_INT_SRCS_low BIT(0)
42 #define AVS_TMON_INT_THRESH 0x18
43 #define AVS_TMON_INT_THRESH_high_msk GENMASK(26, 17)
44 #define AVS_TMON_INT_THRESH_high_shift 17
45 #define AVS_TMON_INT_THRESH_low_msk GENMASK(10, 1)
46 #define AVS_TMON_INT_THRESH_low_shift 1
48 #define AVS_TMON_TEMP_INT_CODE 0x1c
49 #define AVS_TMON_TP_TEST_ENABLE 0x20
51 /* Default coefficients */
52 #define AVS_TMON_TEMP_SLOPE 487
53 #define AVS_TMON_TEMP_OFFSET 410040
55 /* HW related temperature constants */
56 #define AVS_TMON_TEMP_MAX 0x3ff
57 #define AVS_TMON_TEMP_MIN -88161
58 #define AVS_TMON_TEMP_MASK AVS_TMON_TEMP_MAX
60 enum avs_tmon_trip_type
{
61 TMON_TRIP_TYPE_LOW
= 0,
67 struct avs_tmon_trip
{
68 /* HW bit to enable the trip */
72 /* HW field to read the trip temperature */
78 static 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
,
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
,
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
,
105 struct brcmstb_thermal_params
{
108 const struct thermal_zone_of_device_ops
*of_ops
;
111 struct brcmstb_thermal_priv
{
112 void __iomem
*tmon_base
;
114 struct thermal_zone_device
*thermal
;
115 /* Process specific thermal parameters used for calculations */
116 const struct brcmstb_thermal_params
*temp_params
;
119 /* Convert a HW code to a temperature reading (millidegree celsius) */
120 static inline int avs_tmon_code_to_temp(struct brcmstb_thermal_priv
*priv
,
123 int offset
= priv
->temp_params
->offset
;
124 int mult
= priv
->temp_params
->mult
;
126 return (offset
- (int)((code
& AVS_TMON_TEMP_MASK
) * mult
));
130 * Convert a temperature value (millidegree celsius) to a HW code
132 * @temp: temperature to convert
133 * @low: if true, round toward the low side
135 static inline u32
avs_tmon_temp_to_code(struct brcmstb_thermal_priv
*priv
,
138 int offset
= priv
->temp_params
->offset
;
139 int mult
= priv
->temp_params
->mult
;
141 if (temp
< AVS_TMON_TEMP_MIN
)
142 return AVS_TMON_TEMP_MAX
; /* Maximum code value */
145 return 0; /* Minimum code value */
148 return (u32
)(DIV_ROUND_UP(offset
- temp
, mult
));
150 return (u32
)((offset
- temp
) / mult
);
153 static int brcmstb_get_temp(void *data
, int *temp
)
155 struct brcmstb_thermal_priv
*priv
= data
;
159 val
= __raw_readl(priv
->tmon_base
+ AVS_TMON_STATUS
);
161 if (!(val
& AVS_TMON_STATUS_valid_msk
)) {
162 dev_err(priv
->dev
, "reading not valid\n");
166 val
= (val
& AVS_TMON_STATUS_data_msk
) >> AVS_TMON_STATUS_data_shift
;
168 t
= avs_tmon_code_to_temp(priv
, val
);
177 static void avs_tmon_trip_enable(struct brcmstb_thermal_priv
*priv
,
178 enum avs_tmon_trip_type type
, int en
)
180 struct avs_tmon_trip
*trip
= &avs_tmon_trips
[type
];
181 u32 val
= __raw_readl(priv
->tmon_base
+ trip
->enable_offs
);
183 dev_dbg(priv
->dev
, "%sable trip, type %d\n", en
? "en" : "dis", type
);
186 val
|= trip
->enable_mask
;
188 val
&= ~trip
->enable_mask
;
190 __raw_writel(val
, priv
->tmon_base
+ trip
->enable_offs
);
193 static int avs_tmon_get_trip_temp(struct brcmstb_thermal_priv
*priv
,
194 enum avs_tmon_trip_type type
)
196 struct avs_tmon_trip
*trip
= &avs_tmon_trips
[type
];
197 u32 val
= __raw_readl(priv
->tmon_base
+ trip
->reg_offs
);
199 val
&= trip
->reg_msk
;
200 val
>>= trip
->reg_shift
;
202 return avs_tmon_code_to_temp(priv
, val
);
205 static void avs_tmon_set_trip_temp(struct brcmstb_thermal_priv
*priv
,
206 enum avs_tmon_trip_type type
,
209 struct avs_tmon_trip
*trip
= &avs_tmon_trips
[type
];
212 dev_dbg(priv
->dev
, "set temp %d to %d\n", type
, temp
);
214 /* round toward low temp for the low interrupt */
215 val
= avs_tmon_temp_to_code(priv
, temp
,
216 type
== TMON_TRIP_TYPE_LOW
);
218 val
<<= trip
->reg_shift
;
219 val
&= trip
->reg_msk
;
221 orig
= __raw_readl(priv
->tmon_base
+ trip
->reg_offs
);
222 orig
&= ~trip
->reg_msk
;
224 __raw_writel(orig
, priv
->tmon_base
+ trip
->reg_offs
);
227 static int avs_tmon_get_intr_temp(struct brcmstb_thermal_priv
*priv
)
231 val
= __raw_readl(priv
->tmon_base
+ AVS_TMON_TEMP_INT_CODE
);
232 return avs_tmon_code_to_temp(priv
, val
);
235 static irqreturn_t
brcmstb_tmon_irq_thread(int irq
, void *data
)
237 struct brcmstb_thermal_priv
*priv
= data
;
240 low
= avs_tmon_get_trip_temp(priv
, TMON_TRIP_TYPE_LOW
);
241 high
= avs_tmon_get_trip_temp(priv
, TMON_TRIP_TYPE_HIGH
);
242 intr
= avs_tmon_get_intr_temp(priv
);
244 dev_dbg(priv
->dev
, "low/intr/high: %d/%d/%d\n",
247 /* Disable high-temp until next threshold shift */
249 avs_tmon_trip_enable(priv
, TMON_TRIP_TYPE_HIGH
, 0);
250 /* Disable low-temp until next threshold shift */
252 avs_tmon_trip_enable(priv
, TMON_TRIP_TYPE_LOW
, 0);
255 * Notify using the interrupt temperature, in case the temperature
256 * changes before it can next be read out
258 thermal_zone_device_update(priv
->thermal
, intr
);
263 static int brcmstb_set_trips(void *data
, int low
, int high
)
265 struct brcmstb_thermal_priv
*priv
= data
;
267 dev_dbg(priv
->dev
, "set trips %d <--> %d\n", low
, high
);
270 * Disable low-temp if "low" is too small. As per thermal framework
271 * API, we use -INT_MAX rather than INT_MIN.
273 if (low
<= -INT_MAX
) {
274 avs_tmon_trip_enable(priv
, TMON_TRIP_TYPE_LOW
, 0);
276 avs_tmon_set_trip_temp(priv
, TMON_TRIP_TYPE_LOW
, low
);
277 avs_tmon_trip_enable(priv
, TMON_TRIP_TYPE_LOW
, 1);
280 /* Disable high-temp if "high" is too big. */
281 if (high
== INT_MAX
) {
282 avs_tmon_trip_enable(priv
, TMON_TRIP_TYPE_HIGH
, 0);
284 avs_tmon_set_trip_temp(priv
, TMON_TRIP_TYPE_HIGH
, high
);
285 avs_tmon_trip_enable(priv
, TMON_TRIP_TYPE_HIGH
, 1);
291 static const struct thermal_zone_of_device_ops brcmstb_16nm_of_ops
= {
292 .get_temp
= brcmstb_get_temp
,
295 static const struct brcmstb_thermal_params brcmstb_16nm_params
= {
298 .of_ops
= &brcmstb_16nm_of_ops
,
301 static const struct thermal_zone_of_device_ops brcmstb_28nm_of_ops
= {
302 .get_temp
= brcmstb_get_temp
,
303 .set_trips
= brcmstb_set_trips
,
306 static const struct brcmstb_thermal_params brcmstb_28nm_params
= {
309 .of_ops
= &brcmstb_28nm_of_ops
,
312 static const struct of_device_id brcmstb_thermal_id_table
[] = {
313 { .compatible
= "brcm,avs-tmon-bcm7216", .data
= &brcmstb_16nm_params
},
314 { .compatible
= "brcm,avs-tmon", .data
= &brcmstb_28nm_params
},
317 MODULE_DEVICE_TABLE(of
, brcmstb_thermal_id_table
);
319 static int brcmstb_thermal_probe(struct platform_device
*pdev
)
321 const struct thermal_zone_of_device_ops
*of_ops
;
322 struct thermal_zone_device
*thermal
;
323 struct brcmstb_thermal_priv
*priv
;
324 struct resource
*res
;
327 priv
= devm_kzalloc(&pdev
->dev
, sizeof(*priv
), GFP_KERNEL
);
331 priv
->temp_params
= of_device_get_match_data(&pdev
->dev
);
332 if (!priv
->temp_params
)
335 res
= platform_get_resource(pdev
, IORESOURCE_MEM
, 0);
336 priv
->tmon_base
= devm_ioremap_resource(&pdev
->dev
, res
);
337 if (IS_ERR(priv
->tmon_base
))
338 return PTR_ERR(priv
->tmon_base
);
340 priv
->dev
= &pdev
->dev
;
341 platform_set_drvdata(pdev
, priv
);
342 of_ops
= priv
->temp_params
->of_ops
;
344 thermal
= devm_thermal_zone_of_sensor_register(&pdev
->dev
, 0, priv
,
346 if (IS_ERR(thermal
)) {
347 ret
= PTR_ERR(thermal
);
348 dev_err(&pdev
->dev
, "could not register sensor: %d\n", ret
);
352 priv
->thermal
= thermal
;
354 irq
= platform_get_irq(pdev
, 0);
356 ret
= devm_request_threaded_irq(&pdev
->dev
, irq
, NULL
,
357 brcmstb_tmon_irq_thread
,
361 dev_err(&pdev
->dev
, "could not request IRQ: %d\n", ret
);
366 dev_info(&pdev
->dev
, "registered AVS TMON of-sensor driver\n");
371 static struct platform_driver brcmstb_thermal_driver
= {
372 .probe
= brcmstb_thermal_probe
,
375 .of_match_table
= brcmstb_thermal_id_table
,
378 module_platform_driver(brcmstb_thermal_driver
);
380 MODULE_LICENSE("GPL v2");
381 MODULE_AUTHOR("Brian Norris");
382 MODULE_DESCRIPTION("Broadcom STB AVS TMON thermal driver");