| // 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 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 <shuwei.wu@mailbox.org>"); | |
| MODULE_LICENSE("GPL"); |