blob: 614d2eb2b898a945e27ad70084cfc1def41d45f6 [file]
From dabc974a3c36b915c2a3572d71e91a67008a8327 Mon Sep 17 00:00:00 2001
From: David Wang <davidwang@quantatw.com>
Date: Tue, 7 Nov 2023 21:48:26 +0800
Subject: [PATCH 21/23] drivers: thermal: sync npcm-thermal
---
drivers/thermal/Kconfig | 11 +
drivers/thermal/Makefile | 1 +
drivers/thermal/npcm_thermal.c | 409 +++++++++++++++++++++++++++++++++
3 files changed, 421 insertions(+)
create mode 100644 drivers/thermal/npcm_thermal.c
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index d7f44deab5b1..55e85714287a 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -292,6 +292,17 @@ config MAX77620_THERMAL
interrupts when PMIC die temperature cross the threshold of
120 degC and 140 degC.
+config NPCM_THERMAL
+ tristate "NPCM thermal driver"
+ depends on ARCH_NPCM || COMPILE_TEST
+ depends on HAS_IOMEM
+ depends on OF
+ default y
+ help
+ Enable this to plug nuvoton NPCM BMC thermal sensor driver into the Linux
+ thermal framework. cpufreq is used as the cooling device to throttle
+ CPUs when the passive trip is crossed.
+
config QORIQ_THERMAL
tristate "QorIQ Thermal Monitoring Unit"
depends on THERMAL_OF && HAS_IOMEM
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index 82fc3e616e54..cab13a6403be 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -55,6 +55,7 @@ obj-$(CONFIG_QCOM_TSENS) += qcom/
obj-y += tegra/
obj-$(CONFIG_HISI_THERMAL) += hisi_thermal.o
obj-$(CONFIG_MTK_THERMAL) += mtk_thermal.o
+obj-$(CONFIG_NPCM_THERMAL) += npcm_thermal.o
obj-$(CONFIG_GENERIC_ADC_THERMAL) += thermal-generic-adc.o
obj-$(CONFIG_UNIPHIER_THERMAL) += uniphier_thermal.o
obj-$(CONFIG_AMLOGIC_THERMAL) += amlogic_thermal.o
diff --git a/drivers/thermal/npcm_thermal.c b/drivers/thermal/npcm_thermal.c
new file mode 100644
index 000000000000..b69e70e03d5d
--- /dev/null
+++ b/drivers/thermal/npcm_thermal.c
@@ -0,0 +1,409 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * NPCM Thermal Sensor Driver
+ *
+ * Copyright (c) 2021 Nuvoton Technology corporation.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/mfd/syscon.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/thermal.h>
+#include <linux/interrupt.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+#include "thermal_core.h"
+#include "thermal_hwmon.h"
+
+/* NPCM7xx GCR module */
+#define NPCM8XX_THRTL_OFFSET 0xC0
+#define NPCM8XX_THRTL_TH_DIV_MASK GENMASK(1, 0)
+#define NPCM8XX_THRTL_TH_FULL 0
+#define NPCM8XX_THRTL_TH_DIV_HALF BIT(0)
+#define NPCM8XX_THRTL_TH_DIV_QUARTER BIT(1)
+
+/* The NPCM-THRM registers */
+#define NPCM_THRM_REG_BASE(base, n) ((base) + ((n) * 0x10L))
+
+#define NPCM_THRM_CTL_REG 0x0
+#define NPCM_THRM_STAT_REG 0x4
+#define NPCM_THRM_INTEN_REG 0x8
+#define NPCM_THRM_ANCTL_REG 0x10
+#define NPCM_THRM_OUT_REG(base, n) (NPCM_THRM_REG_BASE(base, n) + 0x40)
+#define NPCM_THRM_THA_REG(base, n) (NPCM_THRM_REG_BASE(base, n) + 0x44)
+#define NPCM_THRM_THB_REG(base, n) (NPCM_THRM_REG_BASE(base, n) + 0x48)
+
+#define NPCM_THRM_CTL_EN BIT(0)
+#define NPCM_THRM_CTL_START BIT(1)
+#define NPCM_THRM_CTL_RESETN BIT(4)
+#define NPCM_THRM_CTL_SLEEP BIT(12)
+#define NPCM_THRM_CTL_SNSCLKDIV GENMASK(23, 16)
+#define NPCM_THRM_CTL_TEMP_MODE GENMASK(25, 24)
+
+#define NPCM_THRM_STAT_DA0 BIT(0)
+#define NPCM_THRM_STAT_DA1 BIT(1)
+#define NPCM_THRM_STAT_ATH0 BIT(8)
+#define NPCM_THRM_STAT_ATH1 BIT(9)
+#define NPCM_THRM_STAT_BTH0 BIT(12)
+#define NPCM_THRM_STAT_BTH1 BIT(13)
+
+#define NPCM_THRM_INTEN_DAIE0 BIT(0)
+#define NPCM_THRM_INTEN_DAIE1 BIT(1)
+#define NPCM_THRM_INTEN_ATHIE0 BIT(8)
+#define NPCM_THRM_INTEN_ATHIE1 BIT(9)
+#define NPCM_THRM_INTEN_BTHIE0 BIT(12)
+#define NPCM_THRM_INTEN_BTHIE1 BIT(13)
+
+#define NPCM_THRM_ANCTL_DEFAULT 0x4
+
+#define NPCM_THRM_DATA_OUT_MASK GENMASK(9, 0)
+#define NPCM_THRM_THA_MASK GENMASK(9, 0)
+#define NPCM_THRM_THB_MASK GENMASK(9, 0)
+
+#define NPCM_THRM_TEMP_MIN (-273820)
+#define NPCM_THRM_TEMP_MAX 370670
+
+#define NPCM_THRM_START 1
+#define NPCM_THRM_STOP 0
+
+struct npcm_thermal_priv {
+ const struct npcm_thrm_data *data;
+ struct thermal_zone_device *overheat_sensor;
+ int current_channel;
+ u8 current_conf;
+ struct regmap *clk_regmap;
+ u32 orig_conf;
+ struct device *dev;
+ void __iomem *base;
+ struct clk *clk;
+ u8 enable;
+ u8 start;
+};
+
+struct npcm_thermal_sensor {
+ struct npcm_thermal_priv *priv;
+ int id;
+};
+
+void npcm_thermal_initialize(struct npcm_thermal_priv *tmps)
+{
+ u32 ctl;
+
+ /* init fault registers */
+ iowrite32(0, NPCM_THRM_THA_REG(tmps->base, 0));
+ iowrite32(0, NPCM_THRM_THA_REG(tmps->base, 1));
+ iowrite32(0x3FF, NPCM_THRM_THB_REG(tmps->base, 0));
+ iowrite32(0x3FF, NPCM_THRM_THB_REG(tmps->base, 1));
+
+ /* init sequence */
+ ctl = ioread32(tmps->base + NPCM_THRM_CTL_REG);
+ ctl |= NPCM_THRM_CTL_RESETN;
+ iowrite32(ctl, tmps->base + NPCM_THRM_CTL_REG);
+ udelay(100);
+ ctl &= ~NPCM_THRM_CTL_SLEEP;
+ iowrite32(ctl, tmps->base + NPCM_THRM_CTL_REG);
+ udelay(10);
+ iowrite32(NPCM_THRM_ANCTL_DEFAULT, tmps->base + NPCM_THRM_ANCTL_REG);
+ udelay(10);
+ ctl |= NPCM_THRM_CTL_EN;
+ iowrite32(ctl, tmps->base + NPCM_THRM_CTL_REG);
+ udelay(100);
+ ctl |= NPCM_THRM_CTL_START;
+ iowrite32(ctl, tmps->base + NPCM_THRM_CTL_REG);
+}
+
+static int npcm_thermal_enable(struct npcm_thermal_priv *tmps)
+{
+ u32 ctl;
+
+ ctl = ioread32(tmps->base + NPCM_THRM_CTL_REG);
+ ctl |= NPCM_THRM_CTL_START;
+ iowrite32(ctl, tmps->base + NPCM_THRM_CTL_REG);
+
+ return 0;
+}
+
+static int npcm_thermal_disable(struct npcm_thermal_priv *tmps)
+{
+ u32 ctl;
+
+ ctl = ioread32(tmps->base + NPCM_THRM_CTL_REG);
+ ctl &= ~NPCM_THRM_CTL_START;
+ iowrite32(ctl, tmps->base + NPCM_THRM_CTL_REG);
+
+ return 0;
+}
+
+static irqreturn_t npcm_thermal_isr(int irq, void *data)
+{
+ struct npcm_thermal_priv *tmps = data;
+ int regval;
+
+ /* Notify the core in thread context */
+ thermal_zone_device_update(tmps->overheat_sensor,
+ THERMAL_EVENT_UNSPECIFIED);
+
+ regval = ioread32(tmps->base + NPCM_THRM_STAT_REG);
+ if (regval & (NPCM_THRM_STAT_ATH0 | NPCM_THRM_STAT_ATH1)) {
+ iowrite32(NPCM_THRM_INTEN_BTHIE0 | NPCM_THRM_INTEN_BTHIE1,
+ tmps->base + NPCM_THRM_INTEN_REG);
+ regmap_update_bits(tmps->clk_regmap, NPCM8XX_THRTL_OFFSET,
+ NPCM8XX_THRTL_TH_DIV_MASK,
+ NPCM8XX_THRTL_TH_DIV_HALF);
+ }
+ if (regval & (NPCM_THRM_STAT_BTH0 | NPCM_THRM_STAT_BTH1)) {
+ iowrite32(NPCM_THRM_INTEN_ATHIE0 | NPCM_THRM_INTEN_ATHIE1,
+ tmps->base + NPCM_THRM_INTEN_REG);
+ regmap_update_bits(tmps->clk_regmap, NPCM8XX_THRTL_OFFSET,
+ NPCM8XX_THRTL_TH_DIV_MASK,
+ NPCM8XX_THRTL_TH_FULL);
+ }
+ iowrite32(regval, tmps->base + NPCM_THRM_STAT_REG);
+
+ /* Notify the thermal core that the temperature is acceptable again */
+ thermal_zone_device_update(tmps->overheat_sensor,
+ THERMAL_EVENT_UNSPECIFIED);
+
+ return IRQ_HANDLED;
+}
+
+static int npcm_thermal_get_temp(void *data, int *temp)
+{
+ struct npcm_thermal_sensor *sensor = data;
+ struct npcm_thermal_priv *tmps = sensor->priv;
+ int regval;
+
+ if (!data)
+ return -EINVAL;
+
+ regval = ioread32(NPCM_THRM_OUT_REG(tmps->base, sensor->id));
+ regval &= NPCM_THRM_DATA_OUT_MASK;
+ *temp = (630 * (509 - regval)) + 50000;
+
+ return 0;
+}
+
+static const struct thermal_zone_of_device_ops npcm_thermal_ops = {
+ .get_temp = npcm_thermal_get_temp,
+};
+
+struct npcm_thrm_data {
+ int num_mask_bit;
+ int max_channel;
+ int max_temp;
+ int min_temp;
+};
+
+static const struct npcm_thrm_data npxm8xx_tmps_data = {
+ .num_mask_bit = 10,
+ .max_channel = 2,
+ .max_temp = 370670,
+ .min_temp = -273820,
+};
+
+static const struct of_device_id of_npcm_thermal_match[] = {
+ {
+ .compatible = "nuvoton,npcm845-thermal",
+ .data = &npxm8xx_tmps_data,
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, of_npcm_thermal_match);
+
+static void npcm_set_overheat_thresholds(struct npcm_thermal_priv *tmps,
+ int thresh_mc, int hyst_mc,
+ int channel)
+{
+ int threshold, hysteresis;
+
+ threshold = clamp_val(thresh_mc, NPCM_THRM_TEMP_MIN, NPCM_THRM_TEMP_MAX);
+ iowrite32(DIV_ROUND_CLOSEST(370670-threshold, 630),
+ NPCM_THRM_THA_REG(tmps->base, channel));
+
+ hysteresis = clamp_val(hyst_mc, NPCM_THRM_TEMP_MIN, NPCM_THRM_TEMP_MAX);
+ iowrite32(DIV_ROUND_CLOSEST(370670-(threshold + hysteresis), 630),
+ NPCM_THRM_THB_REG(tmps->base, channel));
+}
+
+static int npcm_configure_overheat_int(struct npcm_thermal_priv *tmps,
+ struct thermal_zone_device *tz,
+ int channel)
+{
+ /* Retrieve the critical trip point to enable the overheat interrupt */
+ const struct thermal_trip *trips = of_thermal_get_trip_points(tz);
+ int ctl;
+ int i;
+
+ if (!trips)
+ return -EINVAL;
+
+ for (i = 0; i < of_thermal_get_ntrips(tz); i++)
+ if (trips[i].type == THERMAL_TRIP_CRITICAL)
+ break;
+
+ if (i == of_thermal_get_ntrips(tz))
+ return -EINVAL;
+
+ tmps->current_channel = channel;
+ npcm_set_overheat_thresholds(tmps, trips[channel].temperature,
+ trips[channel].hysteresis, channel);
+ tmps->overheat_sensor = tz;
+
+ ctl = ioread32(tmps->base + NPCM_THRM_INTEN_REG);
+ if (channel == 0)
+ ctl |= NPCM_THRM_INTEN_ATHIE0 | NPCM_THRM_INTEN_BTHIE0;
+ if (channel == 1)
+ ctl |= NPCM_THRM_INTEN_ATHIE1 | NPCM_THRM_INTEN_BTHIE1;
+ iowrite32(ctl, tmps->base + NPCM_THRM_INTEN_REG);
+
+ return 0;
+}
+
+static int npcm_thermal_probe(struct platform_device *pdev)
+{
+ struct thermal_zone_device *tz;
+ struct npcm_thermal_sensor *sensor;
+ struct device *dev = &pdev->dev;
+ const struct of_device_id *match;
+ struct npcm_thermal_priv *priv;
+ int sensor_id, irq;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(struct npcm_thermal_priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
+ priv->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(priv->clk))
+ return PTR_ERR(priv->clk);
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq == -EPROBE_DEFER)
+ return irq;
+
+ match = of_match_device(of_npcm_thermal_match, dev);
+ if (!match || !match->data) {
+ dev_err(dev, "No compatible OF match\n");
+ return -ENODEV;
+ }
+ priv->data = (struct npcm_thrm_data *)match->data;
+
+ priv->current_channel = -1;
+
+ /* The overheat interrupt feature is not mandatory */
+ if (irq > 0) {
+ ret = devm_request_irq(dev, irq, npcm_thermal_isr, 0,
+ "NPCM_THERMAL", priv);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed requesting interrupt\n");
+ return ret;
+ }
+ if (of_device_is_compatible(dev->of_node, "nuvoton,npcm845-thermal")) {
+ priv->clk_regmap =
+ syscon_regmap_lookup_by_compatible("nuvoton,npcm845-rst");
+ if (IS_ERR(priv->clk_regmap)) {
+ dev_warn(dev, "Didn't find nuvoton,npcm845-rst, cpu throttling disabled\n");
+ }
+ }
+ }
+
+ npcm_thermal_initialize(priv);
+
+ /* There is two channels for BMC */
+ for (sensor_id = 0; sensor_id < priv->data->max_channel; sensor_id++) {
+ sensor = devm_kzalloc(&pdev->dev,
+ sizeof(struct npcm_thermal_sensor),
+ GFP_KERNEL);
+ if (!sensor)
+ return -ENOMEM;
+
+ /* Register the sensor */
+ sensor->priv = priv;
+ sensor->id = sensor_id;
+ tz = devm_thermal_zone_of_sensor_register(&pdev->dev,
+ sensor->id, sensor,
+ &npcm_thermal_ops);
+ if (IS_ERR(tz)) {
+ dev_info(&pdev->dev, "Thermal sensor %d unavailable\n",
+ sensor_id);
+ devm_kfree(&pdev->dev, sensor);
+ continue;
+ }
+
+ /*
+ * The first channel that has a critical trip point registered
+ * in the DT will serve as interrupt source. Others possible
+ * critical trip points will simply be ignored by the driver.
+ */
+ if (irq > 0 && !priv->overheat_sensor)
+ npcm_configure_overheat_int(priv, tz, sensor->id);
+
+ if (devm_thermal_add_hwmon_sysfs(tz))
+ dev_warn(&pdev->dev, "failed to add hwmon sysfs attributes\n");
+
+ }
+
+ /* Just complain if no overheat interrupt was set up */
+ if (!priv->overheat_sensor)
+ dev_warn(&pdev->dev, "Overheat interrupt not available\n");
+
+ pr_info("npcm_thermal_probe Done\n");
+
+ return 0;
+}
+
+static int npcm_thermal_remove(struct platform_device *pdev)
+{
+ struct npcm_thermal_sensor *sensor = platform_get_drvdata(pdev);
+
+ return npcm_thermal_disable(sensor->priv);
+}
+
+static int __maybe_unused npcm_thermal_suspend(struct device *dev)
+{
+ struct npcm_thermal_sensor *sensor = dev_get_drvdata(dev);
+
+ return npcm_thermal_disable(sensor->priv);
+}
+
+static int __maybe_unused npcm_thermal_resume(struct device *dev)
+{
+ struct npcm_thermal_sensor *sensor = dev_get_drvdata(dev);
+
+ return npcm_thermal_enable(sensor->priv);
+}
+
+static SIMPLE_DEV_PM_OPS(npcm_thermal_pm_ops,
+ npcm_thermal_suspend, npcm_thermal_resume);
+
+static struct platform_driver npcm_thermal_driver = {
+ .driver = {
+ .name = "npcm_thermal",
+ .pm = &npcm_thermal_pm_ops,
+ .of_match_table = of_npcm_thermal_match,
+ },
+ .probe = npcm_thermal_probe,
+ .remove = npcm_thermal_remove,
+};
+
+module_platform_driver(npcm_thermal_driver);
+
+MODULE_AUTHOR("Tomer Maimon <tomer.maimon@nuvoton.com>");
+MODULE_DESCRIPTION("NPCM thermal driver");
+MODULE_LICENSE("GPL v2");
+
--
2.25.1