summaryrefslogtreecommitdiffstats
path: root/src/mainboard/prodrive/hermes/eeprom.c
blob: a712156bc1ede0b6b2e9702c445ff6077359b919 (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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
/* SPDX-License-Identifier: GPL-2.0-only */

#include <assert.h>
#include <device/pci_ops.h>
#include <delay.h>
#include <console/console.h>
#include <crc_byte.h>
#include <device/smbus_host.h>
#include <soc/intel/common/block/smbus/smbuslib.h>
#include <types.h>

#include "variants/baseboard/include/eeprom.h"

#define I2C_ADDR_EEPROM 0x57

/*
 * Check Signature in EEPROM (M24C32-FMN6TP)
 * If signature is there we assume that that the content is valid
 */
int check_signature(const size_t offset, const uint64_t signature)
{
	u8 blob[8] = {0};

	if (!read_write_config(blob, offset, 0, ARRAY_SIZE(blob))) {
		/* Check signature */
		if (*(uint64_t *)blob == signature) {
			printk(BIOS_DEBUG, "CFG EEPROM: Signature valid.\n");
			return 1;
		}
		printk(BIOS_DEBUG, "CFG EEPROM: Signature invalid - skipping option write.\n");
		return 0;
	}
	return 0;
}

/*
 * Read board settings from the EEPROM and verify their checksum.
 * If checksum is valid, we assume the settings are sane as well.
 */
static bool get_board_settings_from_eeprom(struct eeprom_board_settings *board_cfg)
{
	const size_t board_settings_offset = offsetof(struct eeprom_layout, BoardSettings);

	if (read_write_config(board_cfg, board_settings_offset, 0, sizeof(*board_cfg))) {
		printk(BIOS_ERR, "CFG EEPROM: Failed to read board settings\n");
		return false;
	}

	const uint32_t crc =
		CRC(&board_cfg->raw_settings, sizeof(board_cfg->raw_settings), crc32_byte);

	if (crc != board_cfg->signature) {
		printk(BIOS_ERR, "CFG EEPROM: Board settings have invalid checksum\n");
		return false;
	}
	return true;
}

struct eeprom_board_settings *get_board_settings(void)
{
	static struct eeprom_board_settings board_cfg = {0};

	/* Tri-state: -1: settings are invalid, 0: uninitialized, 1: settings are valid */
	static int checked_valid = 0;

	if (checked_valid == 0) {
		const bool success = get_board_settings_from_eeprom(&board_cfg);
		checked_valid = success ? 1 : -1;
	}
	return checked_valid > 0 ? &board_cfg : NULL;
}

struct eeprom_bmc_settings *get_bmc_settings(void)
{
	const size_t bmc_settings_offset = offsetof(struct eeprom_layout, BMCSettings);
	static struct eeprom_bmc_settings bmc_cfg = {0};

	/* 0: uninitialized, 1: settings are valid */
	static int valid = 0;

	if (valid == 0) {
		if (read_write_config(&bmc_cfg, bmc_settings_offset, 0, sizeof(bmc_cfg))) {
			printk(BIOS_ERR, "CFG EEPROM: Failed to read BMC settings\n");
			return NULL;
		}
		valid = 1;
	}
	return &bmc_cfg;
}

uint8_t get_bmc_hsi(void)
{
	uint8_t hsi = 0;
	struct eeprom_bmc_settings *s = get_bmc_settings();
	if (s)
		hsi = s->hsi;
	printk(BIOS_DEBUG, "CFG EEPROM: HSI 0x%x\n", hsi);

	return hsi;
}

/* Read data from offset and write it to offset in UPD */
bool read_write_config(void *blob, size_t read_offset, size_t write_offset, size_t size)
{
	int ret = 0;

	u32 smb_ctrl_reg = pci_read_config32(PCH_DEV_SMBUS, HOSTC);
	pci_write_config32(PCH_DEV_SMBUS, HOSTC, smb_ctrl_reg | I2C_EN);

	printk(BIOS_SPEW, "%s\tOffset: %04zx\tSize: %02zx\n", __func__,
		read_offset, size);

	/* We can always read two bytes at a time */
	for (size_t i = 0; i < size; i = i + 2) {
		u8 tmp[2] = {0};

		ret = do_smbus_process_call(SMBUS_IO_BASE, I2C_ADDR_EEPROM, 0,
			swab16(read_offset + i), (uint16_t *)&tmp[0]);
		if (ret < 0)
			break;

		/* Write to UPD */
		uint8_t *writePointer = (uint8_t *)blob + write_offset + i;
		writePointer[0] = tmp[0];
		if (size - i > 1)
			writePointer[1] = tmp[1];
	}

	/* Restore I2C_EN bit */
	pci_write_config32(PCH_DEV_SMBUS, HOSTC, smb_ctrl_reg);

	return ret;
}

void report_eeprom_error(const size_t off)
{
	printk(BIOS_ERR, "MB: Failed to read from EEPROM at addr. 0x%zx\n", off);
}

/*
 * Write a single byte into the EEPROM at specified offset.
 * Returns true on error, false on success.
 */
static bool write_byte_eeprom(const uint8_t data, const uint16_t write_offset)
{
	int ret = 0;

	printk(BIOS_SPEW, "CFG EEPROM: Writing %x at %x\n", data, write_offset);

	const uint32_t smb_ctrl_reg = pci_read_config32(PCH_DEV_SMBUS, HOSTC);
	pci_write_config32(PCH_DEV_SMBUS, HOSTC, smb_ctrl_reg | I2C_EN);

	/*
	 * The EEPROM expects two address bytes.
	 * Use the first byte of the block data as second address byte.
	 */
	uint8_t buffer[2] = {
		write_offset & 0xff,
		data,
	};

	for (size_t retry = 3; retry > 0; retry--) {
		/* The EEPROM NACKs request when busy writing */
		ret = do_smbus_block_write(SMBUS_IO_BASE, I2C_ADDR_EEPROM,
					   (write_offset >> 8) & 0xff,
					   sizeof(buffer), buffer);
		if (ret == sizeof(buffer))
			break;
		/* Maximum of 5 milliseconds write duration */
		mdelay(5);
	}

	/* Restore I2C_EN bit */
	pci_write_config32(PCH_DEV_SMBUS, HOSTC, smb_ctrl_reg);

	return ret != sizeof(buffer);
}

/*
 * Write board layout if it has changed into EEPROM.
 * Return true on error, false on success.
 */
bool write_board_settings(const struct eeprom_board_layout *new_layout)
{
	const size_t off = offsetof(struct eeprom_layout, BoardLayout);
	struct eeprom_board_layout old_layout = {0};
	bool ret = false;
	bool changed = false;

	/* Read old settings */
	if (read_write_config(&old_layout, off, 0, sizeof(old_layout))) {
		printk(BIOS_ERR, "CFG EEPROM: Read operation failed\n");
		return true;
	}

	assert(sizeof(old_layout) == sizeof(*new_layout));
	const uint8_t *const old = (const uint8_t *)&old_layout;
	const uint8_t *const new = (const uint8_t *)new_layout;

	/* Compare with new settings and only write changed bytes */
	for (size_t i = 0; i < sizeof(old_layout); i++) {
		if (old[i] != new[i]) {
			changed = true;
			if (write_byte_eeprom(new[i], off + i)) {
				printk(BIOS_ERR, "CFG EEPROM: Write operation failed\n");
				ret = true;
				break;
			}
		}
	}

	printk(BIOS_DEBUG, "CFG EEPROM: Board Layout up%s\n", changed ? "dated" : " to date");

	return ret;
}