brintos

brintos / linux-shallow public Read only

0
0
Text · 9.7 KiB · 2709827 Raw
375 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Broadcom STB AVS TMON thermal sensor driver4 *5 * Copyright (c) 2015-2017 Broadcom6 */7 8#define DRV_NAME	"brcmstb_thermal"9 10#define pr_fmt(fmt)	DRV_NAME ": " fmt11 12#include <linux/bitops.h>13#include <linux/device.h>14#include <linux/err.h>15#include <linux/io.h>16#include <linux/irqreturn.h>17#include <linux/interrupt.h>18#include <linux/kernel.h>19#include <linux/module.h>20#include <linux/of.h>21#include <linux/platform_device.h>22#include <linux/thermal.h>23 24#define AVS_TMON_STATUS			0x0025 #define AVS_TMON_STATUS_valid_msk	BIT(11)26 #define AVS_TMON_STATUS_data_msk	GENMASK(10, 1)27 #define AVS_TMON_STATUS_data_shift	128 29#define AVS_TMON_EN_OVERTEMP_RESET	0x0430 #define AVS_TMON_EN_OVERTEMP_RESET_msk	BIT(0)31 32#define AVS_TMON_RESET_THRESH		0x0833 #define AVS_TMON_RESET_THRESH_msk	GENMASK(10, 1)34 #define AVS_TMON_RESET_THRESH_shift	135 36#define AVS_TMON_INT_IDLE_TIME		0x1037 38#define AVS_TMON_EN_TEMP_INT_SRCS	0x1439 #define AVS_TMON_EN_TEMP_INT_SRCS_high	BIT(1)40 #define AVS_TMON_EN_TEMP_INT_SRCS_low	BIT(0)41 42#define AVS_TMON_INT_THRESH		0x1843 #define AVS_TMON_INT_THRESH_high_msk	GENMASK(26, 17)44 #define AVS_TMON_INT_THRESH_high_shift	1745 #define AVS_TMON_INT_THRESH_low_msk	GENMASK(10, 1)46 #define AVS_TMON_INT_THRESH_low_shift	147 48#define AVS_TMON_TEMP_INT_CODE		0x1c49#define AVS_TMON_TP_TEST_ENABLE		0x2050 51/* Default coefficients */52#define AVS_TMON_TEMP_SLOPE		48753#define AVS_TMON_TEMP_OFFSET		41004054 55/* HW related temperature constants */56#define AVS_TMON_TEMP_MAX		0x3ff57#define AVS_TMON_TEMP_MIN		-8816158#define AVS_TMON_TEMP_MASK		AVS_TMON_TEMP_MAX59 60enum avs_tmon_trip_type {61	TMON_TRIP_TYPE_LOW = 0,62	TMON_TRIP_TYPE_HIGH,63	TMON_TRIP_TYPE_RESET,64	TMON_TRIP_TYPE_MAX,65};66 67struct avs_tmon_trip {68	/* HW bit to enable the trip */69	u32 enable_offs;70	u32 enable_mask;71 72	/* HW field to read the trip temperature */73	u32 reg_offs;74	u32 reg_msk;75	int reg_shift;76};77 78static struct avs_tmon_trip avs_tmon_trips[] = {79	/* Trips when temperature is below threshold */80	[TMON_TRIP_TYPE_LOW] = {81		.enable_offs	= AVS_TMON_EN_TEMP_INT_SRCS,82		.enable_mask	= AVS_TMON_EN_TEMP_INT_SRCS_low,83		.reg_offs	= AVS_TMON_INT_THRESH,84		.reg_msk	= AVS_TMON_INT_THRESH_low_msk,85		.reg_shift	= AVS_TMON_INT_THRESH_low_shift,86	},87	/* Trips when temperature is above threshold */88	[TMON_TRIP_TYPE_HIGH] = {89		.enable_offs	= AVS_TMON_EN_TEMP_INT_SRCS,90		.enable_mask	= AVS_TMON_EN_TEMP_INT_SRCS_high,91		.reg_offs	= AVS_TMON_INT_THRESH,92		.reg_msk	= AVS_TMON_INT_THRESH_high_msk,93		.reg_shift	= AVS_TMON_INT_THRESH_high_shift,94	},95	/* Automatically resets chip when above threshold */96	[TMON_TRIP_TYPE_RESET] = {97		.enable_offs	= AVS_TMON_EN_OVERTEMP_RESET,98		.enable_mask	= AVS_TMON_EN_OVERTEMP_RESET_msk,99		.reg_offs	= AVS_TMON_RESET_THRESH,100		.reg_msk	= AVS_TMON_RESET_THRESH_msk,101		.reg_shift	= AVS_TMON_RESET_THRESH_shift,102	},103};104 105struct brcmstb_thermal_params {106	unsigned int offset;107	unsigned int mult;108	const struct thermal_zone_device_ops *of_ops;109};110 111struct brcmstb_thermal_priv {112	void __iomem *tmon_base;113	struct device *dev;114	struct thermal_zone_device *thermal;115	/* Process specific thermal parameters used for calculations */116	const struct brcmstb_thermal_params *temp_params;117};118 119/* Convert a HW code to a temperature reading (millidegree celsius) */120static inline int avs_tmon_code_to_temp(struct brcmstb_thermal_priv *priv,121					u32 code)122{123	int offset = priv->temp_params->offset;124	int mult = priv->temp_params->mult;125 126	return (offset - (int)((code & AVS_TMON_TEMP_MASK) * mult));127}128 129/*130 * Convert a temperature value (millidegree celsius) to a HW code131 *132 * @temp: temperature to convert133 * @low: if true, round toward the low side134 */135static inline u32 avs_tmon_temp_to_code(struct brcmstb_thermal_priv *priv,136					int temp, bool low)137{138	int offset = priv->temp_params->offset;139	int mult = priv->temp_params->mult;140 141	if (temp < AVS_TMON_TEMP_MIN)142		return AVS_TMON_TEMP_MAX;	/* Maximum code value */143 144	if (temp >= offset)145		return 0;	/* Minimum code value */146 147	if (low)148		return (u32)(DIV_ROUND_UP(offset - temp, mult));149	else150		return (u32)((offset - temp) / mult);151}152 153static int brcmstb_get_temp(struct thermal_zone_device *tz, int *temp)154{155	struct brcmstb_thermal_priv *priv = thermal_zone_device_priv(tz);156	u32 val;157	long t;158 159	val = __raw_readl(priv->tmon_base + AVS_TMON_STATUS);160 161	if (!(val & AVS_TMON_STATUS_valid_msk))162		return -EIO;163 164	val = (val & AVS_TMON_STATUS_data_msk) >> AVS_TMON_STATUS_data_shift;165 166	t = avs_tmon_code_to_temp(priv, val);167	if (t < 0)168		*temp = 0;169	else170		*temp = t;171 172	return 0;173}174 175static void avs_tmon_trip_enable(struct brcmstb_thermal_priv *priv,176				 enum avs_tmon_trip_type type, int en)177{178	struct avs_tmon_trip *trip = &avs_tmon_trips[type];179	u32 val = __raw_readl(priv->tmon_base + trip->enable_offs);180 181	dev_dbg(priv->dev, "%sable trip, type %d\n", en ? "en" : "dis", type);182 183	if (en)184		val |= trip->enable_mask;185	else186		val &= ~trip->enable_mask;187 188	__raw_writel(val, priv->tmon_base + trip->enable_offs);189}190 191static int avs_tmon_get_trip_temp(struct brcmstb_thermal_priv *priv,192				  enum avs_tmon_trip_type type)193{194	struct avs_tmon_trip *trip = &avs_tmon_trips[type];195	u32 val = __raw_readl(priv->tmon_base + trip->reg_offs);196 197	val &= trip->reg_msk;198	val >>= trip->reg_shift;199 200	return avs_tmon_code_to_temp(priv, val);201}202 203static void avs_tmon_set_trip_temp(struct brcmstb_thermal_priv *priv,204				   enum avs_tmon_trip_type type,205				   int temp)206{207	struct avs_tmon_trip *trip = &avs_tmon_trips[type];208	u32 val, orig;209 210	dev_dbg(priv->dev, "set temp %d to %d\n", type, temp);211 212	/* round toward low temp for the low interrupt */213	val = avs_tmon_temp_to_code(priv, temp,214				    type == TMON_TRIP_TYPE_LOW);215 216	val <<= trip->reg_shift;217	val &= trip->reg_msk;218 219	orig = __raw_readl(priv->tmon_base + trip->reg_offs);220	orig &= ~trip->reg_msk;221	orig |= val;222	__raw_writel(orig, priv->tmon_base + trip->reg_offs);223}224 225static int avs_tmon_get_intr_temp(struct brcmstb_thermal_priv *priv)226{227	u32 val;228 229	val = __raw_readl(priv->tmon_base + AVS_TMON_TEMP_INT_CODE);230	return avs_tmon_code_to_temp(priv, val);231}232 233static irqreturn_t brcmstb_tmon_irq_thread(int irq, void *data)234{235	struct brcmstb_thermal_priv *priv = data;236	int low, high, intr;237 238	low = avs_tmon_get_trip_temp(priv, TMON_TRIP_TYPE_LOW);239	high = avs_tmon_get_trip_temp(priv, TMON_TRIP_TYPE_HIGH);240	intr = avs_tmon_get_intr_temp(priv);241 242	dev_dbg(priv->dev, "low/intr/high: %d/%d/%d\n",243			low, intr, high);244 245	/* Disable high-temp until next threshold shift */246	if (intr >= high)247		avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_HIGH, 0);248	/* Disable low-temp until next threshold shift */249	if (intr <= low)250		avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_LOW, 0);251 252	/*253	 * Notify using the interrupt temperature, in case the temperature254	 * changes before it can next be read out255	 */256	thermal_zone_device_update(priv->thermal, intr);257 258	return IRQ_HANDLED;259}260 261static int brcmstb_set_trips(struct thermal_zone_device *tz, int low, int high)262{263	struct brcmstb_thermal_priv *priv = thermal_zone_device_priv(tz);264 265	dev_dbg(priv->dev, "set trips %d <--> %d\n", low, high);266 267	/*268	 * Disable low-temp if "low" is too small. As per thermal framework269	 * API, we use -INT_MAX rather than INT_MIN.270	 */271	if (low <= -INT_MAX) {272		avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_LOW, 0);273	} else {274		avs_tmon_set_trip_temp(priv, TMON_TRIP_TYPE_LOW, low);275		avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_LOW, 1);276	}277 278	/* Disable high-temp if "high" is too big. */279	if (high == INT_MAX) {280		avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_HIGH, 0);281	} else {282		avs_tmon_set_trip_temp(priv, TMON_TRIP_TYPE_HIGH, high);283		avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_HIGH, 1);284	}285 286	return 0;287}288 289static const struct thermal_zone_device_ops brcmstb_16nm_of_ops = {290	.get_temp	= brcmstb_get_temp,291};292 293static const struct brcmstb_thermal_params brcmstb_16nm_params = {294	.offset	= 457829,295	.mult	= 557,296	.of_ops	= &brcmstb_16nm_of_ops,297};298 299static const struct thermal_zone_device_ops brcmstb_28nm_of_ops = {300	.get_temp	= brcmstb_get_temp,301	.set_trips	= brcmstb_set_trips,302};303 304static const struct brcmstb_thermal_params brcmstb_28nm_params = {305	.offset	= 410040,306	.mult	= 487,307	.of_ops	= &brcmstb_28nm_of_ops,308};309 310static const struct of_device_id brcmstb_thermal_id_table[] = {311	{ .compatible = "brcm,avs-tmon-bcm7216", .data = &brcmstb_16nm_params },312	{ .compatible = "brcm,avs-tmon", .data = &brcmstb_28nm_params },313	{},314};315MODULE_DEVICE_TABLE(of, brcmstb_thermal_id_table);316 317static int brcmstb_thermal_probe(struct platform_device *pdev)318{319	const struct thermal_zone_device_ops *of_ops;320	struct thermal_zone_device *thermal;321	struct brcmstb_thermal_priv *priv;322	int irq, ret;323 324	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);325	if (!priv)326		return -ENOMEM;327 328	priv->temp_params = of_device_get_match_data(&pdev->dev);329	if (!priv->temp_params)330		return -EINVAL;331 332	priv->tmon_base = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);333	if (IS_ERR(priv->tmon_base))334		return PTR_ERR(priv->tmon_base);335 336	priv->dev = &pdev->dev;337	of_ops = priv->temp_params->of_ops;338 339	thermal = devm_thermal_of_zone_register(&pdev->dev, 0, priv,340						of_ops);341	if (IS_ERR(thermal))342		return dev_err_probe(&pdev->dev, PTR_ERR(thermal),343					"could not register sensor\n");344 345	priv->thermal = thermal;346 347	irq = platform_get_irq_optional(pdev, 0);348	if (irq >= 0) {349		ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,350						brcmstb_tmon_irq_thread,351						IRQF_ONESHOT,352						DRV_NAME, priv);353		if (ret < 0)354			return dev_err_probe(&pdev->dev, ret,355						"could not request IRQ\n");356	}357 358	dev_info(&pdev->dev, "registered AVS TMON of-sensor driver\n");359 360	return 0;361}362 363static struct platform_driver brcmstb_thermal_driver = {364	.probe = brcmstb_thermal_probe,365	.driver = {366		.name = DRV_NAME,367		.of_match_table = brcmstb_thermal_id_table,368	},369};370module_platform_driver(brcmstb_thermal_driver);371 372MODULE_LICENSE("GPL v2");373MODULE_AUTHOR("Brian Norris");374MODULE_DESCRIPTION("Broadcom STB AVS TMON thermal driver");375