On Wed, Jul 17, 2024 at 02:22:32PM +0800, Inochi Amaoto wrote: > On Tue, Jul 16, 2024 at 09:42:35AM GMT, Haylen Chu wrote: > > Add support for cv180x SoCs integrated thermal sensors. > > > > Signed-off-by: Haylen Chu <heylenay@xxxxxxxxxxx> > > --- > > drivers/thermal/Kconfig | 6 + > > drivers/thermal/Makefile | 1 + > > drivers/thermal/cv180x_thermal.c | 241 +++++++++++++++++++++++++++++++ > > 3 files changed, 248 insertions(+) > > create mode 100644 drivers/thermal/cv180x_thermal.c > > > > diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig > > index 204ed89a3ec9..f53c973a361d 100644 > > --- a/drivers/thermal/Kconfig > > +++ b/drivers/thermal/Kconfig > > @@ -514,4 +514,10 @@ config LOONGSON2_THERMAL > > is higher than the high temperature threshold or lower than the low > > temperature threshold, the interrupt will occur. > > > > +config CV180X_THERMAL > > + tristate "Temperature sensor driver for Sophgo CV180X" > > + help > > + If you say yes here you get support for thermal sensor integrated in > > + Sophgo CV180X SoCs. > > + > > endif > > diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile > > index 5cdf7d68687f..5b59bde8a579 100644 > > --- a/drivers/thermal/Makefile > > +++ b/drivers/thermal/Makefile > > @@ -65,3 +65,4 @@ obj-$(CONFIG_AMLOGIC_THERMAL) += amlogic_thermal.o > > obj-$(CONFIG_SPRD_THERMAL) += sprd_thermal.o > > obj-$(CONFIG_KHADAS_MCU_FAN_THERMAL) += khadas_mcu_fan.o > > obj-$(CONFIG_LOONGSON2_THERMAL) += loongson2_thermal.o > > +obj-$(CONFIG_CV180X_THERMAL) += cv180x_thermal.o > > diff --git a/drivers/thermal/cv180x_thermal.c b/drivers/thermal/cv180x_thermal.c > > new file mode 100644 > > index 000000000000..8b726c0ad848 > > --- /dev/null > > +++ b/drivers/thermal/cv180x_thermal.c > > @@ -0,0 +1,241 @@ > > +// SPDX-License-Identifier: GPL-2.0 > > +/* > > + * Copyright (C) 2021 Sophgo Inc. > > + * Copyright (C) 2024 Haylen Chu <heylenay@xxxxxxxxxxx> > > + */ > > + > > +#include <linux/bits.h> > > +#include <linux/clk.h> > > +#include <linux/io.h> > > +#include <linux/module.h> > > +#include <linux/of.h> > > +#include <linux/platform_device.h> > > +#include <linux/thermal.h> > > + > > +#define TEMPSEN_VERSION 0x0 > > +#define TEMPSEN_CTRL 0x4 > > +#define TEMPSEN_CTRL_EN BIT(0) > > +#define TEMPSEN_CTRL_SEL_MASK GENMASK(7, 4) > > +#define TEMPSEN_CTRL_SEL_OFFSET 4 > > +#define TEMPSEN_STATUS 0x8 > > +#define TEMPSEN_SET 0xc > > +#define TEMPSEN_SET_CHOPSEL_MASK GENMASK(5, 4) > > +#define TEMPSEN_SET_CHOPSEL_OFFSET 4 > > +#define TEMPSEN_SET_CHOPSEL_128T 0 > > +#define TEMPSEN_SET_CHOPSEL_256T 1 > > +#define TEMPSEN_SET_CHOPSEL_512T 2 > > +#define TEMPSEN_SET_CHOPSEL_1024T 3 > > +#define TEMPSEN_SET_ACCSEL_MASK GENMASK(7, 6) > > +#define TEMPSEN_SET_ACCSEL_OFFSET 6 > > +#define TEMPSEN_SET_ACCSEL_512T 0 > > +#define TEMPSEN_SET_ACCSEL_1024T 1 > > +#define TEMPSEN_SET_ACCSEL_2048T 2 > > +#define TEMPSEN_SET_ACCSEL_4096T 3 > > +#define TEMPSEN_SET_CYC_CLKDIV_MASK GENMASK(15, 8) > > +#define TEMPSEN_SET_CYC_CLKDIV_OFFSET 8 > > +#define TEMPSEN_INTR_EN 0x10 > > +#define TEMPSEN_INTR_CLR 0x14 > > +#define TEMPSEN_INTR_STA 0x18 > > +#define TEMPSEN_INTR_RAW 0x1c > > +#define TEMPSEN_RESULT(n) (0x20 + (n) * 4) > > +#define TEMPSEN_RESULT_RESULT_MASK GENMASK(12, 0) > > +#define TEMPSEN_RESULT_MAX_RESULT_MASK GENMASK(28, 16) > > +#define TEMPSEN_RESULT_CLR_MAX_RESULT BIT(31) > > +#define TEMPSEN_AUTO_PERIOD 0x64 > > +#define TEMPSEN_AUTO_PERIOD_AUTO_CYCLE_MASK GENMASK(23, 0) > > +#define TEMPSEN_AUTO_PERIOD_AUTO_CYCLE_OFFSET 0 > > + > > +struct cv180x_thermal_zone { > > + struct device *dev; > > + void __iomem *base; > > + struct clk *clk_tempsen; > > + u32 sample_cycle; > > +}; > > + > > +static void cv180x_thermal_init(struct cv180x_thermal_zone *ctz) > > +{ > > + void __iomem *base = ctz->base; > > + u32 regval; > > + > > + writel(readl(base + TEMPSEN_INTR_RAW), base + TEMPSEN_INTR_CLR); > > + writel(TEMPSEN_RESULT_CLR_MAX_RESULT, base + TEMPSEN_RESULT(0)); > > + > > + regval = readl(base + TEMPSEN_SET); > > + regval &= ~TEMPSEN_SET_CHOPSEL_MASK; > > + regval &= ~TEMPSEN_SET_ACCSEL_MASK; > > + regval &= ~TEMPSEN_SET_CYC_CLKDIV_MASK; > > + regval |= TEMPSEN_SET_CHOPSEL_1024T << TEMPSEN_SET_CHOPSEL_OFFSET; > > + regval |= TEMPSEN_SET_ACCSEL_2048T << TEMPSEN_SET_ACCSEL_OFFSET; > > + regval |= 0x31 << TEMPSEN_SET_CYC_CLKDIV_OFFSET; > > + writel(regval, base + TEMPSEN_SET); > > + > > + regval = readl(base + TEMPSEN_AUTO_PERIOD); > > + regval &= ~TEMPSEN_AUTO_PERIOD_AUTO_CYCLE_MASK; > > + regval |= ctz->sample_cycle << TEMPSEN_AUTO_PERIOD_AUTO_CYCLE_OFFSET; > > + writel(regval, base + TEMPSEN_AUTO_PERIOD); > > + > > + regval = readl(base + TEMPSEN_CTRL); > > + regval &= ~TEMPSEN_CTRL_SEL_MASK; > > + regval |= 1 << TEMPSEN_CTRL_SEL_OFFSET; > > + regval |= TEMPSEN_CTRL_EN; > > + writel(regval, base + TEMPSEN_CTRL); > > +} > > + > > +static void cv180x_thermal_deinit(struct cv180x_thermal_zone *ct) > > +{ > > + void __iomem *base = ct->base; > > + u32 regval; > > + > > + regval = readl(base + TEMPSEN_CTRL); > > + regval &= ~(TEMPSEN_CTRL_SEL_MASK | TEMPSEN_CTRL_EN); > > + writel(regval, base + TEMPSEN_CTRL); > > + > > + writel(readl(base + TEMPSEN_INTR_RAW), base + TEMPSEN_INTR_CLR); > > +} > > + > > +/* > > + * Raw register value to temperature (mC) formula: > > + * > > + * read_val * 1000 * 716 > > + * Temperature = ----------------------- - 273000 > > + * divider > > + * > > + * where divider should be ticks number of accumulation period, > > + * e.g. 2048 for TEMPSEN_CTRL_ACCSEL_2048T > > + */ > > +static int cv180x_calc_temp(struct cv180x_thermal_zone *ctz, u32 result) > > +{ > > + return (result * 1000) * 716 / 2048 - 273000; > > +} > > + > > +static int cv180x_get_temp(struct thermal_zone_device *tdev, int *temperature) > > +{ > > + struct cv180x_thermal_zone *ctz = thermal_zone_device_priv(tdev); > > + void __iomem *base = ctz->base; > > + u32 result; > > + > > + result = readl(base + TEMPSEN_RESULT(0)) & TEMPSEN_RESULT_RESULT_MASK; > > + *temperature = cv180x_calc_temp(ctz, result); > > + > > + return 0; > > +} > > + > > +static const struct thermal_zone_device_ops cv180x_thermal_ops = { > > + .get_temp = cv180x_get_temp, > > +}; > > + > > +static const struct of_device_id cv180x_thermal_of_match[] = { > > + { .compatible = "sophgo,cv1800-thermal" }, > > + {}, > > +}; > > +MODULE_DEVICE_TABLE(of, cv180x_thermal_of_match); > > + > > +static int > > +cv180x_parse_dt(struct cv180x_thermal_zone *ctz) > > +{ > > + struct device_node *np = ctz->dev->of_node; > > + u32 tmp; > > + > > + if (of_property_read_u32(np, "sample-rate-hz", &tmp)) { > > + ctz->sample_cycle = 1000000; > > + } else { > > + /* sample cycle should be at least 524us */ > > + if (tmp > 1000000 / 524) { > > + dev_err(ctz->dev, "invalid sample rate %d\n", tmp); > > + return -EINVAL; > > + } > > + > > + ctz->sample_cycle = 1000000 / tmp; > > + } > > + > > + return 0; > > +} > > + > > +static int cv180x_thermal_probe(struct platform_device *pdev) > > +{ > > + struct cv180x_thermal_zone *ctz; > > + struct thermal_zone_device *tz; > > + struct resource *res; > > + int ret; > > + > > + ctz = devm_kzalloc(&pdev->dev, sizeof(*ctz), GFP_KERNEL); > > + if (!ctz) > > + return -ENOMEM; > > + > > + ctz->dev = &pdev->dev; > > + > > + ret = cv180x_parse_dt(ctz); > > + if (ret) > > + return dev_err_probe(&pdev->dev, ret, "failed to parse dt\n"); > > + > > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > > + ctz->base = devm_ioremap_resource(&pdev->dev, res); > > + if (IS_ERR(ctz->base)) > > + return dev_err_probe(&pdev->dev, PTR_ERR(ctz->base), > > + "failed to map tempsen registers\n"); > > + > > + ctz->clk_tempsen = devm_clk_get_enabled(&pdev->dev, NULL); > > + if (IS_ERR(ctz->clk_tempsen)) > > + return dev_err_probe(&pdev->dev, PTR_ERR(ctz->clk_tempsen), > > + "failed to get clk_tempsen\n"); > > + > > + cv180x_thermal_init(ctz); > > + > > + tz = devm_thermal_of_zone_register(&pdev->dev, 0, ctz, > > + &cv180x_thermal_ops); > > + if (IS_ERR(tz)) > > + return dev_err_probe(&pdev->dev, PTR_ERR(tz), > > + "failed to register thermal zone\n"); > > + > > + platform_set_drvdata(pdev, ctz); > > + > > + return 0; > > +} > > + > > +static int cv180x_thermal_remove(struct platform_device *pdev) > > +{ > > + struct cv180x_thermal_zone *ctz = platform_get_drvdata(pdev); > > + > > + cv180x_thermal_deinit(ctz); > > + > > + return 0; > > +} > > + > > +static int __maybe_unused cv180x_thermal_suspend(struct device *dev) > > +{ > > + struct cv180x_thermal_zone *ctz = dev_get_drvdata(dev); > > + > > + cv180x_thermal_deinit(ctz); > > + clk_disable_unprepare(ctz->clk_tempsen); > > + > > + return 0; > > +} > > + > > +static int __maybe_unused cv180x_thermal_resume(struct device *dev) > > +{ > > + struct cv180x_thermal_zone *ctz = dev_get_drvdata(dev); > > + > > + clk_prepare_enable(ctz->clk_tempsen); > > + cv180x_thermal_init(ctz); > > + > > + return 0; > > +} > > + > > +static SIMPLE_DEV_PM_OPS(cv180x_thermal_pm_ops, > > + cv180x_thermal_suspend, cv180x_thermal_resume); > > + > > +static struct platform_driver cv180x_thermal_driver = { > > + .probe = cv180x_thermal_probe, > > + .remove = cv180x_thermal_remove, > > + .driver = { > > + .name = "cv180x-thermal", > > + .pm = &cv180x_thermal_pm_ops, > > + .of_match_table = cv180x_thermal_of_match, > > + }, > > +}; > > + > > +module_platform_driver(cv180x_thermal_driver); > > + > > +MODULE_DESCRIPTION("Sophgo CV180x thermal driver"); > > +MODULE_AUTHOR("Haylen Chu <heylenay@xxxxxxxxxxx>"); > > +MODULE_LICENSE("GPL"); > > -- > > 2.45.2 > > > > Where is interrupt handler? I see nothing about it. > > Regards, > Inochi Interrupts are not used. This driver implements only polling the temperature. Best regards, Haylen Chu