common/block/cse: Add Kconfig to support ME specification version 21
[coreboot.git] / src / soc / intel / common / block / imc / imc.c
blobccc3d5c6fb49819a9e532ba4945725e437b67e6c
1 /* SPDX-License-Identifier: GPL-2.0-only */
3 #include <commonlib/bsd/cb_err.h>
4 #include <console/console.h>
5 #include <delay.h>
6 #include <device/mmio.h>
7 #include <device/pci_ops.h>
8 #include <intelblocks/imc.h>
9 #include <soc/pci_devs.h>
10 #include <stdbool.h>
11 #include <timer.h>
13 #include "imclib.h"
15 #define IMC_SMBUS_TIMEOUT_MS 100
17 #define SMB_CMD_CFG 0x80
18 #define SMB_CKOVRD BIT(29)
19 #define SMB_DIS_WRT BIT(28)
20 #define SMB_SOFT_RST BIT(24)
21 #define SMB_TSOD_POLL_EN BIT(20)
22 #define SMB_CMD_TRIGGER BIT(19)
23 #define SMB_WORD_ACCESS BIT(17)
24 #define SMB_WRT_READ (0 << 15)
25 #define SMB_WRT_WRITE BIT(15)
26 #define SMB_DTI_MASK (7 << 11)
27 #define SMB_CMD_SA_SHIFT 8
28 #define SMB_CMD_BA_SHIFT 0
29 #define SMB_CMD_DTI_SHIFT 11
30 #define SMB_STATUS_CFG 0x84
31 #define SMB_SBE BIT(1)
32 #define SMB_BUSY BIT(0)
33 #define SMB_DATA_CFG 0x88
34 #define SMB_PERIOD_CFG 0x90
35 #define SMB_CLOCK_PERIOD_400K 250 /* Clock period for 400K. */
36 #define SMB_CLOCK_OFFSET_400K 35 /* Clock offset for 400K. */
38 static void imc_spd_smbus_reset(pci_devfn_t dev)
40 uint32_t cmd;
42 cmd = pci_read_config32(dev, SMB_CMD_CFG);
43 cmd &= ~SMB_CKOVRD;
44 cmd |= SMB_SOFT_RST;
45 pci_write_config32(dev, SMB_CMD_CFG, cmd);
47 mdelay(35); /* See description of `SMB_CKOVRD` field. */
49 cmd = pci_read_config32(dev, SMB_CMD_CFG);
50 cmd |= SMB_CKOVRD;
51 cmd &= ~SMB_SOFT_RST;
52 pci_write_config32(dev, SMB_CMD_CFG, cmd);
55 void imc_spd_smbus_init(pci_devfn_t dev)
57 uint32_t status, cmd;
58 if (pci_read_config16(dev, 0) == 0xffff) {
59 printk(BIOS_ERR,
60 "IMC SMBUS controller PCI: %02x:%02x:%02x.%01x isn't present!\n",
61 PCI_DEV2SEG(dev), PCI_DEV2BUS(dev), PCI_SLOT(PCI_DEV2DEVFN(dev)),
62 PCI_FUNC(PCI_DEV2DEVFN(dev)));
63 return;
66 /* Set SMB CLOCK to 400K to detect DIMM SPDs. */
67 pci_write_config32(dev, SMB_PERIOD_CFG,
68 (SMB_CLOCK_OFFSET_400K << 16) | SMB_CLOCK_PERIOD_400K);
70 /* Reset the bus if the first access is busy. */
71 status = pci_read_config32(dev, SMB_STATUS_CFG);
72 if (status & SMB_BUSY)
73 imc_spd_smbus_reset(dev);
75 /* Disable TSOD polling. */
76 cmd = pci_read_config32(dev, SMB_CMD_CFG);
77 cmd &= ~SMB_TSOD_POLL_EN;
78 pci_write_config32(dev, SMB_CMD_CFG, cmd);
81 static bool poll_ready(pci_devfn_t dev, uint32_t *status)
83 struct stopwatch sw;
85 stopwatch_init_msecs_expire(&sw, IMC_SMBUS_TIMEOUT_MS);
87 do {
88 *status = pci_read_config32(dev, SMB_STATUS_CFG);
89 if ((*status & SMB_BUSY) == 0)
90 return true;
91 } while (!stopwatch_expired(&sw));
93 return false;
96 static bool claim_controller(pci_devfn_t dev)
98 uint32_t cmd, status;
100 cmd = pci_read_config32(dev, SMB_CMD_CFG);
101 cmd &= ~SMB_TSOD_POLL_EN;
102 cmd &= ~SMB_DIS_WRT;
103 pci_write_config32(dev, SMB_CMD_CFG, cmd);
105 return poll_ready(dev, &status);
108 static bool release_controller(pci_devfn_t dev)
110 uint32_t cmd, status;
112 cmd = pci_read_config32(dev, SMB_CMD_CFG);
113 cmd |= SMB_TSOD_POLL_EN;
114 pci_write_config32(dev, SMB_CMD_CFG, cmd);
116 return poll_ready(dev, &status);
119 int imc_smbus_spd_xfer(pci_devfn_t dev, uint8_t slave_addr, uint8_t bus_addr,
120 enum device_type_id dti, enum access_width width,
121 enum memory_controller_id mcid, enum smbus_command cmd, void *data)
123 int ret = CB_ERR;
124 uint32_t cmdbits, stat, databits, data_mask;
125 uint16_t wdata = 0, rdata = 0;
127 /* Slaves addresses are 3 bits length, and bus address is 8 bits length. */
128 if (slave_addr > (1 << 7) - 1) {
129 printk(BIOS_ERR, "Invalid SMBus slave 0x%02x\n", slave_addr);
130 return CB_ERR;
133 if (!claim_controller(dev)) {
134 printk(BIOS_ERR, "Claim controller failed!\n");
135 return CB_ERR;
138 cmdbits = slave_addr << SMB_CMD_SA_SHIFT;
139 cmdbits |= bus_addr << SMB_CMD_BA_SHIFT;
141 if (cmd == IMC_WRITE) {
142 databits = pci_read_config32(dev, SMB_DATA_CFG);
143 wdata = (width == IMC_DATA_BYTE ? read8(data) : cpu_to_be16(read16(data)));
144 databits |= (wdata << 16);
145 pci_write_config32(dev, SMB_DATA_CFG, databits);
147 cmdbits |= SMB_WRT_WRITE;
148 cmdbits &= ~SMB_DIS_WRT;
149 } else {
150 cmdbits |= SMB_WRT_READ;
153 if (width == IMC_DATA_WORD) {
154 cmdbits |= SMB_WORD_ACCESS;
155 data_mask = 0xffff;
156 } else {
157 data_mask = 0xff;
160 cmdbits &= ~SMB_DTI_MASK;
161 cmdbits |= dti << SMB_CMD_DTI_SHIFT;
162 cmdbits |= SMB_CKOVRD;
164 /* Pull the trigger */
165 cmdbits |= SMB_CMD_TRIGGER;
166 pci_write_config32(dev, SMB_CMD_CFG, cmdbits);
168 if (!poll_ready(dev, &stat)) {
169 printk(BIOS_ERR, "IMC transfer didn't finished for slave 0x%02x\n", slave_addr);
170 ret = CB_ERR;
171 goto cleanup;
174 if (stat & SMB_SBE) {
175 printk(BIOS_ERR, "IMC SMBUS SBE for slave 0x%02x\n", slave_addr);
176 ret = CB_ERR;
177 goto cleanup;
180 if (cmd == IMC_READ) {
181 databits = pci_read_config32(dev, SMB_DATA_CFG);
182 rdata = databits & data_mask;
183 if (width == IMC_DATA_WORD)
184 write16(data, be16_to_cpu(rdata));
185 else
186 write8(data, rdata);
189 ret = CB_SUCCESS;
191 cleanup:
192 if (!release_controller(dev)) {
193 printk(BIOS_ERR, "Release controller failed!\n");
194 return CB_ERR;
197 return ret;