summaryrefslogtreecommitdiff
path: root/src/soc/intel/common/block/imc/imc.c
blob: ccc3d5c6fb49819a9e532ba4945725e437b67e6c (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
/* SPDX-License-Identifier: GPL-2.0-only */

#include <commonlib/bsd/cb_err.h>
#include <console/console.h>
#include <delay.h>
#include <device/mmio.h>
#include <device/pci_ops.h>
#include <intelblocks/imc.h>
#include <soc/pci_devs.h>
#include <stdbool.h>
#include <timer.h>

#include "imclib.h"

#define IMC_SMBUS_TIMEOUT_MS 100

#define SMB_CMD_CFG           0x80
#define SMB_CKOVRD            BIT(29)
#define SMB_DIS_WRT           BIT(28)
#define SMB_SOFT_RST          BIT(24)
#define SMB_TSOD_POLL_EN      BIT(20)
#define SMB_CMD_TRIGGER       BIT(19)
#define SMB_WORD_ACCESS       BIT(17)
#define SMB_WRT_READ          (0 << 15)
#define SMB_WRT_WRITE         BIT(15)
#define SMB_DTI_MASK          (7 << 11)
#define SMB_CMD_SA_SHIFT      8
#define SMB_CMD_BA_SHIFT      0
#define SMB_CMD_DTI_SHIFT     11
#define SMB_STATUS_CFG        0x84
#define SMB_SBE               BIT(1)
#define SMB_BUSY              BIT(0)
#define SMB_DATA_CFG          0x88
#define SMB_PERIOD_CFG        0x90
#define SMB_CLOCK_PERIOD_400K 250 /* Clock period for 400K. */
#define SMB_CLOCK_OFFSET_400K 35  /* Clock offset for 400K. */

static void imc_spd_smbus_reset(pci_devfn_t dev)
{
	uint32_t cmd;

	cmd = pci_read_config32(dev, SMB_CMD_CFG);
	cmd &= ~SMB_CKOVRD;
	cmd |= SMB_SOFT_RST;
	pci_write_config32(dev, SMB_CMD_CFG, cmd);

	mdelay(35); /* See description of `SMB_CKOVRD` field. */

	cmd = pci_read_config32(dev, SMB_CMD_CFG);
	cmd |= SMB_CKOVRD;
	cmd &= ~SMB_SOFT_RST;
	pci_write_config32(dev, SMB_CMD_CFG, cmd);
}

void imc_spd_smbus_init(pci_devfn_t dev)
{
	uint32_t status, cmd;
	if (pci_read_config16(dev, 0) == 0xffff) {
		printk(BIOS_ERR,
		       "IMC SMBUS controller PCI: %02x:%02x:%02x.%01x isn't present!\n",
		       PCI_DEV2SEG(dev), PCI_DEV2BUS(dev), PCI_SLOT(PCI_DEV2DEVFN(dev)),
		       PCI_FUNC(PCI_DEV2DEVFN(dev)));
		return;
	}

	/* Set SMB CLOCK to 400K to detect DIMM SPDs. */
	pci_write_config32(dev, SMB_PERIOD_CFG,
			   (SMB_CLOCK_OFFSET_400K << 16) | SMB_CLOCK_PERIOD_400K);

	/* Reset the bus if the first access is busy. */
	status = pci_read_config32(dev, SMB_STATUS_CFG);
	if (status & SMB_BUSY)
		imc_spd_smbus_reset(dev);

	/* Disable TSOD polling. */
	cmd = pci_read_config32(dev, SMB_CMD_CFG);
	cmd &= ~SMB_TSOD_POLL_EN;
	pci_write_config32(dev, SMB_CMD_CFG, cmd);
}

static bool poll_ready(pci_devfn_t dev, uint32_t *status)
{
	struct stopwatch sw;

	stopwatch_init_msecs_expire(&sw, IMC_SMBUS_TIMEOUT_MS);

	do {
		*status = pci_read_config32(dev, SMB_STATUS_CFG);
		if ((*status & SMB_BUSY) == 0)
			return true;
	} while (!stopwatch_expired(&sw));

	return false;
}

static bool claim_controller(pci_devfn_t dev)
{
	uint32_t cmd, status;

	cmd = pci_read_config32(dev, SMB_CMD_CFG);
	cmd &= ~SMB_TSOD_POLL_EN;
	cmd &= ~SMB_DIS_WRT;
	pci_write_config32(dev, SMB_CMD_CFG, cmd);

	return poll_ready(dev, &status);
}

static bool release_controller(pci_devfn_t dev)
{
	uint32_t cmd, status;

	cmd = pci_read_config32(dev, SMB_CMD_CFG);
	cmd |= SMB_TSOD_POLL_EN;
	pci_write_config32(dev, SMB_CMD_CFG, cmd);

	return poll_ready(dev, &status);
}

int imc_smbus_spd_xfer(pci_devfn_t dev, uint8_t slave_addr, uint8_t bus_addr,
		       enum device_type_id dti, enum access_width width,
		       enum memory_controller_id mcid, enum smbus_command cmd, void *data)
{
	int ret = CB_ERR;
	uint32_t cmdbits, stat, databits, data_mask;
	uint16_t wdata = 0, rdata = 0;

	/* Slaves addresses are 3 bits length, and bus address is 8 bits length. */
	if (slave_addr > (1 << 7) - 1) {
		printk(BIOS_ERR, "Invalid SMBus slave 0x%02x\n", slave_addr);
		return CB_ERR;
	}

	if (!claim_controller(dev)) {
		printk(BIOS_ERR, "Claim controller failed!\n");
		return CB_ERR;
	}

	cmdbits = slave_addr << SMB_CMD_SA_SHIFT;
	cmdbits |= bus_addr << SMB_CMD_BA_SHIFT;

	if (cmd == IMC_WRITE) {
		databits = pci_read_config32(dev, SMB_DATA_CFG);
		wdata = (width == IMC_DATA_BYTE ? read8(data) : cpu_to_be16(read16(data)));
		databits |= (wdata << 16);
		pci_write_config32(dev, SMB_DATA_CFG, databits);

		cmdbits |= SMB_WRT_WRITE;
		cmdbits &= ~SMB_DIS_WRT;
	} else {
		cmdbits |= SMB_WRT_READ;
	}

	if (width == IMC_DATA_WORD) {
		cmdbits |= SMB_WORD_ACCESS;
		data_mask = 0xffff;
	} else {
		data_mask = 0xff;
	}

	cmdbits &= ~SMB_DTI_MASK;
	cmdbits |= dti << SMB_CMD_DTI_SHIFT;
	cmdbits |= SMB_CKOVRD;

	/* Pull the trigger */
	cmdbits |= SMB_CMD_TRIGGER;
	pci_write_config32(dev, SMB_CMD_CFG, cmdbits);

	if (!poll_ready(dev, &stat)) {
		printk(BIOS_ERR, "IMC transfer didn't finished for slave 0x%02x\n", slave_addr);
		ret = CB_ERR;
		goto cleanup;
	}

	if (stat & SMB_SBE) {
		printk(BIOS_ERR, "IMC SMBUS SBE for slave 0x%02x\n", slave_addr);
		ret = CB_ERR;
		goto cleanup;
	}

	if (cmd == IMC_READ) {
		databits = pci_read_config32(dev, SMB_DATA_CFG);
		rdata = databits & data_mask;
		if (width == IMC_DATA_WORD)
			write16(data, be16_to_cpu(rdata));
		else
			write8(data, rdata);
	}

	ret = CB_SUCCESS;

cleanup:
	if (!release_controller(dev)) {
		printk(BIOS_ERR, "Release controller failed!\n");
		return CB_ERR;
	}

	return ret;
}