// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2021-2026 Axiado Corporation * * Driver for the Axiado on-chip temperature sensor (TSADC) */ #include #include #include #include #include #include #include #include /* TSADC register offsets */ #define AXIADO_TSADC_MODE 0x0000 #define AXIADO_TSADC_FUNC_CTRL 0x0004 #define AXIADO_TSADC_AVG_DOUT 0x000c #define AXIADO_TSADC_INTR_MASK 0x0010 #define AXIADO_TSADC_ANA_REG 0x0020 #define AXIADO_TSADC_SEL 0x0028 #define AXIADO_TSADC_TSEN_EN 0x002c #define AXIADO_TSADC_EN 0x0030 /* TSADC mode register: continuous conversion */ #define AXIADO_TSADC_MODE_CONT 0x2 /* * TSADC functional control register: bit 0 starts the conversion and * bits [31:16] specify the number of samples. Average over 16 samples. */ #define AXIADO_TSADC_FUNC_CTRL_START BIT(0) #define AXIADO_TSADC_FUNC_CTRL_NUM_SAMPLES GENMASK(31, 16) #define AXIADO_TSADC_FUNC_CTRL_CLR 0x0 #define AXIADO_TSADC_FUNC_CTRL_SET \ (FIELD_PREP(AXIADO_TSADC_FUNC_CTRL_NUM_SAMPLES, 16) | \ AXIADO_TSADC_FUNC_CTRL_START) /* TSADC average output register: 12-bit ADC result */ #define AXIADO_TSADC_AVG_DOUT_MASK GENMASK(11, 0) /* Interrupt mask bits */ #define AXIADO_TSADC_INTR_BIST_DONE BIT(0) #define AXIADO_TSADC_INTR_LOOP_DONE BIT(1) #define AXIADO_TSADC_INTR_THRESHOLD BIT(2) #define AXIADO_TSADC_INTR_MASK_ALL \ (AXIADO_TSADC_INTR_BIST_DONE | \ AXIADO_TSADC_INTR_LOOP_DONE | \ AXIADO_TSADC_INTR_THRESHOLD) /* TSADC analog block configuration required for temperature measurement */ #define AXIADO_TSADC_ANA_REG_INIT 0x6 /* TSADC SEL: select temperature measurement */ #define AXIADO_TSADC_SEL_TEMP 0x00 /* TSADC EN / TSEN_EN field values */ #define AXIADO_TSADC_DIS 0x00 #define AXIADO_TSADC_ENA 0x01 struct axiado_tsadc { void __iomem *regs; }; /* * Lookup table: raw ADC output (12-bit) vs temperature in millidegrees * Celsius. Raw values decrease as temperature increases, from -40°C * to 125°C in 5°C steps. */ struct axiado_tsadc_point { u32 raw; int temp_mc; }; static const struct axiado_tsadc_point axiado_tsadc_table[] = { { .raw = 2776, .temp_mc = -40000 }, { .raw = 2748, .temp_mc = -35000 }, { .raw = 2721, .temp_mc = -30000 }, { .raw = 2694, .temp_mc = -25000 }, { .raw = 2667, .temp_mc = -20000 }, { .raw = 2640, .temp_mc = -15000 }, { .raw = 2612, .temp_mc = -10000 }, { .raw = 2584, .temp_mc = -5000 }, { .raw = 2556, .temp_mc = 0 }, { .raw = 2528, .temp_mc = 5000 }, { .raw = 2500, .temp_mc = 10000 }, { .raw = 2472, .temp_mc = 15000 }, { .raw = 2444, .temp_mc = 20000 }, { .raw = 2416, .temp_mc = 25000 }, { .raw = 2389, .temp_mc = 30000 }, { .raw = 2362, .temp_mc = 35000 }, { .raw = 2335, .temp_mc = 40000 }, { .raw = 2307, .temp_mc = 45000 }, { .raw = 2279, .temp_mc = 50000 }, { .raw = 2251, .temp_mc = 55000 }, { .raw = 2223, .temp_mc = 60000 }, { .raw = 2195, .temp_mc = 65000 }, { .raw = 2167, .temp_mc = 70000 }, { .raw = 2138, .temp_mc = 75000 }, { .raw = 2110, .temp_mc = 80000 }, { .raw = 2081, .temp_mc = 85000 }, { .raw = 2052, .temp_mc = 90000 }, { .raw = 2024, .temp_mc = 95000 }, { .raw = 1996, .temp_mc = 100000 }, { .raw = 1967, .temp_mc = 105000 }, { .raw = 1938, .temp_mc = 110000 }, { .raw = 1909, .temp_mc = 115000 }, { .raw = 1880, .temp_mc = 120000 }, { .raw = 1852, .temp_mc = 125000 }, }; /* Convert raw ADC code to millidegrees Celsius with linear interpolation. */ static int axiado_tsadc_raw_to_mc(u32 raw, long *val) { int i, n = ARRAY_SIZE(axiado_tsadc_table); long num, denom; if (raw >= axiado_tsadc_table[0].raw) { *val = axiado_tsadc_table[0].temp_mc; return 0; } if (raw <= axiado_tsadc_table[n - 1].raw) { *val = axiado_tsadc_table[n - 1].temp_mc; return 0; } /* Find i such that axiado_tsadc_table[i].raw >= raw > axiado_tsadc_table[i+1].raw */ for (i = 0; i < n - 1; i++) { if (raw >= axiado_tsadc_table[i + 1].raw) break; } if (raw == axiado_tsadc_table[i].raw) { *val = axiado_tsadc_table[i].temp_mc; return 0; } /* Linear interpolation within the interval */ num = (long)(axiado_tsadc_table[i + 1].temp_mc - axiado_tsadc_table[i].temp_mc) * (axiado_tsadc_table[i].raw - raw); denom = axiado_tsadc_table[i].raw - axiado_tsadc_table[i + 1].raw; *val = axiado_tsadc_table[i].temp_mc + num / denom; return 0; } static int axiado_tsadc_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) { struct axiado_tsadc *tsadc = dev_get_drvdata(dev); u32 raw; if (type != hwmon_temp) return -EOPNOTSUPP; switch (attr) { case hwmon_temp_input: if (ioread32(tsadc->regs + AXIADO_TSADC_SEL) != AXIADO_TSADC_SEL_TEMP || ioread32(tsadc->regs + AXIADO_TSADC_EN) != AXIADO_TSADC_ENA) return -ENODATA; raw = ioread32(tsadc->regs + AXIADO_TSADC_AVG_DOUT) & AXIADO_TSADC_AVG_DOUT_MASK; return axiado_tsadc_raw_to_mc(raw, val); default: return -EOPNOTSUPP; } } static umode_t axiado_tsadc_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, int channel) { if (type == hwmon_temp) { switch (attr) { case hwmon_temp_input: return 0444; default: break; } } return 0; } static const struct hwmon_channel_info * const axiado_tsadc_info[] = { HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), NULL }; static const struct hwmon_ops axiado_tsadc_hwmon_ops = { .is_visible = axiado_tsadc_is_visible, .read = axiado_tsadc_read, }; static const struct hwmon_chip_info axiado_tsadc_chip_info = { .ops = &axiado_tsadc_hwmon_ops, .info = axiado_tsadc_info, }; static void axiado_tsadc_disable(void *data) { struct axiado_tsadc *tsadc = data; iowrite32(AXIADO_TSADC_DIS, tsadc->regs + AXIADO_TSADC_TSEN_EN); iowrite32(AXIADO_TSADC_DIS, tsadc->regs + AXIADO_TSADC_EN); } static int axiado_tsadc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct axiado_tsadc *tsadc; struct device *hwmon_dev; struct clk *refclk; int ret; tsadc = devm_kzalloc(dev, sizeof(*tsadc), GFP_KERNEL); if (!tsadc) return -ENOMEM; tsadc->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(tsadc->regs)) return dev_err_probe(dev, PTR_ERR(tsadc->regs), "failed to map registers\n"); refclk = devm_clk_get_enabled(dev, NULL); if (IS_ERR(refclk)) return dev_err_probe(dev, PTR_ERR(refclk), "failed to enable reference clock\n"); /* Mask all interrupts before setup */ iowrite32(AXIADO_TSADC_INTR_MASK_ALL, tsadc->regs + AXIADO_TSADC_INTR_MASK); iowrite32(AXIADO_TSADC_ANA_REG_INIT, tsadc->regs + AXIADO_TSADC_ANA_REG); iowrite32(AXIADO_TSADC_ENA, tsadc->regs + AXIADO_TSADC_EN); iowrite32(AXIADO_TSADC_SEL_TEMP, tsadc->regs + AXIADO_TSADC_SEL); iowrite32(AXIADO_TSADC_MODE_CONT, tsadc->regs + AXIADO_TSADC_MODE); iowrite32(AXIADO_TSADC_FUNC_CTRL_CLR, tsadc->regs + AXIADO_TSADC_FUNC_CTRL); iowrite32(AXIADO_TSADC_FUNC_CTRL_SET, tsadc->regs + AXIADO_TSADC_FUNC_CTRL); /* Enable the bandgap circuit once the block is fully configured */ iowrite32(AXIADO_TSADC_ENA, tsadc->regs + AXIADO_TSADC_TSEN_EN); ret = devm_add_action_or_reset(dev, axiado_tsadc_disable, tsadc); if (ret) return ret; hwmon_dev = devm_hwmon_device_register_with_info(dev, "axiado_tsadc", tsadc, &axiado_tsadc_chip_info, NULL); return PTR_ERR_OR_ZERO(hwmon_dev); } static const struct of_device_id axiado_tsadc_match[] = { { .compatible = "axiado,ax3000-tsadc" }, { } }; MODULE_DEVICE_TABLE(of, axiado_tsadc_match); static struct platform_driver axiado_tsadc_driver = { .driver = { .name = "axiado-tsadc", .of_match_table = axiado_tsadc_match, }, .probe = axiado_tsadc_probe, }; module_platform_driver(axiado_tsadc_driver); MODULE_DESCRIPTION("Axiado TSADC temperature sensor driver"); MODULE_AUTHOR("Axiado Corporation"); MODULE_LICENSE("GPL");