28eaa4a340
Reset function, constants and include are not used outside of scom.c and not going to be. Change-Id: Iff4e98ae52c7099954f0c20fcb639eb87af15534 Signed-off-by: Sergii Dmytruk <sergii.dmytruk@3mdeb.com> Reviewed-on: https://review.coreboot.org/c/coreboot/+/67055 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Michał Kopeć <michal.kopec@3mdeb.com>
137 lines
3.7 KiB
C
137 lines
3.7 KiB
C
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
#include <cpu/power/scom.h>
|
|
#include <cpu/power/spr.h> // HMER
|
|
#include <console/console.h>
|
|
|
|
#define XSCOM_ADDR_IND_ADDR PPC_BITMASK(11, 31)
|
|
#define XSCOM_ADDR_IND_DATA PPC_BITMASK(48, 63)
|
|
|
|
#define XSCOM_DATA_IND_READ PPC_BIT(0)
|
|
#define XSCOM_DATA_IND_COMPLETE PPC_BIT(32)
|
|
#define XSCOM_DATA_IND_ERR PPC_BITMASK(33, 35)
|
|
#define XSCOM_DATA_IND_DATA PPC_BITMASK(48, 63)
|
|
#define XSCOM_DATA_IND_FORM1_DATA PPC_BITMASK(12, 63)
|
|
#define XSCOM_IND_MAX_RETRIES 10
|
|
|
|
#define XSCOM_RCVED_STAT_REG 0x00090018
|
|
#define XSCOM_LOG_REG 0x00090012
|
|
#define XSCOM_ERR_REG 0x00090013
|
|
|
|
static void reset_scom_engine(void)
|
|
{
|
|
/*
|
|
* With cross-CPU SCOM accesses, first register should be cleared on the
|
|
* executing CPU, the other two on target CPU. In that case it may be
|
|
* necessary to do the remote writes in assembly directly to skip checking
|
|
* HMER and possibly end in a loop.
|
|
*/
|
|
write_scom_direct(XSCOM_RCVED_STAT_REG, 0);
|
|
write_scom_direct(XSCOM_LOG_REG, 0);
|
|
write_scom_direct(XSCOM_ERR_REG, 0);
|
|
clear_hmer();
|
|
eieio();
|
|
}
|
|
|
|
uint64_t read_scom_direct(uint64_t reg_address)
|
|
{
|
|
uint64_t val;
|
|
uint64_t hmer = 0;
|
|
do {
|
|
/*
|
|
* Clearing HMER on every SCOM access seems to slow down CCS up
|
|
* to a point where it starts hitting timeout on "less ideal"
|
|
* DIMMs for write centering. Clear it only if this do...while
|
|
* executes more than once.
|
|
*/
|
|
if ((hmer & SPR_HMER_XSCOM_STATUS) == SPR_HMER_XSCOM_OCCUPIED)
|
|
clear_hmer();
|
|
|
|
eieio();
|
|
asm volatile(
|
|
"ldcix %0, %1, %2" :
|
|
"=r"(val) :
|
|
"b"(MMIO_GROUP0_CHIP0_SCOM_BASE_ADDR),
|
|
"r"(reg_address << 3));
|
|
eieio();
|
|
hmer = read_hmer();
|
|
} while ((hmer & SPR_HMER_XSCOM_STATUS) == SPR_HMER_XSCOM_OCCUPIED);
|
|
|
|
if (hmer & SPR_HMER_XSCOM_STATUS) {
|
|
reset_scom_engine();
|
|
/*
|
|
* All F's are returned in case of error, but code polls for a set bit
|
|
* after changes that can make such error appear (e.g. clock settings).
|
|
* Return 0 so caller won't have to test for all F's in that case.
|
|
*/
|
|
return 0;
|
|
}
|
|
return val;
|
|
}
|
|
|
|
void write_scom_direct(uint64_t reg_address, uint64_t data)
|
|
{
|
|
uint64_t hmer = 0;
|
|
do {
|
|
/* See comment in read_scom_direct() */
|
|
if ((hmer & SPR_HMER_XSCOM_STATUS) == SPR_HMER_XSCOM_OCCUPIED)
|
|
clear_hmer();
|
|
|
|
eieio();
|
|
asm volatile(
|
|
"stdcix %0, %1, %2"::
|
|
"r"(data),
|
|
"b"(MMIO_GROUP0_CHIP0_SCOM_BASE_ADDR),
|
|
"r"(reg_address << 3));
|
|
eieio();
|
|
hmer = read_hmer();
|
|
} while ((hmer & SPR_HMER_XSCOM_STATUS) == SPR_HMER_XSCOM_OCCUPIED);
|
|
|
|
if (hmer & SPR_HMER_XSCOM_STATUS)
|
|
reset_scom_engine();
|
|
}
|
|
|
|
void write_scom_indirect(uint64_t reg_address, uint64_t value)
|
|
{
|
|
uint64_t addr;
|
|
uint64_t data;
|
|
addr = reg_address & 0x7FFFFFFF;
|
|
data = reg_address & XSCOM_ADDR_IND_ADDR;
|
|
data |= value & XSCOM_ADDR_IND_DATA;
|
|
|
|
write_scom_direct(addr, data);
|
|
|
|
for (int retries = 0; retries < XSCOM_IND_MAX_RETRIES; ++retries) {
|
|
data = read_scom_direct(addr);
|
|
if ((data & XSCOM_DATA_IND_COMPLETE) && ((data & XSCOM_DATA_IND_ERR) == 0)) {
|
|
return;
|
|
} else if (data & XSCOM_DATA_IND_COMPLETE) {
|
|
printk(BIOS_EMERG, "SCOM WR error %16.16llx = %16.16llx : %16.16llx\n",
|
|
reg_address, value, data);
|
|
}
|
|
// TODO: delay?
|
|
}
|
|
}
|
|
|
|
uint64_t read_scom_indirect(uint64_t reg_address)
|
|
{
|
|
uint64_t addr;
|
|
uint64_t data;
|
|
addr = reg_address & 0x7FFFFFFF;
|
|
data = XSCOM_DATA_IND_READ | (reg_address & XSCOM_ADDR_IND_ADDR);
|
|
|
|
write_scom_direct(addr, data);
|
|
|
|
for (int retries = 0; retries < XSCOM_IND_MAX_RETRIES; ++retries) {
|
|
data = read_scom_direct(addr);
|
|
if ((data & XSCOM_DATA_IND_COMPLETE) && ((data & XSCOM_DATA_IND_ERR) == 0)) {
|
|
break;
|
|
} else if (data & XSCOM_DATA_IND_COMPLETE) {
|
|
printk(BIOS_EMERG, "SCOM RD error %16.16llx : %16.16llx\n",
|
|
reg_address, data);
|
|
}
|
|
// TODO: delay?
|
|
}
|
|
|
|
return data & XSCOM_DATA_IND_DATA;
|
|
}
|