blob: 4f7191b5c0857b5d640c6483c05595f99b647619 [file] [log] [blame]
/*
* Copyright (C) 2019 Marvell International Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
* https://spdx.org/licenses
*/
#include <common/debug.h>
#include <drivers/delay_timer.h>
#include <errno.h>
#include <lib/mmio.h>
#include <mvebu.h>
#include <stdbool.h>
#include "dfx.h"
/* #define DEBUG_DFX */
#ifdef DEBUG_DFX
#define debug(format...) NOTICE(format)
#else
#define debug(format, arg...)
#endif
#define TSEN_CTRL0 0xf06f8084
#define TSEN_CTRL0_START BIT(0)
#define TSEN_CTRL0_RESET BIT(1)
#define TSEN_CTRL0_ENABLE BIT(2)
#define TSEN_CTRL0_AVG_BYPASS BIT(6)
#define TSEN_CTRL0_CHAN_SHIFT 13
#define TSEN_CTRL0_CHAN_MASK 0xF
#define TSEN_CTRL0_OSR_SHIFT 24
#define TSEN_CTRL0_OSR_MAX 0x3
#define TSEN_CTRL0_MODE_SHIFT 30
#define TSEN_CTRL0_MODE_EXTERNAL 0x2U
#define TSEN_CTRL0_MODE_MASK 0x3U
#define TSEN_CTRL1 0xf06f8088
#define TSEN_CTRL1_INT_EN BIT(25)
#define TSEN_CTRL1_HYST_SHIFT 19
#define TSEN_CTRL1_HYST_MASK (0x3 << TSEN_CTRL1_HYST_SHIFT)
#define TSEN_CTRL1_THRESH_SHIFT 3
#define TSEN_CTRL1_THRESH_MASK (0x3ff << TSEN_CTRL1_THRESH_SHIFT)
#define TSEN_STATUS 0xf06f808c
#define TSEN_STATUS_VALID_OFFSET 16
#define TSEN_STATUS_VALID_MASK (0x1 << TSEN_STATUS_VALID_OFFSET)
#define TSEN_STATUS_TEMP_OUT_OFFSET 0
#define TSEN_STATUS_TEMP_OUT_MASK (0x3FF << TSEN_STATUS_TEMP_OUT_OFFSET)
#define DFX_SERVER_IRQ_SUM_MASK_REG 0xf06f8104
#define DFX_SERVER_IRQ_EN BIT(1)
#define DFX_IRQ_CAUSE_REG 0xf06f8108
#define DFX_IRQ_MASK_REG 0xf06f810c
#define DFX_IRQ_TSEN_OVERHEAT_OFFSET BIT(22)
#define THERMAL_SEN_OUTPUT_MSB 512
#define THERMAL_SEN_OUTPUT_COMP 1024
#define COEF_M 423
#define COEF_B -150000LL
static void armada_ap806_thermal_read(u_register_t *temp)
{
uint32_t reg;
reg = mmio_read_32(TSEN_STATUS);
reg = ((reg & TSEN_STATUS_TEMP_OUT_MASK) >>
TSEN_STATUS_TEMP_OUT_OFFSET);
/*
* TSEN output format is signed as a 2s complement number
* ranging from-512 to +511. when MSB is set, need to
* calculate the complement number
*/
if (reg >= THERMAL_SEN_OUTPUT_MSB)
reg -= THERMAL_SEN_OUTPUT_COMP;
*temp = ((COEF_M * ((signed int)reg)) - COEF_B);
}
static void armada_ap806_thermal_irq(void)
{
/* Dummy read, register ROC */
mmio_read_32(DFX_IRQ_CAUSE_REG);
}
static void armada_ap806_thermal_overheat_irq_init(void)
{
uint32_t reg;
/* Clear DFX temperature IRQ cause */
reg = mmio_read_32(DFX_IRQ_CAUSE_REG);
/* Enable DFX Temperature IRQ */
reg = mmio_read_32(DFX_IRQ_MASK_REG);
reg |= DFX_IRQ_TSEN_OVERHEAT_OFFSET;
mmio_write_32(DFX_IRQ_MASK_REG, reg);
/* Enable DFX server IRQ */
reg = mmio_read_32(DFX_SERVER_IRQ_SUM_MASK_REG);
reg |= DFX_SERVER_IRQ_EN;
mmio_write_32(DFX_SERVER_IRQ_SUM_MASK_REG, reg);
/* Enable overheat interrupt */
reg = mmio_read_32(TSEN_CTRL1);
reg |= TSEN_CTRL1_INT_EN;
mmio_write_32(TSEN_CTRL1, reg);
}
static unsigned int armada_mc_to_reg_temp(unsigned int temp_mc)
{
unsigned int sample;
sample = (temp_mc + COEF_B) / COEF_M;
return sample & 0x3ff;
}
/*
* The documentation states:
* high/low watermark = threshold +/- 0.4761 * 2^(hysteresis + 2)
* which is the mathematical derivation for:
* 0x0 <=> 1.9°C, 0x1 <=> 3.8°C, 0x2 <=> 7.6°C, 0x3 <=> 15.2°C
*/
static unsigned int hyst_levels_mc[] = {1900, 3800, 7600, 15200};
static unsigned int armada_mc_to_reg_hyst(int hyst_mc)
{
int i;
/*
* We will always take the smallest possible hysteresis to avoid risking
* the hardware integrity by enlarging the threshold by +8°C in the
* worst case.
*/
for (i = ARRAY_SIZE(hyst_levels_mc) - 1; i > 0; i--)
if (hyst_mc >= hyst_levels_mc[i])
break;
return i;
}
static void armada_ap806_thermal_threshold(int thresh_mc, int hyst_mc)
{
uint32_t ctrl1;
unsigned int threshold = armada_mc_to_reg_temp(thresh_mc);
unsigned int hysteresis = armada_mc_to_reg_hyst(hyst_mc);
ctrl1 = mmio_read_32(TSEN_CTRL1);
/* Set Threshold */
if (thresh_mc >= 0) {
ctrl1 &= ~(TSEN_CTRL1_THRESH_MASK);
ctrl1 |= threshold << TSEN_CTRL1_THRESH_SHIFT;
}
/* Set Hysteresis */
if (hyst_mc >= 0) {
ctrl1 &= ~(TSEN_CTRL1_HYST_MASK);
ctrl1 |= hysteresis << TSEN_CTRL1_HYST_SHIFT;
}
mmio_write_32(TSEN_CTRL1, ctrl1);
}
static void armada_select_channel(int channel)
{
uint32_t ctrl0;
/* Stop the measurements */
ctrl0 = mmio_read_32(TSEN_CTRL0);
ctrl0 &= ~TSEN_CTRL0_START;
mmio_write_32(TSEN_CTRL0, ctrl0);
/* Reset the mode, internal sensor will be automatically selected */
ctrl0 &= ~(TSEN_CTRL0_MODE_MASK << TSEN_CTRL0_MODE_SHIFT);
/* Other channels are external and should be selected accordingly */
if (channel) {
/* Change the mode to external */
ctrl0 |= TSEN_CTRL0_MODE_EXTERNAL <<
TSEN_CTRL0_MODE_SHIFT;
/* Select the sensor */
ctrl0 &= ~(TSEN_CTRL0_CHAN_MASK << TSEN_CTRL0_CHAN_SHIFT);
ctrl0 |= (channel - 1) << TSEN_CTRL0_CHAN_SHIFT;
}
/* Actually set the mode/channel */
mmio_write_32(TSEN_CTRL0, ctrl0);
/* Re-start the measurements */
ctrl0 |= TSEN_CTRL0_START;
mmio_write_32(TSEN_CTRL0, ctrl0);
}
static void armada_ap806_thermal_init(void)
{
uint32_t reg;
reg = mmio_read_32(TSEN_CTRL0);
reg &= ~TSEN_CTRL0_RESET;
reg |= TSEN_CTRL0_START | TSEN_CTRL0_ENABLE;
/* Sample every ~2ms */
reg |= TSEN_CTRL0_OSR_MAX << TSEN_CTRL0_OSR_SHIFT;
/* Enable average (2 samples by default) */
reg &= ~TSEN_CTRL0_AVG_BYPASS;
mmio_write_32(TSEN_CTRL0, reg);
debug("thermal: Initialization done\n");
}
static void armada_is_valid(u_register_t *read)
{
*read = (mmio_read_32(TSEN_STATUS) & TSEN_STATUS_VALID_MASK);
}
int mvebu_dfx_thermal_handle(u_register_t func, u_register_t *read,
u_register_t x2, u_register_t x3)
{
debug_enter();
switch (func) {
case MV_SIP_DFX_THERMAL_INIT:
armada_ap806_thermal_init();
break;
case MV_SIP_DFX_THERMAL_READ:
armada_ap806_thermal_read(read);
break;
case MV_SIP_DFX_THERMAL_IRQ:
armada_ap806_thermal_irq();
break;
case MV_SIP_DFX_THERMAL_THRESH:
armada_ap806_thermal_threshold(x2, x3);
armada_ap806_thermal_overheat_irq_init();
break;
case MV_SIP_DFX_THERMAL_IS_VALID:
armada_is_valid(read);
break;
case MV_SIP_DFX_THERMAL_SEL_CHANNEL:
armada_select_channel(x2);
break;
default:
ERROR("unsupported dfx func\n");
return -EINVAL;
}
debug_exit();
return 0;
}