// SPDX-License-Identifier: GPL-2.0 /* * Thermal sensor driver for SpacemiT K1 SoC * * Copyright (C) 2026 Shuwei Wu */ #include #include #include #include #include #include #include #include #include #include #include #include #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; if (low >= high) return -EINVAL; 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)); 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 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); irq = platform_get_irq(pdev, 0); if (irq < 0) return irq; ret = devm_request_threaded_irq(dev, irq, NULL, k1_tsensor_irq_thread, IRQF_ONESHOT, "k1_tsensor", ts); if (ret < 0) return ret; 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)) return PTR_ERR(ts->ch[i].tzd); /* 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"); k1_tsensor_enable_irq(ts->ch + i); } platform_set_drvdata(pdev, ts); return 0; } 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 "); MODULE_LICENSE("GPL");