brintos

brintos / linux-shallow public Read only

0
0
Text · 9.5 KiB · 0325b71 Raw
385 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * uniphier_thermal.c - Socionext UniPhier thermal driver4 * Copyright 2014      Panasonic Corporation5 * Copyright 2016-2017 Socionext Inc.6 * Author:7 *	Kunihiko Hayashi <hayashi.kunihiko@socionext.com>8 */9 10#include <linux/bitops.h>11#include <linux/interrupt.h>12#include <linux/mfd/syscon.h>13#include <linux/module.h>14#include <linux/of.h>15#include <linux/platform_device.h>16#include <linux/regmap.h>17#include <linux/thermal.h>18 19/*20 * block registers21 * addresses are the offset from .block_base22 */23#define PVTCTLEN			0x000024#define PVTCTLEN_EN			BIT(0)25 26#define PVTCTLMODE			0x000427#define PVTCTLMODE_MASK			0xf28#define PVTCTLMODE_TEMPMON		0x529 30#define EMONREPEAT			0x004031#define EMONREPEAT_ENDLESS		BIT(24)32#define EMONREPEAT_PERIOD		GENMASK(3, 0)33#define EMONREPEAT_PERIOD_1000000	0x934 35/*36 * common registers37 * addresses are the offset from .map_base38 */39#define PVTCTLSEL			0x090040#define PVTCTLSEL_MASK			GENMASK(2, 0)41#define PVTCTLSEL_MONITOR		042 43#define SETALERT0			0x091044#define SETALERT1			0x091445#define SETALERT2			0x091846#define SETALERT_TEMP_OVF		(GENMASK(7, 0) << 16)47#define SETALERT_TEMP_OVF_VALUE(val)	(((val) & GENMASK(7, 0)) << 16)48#define SETALERT_EN			BIT(0)49 50#define PMALERTINTCTL			0x092051#define PMALERTINTCTL_CLR(ch)		BIT(4 * (ch) + 2)52#define PMALERTINTCTL_SET(ch)		BIT(4 * (ch) + 1)53#define PMALERTINTCTL_EN(ch)		BIT(4 * (ch) + 0)54#define PMALERTINTCTL_MASK		(GENMASK(10, 8) | GENMASK(6, 4) | \55					 GENMASK(2, 0))56 57#define TMOD				0x092858#define TMOD_WIDTH			959 60#define TMODCOEF			0x0e5c61 62#define TMODSETUP0_EN			BIT(30)63#define TMODSETUP0_VAL(val)		(((val) & GENMASK(13, 0)) << 16)64#define TMODSETUP1_EN			BIT(15)65#define TMODSETUP1_VAL(val)		((val) & GENMASK(14, 0))66 67/* SoC critical temperature */68#define CRITICAL_TEMP_LIMIT		(120 * 1000)69 70/* Max # of alert channels */71#define ALERT_CH_NUM			372 73/* SoC specific thermal sensor data */74struct uniphier_tm_soc_data {75	u32 map_base;76	u32 block_base;77	u32 tmod_setup_addr;78};79 80struct uniphier_tm_dev {81	struct regmap *regmap;82	struct device *dev;83	bool alert_en[ALERT_CH_NUM];84	struct thermal_zone_device *tz_dev;85	const struct uniphier_tm_soc_data *data;86};87 88static int uniphier_tm_initialize_sensor(struct uniphier_tm_dev *tdev)89{90	struct regmap *map = tdev->regmap;91	u32 val;92	u32 tmod_calib[2];93	int ret;94 95	/* stop PVT */96	regmap_write_bits(map, tdev->data->block_base + PVTCTLEN,97			  PVTCTLEN_EN, 0);98 99	/*100	 * Since SoC has a calibrated value that was set in advance,101	 * TMODCOEF shows non-zero and PVT refers the value internally.102	 *103	 * If TMODCOEF shows zero, the boards don't have the calibrated104	 * value, and the driver has to set default value from DT.105	 */106	ret = regmap_read(map, tdev->data->map_base + TMODCOEF, &val);107	if (ret)108		return ret;109	if (!val) {110		/* look for the default values in DT */111		ret = of_property_read_u32_array(tdev->dev->of_node,112						 "socionext,tmod-calibration",113						 tmod_calib,114						 ARRAY_SIZE(tmod_calib));115		if (ret)116			return ret;117 118		regmap_write(map, tdev->data->tmod_setup_addr,119			TMODSETUP0_EN | TMODSETUP0_VAL(tmod_calib[0]) |120			TMODSETUP1_EN | TMODSETUP1_VAL(tmod_calib[1]));121	}122 123	/* select temperature mode */124	regmap_write_bits(map, tdev->data->block_base + PVTCTLMODE,125			  PVTCTLMODE_MASK, PVTCTLMODE_TEMPMON);126 127	/* set monitoring period */128	regmap_write_bits(map, tdev->data->block_base + EMONREPEAT,129			  EMONREPEAT_ENDLESS | EMONREPEAT_PERIOD,130			  EMONREPEAT_ENDLESS | EMONREPEAT_PERIOD_1000000);131 132	/* set monitor mode */133	regmap_write_bits(map, tdev->data->map_base + PVTCTLSEL,134			  PVTCTLSEL_MASK, PVTCTLSEL_MONITOR);135 136	return 0;137}138 139static void uniphier_tm_set_alert(struct uniphier_tm_dev *tdev, u32 ch,140				  u32 temp)141{142	struct regmap *map = tdev->regmap;143 144	/* set alert temperature */145	regmap_write_bits(map, tdev->data->map_base + SETALERT0 + (ch << 2),146			  SETALERT_EN | SETALERT_TEMP_OVF,147			  SETALERT_EN |148			  SETALERT_TEMP_OVF_VALUE(temp / 1000));149}150 151static void uniphier_tm_enable_sensor(struct uniphier_tm_dev *tdev)152{153	struct regmap *map = tdev->regmap;154	int i;155	u32 bits = 0;156 157	for (i = 0; i < ALERT_CH_NUM; i++)158		if (tdev->alert_en[i])159			bits |= PMALERTINTCTL_EN(i);160 161	/* enable alert interrupt */162	regmap_write_bits(map, tdev->data->map_base + PMALERTINTCTL,163			  PMALERTINTCTL_MASK, bits);164 165	/* start PVT */166	regmap_write_bits(map, tdev->data->block_base + PVTCTLEN,167			  PVTCTLEN_EN, PVTCTLEN_EN);168 169	usleep_range(700, 1500);	/* The spec note says at least 700us */170}171 172static void uniphier_tm_disable_sensor(struct uniphier_tm_dev *tdev)173{174	struct regmap *map = tdev->regmap;175 176	/* disable alert interrupt */177	regmap_write_bits(map, tdev->data->map_base + PMALERTINTCTL,178			  PMALERTINTCTL_MASK, 0);179 180	/* stop PVT */181	regmap_write_bits(map, tdev->data->block_base + PVTCTLEN,182			  PVTCTLEN_EN, 0);183 184	usleep_range(1000, 2000);	/* The spec note says at least 1ms */185}186 187static int uniphier_tm_get_temp(struct thermal_zone_device *tz, int *out_temp)188{189	struct uniphier_tm_dev *tdev = thermal_zone_device_priv(tz);190	struct regmap *map = tdev->regmap;191	int ret;192	u32 temp;193 194	ret = regmap_read(map, tdev->data->map_base + TMOD, &temp);195	if (ret)196		return ret;197 198	/* MSB of the TMOD field is a sign bit */199	*out_temp = sign_extend32(temp, TMOD_WIDTH - 1) * 1000;200 201	return 0;202}203 204static const struct thermal_zone_device_ops uniphier_of_thermal_ops = {205	.get_temp = uniphier_tm_get_temp,206};207 208static void uniphier_tm_irq_clear(struct uniphier_tm_dev *tdev)209{210	u32 mask = 0, bits = 0;211	int i;212 213	for (i = 0; i < ALERT_CH_NUM; i++) {214		mask |= (PMALERTINTCTL_CLR(i) | PMALERTINTCTL_SET(i));215		bits |= PMALERTINTCTL_CLR(i);216	}217 218	/* clear alert interrupt */219	regmap_write_bits(tdev->regmap,220			  tdev->data->map_base + PMALERTINTCTL, mask, bits);221}222 223static irqreturn_t uniphier_tm_alarm_irq(int irq, void *_tdev)224{225	struct uniphier_tm_dev *tdev = _tdev;226 227	disable_irq_nosync(irq);228	uniphier_tm_irq_clear(tdev);229 230	return IRQ_WAKE_THREAD;231}232 233static irqreturn_t uniphier_tm_alarm_irq_thread(int irq, void *_tdev)234{235	struct uniphier_tm_dev *tdev = _tdev;236 237	thermal_zone_device_update(tdev->tz_dev, THERMAL_EVENT_UNSPECIFIED);238 239	return IRQ_HANDLED;240}241 242struct trip_walk_data {243	struct uniphier_tm_dev *tdev;244	int crit_temp;245	int index;246};247 248static int uniphier_tm_trip_walk_cb(struct thermal_trip *trip, void *arg)249{250	struct trip_walk_data *twd = arg;251 252	if (trip->type == THERMAL_TRIP_CRITICAL &&253	    trip->temperature < twd->crit_temp)254		twd->crit_temp = trip->temperature;255 256	uniphier_tm_set_alert(twd->tdev, twd->index, trip->temperature);257	twd->tdev->alert_en[twd->index++] = true;258 259	return 0;260}261 262static int uniphier_tm_probe(struct platform_device *pdev)263{264	struct trip_walk_data twd = { .crit_temp = INT_MAX, .index = 0 };265	struct device *dev = &pdev->dev;266	struct regmap *regmap;267	struct device_node *parent;268	struct uniphier_tm_dev *tdev;269	int ret, irq;270 271	tdev = devm_kzalloc(dev, sizeof(*tdev), GFP_KERNEL);272	if (!tdev)273		return -ENOMEM;274	tdev->dev = dev;275 276	tdev->data = of_device_get_match_data(dev);277	if (WARN_ON(!tdev->data))278		return -EINVAL;279 280	irq = platform_get_irq(pdev, 0);281	if (irq < 0)282		return irq;283 284	/* get regmap from syscon node */285	parent = of_get_parent(dev->of_node); /* parent should be syscon node */286	regmap = syscon_node_to_regmap(parent);287	of_node_put(parent);288	if (IS_ERR(regmap)) {289		dev_err(dev, "failed to get regmap (error %ld)\n",290			PTR_ERR(regmap));291		return PTR_ERR(regmap);292	}293	tdev->regmap = regmap;294 295	ret = uniphier_tm_initialize_sensor(tdev);296	if (ret) {297		dev_err(dev, "failed to initialize sensor\n");298		return ret;299	}300 301	ret = devm_request_threaded_irq(dev, irq, uniphier_tm_alarm_irq,302					uniphier_tm_alarm_irq_thread,303					0, "thermal", tdev);304	if (ret)305		return ret;306 307	platform_set_drvdata(pdev, tdev);308 309	tdev->tz_dev = devm_thermal_of_zone_register(dev, 0, tdev,310						     &uniphier_of_thermal_ops);311	if (IS_ERR(tdev->tz_dev)) {312		dev_err(dev, "failed to register sensor device\n");313		return PTR_ERR(tdev->tz_dev);314	}315 316	/* set alert temperatures */317	twd.tdev = tdev;318	thermal_zone_for_each_trip(tdev->tz_dev, uniphier_tm_trip_walk_cb, &twd);319 320	if (twd.crit_temp > CRITICAL_TEMP_LIMIT) {321		dev_err(dev, "critical trip is over limit(>%d), or not set\n",322			CRITICAL_TEMP_LIMIT);323		return -EINVAL;324	}325 326	uniphier_tm_enable_sensor(tdev);327 328	return 0;329}330 331static void uniphier_tm_remove(struct platform_device *pdev)332{333	struct uniphier_tm_dev *tdev = platform_get_drvdata(pdev);334 335	/* disable sensor */336	uniphier_tm_disable_sensor(tdev);337}338 339static const struct uniphier_tm_soc_data uniphier_pxs2_tm_data = {340	.map_base        = 0xe000,341	.block_base      = 0xe000,342	.tmod_setup_addr = 0xe904,343};344 345static const struct uniphier_tm_soc_data uniphier_ld20_tm_data = {346	.map_base        = 0xe000,347	.block_base      = 0xe800,348	.tmod_setup_addr = 0xe938,349};350 351static const struct of_device_id uniphier_tm_dt_ids[] = {352	{353		.compatible = "socionext,uniphier-pxs2-thermal",354		.data       = &uniphier_pxs2_tm_data,355	},356	{357		.compatible = "socionext,uniphier-ld20-thermal",358		.data       = &uniphier_ld20_tm_data,359	},360	{361		.compatible = "socionext,uniphier-pxs3-thermal",362		.data       = &uniphier_ld20_tm_data,363	},364	{365		.compatible = "socionext,uniphier-nx1-thermal",366		.data       = &uniphier_ld20_tm_data,367	},368	{ /* sentinel */ }369};370MODULE_DEVICE_TABLE(of, uniphier_tm_dt_ids);371 372static struct platform_driver uniphier_tm_driver = {373	.probe = uniphier_tm_probe,374	.remove_new = uniphier_tm_remove,375	.driver = {376		.name = "uniphier-thermal",377		.of_match_table = uniphier_tm_dt_ids,378	},379};380module_platform_driver(uniphier_tm_driver);381 382MODULE_AUTHOR("Kunihiko Hayashi <hayashi.kunihiko@socionext.com>");383MODULE_DESCRIPTION("UniPhier thermal driver");384MODULE_LICENSE("GPL v2");385