[PATCH v10 4/6] thermal/drivers/mediatek: Add the Low Voltage Thermal Sensor driver

AngeloGioacchino Del Regno angelogioacchino.delregno at collabora.com
Mon Jan 16 02:50:46 PST 2023


Il 12/01/23 16:28, bchihi at baylibre.com ha scritto:
> From: Balsam CHIHI <bchihi at baylibre.com>
> 
> The Low Voltage Thermal Sensor (LVTS) is a multiple sensors, multi
> controllers contained in a thermal domain.
> 
> A thermal domains can be the MCU or the AP.
> 
> Each thermal domains contain up to seven controllers, each thermal
> controller handle up to four thermal sensors.
> 
> The LVTS has two Finite State Machines (FSM), one to handle the
> functionin temperatures range like hot or cold temperature and another
> one to handle monitoring trip point. The FSM notifies via interrupts
> when a trip point is crossed.
> 
> The interrupt is managed at the thermal controller level, so when an
> interrupt occurs, the driver has to find out which sensor triggered
> such an interrupt.
> 
> The sampling of the thermal can be filtered or immediate. For the
> former, the LVTS measures several points and applies a low pass
> filter.
> 
> Signed-off-by: Balsam CHIHI <bchihi at baylibre.com>
> ---
>   drivers/thermal/mediatek/Kconfig            |   15 +
>   drivers/thermal/mediatek/Makefile           |    1 +
>   drivers/thermal/mediatek/lvts_thermal.c     | 1244 +++++++++++++++++++
>   include/dt-bindings/thermal/mediatek-lvts.h |   19 +
>   4 files changed, 1279 insertions(+)
>   create mode 100644 drivers/thermal/mediatek/lvts_thermal.c
>   create mode 100644 include/dt-bindings/thermal/mediatek-lvts.h
> 

..snip..

> +
> +static int lvts_set_trips(struct thermal_zone_device *tz, int low, int high)
> +{
> +	struct lvts_sensor *lvts_sensor = tz->devdata;
> +	void __iomem *base = lvts_sensor->base;
> +	u32 raw_low = lvts_temp_to_raw(low);
> +	u32 raw_high = lvts_temp_to_raw(high);
> +
> +	/*
> +	 * Hot to normal temperature threshold
> +	 *
> +	 * LVTS_H2NTHRE
> +	 *
> +	 * Bits:
> +	 *
> +	 * 14-0 : Raw temperature for threshold
> +	 */
> +	if (low != -INT_MAX) {
> +		dev_dbg(&tz->device, "Setting low limit temperature interrupt: %d\n", low);
> +		writel(raw_low, LVTS_H2NTHRE(base));
> +	}
> +
> +	/*
> +	 * Hot temperature threshold
> +	 *
> +	 * LVTS_HTHRE
> +	 *
> +	 * Bits:
> +	 *
> +	 * 14-0 : Raw temperature for threshold
> +	 */
> +	dev_dbg(&tz->device, "Setting high limit temperature interrupt: %d\n", high);
> +	writel(raw_high, LVTS_HTHRE(base));
> +
> +	return 0;
> +}
> +
> +static irqreturn_t lvts_ctrl_irq_handler(struct lvts_ctrl *lvts_ctrl)
> +{
> +	irqreturn_t iret = IRQ_NONE;
> +	u32 value, masks[] = { 0x0009001F, 0X000881F0, 0x00247C00, 0x1FC00000 };

Please, no magic numbers around.

> +	int i;
> +
> +	/*
> +	 * Interrupt monitoring status
> +	 *
> +	 * LVTS_MONINTST
> +	 *
> +	 * Bits:

You're describing the register with nice words, but there's another way to do
the same that will be even more effective.

/*
  * LVTS MONINT: Interrupt Monitoring register
  * Each bit describes the enable status of per-sensor interrupts.
  */
#define LVTS_MONINT_THRES_COLD	BIT(0)	/* Cold threshold */
#define LVTS_MONINT_THRES_HOT	BIT(1)	/* Hot threshold */
#define LVTS_MONINT_OFFST_LOW	BIT(2)	/* Low offset */
#define LVTS_MONINT_OFFST_HIGH	BIT(3)	/* High offset */
#define LVTS_MONINT_OFFST_NTH	BIT(4)	/* Normal To Hot */
#define EVERYTHING_ELSE ........................

#define LVTS_MONINT_SNS0_MASK	GENMASK( ... )
#define LVTS_MONINT_SNS1_MASK	GENMASK .....

/* Find a better name for this one */
#define LVTS_MONINT_EN_IRQS	( LVTS_MONINT_THRES_COLD | LVTS_MONINT_THRES_HOT |
				  LVTS_MONINT_OFFST_LOW ..... etc etc)




> +	 *
> +	 * 31 : Interrupt for stage 3
> +	 * 30 : Interrupt for stage 2
> +	 * 29 : Interrupt for state 1
> +	 * 28 : Interrupt using filter on sensor 3
> +	 *
> +	 * 27 : Interrupt using immediate on sensor 3
> +	 * 26 : Interrupt normal to hot on sensor 3
> +	 * 25 : Interrupt high offset on sensor 3
> +	 * 24 : Interrupt low offset on sensor 3
> +	 *
> +	 * 23 : Interrupt hot threshold on sensor 3
> +	 * 22 : Interrupt cold threshold on sensor 3
> +	 * 21 : Interrupt using filter on sensor 2
> +	 * 20 : Interrupt using filter on sensor 1
> +	 *
> +	 * 19 : Interrupt using filter on sensor 0
> +	 * 18 : Interrupt using immediate on sensor 2
> +	 * 17 : Interrupt using immediate on sensor 1
> +	 * 16 : Interrupt using immediate on sensor 0
> +	 *
> +	 * 15 : Interrupt device access timeout interrupt
> +	 * 14 : Interrupt normal to hot on sensor 2
> +	 * 13 : Interrupt high offset interrupt on sensor 2
> +	 * 12 : Interrupt low offset interrupt on sensor 2
> +	 *
> +	 * 11 : Interrupt hot threshold on sensor 2
> +	 * 10 : Interrupt cold threshold on sensor 2
> +	 *  9 : Interrupt normal to hot on sensor 1
> +	 *  8 : Interrupt high offset interrupt on sensor 1
> +	 *
> +	 *  7 : Interrupt low offset interrupt on sensor 1
> +	 *  6 : Interrupt hot threshold on sensor 1
> +	 *  5 : Interrupt cold threshold on sensor 1
> +	 *  4 : Interrupt normal to hot on sensor 0
> +	 *
> +	 *  3 : Interrupt high offset interrupt on sensor 0
> +	 *  2 : Interrupt low offset interrupt on sensor 0
> +	 *  1 : Interrupt hot threshold on sensor 0
> +	 *  0 : Interrupt cold threshold on sensor 0
> +	 *
> +	 * We are interested in the sensor(s) responsible of the
> +	 * interrupt event. We update the thermal framework with the
> +	 * thermal zone associated with the sensor. The framework will
> +	 * take care of the rest whatever the kind of interrupt, we
> +	 * are only interested in which sensor raised the interrupt.
> +	 *
> +	 * sensor 3 interrupt: 0001 1111 1100 0000 0000 0000 0000 0000
> +	 *                  => 0x1FC00000
> +	 * sensor 2 interrupt: 0000 0000 0010 0100 0111 1100 0000 0000
> +	 *                  => 0x00247C00
> +	 * sensor 1 interrupt: 0000 0000 0001 0001 0000 0011 1110 0000
> +	 *                  => 0X000881F0
> +	 * sensor 0 interrupt: 0000 0000 0000 1001 0000 0000 0001 1111
> +	 *                  => 0x0009001F
> +	 */
> +	value = readl(LVTS_MONINTSTS(lvts_ctrl->base));
> +
> +	/*
> +	 * Let's figure out which sensors raised the interrupt
> +	 *
> +	 * NOTE: the masks array must be ordered with the index
> +	 * corresponding to the sensor id eg. index=0, mask for
> +	 * sensor0.
> +	 */
> +	for (i = 0; i < ARRAY_SIZE(masks); i++) {
> +
> +		if (!(value & masks[i]))

Questions for you:

1. Are the masks always the same for all SoCs?
2. Do they correspond to what we set in lvts_irq_init()?

I'd expect future new SoCs to have different masks... and since lvts_irq_init() is
actually "playing with" interrupts register(s), with one of them (LVTS_MONINT)
having the same layout as this one, I would place all of the initialization in
that function instead.

This means that we'd initialize those masks at lvts_irq_init() time, in a struct
member, and read it back in this interrupt handler: like that, we get that a bit
more ordered and generally more readable.

> +			continue;
> +
> +		thermal_zone_device_update(lvts_ctrl->sensors[i].tz,
> +					   THERMAL_TRIP_VIOLATED);
> +		iret |= IRQ_HANDLED;
> +	}
> +
> +	/*
> +	 * Write back to clear the interrupt status (W1C)
> +	 */
> +	writel(value, LVTS_MONINTSTS(lvts_ctrl->base));
> +
> +	return iret;
> +}
> +
> +/*
> + * Temperature interrupt handler. Even if the driver supports more
> + * interrupt modes, we use the interrupt when the temperature crosses
> + * the hot threshold the way up and the way down (modulo the
> + * hysteresis).
> + *
> + * Each thermal domain has a couple of interrupts, one for hardware
> + * reset and another one for all the thermal events happening on the
> + * different sensors.
> + *
> + * The interrupt is configured for thermal events when crossing the
> + * hot temperature limit. At each interrupt, we check in every
> + * controller if there is an interrupt pending.
> + */
> +static irqreturn_t lvts_irq_handler(int irq, void *data)
> +{
> +	struct lvts_domain *lvts_td = data;
> +	irqreturn_t iret = IRQ_NONE;
> +	int i;
> +
> +	for (i = 0; i < lvts_td->num_lvts_ctrl; i++)
> +		iret |= lvts_ctrl_irq_handler(lvts_td->lvts_ctrl);

Please do *not* OR your function calls! While this is surely fine here in
this function and for this particular case, it's generally bad practice
and shall be avoided.

> +
> +	return iret;
> +}
> +
> +static struct thermal_zone_device_ops lvts_ops = {
> +	.get_temp = lvts_get_temp,
> +	.set_trips = lvts_set_trips,
> +};
> +

..snip..

> +
> +static int lvts_irq_init(struct lvts_ctrl *lvts_ctrl)
> +{
> +	u32 value;
> +
> +	/*
> +	 * LVTS_PROTCTL : Thermal Protection Sensor Selection
> +	 *
> +	 * Bits:
> +	 *
> +	 * 19-18 : Sensor to base the protection on
> +	 * 17-16 : Strategy:
> +	 *         00 : Average of 4 sensors
> +	 *         01 : Max of 4 sensors
> +	 *         10 : Selected sensor with bits 19-18
> +	 *         11 : Reserved
> +	 */
> +	value = BIT(16);
> +	writel(value, LVTS_PROTCTL(lvts_ctrl->base));
> +
> +	/*
> +	 * LVTS_PROTTA : Stage 1 temperature threshold
> +	 * LVTS_PROTTB : Stage 2 temperature threshold
> +	 * LVTS_PROTTC : Stage 3 temperature threshold
> +	 *
> +	 * Bits:
> +	 *
> +	 * 14-0: Raw temperature threshold
> +	 *
> +	 * writel(0x0, LVTS_PROTTA(lvts_ctrl->base));
> +	 * writel(0x0, LVTS_PROTTB(lvts_ctrl->base));
> +	 */
> +	writel(lvts_ctrl->hw_tshut_raw_temp, LVTS_PROTTC(lvts_ctrl->base));
> +
> +	/*
> +	 * LVTS_MONINT : Interrupt configuration register
> +	 *
> +	 * The LVTS_MONINT register layout is the same as the LVTS_MONINTSTS
> +	 * register, except we set the bits to enable the interrupt.
> +	 */
> +	value = 0x9FBF7BDE;

	u32 val;

	val = FIELD_PREP(LVTS_MONINT_SNS0_MASK, LVTS_MONINT_EN_IRQS);
	val |= FIELD_PREP(LVTS_MONINT_SNS1_MASK, LVTS_MONINT_EN_IRQS);

	... etc

	writel(val, ...... )

> +	writel(value, LVTS_MONINT(lvts_ctrl->base));
> +
> +	return 0;
> +}
> +

..snip..

> +
> +static int lvts_ctrl_initialize(struct device *dev, struct lvts_ctrl *lvts_ctrl)
> +{
> +	/*
> +	 * Write device mask: 0xC1030000
> +	 */
> +	u32 cmds[] = {
> +		0xC1030E01, 0xC1030CFC, 0xC1030A8C, 0xC103098D, 0xC10308F1,
> +		0xC10307A6, 0xC10306B8, 0xC1030500, 0xC1030420, 0xC1030300,
> +		0xC1030030, 0xC10300F6, 0xC1030050, 0xC1030060, 0xC10300AC,
> +		0xC10300FC, 0xC103009D, 0xC10300F1, 0xC10300E1
> +	};
...what is this long list of commands?

Why 0xC103_0000? Describe that please.

Also, why is this not a platform data constant?

Example:
struct lvts_plat {
	const struct lvts_ctrl_data *ctrl_data;
	u8 num_ctrl_data;
	const u16 device_mask;
	const u16 *init_cmds;
	u8 num_init_cmds;
}

where device_mask gets set to 0xc103 and init_cmds is an array containing
the low-16 (0x0e01, 0x0cfc, ...), and where this function would simply do
something like

	lvts_write_config(lvts_ctrl, plat->device_mask, init_cmds, num_init_cmds);

... and where lvts_write_config() does something like:

	for (i = 0; i < num_cmds; i++) {
		u32 val = device_mask | init_cmds[i];
		writel(val, LVTS_CONFIG ...)
	}
> +
> +	lvts_write_config(lvts_ctrl, cmds, ARRAY_SIZE(cmds));
> +
> +	return 0;
> +}
> +
> +static int lvts_ctrl_calibrate(struct device *dev, struct lvts_ctrl *lvts_ctrl)
> +{
> +	int i;
> +	void __iomem *lvts_edata[] = {

Can we constify this?

> +		LVTS_EDATA00(lvts_ctrl->base),
> +		LVTS_EDATA01(lvts_ctrl->base),
> +		LVTS_EDATA02(lvts_ctrl->base),
> +		LVTS_EDATA03(lvts_ctrl->base)
> +	};
> +
> +	/*
> +	 * LVTS_EDATA0X : Efuse calibration reference value for sensor X
> +	 *
> +	 * Bits:
> +	 *
> +	 * 20-0 : Efuse value for normalization data
> +	 */
> +	for (i = 0; i < LVTS_SENSOR_MAX; i++)
> +		writel(lvts_ctrl->calibration[i], lvts_edata[i]);
> +
> +	return 0;
> +}
> +
> +static int lvts_ctrl_configure(struct device *dev, struct lvts_ctrl *lvts_ctrl)
> +{
> +	u32 period_unit = (118 * 1000) / (256 * 38);

#define SOMETHING	118
#define SOMETHING_ELSE	1000
#define ....

const u32 period_unit = (SOMETHING * SOMETHING_ELSE) / ....

> +	u32 grp_interval = 1;
> +	u32 flt_interval = 1;
> +	u32 sensor_interval = 1;
> +	u32 hw_filter = 0x2;
> +	u32 value;
> +

...snip...

> +
> +static struct lvts_ctrl_data mt819x_lvts_data_ctrl[] = {

No wildcards. Please, rename this to give the name of the oldest SoC
that uses these values. Assuming that it is MT8192.... mt8192_lvts_data_ctrl[]

> +	{
> +		.cal_offset = { 0x4, 0x7 },
> +		.lvts_sensor = {
> +			{ .dt_id = MT819x_MCU_BIG_CPU0 },
> +			{ .dt_id = MT819x_MCU_BIG_CPU1 }
> +		},
> +		.num_lvts_sensor = 2,
> +		.offset = 0x0,
> +		.hw_tshut_temp = LVTS_HW_SHUTDOWN_MT8195,
> +	},
> +
> +	{
> +		.cal_offset = { 0xd, 0x10 },
> +		.lvts_sensor = {
> +			{ .dt_id = MT819x_MCU_BIG_CPU2 },
> +			{ .dt_id = MT819x_MCU_BIG_CPU3 }
> +		},
> +		.num_lvts_sensor = 2,
> +		.offset = 0x100,
> +		.hw_tshut_temp = LVTS_HW_SHUTDOWN_MT8195,
> +	},
> +
> +	{
> +		.cal_offset = { 0x16, 0x19, 0x1c, 0x1f },
> +		.lvts_sensor = {
> +			{ .dt_id = MT819x_MCU_LITTLE_CPU0 },
> +			{ .dt_id = MT819x_MCU_LITTLE_CPU1 },
> +			{ .dt_id = MT819x_MCU_LITTLE_CPU2 },
> +			{ .dt_id = MT819x_MCU_LITTLE_CPU3 }
> +		},
> +		.num_lvts_sensor = 4,
> +		.offset = 0x200,
> +		.hw_tshut_temp = LVTS_HW_SHUTDOWN_MT8195,
> +	}
> +};
> +
> +static struct lvts_data mt819x_lvts_mcu_data = {

Same here.

> +	.lvts_ctrl	= mt819x_lvts_data_ctrl,
> +	.num_lvts_ctrl	= ARRAY_SIZE(mt819x_lvts_data_ctrl),
> +};
> +
> +static const struct of_device_id lvts_of_match[] = {
> +	{ .compatible = "mediatek,mt8195-lvts-mcu", .data = &mt819x_lvts_mcu_data },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, lvts_of_match);
> +
> +static struct platform_driver lvts_driver = {
> +	.probe = lvts_probe,
> +	.remove = lvts_remove,
> +	.driver = {
> +		.name = "mtk-lvts-thermal",
> +		.of_match_table = lvts_of_match,
> +	},
> +};
> +module_platform_driver(lvts_driver);
> +
> +MODULE_AUTHOR("Balsam CHIHI <bchihi at baylibre.com>");
> +MODULE_DESCRIPTION("MediaTek LVTS Thermal Driver");
> +MODULE_LICENSE("GPL");
> diff --git a/include/dt-bindings/thermal/mediatek-lvts.h b/include/dt-bindings/thermal/mediatek-lvts.h
> new file mode 100644
> index 000000000000..80d060400236
> --- /dev/null
> +++ b/include/dt-bindings/thermal/mediatek-lvts.h

Bindings go in a different commit: add this in your patch [2/6], where you are
adding the yaml binding.

Also, please follow binding names: rename this file to mediatek,mt8192-lvts.h.

> @@ -0,0 +1,19 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Copyright (c) 2023 MediaTek Inc.
> + * Author: Balsam CHIHI <bchihi at baylibre.com>
> + */
> +
> +#ifndef __MEDIATEK_LVTS_DT_H
> +#define __MEDIATEK_LVTS_DT_H
> +
> +#define MT819x_MCU_BIG_CPU0	0

No wildcards: MT8192_MCU_BIG_CPU0

> +#define MT819x_MCU_BIG_CPU1	1
> +#define	MT819x_MCU_BIG_CPU2	2
> +#define	MT819x_MCU_BIG_CPU3	3
> +#define	MT819x_MCU_LITTLE_CPU0	4
> +#define	MT819x_MCU_LITTLE_CPU1	5
> +#define	MT819x_MCU_LITTLE_CPU2	6
> +#define	MT819x_MCU_LITTLE_CPU3	7
> +
> +#endif /* __MEDIATEK_LVTS_DT_H */

Regards,
Angelo



More information about the Linux-mediatek mailing list