18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Driver for Broadcom BCM2835 SoC temperature sensor
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2016 Martin Sperl
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/clk.h>
98c2ecf20Sopenharmony_ci#include <linux/debugfs.h>
108c2ecf20Sopenharmony_ci#include <linux/device.h>
118c2ecf20Sopenharmony_ci#include <linux/err.h>
128c2ecf20Sopenharmony_ci#include <linux/io.h>
138c2ecf20Sopenharmony_ci#include <linux/kernel.h>
148c2ecf20Sopenharmony_ci#include <linux/module.h>
158c2ecf20Sopenharmony_ci#include <linux/of.h>
168c2ecf20Sopenharmony_ci#include <linux/of_address.h>
178c2ecf20Sopenharmony_ci#include <linux/of_device.h>
188c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
198c2ecf20Sopenharmony_ci#include <linux/thermal.h>
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci#include "../thermal_hwmon.h"
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci#define BCM2835_TS_TSENSCTL			0x00
248c2ecf20Sopenharmony_ci#define BCM2835_TS_TSENSSTAT			0x04
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_ci#define BCM2835_TS_TSENSCTL_PRWDW		BIT(0)
278c2ecf20Sopenharmony_ci#define BCM2835_TS_TSENSCTL_RSTB		BIT(1)
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci/*
308c2ecf20Sopenharmony_ci * bandgap reference voltage in 6 mV increments
318c2ecf20Sopenharmony_ci * 000b = 1178 mV, 001b = 1184 mV, ... 111b = 1220 mV
328c2ecf20Sopenharmony_ci */
338c2ecf20Sopenharmony_ci#define BCM2835_TS_TSENSCTL_CTRL_BITS		3
348c2ecf20Sopenharmony_ci#define BCM2835_TS_TSENSCTL_CTRL_SHIFT		2
358c2ecf20Sopenharmony_ci#define BCM2835_TS_TSENSCTL_CTRL_MASK		    \
368c2ecf20Sopenharmony_ci	GENMASK(BCM2835_TS_TSENSCTL_CTRL_BITS +     \
378c2ecf20Sopenharmony_ci		BCM2835_TS_TSENSCTL_CTRL_SHIFT - 1, \
388c2ecf20Sopenharmony_ci		BCM2835_TS_TSENSCTL_CTRL_SHIFT)
398c2ecf20Sopenharmony_ci#define BCM2835_TS_TSENSCTL_CTRL_DEFAULT	1
408c2ecf20Sopenharmony_ci#define BCM2835_TS_TSENSCTL_EN_INT		BIT(5)
418c2ecf20Sopenharmony_ci#define BCM2835_TS_TSENSCTL_DIRECT		BIT(6)
428c2ecf20Sopenharmony_ci#define BCM2835_TS_TSENSCTL_CLR_INT		BIT(7)
438c2ecf20Sopenharmony_ci#define BCM2835_TS_TSENSCTL_THOLD_SHIFT		8
448c2ecf20Sopenharmony_ci#define BCM2835_TS_TSENSCTL_THOLD_BITS		10
458c2ecf20Sopenharmony_ci#define BCM2835_TS_TSENSCTL_THOLD_MASK		     \
468c2ecf20Sopenharmony_ci	GENMASK(BCM2835_TS_TSENSCTL_THOLD_BITS +     \
478c2ecf20Sopenharmony_ci		BCM2835_TS_TSENSCTL_THOLD_SHIFT - 1, \
488c2ecf20Sopenharmony_ci		BCM2835_TS_TSENSCTL_THOLD_SHIFT)
498c2ecf20Sopenharmony_ci/*
508c2ecf20Sopenharmony_ci * time how long the block to be asserted in reset
518c2ecf20Sopenharmony_ci * which based on a clock counter (TSENS clock assumed)
528c2ecf20Sopenharmony_ci */
538c2ecf20Sopenharmony_ci#define BCM2835_TS_TSENSCTL_RSTDELAY_SHIFT	18
548c2ecf20Sopenharmony_ci#define BCM2835_TS_TSENSCTL_RSTDELAY_BITS	8
558c2ecf20Sopenharmony_ci#define BCM2835_TS_TSENSCTL_REGULEN		BIT(26)
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci#define BCM2835_TS_TSENSSTAT_DATA_BITS		10
588c2ecf20Sopenharmony_ci#define BCM2835_TS_TSENSSTAT_DATA_SHIFT		0
598c2ecf20Sopenharmony_ci#define BCM2835_TS_TSENSSTAT_DATA_MASK		     \
608c2ecf20Sopenharmony_ci	GENMASK(BCM2835_TS_TSENSSTAT_DATA_BITS +     \
618c2ecf20Sopenharmony_ci		BCM2835_TS_TSENSSTAT_DATA_SHIFT - 1, \
628c2ecf20Sopenharmony_ci		BCM2835_TS_TSENSSTAT_DATA_SHIFT)
638c2ecf20Sopenharmony_ci#define BCM2835_TS_TSENSSTAT_VALID		BIT(10)
648c2ecf20Sopenharmony_ci#define BCM2835_TS_TSENSSTAT_INTERRUPT		BIT(11)
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_cistruct bcm2835_thermal_data {
678c2ecf20Sopenharmony_ci	struct thermal_zone_device *tz;
688c2ecf20Sopenharmony_ci	void __iomem *regs;
698c2ecf20Sopenharmony_ci	struct clk *clk;
708c2ecf20Sopenharmony_ci	struct dentry *debugfsdir;
718c2ecf20Sopenharmony_ci};
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_cistatic int bcm2835_thermal_adc2temp(u32 adc, int offset, int slope)
748c2ecf20Sopenharmony_ci{
758c2ecf20Sopenharmony_ci	return offset + slope * adc;
768c2ecf20Sopenharmony_ci}
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_cistatic int bcm2835_thermal_temp2adc(int temp, int offset, int slope)
798c2ecf20Sopenharmony_ci{
808c2ecf20Sopenharmony_ci	temp -= offset;
818c2ecf20Sopenharmony_ci	temp /= slope;
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci	if (temp < 0)
848c2ecf20Sopenharmony_ci		temp = 0;
858c2ecf20Sopenharmony_ci	if (temp >= BIT(BCM2835_TS_TSENSSTAT_DATA_BITS))
868c2ecf20Sopenharmony_ci		temp = BIT(BCM2835_TS_TSENSSTAT_DATA_BITS) - 1;
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci	return temp;
898c2ecf20Sopenharmony_ci}
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_cistatic int bcm2835_thermal_get_temp(void *d, int *temp)
928c2ecf20Sopenharmony_ci{
938c2ecf20Sopenharmony_ci	struct bcm2835_thermal_data *data = d;
948c2ecf20Sopenharmony_ci	u32 val = readl(data->regs + BCM2835_TS_TSENSSTAT);
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci	if (!(val & BCM2835_TS_TSENSSTAT_VALID))
978c2ecf20Sopenharmony_ci		return -EIO;
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	val &= BCM2835_TS_TSENSSTAT_DATA_MASK;
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_ci	*temp = bcm2835_thermal_adc2temp(
1028c2ecf20Sopenharmony_ci		val,
1038c2ecf20Sopenharmony_ci		thermal_zone_get_offset(data->tz),
1048c2ecf20Sopenharmony_ci		thermal_zone_get_slope(data->tz));
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci	return 0;
1078c2ecf20Sopenharmony_ci}
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_cistatic const struct debugfs_reg32 bcm2835_thermal_regs[] = {
1108c2ecf20Sopenharmony_ci	{
1118c2ecf20Sopenharmony_ci		.name = "ctl",
1128c2ecf20Sopenharmony_ci		.offset = 0
1138c2ecf20Sopenharmony_ci	},
1148c2ecf20Sopenharmony_ci	{
1158c2ecf20Sopenharmony_ci		.name = "stat",
1168c2ecf20Sopenharmony_ci		.offset = 4
1178c2ecf20Sopenharmony_ci	}
1188c2ecf20Sopenharmony_ci};
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_cistatic void bcm2835_thermal_debugfs(struct platform_device *pdev)
1218c2ecf20Sopenharmony_ci{
1228c2ecf20Sopenharmony_ci	struct bcm2835_thermal_data *data = platform_get_drvdata(pdev);
1238c2ecf20Sopenharmony_ci	struct debugfs_regset32 *regset;
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci	data->debugfsdir = debugfs_create_dir("bcm2835_thermal", NULL);
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci	regset = devm_kzalloc(&pdev->dev, sizeof(*regset), GFP_KERNEL);
1288c2ecf20Sopenharmony_ci	if (!regset)
1298c2ecf20Sopenharmony_ci		return;
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci	regset->regs = bcm2835_thermal_regs;
1328c2ecf20Sopenharmony_ci	regset->nregs = ARRAY_SIZE(bcm2835_thermal_regs);
1338c2ecf20Sopenharmony_ci	regset->base = data->regs;
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	debugfs_create_regset32("regset", 0444, data->debugfsdir, regset);
1368c2ecf20Sopenharmony_ci}
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_cistatic const struct thermal_zone_of_device_ops bcm2835_thermal_ops = {
1398c2ecf20Sopenharmony_ci	.get_temp = bcm2835_thermal_get_temp,
1408c2ecf20Sopenharmony_ci};
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci/*
1438c2ecf20Sopenharmony_ci * Note: as per Raspberry Foundation FAQ
1448c2ecf20Sopenharmony_ci * (https://www.raspberrypi.org/help/faqs/#performanceOperatingTemperature)
1458c2ecf20Sopenharmony_ci * the recommended temperature range for the SoC -40C to +85C
1468c2ecf20Sopenharmony_ci * so the trip limit is set to 80C.
1478c2ecf20Sopenharmony_ci * this applies to all the BCM283X SoC
1488c2ecf20Sopenharmony_ci */
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_cistatic const struct of_device_id bcm2835_thermal_of_match_table[] = {
1518c2ecf20Sopenharmony_ci	{
1528c2ecf20Sopenharmony_ci		.compatible = "brcm,bcm2835-thermal",
1538c2ecf20Sopenharmony_ci	},
1548c2ecf20Sopenharmony_ci	{
1558c2ecf20Sopenharmony_ci		.compatible = "brcm,bcm2836-thermal",
1568c2ecf20Sopenharmony_ci	},
1578c2ecf20Sopenharmony_ci	{
1588c2ecf20Sopenharmony_ci		.compatible = "brcm,bcm2837-thermal",
1598c2ecf20Sopenharmony_ci	},
1608c2ecf20Sopenharmony_ci	{},
1618c2ecf20Sopenharmony_ci};
1628c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, bcm2835_thermal_of_match_table);
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_cistatic int bcm2835_thermal_probe(struct platform_device *pdev)
1658c2ecf20Sopenharmony_ci{
1668c2ecf20Sopenharmony_ci	const struct of_device_id *match;
1678c2ecf20Sopenharmony_ci	struct thermal_zone_device *tz;
1688c2ecf20Sopenharmony_ci	struct bcm2835_thermal_data *data;
1698c2ecf20Sopenharmony_ci	struct resource *res;
1708c2ecf20Sopenharmony_ci	int err = 0;
1718c2ecf20Sopenharmony_ci	u32 val;
1728c2ecf20Sopenharmony_ci	unsigned long rate;
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
1758c2ecf20Sopenharmony_ci	if (!data)
1768c2ecf20Sopenharmony_ci		return -ENOMEM;
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ci	match = of_match_device(bcm2835_thermal_of_match_table,
1798c2ecf20Sopenharmony_ci				&pdev->dev);
1808c2ecf20Sopenharmony_ci	if (!match)
1818c2ecf20Sopenharmony_ci		return -EINVAL;
1828c2ecf20Sopenharmony_ci
1838c2ecf20Sopenharmony_ci	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
1848c2ecf20Sopenharmony_ci	data->regs = devm_ioremap_resource(&pdev->dev, res);
1858c2ecf20Sopenharmony_ci	if (IS_ERR(data->regs)) {
1868c2ecf20Sopenharmony_ci		err = PTR_ERR(data->regs);
1878c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "Could not get registers: %d\n", err);
1888c2ecf20Sopenharmony_ci		return err;
1898c2ecf20Sopenharmony_ci	}
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_ci	data->clk = devm_clk_get(&pdev->dev, NULL);
1928c2ecf20Sopenharmony_ci	if (IS_ERR(data->clk)) {
1938c2ecf20Sopenharmony_ci		err = PTR_ERR(data->clk);
1948c2ecf20Sopenharmony_ci		if (err != -EPROBE_DEFER)
1958c2ecf20Sopenharmony_ci			dev_err(&pdev->dev, "Could not get clk: %d\n", err);
1968c2ecf20Sopenharmony_ci		return err;
1978c2ecf20Sopenharmony_ci	}
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_ci	err = clk_prepare_enable(data->clk);
2008c2ecf20Sopenharmony_ci	if (err)
2018c2ecf20Sopenharmony_ci		return err;
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ci	rate = clk_get_rate(data->clk);
2048c2ecf20Sopenharmony_ci	if ((rate < 1920000) || (rate > 5000000))
2058c2ecf20Sopenharmony_ci		dev_warn(&pdev->dev,
2068c2ecf20Sopenharmony_ci			 "Clock %pCn running at %lu Hz is outside of the recommended range: 1.92 to 5MHz\n",
2078c2ecf20Sopenharmony_ci			 data->clk, rate);
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_ci	/* register of thermal sensor and get info from DT */
2108c2ecf20Sopenharmony_ci	tz = thermal_zone_of_sensor_register(&pdev->dev, 0, data,
2118c2ecf20Sopenharmony_ci					     &bcm2835_thermal_ops);
2128c2ecf20Sopenharmony_ci	if (IS_ERR(tz)) {
2138c2ecf20Sopenharmony_ci		err = PTR_ERR(tz);
2148c2ecf20Sopenharmony_ci		dev_err(&pdev->dev,
2158c2ecf20Sopenharmony_ci			"Failed to register the thermal device: %d\n",
2168c2ecf20Sopenharmony_ci			err);
2178c2ecf20Sopenharmony_ci		goto err_clk;
2188c2ecf20Sopenharmony_ci	}
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_ci	/*
2218c2ecf20Sopenharmony_ci	 * right now the FW does set up the HW-block, so we are not
2228c2ecf20Sopenharmony_ci	 * touching the configuration registers.
2238c2ecf20Sopenharmony_ci	 * But if the HW is not enabled, then set it up
2248c2ecf20Sopenharmony_ci	 * using "sane" values used by the firmware right now.
2258c2ecf20Sopenharmony_ci	 */
2268c2ecf20Sopenharmony_ci	val = readl(data->regs + BCM2835_TS_TSENSCTL);
2278c2ecf20Sopenharmony_ci	if (!(val & BCM2835_TS_TSENSCTL_RSTB)) {
2288c2ecf20Sopenharmony_ci		int trip_temp, offset, slope;
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_ci		slope = thermal_zone_get_slope(tz);
2318c2ecf20Sopenharmony_ci		offset = thermal_zone_get_offset(tz);
2328c2ecf20Sopenharmony_ci		/*
2338c2ecf20Sopenharmony_ci		 * For now we deal only with critical, otherwise
2348c2ecf20Sopenharmony_ci		 * would need to iterate
2358c2ecf20Sopenharmony_ci		 */
2368c2ecf20Sopenharmony_ci		err = tz->ops->get_trip_temp(tz, 0, &trip_temp);
2378c2ecf20Sopenharmony_ci		if (err < 0) {
2388c2ecf20Sopenharmony_ci			dev_err(&pdev->dev,
2398c2ecf20Sopenharmony_ci				"Not able to read trip_temp: %d\n",
2408c2ecf20Sopenharmony_ci				err);
2418c2ecf20Sopenharmony_ci			goto err_tz;
2428c2ecf20Sopenharmony_ci		}
2438c2ecf20Sopenharmony_ci
2448c2ecf20Sopenharmony_ci		/* set bandgap reference voltage and enable voltage regulator */
2458c2ecf20Sopenharmony_ci		val = (BCM2835_TS_TSENSCTL_CTRL_DEFAULT <<
2468c2ecf20Sopenharmony_ci		       BCM2835_TS_TSENSCTL_CTRL_SHIFT) |
2478c2ecf20Sopenharmony_ci		      BCM2835_TS_TSENSCTL_REGULEN;
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_ci		/* use the recommended reset duration */
2508c2ecf20Sopenharmony_ci		val |= (0xFE << BCM2835_TS_TSENSCTL_RSTDELAY_SHIFT);
2518c2ecf20Sopenharmony_ci
2528c2ecf20Sopenharmony_ci		/*  trip_adc value from info */
2538c2ecf20Sopenharmony_ci		val |= bcm2835_thermal_temp2adc(trip_temp,
2548c2ecf20Sopenharmony_ci						offset,
2558c2ecf20Sopenharmony_ci						slope)
2568c2ecf20Sopenharmony_ci			<< BCM2835_TS_TSENSCTL_THOLD_SHIFT;
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_ci		/* write the value back to the register as 2 steps */
2598c2ecf20Sopenharmony_ci		writel(val, data->regs + BCM2835_TS_TSENSCTL);
2608c2ecf20Sopenharmony_ci		val |= BCM2835_TS_TSENSCTL_RSTB;
2618c2ecf20Sopenharmony_ci		writel(val, data->regs + BCM2835_TS_TSENSCTL);
2628c2ecf20Sopenharmony_ci	}
2638c2ecf20Sopenharmony_ci
2648c2ecf20Sopenharmony_ci	data->tz = tz;
2658c2ecf20Sopenharmony_ci
2668c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, data);
2678c2ecf20Sopenharmony_ci
2688c2ecf20Sopenharmony_ci	/*
2698c2ecf20Sopenharmony_ci	 * Thermal_zone doesn't enable hwmon as default,
2708c2ecf20Sopenharmony_ci	 * enable it here
2718c2ecf20Sopenharmony_ci	 */
2728c2ecf20Sopenharmony_ci	tz->tzp->no_hwmon = false;
2738c2ecf20Sopenharmony_ci	err = thermal_add_hwmon_sysfs(tz);
2748c2ecf20Sopenharmony_ci	if (err)
2758c2ecf20Sopenharmony_ci		goto err_tz;
2768c2ecf20Sopenharmony_ci
2778c2ecf20Sopenharmony_ci	bcm2835_thermal_debugfs(pdev);
2788c2ecf20Sopenharmony_ci
2798c2ecf20Sopenharmony_ci	return 0;
2808c2ecf20Sopenharmony_cierr_tz:
2818c2ecf20Sopenharmony_ci	thermal_zone_of_sensor_unregister(&pdev->dev, tz);
2828c2ecf20Sopenharmony_cierr_clk:
2838c2ecf20Sopenharmony_ci	clk_disable_unprepare(data->clk);
2848c2ecf20Sopenharmony_ci
2858c2ecf20Sopenharmony_ci	return err;
2868c2ecf20Sopenharmony_ci}
2878c2ecf20Sopenharmony_ci
2888c2ecf20Sopenharmony_cistatic int bcm2835_thermal_remove(struct platform_device *pdev)
2898c2ecf20Sopenharmony_ci{
2908c2ecf20Sopenharmony_ci	struct bcm2835_thermal_data *data = platform_get_drvdata(pdev);
2918c2ecf20Sopenharmony_ci	struct thermal_zone_device *tz = data->tz;
2928c2ecf20Sopenharmony_ci
2938c2ecf20Sopenharmony_ci	debugfs_remove_recursive(data->debugfsdir);
2948c2ecf20Sopenharmony_ci	thermal_zone_of_sensor_unregister(&pdev->dev, tz);
2958c2ecf20Sopenharmony_ci	clk_disable_unprepare(data->clk);
2968c2ecf20Sopenharmony_ci
2978c2ecf20Sopenharmony_ci	return 0;
2988c2ecf20Sopenharmony_ci}
2998c2ecf20Sopenharmony_ci
3008c2ecf20Sopenharmony_cistatic struct platform_driver bcm2835_thermal_driver = {
3018c2ecf20Sopenharmony_ci	.probe = bcm2835_thermal_probe,
3028c2ecf20Sopenharmony_ci	.remove = bcm2835_thermal_remove,
3038c2ecf20Sopenharmony_ci	.driver = {
3048c2ecf20Sopenharmony_ci		.name = "bcm2835_thermal",
3058c2ecf20Sopenharmony_ci		.of_match_table = bcm2835_thermal_of_match_table,
3068c2ecf20Sopenharmony_ci	},
3078c2ecf20Sopenharmony_ci};
3088c2ecf20Sopenharmony_cimodule_platform_driver(bcm2835_thermal_driver);
3098c2ecf20Sopenharmony_ci
3108c2ecf20Sopenharmony_ciMODULE_AUTHOR("Martin Sperl");
3118c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Thermal driver for bcm2835 chip");
3128c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
313