summaryrefslogtreecommitdiffstats
path: root/src/soc/intel/common/block/imc/imc.c
blob: 517e62eb75696d23f5db6fe4bca1049618b26290 (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
/* SPDX-License-Identifier: GPL-2.0-only */
/* This file is part of the coreboot project. */

/*
 * Please note: the driver uses MMIO PCIe register access. IO based access will
 * not work.
 */

#include <console/console.h>
#include <delay.h>
#include <device/pci_ops.h>
#include <intelblocks/imc.h>
#include <soc/pci_devs.h>
#include <timer.h>

#define IMC_SMBUS_TIMEOUT_MS           100
#define IMC_SMBCNTL_DTI_TSOD           0x3
#define IMC_SMBCNTL_DTI_EEPROM         0xa
#define IMC_SMBCNTL_DTI_WP_EEPROM      0x6

#define SMBSTAT(i)                     (0x180 + 0x10 * i)
#define SMBCMD(i)                      (0x184 + 0x10 * i)
#define SMBCNTL(i)                     (0x188 + 0x10 * i)

#define SMBSTAT_RDO                    (1u << 31)  /* Read Data Valid */
#define SMBSTAT_WOD                    (1u << 30)  /* Write Operation Done */
#define SMBSTAT_SBE                    (1u << 29)  /* SMBus Error */
#define SMBSTAT_SMB_BUSY               (1u << 28)  /* SMBus Busy State */

#define SMBCMD_TRIGGER                 (1u << 31)  /* CMD Trigger */
#define SMBCMD_PNTR_SEL                (1u << 30)  /* HW polls TSOD with pointer */
#define SMBCMD_WORD_ACCESS             (1u << 29)  /* word (vs byte) access */
#define SMBCMD_TYPE_MASK               (3u << 27)  /* Mask for access type */
#define SMBCMD_TYPE_READ               (0u << 27)  /* Read */
#define SMBCMD_TYPE_WRITE              (1u << 27)  /* Write */
#define SMBCMD_TYPE_PNTR_WRITE         (3u << 27)  /* Write to pointer */
#define SMBCMD_SA_MASK                 (7u << 24)  /* Slave Address high bits */
#define SMBCMD_SA_SHIFT                24
#define SMBCMD_BA_MASK                 0xff0000    /* Bus Txn address */
#define SMBCMD_BA_SHIFT                16
#define SMBCMD_WDATA_MASK              0xffff      /* data to write */

#define SMBCNTL_DTI_MASK               0xf0000000  /* Slave Address low bits */
#define SMBCNTL_DTI_SHIFT              28          /* Slave Address low bits */
#define SMBCNTL_CKOVRD                 (1u << 27)  /* # Clock Override */
#define SMBCNTL_DIS_WRT                (1u << 26)  /* Disable Write (sadly) */
#define SMBCNTL_SOFT_RST               (1u << 10)  /* Soft Reset */
#define SMBCNTL_TSOD_POLL_EN           (1u << 8)   /* TSOD Polling Enable */

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

	stopwatch_init_msecs_expire(&sw, IMC_SMBUS_TIMEOUT_MS);

	do {
		*status = pci_mmio_read_config32(dev, SMBSTAT(mcid));
		if (!(*status & SMBSTAT_SMB_BUSY))
			break;
	} while (!stopwatch_expired(&sw));

	return (!(*status & SMBSTAT_SMB_BUSY));
}

static bool claim_controller(pci_devfn_t dev, enum memory_controller_id mcid)
{
	uint32_t cntl, status;

	cntl = pci_mmio_read_config32(dev, SMBCNTL(mcid));
	cntl &= ~SMBCNTL_TSOD_POLL_EN;
	cntl &= ~SMBCNTL_DIS_WRT;
	pci_mmio_write_config32(dev, SMBCNTL(mcid), cntl);

	return poll_ready(dev, mcid, &status);
}


static void release_controller(pci_devfn_t dev, enum memory_controller_id mcid)
{
	uint32_t cntl, status;

	cntl = pci_mmio_read_config32(dev, SMBCNTL(mcid));
	cntl |= SMBCNTL_TSOD_POLL_EN;
	pci_mmio_write_config32(dev, SMBCNTL(mcid), cntl);

	poll_ready(dev, mcid, &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 = -1;
	uint32_t cmdbits = 0, stat = 0, cntlbits = 0, data_mask = 0;
	uint16_t wdata = 0, rdata = 0;

	/* slaves addresses are 7 bits length */
	if (slave_addr > (1 << 7) - 1) {
		printk(BIOS_ERR, "invalid SMBus address, aborting xfer\n");
		return -1;
	}

	if (!claim_controller(dev, mcid)) {
		printk(BIOS_ERR, "ayee! couldn't claim controller, giving up xfer\n");
		return -1;
	}

	cmdbits = (slave_addr << SMBCMD_SA_SHIFT);
	cmdbits |= (bus_addr << SMBCMD_BA_SHIFT);

	if (cmd == IMC_WRITE) {
		wdata = (width == IMC_DATA_BYTE ? read8(data) : cpu_to_be16(read16(data)));
		cmdbits |= (SMBCMD_TYPE_WRITE | wdata);
	} else {
		cmdbits |= SMBCMD_TYPE_READ;
	}

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

	cntlbits = pci_mmio_read_config32(dev, SMBCNTL(mcid));
	cntlbits &= ~SMBCNTL_DTI_MASK;
	cntlbits |= (dti << SMBCNTL_DTI_SHIFT);

	pci_mmio_write_config32(dev, SMBCNTL(mcid), cntlbits);

	/* Pull the trigger */
	cmdbits |= SMBCMD_TRIGGER;
	pci_mmio_write_config32(dev, SMBCMD(mcid), cmdbits);

	if (!poll_ready(dev, mcid, &stat)) {
		printk(BIOS_ERR, "IMC xfer failed for slave %x", slave_addr);
		ret = -1;
		goto cleanup;
	}

	if (stat & SMBSTAT_SBE) {
		ret = -1;
		goto cleanup;
	}

	if (cmd == IMC_READ) {
		rdata = stat & data_mask;
		if (width == IMC_DATA_WORD)
			write16(data, cpu_to_be16(rdata));
		else
			write8(data, rdata);
	}

	ret = 0;
cleanup:
	release_controller(dev, SMBSTAT(mcid));

	return ret;
}