diff options
Diffstat (limited to 'drivers/thermal')
70 files changed, 2937 insertions, 868 deletions
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index b10080d61860..810eeccedfba 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -436,6 +436,7 @@ config AMLOGIC_THERMAL tristate "Amlogic Thermal Support" default ARCH_MESON depends on OF && ARCH_MESON + depends on MESON_SM help If you say yes here you get support for Amlogic Thermal for G12 SoC Family. @@ -472,6 +473,8 @@ endmenu source "drivers/thermal/renesas/Kconfig" +source "drivers/thermal/spacemit/Kconfig" + source "drivers/thermal/tegra/Kconfig" config GENERIC_ADC_THERMAL diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index bb21e7ea7fc6..3b249195c088 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -65,6 +65,7 @@ obj-y += mediatek/ obj-$(CONFIG_GENERIC_ADC_THERMAL) += thermal-generic-adc.o obj-$(CONFIG_UNIPHIER_THERMAL) += uniphier_thermal.o obj-$(CONFIG_AMLOGIC_THERMAL) += amlogic_thermal.o +obj-y += spacemit/ obj-$(CONFIG_SPRD_THERMAL) += sprd_thermal.o obj-$(CONFIG_KHADAS_MCU_FAN_THERMAL) += khadas_mcu_fan.o obj-$(CONFIG_LOONGSON2_THERMAL) += loongson2_thermal.o diff --git a/drivers/thermal/airoha_thermal.c b/drivers/thermal/airoha_thermal.c index b9fd6bfc88e5..77b3a1af271f 100644 --- a/drivers/thermal/airoha_thermal.c +++ b/drivers/thermal/airoha_thermal.c @@ -18,6 +18,12 @@ #define EN7581_DOUT_TADC 0x2f8 #define EN7581_DOUT_TADC_MASK GENMASK(15, 0) +#define AN7583_MUX_SENSOR 0x2a0 +#define AN7583_LOAD_ADJ GENMASK(3, 2) +#define AN7583_MUX_TADC 0x2e4 +#define AN7583_MUX_TADC_MASK GENMASK(3, 1) +#define AN7583_DOUT_TADC 0x2f0 + /* PTP_THERMAL regs */ #define EN7581_TEMPMONCTL0 0x800 #define EN7581_SENSE3_EN BIT(3) @@ -181,6 +187,11 @@ #define EN7581_SCU_THERMAL_PROTECT_KEY 0x12 #define EN7581_SCU_THERMAL_MUX_DIODE1 0x7 +#define AN7583_SCU_THERMAL_PROTECT_KEY 0x80 +#define AN7583_NUM_SENSOR 3 + +#define AIROHA_THERMAL_NO_MUX_SENSOR -1 + /* Convert temp to raw value as read from ADC ((((temp / 100) - init) * slope) / 1000) + offset */ #define TEMP_TO_RAW(priv, temp) ((((((temp) / 100) - (priv)->init_temp) * \ (priv)->default_slope) / 1000) + \ @@ -193,42 +204,131 @@ #define AIROHA_MAX_SAMPLES 6 +/* + * AN7583 supports all these ADC mux but the original driver + * always checked temp with the AN7583_BGP_TEMP_SENSOR. + * Assume using the other sensor temperature is invalid and + * always read from AN7583_BGP_TEMP_SENSOR. + * + * On top of this it's defined that AN7583 supports 3 + * sensor: AN7583_BGP_TEMP_SENSOR, AN7583_GBE_TEMP_SENSOR, + * AN7583_CPU_TEMP_SENSOR. + * + * Provide the ADC mux for reference. + */ +enum an7583_thermal_adc_mux { + AN7583_BGP_TEMP_SENSOR, + AN7583_PAD_AVS, + AN7583_CORE_POWER, + AN7583_AVSDAC_OUT, + AN7583_VCM, + AN7583_GBE_TEMP_SENSOR, + AN7583_CPU_TEMP_SENSOR, + + AN7583_ADC_MUX_MAX, +}; + +enum an7583_thermal_diode_mux { + AN7583_D0_TADC, + AN7583_ZERO_TADC, + AN7583_D1_TADC, +}; + +enum airoha_thermal_chip_scu_field { + AIROHA_THERMAL_DOUT_TADC, + AIROHA_THERMAL_MUX_SENSOR, + AIROHA_THERMAL_MUX_TADC, + + /* keep last */ + AIROHA_THERMAL_FIELD_MAX, +}; + struct airoha_thermal_priv { - void __iomem *base; + struct regmap *map; struct regmap *chip_scu; + struct regmap_field *chip_scu_fields[AIROHA_THERMAL_FIELD_MAX]; struct resource scu_adc_res; + u32 pllrg_protect; + int current_adc; + struct thermal_zone_device *tz; int init_temp; int default_slope; int default_offset; }; +struct airoha_thermal_soc_data { + u32 pllrg_protect; + + const struct thermal_zone_device_ops *thdev_ops; + int (*probe)(struct platform_device *pdev, + struct airoha_thermal_priv *priv); + int (*post_probe)(struct platform_device *pdev); +}; + +static const unsigned int an7583_thermal_coeff[AN7583_ADC_MUX_MAX] = { + [AN7583_BGP_TEMP_SENSOR] = 973, + [AN7583_GBE_TEMP_SENSOR] = 995, + [AN7583_CPU_TEMP_SENSOR] = 1035, +}; + +static const unsigned int an7583_thermal_slope[AN7583_ADC_MUX_MAX] = { + [AN7583_BGP_TEMP_SENSOR] = 7440, + [AN7583_GBE_TEMP_SENSOR] = 7620, + [AN7583_CPU_TEMP_SENSOR] = 8390, +}; + +static const unsigned int an7583_thermal_offset[AN7583_ADC_MUX_MAX] = { + [AN7583_BGP_TEMP_SENSOR] = 294, + [AN7583_GBE_TEMP_SENSOR] = 298, + [AN7583_CPU_TEMP_SENSOR] = 344, +}; + static int airoha_get_thermal_ADC(struct airoha_thermal_priv *priv) { u32 val; - regmap_read(priv->chip_scu, EN7581_DOUT_TADC, &val); - return FIELD_GET(EN7581_DOUT_TADC_MASK, val); + regmap_field_read(priv->chip_scu_fields[AIROHA_THERMAL_DOUT_TADC], + &val); + return val; } -static void airoha_init_thermal_ADC_mode(struct airoha_thermal_priv *priv) +static void airoha_set_thermal_mux(struct airoha_thermal_priv *priv, + int tdac_idx, int sensor_idx) { - u32 adc_mux, pllrg; + u32 pllrg; /* Save PLLRG current value */ regmap_read(priv->chip_scu, EN7581_PLLRG_PROTECT, &pllrg); - /* Give access to thermal regs */ - regmap_write(priv->chip_scu, EN7581_PLLRG_PROTECT, EN7581_SCU_THERMAL_PROTECT_KEY); - adc_mux = FIELD_PREP(EN7581_MUX_TADC, EN7581_SCU_THERMAL_MUX_DIODE1); - regmap_write(priv->chip_scu, EN7581_PWD_TADC, adc_mux); + /* Give access to Thermal regs */ + regmap_write(priv->chip_scu, EN7581_PLLRG_PROTECT, + priv->pllrg_protect); + + /* + * Configure Thermal Sensor mux to sensor_idx. + * (if not supported, sensor_idx is AIROHA_THERMAL_NO_MUX_SENSOR) + */ + if (sensor_idx != AIROHA_THERMAL_NO_MUX_SENSOR) + regmap_field_write(priv->chip_scu_fields[AIROHA_THERMAL_MUX_SENSOR], + sensor_idx); + + /* Configure Thermal ADC mux to tdac_idx */ + if (priv->current_adc != tdac_idx) { + regmap_field_write(priv->chip_scu_fields[AIROHA_THERMAL_MUX_TADC], + tdac_idx); + priv->current_adc = tdac_idx; + } /* Restore PLLRG value on exit */ regmap_write(priv->chip_scu, EN7581_PLLRG_PROTECT, pllrg); + + /* Sleep 10 ms for Thermal ADC to enable */ + usleep_range(10 * USEC_PER_MSEC, 11 * USEC_PER_MSEC); } -static int airoha_thermal_get_temp(struct thermal_zone_device *tz, int *temp) +static int en7581_thermal_get_temp(struct thermal_zone_device *tz, int *temp) { struct airoha_thermal_priv *priv = thermal_zone_device_priv(tz); int min_value, max_value, avg_value, value; @@ -253,7 +353,7 @@ static int airoha_thermal_get_temp(struct thermal_zone_device *tz, int *temp) return 0; } -static int airoha_thermal_set_trips(struct thermal_zone_device *tz, int low, +static int en7581_thermal_set_trips(struct thermal_zone_device *tz, int low, int high) { struct airoha_thermal_priv *priv = thermal_zone_device_priv(tz); @@ -265,44 +365,44 @@ static int airoha_thermal_set_trips(struct thermal_zone_device *tz, int low, RAW_TO_TEMP(priv, FIELD_MAX(EN7581_DOUT_TADC_MASK))); /* We offset the high temp of 1°C to trigger correct event */ - writel(TEMP_TO_RAW(priv, high) >> 4, - priv->base + EN7581_TEMPOFFSETH); + regmap_write(priv->map, EN7581_TEMPOFFSETH, + TEMP_TO_RAW(priv, high) >> 4); enable_monitor = true; } if (low != -INT_MAX) { /* Validate low and clamp it to a supported value */ - low = clamp_t(int, high, RAW_TO_TEMP(priv, 0), + low = clamp_t(int, low, RAW_TO_TEMP(priv, 0), RAW_TO_TEMP(priv, FIELD_MAX(EN7581_DOUT_TADC_MASK))); /* We offset the low temp of 1°C to trigger correct event */ - writel(TEMP_TO_RAW(priv, low) >> 4, - priv->base + EN7581_TEMPOFFSETL); + regmap_write(priv->map, EN7581_TEMPOFFSETL, + TEMP_TO_RAW(priv, low) >> 4); enable_monitor = true; } /* Enable sensor 0 monitor after trip are set */ if (enable_monitor) - writel(EN7581_SENSE0_EN, priv->base + EN7581_TEMPMONCTL0); + regmap_write(priv->map, EN7581_TEMPMONCTL0, EN7581_SENSE0_EN); return 0; } -static const struct thermal_zone_device_ops thdev_ops = { - .get_temp = airoha_thermal_get_temp, - .set_trips = airoha_thermal_set_trips, +static const struct thermal_zone_device_ops en7581_thdev_ops = { + .get_temp = en7581_thermal_get_temp, + .set_trips = en7581_thermal_set_trips, }; -static irqreturn_t airoha_thermal_irq(int irq, void *data) +static irqreturn_t en7581_thermal_irq(int irq, void *data) { struct airoha_thermal_priv *priv = data; enum thermal_notify_event event; bool update = false; - u32 status; + u32 status = 0; - status = readl(priv->base + EN7581_TEMPMONINTSTS); + regmap_read(priv->map, EN7581_TEMPMONINTSTS, &status); switch (status & (EN7581_HOFSINTSTS0 | EN7581_LOFSINTSTS0)) { case EN7581_HOFSINTSTS0: event = THERMAL_TRIP_VIOLATED; @@ -318,7 +418,7 @@ static irqreturn_t airoha_thermal_irq(int irq, void *data) } /* Reset Interrupt */ - writel(status, priv->base + EN7581_TEMPMONINTSTS); + regmap_write(priv->map, EN7581_TEMPMONINTSTS, status); if (update) thermal_zone_device_update(priv->tz, event); @@ -326,21 +426,21 @@ static irqreturn_t airoha_thermal_irq(int irq, void *data) return IRQ_HANDLED; } -static void airoha_thermal_setup_adc_val(struct device *dev, +static void en7581_thermal_setup_adc_val(struct device *dev, struct airoha_thermal_priv *priv) { - u32 efuse_calib_info, cpu_sensor; + u32 efuse_calib_info = 0; + u32 cpu_sensor = 0; - /* Setup thermal sensor to ADC mode and setup the mux to DIODE1 */ - airoha_init_thermal_ADC_mode(priv); - /* sleep 10 ms for ADC to enable */ - usleep_range(10 * USEC_PER_MSEC, 11 * USEC_PER_MSEC); + /* Setup Thermal Sensor to ADC mode and setup the mux to DIODE1 */ + airoha_set_thermal_mux(priv, EN7581_SCU_THERMAL_MUX_DIODE1, + AIROHA_THERMAL_NO_MUX_SENSOR); - efuse_calib_info = readl(priv->base + EN7581_EFUSE_TEMP_OFFSET_REG); + regmap_read(priv->map, EN7581_EFUSE_TEMP_OFFSET_REG, &efuse_calib_info); if (efuse_calib_info) { priv->default_offset = FIELD_GET(EN7581_EFUSE_TEMP_OFFSET, efuse_calib_info); /* Different slope are applied if the sensor is used for CPU or for package */ - cpu_sensor = readl(priv->base + EN7581_EFUSE_TEMP_CPU_SENSOR_REG); + regmap_read(priv->map, EN7581_EFUSE_TEMP_CPU_SENSOR_REG, &cpu_sensor); if (cpu_sensor) { priv->default_slope = EN7581_SLOPE_X100_DIO_DEFAULT; priv->init_temp = EN7581_INIT_TEMP_FTK_X10; @@ -356,11 +456,11 @@ static void airoha_thermal_setup_adc_val(struct device *dev, } } -static void airoha_thermal_setup_monitor(struct airoha_thermal_priv *priv) +static void en7581_thermal_setup_monitor(struct airoha_thermal_priv *priv) { /* Set measure mode */ - writel(FIELD_PREP(EN7581_MSRCTL0, EN7581_MSRCTL_6SAMPLE_MAX_MIX_AVG4), - priv->base + EN7581_TEMPMSRCTL0); + regmap_write(priv->map, EN7581_TEMPMSRCTL0, + FIELD_PREP(EN7581_MSRCTL0, EN7581_MSRCTL_6SAMPLE_MAX_MIX_AVG4)); /* * Configure ADC valid reading addr @@ -375,15 +475,15 @@ static void airoha_thermal_setup_monitor(struct airoha_thermal_priv *priv) * We set valid instead of volt as we don't enable valid/volt * split reading and AHB read valid addr in such case. */ - writel(priv->scu_adc_res.start + EN7581_DOUT_TADC, - priv->base + EN7581_TEMPADCVALIDADDR); + regmap_write(priv->map, EN7581_TEMPADCVALIDADDR, + priv->scu_adc_res.start + EN7581_DOUT_TADC); /* * Configure valid bit on a fake value of bit 16. The ADC outputs * max of 2 bytes for voltage. */ - writel(FIELD_PREP(EN7581_ADV_RD_VALID_POS, 16), - priv->base + EN7581_TEMPADCVALIDMASK); + regmap_write(priv->map, EN7581_TEMPADCVALIDMASK, + FIELD_PREP(EN7581_ADV_RD_VALID_POS, 16)); /* * AHB supports max 12 bytes for ADC voltage. Shift the read @@ -391,40 +491,53 @@ static void airoha_thermal_setup_monitor(struct airoha_thermal_priv *priv) * in the order of half a °C and is acceptable in the context * of triggering interrupt in critical condition. */ - writel(FIELD_PREP(EN7581_ADC_VOLTAGE_SHIFT, 4), - priv->base + EN7581_TEMPADCVOLTAGESHIFT); + regmap_write(priv->map, EN7581_TEMPADCVOLTAGESHIFT, + FIELD_PREP(EN7581_ADC_VOLTAGE_SHIFT, 4)); /* BUS clock is 300MHz counting unit is 3 * 68.64 * 256 = 52.715us */ - writel(FIELD_PREP(EN7581_PERIOD_UNIT, 3), - priv->base + EN7581_TEMPMONCTL1); + regmap_write(priv->map, EN7581_TEMPMONCTL1, + FIELD_PREP(EN7581_PERIOD_UNIT, 3)); /* * filt interval is 1 * 52.715us = 52.715us, * sen interval is 379 * 52.715us = 19.97ms */ - writel(FIELD_PREP(EN7581_FILT_INTERVAL, 1) | - FIELD_PREP(EN7581_FILT_INTERVAL, 379), - priv->base + EN7581_TEMPMONCTL2); + regmap_write(priv->map, EN7581_TEMPMONCTL2, + FIELD_PREP(EN7581_FILT_INTERVAL, 1) | + FIELD_PREP(EN7581_SEN_INTERVAL, 379)); /* AHB poll is set to 146 * 68.64 = 10.02us */ - writel(FIELD_PREP(EN7581_ADC_POLL_INTVL, 146), - priv->base + EN7581_TEMPAHBPOLL); + regmap_write(priv->map, EN7581_TEMPAHBPOLL, + FIELD_PREP(EN7581_ADC_POLL_INTVL, 146)); } -static int airoha_thermal_probe(struct platform_device *pdev) +static const struct regmap_config en7581_thermal_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, +}; + +static const struct reg_field en7581_chip_scu_fields[AIROHA_THERMAL_FIELD_MAX] = { + [AIROHA_THERMAL_DOUT_TADC] = REG_FIELD(EN7581_DOUT_TADC, 0, 15), + [AIROHA_THERMAL_MUX_TADC] = REG_FIELD(EN7581_PWD_TADC, 1, 3), +}; + +static int en7581_thermal_probe(struct platform_device *pdev, + struct airoha_thermal_priv *priv) { - struct airoha_thermal_priv *priv; struct device_node *chip_scu_np; struct device *dev = &pdev->dev; - int irq, ret; + void __iomem *base; + int i, irq, ret; - priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); - priv->base = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(priv->base)) - return PTR_ERR(priv->base); + priv->map = devm_regmap_init_mmio(dev, base, + &en7581_thermal_regmap_config); + if (IS_ERR(priv->map)) + return PTR_ERR(priv->map); chip_scu_np = of_parse_phandle(dev->of_node, "airoha,chip-scu", 0); if (!chip_scu_np) @@ -434,6 +547,23 @@ static int airoha_thermal_probe(struct platform_device *pdev) if (IS_ERR(priv->chip_scu)) return PTR_ERR(priv->chip_scu); + for (i = 0; i < AIROHA_THERMAL_FIELD_MAX; i++) { + struct regmap_field *field; + + /* Skip registering MUX_SENSOR field as not supported */ + if (i == AIROHA_THERMAL_MUX_SENSOR) + continue; + + field = devm_regmap_field_alloc(dev, priv->chip_scu, + en7581_chip_scu_fields[i]); + if (IS_ERR(field)) { + of_node_put(chip_scu_np); + return PTR_ERR(field); + } + + priv->chip_scu_fields[i] = field; + } + of_address_to_resource(chip_scu_np, 0, &priv->scu_adc_res); of_node_put(chip_scu_np); @@ -442,18 +572,122 @@ static int airoha_thermal_probe(struct platform_device *pdev) return irq; ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, - airoha_thermal_irq, IRQF_ONESHOT, + en7581_thermal_irq, IRQF_ONESHOT, pdev->name, priv); - if (ret) { - dev_err(dev, "Can't get interrupt working.\n"); + if (ret) return ret; + + en7581_thermal_setup_monitor(priv); + en7581_thermal_setup_adc_val(dev, priv); + + return 0; +} + +static int en7581_thermal_post_probe(struct platform_device *pdev) +{ + struct airoha_thermal_priv *priv = platform_get_drvdata(pdev); + + /* Enable LOW and HIGH interrupt (if supported) */ + regmap_write(priv->map, EN7581_TEMPMONINT, + EN7581_HOFSINTEN0 | EN7581_LOFSINTEN0); + + return 0; +} + +static int an7583_thermal_get_temp(struct thermal_zone_device *tz, int *temp) +{ + struct airoha_thermal_priv *priv = thermal_zone_device_priv(tz); + int sensor_idx; + int delta_diode, delta_gain; + int coeff, slope, offset; + + int diode_zero, diode_d0, diode_d1; + + /* Always read sensor AN7583_BGP_TEMP_SENSOR */ + sensor_idx = AN7583_BGP_TEMP_SENSOR; + + coeff = an7583_thermal_coeff[sensor_idx]; + slope = an7583_thermal_slope[sensor_idx]; + offset = an7583_thermal_offset[sensor_idx]; + + airoha_set_thermal_mux(priv, AN7583_ZERO_TADC, sensor_idx); + diode_zero = airoha_get_thermal_ADC(priv); + airoha_set_thermal_mux(priv, AN7583_D0_TADC, sensor_idx); + diode_d0 = airoha_get_thermal_ADC(priv); + airoha_set_thermal_mux(priv, AN7583_D1_TADC, sensor_idx); + diode_d1 = airoha_get_thermal_ADC(priv); + + delta_diode = diode_d1 - diode_d0; + delta_gain = (delta_diode * coeff) / 100 + (diode_zero - diode_d1); + if (!delta_gain) + return -EINVAL; + + *temp = (slope * delta_diode * 10) / delta_gain - offset * 10; + *temp *= 100; + + return 0; +} + +static const struct thermal_zone_device_ops an7583_tz_ops = { + .get_temp = an7583_thermal_get_temp, +}; + +static const struct reg_field an7583_chip_scu_fields[AIROHA_THERMAL_FIELD_MAX] = { + [AIROHA_THERMAL_DOUT_TADC] = REG_FIELD(AN7583_DOUT_TADC, 0, 31), + [AIROHA_THERMAL_MUX_TADC] = REG_FIELD(AN7583_MUX_TADC, 1, 3), + [AIROHA_THERMAL_MUX_SENSOR] = REG_FIELD(AN7583_MUX_SENSOR, 2, 3), +}; + +static int an7583_thermal_probe(struct platform_device *pdev, + struct airoha_thermal_priv *priv) +{ + struct device *dev = &pdev->dev; + int i; + + priv->chip_scu = device_node_to_regmap(dev->of_node); + if (IS_ERR(priv->chip_scu)) + return PTR_ERR(priv->chip_scu); + + for (i = 0; i < AIROHA_THERMAL_FIELD_MAX; i++) { + struct regmap_field *field; + + field = devm_regmap_field_alloc(dev, priv->chip_scu, + an7583_chip_scu_fields[i]); + if (IS_ERR(field)) + return PTR_ERR(field); + + priv->chip_scu_fields[i] = field; } - airoha_thermal_setup_monitor(priv); - airoha_thermal_setup_adc_val(dev, priv); + return 0; +} + +static int airoha_thermal_probe(struct platform_device *pdev) +{ + const struct airoha_thermal_soc_data *soc_data; + struct airoha_thermal_priv *priv; + struct device *dev = &pdev->dev; + int ret; + + soc_data = device_get_match_data(dev); + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->pllrg_protect = soc_data->pllrg_protect; + priv->current_adc = -1; + + if (!soc_data->probe) + return -EINVAL; + + ret = soc_data->probe(pdev, priv); + if (ret) + return ret; /* register of thermal sensor and get info from DT */ - priv->tz = devm_thermal_of_zone_register(dev, 0, priv, &thdev_ops); + priv->tz = devm_thermal_of_zone_register(dev, 0, priv, + soc_data->thdev_ops); if (IS_ERR(priv->tz)) { dev_err(dev, "register thermal zone sensor failed\n"); return PTR_ERR(priv->tz); @@ -461,15 +695,25 @@ static int airoha_thermal_probe(struct platform_device *pdev) platform_set_drvdata(pdev, priv); - /* Enable LOW and HIGH interrupt */ - writel(EN7581_HOFSINTEN0 | EN7581_LOFSINTEN0, - priv->base + EN7581_TEMPMONINT); - - return 0; + return soc_data->post_probe ? soc_data->post_probe(pdev) : 0; } +static const struct airoha_thermal_soc_data en7581_data = { + .pllrg_protect = EN7581_SCU_THERMAL_PROTECT_KEY, + .thdev_ops = &en7581_thdev_ops, + .probe = &en7581_thermal_probe, + .post_probe = &en7581_thermal_post_probe, +}; + +static const struct airoha_thermal_soc_data an7583_data = { + .pllrg_protect = AN7583_SCU_THERMAL_PROTECT_KEY, + .thdev_ops = &an7583_tz_ops, + .probe = &an7583_thermal_probe, +}; + static const struct of_device_id airoha_thermal_match[] = { - { .compatible = "airoha,en7581-thermal" }, + { .compatible = "airoha,en7581-thermal", .data = &en7581_data }, + { .compatible = "airoha,an7583-chip-scu", .data = &an7583_data }, {}, }; MODULE_DEVICE_TABLE(of, airoha_thermal_match); diff --git a/drivers/thermal/amlogic_thermal.c b/drivers/thermal/amlogic_thermal.c index 5448d772db12..a0b530624b60 100644 --- a/drivers/thermal/amlogic_thermal.c +++ b/drivers/thermal/amlogic_thermal.c @@ -25,6 +25,7 @@ #include <linux/platform_device.h> #include <linux/regmap.h> #include <linux/thermal.h> +#include <linux/firmware/meson/meson_sm.h> #include "thermal_hwmon.h" @@ -84,12 +85,14 @@ struct amlogic_thermal_soc_calib_data { * @u_efuse_off: register offset to read fused calibration value * @calibration_parameters: calibration parameters structure pointer * @regmap_config: regmap config for the device + * @use_sm: read data from secure monitor instead of efuse * This structure is required for configuration of amlogic thermal driver. */ struct amlogic_thermal_data { int u_efuse_off; const struct amlogic_thermal_soc_calib_data *calibration_parameters; const struct regmap_config *regmap_config; + bool use_sm; }; struct amlogic_thermal { @@ -100,6 +103,8 @@ struct amlogic_thermal { struct clk *clk; struct thermal_zone_device *tzd; u32 trim_info; + struct meson_sm_firmware *sm_fw; + u32 tsensor_id; }; /* @@ -133,26 +138,6 @@ static int amlogic_thermal_code_to_millicelsius(struct amlogic_thermal *pdata, return temp; } -static int amlogic_thermal_initialize(struct amlogic_thermal *pdata) -{ - int ret = 0; - int ver; - - regmap_read(pdata->sec_ao_map, pdata->data->u_efuse_off, - &pdata->trim_info); - - ver = TSENSOR_TRIM_VERSION(pdata->trim_info); - - if ((ver & TSENSOR_TRIM_CALIB_VALID_MASK) == 0) { - ret = -EINVAL; - dev_err(&pdata->pdev->dev, - "tsensor thermal calibration not supported: 0x%x!\n", - ver); - } - - return ret; -} - static int amlogic_thermal_enable(struct amlogic_thermal *data) { int ret; @@ -190,6 +175,67 @@ static int amlogic_thermal_get_temp(struct thermal_zone_device *tz, int *temp) return 0; } +static int amlogic_thermal_probe_sm(struct platform_device *pdev, + struct amlogic_thermal *pdata) +{ + struct device *dev = &pdev->dev; + struct of_phandle_args ph_args; + int ret; + + ret = of_parse_phandle_with_fixed_args(pdev->dev.of_node, + "amlogic,secure-monitor", + 1, 0, &ph_args); + if (ret) + return ret; + + if (!ph_args.np) { + dev_err(dev, "Failed to parse secure monitor phandle\n"); + return -ENODEV; + } + + pdata->sm_fw = meson_sm_get(ph_args.np); + of_node_put(ph_args.np); + if (!pdata->sm_fw) { + dev_err(dev, "Failed to get secure monitor firmware\n"); + return -EPROBE_DEFER; + } + + pdata->tsensor_id = ph_args.args[0]; + + return meson_sm_get_thermal_calib(pdata->sm_fw, + &pdata->trim_info, + pdata->tsensor_id); +} + +static int amlogic_thermal_probe_syscon(struct platform_device *pdev, + struct amlogic_thermal *pdata) +{ + struct device *dev = &pdev->dev; + int ver; + + pdata->sec_ao_map = + syscon_regmap_lookup_by_phandle(pdev->dev.of_node, + "amlogic,ao-secure"); + if (IS_ERR(pdata->sec_ao_map)) { + dev_err(dev, "syscon regmap lookup failed.\n"); + return PTR_ERR(pdata->sec_ao_map); + } + + regmap_read(pdata->sec_ao_map, pdata->data->u_efuse_off, + &pdata->trim_info); + + ver = TSENSOR_TRIM_VERSION(pdata->trim_info); + + if ((ver & TSENSOR_TRIM_CALIB_VALID_MASK) == 0) { + dev_err(&pdata->pdev->dev, + "tsensor thermal calibration not supported: 0x%x!\n", + ver); + return -EINVAL; + } + + return 0; +} + static const struct thermal_zone_device_ops amlogic_thermal_ops = { .get_temp = amlogic_thermal_get_temp, }; @@ -226,6 +272,12 @@ static const struct amlogic_thermal_data amlogic_thermal_a1_cpu_param = { .regmap_config = &amlogic_thermal_regmap_config_g12a, }; +static const struct amlogic_thermal_data amlogic_thermal_t7_param = { + .use_sm = true, + .calibration_parameters = &amlogic_thermal_g12a, + .regmap_config = &amlogic_thermal_regmap_config_g12a, +}; + static const struct of_device_id of_amlogic_thermal_match[] = { { .compatible = "amlogic,g12a-ddr-thermal", @@ -239,6 +291,10 @@ static const struct of_device_id of_amlogic_thermal_match[] = { .compatible = "amlogic,a1-cpu-thermal", .data = &amlogic_thermal_a1_cpu_param, }, + { + .compatible = "amlogic,t7-thermal", + .data = &amlogic_thermal_t7_param, + }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, of_amlogic_thermal_match); @@ -271,12 +327,12 @@ static int amlogic_thermal_probe(struct platform_device *pdev) if (IS_ERR(pdata->clk)) return dev_err_probe(dev, PTR_ERR(pdata->clk), "failed to get clock\n"); - pdata->sec_ao_map = syscon_regmap_lookup_by_phandle - (pdev->dev.of_node, "amlogic,ao-secure"); - if (IS_ERR(pdata->sec_ao_map)) { - dev_err(dev, "syscon regmap lookup failed.\n"); - return PTR_ERR(pdata->sec_ao_map); - } + if (pdata->data->use_sm) + ret = amlogic_thermal_probe_sm(pdev, pdata); + else + ret = amlogic_thermal_probe_syscon(pdev, pdata); + if (ret) + return ret; pdata->tzd = devm_thermal_of_zone_register(&pdev->dev, 0, @@ -290,10 +346,6 @@ static int amlogic_thermal_probe(struct platform_device *pdev) devm_thermal_add_hwmon_sysfs(&pdev->dev, pdata->tzd); - ret = amlogic_thermal_initialize(pdata); - if (ret) - return ret; - ret = amlogic_thermal_enable(pdata); return ret; diff --git a/drivers/thermal/armada_thermal.c b/drivers/thermal/armada_thermal.c index c2fbdb534f61..ff63b4f2b977 100644 --- a/drivers/thermal/armada_thermal.c +++ b/drivers/thermal/armada_thermal.c @@ -4,6 +4,7 @@ * * Copyright (C) 2013 Marvell */ +#include <linux/bitfield.h> #include <linux/device.h> #include <linux/err.h> #include <linux/io.h> @@ -20,41 +21,35 @@ #include <linux/interrupt.h> /* Thermal Manager Control and Status Register */ -#define PMU_TDC0_SW_RST_MASK (0x1 << 1) -#define PMU_TM_DISABLE_OFFS 0 -#define PMU_TM_DISABLE_MASK (0x1 << PMU_TM_DISABLE_OFFS) -#define PMU_TDC0_REF_CAL_CNT_OFFS 11 -#define PMU_TDC0_REF_CAL_CNT_MASK (0x1ff << PMU_TDC0_REF_CAL_CNT_OFFS) -#define PMU_TDC0_OTF_CAL_MASK (0x1 << 30) -#define PMU_TDC0_START_CAL_MASK (0x1 << 25) - -#define A375_UNIT_CONTROL_SHIFT 27 -#define A375_UNIT_CONTROL_MASK 0x7 +#define PMU_TDC0_SW_RST_MASK BIT(1) +#define PMU_TM_DISABLE_MASK BIT(0) +#define PMU_TDC0_REF_CAL_CNT_MASK GENMASK(19, 11) +#define PMU_TDC0_OTF_CAL_MASK BIT(30) +#define PMU_TDC0_START_CAL_MASK BIT(25) + +#define A375_UNIT_CONTROL_MASK GENMASK(29, 27) #define A375_READOUT_INVERT BIT(15) #define A375_HW_RESETn BIT(8) /* Errata fields */ -#define CONTROL0_TSEN_TC_TRIM_MASK 0x7 +#define CONTROL0_TSEN_TC_TRIM_MASK GENMASK(2, 0) #define CONTROL0_TSEN_TC_TRIM_VAL 0x3 #define CONTROL0_TSEN_START BIT(0) #define CONTROL0_TSEN_RESET BIT(1) #define CONTROL0_TSEN_ENABLE BIT(2) #define CONTROL0_TSEN_AVG_BYPASS BIT(6) -#define CONTROL0_TSEN_CHAN_SHIFT 13 -#define CONTROL0_TSEN_CHAN_MASK 0xF -#define CONTROL0_TSEN_OSR_SHIFT 24 -#define CONTROL0_TSEN_OSR_MAX 0x3 -#define CONTROL0_TSEN_MODE_SHIFT 30 +#define CONTROL0_TSEN_CHAN_MASK GENMASK(16, 13) +#define CONTROL0_TSEN_OSR_MASK GENMASK(25, 24) +#define CONTROL0_TSEN_OSR_MAX FIELD_MAX(CONTROL0_TSEN_OSR_MASK) +#define CONTROL0_TSEN_MODE_MASK GENMASK(31, 30) #define CONTROL0_TSEN_MODE_EXTERNAL 0x2 -#define CONTROL0_TSEN_MODE_MASK 0x3 -#define CONTROL1_TSEN_AVG_MASK 0x7 +#define CONTROL1_TSEN_AVG_MASK GENMASK(2, 0) #define CONTROL1_EXT_TSEN_SW_RESET BIT(7) #define CONTROL1_EXT_TSEN_HW_RESETn BIT(8) #define CONTROL1_TSEN_INT_EN BIT(25) -#define CONTROL1_TSEN_SELECT_OFF 21 -#define CONTROL1_TSEN_SELECT_MASK 0x3 +#define CONTROL1_TSEN_SELECT_MASK GENMASK(22, 21) #define STATUS_POLL_PERIOD_US 1000 #define STATUS_POLL_TIMEOUT_US 100000 @@ -140,23 +135,21 @@ static void armadaxp_init(struct platform_device *pdev, u32 reg; regmap_read(priv->syscon, data->syscon_control1_off, ®); - reg |= PMU_TDC0_OTF_CAL_MASK; + FIELD_MODIFY(PMU_TDC0_OTF_CAL_MASK, ®, 1); /* Reference calibration value */ - reg &= ~PMU_TDC0_REF_CAL_CNT_MASK; - reg |= (0xf1 << PMU_TDC0_REF_CAL_CNT_OFFS); + FIELD_MODIFY(PMU_TDC0_REF_CAL_CNT_MASK, ®, 0xf1); /* Reset the sensor */ - reg |= PMU_TDC0_SW_RST_MASK; - + FIELD_MODIFY(PMU_TDC0_SW_RST_MASK, ®, 1); regmap_write(priv->syscon, data->syscon_control1_off, reg); - reg &= ~PMU_TDC0_SW_RST_MASK; + FIELD_MODIFY(PMU_TDC0_SW_RST_MASK, ®, 0); regmap_write(priv->syscon, data->syscon_control1_off, reg); /* Enable the sensor */ regmap_read(priv->syscon, data->syscon_status_off, ®); - reg &= ~PMU_TM_DISABLE_MASK; + FIELD_MODIFY(PMU_TM_DISABLE_MASK, ®, 0); regmap_write(priv->syscon, data->syscon_status_off, reg); } @@ -167,14 +160,13 @@ static void armada370_init(struct platform_device *pdev, u32 reg; regmap_read(priv->syscon, data->syscon_control1_off, ®); - reg |= PMU_TDC0_OTF_CAL_MASK; + FIELD_MODIFY(PMU_TDC0_OTF_CAL_MASK, ®, 1); /* Reference calibration value */ - reg &= ~PMU_TDC0_REF_CAL_CNT_MASK; - reg |= (0xf1 << PMU_TDC0_REF_CAL_CNT_OFFS); + FIELD_MODIFY(PMU_TDC0_REF_CAL_CNT_MASK, ®, 0xf1); /* Reset the sensor */ - reg &= ~PMU_TDC0_START_CAL_MASK; + FIELD_MODIFY(PMU_TDC0_START_CAL_MASK, ®, 0); regmap_write(priv->syscon, data->syscon_control1_off, reg); @@ -188,14 +180,14 @@ static void armada375_init(struct platform_device *pdev, u32 reg; regmap_read(priv->syscon, data->syscon_control1_off, ®); - reg &= ~(A375_UNIT_CONTROL_MASK << A375_UNIT_CONTROL_SHIFT); - reg &= ~A375_READOUT_INVERT; - reg &= ~A375_HW_RESETn; + FIELD_MODIFY(A375_UNIT_CONTROL_MASK, ®, 0); + FIELD_MODIFY(A375_READOUT_INVERT, ®, 0); + FIELD_MODIFY(A375_HW_RESETn, ®, 0); regmap_write(priv->syscon, data->syscon_control1_off, reg); msleep(20); - reg |= A375_HW_RESETn; + FIELD_MODIFY(A375_HW_RESETn, ®, 1); regmap_write(priv->syscon, data->syscon_control1_off, reg); msleep(50); @@ -220,14 +212,13 @@ static void armada380_init(struct platform_device *pdev, /* Disable the HW/SW reset */ regmap_read(priv->syscon, data->syscon_control1_off, ®); - reg |= CONTROL1_EXT_TSEN_HW_RESETn; - reg &= ~CONTROL1_EXT_TSEN_SW_RESET; + FIELD_MODIFY(CONTROL1_EXT_TSEN_HW_RESETn, ®, 1); + FIELD_MODIFY(CONTROL1_EXT_TSEN_SW_RESET, ®, 0); regmap_write(priv->syscon, data->syscon_control1_off, reg); /* Set Tsen Tc Trim to correct default value (errata #132698) */ regmap_read(priv->syscon, data->syscon_control0_off, ®); - reg &= ~CONTROL0_TSEN_TC_TRIM_MASK; - reg |= CONTROL0_TSEN_TC_TRIM_VAL; + FIELD_MODIFY(CONTROL0_TSEN_TC_TRIM_MASK, ®, CONTROL0_TSEN_TC_TRIM_VAL); regmap_write(priv->syscon, data->syscon_control0_off, reg); } @@ -238,14 +229,15 @@ static void armada_ap80x_init(struct platform_device *pdev, u32 reg; regmap_read(priv->syscon, data->syscon_control0_off, ®); - reg &= ~CONTROL0_TSEN_RESET; - reg |= CONTROL0_TSEN_START | CONTROL0_TSEN_ENABLE; + FIELD_MODIFY(CONTROL0_TSEN_RESET, ®, 0); + FIELD_MODIFY(CONTROL0_TSEN_START, ®, 1); + FIELD_MODIFY(CONTROL0_TSEN_ENABLE, ®, 1); /* Sample every ~2ms */ - reg |= CONTROL0_TSEN_OSR_MAX << CONTROL0_TSEN_OSR_SHIFT; + FIELD_MODIFY(CONTROL0_TSEN_OSR_MASK, ®, CONTROL0_TSEN_OSR_MAX); /* Enable average (2 samples by default) */ - reg &= ~CONTROL0_TSEN_AVG_BYPASS; + FIELD_MODIFY(CONTROL0_TSEN_AVG_BYPASS, ®, 0); regmap_write(priv->syscon, data->syscon_control0_off, reg); } @@ -260,13 +252,12 @@ static void armada_cp110_init(struct platform_device *pdev, /* Sample every ~2ms */ regmap_read(priv->syscon, data->syscon_control0_off, ®); - reg |= CONTROL0_TSEN_OSR_MAX << CONTROL0_TSEN_OSR_SHIFT; + FIELD_MODIFY(CONTROL0_TSEN_OSR_MASK, ®, CONTROL0_TSEN_OSR_MAX); regmap_write(priv->syscon, data->syscon_control0_off, reg); /* Average the output value over 2^1 = 2 samples */ regmap_read(priv->syscon, data->syscon_control1_off, ®); - reg &= ~CONTROL1_TSEN_AVG_MASK; - reg |= 1; + FIELD_MODIFY(CONTROL1_TSEN_AVG_MASK, ®, 1); regmap_write(priv->syscon, data->syscon_control1_off, reg); } @@ -313,7 +304,7 @@ armada_disable_overheat_interrupt(struct armada_thermal_priv *priv) u32 reg; regmap_read(priv->syscon, data->syscon_control1_off, ®); - reg &= ~CONTROL1_TSEN_INT_EN; + FIELD_MODIFY(CONTROL1_TSEN_INT_EN, ®, 0); regmap_write(priv->syscon, data->syscon_control1_off, reg); } @@ -331,20 +322,18 @@ static int armada_select_channel(struct armada_thermal_priv *priv, int channel) /* Stop the measurements */ regmap_read(priv->syscon, data->syscon_control0_off, &ctrl0); - ctrl0 &= ~CONTROL0_TSEN_START; + FIELD_MODIFY(CONTROL0_TSEN_START, &ctrl0, 0); regmap_write(priv->syscon, data->syscon_control0_off, ctrl0); - /* Reset the mode, internal sensor will be automatically selected */ - ctrl0 &= ~(CONTROL0_TSEN_MODE_MASK << CONTROL0_TSEN_MODE_SHIFT); - /* Other channels are external and should be selected accordingly */ if (channel) { /* Change the mode to external */ - ctrl0 |= CONTROL0_TSEN_MODE_EXTERNAL << - CONTROL0_TSEN_MODE_SHIFT; + FIELD_MODIFY(CONTROL0_TSEN_MODE_MASK, &ctrl0, CONTROL0_TSEN_MODE_EXTERNAL); /* Select the sensor */ - ctrl0 &= ~(CONTROL0_TSEN_CHAN_MASK << CONTROL0_TSEN_CHAN_SHIFT); - ctrl0 |= (channel - 1) << CONTROL0_TSEN_CHAN_SHIFT; + FIELD_MODIFY(CONTROL0_TSEN_CHAN_MASK, &ctrl0, channel - 1); + } else { + /* Reset the mode, internal sensor will be automatically selected */ + FIELD_MODIFY(CONTROL0_TSEN_MODE_MASK, &ctrl0, 0); } /* Actually set the mode/channel */ @@ -352,7 +341,7 @@ static int armada_select_channel(struct armada_thermal_priv *priv, int channel) priv->current_channel = channel; /* Re-start the measurements */ - ctrl0 |= CONTROL0_TSEN_START; + FIELD_MODIFY(CONTROL0_TSEN_START, &ctrl0, 1); regmap_write(priv->syscon, data->syscon_control0_off, ctrl0); /* @@ -722,7 +711,6 @@ static const struct regmap_config armada_thermal_regmap_config = { .reg_bits = 32, .reg_stride = 4, .val_bits = 32, - .fast_io = true, }; static int armada_thermal_probe_legacy(struct platform_device *pdev, @@ -913,11 +901,8 @@ static int armada_thermal_probe(struct platform_device *pdev) armada_overheat_isr, armada_overheat_isr_thread, 0, NULL, priv); - if (ret) { - dev_err(&pdev->dev, "Cannot request threaded IRQ %d\n", - irq); + if (ret) return ret; - } } /* diff --git a/drivers/thermal/broadcom/brcmstb_thermal.c b/drivers/thermal/broadcom/brcmstb_thermal.c index f46f2ddc174e..5e23f9e00847 100644 --- a/drivers/thermal/broadcom/brcmstb_thermal.c +++ b/drivers/thermal/broadcom/brcmstb_thermal.c @@ -16,6 +16,7 @@ #include <linux/irqreturn.h> #include <linux/interrupt.h> #include <linux/kernel.h> +#include <linux/minmax.h> #include <linux/module.h> #include <linux/of.h> #include <linux/platform_device.h> @@ -154,7 +155,7 @@ static int brcmstb_get_temp(struct thermal_zone_device *tz, int *temp) { struct brcmstb_thermal_priv *priv = thermal_zone_device_priv(tz); u32 val; - long t; + int t; val = __raw_readl(priv->tmon_base + AVS_TMON_STATUS); @@ -164,10 +165,7 @@ static int brcmstb_get_temp(struct thermal_zone_device *tz, int *temp) val = (val & AVS_TMON_STATUS_data_msk) >> AVS_TMON_STATUS_data_shift; t = avs_tmon_code_to_temp(priv, val); - if (t < 0) - *temp = 0; - else - *temp = t; + *temp = max(0, t); return 0; } @@ -358,8 +356,7 @@ static int brcmstb_thermal_probe(struct platform_device *pdev) IRQF_ONESHOT, DRV_NAME, priv); if (ret < 0) - return dev_err_probe(&pdev->dev, ret, - "could not request IRQ\n"); + return ret; } dev_info(&pdev->dev, "registered AVS TMON of-sensor driver\n"); diff --git a/drivers/thermal/cpufreq_cooling.c b/drivers/thermal/cpufreq_cooling.c index 32bf5ab44f4a..768859a7aed0 100644 --- a/drivers/thermal/cpufreq_cooling.c +++ b/drivers/thermal/cpufreq_cooling.c @@ -592,7 +592,7 @@ __cpufreq_cooling_register(struct device_node *np, if (!name) goto remove_qos_req; - cdev = thermal_of_cooling_device_register(np, name, cpufreq_cdev, + cdev = thermal_of_cooling_device_register(np, 0, name, cpufreq_cdev, cooling_ops); kfree(name); diff --git a/drivers/thermal/cpuidle_cooling.c b/drivers/thermal/cpuidle_cooling.c index 425f596614e8..bbd2e91cf5ab 100644 --- a/drivers/thermal/cpuidle_cooling.c +++ b/drivers/thermal/cpuidle_cooling.c @@ -207,7 +207,7 @@ static int __cpuidle_cooling_register(struct device_node *np, goto out_unregister; } - cdev = thermal_of_cooling_device_register(np, name, idle_cdev, + cdev = thermal_of_cooling_device_register(np, 0, name, idle_cdev, &cpuidle_cooling_ops); if (IS_ERR(cdev)) { ret = PTR_ERR(cdev); diff --git a/drivers/thermal/db8500_thermal.c b/drivers/thermal/db8500_thermal.c index 576f88b6a1b3..46005b476722 100644 --- a/drivers/thermal/db8500_thermal.c +++ b/drivers/thermal/db8500_thermal.c @@ -10,7 +10,7 @@ #include <linux/cpu_cooling.h> #include <linux/interrupt.h> -#include <linux/mfd/dbx500-prcmu.h> +#include <linux/mfd/db8500-prcmu.h> #include <linux/module.h> #include <linux/of.h> #include <linux/platform_device.h> @@ -82,7 +82,7 @@ static void db8500_thermal_update_config(struct db8500_thermal_zone *th, unsigned long next_low, unsigned long next_high) { - prcmu_stop_temp_sense(); + db8500_prcmu_stop_temp_sense(); th->cur_index = idx; th->interpolated_temp = (next_low + next_high)/2; @@ -91,8 +91,8 @@ static void db8500_thermal_update_config(struct db8500_thermal_zone *th, * The PRCMU accept absolute temperatures in celsius so divide * down the millicelsius with 1000 */ - prcmu_config_hotmon((u8)(next_low/1000), (u8)(next_high/1000)); - prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME); + db8500_prcmu_config_hotmon((u8)(next_low / 1000), (u8)(next_high / 1000)); + db8500_prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME); } static irqreturn_t prcmu_low_irq_handler(int irq, void *irq_data) @@ -167,10 +167,8 @@ static int db8500_thermal_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(dev, low_irq, NULL, prcmu_low_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT, "dbx500_temp_low", th); - if (ret < 0) { - dev_err(dev, "failed to allocate temp low irq\n"); + if (ret < 0) return ret; - } high_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_HIGH"); if (high_irq < 0) @@ -179,10 +177,8 @@ static int db8500_thermal_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(dev, high_irq, NULL, prcmu_high_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT, "dbx500_temp_high", th); - if (ret < 0) { - dev_err(dev, "failed to allocate temp high irq\n"); + if (ret < 0) return ret; - } /* register of thermal sensor and get info from DT */ th->tz = devm_thermal_of_zone_register(dev, 0, th, &thdev_ops); @@ -204,7 +200,7 @@ static int db8500_thermal_probe(struct platform_device *pdev) static int db8500_thermal_suspend(struct platform_device *pdev, pm_message_t state) { - prcmu_stop_temp_sense(); + db8500_prcmu_stop_temp_sense(); return 0; } diff --git a/drivers/thermal/devfreq_cooling.c b/drivers/thermal/devfreq_cooling.c index 597e86d16a4e..0330a8112832 100644 --- a/drivers/thermal/devfreq_cooling.c +++ b/drivers/thermal/devfreq_cooling.c @@ -454,7 +454,7 @@ of_devfreq_cooling_register_power(struct device_node *np, struct devfreq *df, if (!name) goto remove_qos_req; - cdev = thermal_of_cooling_device_register(np, name, dfc, ops); + cdev = thermal_of_cooling_device_register(np, 0, name, dfc, ops); kfree(name); if (IS_ERR(cdev)) { @@ -472,7 +472,8 @@ of_devfreq_cooling_register_power(struct device_node *np, struct devfreq *df, remove_qos_req: dev_pm_qos_remove_request(&dfc->req_max_freq); free_table: - kfree(dfc->freq_table); + if (!dfc->em_pd) + kfree(dfc->freq_table); free_dfc: kfree(dfc); diff --git a/drivers/thermal/gov_step_wise.c b/drivers/thermal/gov_step_wise.c index ea277c466d8d..4fa4377f0d42 100644 --- a/drivers/thermal/gov_step_wise.c +++ b/drivers/thermal/gov_step_wise.c @@ -65,14 +65,12 @@ static unsigned long get_target_state(struct thermal_instance *instance, min(instance->lower + 1, instance->upper), instance->upper); } else if (trend == THERMAL_TREND_DROPPING) { - if (cur_state <= instance->lower) - return THERMAL_NO_TARGET; - /* - * If 'throttle' is false, no mitigation is necessary, so - * request the lower state for this instance. + * If 'throttle' is false, no mitigation is necessary and + * passive polling is already deactivated, so clear this + * instance state by returning THERMAL_NO_TARGET. */ - return instance->lower; + return THERMAL_NO_TARGET; } return instance->target; diff --git a/drivers/thermal/hisi_thermal.c b/drivers/thermal/hisi_thermal.c index 4307161533a7..17ed0c5b7767 100644 --- a/drivers/thermal/hisi_thermal.c +++ b/drivers/thermal/hisi_thermal.c @@ -578,10 +578,8 @@ static int hisi_thermal_probe(struct platform_device *pdev) hisi_thermal_alarm_irq_thread, IRQF_ONESHOT, sensor->irq_name, sensor); - if (ret < 0) { - dev_err(dev, "Failed to request alarm irq: %d\n", ret); + if (ret < 0) return ret; - } ret = data->ops->enable_sensor(sensor); if (ret) { diff --git a/drivers/thermal/imx91_thermal.c b/drivers/thermal/imx91_thermal.c index 9b20be03d6de..274eee303142 100644 --- a/drivers/thermal/imx91_thermal.c +++ b/drivers/thermal/imx91_thermal.c @@ -17,6 +17,8 @@ #include <linux/thermal.h> #include <linux/units.h> +#include "thermal_hwmon.h" + #define REG_SET 0x4 #define REG_CLR 0x8 #define REG_TOG 0xc @@ -318,6 +320,8 @@ static int imx91_tmu_probe(struct platform_device *pdev) return dev_err_probe(dev, PTR_ERR(tmu->tzd), "failed to register thermal zone sensor\n"); + devm_thermal_add_hwmon_sysfs(dev, tmu->tzd); + irq = platform_get_irq(pdev, 0); if (irq < 0) return irq; @@ -327,7 +331,7 @@ static int imx91_tmu_probe(struct platform_device *pdev) IRQF_ONESHOT, "imx91_thermal", tmu); if (ret < 0) - return dev_err_probe(dev, ret, "failed to request alarm irq\n"); + return ret; pm_runtime_put(dev); diff --git a/drivers/thermal/imx_thermal.c b/drivers/thermal/imx_thermal.c index 38c993d1bcb3..887d381541a3 100644 --- a/drivers/thermal/imx_thermal.c +++ b/drivers/thermal/imx_thermal.c @@ -693,8 +693,8 @@ static int imx_thermal_probe(struct platform_device *pdev) goto clk_disable; } - dev_info(dev, "%s CPU temperature grade - max:%dC" - " critical:%dC passive:%dC\n", data->temp_grade, + dev_info(dev, "%s CPU temperature grade - max:%dC critical:%dC passive:%dC\n", + data->temp_grade, data->temp_max / 1000, trips[IMX_TRIP_CRITICAL].temperature / 1000, trips[IMX_TRIP_PASSIVE].temperature / 1000); @@ -732,10 +732,8 @@ static int imx_thermal_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(dev, data->irq, imx_thermal_alarm_irq, imx_thermal_alarm_irq_thread, 0, "imx_thermal", data); - if (ret < 0) { - dev_err(dev, "failed to request alarm irq: %d\n", ret); + if (ret < 0) goto thermal_zone_unregister; - } pm_runtime_put(data->dev); @@ -832,12 +830,12 @@ static int imx_thermal_runtime_resume(struct device *dev) ret = regmap_write(map, socdata->sensor_ctrl + REG_CLR, socdata->power_down_mask); if (ret) - return ret; + goto disable_clk; ret = regmap_write(map, socdata->sensor_ctrl + REG_SET, socdata->measure_temp_mask); if (ret) - return ret; + goto disable_clk; /* * According to the temp sensor designers, it may require up to ~17us @@ -846,6 +844,11 @@ static int imx_thermal_runtime_resume(struct device *dev) usleep_range(20, 50); return 0; + +disable_clk: + clk_disable_unprepare(data->thermal_clk); + + return ret; } static const struct dev_pm_ops imx_thermal_pm_ops = { diff --git a/drivers/thermal/intel/int340x_thermal/int3400_thermal.c b/drivers/thermal/intel/int340x_thermal/int3400_thermal.c index d200734625ee..5d70301d4a3d 100644 --- a/drivers/thermal/intel/int340x_thermal/int3400_thermal.c +++ b/drivers/thermal/intel/int340x_thermal/int3400_thermal.c @@ -356,8 +356,10 @@ static void cleanup_odvp(struct int3400_thermal_priv *priv) kfree(priv->odvp_attrs[i].attr.attr.name); } kfree(priv->odvp_attrs); + priv->odvp_attrs = NULL; } kfree(priv->odvp); + priv->odvp = NULL; priv->odvp_count = 0; } @@ -635,7 +637,6 @@ free_notify: acpi_remove_notify_handler(priv->adev->handle, ACPI_DEVICE_NOTIFY, int3400_notify); free_sysfs: - cleanup_odvp(priv); if (!ZERO_OR_NULL_PTR(priv->data_vault)) { device_remove_bin_file(&pdev->dev, &bin_attr_data_vault); kfree(priv->data_vault); @@ -649,6 +650,7 @@ free_rel_misc: acpi_thermal_rel_misc_device_remove(priv->adev->handle); thermal_zone_device_unregister(priv->thermal); free_art_trt: + cleanup_odvp(priv); kfree(priv->trts); kfree(priv->arts); free_priv: diff --git a/drivers/thermal/intel/int340x_thermal/platform_temperature_control.c b/drivers/thermal/intel/int340x_thermal/platform_temperature_control.c index 0ccc72c93499..43cb809c5dcc 100644 --- a/drivers/thermal/intel/int340x_thermal/platform_temperature_control.c +++ b/drivers/thermal/intel/int340x_thermal/platform_temperature_control.c @@ -138,7 +138,7 @@ static void ptc_mmio_write(struct pci_dev *pdev, u32 offset, int index, u32 valu reg_val = readq((void __iomem *) (proc_priv->mmio_base + offset)); reg_val &= ~mask; - reg_val |= (value << ptc_mmio_regs[index].shift); + reg_val |= ((u64)value << ptc_mmio_regs[index].shift); writeq(reg_val, (void __iomem *) (proc_priv->mmio_base + offset)); } @@ -227,17 +227,12 @@ static ssize_t ptc_temperature_write(struct file *file, const char __user *data, { struct ptc_data *ptc_instance = file->private_data; struct pci_dev *pdev = ptc_instance->pdev; - char buf[32]; - ssize_t len; u32 value; + int ret; - len = min(count, sizeof(buf) - 1); - if (copy_from_user(buf, data, len)) - return -EFAULT; - - buf[len] = '\0'; - if (kstrtouint(buf, 0, &value)) - return -EINVAL; + ret = kstrtou32_from_user(data, count, 0, &value); + if (unlikely(ret)) + return ret; if (ptc_mmio_regs[PTC_TEMP_OVERRIDE_INDEX].units) value /= ptc_mmio_regs[PTC_TEMP_OVERRIDE_INDEX].units; @@ -278,12 +273,18 @@ static void ptc_delete_debugfs(void) int proc_thermal_ptc_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv) { if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_PTC) { - int i; + int i, ret; for (i = 0; i < PTC_MAX_INSTANCES; i++) { ptc_instance[i].offset = ptc_offsets[i]; ptc_instance[i].pdev = pdev; - ptc_create_groups(pdev, i, &ptc_instance[i]); + ret = ptc_create_groups(pdev, i, &ptc_instance[i]); + if (ret) { + while (i--) + sysfs_remove_group(&pdev->dev.kobj, + &ptc_instance[i].ptc_attr_group); + return ret; + } } ptc_create_debugfs(); diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci.c index c693d934103a..c5131423ec9b 100644 --- a/drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci.c +++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci.c @@ -308,10 +308,8 @@ static int proc_thermal_setup_msi(struct pci_dev *pdev, struct proc_thermal_pci ret = devm_request_threaded_irq(&pdev->dev, irq, proc_thermal_irq_handler, proc_thermal_irq_thread_handler, 0, KBUILD_MODNAME, pci_info); - if (ret) { - dev_err(&pdev->dev, "Request IRQ %d failed\n", irq); + if (ret) goto err_free_msi_vectors; - } proc_thermal_msi_map[i] = irq; } @@ -394,10 +392,8 @@ static int proc_thermal_pci_probe(struct pci_dev *pdev, const struct pci_device_ ret = devm_request_threaded_irq(&pdev->dev, irq, proc_thermal_irq_handler, proc_thermal_irq_thread_handler, irq_flag, KBUILD_MODNAME, pci_info); - if (ret) { - dev_err(&pdev->dev, "Request IRQ %d failed\n", pdev->irq); + if (ret) goto err_ret_tzone; - } } ret = thermal_zone_device_enable(pci_info->tzone); diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_rapl.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_rapl.c index bf51a17c5be6..f8b9745c1b8a 100644 --- a/drivers/thermal/intel/int340x_thermal/processor_thermal_rapl.c +++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_rapl.c @@ -11,6 +11,77 @@ static struct rapl_if_priv rapl_mmio_priv; +/* bitmasks for RAPL MSRs, used by primitive access functions */ +#define MMIO_ENERGY_STATUS_MASK GENMASK(31, 0) + +#define MMIO_POWER_LIMIT1_MASK GENMASK(14, 0) +#define MMIO_POWER_LIMIT1_ENABLE BIT(15) +#define MMIO_POWER_LIMIT1_CLAMP BIT(16) + +#define MMIO_POWER_LIMIT2_MASK GENMASK_ULL(46, 32) +#define MMIO_POWER_LIMIT2_ENABLE BIT_ULL(47) +#define MMIO_POWER_LIMIT2_CLAMP BIT_ULL(48) + +#define MMIO_POWER_LOW_LOCK BIT(31) +#define MMIO_POWER_HIGH_LOCK BIT_ULL(63) + +#define MMIO_POWER_LIMIT4_MASK GENMASK(12, 0) + +#define MMIO_TIME_WINDOW1_MASK GENMASK_ULL(23, 17) +#define MMIO_TIME_WINDOW2_MASK GENMASK_ULL(55, 49) + +#define MMIO_POWER_INFO_MAX_MASK GENMASK_ULL(46, 32) +#define MMIO_POWER_INFO_MIN_MASK GENMASK_ULL(30, 16) +#define MMIO_POWER_INFO_MAX_TIME_WIN_MASK GENMASK_ULL(53, 48) +#define MMIO_POWER_INFO_THERMAL_SPEC_MASK GENMASK(14, 0) + +#define MMIO_PERF_STATUS_THROTTLE_TIME_MASK GENMASK(31, 0) +#define MMIO_PP_POLICY_MASK GENMASK(4, 0) + +/* RAPL primitives for MMIO I/F */ +static struct rapl_primitive_info rpi_mmio[NR_RAPL_PRIMITIVES] = { + /* name, mask, shift, msr index, unit divisor */ + [POWER_LIMIT1] = PRIMITIVE_INFO_INIT(POWER_LIMIT1, MMIO_POWER_LIMIT1_MASK, 0, + RAPL_DOMAIN_REG_LIMIT, POWER_UNIT, 0), + [POWER_LIMIT2] = PRIMITIVE_INFO_INIT(POWER_LIMIT2, MMIO_POWER_LIMIT2_MASK, 32, + RAPL_DOMAIN_REG_LIMIT, POWER_UNIT, 0), + [POWER_LIMIT4] = PRIMITIVE_INFO_INIT(POWER_LIMIT4, MMIO_POWER_LIMIT4_MASK, 0, + RAPL_DOMAIN_REG_PL4, POWER_UNIT, 0), + [ENERGY_COUNTER] = PRIMITIVE_INFO_INIT(ENERGY_COUNTER, MMIO_ENERGY_STATUS_MASK, 0, + RAPL_DOMAIN_REG_STATUS, ENERGY_UNIT, 0), + [FW_LOCK] = PRIMITIVE_INFO_INIT(FW_LOCK, MMIO_POWER_LOW_LOCK, 31, + RAPL_DOMAIN_REG_LIMIT, ARBITRARY_UNIT, 0), + [FW_HIGH_LOCK] = PRIMITIVE_INFO_INIT(FW_LOCK, MMIO_POWER_HIGH_LOCK, 63, + RAPL_DOMAIN_REG_LIMIT, ARBITRARY_UNIT, 0), + [PL1_ENABLE] = PRIMITIVE_INFO_INIT(PL1_ENABLE, MMIO_POWER_LIMIT1_ENABLE, 15, + RAPL_DOMAIN_REG_LIMIT, ARBITRARY_UNIT, 0), + [PL1_CLAMP] = PRIMITIVE_INFO_INIT(PL1_CLAMP, MMIO_POWER_LIMIT1_CLAMP, 16, + RAPL_DOMAIN_REG_LIMIT, ARBITRARY_UNIT, 0), + [PL2_ENABLE] = PRIMITIVE_INFO_INIT(PL2_ENABLE, MMIO_POWER_LIMIT2_ENABLE, 47, + RAPL_DOMAIN_REG_LIMIT, ARBITRARY_UNIT, 0), + [PL2_CLAMP] = PRIMITIVE_INFO_INIT(PL2_CLAMP, MMIO_POWER_LIMIT2_CLAMP, 48, + RAPL_DOMAIN_REG_LIMIT, ARBITRARY_UNIT, 0), + [TIME_WINDOW1] = PRIMITIVE_INFO_INIT(TIME_WINDOW1, MMIO_TIME_WINDOW1_MASK, 17, + RAPL_DOMAIN_REG_LIMIT, TIME_UNIT, 0), + [TIME_WINDOW2] = PRIMITIVE_INFO_INIT(TIME_WINDOW2, MMIO_TIME_WINDOW2_MASK, 49, + RAPL_DOMAIN_REG_LIMIT, TIME_UNIT, 0), + [THERMAL_SPEC_POWER] = PRIMITIVE_INFO_INIT(THERMAL_SPEC_POWER, + MMIO_POWER_INFO_THERMAL_SPEC_MASK, 0, + RAPL_DOMAIN_REG_INFO, POWER_UNIT, 0), + [MAX_POWER] = PRIMITIVE_INFO_INIT(MAX_POWER, MMIO_POWER_INFO_MAX_MASK, 32, + RAPL_DOMAIN_REG_INFO, POWER_UNIT, 0), + [MIN_POWER] = PRIMITIVE_INFO_INIT(MIN_POWER, MMIO_POWER_INFO_MIN_MASK, 16, + RAPL_DOMAIN_REG_INFO, POWER_UNIT, 0), + [MAX_TIME_WINDOW] = PRIMITIVE_INFO_INIT(MAX_TIME_WINDOW, + MMIO_POWER_INFO_MAX_TIME_WIN_MASK, 48, + RAPL_DOMAIN_REG_INFO, TIME_UNIT, 0), + [THROTTLED_TIME] = PRIMITIVE_INFO_INIT(THROTTLED_TIME, + MMIO_PERF_STATUS_THROTTLE_TIME_MASK, 0, + RAPL_DOMAIN_REG_PERF, TIME_UNIT, 0), + [PRIORITY_LEVEL] = PRIMITIVE_INFO_INIT(PRIORITY_LEVEL, MMIO_PP_POLICY_MASK, 0, + RAPL_DOMAIN_REG_POLICY, ARBITRARY_UNIT, 0), +}; + static const struct rapl_mmio_regs rapl_mmio_default = { .reg_unit = 0x5938, .regs[RAPL_DOMAIN_PACKAGE] = { 0x59a0, 0x593c, 0x58f0, 0, 0x5930, 0x59b0}, @@ -19,6 +90,13 @@ static const struct rapl_mmio_regs rapl_mmio_default = { .limits[RAPL_DOMAIN_DRAM] = BIT(POWER_LIMIT2), }; +static const struct rapl_defaults rapl_defaults_mmio = { + .floor_freq_reg_addr = 0, + .check_unit = rapl_default_check_unit, + .set_floor_freq = rapl_default_set_floor_freq, + .compute_time_window = rapl_default_compute_time_window, +}; + static int rapl_mmio_read_raw(int cpu, struct reg_action *ra, bool atomic) { if (!ra->reg.mmio) @@ -67,6 +145,8 @@ int proc_thermal_rapl_add(struct pci_dev *pdev, struct proc_thermal_device *proc rapl_mmio_priv.read_raw = rapl_mmio_read_raw; rapl_mmio_priv.write_raw = rapl_mmio_write_raw; + rapl_mmio_priv.defaults = &rapl_defaults_mmio; + rapl_mmio_priv.rpi = rpi_mmio; rapl_mmio_priv.control_type = powercap_register_control_type(NULL, "intel-rapl-mmio", NULL); if (IS_ERR(rapl_mmio_priv.control_type)) { @@ -111,4 +191,5 @@ void proc_thermal_rapl_remove(void) EXPORT_SYMBOL_GPL(proc_thermal_rapl_remove); MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("INTEL_RAPL"); MODULE_DESCRIPTION("RAPL interface using MMIO"); diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_rfim.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_rfim.c index 314fbc1f490f..96279756177f 100644 --- a/drivers/thermal/intel/int340x_thermal/processor_thermal_rfim.c +++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_rfim.c @@ -402,17 +402,31 @@ static ssize_t rfi_restriction_show(struct device *dev, return sysfs_emit(buf, "%llu\n", resp); } + /* ddr_data_rate */ +static const struct mmio_reg nvl_ddr_data_rate_reg = { 1, 0xE0, 10, 0x3FF, 2}; + +static const struct mmio_reg *ddr_data_rate_reg; + static ssize_t ddr_data_rate_show(struct device *dev, struct device_attribute *attr, char *buf) { - u16 id = 0x0107; u64 resp; - int ret; - ret = processor_thermal_send_mbox_read_cmd(to_pci_dev(dev), id, &resp); - if (ret) - return ret; + if (ddr_data_rate_reg) { + u16 reg_val; + + pci_read_config_word(to_pci_dev(dev), ddr_data_rate_reg->offset, ®_val); + resp = (reg_val >> ddr_data_rate_reg->shift) & ddr_data_rate_reg->mask; + resp = (resp * 3333) / 100; + } else { + const u16 id = 0x0107; + int ret; + + ret = processor_thermal_send_mbox_read_cmd(to_pci_dev(dev), id, &resp); + if (ret) + return ret; + } return sysfs_emit(buf, "%llu\n", resp); } @@ -461,6 +475,7 @@ int proc_thermal_rfim_add(struct pci_dev *pdev, struct proc_thermal_device *proc case PCI_DEVICE_ID_INTEL_NVL_H_THERMAL: case PCI_DEVICE_ID_INTEL_NVL_S_THERMAL: dlvr_mmio_regs_table = nvl_dlvr_mmio_regs; + ddr_data_rate_reg = &nvl_ddr_data_rate_reg; break; default: dlvr_mmio_regs_table = dlvr_mmio_regs; @@ -476,12 +491,11 @@ int proc_thermal_rfim_add(struct pci_dev *pdev, struct proc_thermal_device *proc if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_DVFS) { ret = sysfs_create_group(&pdev->dev.kobj, &dvfs_attribute_group); - if (ret && proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_FIVR) { - sysfs_remove_group(&pdev->dev.kobj, &fivr_attribute_group); - return ret; - } - if (ret && proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_DLVR) { - sysfs_remove_group(&pdev->dev.kobj, &dlvr_attribute_group); + if (ret) { + if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_DLVR) + sysfs_remove_group(&pdev->dev.kobj, &dlvr_attribute_group); + if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_FIVR) + sysfs_remove_group(&pdev->dev.kobj, &fivr_attribute_group); return ret; } } diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_soc_slider.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_soc_slider.c index 49ff3bae7271..91f291627132 100644 --- a/drivers/thermal/intel/int340x_thermal/processor_thermal_soc_slider.c +++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_soc_slider.c @@ -176,15 +176,21 @@ static inline void write_soc_slider(struct proc_thermal_device *proc_priv, u64 v static void set_soc_power_profile(struct proc_thermal_device *proc_priv, int slider) { + u8 offset; u64 val; val = read_soc_slider(proc_priv); val &= ~SLIDER_MASK; val |= FIELD_PREP(SLIDER_MASK, slider) | BIT(SLIDER_ENABLE_BIT); + if (slider == SOC_SLIDER_VALUE_MINIMUM || slider == SOC_SLIDER_VALUE_MAXIMUM) + offset = 0; + else + offset = slider_offset; + /* Set the slider offset from module params */ val &= ~SLIDER_OFFSET_MASK; - val |= FIELD_PREP(SLIDER_OFFSET_MASK, slider_offset); + val |= FIELD_PREP(SLIDER_OFFSET_MASK, offset); write_soc_slider(proc_priv, val); } diff --git a/drivers/thermal/intel/intel_bxt_pmic_thermal.c b/drivers/thermal/intel/intel_bxt_pmic_thermal.c index 6312c6ba081f..aeaefbbd5d8f 100644 --- a/drivers/thermal/intel/intel_bxt_pmic_thermal.c +++ b/drivers/thermal/intel/intel_bxt_pmic_thermal.c @@ -245,10 +245,8 @@ static int pmic_thermal_probe(struct platform_device *pdev) NULL, pmic_thermal_irq_handler, IRQF_ONESHOT, "pmic_thermal", pdev); - if (ret) { - dev_err(dev, "request irq(%d) failed: %d\n", virq, ret); + if (ret) return ret; - } pmic_irq_count++; } diff --git a/drivers/thermal/intel/intel_hfi.c b/drivers/thermal/intel/intel_hfi.c index 1a1a95b39405..3273b8fe3d4d 100644 --- a/drivers/thermal/intel/intel_hfi.c +++ b/drivers/thermal/intel/intel_hfi.c @@ -41,6 +41,7 @@ #include <linux/topology.h> #include <linux/workqueue.h> +#include <asm/cpuid/api.h> #include <asm/msr.h> #include "intel_hfi.h" @@ -526,7 +527,7 @@ void intel_hfi_offline(unsigned int cpu) mutex_lock(&hfi_instance_lock); cpumask_clear_cpu(cpu, hfi_instance->cpus); - if (!cpumask_weight(hfi_instance->cpus)) + if (cpumask_empty(hfi_instance->cpus)) hfi_disable(); mutex_unlock(&hfi_instance_lock); diff --git a/drivers/thermal/intel/intel_powerclamp.c b/drivers/thermal/intel/intel_powerclamp.c index 9a4cec000910..bd7fd98dc310 100644 --- a/drivers/thermal/intel/intel_powerclamp.c +++ b/drivers/thermal/intel/intel_powerclamp.c @@ -200,8 +200,7 @@ static int cpumask_get(char *buf, const struct kernel_param *kp) if (!cpumask_available(idle_injection_cpu_mask)) return -ENODEV; - return bitmap_print_to_pagebuf(false, buf, cpumask_bits(idle_injection_cpu_mask), - nr_cpumask_bits); + return sysfs_emit(buf, "%*pb\n", cpumask_pr_args(idle_injection_cpu_mask)); } static const struct kernel_param_ops cpumask_ops = { diff --git a/drivers/thermal/intel/intel_tcc.c b/drivers/thermal/intel/intel_tcc.c index ab61fb122937..d1fa3c63d554 100644 --- a/drivers/thermal/intel/intel_tcc.c +++ b/drivers/thermal/intel/intel_tcc.c @@ -181,17 +181,17 @@ static u32 get_temp_mask(bool pkg) */ int intel_tcc_get_tjmax(int cpu) { - u32 low, high; + struct msr msrval; int val, err; if (cpu < 0) - err = rdmsr_safe(MSR_IA32_TEMPERATURE_TARGET, &low, &high); + err = rdmsrq_safe(MSR_IA32_TEMPERATURE_TARGET, &msrval.q); else - err = rdmsr_safe_on_cpu(cpu, MSR_IA32_TEMPERATURE_TARGET, &low, &high); + err = rdmsrq_safe_on_cpu(cpu, MSR_IA32_TEMPERATURE_TARGET, &msrval.q); if (err) return err; - val = (low >> 16) & 0xff; + val = (msrval.l >> 16) & 0xff; return val ? val : -ENODATA; } @@ -208,17 +208,17 @@ EXPORT_SYMBOL_NS_GPL(intel_tcc_get_tjmax, "INTEL_TCC"); */ int intel_tcc_get_offset(int cpu) { - u32 low, high; + struct msr val; int err; if (cpu < 0) - err = rdmsr_safe(MSR_IA32_TEMPERATURE_TARGET, &low, &high); + err = rdmsrq_safe(MSR_IA32_TEMPERATURE_TARGET, &val.q); else - err = rdmsr_safe_on_cpu(cpu, MSR_IA32_TEMPERATURE_TARGET, &low, &high); + err = rdmsrq_safe_on_cpu(cpu, MSR_IA32_TEMPERATURE_TARGET, &val.q); if (err) return err; - return (low >> 24) & intel_tcc_temp_masks.tcc_offset; + return (val.l >> 24) & intel_tcc_temp_masks.tcc_offset; } EXPORT_SYMBOL_NS_GPL(intel_tcc_get_offset, "INTEL_TCC"); @@ -235,7 +235,7 @@ EXPORT_SYMBOL_NS_GPL(intel_tcc_get_offset, "INTEL_TCC"); int intel_tcc_set_offset(int cpu, int offset) { - u32 low, high; + struct msr val; int err; if (!intel_tcc_temp_masks.tcc_offset) @@ -245,23 +245,23 @@ int intel_tcc_set_offset(int cpu, int offset) return -EINVAL; if (cpu < 0) - err = rdmsr_safe(MSR_IA32_TEMPERATURE_TARGET, &low, &high); + err = rdmsrq_safe(MSR_IA32_TEMPERATURE_TARGET, &val.q); else - err = rdmsr_safe_on_cpu(cpu, MSR_IA32_TEMPERATURE_TARGET, &low, &high); + err = rdmsrq_safe_on_cpu(cpu, MSR_IA32_TEMPERATURE_TARGET, &val.q); if (err) return err; /* MSR Locked */ - if (low & BIT(31)) + if (val.l & BIT(31)) return -EPERM; - low &= ~(intel_tcc_temp_masks.tcc_offset << 24); - low |= offset << 24; + val.l &= ~(intel_tcc_temp_masks.tcc_offset << 24); + val.l |= offset << 24; if (cpu < 0) - return wrmsr_safe(MSR_IA32_TEMPERATURE_TARGET, low, high); + return wrmsrq_safe(MSR_IA32_TEMPERATURE_TARGET, val.q); else - return wrmsr_safe_on_cpu(cpu, MSR_IA32_TEMPERATURE_TARGET, low, high); + return wrmsrq_safe_on_cpu(cpu, MSR_IA32_TEMPERATURE_TARGET, val.q); } EXPORT_SYMBOL_NS_GPL(intel_tcc_set_offset, "INTEL_TCC"); @@ -279,7 +279,8 @@ EXPORT_SYMBOL_NS_GPL(intel_tcc_set_offset, "INTEL_TCC"); int intel_tcc_get_temp(int cpu, int *temp, bool pkg) { u32 msr = pkg ? MSR_IA32_PACKAGE_THERM_STATUS : MSR_IA32_THERM_STATUS; - u32 low, high, mask; + u32 mask; + struct msr val; int tjmax, err; tjmax = intel_tcc_get_tjmax(cpu); @@ -287,19 +288,19 @@ int intel_tcc_get_temp(int cpu, int *temp, bool pkg) return tjmax; if (cpu < 0) - err = rdmsr_safe(msr, &low, &high); + err = rdmsrq_safe(msr, &val.q); else - err = rdmsr_safe_on_cpu(cpu, msr, &low, &high); + err = rdmsrq_safe_on_cpu(cpu, msr, &val.q); if (err) return err; /* Temperature is beyond the valid thermal sensor range */ - if (!(low & BIT(31))) + if (!(val.l & BIT(31))) return -ENODATA; mask = get_temp_mask(pkg); - *temp = tjmax - ((low >> 16) & mask); + *temp = tjmax - ((val.l >> 16) & mask); return 0; } diff --git a/drivers/thermal/intel/intel_tcc_cooling.c b/drivers/thermal/intel/intel_tcc_cooling.c index 6c2ba0ba3178..52ea4f7fc9f9 100644 --- a/drivers/thermal/intel/intel_tcc_cooling.c +++ b/drivers/thermal/intel/intel_tcc_cooling.c @@ -65,6 +65,9 @@ static const struct x86_cpu_id tcc_ids[] __initconst = { X86_MATCH_VFM(INTEL_RAPTORLAKE, NULL), X86_MATCH_VFM(INTEL_RAPTORLAKE_P, NULL), X86_MATCH_VFM(INTEL_RAPTORLAKE_S, NULL), + X86_MATCH_VFM(INTEL_ARROWLAKE, NULL), + X86_MATCH_VFM(INTEL_ARROWLAKE_U, NULL), + X86_MATCH_VFM(INTEL_ARROWLAKE_H, NULL), X86_MATCH_VFM(INTEL_PANTHERLAKE_L, NULL), X86_MATCH_VFM(INTEL_WILDCATLAKE_L, NULL), X86_MATCH_VFM(INTEL_NOVALAKE, NULL), diff --git a/drivers/thermal/intel/therm_throt.c b/drivers/thermal/intel/therm_throt.c index 44fa4dd15dd1..4d4e81601d17 100644 --- a/drivers/thermal/intel/therm_throt.c +++ b/drivers/thermal/intel/therm_throt.c @@ -14,12 +14,14 @@ * Credits: Adapted from Zwane Mwaikambo's original code in mce_intel.c. * Inspired by Ross Biro's and Al Borchers' counter code. */ +#include <linux/syscore_ops.h> #include <linux/interrupt.h> #include <linux/notifier.h> #include <linux/jiffies.h> #include <linux/kernel.h> #include <linux/percpu.h> #include <linux/export.h> +#include <linux/delay.h> #include <linux/types.h> #include <linux/init.h> #include <linux/smp.h> @@ -244,16 +246,23 @@ static void thermal_intr_init_pkg_clear_mask(void) * IA32_PACKAGE_THERM_STATUS. */ - /* All bits except BIT 26 depend on CPUID.06H: EAX[6] = 1 */ + /* All bits except BITs 25 and 26 depend on CPUID.06H: EAX[6] = 1 */ if (boot_cpu_has(X86_FEATURE_PTS)) therm_intr_pkg_clear_mask = (BIT(1) | BIT(3) | BIT(5) | BIT(7) | BIT(9) | BIT(11)); /* - * Intel SDM Volume 2A: Thermal and Power Management Leaf + * Intel SDM Volume 1: Thermal and Power Management Leaf * Bit 26: CPUID.06H: EAX[19] = 1 */ if (boot_cpu_has(X86_FEATURE_HFI)) therm_intr_pkg_clear_mask |= BIT(26); + + /* + * Intel SDM Volume 1: Thermal and Power Management Leaf + * Bit 25: CPUID.06H: EAX[24] = 1 + */ + if (boot_cpu_has(X86_FEATURE_DPTI)) + therm_intr_pkg_clear_mask |= BIT(25); } /* @@ -524,13 +533,266 @@ static void thermal_throttle_remove_dev(struct device *dev) sysfs_remove_group(&dev->kobj, &thermal_attr_group); } +static int check_directed_thermal_pkg_intr_ack(void) +{ + unsigned int count = 15000; + u64 msr_val; + + /* + * Hardware acknowledges the directed interrupt setup in 10ms or less. + * Wait 15ms to be safe. + */ + do { + rdmsrq(MSR_IA32_PACKAGE_THERM_STATUS, msr_val); + udelay(1); + } while (!(msr_val & PACKAGE_THERM_STATUS_DPTI_ACK) && --count); + + if (!count) + return -ETIMEDOUT; + + thermal_clear_package_intr_status(PACKAGE_LEVEL, + PACKAGE_THERM_STATUS_DPTI_ACK); + + return 0; +} + +static void config_directed_thermal_pkg_intr(void *info) +{ + bool enable = *((bool *)info); + u64 msr_val; + + rdmsrq(MSR_IA32_THERM_INTERRUPT, msr_val); + + if (enable) + msr_val |= THERM_INT_DPTI_ENABLE; + else + msr_val &= ~THERM_INT_DPTI_ENABLE; + + wrmsrq(MSR_IA32_THERM_INTERRUPT, msr_val); +} + +/* + * Accessed from CPU hotplug callbacks and from code that runs while CPU + * hotplug is inactive: the init and cleanup paths as well as syscore callbacks. + * No extra locking needed. + */ +static unsigned int *directed_intr_handler_cpus; + +static bool directed_thermal_pkg_intr_supported(void) +{ + if (!boot_cpu_has(X86_FEATURE_DPTI)) + return false; + + if (!directed_intr_handler_cpus) + return false; + + return true; +} + +/* + * Must be called with cpu_hotplug_lock held to prevent CPUs from going offline + * while iterating through packages and interrupts must be enabled to avoid + * deadlocks in SMP function calls. The syscore shutdown callback also calls + * this function, but runs with CPU hotplug disabled (and interrupts enabled). + */ +static void disable_directed_thermal_pkg_intr_all(void) +{ + bool enable = false; + int i; + + if (!directed_thermal_pkg_intr_supported()) + return; + + for (i = 0; i < topology_max_packages(); i++) { + if (directed_intr_handler_cpus[i] == nr_cpu_ids) + continue; + + smp_call_function_single(directed_intr_handler_cpus[i], + config_directed_thermal_pkg_intr, + &enable, true); + } +} + +static int enable_directed_thermal_pkg_intr(unsigned int cpu) +{ + bool enable = true; + u16 pkg_id; + + if (!directed_thermal_pkg_intr_supported()) + return 0; + + pkg_id = topology_logical_package_id(cpu); + if (pkg_id >= topology_max_packages()) + return -EINVAL; + + /* Another CPU in this package already handles the directed interrupt. */ + if (directed_intr_handler_cpus[pkg_id] != nr_cpu_ids) + return 0; + + thermal_clear_package_intr_status(PACKAGE_LEVEL, + PACKAGE_THERM_STATUS_DPTI_ACK); + + config_directed_thermal_pkg_intr(&enable); + if (!check_directed_thermal_pkg_intr_ack()) { + directed_intr_handler_cpus[pkg_id] = cpu; + return 0; + } + + /* + * A failure indicates faulty hardware. Roll back completely so that + * no other CPU tries. This is especially important during boot as all + * CPUs may come online and would otherwise keep trying. + */ + enable = false; + config_directed_thermal_pkg_intr(&enable); + + return -ETIMEDOUT; +} + +static void disable_directed_thermal_pkg_intr(unsigned int cpu) +{ + unsigned int new_cpu; + bool enable; + u16 pkg_id; + + if (!directed_thermal_pkg_intr_supported()) + return; + + pkg_id = topology_logical_package_id(cpu); + if (pkg_id >= topology_max_packages()) + return; + + /* Not the CPU handling the directed interrupt. */ + if (directed_intr_handler_cpus[pkg_id] != cpu) + return; + + /* + * The package-level interrupt must remain directed after this CPU goes + * offline. + */ + new_cpu = cpumask_any_but(topology_core_cpumask(cpu), cpu); + if (new_cpu < nr_cpu_ids) { + enable = true; + thermal_clear_package_intr_status(PACKAGE_LEVEL, + PACKAGE_THERM_STATUS_DPTI_ACK); + + /* + * We are here via CPU hotplug. Since we are holding the + * cpu_hotplug_lock, @new_cpu cannot go offline and interrupts + * are enabled, so the SMP function call is safe. + * + * The syscore suspend callback runs with interrupts disabled, + * but it does not reach this path because all the secondary + * CPUs are offline. + */ + smp_call_function_single(new_cpu, config_directed_thermal_pkg_intr, + &enable, true); + } + + /* + * If hardware does not acknowledge the directed interrupt setup on + * @new_cpu, disable the redirection. Since no other CPU is configured + * to receive the package-level interrupt, all CPUs in the package will + * receive it. + */ + enable = false; + if (new_cpu < nr_cpu_ids && check_directed_thermal_pkg_intr_ack()) { + smp_call_function_single(new_cpu, config_directed_thermal_pkg_intr, + &enable, true); + + pr_warn_once("Failed to redirect package thermal interrupt from CPU%u to CPU%u; reverting to broadcast.\n", + cpu, new_cpu); + + new_cpu = nr_cpu_ids; + } + + /* + * Clear the directed interrupt on @cpu. Hardware acknowledgment can be + * ignored since @cpu is going offline. + */ + config_directed_thermal_pkg_intr(&enable); + + directed_intr_handler_cpus[pkg_id] = (new_cpu < nr_cpu_ids) ? new_cpu : nr_cpu_ids; +} + +/* + * CPU0 may be handling the directed interrupt, but the CPU hotplug callbacks + * are not called for CPU0 during suspend and resume. + */ +static void directed_pkg_intr_syscore_resume(void *data) +{ + /* + * We can't do anything to handle errors. If direction fails for CPU0, + * another CPU will take over or disable direction entirely during CPU + * hotplug. + */ + enable_directed_thermal_pkg_intr(0); +} + +static int directed_pkg_intr_syscore_suspend(void *data) +{ + disable_directed_thermal_pkg_intr(0); + + return 0; +} + +static void directed_pkg_intr_syscore_shutdown(void *data) +{ + disable_directed_thermal_pkg_intr_all(); +} + +static const struct syscore_ops directed_pkg_intr_pm_ops = { + .resume = directed_pkg_intr_syscore_resume, + .suspend = directed_pkg_intr_syscore_suspend, + .shutdown = directed_pkg_intr_syscore_shutdown, +}; + +static struct syscore directed_pkg_intr_pm = { + .ops = &directed_pkg_intr_pm_ops, +}; + +static __init void init_directed_pkg_intr(void) +{ + int i; + + if (!boot_cpu_has(X86_FEATURE_DPTI)) + return; + + directed_intr_handler_cpus = kmalloc_array(topology_max_packages(), + sizeof(*directed_intr_handler_cpus), + GFP_KERNEL); + if (!directed_intr_handler_cpus) + return; + + for (i = 0; i < topology_max_packages(); i++) + directed_intr_handler_cpus[i] = nr_cpu_ids; + + register_syscore(&directed_pkg_intr_pm); +} + +static void cleanup_directed_pkg_thermal_intr(void) +{ + if (!directed_thermal_pkg_intr_supported()) + return; + + unregister_syscore(&directed_pkg_intr_pm); + disable_directed_thermal_pkg_intr_all(); + kfree(directed_intr_handler_cpus); + directed_intr_handler_cpus = NULL; +} + /* Get notified when a cpu comes on/off. Be hotplug friendly. */ static int thermal_throttle_online(unsigned int cpu) { struct thermal_state *state = &per_cpu(thermal_state, cpu); struct device *dev = get_cpu_device(cpu); + int err; u32 l; + err = thermal_throttle_add_dev(dev, cpu); + if (err) + return err; + state->package_throttle.level = PACKAGE_LEVEL; state->core_throttle.level = CORE_LEVEL; @@ -544,11 +806,16 @@ static int thermal_throttle_online(unsigned int cpu) */ intel_hfi_online(cpu); + if (enable_directed_thermal_pkg_intr(cpu)) { + pr_info_once("Failed to direct package thermal interrupts. All CPUs will receive it.\n"); + cleanup_directed_pkg_thermal_intr(); + } + /* Unmask the thermal vector after the above workqueues are initialized. */ l = apic_read(APIC_LVTTHMR); apic_write(APIC_LVTTHMR, l & ~APIC_LVT_MASKED); - return thermal_throttle_add_dev(dev, cpu); + return err; } static int thermal_throttle_offline(unsigned int cpu) @@ -561,6 +828,8 @@ static int thermal_throttle_offline(unsigned int cpu) l = apic_read(APIC_LVTTHMR); apic_write(APIC_LVTTHMR, l | APIC_LVT_MASKED); + disable_directed_thermal_pkg_intr(cpu); + intel_hfi_offline(cpu); cancel_delayed_work_sync(&state->package_throttle.therm_work); @@ -580,12 +849,19 @@ static __init int thermal_throttle_init_device(void) if (!atomic_read(&therm_throt_en)) return 0; + init_directed_pkg_intr(); + intel_hfi_init(); ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "x86/therm:online", thermal_throttle_online, thermal_throttle_offline); - return ret < 0 ? ret : 0; + if (ret >= 0) + return 0; + + cleanup_directed_pkg_thermal_intr(); + + return ret; } device_initcall(thermal_throttle_init_device); @@ -652,7 +928,7 @@ void intel_thermal_interrupt(void) { __u64 msr_val; - if (static_cpu_has(X86_FEATURE_HWP)) + if (cpu_feature_enabled(X86_FEATURE_HWP)) notify_hwp_interrupt(); rdmsrq(MSR_IA32_THERM_STATUS, msr_val); @@ -717,8 +993,8 @@ void __init therm_lvt_init(void) void intel_init_thermal(struct cpuinfo_x86 *c) { unsigned int cpu = smp_processor_id(); + struct msr val; int tm2 = 0; - u32 l, h; if (!intel_thermal_supported(c)) return; @@ -728,9 +1004,9 @@ void intel_init_thermal(struct cpuinfo_x86 *c) * be some SMM goo which handles it, so we can't even put a handler * since it might be delivered via SMI already: */ - rdmsr(MSR_IA32_MISC_ENABLE, l, h); + rdmsrq(MSR_IA32_MISC_ENABLE, val.q); - h = lvtthmr_init; + val.h = lvtthmr_init; /* * The initial value of thermal LVT entries on all APs always reads * 0x10000 because APs are woken up by BSP issuing INIT-SIPI-SIPI @@ -741,11 +1017,11 @@ void intel_init_thermal(struct cpuinfo_x86 *c) * BIOS has programmed on AP based on BSP's info we saved since BIOS * is always setting the same value for all threads/cores. */ - if ((h & APIC_DM_FIXED_MASK) != APIC_DM_FIXED) + if ((val.h & APIC_DM_FIXED_MASK) != APIC_DM_FIXED) apic_write(APIC_LVTTHMR, lvtthmr_init); - if ((l & MSR_IA32_MISC_ENABLE_TM1) && (h & APIC_DM_SMI)) { + if ((val.l & MSR_IA32_MISC_ENABLE_TM1) && (val.h & APIC_DM_SMI)) { if (system_state == SYSTEM_BOOTING) pr_debug("CPU%d: Thermal monitoring handled by SMI\n", cpu); return; @@ -754,59 +1030,55 @@ void intel_init_thermal(struct cpuinfo_x86 *c) /* early Pentium M models use different method for enabling TM2 */ if (cpu_has(c, X86_FEATURE_TM2)) { if (c->x86 == 6 && (c->x86_model == 9 || c->x86_model == 13)) { - rdmsr(MSR_THERM2_CTL, l, h); - if (l & MSR_THERM2_CTL_TM_SELECT) + rdmsrq(MSR_THERM2_CTL, val.q); + if (val.l & MSR_THERM2_CTL_TM_SELECT) tm2 = 1; - } else if (l & MSR_IA32_MISC_ENABLE_TM2) + } else if (val.l & MSR_IA32_MISC_ENABLE_TM2) tm2 = 1; } /* We'll mask the thermal vector in the lapic till we're ready: */ - h = THERMAL_APIC_VECTOR | APIC_DM_FIXED | APIC_LVT_MASKED; - apic_write(APIC_LVTTHMR, h); + val.h = THERMAL_APIC_VECTOR | APIC_DM_FIXED | APIC_LVT_MASKED; + apic_write(APIC_LVTTHMR, val.h); thermal_intr_init_core_clear_mask(); thermal_intr_init_pkg_clear_mask(); - rdmsr(MSR_IA32_THERM_INTERRUPT, l, h); - if (cpu_has(c, X86_FEATURE_PLN) && !int_pln_enable) - wrmsr(MSR_IA32_THERM_INTERRUPT, - (l | (THERM_INT_LOW_ENABLE - | THERM_INT_HIGH_ENABLE)) & ~THERM_INT_PLN_ENABLE, h); - else if (cpu_has(c, X86_FEATURE_PLN) && int_pln_enable) - wrmsr(MSR_IA32_THERM_INTERRUPT, - l | (THERM_INT_LOW_ENABLE - | THERM_INT_HIGH_ENABLE | THERM_INT_PLN_ENABLE), h); + rdmsrq(MSR_IA32_THERM_INTERRUPT, val.q); + if (cpu_has(c, X86_FEATURE_PLN) && !int_pln_enable) { + val.l |= THERM_INT_LOW_ENABLE | THERM_INT_HIGH_ENABLE; + val.l &= ~THERM_INT_PLN_ENABLE; + } else if (cpu_has(c, X86_FEATURE_PLN) && int_pln_enable) + val.l |= THERM_INT_LOW_ENABLE | THERM_INT_HIGH_ENABLE | + THERM_INT_PLN_ENABLE; else - wrmsr(MSR_IA32_THERM_INTERRUPT, - l | (THERM_INT_LOW_ENABLE | THERM_INT_HIGH_ENABLE), h); + val.l |= THERM_INT_LOW_ENABLE | THERM_INT_HIGH_ENABLE; + wrmsrq(MSR_IA32_THERM_INTERRUPT, val.q); if (cpu_has(c, X86_FEATURE_PTS)) { - rdmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h); - if (cpu_has(c, X86_FEATURE_PLN) && !int_pln_enable) - wrmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, - (l | (PACKAGE_THERM_INT_LOW_ENABLE - | PACKAGE_THERM_INT_HIGH_ENABLE)) - & ~PACKAGE_THERM_INT_PLN_ENABLE, h); - else if (cpu_has(c, X86_FEATURE_PLN) && int_pln_enable) - wrmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, - l | (PACKAGE_THERM_INT_LOW_ENABLE - | PACKAGE_THERM_INT_HIGH_ENABLE - | PACKAGE_THERM_INT_PLN_ENABLE), h); + rdmsrq(MSR_IA32_PACKAGE_THERM_INTERRUPT, val.q); + if (cpu_has(c, X86_FEATURE_PLN) && !int_pln_enable) { + val.l |= PACKAGE_THERM_INT_LOW_ENABLE | + PACKAGE_THERM_INT_HIGH_ENABLE; + val.l &= ~PACKAGE_THERM_INT_PLN_ENABLE; + } else if (cpu_has(c, X86_FEATURE_PLN) && int_pln_enable) + val.l |= PACKAGE_THERM_INT_LOW_ENABLE | + PACKAGE_THERM_INT_HIGH_ENABLE | + PACKAGE_THERM_INT_PLN_ENABLE; else - wrmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, - l | (PACKAGE_THERM_INT_LOW_ENABLE - | PACKAGE_THERM_INT_HIGH_ENABLE), h); + val.l |= PACKAGE_THERM_INT_LOW_ENABLE | + PACKAGE_THERM_INT_HIGH_ENABLE; + wrmsrq(MSR_IA32_PACKAGE_THERM_INTERRUPT, val.q); if (cpu_has(c, X86_FEATURE_HFI)) { - rdmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h); - wrmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, - l | PACKAGE_THERM_INT_HFI_ENABLE, h); + rdmsrq(MSR_IA32_PACKAGE_THERM_INTERRUPT, val.q); + wrmsrq(MSR_IA32_PACKAGE_THERM_INTERRUPT, + val.q | PACKAGE_THERM_INT_HFI_ENABLE); } } - rdmsr(MSR_IA32_MISC_ENABLE, l, h); - wrmsr(MSR_IA32_MISC_ENABLE, l | MSR_IA32_MISC_ENABLE_TM1, h); + rdmsrq(MSR_IA32_MISC_ENABLE, val.q); + wrmsrq(MSR_IA32_MISC_ENABLE, val.q | MSR_IA32_MISC_ENABLE_TM1); pr_info_once("CPU0: Thermal monitoring enabled (%s)\n", tm2 ? "TM2" : "TM1"); diff --git a/drivers/thermal/intel/x86_pkg_temp_thermal.c b/drivers/thermal/intel/x86_pkg_temp_thermal.c index 540109761f0a..43fd5bdf1d8d 100644 --- a/drivers/thermal/intel/x86_pkg_temp_thermal.c +++ b/drivers/thermal/intel/x86_pkg_temp_thermal.c @@ -20,6 +20,7 @@ #include <linux/debugfs.h> #include <asm/cpu_device_id.h> +#include <asm/cpuid/api.h> #include <asm/msr.h> #include "thermal_interrupt.h" @@ -50,8 +51,7 @@ MODULE_PARM_DESC(notify_delay_ms, struct zone_device { int cpu; bool work_scheduled; - u32 msr_pkg_therm_low; - u32 msr_pkg_therm_high; + u64 msr_pkg_therm; struct delayed_work work; struct thermal_zone_device *tzone; struct cpumask cpumask; @@ -125,8 +125,9 @@ sys_set_trip_temp(struct thermal_zone_device *tzd, { struct zone_device *zonedev = thermal_zone_device_priv(tzd); unsigned int trip_index = THERMAL_TRIP_PRIV_TO_INT(trip->priv); - u32 l, h, mask, shift, intr; + u32 mask, shift, intr; int tj_max, val, ret; + struct msr v; if (temp == THERMAL_TEMP_INVALID) temp = 0; @@ -141,8 +142,7 @@ sys_set_trip_temp(struct thermal_zone_device *tzd, if (trip_index >= MAX_NUMBER_OF_TRIPS || val < 0 || val > 0x7f) return -EINVAL; - ret = rdmsr_on_cpu(zonedev->cpu, MSR_IA32_PACKAGE_THERM_INTERRUPT, - &l, &h); + ret = rdmsrq_on_cpu(zonedev->cpu, MSR_IA32_PACKAGE_THERM_INTERRUPT, &v.q); if (ret < 0) return ret; @@ -155,20 +155,19 @@ sys_set_trip_temp(struct thermal_zone_device *tzd, shift = THERM_SHIFT_THRESHOLD0; intr = THERM_INT_THRESHOLD0_ENABLE; } - l &= ~mask; + v.l &= ~mask; /* * When users space sets a trip temperature == 0, which is indication * that, it is no longer interested in receiving notifications. */ if (!temp) { - l &= ~intr; + v.l &= ~intr; } else { - l |= val << shift; - l |= intr; + v.l |= val << shift; + v.l |= intr; } - return wrmsr_on_cpu(zonedev->cpu, MSR_IA32_PACKAGE_THERM_INTERRUPT, - l, h); + return wrmsrq_on_cpu(zonedev->cpu, MSR_IA32_PACKAGE_THERM_INTERRUPT, v.q); } /* Thermal zone callback registry */ @@ -186,28 +185,28 @@ static bool pkg_thermal_rate_control(void) static inline void enable_pkg_thres_interrupt(void) { u8 thres_0, thres_1; - u32 l, h; + struct msr val; - rdmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h); + rdmsrq(MSR_IA32_PACKAGE_THERM_INTERRUPT, val.q); /* only enable/disable if it had valid threshold value */ - thres_0 = (l & THERM_MASK_THRESHOLD0) >> THERM_SHIFT_THRESHOLD0; - thres_1 = (l & THERM_MASK_THRESHOLD1) >> THERM_SHIFT_THRESHOLD1; + thres_0 = (val.l & THERM_MASK_THRESHOLD0) >> THERM_SHIFT_THRESHOLD0; + thres_1 = (val.l & THERM_MASK_THRESHOLD1) >> THERM_SHIFT_THRESHOLD1; if (thres_0) - l |= THERM_INT_THRESHOLD0_ENABLE; + val.l |= THERM_INT_THRESHOLD0_ENABLE; if (thres_1) - l |= THERM_INT_THRESHOLD1_ENABLE; - wrmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h); + val.l |= THERM_INT_THRESHOLD1_ENABLE; + wrmsrq(MSR_IA32_PACKAGE_THERM_INTERRUPT, val.q); } /* Disable threshold interrupt on local package/cpu */ static inline void disable_pkg_thres_interrupt(void) { - u32 l, h; + struct msr val; - rdmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h); + rdmsrq(MSR_IA32_PACKAGE_THERM_INTERRUPT, val.q); - l &= ~(THERM_INT_THRESHOLD0_ENABLE | THERM_INT_THRESHOLD1_ENABLE); - wrmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h); + val.l &= ~(THERM_INT_THRESHOLD0_ENABLE | THERM_INT_THRESHOLD1_ENABLE); + wrmsrq(MSR_IA32_PACKAGE_THERM_INTERRUPT, val.q); } static void pkg_temp_thermal_threshold_work_fn(struct work_struct *work) @@ -277,7 +276,8 @@ static int pkg_temp_thermal_trips_init(int cpu, int tj_max, struct thermal_trip *trips, int num_trips) { unsigned long thres_reg_value; - u32 mask, shift, eax, edx; + u32 mask, shift; + struct msr val; int ret, i; for (i = 0; i < num_trips; i++) { @@ -290,12 +290,11 @@ static int pkg_temp_thermal_trips_init(int cpu, int tj_max, shift = THERM_SHIFT_THRESHOLD0; } - ret = rdmsr_on_cpu(cpu, MSR_IA32_PACKAGE_THERM_INTERRUPT, - &eax, &edx); + ret = rdmsrq_on_cpu(cpu, MSR_IA32_PACKAGE_THERM_INTERRUPT, &val.q); if (ret < 0) return ret; - thres_reg_value = (eax & mask) >> shift; + thres_reg_value = (val.l & mask) >> shift; trips[i].temperature = thres_reg_value ? tj_max - thres_reg_value * 1000 : THERMAL_TEMP_INVALID; @@ -357,8 +356,7 @@ static int pkg_temp_thermal_device_add(unsigned int cpu) goto out_unregister_tz; /* Store MSR value for package thermal interrupt, to restore at exit */ - rdmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, zonedev->msr_pkg_therm_low, - zonedev->msr_pkg_therm_high); + rdmsrq(MSR_IA32_PACKAGE_THERM_INTERRUPT, zonedev->msr_pkg_therm); cpumask_set_cpu(cpu, &zonedev->cpumask); raw_spin_lock_irq(&pkg_temp_lock); @@ -426,8 +424,8 @@ static int pkg_thermal_cpu_offline(unsigned int cpu) if (lastcpu) { zones[topology_logical_die_id(cpu)] = NULL; /* After this point nothing touches the MSR anymore. */ - wrmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, - zonedev->msr_pkg_therm_low, zonedev->msr_pkg_therm_high); + wrmsrq(MSR_IA32_PACKAGE_THERM_INTERRUPT, + zonedev->msr_pkg_therm); } /* diff --git a/drivers/thermal/khadas_mcu_fan.c b/drivers/thermal/khadas_mcu_fan.c index d35e5313bea4..21b3d0a71bd0 100644 --- a/drivers/thermal/khadas_mcu_fan.c +++ b/drivers/thermal/khadas_mcu_fan.c @@ -90,9 +90,10 @@ static int khadas_mcu_fan_probe(struct platform_device *pdev) ctx->mcu = mcu; platform_set_drvdata(pdev, ctx); - cdev = devm_thermal_of_cooling_device_register(dev->parent, - dev->parent->of_node, "khadas-mcu-fan", ctx, - &khadas_mcu_fan_cooling_ops); + cdev = devm_thermal_of_child_cooling_device_register(dev->parent, + dev->parent->of_node, + "khadas-mcu-fan", ctx, + &khadas_mcu_fan_cooling_ops); if (IS_ERR(cdev)) { ret = PTR_ERR(cdev); dev_err(dev, "Failed to register khadas-mcu-fan as cooling device: %d\n", diff --git a/drivers/thermal/loongson2_thermal.c b/drivers/thermal/loongson2_thermal.c index ea4dd2fb1f47..4d40fc706a53 100644 --- a/drivers/thermal/loongson2_thermal.c +++ b/drivers/thermal/loongson2_thermal.c @@ -8,7 +8,6 @@ #include <linux/interrupt.h> #include <linux/io.h> #include <linux/minmax.h> -#include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/property.h> @@ -174,7 +173,7 @@ static int loongson2_thermal_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(dev, irq, NULL, loongson2_thermal_irq_thread, IRQF_ONESHOT, "loongson2_thermal", tzd); if (ret < 0) - return dev_err_probe(dev, ret, "failed to request alarm irq\n"); + return ret; devm_thermal_add_hwmon_sysfs(dev, tzd); diff --git a/drivers/thermal/max77620_thermal.c b/drivers/thermal/max77620_thermal.c index 85a12e98d6dc..f4a1535f4806 100644 --- a/drivers/thermal/max77620_thermal.c +++ b/drivers/thermal/max77620_thermal.c @@ -121,19 +121,15 @@ static int max77620_thermal_probe(struct platform_device *pdev) max77620_thermal_irq, IRQF_ONESHOT | IRQF_SHARED, dev_name(&pdev->dev), mtherm); - if (ret < 0) { - dev_err(&pdev->dev, "Failed to request irq1: %d\n", ret); + if (ret < 0) return ret; - } ret = devm_request_threaded_irq(&pdev->dev, mtherm->irq_tjalarm2, NULL, max77620_thermal_irq, IRQF_ONESHOT | IRQF_SHARED, dev_name(&pdev->dev), mtherm); - if (ret < 0) { - dev_err(&pdev->dev, "Failed to request irq2: %d\n", ret); + if (ret < 0) return ret; - } return 0; } diff --git a/drivers/thermal/mediatek/lvts_thermal.c b/drivers/thermal/mediatek/lvts_thermal.c index a9617d5e0077..52f99f3e6450 100644 --- a/drivers/thermal/mediatek/lvts_thermal.c +++ b/drivers/thermal/mediatek/lvts_thermal.c @@ -1470,7 +1470,20 @@ static int lvts_probe(struct platform_device *pdev) if (IS_ERR(lvts_td->base)) return dev_err_probe(dev, PTR_ERR(lvts_td->base), "Failed to map io resource\n"); - lvts_td->reset = devm_reset_control_get_by_index(dev, 0); + /* + * Depending on the SoC+Firmware combination, the LVTS hardware may be + * may be actively used by one or even multiple concurrent MCUs! + * In this case, resetting it may produce either a severe slowdown of + * the entire system, or even a thermal protection AP reset, as some + * MCU(s) may be reading a very high or very low temperature while the + * LVTS is being reset. + * + * On those, don't fail if no reset is found as that may be omitted on + * purpose, but still check if there's one, because some board(s) may + * be running on a different bootchain with reduced firmwares or using + * firmwares with reduced functionality. + */ + lvts_td->reset = devm_reset_control_get_optional_exclusive(dev, NULL); if (IS_ERR(lvts_td->reset)) return dev_err_probe(dev, PTR_ERR(lvts_td->reset), "Failed to get reset control\n"); @@ -1491,7 +1504,7 @@ static int lvts_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(dev, irq, NULL, lvts_irq_handler, IRQF_ONESHOT, dev_name(dev), lvts_td); if (ret) - return dev_err_probe(dev, ret, "Failed to request interrupt\n"); + return ret; platform_set_drvdata(pdev, lvts_td); diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig index a6bb01082ec6..a8cf7e258201 100644 --- a/drivers/thermal/qcom/Kconfig +++ b/drivers/thermal/qcom/Kconfig @@ -21,6 +21,29 @@ config QCOM_SPMI_ADC_TM5 Thermal client sets threshold temperature for both warm and cool and gets updated when a threshold is reached. +config QCOM_SPMI_MBG_TM + tristate "Qualcomm SPMI PMIC MBG Temperature monitor" + depends on QCOM_SPMI_ADC5_GEN3 + select REGMAP_SPMI + help + This enables Qualcomm PMIC MBG (Master Bandgap) thermal monitor. + + The MBG block monitors PMIC die temperature in hardware and raises an + interrupt when the programmed threshold is crossed. + + Temperature is read from the associated ADC channel, and threshold + interrupts are forwarded to the thermal framework as trip-violation + events. Current support handles a single LVL1 upper (hot) trip. + +config QCOM_SPMI_ADC_TM5_GEN3 + tristate "Qualcomm SPMI PMIC Thermal Monitor ADC5 Gen3" + depends on QCOM_SPMI_ADC5_GEN3 + help + This enables the auxiliary thermal driver for the ADC5 Gen3 thermal + monitoring device. It shows up as a thermal zone with multiple trip points. + Thermal client sets threshold temperature for both warm and cool and + gets updated when a threshold is reached. + config QCOM_SPMI_TEMP_ALARM tristate "Qualcomm SPMI PMIC Temperature Alarm" depends on OF && SPMI && IIO diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile index 0fa2512042e7..937ba0fe2801 100644 --- a/drivers/thermal/qcom/Makefile +++ b/drivers/thermal/qcom/Makefile @@ -4,5 +4,7 @@ obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o qcom_tsens-y += tsens.o tsens-v2.o tsens-v1.o tsens-v0_1.o \ tsens-8960.o obj-$(CONFIG_QCOM_SPMI_ADC_TM5) += qcom-spmi-adc-tm5.o +obj-$(CONFIG_QCOM_SPMI_ADC_TM5_GEN3) += qcom-spmi-adc-tm5-gen3.o +obj-$(CONFIG_QCOM_SPMI_MBG_TM) += qcom-spmi-mbg-tm.o obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM) += qcom-spmi-temp-alarm.o obj-$(CONFIG_QCOM_LMH) += lmh.o diff --git a/drivers/thermal/qcom/lmh.c b/drivers/thermal/qcom/lmh.c index 3d072b7a4a6d..99396b93eff5 100644 --- a/drivers/thermal/qcom/lmh.c +++ b/drivers/thermal/qcom/lmh.c @@ -223,7 +223,6 @@ static int lmh_probe(struct platform_device *pdev) IRQF_NO_THREAD | IRQF_NO_SUSPEND, "lmh-irq", lmh_data); if (ret) { - dev_err(dev, "Error %d registering irq %x\n", ret, lmh_data->irq); irq_domain_remove(lmh_data->domain); return ret; } diff --git a/drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c b/drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c new file mode 100644 index 000000000000..9cf552d36868 --- /dev/null +++ b/drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c @@ -0,0 +1,434 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include <linux/auxiliary_bus.h> +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/cleanup.h> +#include <linux/container_of.h> +#include <linux/device/devres.h> +#include <linux/dev_printk.h> +#include <linux/err.h> +#include <linux/iio/adc/qcom-adc5-gen3-common.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/thermal.h> +#include <linux/types.h> +#include <linux/unaligned.h> + +#include "../thermal_hwmon.h" + +#define ADC_TM5_GEN3_CONFIG_REGS 12 + +struct device; +struct adc_tm5_gen3_chip; + +/** + * struct adc_tm5_gen3_channel_props - ADC_TM channel structure + * @common_props: structure with common ADC channel properties. + * @chip: ADC TM device. + * @tzd: pointer to thermal device corresponding to TM channel. + * @sdam_index: SDAM on which this TM channel lies. + * @timer: time period of recurring TM measurement. + * @tm_chan_index: TM channel number used. + * @high_thr_en: TM high threshold crossing detection enabled. + * @low_thr_en: TM low threshold crossing detection enabled. + */ +struct adc_tm5_gen3_channel_props { + struct adc5_channel_common_prop common_props; + struct adc_tm5_gen3_chip *chip; + struct thermal_zone_device *tzd; + unsigned int sdam_index; + unsigned int timer; + unsigned int tm_chan_index; + bool high_thr_en; + bool low_thr_en; +}; + +/** + * struct adc_tm5_gen3_chip - ADC Thermal Monitoring device structure + * @dev_data: Top-level ADC device data. + * @chan_props: Array of ADC_TM channel structures. + * @dev: SPMI ADC5 Gen3 device. + * @nchannels: number of TM channels allocated + */ +struct adc_tm5_gen3_chip { + struct adc5_device_data *dev_data; + struct adc_tm5_gen3_channel_props *chan_props; + struct device *dev; + unsigned int nchannels; +}; + +DEFINE_GUARD(adc5_gen3, struct adc_tm5_gen3_chip *, + adc5_gen3_mutex_lock(_T->dev), adc5_gen3_mutex_unlock(_T->dev)) + +static int get_sdam_from_irq(struct adc_tm5_gen3_chip *adc_tm5, int irq) +{ + for (int i = 0; i < adc_tm5->dev_data->num_sdams; i++) { + if (adc_tm5->dev_data->base[i].irq == irq) + return i; + } + return -ENOENT; +} + +static irqreturn_t adctm5_gen3_isr(int irq, void *dev_id) +{ + struct adc_tm5_gen3_chip *adc_tm5 = dev_id; + int ret, sdam_num; + u8 tm_status[2]; + u8 status, val; + + sdam_num = get_sdam_from_irq(adc_tm5, irq); + if (sdam_num < 0) + return IRQ_NONE; + + ret = adc5_gen3_read(adc_tm5->dev_data, sdam_num, ADC5_GEN3_STATUS1, + &status, sizeof(status)); + if (ret) + return IRQ_NONE; + + if (status & ADC5_GEN3_STATUS1_CONV_FAULT) { + val = ADC5_GEN3_CONV_ERR_CLR_REQ; + adc5_gen3_status_clear(adc_tm5->dev_data, sdam_num, + ADC5_GEN3_CONV_ERR_CLR, &val, 1); + return IRQ_HANDLED; + } + + ret = adc5_gen3_read(adc_tm5->dev_data, sdam_num, ADC5_GEN3_TM_HIGH_STS, + tm_status, sizeof(tm_status)); + if (ret) + return IRQ_NONE; + + if (tm_status[0] || tm_status[1]) + return IRQ_WAKE_THREAD; + + return IRQ_NONE; +} + +static irqreturn_t adctm5_gen3_isr_thread(int irq, void *dev_id) +{ + struct adc_tm5_gen3_chip *adc_tm5 = dev_id; + u8 tm_status[2]; + int sdam_index; + + sdam_index = get_sdam_from_irq(adc_tm5, irq); + if (sdam_index < 0) + return IRQ_NONE; + + scoped_guard(adc5_gen3, adc_tm5) { + int ret; + + ret = adc5_gen3_read(adc_tm5->dev_data, sdam_index, ADC5_GEN3_TM_HIGH_STS, + tm_status, sizeof(tm_status)); + if (ret) + return IRQ_NONE; + + ret = adc5_gen3_status_clear(adc_tm5->dev_data, sdam_index, + ADC5_GEN3_TM_HIGH_STS_CLR, tm_status, + sizeof(tm_status)); + if (ret) + return IRQ_NONE; + } + + for (int i = 0; i < adc_tm5->nchannels; i++) { + struct adc_tm5_gen3_channel_props *chan_prop = &adc_tm5->chan_props[i]; + int offset = chan_prop->tm_chan_index; + bool upper_set, lower_set; + + if (chan_prop->sdam_index != sdam_index) + continue; + + upper_set = ((tm_status[0] & BIT(offset)) && chan_prop->high_thr_en); + lower_set = ((tm_status[1] & BIT(offset)) && chan_prop->low_thr_en); + + if (!(upper_set || lower_set)) + continue; + + thermal_zone_device_update(chan_prop->tzd, THERMAL_TRIP_VIOLATED); + } + + return IRQ_HANDLED; +} + +static int adc_tm5_gen3_get_temp(struct thermal_zone_device *tz, int *temp) +{ + struct adc_tm5_gen3_channel_props *prop = thermal_zone_device_priv(tz); + struct adc_tm5_gen3_chip *adc_tm5; + + if (!prop || !prop->chip) + return -EINVAL; + + adc_tm5 = prop->chip; + + return adc5_gen3_get_scaled_reading(adc_tm5->dev, &prop->common_props, temp); +} + +static int adc_tm5_gen3_disable_channel(struct adc_tm5_gen3_channel_props *prop) +{ + struct adc_tm5_gen3_chip *adc_tm5 = prop->chip; + int ret; + u8 val; + + prop->high_thr_en = false; + prop->low_thr_en = false; + + ret = adc5_gen3_poll_wait_hs(adc_tm5->dev_data, prop->sdam_index); + if (ret) + return ret; + + val = BIT(prop->tm_chan_index); + ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, + ADC5_GEN3_TM_HIGH_STS_CLR, &val, sizeof(val)); + if (ret) + return ret; + + ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, + ADC5_GEN3_TM_LOW_STS_CLR, &val, sizeof(val)); + if (ret) + return ret; + + val = MEAS_INT_DISABLE; + ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, + ADC5_GEN3_TIMER_SEL, &val, sizeof(val)); + if (ret) + return ret; + + /* To indicate there is an actual conversion request */ + val = ADC5_GEN3_CHAN_CONV_REQ | prop->tm_chan_index; + ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, + ADC5_GEN3_PERPH_CH, &val, sizeof(val)); + if (ret) + return ret; + + val = ADC5_GEN3_CONV_REQ_REQ; + return adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, + ADC5_GEN3_CONV_REQ, &val, sizeof(val)); +} + +static int adc_tm5_gen3_configure(struct adc_tm5_gen3_channel_props *prop, + int low_temp, int high_temp) +{ + struct adc_tm5_gen3_chip *adc_tm5 = prop->chip; + u8 buf[ADC_TM5_GEN3_CONFIG_REGS]; + u8 conv_req; + u16 adc_code; + int ret; + + ret = adc5_gen3_poll_wait_hs(adc_tm5->dev_data, prop->sdam_index); + if (ret < 0) + return ret; + + ret = adc5_gen3_read(adc_tm5->dev_data, prop->sdam_index, + ADC5_GEN3_SID, buf, sizeof(buf)); + if (ret < 0) + return ret; + + /* Write SID */ + buf[0] = FIELD_PREP(ADC5_GEN3_SID_MASK, prop->common_props.sid); + + /* Select TM channel and indicate there is an actual conversion request */ + buf[1] = ADC5_GEN3_CHAN_CONV_REQ | prop->tm_chan_index; + + buf[2] = prop->timer; + + /* Digital param selection */ + adc5_gen3_update_dig_param(&prop->common_props, &buf[3]); + + /* Update fast average sample value */ + buf[4] = FIELD_PREP(ADC5_GEN3_FAST_AVG_CTL_SAMPLES_MASK, + prop->common_props.avg_samples) | ADC5_GEN3_FAST_AVG_CTL_EN; + + /* Select ADC channel */ + buf[5] = prop->common_props.channel; + + /* Select HW settle delay for channel */ + buf[6] = FIELD_PREP(ADC5_GEN3_HW_SETTLE_DELAY_MASK, + prop->common_props.hw_settle_time_us); + + buf[7] = 0; + + /* High temperature corresponds to low voltage threshold */ + prop->low_thr_en = (high_temp != INT_MAX); + if (prop->low_thr_en) { + adc_code = qcom_adc_tm5_gen2_temp_res_scale(high_temp); + put_unaligned_le16(adc_code, &buf[8]); + buf[7] |= ADC5_GEN3_LOW_THR_INT_EN; + } + + /* Low temperature corresponds to high voltage threshold */ + prop->high_thr_en = (low_temp != -INT_MAX); + if (prop->high_thr_en) { + adc_code = qcom_adc_tm5_gen2_temp_res_scale(low_temp); + put_unaligned_le16(adc_code, &buf[10]); + buf[7] |= ADC5_GEN3_HIGH_THR_INT_EN; + } + + ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, ADC5_GEN3_SID, + buf, sizeof(buf)); + if (ret < 0) + return ret; + + conv_req = ADC5_GEN3_CONV_REQ_REQ; + return adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, + ADC5_GEN3_CONV_REQ, &conv_req, sizeof(conv_req)); +} + +static int adc_tm5_gen3_set_trip_temp(struct thermal_zone_device *tz, + int low_temp, int high_temp) +{ + struct adc_tm5_gen3_channel_props *prop = thermal_zone_device_priv(tz); + struct adc_tm5_gen3_chip *adc_tm5; + + if (!prop || !prop->chip) + return -EINVAL; + + adc_tm5 = prop->chip; + + dev_dbg(adc_tm5->dev, "channel:%s, low_temp(mdegC):%d, high_temp(mdegC):%d\n", + prop->common_props.label, low_temp, high_temp); + + guard(adc5_gen3)(adc_tm5); + + return adc_tm5_gen3_configure(prop, low_temp, high_temp); +} + +static const struct thermal_zone_device_ops adc_tm_ops = { + .get_temp = adc_tm5_gen3_get_temp, + .set_trips = adc_tm5_gen3_set_trip_temp, +}; + +static int adc_tm5_register_tzd(struct adc_tm5_gen3_chip *adc_tm5) +{ + struct thermal_zone_device *tzd; + unsigned int channel; + int ret; + + for (int i = 0; i < adc_tm5->nchannels; i++) { + channel = ADC5_GEN3_V_CHAN(adc_tm5->chan_props[i].common_props); + tzd = devm_thermal_of_zone_register(adc_tm5->dev, channel, + &adc_tm5->chan_props[i], + &adc_tm_ops); + if (IS_ERR(tzd)) { + if (PTR_ERR(tzd) == -ENODEV) { + dev_dbg(adc_tm5->dev, + "thermal sensor on channel %d is not used\n", + channel); + continue; + } + return PTR_ERR(tzd); + } + adc_tm5->chan_props[i].tzd = tzd; + ret = devm_thermal_add_hwmon_sysfs(adc_tm5->dev, tzd); + if (ret) + return ret; + } + + return 0; +} + +static void adc5_gen3_disable(void *data) +{ + struct adc_tm5_gen3_chip *adc_tm5 = data; + + guard(adc5_gen3)(adc_tm5); + + /* Disable all available TM channels */ + for (int i = 0; i < adc_tm5->nchannels; i++) + adc_tm5_gen3_disable_channel(&adc_tm5->chan_props[i]); +} + +static int adc_tm5_probe(struct auxiliary_device *aux_dev, + const struct auxiliary_device_id *id) +{ + struct adc_tm5_gen3_chip *adc_tm5; + struct tm5_aux_dev_wrapper *aux_dev_wrapper; + struct device *dev = &aux_dev->dev; + int ret; + + adc_tm5 = devm_kzalloc(dev, sizeof(*adc_tm5), GFP_KERNEL); + if (!adc_tm5) + return -ENOMEM; + + aux_dev_wrapper = container_of(aux_dev, struct tm5_aux_dev_wrapper, aux_dev); + + adc_tm5->dev = dev; + adc_tm5->dev_data = aux_dev_wrapper->dev_data; + adc_tm5->nchannels = aux_dev_wrapper->n_tm_channels; + adc_tm5->chan_props = devm_kcalloc(dev, aux_dev_wrapper->n_tm_channels, + sizeof(*adc_tm5->chan_props), GFP_KERNEL); + if (!adc_tm5->chan_props) + return -ENOMEM; + + for (int i = 0; i < adc_tm5->nchannels; i++) { + /* + * Since the first channel of the first SDAM is reserved for + * immediate ADC conversions, TM channel count must start from + * the channel just after it. The variable tm_count is used to + * calculate SDAM and TM channel index on that SDAM correctly + * for each TM channel. + */ + int tm_count = i + 1; + + adc_tm5->chan_props[i].common_props = aux_dev_wrapper->tm_props[i]; + adc_tm5->chan_props[i].timer = MEAS_INT_1S; + adc_tm5->chan_props[i].sdam_index = tm_count / 8; + adc_tm5->chan_props[i].tm_chan_index = tm_count % 8; + adc_tm5->chan_props[i].chip = adc_tm5; + } + + /* + * ADC_TM channels are enabled in the loop in adc_tm5_register_tzd() as + * part of the set_trips calls during thermal zone registration. This + * action is to disable them all in case of probe failure. + */ + ret = devm_add_action(dev, adc5_gen3_disable, adc_tm5); + if (ret) + return ret; + + ret = adc_tm5_register_tzd(adc_tm5); + if (ret) + return ret; + + for (int i = 0; i < adc_tm5->dev_data->num_sdams; i++) { + u32 irq_flags = IRQF_ONESHOT; + + /* + * First SDAM's interrupt is shared between main ADC driver and + * auxiliary TM driver, so its flags must include IRQF_SHARED. + * This is not needed for other SDAMs as they will be used only + * for TM functionality. + */ + if (i == 0) + irq_flags |= IRQF_SHARED; + + ret = devm_request_threaded_irq(dev, + adc_tm5->dev_data->base[i].irq, + adctm5_gen3_isr, + adctm5_gen3_isr_thread, + irq_flags, + adc_tm5->dev_data->base[i].irq_name, + adc_tm5); + if (ret < 0) + return ret; + } + + return 0; +} + +static const struct auxiliary_device_id adctm5_auxiliary_id_table[] = { + { .name = "qcom_spmi_adc5_gen3.adc5_tm_gen3" }, + { } +}; +MODULE_DEVICE_TABLE(auxiliary, adctm5_auxiliary_id_table); + +static struct auxiliary_driver adctm5gen3_auxiliary_driver = { + .id_table = adctm5_auxiliary_id_table, + .probe = adc_tm5_probe, +}; +module_auxiliary_driver(adctm5gen3_auxiliary_driver); + +MODULE_DESCRIPTION("SPMI PMIC Thermal Monitor ADC driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("QCOM_SPMI_ADC5_GEN3"); diff --git a/drivers/thermal/qcom/qcom-spmi-adc-tm5.c b/drivers/thermal/qcom/qcom-spmi-adc-tm5.c index d7f2e6ca92c2..af72db6299cd 100644 --- a/drivers/thermal/qcom/qcom-spmi-adc-tm5.c +++ b/drivers/thermal/qcom/qcom-spmi-adc-tm5.c @@ -369,9 +369,6 @@ static int adc_tm5_get_temp(struct thermal_zone_device *tz, int *temp) if (ret < 0) return ret; - if (ret != IIO_VAL_INT) - return -EINVAL; - return 0; } @@ -1069,3 +1066,4 @@ module_platform_driver(adc_tm5_driver); MODULE_DESCRIPTION("SPMI PMIC Thermal Monitor ADC driver"); MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_CONSUMER"); diff --git a/drivers/thermal/qcom/qcom-spmi-mbg-tm.c b/drivers/thermal/qcom/qcom-spmi-mbg-tm.c new file mode 100644 index 000000000000..a05bb444627a --- /dev/null +++ b/drivers/thermal/qcom/qcom-spmi-mbg-tm.c @@ -0,0 +1,257 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ +#include <linux/bitfield.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/thermal.h> +#include <linux/iio/consumer.h> + +#define MBG_TEMP_MON2_FAULT_STATUS 0x50 + +#define MON_FAULT_STATUS_MASK GENMASK(7, 4) +#define MON_FAULT_LVL1_UPR 0x5 + +#define MON2_LVL1_UP_THRESH 0x59 + +#define MBG_TEMP_MON2_MISC_CFG 0x5f +#define MON2_UP_THRESH_EN BIT(1) + +#define MBG_TEMP_STEP_MV 8 +#define MBG_TEMP_DEFAULT_TEMP_MV 600 +#define MBG_TEMP_CONSTANT 1000 +#define MBG_MIN_TRIP_TEMP 25000 +#define MBG_MAX_SUPPORTED_TEMP 160000 + +/** + * struct mbg_tm_chip - MBG thermal monitor device data. + * @map: regmap for accessing MBG thermal registers. + * @dev: mbg_tm_chip device. + * @tz_dev: thermal zone device registered with the thermal framework. + * @lock: mbg_tm_chip lock for set trip temperature. + * @base: base register offset for this MBG instance + * @irq: interrupt line used to signal threshold events + * @last_temp: last measured temperature. + * @last_thres_crossed: indicates whether the last interrupt crossed a threshold + * @adc: IIO ADC channel used for temperature sensing + */ +struct mbg_tm_chip { + struct regmap *map; + struct device *dev; + struct thermal_zone_device *tz_dev; + struct mutex lock; + unsigned int base; + int irq; + int last_temp; + bool last_thres_crossed; + struct iio_channel *adc; +}; + +/** + * struct mbg_map_table - temperature to voltage mapping entry + * @min_temp: minimum temperature supported by this mapping entry + * @vtemp0: reference voltage or ADC code corresponding to the temperature + * @tc: temperature coefficient used for conversion calculations + */ +struct mbg_map_table { + int min_temp; + int vtemp0; + int tc; +}; + +static const struct mbg_map_table map_table[] = { + { -60000, 4337, 1967 }, + { -40000, 4731, 1964 }, + { -20000, 5124, 1957 }, + { 0, 5515, 1949 }, + { 20000, 5905, 1940 }, + { 40000, 6293, 1930 }, + { 60000, 6679, 1921 }, + { 80000, 7064, 1910 }, + { 100000, 7446, 1896 }, + { 120000, 7825, 1878 }, + { 140000, 8201, 1859 }, +}; + +static int mbg_tm_get_temp(struct thermal_zone_device *tz, int *temp) +{ + struct mbg_tm_chip *chip = thermal_zone_device_priv(tz); + int ret, milli_celsius; + + scoped_guard(mutex, &chip->lock) { + if (chip->last_thres_crossed) { + dev_dbg(chip->dev, "last_temp: %d\n", chip->last_temp); + chip->last_thres_crossed = false; + *temp = chip->last_temp; + return 0; + } + } + + ret = iio_read_channel_processed(chip->adc, &milli_celsius); + if (ret < 0) { + dev_err(chip->dev, "Failed to read iio channel with %d\n", ret); + return ret; + } + + *temp = milli_celsius; + + return 0; +} + +static int temp_to_vtemp_mv(int temp) +{ + int idx, vtemp, tc = 0, t0 = 0, vtemp0 = 0; + + for (idx = 0; idx < ARRAY_SIZE(map_table); idx++) + if (temp >= map_table[idx].min_temp && + temp < (map_table[idx].min_temp + 20000)) { + tc = map_table[idx].tc; + t0 = map_table[idx].min_temp; + vtemp0 = map_table[idx].vtemp0; + break; + } + + /* + * Formula to calculate vtemp(mV) from a given temp + * vtemp = (temp - minT) * tc + vtemp0 + * tc, t0 and vtemp0 values are mentioned in the map_table array. + */ + vtemp = ((temp - t0) * tc + vtemp0 * 100000) / 1000000; + + /* step size is 8mV */ + return abs(vtemp - MBG_TEMP_DEFAULT_TEMP_MV) / MBG_TEMP_STEP_MV; +} + +static int mbg_tm_set_trip_temp(struct thermal_zone_device *tz, int low_temp, + int temp) +{ + struct mbg_tm_chip *chip = thermal_zone_device_priv(tz); + int ret = 0; + + guard(mutex)(&chip->lock); + + /* The HW has a limitation that the trip set must be above 25C */ + if (temp > MBG_MIN_TRIP_TEMP && temp < MBG_MAX_SUPPORTED_TEMP) { + ret = regmap_write(chip->map, chip->base + MON2_LVL1_UP_THRESH, + temp_to_vtemp_mv(temp)); + if (ret < 0) + return ret; + + ret = regmap_set_bits(chip->map, chip->base + MBG_TEMP_MON2_MISC_CFG, + MON2_UP_THRESH_EN); + if (ret < 0) + return ret; + } else { + dev_err(chip->dev, "Set trip b/w 25C and 160C\n"); + ret = regmap_clear_bits(chip->map, chip->base + MBG_TEMP_MON2_MISC_CFG, + MON2_UP_THRESH_EN); + return -ERANGE; + } + + /* + * Configure the last_temp one degree higher, to ensure the + * violated temp is returned to thermal framework when it reads + * temperature for the first time after the violation happens. + * This is needed to account for the inaccuracy in the conversion + * formula used which leads to the thermal framework setting back + * the same thresholds in case the temperature it reads does not + * show violation. + */ + chip->last_temp = temp + MBG_TEMP_CONSTANT; + + return ret; +} + +static const struct thermal_zone_device_ops mbg_tm_ops = { + .get_temp = mbg_tm_get_temp, + .set_trips = mbg_tm_set_trip_temp, +}; + +static irqreturn_t mbg_tm_isr(int irq, void *data) +{ + struct mbg_tm_chip *chip = data; + int ret, val; + + scoped_guard(mutex, &chip->lock) { + ret = regmap_read(chip->map, chip->base + MBG_TEMP_MON2_FAULT_STATUS, &val); + if (ret < 0) + return IRQ_HANDLED; + if (FIELD_GET(MON_FAULT_STATUS_MASK, val) == MON_FAULT_LVL1_UPR) + chip->last_thres_crossed = true; + } + + if (FIELD_GET(MON_FAULT_STATUS_MASK, val) == MON_FAULT_LVL1_UPR) { + dev_dbg(chip->dev, "Notifying Thermal, fault status=%d\n", val); + thermal_zone_device_update(chip->tz_dev, THERMAL_TRIP_VIOLATED); + } else { + dev_dbg(chip->dev, "Lvl1 upper threshold not violated, ignoring interrupt\n"); + } + + return IRQ_HANDLED; +} + +static int mbg_tm_probe(struct platform_device *pdev) +{ + struct mbg_tm_chip *chip; + struct device_node *node = pdev->dev.of_node; + u32 res; + int ret; + + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->dev = &pdev->dev; + + mutex_init(&chip->lock); + + chip->map = dev_get_regmap(pdev->dev.parent, NULL); + if (!chip->map) + return -ENXIO; + + ret = device_property_read_u32(chip->dev, "reg", &res); + if (ret < 0) + return dev_err_probe(chip->dev, ret, "Couldn't read reg property\n"); + + chip->base = res; + + chip->irq = platform_get_irq(pdev, 0); + if (chip->irq < 0) + return dev_err_probe(chip->dev, chip->irq, "Failed to get irq\n"); + + chip->adc = devm_iio_channel_get(&pdev->dev, "thermal"); + if (IS_ERR(chip->adc)) + return dev_err_probe(chip->dev, PTR_ERR(chip->adc), "Failed to get adc channel\n"); + + chip->tz_dev = devm_thermal_of_zone_register(chip->dev, 0, chip, &mbg_tm_ops); + if (IS_ERR(chip->tz_dev)) + return dev_err_probe(chip->dev, PTR_ERR(chip->tz_dev), + "Failed to register sensor\n"); + + return devm_request_threaded_irq(&pdev->dev, chip->irq, NULL, mbg_tm_isr, IRQF_ONESHOT, + node->name, chip); +} + +static const struct of_device_id mbg_tm_match_table[] = { + { .compatible = "qcom,pm8775-mbg-tm" }, + { } +}; +MODULE_DEVICE_TABLE(of, mbg_tm_match_table); + +static struct platform_driver mbg_tm_driver = { + .driver = { + .name = "qcom-spmi-mbg-tm", + .of_match_table = mbg_tm_match_table, + }, + .probe = mbg_tm_probe, +}; +module_platform_driver(mbg_tm_driver); + +MODULE_DESCRIPTION("PMIC MBG Temperature monitor driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_CONSUMER"); diff --git a/drivers/thermal/qcom/qcom-spmi-temp-alarm.c b/drivers/thermal/qcom/qcom-spmi-temp-alarm.c index f39ca0ddd17b..fb003ca96454 100644 --- a/drivers/thermal/qcom/qcom-spmi-temp-alarm.c +++ b/drivers/thermal/qcom/qcom-spmi-temp-alarm.c @@ -904,3 +904,4 @@ module_platform_driver(qpnp_tm_driver); MODULE_ALIAS("platform:spmi-temp-alarm"); MODULE_DESCRIPTION("QPNP PMIC Temperature Alarm driver"); MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_CONSUMER"); diff --git a/drivers/thermal/qcom/tsens-v2.c b/drivers/thermal/qcom/tsens-v2.c index 8d9698ea3ec4..2ee117aa91ba 100644 --- a/drivers/thermal/qcom/tsens-v2.c +++ b/drivers/thermal/qcom/tsens-v2.c @@ -263,7 +263,6 @@ static int __init init_tsens_v2_no_rpm(struct tsens_priv *priv) static const struct tsens_ops ops_generic_v2 = { .init = init_common, .get_temp = get_temp_tsens_valid, - .resume = tsens_resume_common, }; struct tsens_plat_data data_tsens_v2 = { @@ -307,3 +306,11 @@ struct tsens_plat_data data_8996 = { .feat = &tsens_v2_feat, .fields = tsens_v2_regfields, }; + +/* Do not enable wakeup capable interrupts for automotive platforms */ +struct tsens_plat_data data_automotive_v2 = { + .ops = &ops_generic_v2, + .feat = &tsens_v2_feat, + .fields = tsens_v2_regfields, + .no_irq_wake = true, +}; diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c index a2422ebee816..b5ec70201e2f 100644 --- a/drivers/thermal/qcom/tsens.c +++ b/drivers/thermal/qcom/tsens.c @@ -27,7 +27,7 @@ * @up_viol: upper threshold violated * @up_thresh: upper threshold temperature value * @up_irq_mask: mask register for upper threshold irqs - * @up_irq_clear: clear register for uppper threshold irqs + * @up_irq_clear: clear register for upper threshold irqs * @low_viol: lower threshold violated * @low_thresh: lower threshold temperature value * @low_irq_mask: mask register for lower threshold irqs @@ -316,9 +316,66 @@ static inline int code_to_degc(u32 adc_code, const struct tsens_sensor *s) } /** - * tsens_hw_to_mC - Return sign-extended temperature in mCelsius. + * tsens_read_temp - Retrieve temperature readings from the hardware. * @s: Pointer to sensor struct * @field: Index into regmap_field array pointing to temperature data + * @temp: temperature in deciCelsius to be read from hardware + * + * This function handles temperature returned in ADC code or deciCelsius + * depending on IP version. + * + * Return: 0 on success, a negative errno will be returned in error cases + */ +static int tsens_read_temp(const struct tsens_sensor *s, int field, int *temp) +{ + struct tsens_priv *priv = s->priv; + int temp_val[MAX_READ_RETRY] = {0}; + u32 status; + int ret; + u32 last_temp_mask = GENMASK(priv->fields[LAST_TEMP_0].msb, + priv->fields[LAST_TEMP_0].lsb); + u32 valid_bit = priv->rf[VALID_0] ? BIT(priv->fields[VALID_0].lsb) : 0; + + for (int i = 0; i < MAX_READ_RETRY; i++) { + ret = regmap_read(priv->tm_map, priv->fields[field].reg, &status); + if (ret) + return ret; + + /* VER_0 doesn't have a VALID bit */ + if (!valid_bit) { + *temp = status & last_temp_mask; + return 0; + } + + temp_val[i] = status & last_temp_mask; + + if (status & valid_bit) { + *temp = temp_val[i]; + return 0; + } + } + + /* + * As per the HW guidelines, if none of the attempts observe a + * valid sample, a stable fallback value must be returned. If the + * first and second samples match, the second value is returned; + * otherwise, if the second and third samples match, the third + * value is returned. + */ + if (temp_val[0] == temp_val[1]) + *temp = temp_val[1]; + else if (temp_val[1] == temp_val[2]) + *temp = temp_val[2]; + else + return -EAGAIN; + + return 0; +} + +/** + * tsens_hw_to_mC - Return sign-extended temperature in mCelsius. + * @s: Pointer to sensor struct + * @temp: temperature in milliCelsius to be read from hardware * * This function handles temperature returned in ADC code or deciCelsius * depending on IP version. @@ -326,20 +383,14 @@ static inline int code_to_degc(u32 adc_code, const struct tsens_sensor *s) * Return: Temperature in milliCelsius on success, a negative errno will * be returned in error cases */ -static int tsens_hw_to_mC(const struct tsens_sensor *s, int field) +static int tsens_hw_to_mC(const struct tsens_sensor *s, int temp) { struct tsens_priv *priv = s->priv; u32 resolution; - u32 temp = 0; - int ret; resolution = priv->fields[LAST_TEMP_0].msb - priv->fields[LAST_TEMP_0].lsb; - ret = regmap_field_read(priv->rf[field], &temp); - if (ret) - return ret; - /* Convert temperature from ADC code to milliCelsius */ if (priv->feat->adc) return code_to_degc(temp, s) * 1000; @@ -514,8 +565,10 @@ static int tsens_read_irq_state(struct tsens_priv *priv, u32 hw_id, &d->crit_irq_mask); if (ret) return ret; - - d->crit_thresh = tsens_hw_to_mC(s, CRIT_THRESH_0 + hw_id); + ret = regmap_field_read(priv->rf[CRIT_THRESH_0 + hw_id], &d->crit_thresh); + if (ret) + return ret; + d->crit_thresh = tsens_hw_to_mC(s, d->crit_thresh); } else { /* No mask register on older TSENS */ d->up_irq_mask = 0; @@ -525,8 +578,16 @@ static int tsens_read_irq_state(struct tsens_priv *priv, u32 hw_id, d->crit_thresh = 0; } - d->up_thresh = tsens_hw_to_mC(s, UP_THRESH_0 + hw_id); - d->low_thresh = tsens_hw_to_mC(s, LOW_THRESH_0 + hw_id); + ret = regmap_field_read(priv->rf[UP_THRESH_0 + hw_id], &d->up_thresh); + if (ret) + return ret; + + d->up_thresh = tsens_hw_to_mC(s, d->up_thresh); + ret = regmap_field_read(priv->rf[LOW_THRESH_0 + hw_id], &d->low_thresh); + if (ret) + return ret; + + d->low_thresh = tsens_hw_to_mC(s, d->low_thresh); dev_dbg(priv->dev, "[%u] %s%s: status(%u|%u|%u) | clr(%u|%u|%u) | mask(%u|%u|%u)\n", hw_id, __func__, @@ -750,33 +811,15 @@ static void tsens_disable_irq(struct tsens_priv *priv) int get_temp_tsens_valid(const struct tsens_sensor *s, int *temp) { - struct tsens_priv *priv = s->priv; int hw_id = s->hw_id; u32 temp_idx = LAST_TEMP_0 + hw_id; - u32 valid_idx = VALID_0 + hw_id; - u32 valid; int ret; - /* VER_0 doesn't have VALID bit */ - if (tsens_version(priv) == VER_0) - goto get_temp; - - /* Valid bit is 0 for 6 AHB clock cycles. - * At 19.2MHz, 1 AHB clock is ~60ns. - * We should enter this loop very, very rarely. - * Wait 1 us since it's the min of poll_timeout macro. - * Old value was 400 ns. - */ - ret = regmap_field_read_poll_timeout(priv->rf[valid_idx], valid, - valid, 1, 20 * USEC_PER_MSEC); - if (ret) - return ret; - -get_temp: - /* Valid bit is set, OK to read the temperature */ - *temp = tsens_hw_to_mC(s, temp_idx); + ret = tsens_read_temp(s, temp_idx, temp); + if (!ret) + *temp = tsens_hw_to_mC(s, *temp); - return 0; + return ret; } int get_temp_common(const struct tsens_sensor *s, int *temp) @@ -1086,22 +1129,30 @@ static int tsens_get_temp(struct thermal_zone_device *tz, int *temp) static int __maybe_unused tsens_suspend(struct device *dev) { + int ret = 0; struct tsens_priv *priv = dev_get_drvdata(dev); - if (priv->ops && priv->ops->suspend) - return priv->ops->suspend(priv); + if (priv->ops && priv->ops->suspend) { + ret = priv->ops->suspend(priv); + if (ret) + return ret; + } - return 0; + return tsens_suspend_common(priv); } static int __maybe_unused tsens_resume(struct device *dev) { + int ret = 0; struct tsens_priv *priv = dev_get_drvdata(dev); - if (priv->ops && priv->ops->resume) - return priv->ops->resume(priv); + if (priv->ops && priv->ops->resume) { + ret = priv->ops->resume(priv); + if (ret) + return ret; + } - return 0; + return tsens_resume_common(priv); } static SIMPLE_DEV_PM_OPS(tsens_pm_ops, tsens_suspend, tsens_resume); @@ -1161,6 +1212,12 @@ static const struct of_device_id tsens_table[] = { }, { .compatible = "qcom,tsens-v2", .data = &data_tsens_v2, + }, { + .compatible = "qcom,sa8775p-tsens", + .data = &data_automotive_v2, + }, { + .compatible = "qcom,sa8255p-tsens", + .data = &data_automotive_v2, }, {} }; @@ -1172,7 +1229,7 @@ static const struct thermal_zone_device_ops tsens_of_ops = { }; static int tsens_register_irq(struct tsens_priv *priv, char *irqname, - irq_handler_t thread_fn) + irq_handler_t thread_fn, int *irq_num) { struct platform_device *pdev; int ret, irq; @@ -1201,11 +1258,8 @@ static int tsens_register_irq(struct tsens_priv *priv, char *irqname, dev_name(&pdev->dev), priv); - if (ret) - dev_err(&pdev->dev, "%s: failed to get irq\n", - __func__); - else - enable_irq_wake(irq); + if (!ret) + *irq_num = irq; } put_device(&pdev->dev); @@ -1232,11 +1286,38 @@ static int tsens_reinit(struct tsens_priv *priv) return 0; } +int tsens_suspend_common(struct tsens_priv *priv) +{ + if (!device_may_wakeup(priv->dev)) + return 0; + + if (priv->feat->combo_int) + enable_irq_wake(priv->combined_irq); + else { + enable_irq_wake(priv->uplow_irq); + if (priv->feat->crit_int) + enable_irq_wake(priv->crit_irq); + } + + return 0; +} + int tsens_resume_common(struct tsens_priv *priv) { if (pm_suspend_target_state == PM_SUSPEND_MEM) tsens_reinit(priv); + if (!device_may_wakeup(priv->dev)) + return 0; + + if (priv->feat->combo_int) + disable_irq_wake(priv->combined_irq); + else { + disable_irq_wake(priv->uplow_irq); + if (priv->feat->crit_int) + disable_irq_wake(priv->crit_irq); + } + return 0; } @@ -1276,15 +1357,18 @@ static int tsens_register(struct tsens_priv *priv) if (priv->feat->combo_int) { ret = tsens_register_irq(priv, "combined", - tsens_combined_irq_thread); + tsens_combined_irq_thread, &priv->combined_irq); } else { - ret = tsens_register_irq(priv, "uplow", tsens_irq_thread); + ret = tsens_register_irq(priv, "uplow", tsens_irq_thread, + &priv->uplow_irq); if (ret < 0) return ret; - if (priv->feat->crit_int) + if (priv->feat->crit_int) { ret = tsens_register_irq(priv, "critical", - tsens_critical_irq_thread); + tsens_critical_irq_thread, + &priv->crit_irq); + } } return ret; @@ -1343,6 +1427,8 @@ static int tsens_probe(struct platform_device *pdev) platform_set_drvdata(pdev, priv); + device_init_wakeup(dev, !data->no_irq_wake); + if (!priv->ops || !priv->ops->init || !priv->ops->get_temp) return -EINVAL; diff --git a/drivers/thermal/qcom/tsens.h b/drivers/thermal/qcom/tsens.h index 2a7afa4c899b..e8376accdff3 100644 --- a/drivers/thermal/qcom/tsens.h +++ b/drivers/thermal/qcom/tsens.h @@ -21,6 +21,7 @@ #define THRESHOLD_MIN_ADC_CODE 0x0 #define MAX_SENSORS 16 +#define MAX_READ_RETRY 3 #include <linux/interrupt.h> #include <linux/thermal.h> @@ -531,6 +532,7 @@ struct tsens_features { * @hw_ids: Subset of sensors ids supported by platform, if not the first n * @feat: features of the IP * @fields: bitfield locations + * @no_irq_wake: if set, TSENS interrupts will not be configured as wakeup sources */ struct tsens_plat_data { const u32 num_sensors; @@ -538,6 +540,7 @@ struct tsens_plat_data { unsigned int *hw_ids; struct tsens_features *feat; const struct reg_field *fields; + bool no_irq_wake; }; /** @@ -567,6 +570,9 @@ struct tsens_context { * @ops: pointer to list of callbacks supported by this device * @debug_root: pointer to debugfs dentry for all tsens * @debug: pointer to debugfs dentry for tsens controller + * @uplow_irq: IRQ number for uplow (upper/lower) threshold interrupts + * @crit_irq: IRQ number for critical threshold interrupts + * @combined_irq: IRQ number for combined threshold interrupts * @sensor: list of sensors attached to this device */ struct tsens_priv { @@ -588,6 +594,10 @@ struct tsens_priv { struct dentry *debug_root; struct dentry *debug; + int uplow_irq; + int crit_irq; + int combined_irq; + struct tsens_sensor sensor[] __counted_by(num_sensors); }; @@ -639,8 +649,17 @@ int get_temp_tsens_valid(const struct tsens_sensor *s, int *temp); int get_temp_common(const struct tsens_sensor *s, int *temp); #ifdef CONFIG_SUSPEND int tsens_resume_common(struct tsens_priv *priv); +int tsens_suspend_common(struct tsens_priv *priv); #else -#define tsens_resume_common NULL +static inline int tsens_resume_common(struct tsens_priv *priv) +{ + return 0; +} + +static inline int tsens_suspend_common(struct tsens_priv *priv) +{ + return 0; +} #endif /* TSENS target */ @@ -659,4 +678,7 @@ extern const struct tsens_plat_data data_ipq5018; extern struct tsens_plat_data data_8996, data_ipq8074, data_tsens_v2; extern const struct tsens_plat_data data_ipq5332, data_ipq5424; +/* TSENS automotive targets */ +extern struct tsens_plat_data data_automotive_v2; + #endif /* __QCOM_TSENS_H__ */ diff --git a/drivers/thermal/qoriq_thermal.c b/drivers/thermal/qoriq_thermal.c index 01b58be0dcc6..297724e81593 100644 --- a/drivers/thermal/qoriq_thermal.c +++ b/drivers/thermal/qoriq_thermal.c @@ -1,7 +1,9 @@ // SPDX-License-Identifier: GPL-2.0 // // Copyright 2016 Freescale Semiconductor, Inc. +// Copyright 2025 NXP +#include <linux/bitfield.h> #include <linux/clk.h> #include <linux/err.h> #include <linux/io.h> @@ -24,10 +26,14 @@ #define TMTMIR_DEFAULT 0x0000000f #define TIER_DISABLE 0x0 #define TEUMR0_V2 0x51009c00 +#define TEUMR0_V21 0x55000c00 #define TMSARA_V2 0xe #define TMU_VER1 0x1 #define TMU_VER2 0x2 +/* errata ID info define */ +#define TMU_ERR052243 BIT(0) + #define REGS_TMR 0x000 /* Mode Register */ #define TMR_DISABLE 0x0 #define TMR_ME 0x80000000 @@ -43,6 +49,15 @@ #define REGS_TIER 0x020 /* Interrupt Enable Register */ #define TIER_DISABLE 0x0 +#define REGS_TIDR 0x24 +#define TEMP_RATE_IRQ_MASK GENMASK(25, 24) +#define TMRTRCTR 0x70 +#define TMRTRCTR_EN BIT(31) +#define TMRTRCTR_TEMP_MASK GENMASK(7, 0) +#define TMFTRCTR 0x74 +#define TMFTRCTR_EN BIT(31) +#define TMFTRCTR_TEMP_MASK GENMASK(7, 0) +#define TEMP_RATE_THR_LVL 0x7 #define REGS_TTCFGR 0x080 /* Temperature Configuration Register */ #define REGS_TSCFGR 0x084 /* Sensor Configuration Register */ @@ -73,14 +88,26 @@ struct qoriq_sensor { int id; }; +struct tmu_drvdata { + u32 teumr0; + u32 tmu_errata; +}; + struct qoriq_tmu_data { int ver; u32 ttrcr[NUM_TTRCR_MAX]; struct regmap *regmap; struct clk *clk; struct qoriq_sensor sensor[SITES_MAX]; + const struct tmu_drvdata *drvdata; }; +static inline bool qoriq_tmu_has_errata(const struct tmu_drvdata *drvdata, + u32 flag) +{ + return drvdata->tmu_errata & flag; +} + static struct qoriq_tmu_data *qoriq_sensor_to_data(struct qoriq_sensor *s) { return container_of(s, struct qoriq_tmu_data, sensor[s->id]); @@ -90,7 +117,7 @@ static int tmu_get_temp(struct thermal_zone_device *tz, int *temp) { struct qoriq_sensor *qsensor = thermal_zone_device_priv(tz); struct qoriq_tmu_data *qdata = qoriq_sensor_to_data(qsensor); - u32 val; + u32 val, tidr; /* * REGS_TRITSR(id) has the following layout: * @@ -123,6 +150,15 @@ static int tmu_get_temp(struct thermal_zone_device *tz, int *temp) 10 * USEC_PER_MSEC)) return -ENODATA; + /*ERR052243: If a raising or falling edge happens, try later */ + if (qoriq_tmu_has_errata(qdata->drvdata, TMU_ERR052243)) { + regmap_read(qdata->regmap, REGS_TIDR, &tidr); + if (tidr & TEMP_RATE_IRQ_MASK) { + regmap_write(qdata->regmap, REGS_TIDR, TEMP_RATE_IRQ_MASK); + return -EAGAIN; + } + } + if (qdata->ver == TMU_VER1) { *temp = (val & GENMASK(7, 0)) * MILLIDEGREE_PER_DEGREE; } else { @@ -234,9 +270,18 @@ static void qoriq_tmu_init_device(struct qoriq_tmu_data *data) regmap_write(data->regmap, REGS_TMTMIR, TMTMIR_DEFAULT); } else { regmap_write(data->regmap, REGS_V2_TMTMIR, TMTMIR_DEFAULT); - regmap_write(data->regmap, REGS_V2_TEUMR(0), TEUMR0_V2); + regmap_write(data->regmap, REGS_V2_TEUMR(0), + data->drvdata->teumr0); } + /* ERR052243: Set the raising & falling edge monitor */ + if (qoriq_tmu_has_errata(data->drvdata, TMU_ERR052243)) { + regmap_write(data->regmap, TMRTRCTR, TMRTRCTR_EN | + FIELD_PREP(TMRTRCTR_TEMP_MASK, TEMP_RATE_THR_LVL)); + regmap_write(data->regmap, TMFTRCTR, TMFTRCTR_EN | + FIELD_PREP(TMFTRCTR_TEMP_MASK, TEMP_RATE_THR_LVL)); + + } /* Disable monitoring */ regmap_write(data->regmap, REGS_TMR, TMR_DISABLE); } @@ -319,6 +364,10 @@ static int qoriq_tmu_probe(struct platform_device *pdev) data->ver = (ver >> 8) & 0xff; + data->drvdata = of_device_get_match_data(&pdev->dev); + if (!data->drvdata) + return dev_err_probe(dev, -EINVAL, "Failed to get match data\n"); + qoriq_tmu_init_device(data); /* TMU initialization */ ret = qoriq_tmu_calibration(dev, data); /* TMU calibration */ @@ -366,19 +415,42 @@ static int qoriq_tmu_resume(struct device *dev) if (data->ver > TMU_VER1) { ret = regmap_clear_bits(data->regmap, REGS_TMR, TMR_CMD); if (ret) - return ret; + goto disable_clk; } /* Enable monitoring */ - return regmap_update_bits(data->regmap, REGS_TMR, TMR_ME, TMR_ME); + ret = regmap_update_bits(data->regmap, REGS_TMR, TMR_ME, TMR_ME); + if (ret) + goto disable_clk; + + return 0; + +disable_clk: + clk_disable_unprepare(data->clk); + + return ret; } static DEFINE_SIMPLE_DEV_PM_OPS(qoriq_tmu_pm_ops, qoriq_tmu_suspend, qoriq_tmu_resume); +static const struct tmu_drvdata qoriq_tmu_data = { + .teumr0 = TEUMR0_V2, +}; + +static const struct tmu_drvdata imx8mq_tmu_data = { + .teumr0 = TEUMR0_V2, +}; + +static const struct tmu_drvdata imx93_data = { + .teumr0 = TEUMR0_V21, + .tmu_errata = TMU_ERR052243, +}; + static const struct of_device_id qoriq_tmu_match[] = { - { .compatible = "fsl,qoriq-tmu", }, - { .compatible = "fsl,imx8mq-tmu", }, + { .compatible = "fsl,qoriq-tmu", .data = &qoriq_tmu_data }, + { .compatible = "fsl,imx8mq-tmu", .data = &imx8mq_tmu_data }, + { .compatible = "fsl,imx93-tmu", .data = &imx93_data }, {}, }; MODULE_DEVICE_TABLE(of, qoriq_tmu_match); diff --git a/drivers/thermal/renesas/rcar_thermal.c b/drivers/thermal/renesas/rcar_thermal.c index 6e5dcac5d47a..9a9038671b04 100644 --- a/drivers/thermal/renesas/rcar_thermal.c +++ b/drivers/thermal/renesas/rcar_thermal.c @@ -446,10 +446,8 @@ static int rcar_thermal_probe(struct platform_device *pdev) ret = devm_request_irq(dev, irq, rcar_thermal_irq, IRQF_SHARED, dev_name(dev), common); - if (ret) { - dev_err(dev, "irq request failed\n"); + if (ret) goto error_unregister; - } /* update ENR bits */ if (chip->irq_per_ch) @@ -492,12 +490,6 @@ static int rcar_thermal_probe(struct platform_device *pdev) "rcar_thermal", trips, ARRAY_SIZE(trips), priv, &rcar_thermal_zone_ops, NULL, 0, idle); - - ret = thermal_zone_device_enable(priv->zone); - if (ret) { - thermal_zone_device_unregister(priv->zone); - priv->zone = ERR_PTR(ret); - } } if (IS_ERR(priv->zone)) { dev_err(dev, "can't register thermal zone\n"); @@ -506,11 +498,12 @@ static int rcar_thermal_probe(struct platform_device *pdev) goto error_unregister; } - if (chip->use_of_thermal) { + if (chip->use_of_thermal) ret = thermal_add_hwmon_sysfs(priv->zone); - if (ret) - goto error_unregister; - } + else + ret = thermal_zone_device_enable(priv->zone); + if (ret) + goto error_unregister; rcar_thermal_irq_enable(priv); diff --git a/drivers/thermal/renesas/rzg2l_thermal.c b/drivers/thermal/renesas/rzg2l_thermal.c index b588be628640..d9afd0619167 100644 --- a/drivers/thermal/renesas/rzg2l_thermal.c +++ b/drivers/thermal/renesas/rzg2l_thermal.c @@ -9,7 +9,6 @@ #include <linux/io.h> #include <linux/iopoll.h> #include <linux/math.h> -#include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> diff --git a/drivers/thermal/renesas/rzg3e_thermal.c b/drivers/thermal/renesas/rzg3e_thermal.c index dde021e283b7..c44f5b8858d0 100644 --- a/drivers/thermal/renesas/rzg3e_thermal.c +++ b/drivers/thermal/renesas/rzg3e_thermal.c @@ -93,7 +93,6 @@ struct rzg3e_thermal_info { * @info: chip type specific information * @trmval0: calibration value 0 (b) * @trmval1: calibration value 1 (c) - * @trim_offset: offset for trim registers in syscon * @lock: protects hardware access during conversions */ struct rzg3e_thermal_priv { @@ -462,10 +461,8 @@ static int rzg3e_thermal_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(dev, irq, rzg3e_thermal_irq, rzg3e_thermal_irq_thread, IRQF_ONESHOT, "rzg3e_thermal", priv); - if (ret) { - dev_err(dev, "Failed to request IRQ: %d\n", ret); + if (ret) goto err_pm_put; - } /* Add hwmon sysfs interface */ ret = devm_thermal_add_hwmon_sysfs(dev, priv->zone); diff --git a/drivers/thermal/renesas/rzg3s_thermal.c b/drivers/thermal/renesas/rzg3s_thermal.c index e25e36c99a88..7ced8f76a0ec 100644 --- a/drivers/thermal/renesas/rzg3s_thermal.c +++ b/drivers/thermal/renesas/rzg3s_thermal.c @@ -270,3 +270,4 @@ module_platform_driver(rzg3s_thermal_driver); MODULE_DESCRIPTION("Renesas RZ/G3S Thermal Sensor Unit Driver"); MODULE_AUTHOR("Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_CONSUMER"); diff --git a/drivers/thermal/rockchip_thermal.c b/drivers/thermal/rockchip_thermal.c index c49ddf70f86e..08891608baa6 100644 --- a/drivers/thermal/rockchip_thermal.c +++ b/drivers/thermal/rockchip_thermal.c @@ -1773,8 +1773,7 @@ static int rockchip_thermal_probe(struct platform_device *pdev) IRQF_ONESHOT, "rockchip_thermal", thermal); if (error) - return dev_err_probe(&pdev->dev, error, - "failed to request tsadc irq.\n"); + return error; thermal->chip->control(thermal->regs, true); diff --git a/drivers/thermal/samsung/Kconfig b/drivers/thermal/samsung/Kconfig index f4eff5a41a84..e1e8035d24fb 100644 --- a/drivers/thermal/samsung/Kconfig +++ b/drivers/thermal/samsung/Kconfig @@ -3,6 +3,7 @@ config EXYNOS_THERMAL tristate "Exynos thermal management unit driver" depends on THERMAL_OF depends on HAS_IOMEM + default ARCH_EXYNOS help If you say yes here you get support for the TMU (Thermal Management Unit) driver for Samsung Exynos series of SoCs. This driver initialises diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c index 47a99b3c5395..56717bb50d60 100644 --- a/drivers/thermal/samsung/exynos_tmu.c +++ b/drivers/thermal/samsung/exynos_tmu.c @@ -1102,10 +1102,8 @@ static int exynos_tmu_probe(struct platform_device *pdev) IRQF_TRIGGER_RISING | IRQF_SHARED | IRQF_ONESHOT, dev_name(dev), data); - if (ret) { - dev_err(dev, "Failed to request irq: %d\n", data->irq); + if (ret) goto err_sclk; - } exynos_tmu_control(pdev, true); return 0; diff --git a/drivers/thermal/spacemit/Kconfig b/drivers/thermal/spacemit/Kconfig new file mode 100644 index 000000000000..de7b5ece5af2 --- /dev/null +++ b/drivers/thermal/spacemit/Kconfig @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0-only +menu "SpacemiT thermal drivers" +depends on ARCH_SPACEMIT || COMPILE_TEST + +config SPACEMIT_K1_TSENSOR + tristate "SpacemiT K1 thermal sensor driver" + depends on THERMAL_OF + help + This driver provides support for the thermal sensor + integrated in the SpacemiT K1 SoC. + + The thermal sensor monitors temperatures for five thermal zones: + soc, package, gpu, cluster0, and cluster1. It supports reporting + temperature values and handling high/low threshold interrupts. + + Say Y here if you want to enable thermal monitoring on SpacemiT K1. + If compiled as a module, it will be called k1_tsensor. + +endmenu diff --git a/drivers/thermal/spacemit/Makefile b/drivers/thermal/spacemit/Makefile new file mode 100644 index 000000000000..82b30741e4ec --- /dev/null +++ b/drivers/thermal/spacemit/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_SPACEMIT_K1_TSENSOR) += k1_tsensor.o diff --git a/drivers/thermal/spacemit/k1_tsensor.c b/drivers/thermal/spacemit/k1_tsensor.c new file mode 100644 index 000000000000..ef91453e5538 --- /dev/null +++ b/drivers/thermal/spacemit/k1_tsensor.c @@ -0,0 +1,326 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Thermal sensor driver for SpacemiT K1 SoC + * + * Copyright (C) 2026 Shuwei Wu <shuwei.wu@mailbox.org> + */ +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/reset.h> +#include <linux/slab.h> +#include <linux/thermal.h> + +#include "../thermal_hwmon.h" + +#define K1_TSENSOR_PCTRL_REG 0x00 +#define K1_TSENSOR_PCTRL_ENABLE BIT(0) +#define K1_TSENSOR_PCTRL_TEMP_MODE BIT(3) +#define K1_TSENSOR_PCTRL_RAW_SEL BIT(7) + +#define K1_TSENSOR_PCTRL_CTUNE GENMASK(11, 8) +#define K1_TSENSOR_PCTRL_SW_CTRL GENMASK(21, 18) +#define K1_TSENSOR_PCTRL_HW_AUTO_MODE BIT(23) + +#define K1_TSENSOR_EN_REG 0x08 +#define K1_TSENSOR_EN_ALL GENMASK(MAX_SENSOR_NUMBER - 1, 0) + +#define K1_TSENSOR_TIME_REG 0x0C +#define K1_TSENSOR_TIME_WAIT_REF_CNT GENMASK(3, 0) +#define K1_TSENSOR_TIME_ADC_CNT_RST GENMASK(7, 4) +#define K1_TSENSOR_TIME_FILTER_PERIOD GENMASK(21, 20) +#define K1_TSENSOR_TIME_MASK GENMASK(23, 0) + +#define K1_TSENSOR_INT_CLR_REG 0x10 +#define K1_TSENSOR_INT_EN_REG 0x14 +#define K1_TSENSOR_INT_STA_REG 0x18 + +#define K1_TSENSOR_INT_EN_MASK BIT(0) +#define K1_TSENSOR_INT_MASK(x) (GENMASK(2, 1) << ((x) * 2)) + +#define K1_TSENSOR_DATA_BASE_REG 0x20 +#define K1_TSENSOR_DATA_REG(x) (K1_TSENSOR_DATA_BASE_REG + ((x) / 2) * 4) +#define K1_TSENSOR_DATA_LOW_MASK GENMASK(15, 0) +#define K1_TSENSOR_DATA_HIGH_MASK GENMASK(31, 16) + +#define K1_TSENSOR_THRSH_BASE_REG 0x40 +#define K1_TSENSOR_THRSH_REG(x) (K1_TSENSOR_THRSH_BASE_REG + ((x) * 4)) +#define K1_TSENSOR_THRSH_LOW_MASK GENMASK(15, 0) +#define K1_TSENSOR_THRSH_HIGH_MASK GENMASK(31, 16) + +#define MAX_SENSOR_NUMBER 5 + +/* Hardware offset value required for temperature calculation */ +#define TEMPERATURE_OFFSET 278 + +struct k1_tsensor_channel { + struct k1_tsensor *ts; + struct thermal_zone_device *tzd; + int id; +}; + +struct k1_tsensor { + void __iomem *base; + struct k1_tsensor_channel ch[MAX_SENSOR_NUMBER]; +}; + +static void k1_tsensor_init(struct k1_tsensor *ts) +{ + u32 val; + + /* Disable all the interrupts */ + writel(0xffffffff, ts->base + K1_TSENSOR_INT_EN_REG); + + /* Configure ADC sampling time and filter period */ + val = readl(ts->base + K1_TSENSOR_TIME_REG); + val &= ~K1_TSENSOR_TIME_MASK; + val |= K1_TSENSOR_TIME_FILTER_PERIOD | + K1_TSENSOR_TIME_ADC_CNT_RST | + K1_TSENSOR_TIME_WAIT_REF_CNT; + writel(val, ts->base + K1_TSENSOR_TIME_REG); + + /* + * Enable all sensors' auto mode, enable dither control, + * consecutive mode, and power up sensor. + */ + val = readl(ts->base + K1_TSENSOR_PCTRL_REG); + val &= ~K1_TSENSOR_PCTRL_SW_CTRL; + val &= ~K1_TSENSOR_PCTRL_CTUNE; + val |= K1_TSENSOR_PCTRL_RAW_SEL | + K1_TSENSOR_PCTRL_TEMP_MODE | + K1_TSENSOR_PCTRL_HW_AUTO_MODE | + K1_TSENSOR_PCTRL_ENABLE; + writel(val, ts->base + K1_TSENSOR_PCTRL_REG); + + /* Enable each sensor */ + val = readl(ts->base + K1_TSENSOR_EN_REG); + val |= K1_TSENSOR_EN_ALL; + writel(val, ts->base + K1_TSENSOR_EN_REG); +} + +static void k1_tsensor_enable_irq(struct k1_tsensor_channel *ch) +{ + struct k1_tsensor *ts = ch->ts; + u32 val; + + val = readl(ts->base + K1_TSENSOR_INT_CLR_REG); + val |= K1_TSENSOR_INT_MASK(ch->id); + writel(val, ts->base + K1_TSENSOR_INT_CLR_REG); + + val = readl(ts->base + K1_TSENSOR_INT_EN_REG); + val &= ~K1_TSENSOR_INT_MASK(ch->id); + writel(val, ts->base + K1_TSENSOR_INT_EN_REG); + + /* Enable thermal interrupt */ + val = readl(ts->base + K1_TSENSOR_INT_EN_REG); + val |= K1_TSENSOR_INT_EN_MASK; + writel(val, ts->base + K1_TSENSOR_INT_EN_REG); +} + +/* + * The conversion formula used is: + * T(m°C) = (((raw_value & mask) >> shift) - TEMPERATURE_OFFSET) * 1000 + */ +static int k1_tsensor_get_temp(struct thermal_zone_device *tz, int *temp) +{ + struct k1_tsensor_channel *ch = thermal_zone_device_priv(tz); + struct k1_tsensor *ts = ch->ts; + u32 val; + + val = readl(ts->base + K1_TSENSOR_DATA_REG(ch->id)); + if (ch->id % 2) + *temp = FIELD_GET(K1_TSENSOR_DATA_HIGH_MASK, val); + else + *temp = FIELD_GET(K1_TSENSOR_DATA_LOW_MASK, val); + + *temp -= TEMPERATURE_OFFSET; + *temp *= 1000; + + return 0; +} + +/* + * For each sensor, the hardware threshold register is 32 bits: + * - Lower 16 bits [15:0] configure the low threshold temperature. + * - Upper 16 bits [31:16] configure the high threshold temperature. + */ +static int k1_tsensor_set_trips(struct thermal_zone_device *tz, int low, int high) +{ + struct k1_tsensor_channel *ch = thermal_zone_device_priv(tz); + struct k1_tsensor *ts = ch->ts; + u32 val; + + low = clamp_val(low / 1000 + TEMPERATURE_OFFSET, TEMPERATURE_OFFSET, + FIELD_MAX(K1_TSENSOR_THRSH_LOW_MASK)); + high = clamp_val(high / 1000 + TEMPERATURE_OFFSET, TEMPERATURE_OFFSET, + FIELD_MAX(K1_TSENSOR_THRSH_HIGH_MASK)); + if (low >= high) + return -EINVAL; + + val = readl(ts->base + K1_TSENSOR_THRSH_REG(ch->id)); + + val &= ~(K1_TSENSOR_THRSH_LOW_MASK | K1_TSENSOR_THRSH_HIGH_MASK); + val |= FIELD_PREP(K1_TSENSOR_THRSH_LOW_MASK, low); + val |= FIELD_PREP(K1_TSENSOR_THRSH_HIGH_MASK, high); + + writel(val, ts->base + K1_TSENSOR_THRSH_REG(ch->id)); + + return 0; +} + +static const struct thermal_zone_device_ops k1_tsensor_ops = { + .get_temp = k1_tsensor_get_temp, + .set_trips = k1_tsensor_set_trips, +}; + +static irqreturn_t k1_tsensor_irq_thread(int irq, void *data) +{ + struct k1_tsensor *ts = (struct k1_tsensor *)data; + int mask, status, i; + + status = readl(ts->base + K1_TSENSOR_INT_STA_REG); + + for (i = 0; i < MAX_SENSOR_NUMBER; i++) { + if (status & K1_TSENSOR_INT_MASK(i)) { + mask = readl(ts->base + K1_TSENSOR_INT_CLR_REG); + mask |= K1_TSENSOR_INT_MASK(i); + writel(mask, ts->base + K1_TSENSOR_INT_CLR_REG); + thermal_zone_device_update(ts->ch[i].tzd, THERMAL_EVENT_UNSPECIFIED); + } + } + + return IRQ_HANDLED; +} + +static void k1_tsensor_shutdown(struct k1_tsensor *ts) +{ + u32 val; + + /* Disable all interrupts */ + writel(0xffffffff, ts->base + K1_TSENSOR_INT_EN_REG); + + /* Disable all sensors */ + val = readl(ts->base + K1_TSENSOR_EN_REG); + val &= ~K1_TSENSOR_EN_ALL; + writel(val, ts->base + K1_TSENSOR_EN_REG); + + /* Clear the sampling configuration set by k1_tsensor_init() */ + val = readl(ts->base + K1_TSENSOR_TIME_REG); + val &= ~(K1_TSENSOR_TIME_FILTER_PERIOD | + K1_TSENSOR_TIME_ADC_CNT_RST | + K1_TSENSOR_TIME_WAIT_REF_CNT); + writel(val, ts->base + K1_TSENSOR_TIME_REG); + + /* Clear the control bits configured by k1_tsensor_init() */ + val = readl(ts->base + K1_TSENSOR_PCTRL_REG); + val &= ~(K1_TSENSOR_PCTRL_RAW_SEL | + K1_TSENSOR_PCTRL_TEMP_MODE | + K1_TSENSOR_PCTRL_HW_AUTO_MODE | + K1_TSENSOR_PCTRL_ENABLE); + writel(val, ts->base + K1_TSENSOR_PCTRL_REG); +} + +static void k1_tsensor_shutdown_action(void *data) +{ + k1_tsensor_shutdown(data); +} + +static int k1_tsensor_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct k1_tsensor *ts; + struct reset_control *reset; + struct clk *clk; + int i, irq, ret; + + ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL); + if (!ts) + return -ENOMEM; + + ts->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ts->base)) + return dev_err_probe(dev, PTR_ERR(ts->base), "Failed to get reg\n"); + + reset = devm_reset_control_get_exclusive_deasserted(dev, NULL); + if (IS_ERR(reset)) + return dev_err_probe(dev, PTR_ERR(reset), "Failed to get/deassert reset control\n"); + + clk = devm_clk_get_enabled(dev, "core"); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), "Failed to get core clock\n"); + + clk = devm_clk_get_enabled(dev, "bus"); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), "Failed to get bus clock\n"); + + k1_tsensor_init(ts); + + for (i = 0; i < MAX_SENSOR_NUMBER; ++i) { + ts->ch[i].id = i; + ts->ch[i].ts = ts; + ts->ch[i].tzd = devm_thermal_of_zone_register(dev, i, ts->ch + i, &k1_tsensor_ops); + if (IS_ERR(ts->ch[i].tzd)) { + ret = PTR_ERR(ts->ch[i].tzd); + goto err_shutdown; + } + + /* Attach sysfs hwmon attributes for userspace monitoring */ + ret = devm_thermal_add_hwmon_sysfs(dev, ts->ch[i].tzd); + if (ret) + dev_warn(dev, "Failed to add hwmon sysfs attributes\n"); + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + ret = irq; + goto err_shutdown; + } + + ret = devm_request_threaded_irq(dev, irq, NULL, + k1_tsensor_irq_thread, + IRQF_ONESHOT, "k1_tsensor", ts); + if (ret < 0) + goto err_shutdown; + + ret = devm_add_action_or_reset(dev, k1_tsensor_shutdown_action, ts); + if (ret) + return ret; + + /* Enable interrupts only after all zones and the handler are ready */ + for (i = 0; i < MAX_SENSOR_NUMBER; ++i) + k1_tsensor_enable_irq(ts->ch + i); + + platform_set_drvdata(pdev, ts); + + return 0; + +err_shutdown: + k1_tsensor_shutdown(ts); + return ret; +} + +static const struct of_device_id k1_tsensor_dt_ids[] = { + { .compatible = "spacemit,k1-tsensor" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, k1_tsensor_dt_ids); + +static struct platform_driver k1_tsensor_driver = { + .driver = { + .name = "k1_tsensor", + .of_match_table = k1_tsensor_dt_ids, + }, + .probe = k1_tsensor_probe, +}; +module_platform_driver(k1_tsensor_driver); + +MODULE_DESCRIPTION("SpacemiT K1 Thermal Sensor Driver"); +MODULE_AUTHOR("Shuwei Wu <shuwei.wu@mailbox.org>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/thermal/spear_thermal.c b/drivers/thermal/spear_thermal.c index 603dadcd3df5..5e3e9c1f32f8 100644 --- a/drivers/thermal/spear_thermal.c +++ b/drivers/thermal/spear_thermal.c @@ -93,7 +93,7 @@ static int spear_thermal_probe(struct platform_device *pdev) struct device_node *np = pdev->dev.of_node; int ret = 0, val; - if (!np || !of_property_read_u32(np, "st,thermal-flags", &val)) { + if (!np || of_property_read_u32(np, "st,thermal-flags", &val)) { dev_err(&pdev->dev, "Failed: DT Pdata not passed\n"); return -EINVAL; } diff --git a/drivers/thermal/sprd_thermal.c b/drivers/thermal/sprd_thermal.c index e546067c9621..d683fcb0f8ab 100644 --- a/drivers/thermal/sprd_thermal.c +++ b/drivers/thermal/sprd_thermal.c @@ -178,7 +178,7 @@ static int sprd_thm_sensor_calibration(struct device_node *np, static int sprd_thm_rawdata_to_temp(struct sprd_thermal_sensor *sen, u32 rawdata) { - clamp(rawdata, (u32)SPRD_THM_RAW_DATA_LOW, (u32)SPRD_THM_RAW_DATA_HIGH); + rawdata = clamp(rawdata, SPRD_THM_RAW_DATA_LOW, SPRD_THM_RAW_DATA_HIGH); /* * According to the thermal datasheet, the formula of converting @@ -192,7 +192,7 @@ static int sprd_thm_temp_to_rawdata(int temp, struct sprd_thermal_sensor *sen) { u32 val; - clamp(temp, (int)SPRD_THM_TEMP_LOW, (int)SPRD_THM_TEMP_HIGH); + temp = clamp(temp, SPRD_THM_TEMP_LOW, SPRD_THM_TEMP_HIGH); /* * According to the thermal datasheet, the formula of converting @@ -201,7 +201,7 @@ static int sprd_thm_temp_to_rawdata(int temp, struct sprd_thermal_sensor *sen) */ val = (temp + sen->cal_offset) / sen->cal_slope; - return clamp(val, val, (u32)(SPRD_THM_RAW_DATA_HIGH - 1)); + return min(val, SPRD_THM_RAW_DATA_HIGH - 1); } static int sprd_thm_read_temp(struct thermal_zone_device *tz, int *temp) diff --git a/drivers/thermal/st/st_thermal_memmap.c b/drivers/thermal/st/st_thermal_memmap.c index 8f76e50ea567..e3dbe4df80cb 100644 --- a/drivers/thermal/st/st_thermal_memmap.c +++ b/drivers/thermal/st/st_thermal_memmap.c @@ -101,10 +101,8 @@ static int st_mmap_register_enable_irq(struct st_thermal_sensor *sensor) NULL, st_mmap_thermal_trip_handler, IRQF_TRIGGER_RISING | IRQF_ONESHOT, dev->driver->name, sensor); - if (ret) { - dev_err(dev, "failed to register IRQ %d\n", sensor->irq); + if (ret) return ret; - } return st_mmap_enable_irq(sensor); } diff --git a/drivers/thermal/st/stm_thermal.c b/drivers/thermal/st/stm_thermal.c index 5d8170bfb382..3290da7ab607 100644 --- a/drivers/thermal/st/stm_thermal.c +++ b/drivers/thermal/st/stm_thermal.c @@ -390,11 +390,8 @@ static int stm_register_irq(struct stm_thermal_sensor *sensor) stm_thermal_irq_handler, IRQF_ONESHOT, dev->driver->name, sensor); - if (ret) { - dev_err(dev, "%s: Failed to register IRQ %d\n", __func__, - sensor->irq); + if (ret) return ret; - } dev_dbg(dev, "%s: thermal IRQ registered", __func__); diff --git a/drivers/thermal/sun8i_thermal.c b/drivers/thermal/sun8i_thermal.c index 22674790629a..284684137c43 100644 --- a/drivers/thermal/sun8i_thermal.c +++ b/drivers/thermal/sun8i_thermal.c @@ -149,7 +149,6 @@ static const struct regmap_config config = { .reg_bits = 32, .val_bits = 32, .reg_stride = 4, - .fast_io = true, .max_register = 0xfc, }; diff --git a/drivers/thermal/tegra/soctherm.c b/drivers/thermal/tegra/soctherm.c index 5d26b52beaba..f74acf13b24d 100644 --- a/drivers/thermal/tegra/soctherm.c +++ b/drivers/thermal/tegra/soctherm.c @@ -1499,6 +1499,13 @@ static int soctherm_clk_enable(struct platform_device *pdev, bool enable) return 0; } +static void soctherm_clk_disable(void *data) +{ + struct platform_device *pdev = data; + + soctherm_clk_enable(pdev, false); +} + static int throt_get_cdev_max_state(struct thermal_cooling_device *cdev, unsigned long *max_state) { @@ -1700,9 +1707,9 @@ static void soctherm_init_hw_throt_cdev(struct platform_device *pdev) stc->init = true; } else { - tcd = thermal_of_cooling_device_register(np_stcc, - (char *)name, ts, - &throt_cooling_ops); + tcd = devm_thermal_of_child_cooling_device_register(dev, np_stcc, + (char *)name, ts, + &throt_cooling_ops); if (IS_ERR_OR_NULL(tcd)) { dev_err(dev, "throttle-cfg: %s: failed to register cooling device\n", @@ -2000,10 +2007,8 @@ static int soctherm_interrupts_init(struct platform_device *pdev, IRQF_ONESHOT, dev_name(&pdev->dev), tegra); - if (ret < 0) { - dev_err(&pdev->dev, "request_irq 'thermal_irq' failed.\n"); + if (ret < 0) return ret; - } ret = devm_request_threaded_irq(&pdev->dev, tegra->edp_irq, @@ -2012,10 +2017,8 @@ static int soctherm_interrupts_init(struct platform_device *pdev, IRQF_ONESHOT, "soctherm_edp", tegra); - if (ret < 0) { - dev_err(&pdev->dev, "request_irq 'edp_irq' failed.\n"); + if (ret < 0) return ret; - } return 0; } @@ -2175,6 +2178,10 @@ static int tegra_soctherm_probe(struct platform_device *pdev) if (err) return err; + err = devm_add_action_or_reset(&pdev->dev, soctherm_clk_disable, pdev); + if (err) + return err; + soctherm_thermtrips_parse(pdev); soctherm_init_hw_throt_cdev(pdev); @@ -2184,10 +2191,8 @@ static int tegra_soctherm_probe(struct platform_device *pdev) for (i = 0; i < soc->num_ttgs; ++i) { struct tegra_thermctl_zone *zone = devm_kzalloc(&pdev->dev, sizeof(*zone), GFP_KERNEL); - if (!zone) { - err = -ENOMEM; - goto disable_clocks; - } + if (!zone) + return -ENOMEM; zone->reg = tegra->regs + soc->ttgs[i]->sensor_temp_offset; zone->dev = &pdev->dev; @@ -2201,7 +2206,7 @@ static int tegra_soctherm_probe(struct platform_device *pdev) err = PTR_ERR(z); dev_err(&pdev->dev, "failed to register sensor: %d\n", err); - goto disable_clocks; + return err; } zone->tz = z; @@ -2210,7 +2215,7 @@ static int tegra_soctherm_probe(struct platform_device *pdev) /* Configure hw trip points */ err = tegra_soctherm_set_hwtrips(&pdev->dev, soc->ttgs[i], z); if (err) - goto disable_clocks; + return err; } err = soctherm_interrupts_init(pdev, tegra); @@ -2218,11 +2223,6 @@ static int tegra_soctherm_probe(struct platform_device *pdev) soctherm_debug_init(pdev); return 0; - -disable_clocks: - soctherm_clk_enable(pdev, false); - - return err; } static void tegra_soctherm_remove(struct platform_device *pdev) @@ -2230,8 +2230,6 @@ static void tegra_soctherm_remove(struct platform_device *pdev) struct tegra_soctherm *tegra = platform_get_drvdata(pdev); debugfs_remove_recursive(tegra->debugfs_dir); - - soctherm_clk_enable(pdev, false); } static int __maybe_unused soctherm_suspend(struct device *dev) diff --git a/drivers/thermal/tegra/tegra30-tsensor.c b/drivers/thermal/tegra/tegra30-tsensor.c index 6245f6b97f43..10a5ab1fe1b9 100644 --- a/drivers/thermal/tegra/tegra30-tsensor.c +++ b/drivers/thermal/tegra/tegra30-tsensor.c @@ -602,8 +602,7 @@ static int tegra_tsensor_probe(struct platform_device *pdev) tegra_tsensor_isr, IRQF_ONESHOT, "tegra_tsensor", ts); if (err) - return dev_err_probe(&pdev->dev, err, - "failed to request interrupt\n"); + return err; return 0; } diff --git a/drivers/thermal/testing/command.c b/drivers/thermal/testing/command.c index 1159ecea57e7..90e06c593327 100644 --- a/drivers/thermal/testing/command.c +++ b/drivers/thermal/testing/command.c @@ -86,7 +86,10 @@ #include "thermal_testing.h" -struct dentry *d_testing; +struct workqueue_struct *tt_wq __ro_after_init; + +struct dentry *d_testing __ro_after_init; +static struct dentry *d_command __ro_after_init; #define TT_COMMAND_SIZE 16 @@ -116,18 +119,30 @@ static int tt_command_exec(int index, const char *arg) break; case TT_CMD_DELTZ: + if (!arg || !*arg) + return -EINVAL; + ret = tt_del_tz(arg); break; case TT_CMD_TZADDTRIP: + if (!arg || !*arg) + return -EINVAL; + ret = tt_zone_add_trip(arg); break; case TT_CMD_TZREG: + if (!arg || !*arg) + return -EINVAL; + ret = tt_zone_reg(arg); break; case TT_CMD_TZUNREG: + if (!arg || !*arg) + return -EINVAL; + ret = tt_zone_unreg(arg); break; @@ -191,17 +206,42 @@ static const struct file_operations tt_command_fops = { static int __init thermal_testing_init(void) { + int error; + + tt_wq = alloc_workqueue("thermal_testing", WQ_UNBOUND, 0); + if (!tt_wq) + return -ENOMEM; + d_testing = debugfs_create_dir("thermal-testing", NULL); - if (!IS_ERR(d_testing)) - debugfs_create_file("command", 0200, d_testing, NULL, - &tt_command_fops); + if (IS_ERR(d_testing)) { + error = PTR_ERR(d_testing); + goto destroy_wq; + } + + d_command = debugfs_create_file("command", 0200, d_testing, NULL, &tt_command_fops); + if (IS_ERR(d_command)) { + error = PTR_ERR(d_command); + goto remove_d_testing; + } return 0; + +remove_d_testing: + debugfs_remove(d_testing); +destroy_wq: + destroy_workqueue(tt_wq); + return error; } module_init(thermal_testing_init); static void __exit thermal_testing_exit(void) { + /* First, prevent new commands from being entered. */ + debugfs_remove(d_command); + /* Flush commands in progress (if any). */ + flush_workqueue(tt_wq); + destroy_workqueue(tt_wq); + /* Remove the directory structure and clean up. */ debugfs_remove(d_testing); tt_zone_cleanup(); } diff --git a/drivers/thermal/testing/thermal_testing.h b/drivers/thermal/testing/thermal_testing.h index c790a32aae4e..5880c9a63dba 100644 --- a/drivers/thermal/testing/thermal_testing.h +++ b/drivers/thermal/testing/thermal_testing.h @@ -1,4 +1,12 @@ /* SPDX-License-Identifier: GPL-2.0 */ +#include <linux/workqueue.h> + +extern struct workqueue_struct *tt_wq; + +static inline void tt_queue_work(struct work_struct *work) +{ + queue_work(tt_wq, work); +} extern struct dentry *d_testing; diff --git a/drivers/thermal/testing/zone.c b/drivers/thermal/testing/zone.c index 3c339242f52d..6db26276dd41 100644 --- a/drivers/thermal/testing/zone.c +++ b/drivers/thermal/testing/zone.c @@ -13,7 +13,6 @@ #include <linux/idr.h> #include <linux/list.h> #include <linux/thermal.h> -#include <linux/workqueue.h> #include "thermal_testing.h" @@ -207,7 +206,7 @@ int tt_add_tz(void) INIT_WORK(&tt_work->work, tt_add_tz_work_fn); tt_work->tt_zone = no_free_ptr(tt_zone); - schedule_work(&(no_free_ptr(tt_work)->work)); + tt_queue_work(&(no_free_ptr(tt_work)->work)); return 0; } @@ -239,9 +238,9 @@ int tt_del_tz(const char *arg) int ret; int id; - ret = sscanf(arg, "%d", &id); - if (ret != 1) - return -EINVAL; + ret = kstrtoint(arg, 10, &id); + if (ret < 0) + return ret; struct tt_work *tt_work __free(kfree) = kzalloc_obj(*tt_work); if (!tt_work) @@ -269,7 +268,7 @@ int tt_del_tz(const char *arg) INIT_WORK(&tt_work->work, tt_del_tz_work_fn); tt_work->tt_zone = tt_zone; - schedule_work(&(no_free_ptr(tt_work)->work)); + tt_queue_work(&(no_free_ptr(tt_work)->work)); return 0; } @@ -279,9 +278,9 @@ static struct tt_thermal_zone *tt_get_tt_zone(const char *arg) struct tt_thermal_zone *tt_zone; int ret, id; - ret = sscanf(arg, "%d", &id); - if (ret != 1) - return ERR_PTR(-EINVAL); + ret = kstrtoint(arg, 10, &id); + if (ret < 0) + return ERR_PTR(ret); guard(mutex)(&tt_thermal_zones_lock); @@ -358,7 +357,7 @@ int tt_zone_add_trip(const char *arg) INIT_WORK(&tt_work->work, tt_zone_add_trip_work_fn); tt_work->tt_zone = no_free_ptr(tt_zone); tt_work->tt_trip = no_free_ptr(tt_trip); - schedule_work(&(no_free_ptr(tt_work)->work)); + tt_queue_work(&(no_free_ptr(tt_work)->work)); return 0; } diff --git a/drivers/thermal/thermal-fw.c b/drivers/thermal/thermal-fw.c new file mode 100644 index 000000000000..47c557e3244d --- /dev/null +++ b/drivers/thermal/thermal-fw.c @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * of-thermal-testing.c - Generic Thermal Management device tree testing support + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ +#include <linux/platform_device.h> +#include <linux/fwnode.h> +#include <linux/module.h> + + + diff --git a/drivers/thermal/thermal-generic-adc.c b/drivers/thermal/thermal-generic-adc.c index 7c844589b153..cfdb8e674dd2 100644 --- a/drivers/thermal/thermal-generic-adc.c +++ b/drivers/thermal/thermal-generic-adc.c @@ -228,3 +228,4 @@ module_platform_driver(gadc_thermal_driver); MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>"); MODULE_DESCRIPTION("Generic ADC thermal driver using IIO framework with DT"); MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_CONSUMER"); diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index b7d706ed7ed9..82e2f0d8a26d 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -41,6 +41,8 @@ static struct thermal_governor *def_governor; static bool thermal_pm_suspended; +static struct workqueue_struct *thermal_wq __ro_after_init; + /* * Governor section: set of functions to handle thermal governors * @@ -114,78 +116,24 @@ static int thermal_set_governor(struct thermal_zone_device *tz, return ret; } -int thermal_register_governor(struct thermal_governor *governor) +static int __init thermal_register_governor(struct thermal_governor *governor) { - int err; - const char *name; - struct thermal_zone_device *pos; if (!governor) return -EINVAL; - guard(mutex)(&thermal_governor_lock); - - err = -EBUSY; - if (!__find_governor(governor->name)) { - bool match_default; - - err = 0; - list_add(&governor->governor_list, &thermal_governor_list); - match_default = !strncmp(governor->name, - DEFAULT_THERMAL_GOVERNOR, - THERMAL_NAME_LENGTH); - - if (!def_governor && match_default) - def_governor = governor; - } - - guard(mutex)(&thermal_list_lock); - - list_for_each_entry(pos, &thermal_tz_list, node) { - /* - * only thermal zones with specified tz->tzp->governor_name - * may run with tz->govenor unset - */ - if (pos->governor) - continue; - - name = pos->tzp->governor_name; + if (__find_governor(governor->name)) + return -EBUSY; - if (!strncasecmp(name, governor->name, THERMAL_NAME_LENGTH)) { - int ret; + list_add(&governor->governor_list, &thermal_governor_list); - ret = thermal_set_governor(pos, governor); - if (ret) - dev_err(&pos->device, - "Failed to set governor %s for thermal zone %s: %d\n", - governor->name, pos->type, ret); - } - } - - return err; -} - -void thermal_unregister_governor(struct thermal_governor *governor) -{ - struct thermal_zone_device *pos; - - if (!governor) - return; - - guard(mutex)(&thermal_governor_lock); - - if (!__find_governor(governor->name)) - return; - - list_del(&governor->governor_list); + if (strncmp(governor->name, DEFAULT_THERMAL_GOVERNOR, THERMAL_NAME_LENGTH)) + return 0; - guard(mutex)(&thermal_list_lock); + if (!def_governor) + def_governor = governor; - list_for_each_entry(pos, &thermal_tz_list, node) { - if (!strncasecmp(pos->governor->name, governor->name, - THERMAL_NAME_LENGTH)) - thermal_set_governor(pos, NULL); - } + return 0; } int thermal_zone_device_set_policy(struct thermal_zone_device *tz, @@ -223,40 +171,34 @@ int thermal_build_list_of_policies(char *buf) static void __init thermal_unregister_governors(void) { - struct thermal_governor **governor; + struct thermal_governor *gov, *pos; - for_each_governor_table(governor) - thermal_unregister_governor(*governor); + guard(mutex)(&thermal_governor_lock); + + list_for_each_entry_safe(gov, pos, &thermal_governor_list, governor_list) + list_del(&gov->governor_list); } static int __init thermal_register_governors(void) { - int ret = 0; struct thermal_governor **governor; + guard(mutex)(&thermal_governor_lock); + for_each_governor_table(governor) { + int ret; + ret = thermal_register_governor(*governor); if (ret) { pr_err("Failed to register governor: '%s'", (*governor)->name); - break; + return ret; } - pr_info("Registered thermal governor '%s'", - (*governor)->name); - } - - if (ret) { - struct thermal_governor **gov; - - for_each_governor_table(gov) { - if (gov == governor) - break; - thermal_unregister_governor(*gov); - } + pr_info("Registered thermal governor '%s'", (*governor)->name); } - return ret; + return 0; } static int __thermal_zone_device_set_mode(struct thermal_zone_device *tz, @@ -313,7 +255,7 @@ static void thermal_zone_device_set_polling(struct thermal_zone_device *tz, if (delay > HZ) delay = round_jiffies_relative(delay); - mod_delayed_work(system_freezable_power_efficient_wq, &tz->poll_queue, delay); + mod_delayed_work(thermal_wq, &tz->poll_queue, delay); } static void thermal_zone_recheck(struct thermal_zone_device *tz, int error) @@ -693,18 +635,12 @@ int thermal_zone_device_disable(struct thermal_zone_device *tz) } EXPORT_SYMBOL_GPL(thermal_zone_device_disable); -static bool thermal_zone_is_present(struct thermal_zone_device *tz) -{ - return !list_empty(&tz->node); -} - void thermal_zone_device_update(struct thermal_zone_device *tz, enum thermal_notify_event event) { guard(thermal_zone)(tz); - if (thermal_zone_is_present(tz)) - __thermal_zone_device_update(tz, event); + __thermal_zone_device_update(tz, event); } EXPORT_SYMBOL_GPL(thermal_zone_device_update); @@ -861,7 +797,7 @@ static int thermal_bind_cdev_to_trip(struct thermal_zone_device *tz, goto free_mem; dev->id = result; - sprintf(dev->name, "cdev%d", dev->id); + snprintf(dev->name, sizeof(dev->name), "cdev%d", dev->id); result = sysfs_create_link(&tz->device.kobj, &cdev->device.kobj, dev->name); if (result) @@ -953,28 +889,10 @@ unbind: kfree(pos); } -static void thermal_release(struct device *dev) -{ - struct thermal_zone_device *tz; - struct thermal_cooling_device *cdev; - - if (!strncmp(dev_name(dev), "thermal_zone", - sizeof("thermal_zone") - 1)) { - tz = to_thermal_zone(dev); - thermal_zone_destroy_device_groups(tz); - mutex_destroy(&tz->lock); - complete(&tz->removal); - } else if (!strncmp(dev_name(dev), "cooling_device", - sizeof("cooling_device") - 1)) { - cdev = to_cooling_device(dev); - thermal_cooling_device_destroy_sysfs(cdev); - kfree_const(cdev->type); - ida_free(&thermal_cdev_ida, cdev->id); - kfree(cdev); - } -} - -static struct class *thermal_class; +static const struct class thermal_class = { + .name = "thermal", +}; +static bool thermal_class_unavailable __ro_after_init = true; static inline void print_bind_err_msg(struct thermal_zone_device *tz, @@ -1038,47 +956,39 @@ static void thermal_cooling_device_init_complete(struct thermal_cooling_device * thermal_zone_cdev_bind(tz, cdev); } -/** - * __thermal_cooling_device_register() - register a new thermal cooling device - * @np: a pointer to a device tree node. - * @type: the thermal cooling device type. - * @devdata: device private data. - * @ops: standard thermal cooling devices callbacks. - * - * This interface function adds a new thermal cooling device (fan/processor/...) - * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself - * to all the thermal zone devices registered at the same time. - * It also gives the opportunity to link the cooling device to a device tree - * node, so that it can be bound to a thermal zone created out of device tree. - * - * Return: a pointer to the created struct thermal_cooling_device or an - * ERR_PTR. Caller must check return value with IS_ERR*() helpers. - */ -static struct thermal_cooling_device * -__thermal_cooling_device_register(struct device_node *np, - const char *type, void *devdata, - const struct thermal_cooling_device_ops *ops) +static void thermal_cdev_release(struct device *dev) +{ + struct thermal_cooling_device *cdev = to_cooling_device(dev); + + thermal_cooling_device_destroy_sysfs(cdev); + kfree_const(cdev->type); + ida_free(&thermal_cdev_ida, cdev->id); + kfree(cdev); +} + +struct thermal_cooling_device * +thermal_cooling_device_alloc(const char *type, const struct thermal_cooling_device_ops *ops) { struct thermal_cooling_device *cdev; - unsigned long current_state; - int id, ret; + int ret; if (!ops || !ops->get_max_state || !ops->get_cur_state || !ops->set_cur_state) return ERR_PTR(-EINVAL); - if (!thermal_class) + if (thermal_class_unavailable) return ERR_PTR(-ENODEV); cdev = kzalloc_obj(*cdev); if (!cdev) return ERR_PTR(-ENOMEM); + cdev->ops = ops; + ret = ida_alloc(&thermal_cdev_ida, GFP_KERNEL); if (ret < 0) goto out_kfree_cdev; cdev->id = ret; - id = ret; cdev->type = kstrdup_const(type ? type : "", GFP_KERNEL); if (!cdev->type) { @@ -1086,17 +996,35 @@ __thermal_cooling_device_register(struct device_node *np, goto out_ida_remove; } + return cdev; + +out_ida_remove: + ida_free(&thermal_cdev_ida, cdev->id); +out_kfree_cdev: + kfree(cdev); + return ERR_PTR(ret); +} + +int thermal_cooling_device_add(struct thermal_cooling_device *cdev, void *devdata) +{ + unsigned long current_state; + int ret; + mutex_init(&cdev->lock); INIT_LIST_HEAD(&cdev->thermal_instances); - cdev->np = np; - cdev->ops = ops; cdev->updated = false; - cdev->device.class = thermal_class; + cdev->device.class = &thermal_class; + cdev->device.release = thermal_cdev_release; + device_initialize(&cdev->device); cdev->devdata = devdata; + ret = dev_set_name(&cdev->device, "cooling_device%d", cdev->id); + if (ret) + goto out_put_device; + ret = cdev->ops->get_max_state(cdev, &cdev->max_state); if (ret) - goto out_cdev_type; + goto out_put_device; /* * The cooling device's current state is only needed for debug @@ -1112,40 +1040,32 @@ __thermal_cooling_device_register(struct device_node *np, thermal_cooling_device_setup_sysfs(cdev); - ret = dev_set_name(&cdev->device, "cooling_device%d", cdev->id); + ret = device_add(&cdev->device); if (ret) - goto out_cooling_dev; - - ret = device_register(&cdev->device); - if (ret) { - /* thermal_release() handles rest of the cleanup */ - put_device(&cdev->device); - return ERR_PTR(ret); - } + goto out_put_device; if (current_state <= cdev->max_state) thermal_debug_cdev_add(cdev, current_state); thermal_cooling_device_init_complete(cdev); - return cdev; + return 0; -out_cooling_dev: - thermal_cooling_device_destroy_sysfs(cdev); -out_cdev_type: - kfree_const(cdev->type); -out_ida_remove: - ida_free(&thermal_cdev_ida, id); -out_kfree_cdev: - kfree(cdev); - return ERR_PTR(ret); +out_put_device: + /* + * The device core will release the memory via + * thermal_release() after put_device() is called in the error + * path + */ + put_device(&cdev->device); + return ret; } /** * thermal_cooling_device_register() - register a new thermal cooling device * @type: the thermal cooling device type. * @devdata: device private data. - * @ops: standard thermal cooling devices callbacks. + * @ops: standard thermal cooling devices callbacks. * * This interface function adds a new thermal cooling device (fan/processor/...) * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself @@ -1158,82 +1078,62 @@ struct thermal_cooling_device * thermal_cooling_device_register(const char *type, void *devdata, const struct thermal_cooling_device_ops *ops) { - return __thermal_cooling_device_register(NULL, type, devdata, ops); + struct thermal_cooling_device *cdev; + int ret; + + cdev = thermal_cooling_device_alloc(type, ops); + if (IS_ERR(cdev)) + return cdev; + + ret = thermal_cooling_device_add(cdev, devdata); + if (ret) + return ERR_PTR(ret); + + return cdev; } EXPORT_SYMBOL_GPL(thermal_cooling_device_register); -/** - * thermal_of_cooling_device_register() - register an OF thermal cooling device - * @np: a pointer to a device tree node. - * @type: the thermal cooling device type. - * @devdata: device private data. - * @ops: standard thermal cooling devices callbacks. - * - * This function will register a cooling device with device tree node reference. - * This interface function adds a new thermal cooling device (fan/processor/...) - * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself - * to all the thermal zone devices registered at the same time. - * - * Return: a pointer to the created struct thermal_cooling_device or an - * ERR_PTR. Caller must check return value with IS_ERR*() helpers. - */ -struct thermal_cooling_device * -thermal_of_cooling_device_register(struct device_node *np, - const char *type, void *devdata, - const struct thermal_cooling_device_ops *ops) +static void thermal_cooling_device_release(void *data) { - return __thermal_cooling_device_register(np, type, devdata, ops); -} -EXPORT_SYMBOL_GPL(thermal_of_cooling_device_register); + struct thermal_cooling_device *cdev = data; -static void thermal_cooling_device_release(struct device *dev, void *res) -{ - thermal_cooling_device_unregister( - *(struct thermal_cooling_device **)res); + thermal_cooling_device_unregister(cdev); } /** - * devm_thermal_of_cooling_device_register() - register an OF thermal cooling - * device + * devm_thermal_cooling_device_register() - register a thermal cooling device + * * @dev: a valid struct device pointer of a sensor device. - * @np: a pointer to a device tree node. * @type: the thermal cooling device type. * @devdata: device private data. * @ops: standard thermal cooling devices callbacks. * - * This function will register a cooling device with device tree node reference. - * This interface function adds a new thermal cooling device (fan/processor/...) - * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself - * to all the thermal zone devices registered at the same time. + * This function will register a cooling device. This interface + * function adds a new thermal cooling device (fan/processor/...) to + * /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind + * itself to all the thermal zone devices registered at the same time. * * Return: a pointer to the created struct thermal_cooling_device or an * ERR_PTR. Caller must check return value with IS_ERR*() helpers. */ struct thermal_cooling_device * -devm_thermal_of_cooling_device_register(struct device *dev, - struct device_node *np, - const char *type, void *devdata, - const struct thermal_cooling_device_ops *ops) +devm_thermal_cooling_device_register(struct device *dev, const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops) { - struct thermal_cooling_device **ptr, *tcd; - - ptr = devres_alloc(thermal_cooling_device_release, sizeof(*ptr), - GFP_KERNEL); - if (!ptr) - return ERR_PTR(-ENOMEM); + struct thermal_cooling_device *cdev; + int ret; - tcd = __thermal_cooling_device_register(np, type, devdata, ops); - if (IS_ERR(tcd)) { - devres_free(ptr); - return tcd; - } + cdev = thermal_cooling_device_register(type, devdata, ops); + if (IS_ERR(cdev)) + return cdev; - *ptr = tcd; - devres_add(dev, ptr); + ret = devm_add_action_or_reset(dev, thermal_cooling_device_release, cdev); + if (ret) + return ERR_PTR(ret); - return tcd; + return cdev; } -EXPORT_SYMBOL_GPL(devm_thermal_of_cooling_device_register); +EXPORT_SYMBOL_GPL(devm_thermal_cooling_device_register); static bool thermal_cooling_device_present(struct thermal_cooling_device *cdev) { @@ -1469,6 +1369,17 @@ static void thermal_zone_init_complete(struct thermal_zone_device *tz) __thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED); } +static void thermal_zone_device_release(struct device *dev) +{ + struct thermal_zone_device *tz = to_thermal_zone(dev); + + thermal_zone_destroy_device_groups(tz); + thermal_set_governor(tz, NULL); + ida_destroy(&tz->ida); + mutex_destroy(&tz->lock); + complete(&tz->removal); +} + /** * thermal_zone_device_register_with_trips() - register a new thermal zone device * @type: the thermal zone device type @@ -1539,7 +1450,7 @@ thermal_zone_device_register_with_trips(const char *type, if (polling_delay && passive_delay > polling_delay) return ERR_PTR(-EINVAL); - if (!thermal_class) + if (thermal_class_unavailable) return ERR_PTR(-ENODEV); tz = kzalloc_flex(*tz, trips, num_trips); @@ -1575,7 +1486,8 @@ thermal_zone_device_register_with_trips(const char *type, if (!tz->ops.critical) tz->ops.critical = thermal_zone_device_critical; - tz->device.class = thermal_class; + tz->device.class = &thermal_class; + tz->device.release = thermal_zone_device_release; tz->devdata = devdata; tz->num_trips = num_trips; for_each_trip_desc(tz, td) { @@ -1609,8 +1521,10 @@ thermal_zone_device_register_with_trips(const char *type, /* sys I/F */ /* Add nodes that are always present via .groups */ result = thermal_zone_create_device_groups(tz); - if (result) + if (result) { + thermal_set_governor(tz, NULL); goto remove_id; + } result = device_register(&tz->device); if (result) @@ -1640,6 +1554,7 @@ unregister: device_del(&tz->device); release_device: put_device(&tz->device); + wait_for_completion(&tz->removal); remove_id: ida_free(&thermal_tz_ida, id); free_tzp: @@ -1722,12 +1637,8 @@ void thermal_zone_device_unregister(struct thermal_zone_device *tz) cancel_delayed_work_sync(&tz->poll_queue); - thermal_set_governor(tz, NULL); - thermal_thresholds_exit(tz); thermal_remove_hwmon_sysfs(tz); - ida_free(&thermal_tz_ida, tz->id); - ida_destroy(&tz->ida); device_del(&tz->device); put_device(&tz->device); @@ -1735,6 +1646,9 @@ void thermal_zone_device_unregister(struct thermal_zone_device *tz) thermal_notify_tz_delete(tz); wait_for_completion(&tz->removal); + + ida_free(&thermal_tz_ida, tz->id); + kfree(tz->tzp); kfree(tz); } @@ -1785,6 +1699,10 @@ static void thermal_zone_device_resume(struct work_struct *work) guard(thermal_zone)(tz); + /* If the thermal zone is going away, there's nothing to do. */ + if (tz->state & TZ_STATE_FLAG_EXIT) + return; + tz->state &= ~(TZ_STATE_FLAG_SUSPENDED | TZ_STATE_FLAG_RESUMING); thermal_debug_tz_resume(tz); @@ -1811,9 +1729,12 @@ static void thermal_zone_pm_prepare(struct thermal_zone_device *tz) } tz->state |= TZ_STATE_FLAG_SUSPENDED; + + /* Prevent new work from getting to the workqueue subsequently. */ + cancel_delayed_work(&tz->poll_queue); } -static void thermal_pm_notify_prepare(void) +static void __thermal_pm_prepare(void) { struct thermal_zone_device *tz; @@ -1825,12 +1746,23 @@ static void thermal_pm_notify_prepare(void) thermal_zone_pm_prepare(tz); } +void thermal_pm_prepare(void) +{ + if (thermal_class_unavailable) + return; + + __thermal_pm_prepare(); + /* + * Allow any leftover thermal work items already on the worqueue to + * complete so they don't get in the way later. + */ + flush_workqueue(thermal_wq); +} + static void thermal_zone_pm_complete(struct thermal_zone_device *tz) { guard(thermal_zone)(tz); - cancel_delayed_work(&tz->poll_queue); - reinit_completion(&tz->resume); tz->state |= TZ_STATE_FLAG_RESUMING; @@ -1840,13 +1772,16 @@ static void thermal_zone_pm_complete(struct thermal_zone_device *tz) */ INIT_DELAYED_WORK(&tz->poll_queue, thermal_zone_device_resume); /* Queue up the work without a delay. */ - mod_delayed_work(system_freezable_power_efficient_wq, &tz->poll_queue, 0); + mod_delayed_work(thermal_wq, &tz->poll_queue, 0); } -static void thermal_pm_notify_complete(void) +void thermal_pm_complete(void) { struct thermal_zone_device *tz; + if (thermal_class_unavailable) + return; + guard(mutex)(&thermal_list_lock); thermal_pm_suspended = false; @@ -1855,36 +1790,6 @@ static void thermal_pm_notify_complete(void) thermal_zone_pm_complete(tz); } -static int thermal_pm_notify(struct notifier_block *nb, - unsigned long mode, void *_unused) -{ - switch (mode) { - case PM_HIBERNATION_PREPARE: - case PM_RESTORE_PREPARE: - case PM_SUSPEND_PREPARE: - thermal_pm_notify_prepare(); - break; - case PM_POST_HIBERNATION: - case PM_POST_RESTORE: - case PM_POST_SUSPEND: - thermal_pm_notify_complete(); - break; - default: - break; - } - return 0; -} - -static struct notifier_block thermal_pm_nb = { - .notifier_call = thermal_pm_notify, - /* - * Run at the lowest priority to avoid interference between the thermal - * zone resume work items spawned by thermal_pm_notify() and the other - * PM notifiers. - */ - .priority = INT_MIN, -}; - static int __init thermal_init(void) { int result; @@ -1895,35 +1800,27 @@ static int __init thermal_init(void) if (result) goto error; - result = thermal_register_governors(); - if (result) - goto unregister_netlink; - - thermal_class = kzalloc_obj(*thermal_class); - if (!thermal_class) { + thermal_wq = alloc_workqueue("thermal_events", WQ_UNBOUND, 0); + if (!thermal_wq) { result = -ENOMEM; - goto unregister_governors; + goto unregister_netlink; } - thermal_class->name = "thermal"; - thermal_class->dev_release = thermal_release; - - result = class_register(thermal_class); - if (result) { - kfree(thermal_class); - thermal_class = NULL; + result = thermal_register_governors(); + if (result) goto unregister_governors; - } - result = register_pm_notifier(&thermal_pm_nb); + result = class_register(&thermal_class); if (result) - pr_warn("Thermal: Can not register suspend notifier, return %d\n", - result); + goto unregister_governors; + + thermal_class_unavailable = false; return 0; unregister_governors: thermal_unregister_governors(); + destroy_workqueue(thermal_wq); unregister_netlink: thermal_netlink_exit(); error: diff --git a/drivers/thermal/thermal_core.h b/drivers/thermal/thermal_core.h index d3acff602f9c..e98b0aa5aacc 100644 --- a/drivers/thermal/thermal_core.h +++ b/drivers/thermal/thermal_core.h @@ -258,8 +258,6 @@ struct thermal_instance { #define to_cooling_device(_dev) \ container_of(_dev, struct thermal_cooling_device, device) -int thermal_register_governor(struct thermal_governor *); -void thermal_unregister_governor(struct thermal_governor *); int thermal_zone_device_set_policy(struct thermal_zone_device *, char *); int thermal_build_list_of_policies(char *buf); void __thermal_zone_device_update(struct thermal_zone_device *tz, @@ -269,6 +267,11 @@ void thermal_zone_device_critical_shutdown(struct thermal_zone_device *tz); void thermal_governor_update_tz(struct thermal_zone_device *tz, enum thermal_notify_event reason); +struct thermal_cooling_device * +thermal_cooling_device_alloc(const char *type, const struct thermal_cooling_device_ops *ops); + +int thermal_cooling_device_add(struct thermal_cooling_device *cdev, void *devdata); + /* Helpers */ #define for_each_trip_desc(__tz, __td) \ for (__td = __tz->trips; __td - __tz->trips < __tz->num_trips; __td++) diff --git a/drivers/thermal/thermal_fw.c b/drivers/thermal/thermal_fw.c new file mode 100644 index 000000000000..e8bd2049cf74 --- /dev/null +++ b/drivers/thermal/thermal_fw.c @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * thermal-fw.c - Thermal components creation from firmware description + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ +#include <linux/fwnode.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/thermal.h> + +/** + * thermal_fwnode_cooling_device_register() - register an thermal cooling device + * @np: a pointer to a device tree node. + * @of_index: a cooling device index in the cooling controller + * @type: the thermal cooling device type. + * @devdata: device private data. + * @ops: standard thermal cooling devices callbacks. + * + * This function will register a cooling device with device tree node reference. + * This interface function adds a new thermal cooling device (fan/processor/...) + * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself + * to all the thermal zone devices registered at the same time. + * + * Return: a pointer to the created struct thermal_cooling_device or an + * ERR_PTR. Caller must check return value with IS_ERR*() helpers. + */ +struct thermal_cooling_device * +thermal_fwnode_cooling_device_register(struct fwnode_handle *fwnode, int fwn_index, + const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops) +{ + struct thermal_cooling_device *cdev; + + cdev = __thermal_cooling_device_register(type, devdata, ops); + if (IS_ERR(cdev)) + return cdev; + + cdev->np = (struct device_node *)fwnode; + cdev->of_index = fwn_index; + thermal_cooling_device_init_complete(cdev); + + return cdev; +} +EXPORT_SYMBOL_GPL(thermal_fwnode_cooling_device_register); + +static struct thermal_cooling_device * +__devm_thermal_fwnode_cooling_device_register(struct device *dev, struct fwnode_handle *fwnode, + int fwn_index, const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops) +{ + struct thermal_cooling_device **ptr, *tcd; + + ptr = devres_alloc(thermal_cooling_device_release, sizeof(*ptr), + GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + tcd = thermal_fwnode_cooling_device_register(fwnode, fwn_index, type, devdata, ops); + if (IS_ERR(tcd)) { + devres_free(ptr); + return tcd; + } + + *ptr = tcd; + devres_add(dev, ptr); + + return tcd; +} + +/** + * devm_thermal_fwnode_cooling_device_register() - register a thermal cooling device + * @dev: a valid struct device pointer of a sensor device. + * @fw_index: a cooling device index in the cooling controller + * @type: the thermal cooling device type. + * @devdata: device private data. + * @ops: standard thermal cooling devices callbacks. + * + * This function will register a cooling device with a firmware node reference. + * This interface function adds a new thermal cooling device (fan/processor/...) + * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself + * to all the thermal zone devices registered at the same time. + * + * Return: a pointer to the created struct thermal_cooling_device or an + * ERR_PTR. Caller must check return value with IS_ERR*() helpers. + */ +struct thermal_cooling_device * +devm_thermal_fwnode_cooling_device_register(struct device *dev, int fwn_index, + const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops) +{ + return __devm_thermal_fwnode_cooling_device_register(dev, dev_fwnode(dev), fwn_index, + type, devdata, ops); +} +EXPORT_SYMBOL_GPL(devm_thermal_fwnode_cooling_device_register); + + diff --git a/drivers/thermal/thermal_hwmon.c b/drivers/thermal/thermal_hwmon.c index b624892bc6d6..45ce21914df8 100644 --- a/drivers/thermal/thermal_hwmon.c +++ b/drivers/thermal/thermal_hwmon.c @@ -40,6 +40,7 @@ struct thermal_hwmon_temp { struct thermal_zone_device *tz; struct thermal_hwmon_attr temp_input; /* hwmon sys attr */ struct thermal_hwmon_attr temp_crit; /* hwmon sys attr */ + bool temp_crit_present; }; static LIST_HEAD(thermal_hwmon_list); @@ -94,34 +95,12 @@ thermal_hwmon_lookup_by_type(const struct thermal_zone_device *tz) struct thermal_hwmon_device *hwmon; char type[THERMAL_NAME_LENGTH]; - mutex_lock(&thermal_hwmon_list_lock); list_for_each_entry(hwmon, &thermal_hwmon_list, node) { strscpy(type, tz->type); strreplace(type, '-', '_'); - if (!strcmp(hwmon->type, type)) { - mutex_unlock(&thermal_hwmon_list_lock); + if (!strcmp(hwmon->type, type)) return hwmon; - } } - mutex_unlock(&thermal_hwmon_list_lock); - - return NULL; -} - -/* Find the temperature input matching a given thermal zone */ -static struct thermal_hwmon_temp * -thermal_hwmon_lookup_temp(const struct thermal_hwmon_device *hwmon, - const struct thermal_zone_device *tz) -{ - struct thermal_hwmon_temp *temp; - - mutex_lock(&thermal_hwmon_list_lock); - list_for_each_entry(temp, &hwmon->tz_list, hwmon_node) - if (temp->tz == tz) { - mutex_unlock(&thermal_hwmon_list_lock); - return temp; - } - mutex_unlock(&thermal_hwmon_list_lock); return NULL; } @@ -137,7 +116,9 @@ int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) struct thermal_hwmon_device *hwmon; struct thermal_hwmon_temp *temp; int new_hwmon_device = 1; - int result; + int result = 0; + + mutex_lock(&thermal_hwmon_list_lock); hwmon = thermal_hwmon_lookup_by_type(tz); if (hwmon) { @@ -146,8 +127,10 @@ int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) } hwmon = kzalloc_obj(*hwmon); - if (!hwmon) - return -ENOMEM; + if (!hwmon) { + result = -ENOMEM; + goto unlock; + } INIT_LIST_HEAD(&hwmon->tz_list); strscpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH); @@ -191,25 +174,28 @@ int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) &temp->temp_crit.attr); if (result) goto unregister_input; + + temp->temp_crit_present = true; } - mutex_lock(&thermal_hwmon_list_lock); if (new_hwmon_device) list_add_tail(&hwmon->node, &thermal_hwmon_list); list_add_tail(&temp->hwmon_node, &hwmon->tz_list); - mutex_unlock(&thermal_hwmon_list_lock); - return 0; + goto unlock; - unregister_input: +unregister_input: device_remove_file(hwmon->device, &temp->temp_input.attr); - free_temp_mem: +free_temp_mem: kfree(temp); - unregister_name: +unregister_name: if (new_hwmon_device) hwmon_device_unregister(hwmon->device); - free_mem: - kfree(hwmon); +free_mem: + if (new_hwmon_device) + kfree(hwmon); +unlock: + mutex_unlock(&thermal_hwmon_list_lock); return result; } @@ -217,8 +203,11 @@ EXPORT_SYMBOL_GPL(thermal_add_hwmon_sysfs); void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz) { + struct thermal_hwmon_temp *temp, *entry; struct thermal_hwmon_device *hwmon; - struct thermal_hwmon_temp *temp; + bool unregister; + + guard(mutex)(&thermal_hwmon_list_lock); hwmon = thermal_hwmon_lookup_by_type(tz); if (unlikely(!hwmon)) { @@ -227,29 +216,25 @@ void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz) return; } - temp = thermal_hwmon_lookup_temp(hwmon, tz); - if (unlikely(!temp)) { - /* Should never happen... */ - dev_dbg(&tz->device, "temperature input lookup failed!\n"); - return; - } + unregister = hwmon->device->parent == &tz->device; - device_remove_file(hwmon->device, &temp->temp_input.attr); - if (thermal_zone_crit_temp_valid(tz)) - device_remove_file(hwmon->device, &temp->temp_crit.attr); + list_for_each_entry_safe_reverse(temp, entry, &hwmon->tz_list, hwmon_node) { + if (!unregister && temp->tz != tz) + continue; - mutex_lock(&thermal_hwmon_list_lock); - list_del(&temp->hwmon_node); - kfree(temp); - if (!list_empty(&hwmon->tz_list)) { - mutex_unlock(&thermal_hwmon_list_lock); - return; + device_remove_file(hwmon->device, &temp->temp_input.attr); + if (temp->temp_crit_present) + device_remove_file(hwmon->device, &temp->temp_crit.attr); + + list_del(&temp->hwmon_node); + kfree(temp); } - list_del(&hwmon->node); - mutex_unlock(&thermal_hwmon_list_lock); - hwmon_device_unregister(hwmon->device); - kfree(hwmon); + if (unregister) { + list_del(&hwmon->node); + hwmon_device_unregister(hwmon->device); + kfree(hwmon); + } } EXPORT_SYMBOL_GPL(thermal_remove_hwmon_sysfs); diff --git a/drivers/thermal/thermal_of.c b/drivers/thermal/thermal_of.c index 6ebb83cb70b2..0217a49b08ae 100644 --- a/drivers/thermal/thermal_of.c +++ b/drivers/thermal/thermal_of.c @@ -98,7 +98,7 @@ static struct thermal_trip *thermal_of_trips_init(struct device_node *np, int *n int ret, count; *ntrips = 0; - + struct device_node *trips __free(device_node) = of_get_child_by_name(np, "trips"); if (!trips) return NULL; @@ -144,7 +144,7 @@ static struct device_node *of_thermal_zone_find(struct device_node *sensor, int count = of_count_phandle_with_args(child, "thermal-sensors", "#thermal-sensor-cells"); if (count <= 0) { - pr_err("%pOFn: missing thermal sensor\n", child); + pr_err("%pOFP: missing thermal sensor\n", child); return ERR_PTR(-EINVAL); } @@ -156,14 +156,14 @@ static struct device_node *of_thermal_zone_find(struct device_node *sensor, int "#thermal-sensor-cells", i, &sensor_specs); if (ret < 0) { - pr_err("%pOFn: Failed to read thermal-sensors cells: %d\n", child, ret); + pr_err("%pOFP: Failed to read thermal-sensors cells: %d\n", child, ret); return ERR_PTR(ret); } of_node_put(sensor_specs.np); if ((sensor == sensor_specs.np) && id == (sensor_specs.args_count ? sensor_specs.args[0] : 0)) { - pr_debug("sensor %pOFn id=%d belongs to %pOFn\n", sensor, id, child); + pr_debug("sensor %pOFP id=%d belongs to %pOFP\n", sensor, id, child); return no_free_ptr(child); } } @@ -180,7 +180,7 @@ static int thermal_of_monitor_init(struct device_node *np, int *delay, int *pdel if (ret == -EINVAL) { *pdelay = 0; } else if (ret < 0) { - pr_err("%pOFn: Couldn't get polling-delay-passive: %d\n", np, ret); + pr_err("%pOFP: Couldn't get polling-delay-passive: %d\n", np, ret); return ret; } @@ -188,7 +188,7 @@ static int thermal_of_monitor_init(struct device_node *np, int *delay, int *pdel if (ret == -EINVAL) { *delay = 0; } else if (ret < 0) { - pr_err("%pOFn: Couldn't get polling-delay: %d\n", np, ret); + pr_err("%pOFP: Couldn't get polling-delay: %d\n", np, ret); return ret; } @@ -259,16 +259,34 @@ static bool thermal_of_get_cooling_spec(struct device_node *map_np, int index, of_node_put(cooling_spec.np); - if (cooling_spec.args_count < 2) { - pr_err("wrong reference to cooling device, missing limits\n"); + /* + * There are two formats: + * - Legacy format : <&cdev lower upper> + * - New format : <&cdev cdev_id lower upper> + * + * With the new format, along with the device node pointer, + * the cdev_id must match with the cooling device cdev_id in + * order to bind + */ + if (cooling_spec.args_count < 2 || cooling_spec.args_count > 3) { + pr_err("Invalid number of cooling device parameters\n"); return false; } if (cooling_spec.np != cdev->np) return false; - c->lower = cooling_spec.args[0]; - c->upper = cooling_spec.args[1]; + if (cooling_spec.args_count == 3 && + cooling_spec.args[0] != cdev->cdev_id) + return false; + + if (cooling_spec.args_count != 3) { + c->lower = cooling_spec.args[0]; + c->upper = cooling_spec.args[1]; + } else { + c->lower = cooling_spec.args[1]; + c->upper = cooling_spec.args[2]; + } c->weight = weight; return true; @@ -380,23 +398,23 @@ static struct thermal_zone_device *thermal_of_zone_register(struct device_node * np = of_thermal_zone_find(sensor, id); if (IS_ERR(np)) { if (PTR_ERR(np) != -ENODEV) - pr_err("Failed to find thermal zone for %pOFn id=%d\n", sensor, id); + pr_err("Failed to find thermal zone for %pOFP id=%d\n", sensor, id); return ERR_CAST(np); } trips = thermal_of_trips_init(np, &ntrips); if (IS_ERR(trips)) { - pr_err("Failed to parse trip points for %pOFn id=%d\n", sensor, id); + pr_err("Failed to parse trip points for %pOFP id=%d\n", sensor, id); ret = PTR_ERR(trips); goto out_of_node_put; } if (!trips) - pr_info("No trip points found for %pOFn id=%d\n", sensor, id); + pr_info("No trip points found for %pOFP id=%d\n", sensor, id); ret = thermal_of_monitor_init(np, &delay, &pdelay); if (ret) { - pr_err("Failed to initialize monitoring delays from %pOFn\n", np); + pr_err("Failed to initialize monitoring delays from %pOFP\n", np); goto out_kfree_trips; } @@ -417,7 +435,7 @@ static struct thermal_zone_device *thermal_of_zone_register(struct device_node * pdelay, delay); if (IS_ERR(tz)) { ret = PTR_ERR(tz); - pr_err("Failed to register thermal zone %pOFn: %d\n", np, ret); + pr_err("Failed to register thermal zone %pOFP: %d\n", np, ret); goto out_kfree_trips; } @@ -426,7 +444,7 @@ static struct thermal_zone_device *thermal_of_zone_register(struct device_node * ret = thermal_zone_device_enable(tz); if (ret) { - pr_err("Failed to enabled thermal zone '%s', id=%d: %d\n", + pr_err("Failed to enable thermal zone '%s', id=%d: %d\n", tz->type, tz->id, ret); thermal_of_zone_unregister(tz); return ERR_PTR(ret); @@ -494,7 +512,7 @@ EXPORT_SYMBOL_GPL(devm_thermal_of_zone_register); /** * devm_thermal_of_zone_unregister - Resource managed version of * thermal_of_zone_unregister(). - * @dev: Device for which which resource was allocated. + * @dev: Device for which resource was allocated. * @tz: a pointer to struct thermal_zone where the sensor is registered. * * This function removes the sensor callbacks and private data from the @@ -510,3 +528,125 @@ void devm_thermal_of_zone_unregister(struct device *dev, struct thermal_zone_dev devm_thermal_of_zone_match, tz)); } EXPORT_SYMBOL_GPL(devm_thermal_of_zone_unregister); + +/** + * thermal_of_cooling_device_register() - register an OF thermal cooling device + * @np: a pointer to a device tree node. + * @cdev_id: a cooling device id in the cooling controller + * @type: the thermal cooling device type. + * @devdata: device private data. + * @ops: standard thermal cooling devices callbacks. + * + * This interface function adds a new thermal cooling device (fan/processor/...) + * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself + * to all the thermal zone devices registered at the same time. + * It also gives the opportunity to link the cooling device to a device tree + * node, so that it can be bound to a thermal zone created out of device tree. + * + * Return: a pointer to the created struct thermal_cooling_device or an + * ERR_PTR. Caller must check return value with IS_ERR*() helpers. + */ +struct thermal_cooling_device * +thermal_of_cooling_device_register(struct device_node *np, u32 cdev_id, + const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops) +{ + struct thermal_cooling_device *cdev; + int ret; + + cdev = thermal_cooling_device_alloc(type, ops); + if (IS_ERR(cdev)) + return cdev; + + cdev->np = np; + cdev->cdev_id = cdev_id; + + ret = thermal_cooling_device_add(cdev, devdata); + if (ret) + return ERR_PTR(ret); + + return cdev; +} +EXPORT_SYMBOL_GPL(thermal_of_cooling_device_register); + +static void thermal_of_cooling_device_release(void *data) +{ + struct thermal_cooling_device *cdev = data; + + thermal_cooling_device_unregister(cdev); +} + +static struct thermal_cooling_device * +__devm_thermal_of_cooling_device_register(struct device *dev, struct device_node *np, + u32 cdev_id, const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops) +{ + struct thermal_cooling_device *cdev; + int ret; + + cdev = thermal_of_cooling_device_register(np, cdev_id, type, devdata, ops); + if (IS_ERR(cdev)) + return cdev; + + ret = devm_add_action_or_reset(dev, thermal_of_cooling_device_release, cdev); + if (ret) + return ERR_PTR(ret); + + return cdev; +} + +/** + * devm_thermal_of_cooling_device_register() - register an OF thermal cooling device + * @dev: a valid struct device pointer of a sensor device. + * @cdev_id: a cooling device index in the cooling controller + * @type: the thermal cooling device type. + * @devdata: device private data. + * @ops: standard thermal cooling devices callbacks. + * + * This function will register a cooling device with device tree node reference. + * This interface function adds a new thermal cooling device (fan/processor/...) + * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself + * to all the thermal zone devices registered at the same time. + * + * Return: a pointer to the created struct thermal_cooling_device or an + * ERR_PTR. Caller must check return value with IS_ERR*() helpers. + */ +struct thermal_cooling_device * +devm_thermal_of_cooling_device_register(struct device *dev, u32 cdev_id, + const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops) +{ + return __devm_thermal_of_cooling_device_register(dev, dev->of_node, cdev_id, + type, devdata, ops); +} +EXPORT_SYMBOL_GPL(devm_thermal_of_cooling_device_register); + +/** + * devm_thermal_of_child_cooling_device_register() - register an OF thermal cooling + * device + * @dev: a valid struct device pointer of a sensor device. + * @np: a pointer to a device tree node. + * @type: the thermal cooling device type. + * @devdata: device private data. + * @ops: standard thermal cooling devices callbacks. + * + * This function will register a cooling device with device tree node reference. + * This interface function adds a new thermal cooling device (fan/processor/...) + * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself + * to all the thermal zone devices registered at the same time. + * + * This function should be used when a cooling controller has child + * nodes which are referenced in the thermal zone cooling map. + * + * Return: a pointer to the created struct thermal_cooling_device or an + * ERR_PTR. Caller must check return value with IS_ERR*() helpers. + */ +struct thermal_cooling_device * +devm_thermal_of_child_cooling_device_register(struct device *dev, + struct device_node *np, + const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops) +{ + return __devm_thermal_of_cooling_device_register(dev, np, 0, type, devdata, ops); +} +EXPORT_SYMBOL_GPL(devm_thermal_of_child_cooling_device_register); diff --git a/drivers/thermal/thermal_sysfs.c b/drivers/thermal/thermal_sysfs.c index 46b3b2df935c..96fe5d591a0a 100644 --- a/drivers/thermal/thermal_sysfs.c +++ b/drivers/thermal/thermal_sysfs.c @@ -18,6 +18,7 @@ #include <linux/err.h> #include <linux/slab.h> #include <linux/string.h> +#include <linux/string_choices.h> #include <linux/jiffies.h> #include "thermal_core.h" @@ -56,10 +57,8 @@ mode_show(struct device *dev, struct device_attribute *attr, char *buf) guard(thermal_zone)(tz); - if (tz->mode == THERMAL_DEVICE_ENABLED) - return sysfs_emit(buf, "enabled\n"); - - return sysfs_emit(buf, "disabled\n"); + return sysfs_emit(buf, "%s\n", + str_enabled_disabled(tz->mode == THERMAL_DEVICE_ENABLED)); } static ssize_t @@ -83,7 +82,7 @@ mode_store(struct device *dev, struct device_attribute *attr, } #define thermal_trip_of_attr(_ptr_, _attr_) \ - ({ \ + ({ \ struct thermal_trip_desc *td; \ \ td = container_of(_ptr_, struct thermal_trip_desc, \ @@ -401,8 +400,8 @@ static int create_trip_attrs(struct thermal_zone_device *tz) struct thermal_trip_attrs *trip_attrs = &td->trip_attrs; /* create trip type attribute */ - snprintf(trip_attrs->type.name, THERMAL_NAME_LENGTH, - "trip_point_%d_type", i); + scnprintf(trip_attrs->type.name, sizeof(trip_attrs->type.name), + "trip_point_%d_type", i); sysfs_attr_init(&trip_attrs->type.attr.attr); trip_attrs->type.attr.attr.name = trip_attrs->type.name; @@ -411,8 +410,8 @@ static int create_trip_attrs(struct thermal_zone_device *tz) attrs[i] = &trip_attrs->type.attr.attr; /* create trip temp attribute */ - snprintf(trip_attrs->temp.name, THERMAL_NAME_LENGTH, - "trip_point_%d_temp", i); + scnprintf(trip_attrs->temp.name, sizeof(trip_attrs->temp.name), + "trip_point_%d_temp", i); sysfs_attr_init(&trip_attrs->temp.attr.attr); trip_attrs->temp.attr.attr.name = trip_attrs->temp.name; @@ -424,8 +423,8 @@ static int create_trip_attrs(struct thermal_zone_device *tz) } attrs[i + tz->num_trips] = &trip_attrs->temp.attr.attr; - snprintf(trip_attrs->hyst.name, THERMAL_NAME_LENGTH, - "trip_point_%d_hyst", i); + scnprintf(trip_attrs->hyst.name, sizeof(trip_attrs->hyst.name), + "trip_point_%d_hyst", i); sysfs_attr_init(&trip_attrs->hyst.attr.attr); trip_attrs->hyst.attr.attr.name = trip_attrs->hyst.name; @@ -537,11 +536,9 @@ cur_state_store(struct device *dev, struct device_attribute *attr, unsigned long state; int result; - if (sscanf(buf, "%ld\n", &state) != 1) - return -EINVAL; - - if ((long)state < 0) - return -EINVAL; + result = kstrtoul(buf, 10, &state); + if (result < 0) + return result; /* Requested state should be less than max_state + 1 */ if (state > cdev->max_state) @@ -709,7 +706,7 @@ static ssize_t trans_table_show(struct device *dev, struct thermal_cooling_device *cdev = to_cooling_device(dev); struct cooling_dev_stats *stats; ssize_t len = 0; - int i, j; + int i, j, copied; guard(cooling_dev)(cdev); @@ -717,41 +714,40 @@ static ssize_t trans_table_show(struct device *dev, if (!stats) return -ENODATA; - len += snprintf(buf + len, PAGE_SIZE - len, " From : To\n"); - len += snprintf(buf + len, PAGE_SIZE - len, " : "); + len += sysfs_emit_at(buf, len, " From : To\n"); + len += sysfs_emit_at(buf, len, " : "); for (i = 0; i <= cdev->max_state; i++) { - if (len >= PAGE_SIZE) - break; - len += snprintf(buf + len, PAGE_SIZE - len, "state%2u ", i); + copied = sysfs_emit_at(buf, len, "state%2u ", i); + if (!copied) + goto buf_full; + len += copied; } - if (len >= PAGE_SIZE) - return PAGE_SIZE; - - len += snprintf(buf + len, PAGE_SIZE - len, "\n"); + len += sysfs_emit_at(buf, len, "\n"); for (i = 0; i <= cdev->max_state; i++) { - if (len >= PAGE_SIZE) - break; - - len += snprintf(buf + len, PAGE_SIZE - len, "state%2u:", i); + copied = sysfs_emit_at(buf, len, "state%2u:", i); + if (!copied) + goto buf_full; + len += copied; for (j = 0; j <= cdev->max_state; j++) { - if (len >= PAGE_SIZE) - break; - len += snprintf(buf + len, PAGE_SIZE - len, "%8u ", + copied = sysfs_emit_at(buf, len, "%8u ", stats->trans_table[i * (cdev->max_state + 1) + j]); + if (!copied) + goto buf_full; + len += copied; } - if (len >= PAGE_SIZE) - break; - len += snprintf(buf + len, PAGE_SIZE - len, "\n"); - } - - if (len >= PAGE_SIZE) { - pr_warn_once("Thermal transition table exceeds PAGE_SIZE. Disabling\n"); - len = -EFBIG; + copied = sysfs_emit_at(buf, len, "\n"); + if (!copied) + goto buf_full; + len += copied; } return len; + +buf_full: + pr_warn_once("Thermal transition table exceeds PAGE_SIZE. Disabling\n"); + return -EFBIG; } static DEVICE_ATTR_RO(total_trans); |
