aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLubomir Rintel <lkundrak@v3.sk>2019-08-12 08:45:54 +0200
committerLubomir Rintel <lkundrak@v3.sk>2020-10-22 00:43:43 +0200
commit5954b46ee7b1de0c8e187643c8e4907da8f41f35 (patch)
tree64998bf13f20d4b214dde9e07fe9a7d98616ecea
parent94ded60f412746f7d710073588e22b6c2d0c832a (diff)
downloadlinux-mmp-lr/mmp3-thermal-v1.tar.gz
thermal driverlr/mmp3-thermal-v1
-rw-r--r--drivers/thermal/Kconfig6
-rw-r--r--drivers/thermal/Makefile1
-rw-r--r--drivers/thermal/mmp3_thermal.c260
3 files changed, 267 insertions, 0 deletions
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index 7edc8dc6bbabe..c3369da68e5a9 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -401,6 +401,12 @@ config DA9062_THERMAL
zone.
Compatible with the DA9062 and DA9061 PMICs.
+config MMP3_THERMAL
+ tristate "MMP3 Thermal interface support"
+ depends on THERMAL
+ help
+ Adds thermal management for Marvell MMP3.
+
config MTK_THERMAL
tristate "Temperature sensor driver for mediatek SoCs"
depends on ARCH_MEDIATEK || COMPILE_TEST
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index b64dd50a66292..c902216a86537 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -55,6 +55,7 @@ obj-y += st/
obj-$(CONFIG_QCOM_TSENS) += qcom/
obj-y += tegra/
obj-$(CONFIG_HISI_THERMAL) += hisi_thermal.o
+obj-$(CONFIG_MMP3_THERMAL) += mmp3_thermal.o
obj-$(CONFIG_MTK_THERMAL) += mtk_thermal.o
obj-$(CONFIG_GENERIC_ADC_THERMAL) += thermal-generic-adc.o
obj-$(CONFIG_ZX2967_THERMAL) += zx2967_thermal.o
diff --git a/drivers/thermal/mmp3_thermal.c b/drivers/thermal/mmp3_thermal.c
new file mode 100644
index 0000000000000..aba4e15b8d07b
--- /dev/null
+++ b/drivers/thermal/mmp3_thermal.c
@@ -0,0 +1,260 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2012 Marvell Semiconductor, Inc.
+ * Copyright 2012 One Laptop per Child
+ * Copyright 2020 Lubomir Rintel <lkundrak@v3.sk>
+ */
+
+#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/thermal.h>
+#include <linux/platform_device.h>
+
+struct mmp3_thermal {
+ struct device *dev;
+ struct thermal_zone_device *tz;
+ u32 __iomem *regs;
+ struct clk_bulk_data *clks;
+ int nr_clks;
+};
+
+#define TEMP_RESERVED BIT(31) // undoc'd, appears in original marvell code
+#define TEMP_START BIT(30)
+#define TEMP_STATUS BIT(29)
+#define TEMP_OVER_INT BIT(28)
+#define TEMP_LOWRANGE BIT(27)
+#define TEMP_EN_WDOG BIT(26)
+#define TEMP_TEMP_INT BIT(25)
+#define TEMP_EN_OVER_INT BIT(24)
+#define TEMP_EN_TEMP_INT BIT(23)
+#define TEMP_AUTO_READ BIT(21) // undoc'd, useful for watchdog but causes interrupt storm
+#define TEMP_WDOG_THSHLD 0x0f00
+#define TEMP_WDOG_SHIFT 8
+#define TEMP_INT_TSHLD_MASK 0x00f0
+#define TEMP_TEMP_VALUE_MASK 0x000f
+
+enum {
+ HIRANGE = 0,
+ LORANGE = 1,
+};
+
+static unsigned int gray_to_temp[2][16] = {{
+ 780, 805, 855, 830, 955, 930, 880, 905,
+ 0, 1130, 1080, 1105, 980, 1005, 1055, 1030
+}, {
+ 260, 285, 335, 310, 435, 410, 360, 385,
+ 0, 610, 560, 585, 460, 485, 535, 510,
+}};
+
+static DEFINE_MUTEX(mmp3_thermal_sensor_mutex);
+
+static unsigned long read_temperature_sensor(struct mmp3_thermal *priv, int range, int index)
+{
+ int i, j, gray;
+ unsigned long temp;
+ u32 val;
+
+ mutex_lock(&mmp3_thermal_sensor_mutex);
+
+ val = readl(priv->regs + index);
+
+ if (val & TEMP_EN_WDOG) {
+ if ((range == HIRANGE) == ((val & TEMP_LOWRANGE) == 0))
+ return 0;
+ }
+
+ if (range == HIRANGE) {
+ val &= ~TEMP_LOWRANGE;
+ } else {
+ val |= TEMP_LOWRANGE;
+ }
+
+ val &= ~TEMP_START;
+ writel(val, priv->regs + index);
+ usleep_range(300, 1000);
+
+ for (i = 0; i < 10; i++) {
+ writel(val | TEMP_START, priv->regs + index);
+ for (j = 0; j < 10; j++) {
+ usleep_range(300, 1000);
+ if (readl(priv->regs + index) & TEMP_STATUS)
+ goto done;
+ }
+ usleep_range(300, 1000);
+ writel(val, priv->regs + index);
+ }
+ done:
+
+ if (i == 10) {
+ printk(KERN_ERR "mmp3_thermal: timeout reading sensor %d, range %d\n",
+ index, range);
+ writel(val, priv->regs + index);
+ temp = 0; // filtering may fix this
+ } else {
+ gray = readl(priv->regs + index) & TEMP_TEMP_VALUE_MASK;
+ temp = gray_to_temp[range][gray];
+ }
+
+ mutex_unlock(&mmp3_thermal_sensor_mutex);
+
+ return temp; // return value is tenths, C
+}
+
+static unsigned long read_filtered_temp(struct mmp3_thermal *priv, int range, int sensor)
+{
+ int i;
+ unsigned long tvals[3];
+
+ for (i = 0; i < 3; i++)
+ tvals[i] = read_temperature_sensor(priv, range, sensor);
+
+ // sort the values
+ if (tvals[0] > tvals[1]) swap(tvals[0], tvals[1]);
+ if (tvals[1] > tvals[2]) swap(tvals[1], tvals[2]);
+ if (tvals[0] > tvals[1]) swap(tvals[0], tvals[1]);
+
+ // return the middle value
+ return tvals[1];
+}
+
+static int mmp3_thermal_get_temp(void *data, int *temp)
+{
+ struct mmp3_thermal *priv = data;
+ unsigned long t, t0, t1, t2;
+
+ t0 = read_filtered_temp(priv, HIRANGE, 0);
+ t1 = read_filtered_temp(priv, HIRANGE, 1);
+ t2 = read_filtered_temp(priv, HIRANGE, 2);
+ t = max(t0,t1);
+ t = max(t,t2);
+
+ if (t < 800) {
+ t0 = read_filtered_temp(priv, LORANGE, 0);
+ t1 = read_filtered_temp(priv, LORANGE, 1);
+ t2 = read_filtered_temp(priv, LORANGE, 2);
+ t = max(t0,t1);
+ t = max(t,t2);
+
+ // the sensors can't report values between 60 and 80
+ if (t > 600)
+ t = 700;
+ }
+
+ *temp = t * 100; // return value is in millidegrees C
+
+ return 0;
+}
+
+static void mmp3_thermal_set_watchdog(struct mmp3_thermal *priv, unsigned int temp)
+{
+ int range = HIRANGE;
+ int grey = 8;
+ int t = 0;
+ int r, i;
+ u32 val;
+
+ for (r = HIRANGE; r <= LORANGE; r++) {
+ for (i = 0; i < 16; i++) {
+ if (gray_to_temp[r][i] > temp)
+ continue;
+ if (t > gray_to_temp[r][i])
+ continue;
+ range = r;
+ grey = i;
+ t = gray_to_temp[range][grey];
+ }
+ }
+
+ dev_info(priv->dev, "Enabling thermal watchdog at %d.%dC\n", t / 10, t % 10);
+
+ val = readl(priv->regs + 1);
+ val &= ~TEMP_LOWRANGE;
+ val |= grey << TEMP_WDOG_SHIFT;
+ val |= TEMP_AUTO_READ;
+ val |= TEMP_EN_WDOG;
+ writel(val, priv->regs + 1);
+}
+
+static const struct thermal_zone_of_device_ops mmp3_thermal_ops = {
+ .get_temp = mmp3_thermal_get_temp,
+};
+
+static int mmp3_thermal_probe(struct platform_device *pdev)
+{
+ struct mmp3_thermal *priv;
+ int index;
+ int temp;
+ int ret;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = &pdev->dev;
+ platform_set_drvdata(pdev, priv);
+
+ priv->regs = devm_of_iomap(priv->dev, dev_of_node(priv->dev), 0, NULL);
+ if (IS_ERR(priv->regs)) {
+ dev_warn(priv->dev, "Unable to get I/O range\n");
+ return PTR_ERR(priv->regs);
+ }
+
+ priv->nr_clks = devm_clk_bulk_get_all(priv->dev, &priv->clks);
+ if (priv->nr_clks < 0) {
+ dev_warn(priv->dev, "Unable to get clocks\n");
+ return priv->nr_clks;
+ }
+
+ ret = clk_bulk_prepare_enable(priv->nr_clks, priv->clks);
+ if (ret) {
+ dev_warn(priv->dev, "Unable to enable clocks\n");
+ return ret;
+ }
+
+ for (index = 0; index < 3; index++)
+ writel(0, priv->regs + index);
+
+ priv->tz = devm_thermal_zone_of_sensor_register(priv->dev, 0, priv, &mmp3_thermal_ops);
+ if (IS_ERR(priv->tz)) {
+ dev_warn(priv->dev, "Unable to register a thermal zone\n");
+ return PTR_ERR(priv->tz);
+ }
+
+ if (priv->tz->ops->get_crit_temp(priv->tz, &temp))
+ mmp3_thermal_set_watchdog(priv, temp);
+
+ dev_info(priv->dev, "MMP3 Thermal Sensor\n");
+ return 0;
+}
+
+static int mmp3_thermal_remove(struct platform_device *pdev)
+{
+ struct mmp3_thermal *priv = platform_get_drvdata(pdev);
+
+ clk_bulk_disable_unprepare(priv->nr_clks, priv->clks);
+
+ return 0;
+}
+
+static const struct of_device_id mmp3_thermal_of_match[] = {
+ { .compatible = "marvell,mmp3-thermal", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, mmp3_thermal_of_match);
+
+static struct platform_driver mmp3_thermal_driver = {
+ .probe = mmp3_thermal_probe,
+ .remove = mmp3_thermal_remove,
+ .driver = {
+ .name = "mmp3-thermal",
+ .of_match_table = mmp3_thermal_of_match,
+ },
+};
+module_platform_driver(mmp3_thermal_driver);
+
+MODULE_AUTHOR("Marvell Semiconductor");
+MODULE_AUTHOR("Lubomir Rintel <lkundrak@v3.sk>");
+MODULE_DESCRIPTION("MMP3 Thermal Sensor Driver");
+MODULE_LICENSE("GPL");