18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Marvell EBU Armada SoCs thermal sensor driver
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2013 Marvell
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci#include <linux/device.h>
88c2ecf20Sopenharmony_ci#include <linux/err.h>
98c2ecf20Sopenharmony_ci#include <linux/io.h>
108c2ecf20Sopenharmony_ci#include <linux/kernel.h>
118c2ecf20Sopenharmony_ci#include <linux/of.h>
128c2ecf20Sopenharmony_ci#include <linux/module.h>
138c2ecf20Sopenharmony_ci#include <linux/delay.h>
148c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
158c2ecf20Sopenharmony_ci#include <linux/of_device.h>
168c2ecf20Sopenharmony_ci#include <linux/thermal.h>
178c2ecf20Sopenharmony_ci#include <linux/iopoll.h>
188c2ecf20Sopenharmony_ci#include <linux/mfd/syscon.h>
198c2ecf20Sopenharmony_ci#include <linux/regmap.h>
208c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_ci#include "thermal_core.h"
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci/* Thermal Manager Control and Status Register */
258c2ecf20Sopenharmony_ci#define PMU_TDC0_SW_RST_MASK		(0x1 << 1)
268c2ecf20Sopenharmony_ci#define PMU_TM_DISABLE_OFFS		0
278c2ecf20Sopenharmony_ci#define PMU_TM_DISABLE_MASK		(0x1 << PMU_TM_DISABLE_OFFS)
288c2ecf20Sopenharmony_ci#define PMU_TDC0_REF_CAL_CNT_OFFS	11
298c2ecf20Sopenharmony_ci#define PMU_TDC0_REF_CAL_CNT_MASK	(0x1ff << PMU_TDC0_REF_CAL_CNT_OFFS)
308c2ecf20Sopenharmony_ci#define PMU_TDC0_OTF_CAL_MASK		(0x1 << 30)
318c2ecf20Sopenharmony_ci#define PMU_TDC0_START_CAL_MASK		(0x1 << 25)
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci#define A375_UNIT_CONTROL_SHIFT		27
348c2ecf20Sopenharmony_ci#define A375_UNIT_CONTROL_MASK		0x7
358c2ecf20Sopenharmony_ci#define A375_READOUT_INVERT		BIT(15)
368c2ecf20Sopenharmony_ci#define A375_HW_RESETn			BIT(8)
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_ci/* Errata fields */
398c2ecf20Sopenharmony_ci#define CONTROL0_TSEN_TC_TRIM_MASK	0x7
408c2ecf20Sopenharmony_ci#define CONTROL0_TSEN_TC_TRIM_VAL	0x3
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci#define CONTROL0_TSEN_START		BIT(0)
438c2ecf20Sopenharmony_ci#define CONTROL0_TSEN_RESET		BIT(1)
448c2ecf20Sopenharmony_ci#define CONTROL0_TSEN_ENABLE		BIT(2)
458c2ecf20Sopenharmony_ci#define CONTROL0_TSEN_AVG_BYPASS	BIT(6)
468c2ecf20Sopenharmony_ci#define CONTROL0_TSEN_CHAN_SHIFT	13
478c2ecf20Sopenharmony_ci#define CONTROL0_TSEN_CHAN_MASK		0xF
488c2ecf20Sopenharmony_ci#define CONTROL0_TSEN_OSR_SHIFT		24
498c2ecf20Sopenharmony_ci#define CONTROL0_TSEN_OSR_MAX		0x3
508c2ecf20Sopenharmony_ci#define CONTROL0_TSEN_MODE_SHIFT	30
518c2ecf20Sopenharmony_ci#define CONTROL0_TSEN_MODE_EXTERNAL	0x2
528c2ecf20Sopenharmony_ci#define CONTROL0_TSEN_MODE_MASK		0x3
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci#define CONTROL1_TSEN_AVG_MASK		0x7
558c2ecf20Sopenharmony_ci#define CONTROL1_EXT_TSEN_SW_RESET	BIT(7)
568c2ecf20Sopenharmony_ci#define CONTROL1_EXT_TSEN_HW_RESETn	BIT(8)
578c2ecf20Sopenharmony_ci#define CONTROL1_TSEN_INT_EN		BIT(25)
588c2ecf20Sopenharmony_ci#define CONTROL1_TSEN_SELECT_OFF	21
598c2ecf20Sopenharmony_ci#define CONTROL1_TSEN_SELECT_MASK	0x3
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci#define STATUS_POLL_PERIOD_US		1000
628c2ecf20Sopenharmony_ci#define STATUS_POLL_TIMEOUT_US		100000
638c2ecf20Sopenharmony_ci#define OVERHEAT_INT_POLL_DELAY_MS	1000
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_cistruct armada_thermal_data;
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci/* Marvell EBU Thermal Sensor Dev Structure */
688c2ecf20Sopenharmony_cistruct armada_thermal_priv {
698c2ecf20Sopenharmony_ci	struct device *dev;
708c2ecf20Sopenharmony_ci	struct regmap *syscon;
718c2ecf20Sopenharmony_ci	char zone_name[THERMAL_NAME_LENGTH];
728c2ecf20Sopenharmony_ci	/* serialize temperature reads/updates */
738c2ecf20Sopenharmony_ci	struct mutex update_lock;
748c2ecf20Sopenharmony_ci	struct armada_thermal_data *data;
758c2ecf20Sopenharmony_ci	struct thermal_zone_device *overheat_sensor;
768c2ecf20Sopenharmony_ci	int interrupt_source;
778c2ecf20Sopenharmony_ci	int current_channel;
788c2ecf20Sopenharmony_ci	long current_threshold;
798c2ecf20Sopenharmony_ci	long current_hysteresis;
808c2ecf20Sopenharmony_ci};
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_cistruct armada_thermal_data {
838c2ecf20Sopenharmony_ci	/* Initialize the thermal IC */
848c2ecf20Sopenharmony_ci	void (*init)(struct platform_device *pdev,
858c2ecf20Sopenharmony_ci		     struct armada_thermal_priv *priv);
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci	/* Formula coeficients: temp = (b - m * reg) / div */
888c2ecf20Sopenharmony_ci	s64 coef_b;
898c2ecf20Sopenharmony_ci	s64 coef_m;
908c2ecf20Sopenharmony_ci	u32 coef_div;
918c2ecf20Sopenharmony_ci	bool inverted;
928c2ecf20Sopenharmony_ci	bool signed_sample;
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci	/* Register shift and mask to access the sensor temperature */
958c2ecf20Sopenharmony_ci	unsigned int temp_shift;
968c2ecf20Sopenharmony_ci	unsigned int temp_mask;
978c2ecf20Sopenharmony_ci	unsigned int thresh_shift;
988c2ecf20Sopenharmony_ci	unsigned int hyst_shift;
998c2ecf20Sopenharmony_ci	unsigned int hyst_mask;
1008c2ecf20Sopenharmony_ci	u32 is_valid_bit;
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci	/* Syscon access */
1038c2ecf20Sopenharmony_ci	unsigned int syscon_control0_off;
1048c2ecf20Sopenharmony_ci	unsigned int syscon_control1_off;
1058c2ecf20Sopenharmony_ci	unsigned int syscon_status_off;
1068c2ecf20Sopenharmony_ci	unsigned int dfx_irq_cause_off;
1078c2ecf20Sopenharmony_ci	unsigned int dfx_irq_mask_off;
1088c2ecf20Sopenharmony_ci	unsigned int dfx_overheat_irq;
1098c2ecf20Sopenharmony_ci	unsigned int dfx_server_irq_mask_off;
1108c2ecf20Sopenharmony_ci	unsigned int dfx_server_irq_en;
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci	/* One sensor is in the thermal IC, the others are in the CPUs if any */
1138c2ecf20Sopenharmony_ci	unsigned int cpu_nr;
1148c2ecf20Sopenharmony_ci};
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_cistruct armada_drvdata {
1178c2ecf20Sopenharmony_ci	enum drvtype {
1188c2ecf20Sopenharmony_ci		LEGACY,
1198c2ecf20Sopenharmony_ci		SYSCON
1208c2ecf20Sopenharmony_ci	} type;
1218c2ecf20Sopenharmony_ci	union {
1228c2ecf20Sopenharmony_ci		struct armada_thermal_priv *priv;
1238c2ecf20Sopenharmony_ci		struct thermal_zone_device *tz;
1248c2ecf20Sopenharmony_ci	} data;
1258c2ecf20Sopenharmony_ci};
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci/*
1288c2ecf20Sopenharmony_ci * struct armada_thermal_sensor - hold the information of one thermal sensor
1298c2ecf20Sopenharmony_ci * @thermal: pointer to the local private structure
1308c2ecf20Sopenharmony_ci * @tzd: pointer to the thermal zone device
1318c2ecf20Sopenharmony_ci * @id: identifier of the thermal sensor
1328c2ecf20Sopenharmony_ci */
1338c2ecf20Sopenharmony_cistruct armada_thermal_sensor {
1348c2ecf20Sopenharmony_ci	struct armada_thermal_priv *priv;
1358c2ecf20Sopenharmony_ci	int id;
1368c2ecf20Sopenharmony_ci};
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_cistatic void armadaxp_init(struct platform_device *pdev,
1398c2ecf20Sopenharmony_ci			  struct armada_thermal_priv *priv)
1408c2ecf20Sopenharmony_ci{
1418c2ecf20Sopenharmony_ci	struct armada_thermal_data *data = priv->data;
1428c2ecf20Sopenharmony_ci	u32 reg;
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci	regmap_read(priv->syscon, data->syscon_control1_off, &reg);
1458c2ecf20Sopenharmony_ci	reg |= PMU_TDC0_OTF_CAL_MASK;
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci	/* Reference calibration value */
1488c2ecf20Sopenharmony_ci	reg &= ~PMU_TDC0_REF_CAL_CNT_MASK;
1498c2ecf20Sopenharmony_ci	reg |= (0xf1 << PMU_TDC0_REF_CAL_CNT_OFFS);
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ci	/* Reset the sensor */
1528c2ecf20Sopenharmony_ci	reg |= PMU_TDC0_SW_RST_MASK;
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci	regmap_write(priv->syscon, data->syscon_control1_off, reg);
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ci	reg &= ~PMU_TDC0_SW_RST_MASK;
1578c2ecf20Sopenharmony_ci	regmap_write(priv->syscon, data->syscon_control1_off, reg);
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_ci	/* Enable the sensor */
1608c2ecf20Sopenharmony_ci	regmap_read(priv->syscon, data->syscon_status_off, &reg);
1618c2ecf20Sopenharmony_ci	reg &= ~PMU_TM_DISABLE_MASK;
1628c2ecf20Sopenharmony_ci	regmap_write(priv->syscon, data->syscon_status_off, reg);
1638c2ecf20Sopenharmony_ci}
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_cistatic void armada370_init(struct platform_device *pdev,
1668c2ecf20Sopenharmony_ci			   struct armada_thermal_priv *priv)
1678c2ecf20Sopenharmony_ci{
1688c2ecf20Sopenharmony_ci	struct armada_thermal_data *data = priv->data;
1698c2ecf20Sopenharmony_ci	u32 reg;
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_ci	regmap_read(priv->syscon, data->syscon_control1_off, &reg);
1728c2ecf20Sopenharmony_ci	reg |= PMU_TDC0_OTF_CAL_MASK;
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci	/* Reference calibration value */
1758c2ecf20Sopenharmony_ci	reg &= ~PMU_TDC0_REF_CAL_CNT_MASK;
1768c2ecf20Sopenharmony_ci	reg |= (0xf1 << PMU_TDC0_REF_CAL_CNT_OFFS);
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ci	/* Reset the sensor */
1798c2ecf20Sopenharmony_ci	reg &= ~PMU_TDC0_START_CAL_MASK;
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_ci	regmap_write(priv->syscon, data->syscon_control1_off, reg);
1828c2ecf20Sopenharmony_ci
1838c2ecf20Sopenharmony_ci	msleep(10);
1848c2ecf20Sopenharmony_ci}
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_cistatic void armada375_init(struct platform_device *pdev,
1878c2ecf20Sopenharmony_ci			   struct armada_thermal_priv *priv)
1888c2ecf20Sopenharmony_ci{
1898c2ecf20Sopenharmony_ci	struct armada_thermal_data *data = priv->data;
1908c2ecf20Sopenharmony_ci	u32 reg;
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ci	regmap_read(priv->syscon, data->syscon_control1_off, &reg);
1938c2ecf20Sopenharmony_ci	reg &= ~(A375_UNIT_CONTROL_MASK << A375_UNIT_CONTROL_SHIFT);
1948c2ecf20Sopenharmony_ci	reg &= ~A375_READOUT_INVERT;
1958c2ecf20Sopenharmony_ci	reg &= ~A375_HW_RESETn;
1968c2ecf20Sopenharmony_ci	regmap_write(priv->syscon, data->syscon_control1_off, reg);
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ci	msleep(20);
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_ci	reg |= A375_HW_RESETn;
2018c2ecf20Sopenharmony_ci	regmap_write(priv->syscon, data->syscon_control1_off, reg);
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ci	msleep(50);
2048c2ecf20Sopenharmony_ci}
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_cistatic int armada_wait_sensor_validity(struct armada_thermal_priv *priv)
2078c2ecf20Sopenharmony_ci{
2088c2ecf20Sopenharmony_ci	u32 reg;
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_ci	return regmap_read_poll_timeout(priv->syscon,
2118c2ecf20Sopenharmony_ci					priv->data->syscon_status_off, reg,
2128c2ecf20Sopenharmony_ci					reg & priv->data->is_valid_bit,
2138c2ecf20Sopenharmony_ci					STATUS_POLL_PERIOD_US,
2148c2ecf20Sopenharmony_ci					STATUS_POLL_TIMEOUT_US);
2158c2ecf20Sopenharmony_ci}
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_cistatic void armada380_init(struct platform_device *pdev,
2188c2ecf20Sopenharmony_ci			   struct armada_thermal_priv *priv)
2198c2ecf20Sopenharmony_ci{
2208c2ecf20Sopenharmony_ci	struct armada_thermal_data *data = priv->data;
2218c2ecf20Sopenharmony_ci	u32 reg;
2228c2ecf20Sopenharmony_ci
2238c2ecf20Sopenharmony_ci	/* Disable the HW/SW reset */
2248c2ecf20Sopenharmony_ci	regmap_read(priv->syscon, data->syscon_control1_off, &reg);
2258c2ecf20Sopenharmony_ci	reg |= CONTROL1_EXT_TSEN_HW_RESETn;
2268c2ecf20Sopenharmony_ci	reg &= ~CONTROL1_EXT_TSEN_SW_RESET;
2278c2ecf20Sopenharmony_ci	regmap_write(priv->syscon, data->syscon_control1_off, reg);
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_ci	/* Set Tsen Tc Trim to correct default value (errata #132698) */
2308c2ecf20Sopenharmony_ci	regmap_read(priv->syscon, data->syscon_control0_off, &reg);
2318c2ecf20Sopenharmony_ci	reg &= ~CONTROL0_TSEN_TC_TRIM_MASK;
2328c2ecf20Sopenharmony_ci	reg |= CONTROL0_TSEN_TC_TRIM_VAL;
2338c2ecf20Sopenharmony_ci	regmap_write(priv->syscon, data->syscon_control0_off, reg);
2348c2ecf20Sopenharmony_ci}
2358c2ecf20Sopenharmony_ci
2368c2ecf20Sopenharmony_cistatic void armada_ap806_init(struct platform_device *pdev,
2378c2ecf20Sopenharmony_ci			      struct armada_thermal_priv *priv)
2388c2ecf20Sopenharmony_ci{
2398c2ecf20Sopenharmony_ci	struct armada_thermal_data *data = priv->data;
2408c2ecf20Sopenharmony_ci	u32 reg;
2418c2ecf20Sopenharmony_ci
2428c2ecf20Sopenharmony_ci	regmap_read(priv->syscon, data->syscon_control0_off, &reg);
2438c2ecf20Sopenharmony_ci	reg &= ~CONTROL0_TSEN_RESET;
2448c2ecf20Sopenharmony_ci	reg |= CONTROL0_TSEN_START | CONTROL0_TSEN_ENABLE;
2458c2ecf20Sopenharmony_ci
2468c2ecf20Sopenharmony_ci	/* Sample every ~2ms */
2478c2ecf20Sopenharmony_ci	reg |= CONTROL0_TSEN_OSR_MAX << CONTROL0_TSEN_OSR_SHIFT;
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_ci	/* Enable average (2 samples by default) */
2508c2ecf20Sopenharmony_ci	reg &= ~CONTROL0_TSEN_AVG_BYPASS;
2518c2ecf20Sopenharmony_ci
2528c2ecf20Sopenharmony_ci	regmap_write(priv->syscon, data->syscon_control0_off, reg);
2538c2ecf20Sopenharmony_ci}
2548c2ecf20Sopenharmony_ci
2558c2ecf20Sopenharmony_cistatic void armada_cp110_init(struct platform_device *pdev,
2568c2ecf20Sopenharmony_ci			      struct armada_thermal_priv *priv)
2578c2ecf20Sopenharmony_ci{
2588c2ecf20Sopenharmony_ci	struct armada_thermal_data *data = priv->data;
2598c2ecf20Sopenharmony_ci	u32 reg;
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_ci	armada380_init(pdev, priv);
2628c2ecf20Sopenharmony_ci
2638c2ecf20Sopenharmony_ci	/* Sample every ~2ms */
2648c2ecf20Sopenharmony_ci	regmap_read(priv->syscon, data->syscon_control0_off, &reg);
2658c2ecf20Sopenharmony_ci	reg |= CONTROL0_TSEN_OSR_MAX << CONTROL0_TSEN_OSR_SHIFT;
2668c2ecf20Sopenharmony_ci	regmap_write(priv->syscon, data->syscon_control0_off, reg);
2678c2ecf20Sopenharmony_ci
2688c2ecf20Sopenharmony_ci	/* Average the output value over 2^1 = 2 samples */
2698c2ecf20Sopenharmony_ci	regmap_read(priv->syscon, data->syscon_control1_off, &reg);
2708c2ecf20Sopenharmony_ci	reg &= ~CONTROL1_TSEN_AVG_MASK;
2718c2ecf20Sopenharmony_ci	reg |= 1;
2728c2ecf20Sopenharmony_ci	regmap_write(priv->syscon, data->syscon_control1_off, reg);
2738c2ecf20Sopenharmony_ci}
2748c2ecf20Sopenharmony_ci
2758c2ecf20Sopenharmony_cistatic bool armada_is_valid(struct armada_thermal_priv *priv)
2768c2ecf20Sopenharmony_ci{
2778c2ecf20Sopenharmony_ci	u32 reg;
2788c2ecf20Sopenharmony_ci
2798c2ecf20Sopenharmony_ci	if (!priv->data->is_valid_bit)
2808c2ecf20Sopenharmony_ci		return true;
2818c2ecf20Sopenharmony_ci
2828c2ecf20Sopenharmony_ci	regmap_read(priv->syscon, priv->data->syscon_status_off, &reg);
2838c2ecf20Sopenharmony_ci
2848c2ecf20Sopenharmony_ci	return reg & priv->data->is_valid_bit;
2858c2ecf20Sopenharmony_ci}
2868c2ecf20Sopenharmony_ci
2878c2ecf20Sopenharmony_cistatic void armada_enable_overheat_interrupt(struct armada_thermal_priv *priv)
2888c2ecf20Sopenharmony_ci{
2898c2ecf20Sopenharmony_ci	struct armada_thermal_data *data = priv->data;
2908c2ecf20Sopenharmony_ci	u32 reg;
2918c2ecf20Sopenharmony_ci
2928c2ecf20Sopenharmony_ci	/* Clear DFX temperature IRQ cause */
2938c2ecf20Sopenharmony_ci	regmap_read(priv->syscon, data->dfx_irq_cause_off, &reg);
2948c2ecf20Sopenharmony_ci
2958c2ecf20Sopenharmony_ci	/* Enable DFX Temperature IRQ */
2968c2ecf20Sopenharmony_ci	regmap_read(priv->syscon, data->dfx_irq_mask_off, &reg);
2978c2ecf20Sopenharmony_ci	reg |= data->dfx_overheat_irq;
2988c2ecf20Sopenharmony_ci	regmap_write(priv->syscon, data->dfx_irq_mask_off, reg);
2998c2ecf20Sopenharmony_ci
3008c2ecf20Sopenharmony_ci	/* Enable DFX server IRQ */
3018c2ecf20Sopenharmony_ci	regmap_read(priv->syscon, data->dfx_server_irq_mask_off, &reg);
3028c2ecf20Sopenharmony_ci	reg |= data->dfx_server_irq_en;
3038c2ecf20Sopenharmony_ci	regmap_write(priv->syscon, data->dfx_server_irq_mask_off, reg);
3048c2ecf20Sopenharmony_ci
3058c2ecf20Sopenharmony_ci	/* Enable overheat interrupt */
3068c2ecf20Sopenharmony_ci	regmap_read(priv->syscon, data->syscon_control1_off, &reg);
3078c2ecf20Sopenharmony_ci	reg |= CONTROL1_TSEN_INT_EN;
3088c2ecf20Sopenharmony_ci	regmap_write(priv->syscon, data->syscon_control1_off, reg);
3098c2ecf20Sopenharmony_ci}
3108c2ecf20Sopenharmony_ci
3118c2ecf20Sopenharmony_cistatic void __maybe_unused
3128c2ecf20Sopenharmony_ciarmada_disable_overheat_interrupt(struct armada_thermal_priv *priv)
3138c2ecf20Sopenharmony_ci{
3148c2ecf20Sopenharmony_ci	struct armada_thermal_data *data = priv->data;
3158c2ecf20Sopenharmony_ci	u32 reg;
3168c2ecf20Sopenharmony_ci
3178c2ecf20Sopenharmony_ci	regmap_read(priv->syscon, data->syscon_control1_off, &reg);
3188c2ecf20Sopenharmony_ci	reg &= ~CONTROL1_TSEN_INT_EN;
3198c2ecf20Sopenharmony_ci	regmap_write(priv->syscon, data->syscon_control1_off, reg);
3208c2ecf20Sopenharmony_ci}
3218c2ecf20Sopenharmony_ci
3228c2ecf20Sopenharmony_ci/* There is currently no board with more than one sensor per channel */
3238c2ecf20Sopenharmony_cistatic int armada_select_channel(struct armada_thermal_priv *priv, int channel)
3248c2ecf20Sopenharmony_ci{
3258c2ecf20Sopenharmony_ci	struct armada_thermal_data *data = priv->data;
3268c2ecf20Sopenharmony_ci	u32 ctrl0;
3278c2ecf20Sopenharmony_ci
3288c2ecf20Sopenharmony_ci	if (channel < 0 || channel > priv->data->cpu_nr)
3298c2ecf20Sopenharmony_ci		return -EINVAL;
3308c2ecf20Sopenharmony_ci
3318c2ecf20Sopenharmony_ci	if (priv->current_channel == channel)
3328c2ecf20Sopenharmony_ci		return 0;
3338c2ecf20Sopenharmony_ci
3348c2ecf20Sopenharmony_ci	/* Stop the measurements */
3358c2ecf20Sopenharmony_ci	regmap_read(priv->syscon, data->syscon_control0_off, &ctrl0);
3368c2ecf20Sopenharmony_ci	ctrl0 &= ~CONTROL0_TSEN_START;
3378c2ecf20Sopenharmony_ci	regmap_write(priv->syscon, data->syscon_control0_off, ctrl0);
3388c2ecf20Sopenharmony_ci
3398c2ecf20Sopenharmony_ci	/* Reset the mode, internal sensor will be automatically selected */
3408c2ecf20Sopenharmony_ci	ctrl0 &= ~(CONTROL0_TSEN_MODE_MASK << CONTROL0_TSEN_MODE_SHIFT);
3418c2ecf20Sopenharmony_ci
3428c2ecf20Sopenharmony_ci	/* Other channels are external and should be selected accordingly */
3438c2ecf20Sopenharmony_ci	if (channel) {
3448c2ecf20Sopenharmony_ci		/* Change the mode to external */
3458c2ecf20Sopenharmony_ci		ctrl0 |= CONTROL0_TSEN_MODE_EXTERNAL <<
3468c2ecf20Sopenharmony_ci			 CONTROL0_TSEN_MODE_SHIFT;
3478c2ecf20Sopenharmony_ci		/* Select the sensor */
3488c2ecf20Sopenharmony_ci		ctrl0 &= ~(CONTROL0_TSEN_CHAN_MASK << CONTROL0_TSEN_CHAN_SHIFT);
3498c2ecf20Sopenharmony_ci		ctrl0 |= (channel - 1) << CONTROL0_TSEN_CHAN_SHIFT;
3508c2ecf20Sopenharmony_ci	}
3518c2ecf20Sopenharmony_ci
3528c2ecf20Sopenharmony_ci	/* Actually set the mode/channel */
3538c2ecf20Sopenharmony_ci	regmap_write(priv->syscon, data->syscon_control0_off, ctrl0);
3548c2ecf20Sopenharmony_ci	priv->current_channel = channel;
3558c2ecf20Sopenharmony_ci
3568c2ecf20Sopenharmony_ci	/* Re-start the measurements */
3578c2ecf20Sopenharmony_ci	ctrl0 |= CONTROL0_TSEN_START;
3588c2ecf20Sopenharmony_ci	regmap_write(priv->syscon, data->syscon_control0_off, ctrl0);
3598c2ecf20Sopenharmony_ci
3608c2ecf20Sopenharmony_ci	/*
3618c2ecf20Sopenharmony_ci	 * The IP has a latency of ~15ms, so after updating the selected source,
3628c2ecf20Sopenharmony_ci	 * we must absolutely wait for the sensor validity bit to ensure we read
3638c2ecf20Sopenharmony_ci	 * actual data.
3648c2ecf20Sopenharmony_ci	 */
3658c2ecf20Sopenharmony_ci	if (armada_wait_sensor_validity(priv)) {
3668c2ecf20Sopenharmony_ci		dev_err(priv->dev,
3678c2ecf20Sopenharmony_ci			"Temperature sensor reading not valid\n");
3688c2ecf20Sopenharmony_ci		return -EIO;
3698c2ecf20Sopenharmony_ci	}
3708c2ecf20Sopenharmony_ci
3718c2ecf20Sopenharmony_ci	return 0;
3728c2ecf20Sopenharmony_ci}
3738c2ecf20Sopenharmony_ci
3748c2ecf20Sopenharmony_cistatic int armada_read_sensor(struct armada_thermal_priv *priv, int *temp)
3758c2ecf20Sopenharmony_ci{
3768c2ecf20Sopenharmony_ci	u32 reg, div;
3778c2ecf20Sopenharmony_ci	s64 sample, b, m;
3788c2ecf20Sopenharmony_ci
3798c2ecf20Sopenharmony_ci	regmap_read(priv->syscon, priv->data->syscon_status_off, &reg);
3808c2ecf20Sopenharmony_ci	reg = (reg >> priv->data->temp_shift) & priv->data->temp_mask;
3818c2ecf20Sopenharmony_ci	if (priv->data->signed_sample)
3828c2ecf20Sopenharmony_ci		/* The most significant bit is the sign bit */
3838c2ecf20Sopenharmony_ci		sample = sign_extend32(reg, fls(priv->data->temp_mask) - 1);
3848c2ecf20Sopenharmony_ci	else
3858c2ecf20Sopenharmony_ci		sample = reg;
3868c2ecf20Sopenharmony_ci
3878c2ecf20Sopenharmony_ci	/* Get formula coeficients */
3888c2ecf20Sopenharmony_ci	b = priv->data->coef_b;
3898c2ecf20Sopenharmony_ci	m = priv->data->coef_m;
3908c2ecf20Sopenharmony_ci	div = priv->data->coef_div;
3918c2ecf20Sopenharmony_ci
3928c2ecf20Sopenharmony_ci	if (priv->data->inverted)
3938c2ecf20Sopenharmony_ci		*temp = div_s64((m * sample) - b, div);
3948c2ecf20Sopenharmony_ci	else
3958c2ecf20Sopenharmony_ci		*temp = div_s64(b - (m * sample), div);
3968c2ecf20Sopenharmony_ci
3978c2ecf20Sopenharmony_ci	return 0;
3988c2ecf20Sopenharmony_ci}
3998c2ecf20Sopenharmony_ci
4008c2ecf20Sopenharmony_cistatic int armada_get_temp_legacy(struct thermal_zone_device *thermal,
4018c2ecf20Sopenharmony_ci				  int *temp)
4028c2ecf20Sopenharmony_ci{
4038c2ecf20Sopenharmony_ci	struct armada_thermal_priv *priv = thermal->devdata;
4048c2ecf20Sopenharmony_ci	int ret;
4058c2ecf20Sopenharmony_ci
4068c2ecf20Sopenharmony_ci	/* Valid check */
4078c2ecf20Sopenharmony_ci	if (!armada_is_valid(priv)) {
4088c2ecf20Sopenharmony_ci		dev_err(priv->dev,
4098c2ecf20Sopenharmony_ci			"Temperature sensor reading not valid\n");
4108c2ecf20Sopenharmony_ci		return -EIO;
4118c2ecf20Sopenharmony_ci	}
4128c2ecf20Sopenharmony_ci
4138c2ecf20Sopenharmony_ci	/* Do the actual reading */
4148c2ecf20Sopenharmony_ci	ret = armada_read_sensor(priv, temp);
4158c2ecf20Sopenharmony_ci
4168c2ecf20Sopenharmony_ci	return ret;
4178c2ecf20Sopenharmony_ci}
4188c2ecf20Sopenharmony_ci
4198c2ecf20Sopenharmony_cistatic struct thermal_zone_device_ops legacy_ops = {
4208c2ecf20Sopenharmony_ci	.get_temp = armada_get_temp_legacy,
4218c2ecf20Sopenharmony_ci};
4228c2ecf20Sopenharmony_ci
4238c2ecf20Sopenharmony_cistatic int armada_get_temp(void *_sensor, int *temp)
4248c2ecf20Sopenharmony_ci{
4258c2ecf20Sopenharmony_ci	struct armada_thermal_sensor *sensor = _sensor;
4268c2ecf20Sopenharmony_ci	struct armada_thermal_priv *priv = sensor->priv;
4278c2ecf20Sopenharmony_ci	int ret;
4288c2ecf20Sopenharmony_ci
4298c2ecf20Sopenharmony_ci	mutex_lock(&priv->update_lock);
4308c2ecf20Sopenharmony_ci
4318c2ecf20Sopenharmony_ci	/* Select the desired channel */
4328c2ecf20Sopenharmony_ci	ret = armada_select_channel(priv, sensor->id);
4338c2ecf20Sopenharmony_ci	if (ret)
4348c2ecf20Sopenharmony_ci		goto unlock_mutex;
4358c2ecf20Sopenharmony_ci
4368c2ecf20Sopenharmony_ci	/* Do the actual reading */
4378c2ecf20Sopenharmony_ci	ret = armada_read_sensor(priv, temp);
4388c2ecf20Sopenharmony_ci	if (ret)
4398c2ecf20Sopenharmony_ci		goto unlock_mutex;
4408c2ecf20Sopenharmony_ci
4418c2ecf20Sopenharmony_ci	/*
4428c2ecf20Sopenharmony_ci	 * Select back the interrupt source channel from which a potential
4438c2ecf20Sopenharmony_ci	 * critical trip point has been set.
4448c2ecf20Sopenharmony_ci	 */
4458c2ecf20Sopenharmony_ci	ret = armada_select_channel(priv, priv->interrupt_source);
4468c2ecf20Sopenharmony_ci
4478c2ecf20Sopenharmony_ciunlock_mutex:
4488c2ecf20Sopenharmony_ci	mutex_unlock(&priv->update_lock);
4498c2ecf20Sopenharmony_ci
4508c2ecf20Sopenharmony_ci	return ret;
4518c2ecf20Sopenharmony_ci}
4528c2ecf20Sopenharmony_ci
4538c2ecf20Sopenharmony_cistatic const struct thermal_zone_of_device_ops of_ops = {
4548c2ecf20Sopenharmony_ci	.get_temp = armada_get_temp,
4558c2ecf20Sopenharmony_ci};
4568c2ecf20Sopenharmony_ci
4578c2ecf20Sopenharmony_cistatic unsigned int armada_mc_to_reg_temp(struct armada_thermal_data *data,
4588c2ecf20Sopenharmony_ci					  unsigned int temp_mc)
4598c2ecf20Sopenharmony_ci{
4608c2ecf20Sopenharmony_ci	s64 b = data->coef_b;
4618c2ecf20Sopenharmony_ci	s64 m = data->coef_m;
4628c2ecf20Sopenharmony_ci	s64 div = data->coef_div;
4638c2ecf20Sopenharmony_ci	unsigned int sample;
4648c2ecf20Sopenharmony_ci
4658c2ecf20Sopenharmony_ci	if (data->inverted)
4668c2ecf20Sopenharmony_ci		sample = div_s64(((temp_mc * div) + b), m);
4678c2ecf20Sopenharmony_ci	else
4688c2ecf20Sopenharmony_ci		sample = div_s64((b - (temp_mc * div)), m);
4698c2ecf20Sopenharmony_ci
4708c2ecf20Sopenharmony_ci	return sample & data->temp_mask;
4718c2ecf20Sopenharmony_ci}
4728c2ecf20Sopenharmony_ci
4738c2ecf20Sopenharmony_ci/*
4748c2ecf20Sopenharmony_ci * The documentation states:
4758c2ecf20Sopenharmony_ci * high/low watermark = threshold +/- 0.4761 * 2^(hysteresis + 2)
4768c2ecf20Sopenharmony_ci * which is the mathematical derivation for:
4778c2ecf20Sopenharmony_ci * 0x0 <=> 1.9°C, 0x1 <=> 3.8°C, 0x2 <=> 7.6°C, 0x3 <=> 15.2°C
4788c2ecf20Sopenharmony_ci */
4798c2ecf20Sopenharmony_cistatic unsigned int hyst_levels_mc[] = {1900, 3800, 7600, 15200};
4808c2ecf20Sopenharmony_ci
4818c2ecf20Sopenharmony_cistatic unsigned int armada_mc_to_reg_hyst(struct armada_thermal_data *data,
4828c2ecf20Sopenharmony_ci					  unsigned int hyst_mc)
4838c2ecf20Sopenharmony_ci{
4848c2ecf20Sopenharmony_ci	int i;
4858c2ecf20Sopenharmony_ci
4868c2ecf20Sopenharmony_ci	/*
4878c2ecf20Sopenharmony_ci	 * We will always take the smallest possible hysteresis to avoid risking
4888c2ecf20Sopenharmony_ci	 * the hardware integrity by enlarging the threshold by +8°C in the
4898c2ecf20Sopenharmony_ci	 * worst case.
4908c2ecf20Sopenharmony_ci	 */
4918c2ecf20Sopenharmony_ci	for (i = ARRAY_SIZE(hyst_levels_mc) - 1; i > 0; i--)
4928c2ecf20Sopenharmony_ci		if (hyst_mc >= hyst_levels_mc[i])
4938c2ecf20Sopenharmony_ci			break;
4948c2ecf20Sopenharmony_ci
4958c2ecf20Sopenharmony_ci	return i & data->hyst_mask;
4968c2ecf20Sopenharmony_ci}
4978c2ecf20Sopenharmony_ci
4988c2ecf20Sopenharmony_cistatic void armada_set_overheat_thresholds(struct armada_thermal_priv *priv,
4998c2ecf20Sopenharmony_ci					   int thresh_mc, int hyst_mc)
5008c2ecf20Sopenharmony_ci{
5018c2ecf20Sopenharmony_ci	struct armada_thermal_data *data = priv->data;
5028c2ecf20Sopenharmony_ci	unsigned int threshold = armada_mc_to_reg_temp(data, thresh_mc);
5038c2ecf20Sopenharmony_ci	unsigned int hysteresis = armada_mc_to_reg_hyst(data, hyst_mc);
5048c2ecf20Sopenharmony_ci	u32 ctrl1;
5058c2ecf20Sopenharmony_ci
5068c2ecf20Sopenharmony_ci	regmap_read(priv->syscon, data->syscon_control1_off, &ctrl1);
5078c2ecf20Sopenharmony_ci
5088c2ecf20Sopenharmony_ci	/* Set Threshold */
5098c2ecf20Sopenharmony_ci	if (thresh_mc >= 0) {
5108c2ecf20Sopenharmony_ci		ctrl1 &= ~(data->temp_mask << data->thresh_shift);
5118c2ecf20Sopenharmony_ci		ctrl1 |= threshold << data->thresh_shift;
5128c2ecf20Sopenharmony_ci		priv->current_threshold = thresh_mc;
5138c2ecf20Sopenharmony_ci	}
5148c2ecf20Sopenharmony_ci
5158c2ecf20Sopenharmony_ci	/* Set Hysteresis */
5168c2ecf20Sopenharmony_ci	if (hyst_mc >= 0) {
5178c2ecf20Sopenharmony_ci		ctrl1 &= ~(data->hyst_mask << data->hyst_shift);
5188c2ecf20Sopenharmony_ci		ctrl1 |= hysteresis << data->hyst_shift;
5198c2ecf20Sopenharmony_ci		priv->current_hysteresis = hyst_mc;
5208c2ecf20Sopenharmony_ci	}
5218c2ecf20Sopenharmony_ci
5228c2ecf20Sopenharmony_ci	regmap_write(priv->syscon, data->syscon_control1_off, ctrl1);
5238c2ecf20Sopenharmony_ci}
5248c2ecf20Sopenharmony_ci
5258c2ecf20Sopenharmony_cistatic irqreturn_t armada_overheat_isr(int irq, void *blob)
5268c2ecf20Sopenharmony_ci{
5278c2ecf20Sopenharmony_ci	/*
5288c2ecf20Sopenharmony_ci	 * Disable the IRQ and continue in thread context (thermal core
5298c2ecf20Sopenharmony_ci	 * notification and temperature monitoring).
5308c2ecf20Sopenharmony_ci	 */
5318c2ecf20Sopenharmony_ci	disable_irq_nosync(irq);
5328c2ecf20Sopenharmony_ci
5338c2ecf20Sopenharmony_ci	return IRQ_WAKE_THREAD;
5348c2ecf20Sopenharmony_ci}
5358c2ecf20Sopenharmony_ci
5368c2ecf20Sopenharmony_cistatic irqreturn_t armada_overheat_isr_thread(int irq, void *blob)
5378c2ecf20Sopenharmony_ci{
5388c2ecf20Sopenharmony_ci	struct armada_thermal_priv *priv = blob;
5398c2ecf20Sopenharmony_ci	int low_threshold = priv->current_threshold - priv->current_hysteresis;
5408c2ecf20Sopenharmony_ci	int temperature;
5418c2ecf20Sopenharmony_ci	u32 dummy;
5428c2ecf20Sopenharmony_ci	int ret;
5438c2ecf20Sopenharmony_ci
5448c2ecf20Sopenharmony_ci	/* Notify the core in thread context */
5458c2ecf20Sopenharmony_ci	thermal_zone_device_update(priv->overheat_sensor,
5468c2ecf20Sopenharmony_ci				   THERMAL_EVENT_UNSPECIFIED);
5478c2ecf20Sopenharmony_ci
5488c2ecf20Sopenharmony_ci	/*
5498c2ecf20Sopenharmony_ci	 * The overheat interrupt must be cleared by reading the DFX interrupt
5508c2ecf20Sopenharmony_ci	 * cause _after_ the temperature has fallen down to the low threshold.
5518c2ecf20Sopenharmony_ci	 * Otherwise future interrupts might not be served.
5528c2ecf20Sopenharmony_ci	 */
5538c2ecf20Sopenharmony_ci	do {
5548c2ecf20Sopenharmony_ci		msleep(OVERHEAT_INT_POLL_DELAY_MS);
5558c2ecf20Sopenharmony_ci		mutex_lock(&priv->update_lock);
5568c2ecf20Sopenharmony_ci		ret = armada_read_sensor(priv, &temperature);
5578c2ecf20Sopenharmony_ci		mutex_unlock(&priv->update_lock);
5588c2ecf20Sopenharmony_ci		if (ret)
5598c2ecf20Sopenharmony_ci			goto enable_irq;
5608c2ecf20Sopenharmony_ci	} while (temperature >= low_threshold);
5618c2ecf20Sopenharmony_ci
5628c2ecf20Sopenharmony_ci	regmap_read(priv->syscon, priv->data->dfx_irq_cause_off, &dummy);
5638c2ecf20Sopenharmony_ci
5648c2ecf20Sopenharmony_ci	/* Notify the thermal core that the temperature is acceptable again */
5658c2ecf20Sopenharmony_ci	thermal_zone_device_update(priv->overheat_sensor,
5668c2ecf20Sopenharmony_ci				   THERMAL_EVENT_UNSPECIFIED);
5678c2ecf20Sopenharmony_ci
5688c2ecf20Sopenharmony_cienable_irq:
5698c2ecf20Sopenharmony_ci	enable_irq(irq);
5708c2ecf20Sopenharmony_ci
5718c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
5728c2ecf20Sopenharmony_ci}
5738c2ecf20Sopenharmony_ci
5748c2ecf20Sopenharmony_cistatic const struct armada_thermal_data armadaxp_data = {
5758c2ecf20Sopenharmony_ci	.init = armadaxp_init,
5768c2ecf20Sopenharmony_ci	.temp_shift = 10,
5778c2ecf20Sopenharmony_ci	.temp_mask = 0x1ff,
5788c2ecf20Sopenharmony_ci	.coef_b = 3153000000ULL,
5798c2ecf20Sopenharmony_ci	.coef_m = 10000000ULL,
5808c2ecf20Sopenharmony_ci	.coef_div = 13825,
5818c2ecf20Sopenharmony_ci	.syscon_status_off = 0xb0,
5828c2ecf20Sopenharmony_ci	.syscon_control1_off = 0x2d0,
5838c2ecf20Sopenharmony_ci};
5848c2ecf20Sopenharmony_ci
5858c2ecf20Sopenharmony_cistatic const struct armada_thermal_data armada370_data = {
5868c2ecf20Sopenharmony_ci	.init = armada370_init,
5878c2ecf20Sopenharmony_ci	.is_valid_bit = BIT(9),
5888c2ecf20Sopenharmony_ci	.temp_shift = 10,
5898c2ecf20Sopenharmony_ci	.temp_mask = 0x1ff,
5908c2ecf20Sopenharmony_ci	.coef_b = 3153000000ULL,
5918c2ecf20Sopenharmony_ci	.coef_m = 10000000ULL,
5928c2ecf20Sopenharmony_ci	.coef_div = 13825,
5938c2ecf20Sopenharmony_ci	.syscon_status_off = 0x0,
5948c2ecf20Sopenharmony_ci	.syscon_control1_off = 0x4,
5958c2ecf20Sopenharmony_ci};
5968c2ecf20Sopenharmony_ci
5978c2ecf20Sopenharmony_cistatic const struct armada_thermal_data armada375_data = {
5988c2ecf20Sopenharmony_ci	.init = armada375_init,
5998c2ecf20Sopenharmony_ci	.is_valid_bit = BIT(10),
6008c2ecf20Sopenharmony_ci	.temp_shift = 0,
6018c2ecf20Sopenharmony_ci	.temp_mask = 0x1ff,
6028c2ecf20Sopenharmony_ci	.coef_b = 3171900000ULL,
6038c2ecf20Sopenharmony_ci	.coef_m = 10000000ULL,
6048c2ecf20Sopenharmony_ci	.coef_div = 13616,
6058c2ecf20Sopenharmony_ci	.syscon_status_off = 0x78,
6068c2ecf20Sopenharmony_ci	.syscon_control0_off = 0x7c,
6078c2ecf20Sopenharmony_ci	.syscon_control1_off = 0x80,
6088c2ecf20Sopenharmony_ci};
6098c2ecf20Sopenharmony_ci
6108c2ecf20Sopenharmony_cistatic const struct armada_thermal_data armada380_data = {
6118c2ecf20Sopenharmony_ci	.init = armada380_init,
6128c2ecf20Sopenharmony_ci	.is_valid_bit = BIT(10),
6138c2ecf20Sopenharmony_ci	.temp_shift = 0,
6148c2ecf20Sopenharmony_ci	.temp_mask = 0x3ff,
6158c2ecf20Sopenharmony_ci	.coef_b = 1172499100ULL,
6168c2ecf20Sopenharmony_ci	.coef_m = 2000096ULL,
6178c2ecf20Sopenharmony_ci	.coef_div = 4201,
6188c2ecf20Sopenharmony_ci	.inverted = true,
6198c2ecf20Sopenharmony_ci	.syscon_control0_off = 0x70,
6208c2ecf20Sopenharmony_ci	.syscon_control1_off = 0x74,
6218c2ecf20Sopenharmony_ci	.syscon_status_off = 0x78,
6228c2ecf20Sopenharmony_ci};
6238c2ecf20Sopenharmony_ci
6248c2ecf20Sopenharmony_cistatic const struct armada_thermal_data armada_ap806_data = {
6258c2ecf20Sopenharmony_ci	.init = armada_ap806_init,
6268c2ecf20Sopenharmony_ci	.is_valid_bit = BIT(16),
6278c2ecf20Sopenharmony_ci	.temp_shift = 0,
6288c2ecf20Sopenharmony_ci	.temp_mask = 0x3ff,
6298c2ecf20Sopenharmony_ci	.thresh_shift = 3,
6308c2ecf20Sopenharmony_ci	.hyst_shift = 19,
6318c2ecf20Sopenharmony_ci	.hyst_mask = 0x3,
6328c2ecf20Sopenharmony_ci	.coef_b = -150000LL,
6338c2ecf20Sopenharmony_ci	.coef_m = 423ULL,
6348c2ecf20Sopenharmony_ci	.coef_div = 1,
6358c2ecf20Sopenharmony_ci	.inverted = true,
6368c2ecf20Sopenharmony_ci	.signed_sample = true,
6378c2ecf20Sopenharmony_ci	.syscon_control0_off = 0x84,
6388c2ecf20Sopenharmony_ci	.syscon_control1_off = 0x88,
6398c2ecf20Sopenharmony_ci	.syscon_status_off = 0x8C,
6408c2ecf20Sopenharmony_ci	.dfx_irq_cause_off = 0x108,
6418c2ecf20Sopenharmony_ci	.dfx_irq_mask_off = 0x10C,
6428c2ecf20Sopenharmony_ci	.dfx_overheat_irq = BIT(22),
6438c2ecf20Sopenharmony_ci	.dfx_server_irq_mask_off = 0x104,
6448c2ecf20Sopenharmony_ci	.dfx_server_irq_en = BIT(1),
6458c2ecf20Sopenharmony_ci	.cpu_nr = 4,
6468c2ecf20Sopenharmony_ci};
6478c2ecf20Sopenharmony_ci
6488c2ecf20Sopenharmony_cistatic const struct armada_thermal_data armada_cp110_data = {
6498c2ecf20Sopenharmony_ci	.init = armada_cp110_init,
6508c2ecf20Sopenharmony_ci	.is_valid_bit = BIT(10),
6518c2ecf20Sopenharmony_ci	.temp_shift = 0,
6528c2ecf20Sopenharmony_ci	.temp_mask = 0x3ff,
6538c2ecf20Sopenharmony_ci	.thresh_shift = 16,
6548c2ecf20Sopenharmony_ci	.hyst_shift = 26,
6558c2ecf20Sopenharmony_ci	.hyst_mask = 0x3,
6568c2ecf20Sopenharmony_ci	.coef_b = 1172499100ULL,
6578c2ecf20Sopenharmony_ci	.coef_m = 2000096ULL,
6588c2ecf20Sopenharmony_ci	.coef_div = 4201,
6598c2ecf20Sopenharmony_ci	.inverted = true,
6608c2ecf20Sopenharmony_ci	.syscon_control0_off = 0x70,
6618c2ecf20Sopenharmony_ci	.syscon_control1_off = 0x74,
6628c2ecf20Sopenharmony_ci	.syscon_status_off = 0x78,
6638c2ecf20Sopenharmony_ci	.dfx_irq_cause_off = 0x108,
6648c2ecf20Sopenharmony_ci	.dfx_irq_mask_off = 0x10C,
6658c2ecf20Sopenharmony_ci	.dfx_overheat_irq = BIT(20),
6668c2ecf20Sopenharmony_ci	.dfx_server_irq_mask_off = 0x104,
6678c2ecf20Sopenharmony_ci	.dfx_server_irq_en = BIT(1),
6688c2ecf20Sopenharmony_ci};
6698c2ecf20Sopenharmony_ci
6708c2ecf20Sopenharmony_cistatic const struct of_device_id armada_thermal_id_table[] = {
6718c2ecf20Sopenharmony_ci	{
6728c2ecf20Sopenharmony_ci		.compatible = "marvell,armadaxp-thermal",
6738c2ecf20Sopenharmony_ci		.data       = &armadaxp_data,
6748c2ecf20Sopenharmony_ci	},
6758c2ecf20Sopenharmony_ci	{
6768c2ecf20Sopenharmony_ci		.compatible = "marvell,armada370-thermal",
6778c2ecf20Sopenharmony_ci		.data       = &armada370_data,
6788c2ecf20Sopenharmony_ci	},
6798c2ecf20Sopenharmony_ci	{
6808c2ecf20Sopenharmony_ci		.compatible = "marvell,armada375-thermal",
6818c2ecf20Sopenharmony_ci		.data       = &armada375_data,
6828c2ecf20Sopenharmony_ci	},
6838c2ecf20Sopenharmony_ci	{
6848c2ecf20Sopenharmony_ci		.compatible = "marvell,armada380-thermal",
6858c2ecf20Sopenharmony_ci		.data       = &armada380_data,
6868c2ecf20Sopenharmony_ci	},
6878c2ecf20Sopenharmony_ci	{
6888c2ecf20Sopenharmony_ci		.compatible = "marvell,armada-ap806-thermal",
6898c2ecf20Sopenharmony_ci		.data       = &armada_ap806_data,
6908c2ecf20Sopenharmony_ci	},
6918c2ecf20Sopenharmony_ci	{
6928c2ecf20Sopenharmony_ci		.compatible = "marvell,armada-cp110-thermal",
6938c2ecf20Sopenharmony_ci		.data       = &armada_cp110_data,
6948c2ecf20Sopenharmony_ci	},
6958c2ecf20Sopenharmony_ci	{
6968c2ecf20Sopenharmony_ci		/* sentinel */
6978c2ecf20Sopenharmony_ci	},
6988c2ecf20Sopenharmony_ci};
6998c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, armada_thermal_id_table);
7008c2ecf20Sopenharmony_ci
7018c2ecf20Sopenharmony_cistatic const struct regmap_config armada_thermal_regmap_config = {
7028c2ecf20Sopenharmony_ci	.reg_bits = 32,
7038c2ecf20Sopenharmony_ci	.reg_stride = 4,
7048c2ecf20Sopenharmony_ci	.val_bits = 32,
7058c2ecf20Sopenharmony_ci	.fast_io = true,
7068c2ecf20Sopenharmony_ci};
7078c2ecf20Sopenharmony_ci
7088c2ecf20Sopenharmony_cistatic int armada_thermal_probe_legacy(struct platform_device *pdev,
7098c2ecf20Sopenharmony_ci				       struct armada_thermal_priv *priv)
7108c2ecf20Sopenharmony_ci{
7118c2ecf20Sopenharmony_ci	struct armada_thermal_data *data = priv->data;
7128c2ecf20Sopenharmony_ci	struct resource *res;
7138c2ecf20Sopenharmony_ci	void __iomem *base;
7148c2ecf20Sopenharmony_ci
7158c2ecf20Sopenharmony_ci	/* First memory region points towards the status register */
7168c2ecf20Sopenharmony_ci	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
7178c2ecf20Sopenharmony_ci	base = devm_ioremap_resource(&pdev->dev, res);
7188c2ecf20Sopenharmony_ci	if (IS_ERR(base))
7198c2ecf20Sopenharmony_ci		return PTR_ERR(base);
7208c2ecf20Sopenharmony_ci
7218c2ecf20Sopenharmony_ci	/*
7228c2ecf20Sopenharmony_ci	 * Fix up from the old individual DT register specification to
7238c2ecf20Sopenharmony_ci	 * cover all the registers.  We do this by adjusting the ioremap()
7248c2ecf20Sopenharmony_ci	 * result, which should be fine as ioremap() deals with pages.
7258c2ecf20Sopenharmony_ci	 * However, validate that we do not cross a page boundary while
7268c2ecf20Sopenharmony_ci	 * making this adjustment.
7278c2ecf20Sopenharmony_ci	 */
7288c2ecf20Sopenharmony_ci	if (((unsigned long)base & ~PAGE_MASK) < data->syscon_status_off)
7298c2ecf20Sopenharmony_ci		return -EINVAL;
7308c2ecf20Sopenharmony_ci	base -= data->syscon_status_off;
7318c2ecf20Sopenharmony_ci
7328c2ecf20Sopenharmony_ci	priv->syscon = devm_regmap_init_mmio(&pdev->dev, base,
7338c2ecf20Sopenharmony_ci					     &armada_thermal_regmap_config);
7348c2ecf20Sopenharmony_ci	return PTR_ERR_OR_ZERO(priv->syscon);
7358c2ecf20Sopenharmony_ci}
7368c2ecf20Sopenharmony_ci
7378c2ecf20Sopenharmony_cistatic int armada_thermal_probe_syscon(struct platform_device *pdev,
7388c2ecf20Sopenharmony_ci				       struct armada_thermal_priv *priv)
7398c2ecf20Sopenharmony_ci{
7408c2ecf20Sopenharmony_ci	priv->syscon = syscon_node_to_regmap(pdev->dev.parent->of_node);
7418c2ecf20Sopenharmony_ci	return PTR_ERR_OR_ZERO(priv->syscon);
7428c2ecf20Sopenharmony_ci}
7438c2ecf20Sopenharmony_ci
7448c2ecf20Sopenharmony_cistatic void armada_set_sane_name(struct platform_device *pdev,
7458c2ecf20Sopenharmony_ci				 struct armada_thermal_priv *priv)
7468c2ecf20Sopenharmony_ci{
7478c2ecf20Sopenharmony_ci	const char *name = dev_name(&pdev->dev);
7488c2ecf20Sopenharmony_ci	char *insane_char;
7498c2ecf20Sopenharmony_ci
7508c2ecf20Sopenharmony_ci	if (strlen(name) > THERMAL_NAME_LENGTH) {
7518c2ecf20Sopenharmony_ci		/*
7528c2ecf20Sopenharmony_ci		 * When inside a system controller, the device name has the
7538c2ecf20Sopenharmony_ci		 * form: f06f8000.system-controller:ap-thermal so stripping
7548c2ecf20Sopenharmony_ci		 * after the ':' should give us a shorter but meaningful name.
7558c2ecf20Sopenharmony_ci		 */
7568c2ecf20Sopenharmony_ci		name = strrchr(name, ':');
7578c2ecf20Sopenharmony_ci		if (!name)
7588c2ecf20Sopenharmony_ci			name = "armada_thermal";
7598c2ecf20Sopenharmony_ci		else
7608c2ecf20Sopenharmony_ci			name++;
7618c2ecf20Sopenharmony_ci	}
7628c2ecf20Sopenharmony_ci
7638c2ecf20Sopenharmony_ci	/* Save the name locally */
7648c2ecf20Sopenharmony_ci	strncpy(priv->zone_name, name, THERMAL_NAME_LENGTH - 1);
7658c2ecf20Sopenharmony_ci	priv->zone_name[THERMAL_NAME_LENGTH - 1] = '\0';
7668c2ecf20Sopenharmony_ci
7678c2ecf20Sopenharmony_ci	/* Then check there are no '-' or hwmon core will complain */
7688c2ecf20Sopenharmony_ci	do {
7698c2ecf20Sopenharmony_ci		insane_char = strpbrk(priv->zone_name, "-");
7708c2ecf20Sopenharmony_ci		if (insane_char)
7718c2ecf20Sopenharmony_ci			*insane_char = '_';
7728c2ecf20Sopenharmony_ci	} while (insane_char);
7738c2ecf20Sopenharmony_ci}
7748c2ecf20Sopenharmony_ci
7758c2ecf20Sopenharmony_ci/*
7768c2ecf20Sopenharmony_ci * The IP can manage to trigger interrupts on overheat situation from all the
7778c2ecf20Sopenharmony_ci * sensors. However, the interrupt source changes along with the last selected
7788c2ecf20Sopenharmony_ci * source (ie. the last read sensor), which is an inconsistent behavior. Avoid
7798c2ecf20Sopenharmony_ci * possible glitches by always selecting back only one channel (arbitrarily: the
7808c2ecf20Sopenharmony_ci * first in the DT which has a critical trip point). We also disable sensor
7818c2ecf20Sopenharmony_ci * switch during overheat situations.
7828c2ecf20Sopenharmony_ci */
7838c2ecf20Sopenharmony_cistatic int armada_configure_overheat_int(struct armada_thermal_priv *priv,
7848c2ecf20Sopenharmony_ci					 struct thermal_zone_device *tz,
7858c2ecf20Sopenharmony_ci					 int sensor_id)
7868c2ecf20Sopenharmony_ci{
7878c2ecf20Sopenharmony_ci	/* Retrieve the critical trip point to enable the overheat interrupt */
7888c2ecf20Sopenharmony_ci	const struct thermal_trip *trips = of_thermal_get_trip_points(tz);
7898c2ecf20Sopenharmony_ci	int ret;
7908c2ecf20Sopenharmony_ci	int i;
7918c2ecf20Sopenharmony_ci
7928c2ecf20Sopenharmony_ci	if (!trips)
7938c2ecf20Sopenharmony_ci		return -EINVAL;
7948c2ecf20Sopenharmony_ci
7958c2ecf20Sopenharmony_ci	for (i = 0; i < of_thermal_get_ntrips(tz); i++)
7968c2ecf20Sopenharmony_ci		if (trips[i].type == THERMAL_TRIP_CRITICAL)
7978c2ecf20Sopenharmony_ci			break;
7988c2ecf20Sopenharmony_ci
7998c2ecf20Sopenharmony_ci	if (i == of_thermal_get_ntrips(tz))
8008c2ecf20Sopenharmony_ci		return -EINVAL;
8018c2ecf20Sopenharmony_ci
8028c2ecf20Sopenharmony_ci	ret = armada_select_channel(priv, sensor_id);
8038c2ecf20Sopenharmony_ci	if (ret)
8048c2ecf20Sopenharmony_ci		return ret;
8058c2ecf20Sopenharmony_ci
8068c2ecf20Sopenharmony_ci	armada_set_overheat_thresholds(priv,
8078c2ecf20Sopenharmony_ci				       trips[i].temperature,
8088c2ecf20Sopenharmony_ci				       trips[i].hysteresis);
8098c2ecf20Sopenharmony_ci	priv->overheat_sensor = tz;
8108c2ecf20Sopenharmony_ci	priv->interrupt_source = sensor_id;
8118c2ecf20Sopenharmony_ci
8128c2ecf20Sopenharmony_ci	armada_enable_overheat_interrupt(priv);
8138c2ecf20Sopenharmony_ci
8148c2ecf20Sopenharmony_ci	return 0;
8158c2ecf20Sopenharmony_ci}
8168c2ecf20Sopenharmony_ci
8178c2ecf20Sopenharmony_cistatic int armada_thermal_probe(struct platform_device *pdev)
8188c2ecf20Sopenharmony_ci{
8198c2ecf20Sopenharmony_ci	struct thermal_zone_device *tz;
8208c2ecf20Sopenharmony_ci	struct armada_thermal_sensor *sensor;
8218c2ecf20Sopenharmony_ci	struct armada_drvdata *drvdata;
8228c2ecf20Sopenharmony_ci	const struct of_device_id *match;
8238c2ecf20Sopenharmony_ci	struct armada_thermal_priv *priv;
8248c2ecf20Sopenharmony_ci	int sensor_id, irq;
8258c2ecf20Sopenharmony_ci	int ret;
8268c2ecf20Sopenharmony_ci
8278c2ecf20Sopenharmony_ci	match = of_match_device(armada_thermal_id_table, &pdev->dev);
8288c2ecf20Sopenharmony_ci	if (!match)
8298c2ecf20Sopenharmony_ci		return -ENODEV;
8308c2ecf20Sopenharmony_ci
8318c2ecf20Sopenharmony_ci	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
8328c2ecf20Sopenharmony_ci	if (!priv)
8338c2ecf20Sopenharmony_ci		return -ENOMEM;
8348c2ecf20Sopenharmony_ci
8358c2ecf20Sopenharmony_ci	drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL);
8368c2ecf20Sopenharmony_ci	if (!drvdata)
8378c2ecf20Sopenharmony_ci		return -ENOMEM;
8388c2ecf20Sopenharmony_ci
8398c2ecf20Sopenharmony_ci	priv->dev = &pdev->dev;
8408c2ecf20Sopenharmony_ci	priv->data = (struct armada_thermal_data *)match->data;
8418c2ecf20Sopenharmony_ci
8428c2ecf20Sopenharmony_ci	mutex_init(&priv->update_lock);
8438c2ecf20Sopenharmony_ci
8448c2ecf20Sopenharmony_ci	/*
8458c2ecf20Sopenharmony_ci	 * Legacy DT bindings only described "control1" register (also referred
8468c2ecf20Sopenharmony_ci	 * as "control MSB" on old documentation). Then, bindings moved to cover
8478c2ecf20Sopenharmony_ci	 * "control0/control LSB" and "control1/control MSB" registers within
8488c2ecf20Sopenharmony_ci	 * the same resource, which was then of size 8 instead of 4.
8498c2ecf20Sopenharmony_ci	 *
8508c2ecf20Sopenharmony_ci	 * The logic of defining sporadic registers is broken. For instance, it
8518c2ecf20Sopenharmony_ci	 * blocked the addition of the overheat interrupt feature that needed
8528c2ecf20Sopenharmony_ci	 * another resource somewhere else in the same memory area. One solution
8538c2ecf20Sopenharmony_ci	 * is to define an overall system controller and put the thermal node
8548c2ecf20Sopenharmony_ci	 * into it, which requires the use of regmaps across all the driver.
8558c2ecf20Sopenharmony_ci	 */
8568c2ecf20Sopenharmony_ci	if (IS_ERR(syscon_node_to_regmap(pdev->dev.parent->of_node))) {
8578c2ecf20Sopenharmony_ci		/* Ensure device name is correct for the thermal core */
8588c2ecf20Sopenharmony_ci		armada_set_sane_name(pdev, priv);
8598c2ecf20Sopenharmony_ci
8608c2ecf20Sopenharmony_ci		ret = armada_thermal_probe_legacy(pdev, priv);
8618c2ecf20Sopenharmony_ci		if (ret)
8628c2ecf20Sopenharmony_ci			return ret;
8638c2ecf20Sopenharmony_ci
8648c2ecf20Sopenharmony_ci		priv->data->init(pdev, priv);
8658c2ecf20Sopenharmony_ci
8668c2ecf20Sopenharmony_ci		/* Wait the sensors to be valid */
8678c2ecf20Sopenharmony_ci		armada_wait_sensor_validity(priv);
8688c2ecf20Sopenharmony_ci
8698c2ecf20Sopenharmony_ci		tz = thermal_zone_device_register(priv->zone_name, 0, 0, priv,
8708c2ecf20Sopenharmony_ci						  &legacy_ops, NULL, 0, 0);
8718c2ecf20Sopenharmony_ci		if (IS_ERR(tz)) {
8728c2ecf20Sopenharmony_ci			dev_err(&pdev->dev,
8738c2ecf20Sopenharmony_ci				"Failed to register thermal zone device\n");
8748c2ecf20Sopenharmony_ci			return PTR_ERR(tz);
8758c2ecf20Sopenharmony_ci		}
8768c2ecf20Sopenharmony_ci
8778c2ecf20Sopenharmony_ci		ret = thermal_zone_device_enable(tz);
8788c2ecf20Sopenharmony_ci		if (ret) {
8798c2ecf20Sopenharmony_ci			thermal_zone_device_unregister(tz);
8808c2ecf20Sopenharmony_ci			return ret;
8818c2ecf20Sopenharmony_ci		}
8828c2ecf20Sopenharmony_ci
8838c2ecf20Sopenharmony_ci		drvdata->type = LEGACY;
8848c2ecf20Sopenharmony_ci		drvdata->data.tz = tz;
8858c2ecf20Sopenharmony_ci		platform_set_drvdata(pdev, drvdata);
8868c2ecf20Sopenharmony_ci
8878c2ecf20Sopenharmony_ci		return 0;
8888c2ecf20Sopenharmony_ci	}
8898c2ecf20Sopenharmony_ci
8908c2ecf20Sopenharmony_ci	ret = armada_thermal_probe_syscon(pdev, priv);
8918c2ecf20Sopenharmony_ci	if (ret)
8928c2ecf20Sopenharmony_ci		return ret;
8938c2ecf20Sopenharmony_ci
8948c2ecf20Sopenharmony_ci	priv->current_channel = -1;
8958c2ecf20Sopenharmony_ci	priv->data->init(pdev, priv);
8968c2ecf20Sopenharmony_ci	drvdata->type = SYSCON;
8978c2ecf20Sopenharmony_ci	drvdata->data.priv = priv;
8988c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, drvdata);
8998c2ecf20Sopenharmony_ci
9008c2ecf20Sopenharmony_ci	irq = platform_get_irq(pdev, 0);
9018c2ecf20Sopenharmony_ci	if (irq == -EPROBE_DEFER)
9028c2ecf20Sopenharmony_ci		return irq;
9038c2ecf20Sopenharmony_ci
9048c2ecf20Sopenharmony_ci	/* The overheat interrupt feature is not mandatory */
9058c2ecf20Sopenharmony_ci	if (irq > 0) {
9068c2ecf20Sopenharmony_ci		ret = devm_request_threaded_irq(&pdev->dev, irq,
9078c2ecf20Sopenharmony_ci						armada_overheat_isr,
9088c2ecf20Sopenharmony_ci						armada_overheat_isr_thread,
9098c2ecf20Sopenharmony_ci						0, NULL, priv);
9108c2ecf20Sopenharmony_ci		if (ret) {
9118c2ecf20Sopenharmony_ci			dev_err(&pdev->dev, "Cannot request threaded IRQ %d\n",
9128c2ecf20Sopenharmony_ci				irq);
9138c2ecf20Sopenharmony_ci			return ret;
9148c2ecf20Sopenharmony_ci		}
9158c2ecf20Sopenharmony_ci	}
9168c2ecf20Sopenharmony_ci
9178c2ecf20Sopenharmony_ci	/*
9188c2ecf20Sopenharmony_ci	 * There is one channel for the IC and one per CPU (if any), each
9198c2ecf20Sopenharmony_ci	 * channel has one sensor.
9208c2ecf20Sopenharmony_ci	 */
9218c2ecf20Sopenharmony_ci	for (sensor_id = 0; sensor_id <= priv->data->cpu_nr; sensor_id++) {
9228c2ecf20Sopenharmony_ci		sensor = devm_kzalloc(&pdev->dev,
9238c2ecf20Sopenharmony_ci				      sizeof(struct armada_thermal_sensor),
9248c2ecf20Sopenharmony_ci				      GFP_KERNEL);
9258c2ecf20Sopenharmony_ci		if (!sensor)
9268c2ecf20Sopenharmony_ci			return -ENOMEM;
9278c2ecf20Sopenharmony_ci
9288c2ecf20Sopenharmony_ci		/* Register the sensor */
9298c2ecf20Sopenharmony_ci		sensor->priv = priv;
9308c2ecf20Sopenharmony_ci		sensor->id = sensor_id;
9318c2ecf20Sopenharmony_ci		tz = devm_thermal_zone_of_sensor_register(&pdev->dev,
9328c2ecf20Sopenharmony_ci							  sensor->id, sensor,
9338c2ecf20Sopenharmony_ci							  &of_ops);
9348c2ecf20Sopenharmony_ci		if (IS_ERR(tz)) {
9358c2ecf20Sopenharmony_ci			dev_info(&pdev->dev, "Thermal sensor %d unavailable\n",
9368c2ecf20Sopenharmony_ci				 sensor_id);
9378c2ecf20Sopenharmony_ci			devm_kfree(&pdev->dev, sensor);
9388c2ecf20Sopenharmony_ci			continue;
9398c2ecf20Sopenharmony_ci		}
9408c2ecf20Sopenharmony_ci
9418c2ecf20Sopenharmony_ci		/*
9428c2ecf20Sopenharmony_ci		 * The first channel that has a critical trip point registered
9438c2ecf20Sopenharmony_ci		 * in the DT will serve as interrupt source. Others possible
9448c2ecf20Sopenharmony_ci		 * critical trip points will simply be ignored by the driver.
9458c2ecf20Sopenharmony_ci		 */
9468c2ecf20Sopenharmony_ci		if (irq > 0 && !priv->overheat_sensor)
9478c2ecf20Sopenharmony_ci			armada_configure_overheat_int(priv, tz, sensor->id);
9488c2ecf20Sopenharmony_ci	}
9498c2ecf20Sopenharmony_ci
9508c2ecf20Sopenharmony_ci	/* Just complain if no overheat interrupt was set up */
9518c2ecf20Sopenharmony_ci	if (!priv->overheat_sensor)
9528c2ecf20Sopenharmony_ci		dev_warn(&pdev->dev, "Overheat interrupt not available\n");
9538c2ecf20Sopenharmony_ci
9548c2ecf20Sopenharmony_ci	return 0;
9558c2ecf20Sopenharmony_ci}
9568c2ecf20Sopenharmony_ci
9578c2ecf20Sopenharmony_cistatic int armada_thermal_exit(struct platform_device *pdev)
9588c2ecf20Sopenharmony_ci{
9598c2ecf20Sopenharmony_ci	struct armada_drvdata *drvdata = platform_get_drvdata(pdev);
9608c2ecf20Sopenharmony_ci
9618c2ecf20Sopenharmony_ci	if (drvdata->type == LEGACY)
9628c2ecf20Sopenharmony_ci		thermal_zone_device_unregister(drvdata->data.tz);
9638c2ecf20Sopenharmony_ci
9648c2ecf20Sopenharmony_ci	return 0;
9658c2ecf20Sopenharmony_ci}
9668c2ecf20Sopenharmony_ci
9678c2ecf20Sopenharmony_cistatic struct platform_driver armada_thermal_driver = {
9688c2ecf20Sopenharmony_ci	.probe = armada_thermal_probe,
9698c2ecf20Sopenharmony_ci	.remove = armada_thermal_exit,
9708c2ecf20Sopenharmony_ci	.driver = {
9718c2ecf20Sopenharmony_ci		.name = "armada_thermal",
9728c2ecf20Sopenharmony_ci		.of_match_table = armada_thermal_id_table,
9738c2ecf20Sopenharmony_ci	},
9748c2ecf20Sopenharmony_ci};
9758c2ecf20Sopenharmony_ci
9768c2ecf20Sopenharmony_cimodule_platform_driver(armada_thermal_driver);
9778c2ecf20Sopenharmony_ci
9788c2ecf20Sopenharmony_ciMODULE_AUTHOR("Ezequiel Garcia <ezequiel.garcia@free-electrons.com>");
9798c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Marvell EBU Armada SoCs thermal driver");
9808c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
981