/ src / cpu / power9 / scom.c
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  }