summaryrefslogtreecommitdiff
path: root/driver/sb_rmi.c
blob: fbcbd990ff40073b6a66dbd0dd3d5c1d0c21c07d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
/* Copyright 2021 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

/* AMD SB-RMI (Side-band Remote Management Interface) Driver */

#include "common.h"
#include "chipset.h"
#include "i2c.h"
#include "sb_rmi.h"
#include "stdbool.h"
#include "time.h"
#include "util.h"

/* Console output macros */
#define CPUTS(outstr) cputs(CC_SYSTEM, outstr)
#define CPRINTS(format, args...) cprints(CC_SYSTEM, format, ## args)

#define SB_RMI_MAILBOX_TIMEOUT_MS 10
#define SB_RMI_MAILBOX_RETRY_DELAY_US 200

/**
 * Write an SB-RMI register
 */
static int sb_rmi_write(const int reg, int data)
{
	return i2c_write8(I2C_PORT_THERMAL_AP, SB_RMI_I2C_ADDR_FLAGS0, reg,
			  data);
}

/**
 * Read an SB-RMI register
 */
static int sb_rmi_read(const int reg, int *data)
{
	return i2c_read8(I2C_PORT_THERMAL_AP, SB_RMI_I2C_ADDR_FLAGS0, reg,
			 data);
}

/**
 * Set SB-RMI software interrupt
 */
static int sb_rmi_assert_interrupt(bool assert)
{
	return sb_rmi_write(SB_RMI_SW_INTR_REG, assert ? 0x1 : 0x0);
}


/**
 * Execute a SB-RMI mailbox transaction
 *
 * cmd:
 *	See "SB-RMI Soft Mailbox Message" table in PPR for command id
 * msg_in:
 *	Message In buffer
 * msg_out:
 *	Message Out buffer
 */
int sb_rmi_mailbox_xfer(int cmd, uint32_t msg_in, uint32_t *msg_out_ptr)
{
       /**
	* The sequence is as follows:
	* 1. The initiator (BMC) indicates that command is to be serviced by
	*    firmware by writing 0x80 to SBRMI::InBndMsg_inst7 (SBRMI_x3F). This
	*    register must be set to 0x80 after reset.
	* 2. The initiator (BMC) writes the command to SBRMI::InBndMsg_inst0
	*    (SBRMI_x38).
	* 3. For write operations or read operations which require additional
	*    addressing information as shown in the table above, the initiator
	*    (BMC) writes Command Data In[31:0] to SBRMI::InBndMsg_inst[4:1]
	*    {SBRMI_x3C(MSB):SBRMI_x39(LSB)}.
	* 4. The initiator (BMC) writes 0x01 to SBRMI::SoftwareInterrupt to
	*    notify firmware to perform the requested read or write command.
	* 5. Firmware reads the message and performs the defined action.
	* 6. Firmware writes the original command to outbound message register
	*    SBRMI::OutBndMsg_inst0 (SBRMI_x30).
	* 7. Firmware will write SBRMI::Status[SwAlertSts]=1 to generate an
	*    ALERT (if enabled) to initiator (BMC) to indicate completion of the
	*    requested command. Firmware must (if applicable) put the message
	*    data into the message registers SBRMI::OutBndMsg_inst[4:1]
	*    {SBRMI_x34(MSB):SBRMI_x31(LSB)}.
	* 8. For a read operation, the initiator (BMC) reads the firmware
	*    response Command Data Out[31:0] from SBRMI::OutBndMsg_inst[4:1]
	*    {SBRMI_x34(MSB):SBRMI_x31(LSB)}.
	* 9. Firmware clears the interrupt on SBRMI::SoftwareInterrupt.
	* 10. BMC must write 1'b1 to SBRMI::Status[SwAlertSts] to clear the
	*     ALERT to initiator (BMC). It is recommended to clear the ALERT
	*     upon completion of the current mailbox command.
	*/
	int val;
	bool alerted;
	timestamp_t start;

	if (!chipset_in_state(CHIPSET_STATE_ON))
		return EC_ERROR_NOT_POWERED;

	/**
	 * Step 1: writing 0x80 to SBRMI::InBndMsg_inst7 (SBRMI_x3F) to
	 *         indicate that command is to be serviced and to make sure
	 *         SBRMIx40[Software Interrupt] is cleared
	 */
	RETURN_ERROR(sb_rmi_write(SB_RMI_IN_BND_MSG7_REG, 0x80));
	RETURN_ERROR(sb_rmi_assert_interrupt(0));

	/* Step 2: writes the command to SBRMI::InBndMsg_inst0 (SBRMI_x38) */
	RETURN_ERROR(sb_rmi_write(SB_RMI_IN_BND_MSG0_REG, cmd));
	/* Step 3: msgIn to {SBRMI_x3C(MSB):SBRMI_x39(LSB)} */
	RETURN_ERROR(sb_rmi_write(SB_RMI_IN_BND_MSG1_REG, msg_in & 0xFF));
	RETURN_ERROR(
		sb_rmi_write(SB_RMI_IN_BND_MSG2_REG, (msg_in >> 8) & 0xFF));
	RETURN_ERROR(
		sb_rmi_write(SB_RMI_IN_BND_MSG3_REG, (msg_in >> 16) & 0xFF));
	RETURN_ERROR(
		sb_rmi_write(SB_RMI_IN_BND_MSG4_REG, (msg_in >> 24) & 0xFF));

	/**
	 * Step 4: writes 0x01 to SBRMIx40[Software Interrupt] to notify
	 *         firmware to start service.
	 */
	RETURN_ERROR(sb_rmi_assert_interrupt(1));

	/**
	 * Step 5: SoC do the service
	 * Step 6: The original command will be copied to SBRMI::OutBndMsg_inst0
	 *         (SBRMI_x30)
	 * Step 7: wait SBRMIx02[SwAlertSts] to 1 which indicate the completion
	 *         of a mailbox operation
	 */
	alerted = false;
	start = get_time();
	do {
		if (sb_rmi_read(SB_RMI_STATUS_REG, &val))
			break;
		if (val & 0x02) {
			alerted = true;
			break;
		}
		msleep(1);
	} while (time_since32(start) < SB_RMI_MAILBOX_TIMEOUT_MS * MSEC);

	if (!alerted) {
		CPRINTS("SB-SMI: Mailbox transfer timeout");
		/* Clear interrupt */
		sb_rmi_assert_interrupt(0);
		return EC_ERROR_TIMEOUT;
	}

	RETURN_ERROR(sb_rmi_read(SB_RMI_OUT_BND_MSG0_REG, &val));
	if (val != cmd) {
		CPRINTS("RMI: Unexpected command value in out bound message");
		sb_rmi_assert_interrupt(0);
		return EC_ERROR_UNKNOWN;
	}

	/* Step 8: read msgOut from {SBRMI_x34(MSB):SBRMI_x31(LSB)} */
	*msg_out_ptr = 0;
	RETURN_ERROR(sb_rmi_read(SB_RMI_OUT_BND_MSG1_REG, &val));
	*msg_out_ptr |= val;
	RETURN_ERROR(sb_rmi_read(SB_RMI_OUT_BND_MSG2_REG, &val));
	*msg_out_ptr |= val << 8;
	RETURN_ERROR(sb_rmi_read(SB_RMI_OUT_BND_MSG3_REG, &val));
	*msg_out_ptr |= val << 16;
	RETURN_ERROR(sb_rmi_read(SB_RMI_OUT_BND_MSG4_REG, &val));
	*msg_out_ptr |= val << 24;

	/* Step 9: clear SBRMIx40[Software Interrupt] */
	RETURN_ERROR(sb_rmi_assert_interrupt(0));

	/**
	 * Step 10: BMC must write 1'b1 to SBRMI::Status[SwAlertSts] to clear
	 *          the ALERT to initiator (BMC). It is recommended to clear the
	 *          ALERT upon completion of the current mailbox command.
	 */
	RETURN_ERROR(sb_rmi_write(SB_RMI_STATUS_REG, 0x2));

	/* Step 11: read the return code from OutBndMsg_inst7 (SBRMI_x37) */
	RETURN_ERROR(sb_rmi_read(SB_RMI_OUT_BND_MSG7_REG, &val));

	switch (val) {
	case SB_RMI_MAILBOX_SUCCESS:
		return EC_SUCCESS;
	case SB_RMI_MAILBOX_ERROR_ABORTED:
		return EC_ERROR_UNKNOWN;
	case SB_RMI_MAILBOX_ERROR_UNKNOWN_CMD:
		return EC_ERROR_INVAL;
	case SB_RMI_MAILBOX_ERROR_INVALID_CORE:
		return EC_ERROR_PARAM1;
	default:
		return EC_ERROR_UNKNOWN;
	}
}