@@ -4,6 +4,7 @@
*
* Copyright (C) 2013 Marvell
*/
+#include <linux/arm-smccc.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/io.h>
@@ -18,6 +19,8 @@
#include <linux/mfd/syscon.h>
#include <linux/regmap.h>
#include <linux/interrupt.h>
+#include <linux/time.h>
+#include "soc/marvell/armada8k/fw.h"
#include "thermal_core.h"
@@ -62,6 +65,8 @@
#define STATUS_POLL_TIMEOUT_US 100000
#define OVERHEAT_INT_POLL_DELAY_MS 1000
+#define THERMAL_SUPPORTED_IN_FIRMWARE(priv) (priv->data->is_smc_supported)
+
struct armada_thermal_data;
/* Marvell EBU Thermal Sensor Dev Structure */
@@ -111,6 +116,12 @@ struct armada_thermal_data {
/* One sensor is in the thermal IC, the others are in the CPUs if any */
unsigned int cpu_nr;
+
+ /*
+ * Thermal sensor operations exposed as firmware SIP services and
+ * accessed via SMC
+ */
+ bool is_smc_supported;
};
struct armada_drvdata {
@@ -135,6 +146,18 @@ struct armada_thermal_sensor {
int id;
};
+static int thermal_smc(u32 addr, u32 *reg, u32 val1, u32 val2)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_smc(MV_SIP_DFX, addr, val1, val2, 0, 0, 0, 0, &res);
+
+ if (res.a0 == 0 && reg != NULL)
+ *reg = res.a1;
+
+ return res.a0;
+}
+
static void armadaxp_init(struct platform_device *pdev,
struct armada_thermal_priv *priv)
{
@@ -206,6 +229,27 @@ static void armada375_init(struct platform_device *pdev,
static int armada_wait_sensor_validity(struct armada_thermal_priv *priv)
{
u32 reg;
+ int ret;
+ ktime_t timeout;
+
+ if (THERMAL_SUPPORTED_IN_FIRMWARE(priv)) {
+ timeout = ktime_add_us(ktime_get(), STATUS_POLL_TIMEOUT_US);
+ do {
+ ret = thermal_smc(MV_SIP_DFX_THERMAL_IS_VALID,
+ ®, 0, 0);
+ if (ret || reg)
+ break;
+
+ usleep_range((STATUS_POLL_PERIOD_US >> 2) + 1,
+ STATUS_POLL_PERIOD_US);
+
+ } while (ktime_before(ktime_get(), timeout));
+
+ if (ret == SMCCC_RET_SUCCESS)
+ return reg ? 0 : -ETIMEDOUT;
+
+ return ret;
+ }
return regmap_read_poll_timeout(priv->syscon,
priv->data->syscon_status_off, reg,
@@ -238,6 +282,22 @@ static void armada_ap806_init(struct platform_device *pdev,
{
struct armada_thermal_data *data = priv->data;
u32 reg;
+ int ret;
+
+ /*
+ * The ap806 thermal sensor registers are part of DFX which is secured
+ * by latest firmware, therefore accessing relevant registers from
+ * not-secure world will not be possible. In that case Arm Trusted
+ * Firmware exposes thermal operations as firmware run-time service. If
+ * SMC initialization succeeds, perform other thermal operations using
+ * SMC, otherwise (old fw case) fallback to regmap handling.
+ */
+ ret = thermal_smc(MV_SIP_DFX_THERMAL_INIT, 0x0, 0, 0);
+ if (ret == SMCCC_RET_SUCCESS) {
+ dev_info(&pdev->dev, "firmware support\n");
+ THERMAL_SUPPORTED_IN_FIRMWARE(priv) = true;
+ return;
+ }
regmap_read(priv->syscon, data->syscon_control0_off, ®);
reg &= ~CONTROL0_TSEN_RESET;
@@ -274,11 +334,17 @@ static void armada_cp110_init(struct platform_device *pdev,
static bool armada_is_valid(struct armada_thermal_priv *priv)
{
+ int ret;
u32 reg;
if (!priv->data->is_valid_bit)
return true;
+ if (THERMAL_SUPPORTED_IN_FIRMWARE(priv)) {
+ ret = thermal_smc(MV_SIP_DFX_THERMAL_IS_VALID, ®, 0, 0);
+ return ret ? false : reg;
+ }
+
regmap_read(priv->syscon, priv->data->syscon_status_off, ®);
return reg & priv->data->is_valid_bit;
@@ -324,6 +390,7 @@ static int armada_select_channel(struct armada_thermal_priv *priv, int channel)
{
struct armada_thermal_data *data = priv->data;
u32 ctrl0;
+ int ret;
if (channel < 0 || channel > priv->data->cpu_nr)
return -EINVAL;
@@ -331,6 +398,16 @@ static int armada_select_channel(struct armada_thermal_priv *priv, int channel)
if (priv->current_channel == channel)
return 0;
+ if (THERMAL_SUPPORTED_IN_FIRMWARE(priv)) {
+ ret = thermal_smc(MV_SIP_DFX_THERMAL_SEL_CHANNEL,
+ NULL, channel, 0);
+ if (ret)
+ return ret;
+
+ priv->current_channel = channel;
+ goto is_valid;
+ }
+
/* Stop the measurements */
regmap_read(priv->syscon, data->syscon_control0_off, &ctrl0);
ctrl0 &= ~CONTROL0_TSEN_START;
@@ -357,6 +434,7 @@ static int armada_select_channel(struct armada_thermal_priv *priv, int channel)
ctrl0 |= CONTROL0_TSEN_START;
regmap_write(priv->syscon, data->syscon_control0_off, ctrl0);
+is_valid:
/*
* The IP has a latency of ~15ms, so after updating the selected source,
* we must absolutely wait for the sensor validity bit to ensure we read
@@ -376,6 +454,9 @@ static int armada_read_sensor(struct armada_thermal_priv *priv, int *temp)
u32 reg, div;
s64 sample, b, m;
+ if (THERMAL_SUPPORTED_IN_FIRMWARE(priv))
+ return thermal_smc(MV_SIP_DFX_THERMAL_READ, temp, 0, 0);
+
regmap_read(priv->syscon, priv->data->syscon_status_off, ®);
reg = (reg >> priv->data->temp_shift) & priv->data->temp_mask;
if (priv->data->signed_sample)
@@ -559,7 +640,13 @@ static irqreturn_t armada_overheat_isr_thread(int irq, void *blob)
goto enable_irq;
} while (temperature >= low_threshold);
- regmap_read(priv->syscon, priv->data->dfx_irq_cause_off, &dummy);
+ if (THERMAL_SUPPORTED_IN_FIRMWARE(priv)) {
+ if (thermal_smc(MV_SIP_DFX_THERMAL_IRQ, 0, 0, 0))
+ return IRQ_NONE;
+ } else {
+ regmap_read(priv->syscon, priv->data->dfx_irq_cause_off,
+ &dummy);
+ }
/* Notify the thermal core that the temperature is acceptable again */
thermal_zone_device_update(priv->overheat_sensor,
@@ -772,6 +859,27 @@ static void armada_set_sane_name(struct platform_device *pdev,
} while (insane_char);
}
+/*
+ * Let the firmware configure the thermal overheat threshold, hysteresis and
+ * enable overheat interrupt
+ */
+static int armada_fw_overheat_settings(struct armada_thermal_priv *priv,
+ int thresh_mc, int hyst_mc)
+{
+ int ret;
+
+ ret = thermal_smc(MV_SIP_DFX_THERMAL_THRESH, NULL, thresh_mc, hyst_mc);
+ if (ret)
+ return ret;
+
+ if (thresh_mc >= 0)
+ priv->current_threshold = thresh_mc;
+ if (hyst_mc >= 0)
+ priv->current_hysteresis = hyst_mc;
+
+ return 0;
+}
+
/*
* The IP can manage to trigger interrupts on overheat situation from all the
* sensors. However, the interrupt source changes along with the last selected
@@ -803,11 +911,22 @@ static int armada_configure_overheat_int(struct armada_thermal_priv *priv,
if (ret)
return ret;
+ priv->overheat_sensor = tz;
+ priv->interrupt_source = sensor_id;
+
+ if (THERMAL_SUPPORTED_IN_FIRMWARE(priv)) {
+ /*
+ * When thermal supported in firmware the configuring overheat
+ * threshold and enabling overheat interrupt is done in one
+ * step.
+ */
+ return armada_fw_overheat_settings(priv, trips[i].temperature,
+ trips[i].hysteresis);
+ }
+
armada_set_overheat_thresholds(priv,
trips[i].temperature,
trips[i].hysteresis);
- priv->overheat_sensor = tz;
- priv->interrupt_source = sensor_id;
armada_enable_overheat_interrupt(priv);
new file mode 100644
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2019 Marvell International Ltd.
+ */
+
+#ifndef _SOC_MARVELL_ARMADA8K_FW_H
+#define _SOC_MARVELL_ARMADA8K_FW_H
+
+/* FW related definitions */
+#define MV_SIP_DFX 0x82000014
+
+#define MV_SIP_DFX_THERMAL_INIT 1
+#define MV_SIP_DFX_THERMAL_READ 2
+#define MV_SIP_DFX_THERMAL_IS_VALID 3
+#define MV_SIP_DFX_THERMAL_IRQ 4
+#define MV_SIP_DFX_THERMAL_THRESH 5
+#define MV_SIP_DFX_THERMAL_SEL_CHANNEL 6
+
+#endif /* _SOC_MARVELL_ARMADA8K_FW_H */