xref: /aosp_15_r20/external/coreboot/src/cpu/power9/scom.c (revision b9411a12aaaa7e1e6a6fb7c5e057f44ee179a49c)
1 /* SPDX-License-Identifier: GPL-2.0-or-later */
2 
3 #include <cpu/power/scom.h>
4 #include <cpu/power/spr.h>		// HMER
5 #include <console/console.h>
6 
7 #define XSCOM_ADDR_IND_ADDR			PPC_BITMASK(11, 31)
8 #define XSCOM_ADDR_IND_DATA			PPC_BITMASK(48, 63)
9 
10 #define XSCOM_DATA_IND_READ			PPC_BIT(0)
11 #define XSCOM_DATA_IND_COMPLETE			PPC_BIT(32)
12 #define XSCOM_DATA_IND_ERR			PPC_BITMASK(33, 35)
13 #define XSCOM_DATA_IND_DATA			PPC_BITMASK(48, 63)
14 #define XSCOM_DATA_IND_FORM1_DATA		PPC_BITMASK(12, 63)
15 #define XSCOM_IND_MAX_RETRIES			10
16 
17 #define XSCOM_RCVED_STAT_REG			0x00090018
18 #define XSCOM_LOG_REG				0x00090012
19 #define XSCOM_ERR_REG				0x00090013
20 
reset_scom_engine(void)21 static void reset_scom_engine(void)
22 {
23 	/*
24 	 * With cross-CPU SCOM accesses, first register should be cleared on the
25 	 * executing CPU, the other two on target CPU. In that case it may be
26 	 * necessary to do the remote writes in assembly directly to skip checking
27 	 * HMER and possibly end in a loop.
28 	 */
29 	write_scom_direct(XSCOM_RCVED_STAT_REG, 0);
30 	write_scom_direct(XSCOM_LOG_REG, 0);
31 	write_scom_direct(XSCOM_ERR_REG, 0);
32 	clear_hmer();
33 	eieio();
34 }
35 
read_scom_direct(uint64_t reg_address)36 uint64_t read_scom_direct(uint64_t reg_address)
37 {
38 	uint64_t val;
39 	uint64_t hmer = 0;
40 	do {
41 		/*
42 		 * Clearing HMER on every SCOM access seems to slow down CCS up
43 		 * to a point where it starts hitting timeout on "less ideal"
44 		 * DIMMs for write centering. Clear it only if this do...while
45 		 * executes more than once.
46 		 */
47 		if ((hmer & SPR_HMER_XSCOM_STATUS) == SPR_HMER_XSCOM_OCCUPIED)
48 			clear_hmer();
49 
50 		eieio();
51 		asm volatile(
52 			"ldcix %0, %1, %2" :
53 			"=r"(val) :
54 			"b"(MMIO_GROUP0_CHIP0_SCOM_BASE_ADDR),
55 			"r"(reg_address << 3));
56 		eieio();
57 		hmer = read_hmer();
58 	} while ((hmer & SPR_HMER_XSCOM_STATUS) == SPR_HMER_XSCOM_OCCUPIED);
59 
60 	if (hmer & SPR_HMER_XSCOM_STATUS) {
61 		reset_scom_engine();
62 		/*
63 		 * All F's are returned in case of error, but code polls for a set bit
64 		 * after changes that can make such error appear (e.g. clock settings).
65 		 * Return 0 so caller won't have to test for all F's in that case.
66 		 */
67 		return 0;
68 	}
69 	return val;
70 }
71 
write_scom_direct(uint64_t reg_address,uint64_t data)72 void write_scom_direct(uint64_t reg_address, uint64_t data)
73 {
74 	uint64_t hmer = 0;
75 	do {
76 		/* See comment in read_scom_direct() */
77 		if ((hmer & SPR_HMER_XSCOM_STATUS) == SPR_HMER_XSCOM_OCCUPIED)
78 			clear_hmer();
79 
80 		eieio();
81 		asm volatile(
82 			"stdcix %0, %1, %2"::
83 			"r"(data),
84 			"b"(MMIO_GROUP0_CHIP0_SCOM_BASE_ADDR),
85 			"r"(reg_address << 3));
86 		eieio();
87 		hmer = read_hmer();
88 	} while ((hmer & SPR_HMER_XSCOM_STATUS) == SPR_HMER_XSCOM_OCCUPIED);
89 
90 	if (hmer & SPR_HMER_XSCOM_STATUS)
91 		reset_scom_engine();
92 }
93 
write_scom_indirect(uint64_t reg_address,uint64_t value)94 void write_scom_indirect(uint64_t reg_address, uint64_t value)
95 {
96 	uint64_t addr;
97 	uint64_t data;
98 	addr = reg_address & 0x7FFFFFFF;
99 	data = reg_address & XSCOM_ADDR_IND_ADDR;
100 	data |= value & XSCOM_ADDR_IND_DATA;
101 
102 	write_scom_direct(addr, data);
103 
104 	for (int retries = 0; retries < XSCOM_IND_MAX_RETRIES; ++retries) {
105 		data = read_scom_direct(addr);
106 		if ((data & XSCOM_DATA_IND_COMPLETE) && ((data & XSCOM_DATA_IND_ERR) == 0)) {
107 			return;
108 		} else if (data & XSCOM_DATA_IND_COMPLETE) {
109 			printk(BIOS_EMERG, "SCOM WR error  %16.16llx = %16.16llx : %16.16llx\n",
110 			       reg_address, value, data);
111 		}
112 		// TODO: delay?
113 	}
114 }
115 
read_scom_indirect(uint64_t reg_address)116 uint64_t read_scom_indirect(uint64_t reg_address)
117 {
118 	uint64_t addr;
119 	uint64_t data;
120 	addr = reg_address & 0x7FFFFFFF;
121 	data = XSCOM_DATA_IND_READ | (reg_address & XSCOM_ADDR_IND_ADDR);
122 
123 	write_scom_direct(addr, data);
124 
125 	for (int retries = 0; retries < XSCOM_IND_MAX_RETRIES; ++retries) {
126 		data = read_scom_direct(addr);
127 		if ((data & XSCOM_DATA_IND_COMPLETE) && ((data & XSCOM_DATA_IND_ERR) == 0)) {
128 			break;
129 		} else if (data & XSCOM_DATA_IND_COMPLETE) {
130 			printk(BIOS_EMERG, "SCOM RD error  %16.16llx : %16.16llx\n",
131 			       reg_address, data);
132 		}
133 		// TODO: delay?
134 	}
135 
136 	return data & XSCOM_DATA_IND_DATA;
137 }
138