| // 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"); |