scom.c
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 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 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 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 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 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 }