summaryrefslogtreecommitdiff
path: root/src/northbridge/intel/haswell/vcu_mailbox.c
blob: 196b720c821625b06f64d56f682fc567c56e6852 (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
/* SPDX-License-Identifier: GPL-2.0-or-later */

#include <assert.h>
#include <console/console.h>
#include <delay.h>
#include <northbridge/intel/haswell/haswell.h>
#include <northbridge/intel/haswell/vcu_mailbox.h>
#include <types.h>

/*
 * This is a library for the VCU (Validation Control Unit) mailbox. This
 * mailbox is primarily used to adjust some magic PCIe tuning parameters.
 *
 * There are two revisions of the VCU mailbox. Rev1 is specific to Haswell
 * stepping A0, and all other steppings use Rev2. Haswell stepping A0 CPUs
 * are early Engineering Samples with undocumented errata, and most likely
 * need special microcode updates to boot. Thus, the code does not support
 * VCU mailbox Rev1, because no one should need it anymore.
 */

#define VCU_MAILBOX_INTERFACE	0x6c00
#define VCU_MAILBOX_DATA	0x6c04

#define VCU_RUN_BUSY		(1U << 31)

enum vcu_opcode {
	VCU_INVALID_OPCODE		= 0x00,
	VCU_OPCODE_READ_VCU_API_VER_ID	= 0x01,
	VCU_OPCODE_OPEN_SEQ		= 0x02,
	VCU_OPCODE_CLOSE_SEQ		= 0x03,
	VCU_OPCODE_READ_DATA		= 0x07,
	VCU_OPCODE_WRITE_DATA		= 0x08,
	VCU_OPCODE_READ_CSR		= 0x13,
	VCU_OPCODE_WRITE_CSR		= 0x14,
	VCU_OPCODE_READ_MMIO		= 0x15,
	VCU_OPCODE_WRITE_MMIO		= 0x16,
};

enum vcu_sequence {
	SEQ_ID_READ_CSR		= 0x1,
	SEQ_ID_WRITE_CSR	= 0x2,
	SEQ_ID_READ_MMIO	= 0x3,
	SEQ_ID_WRITE_MMIO	= 0x4,
};

#define VCU_RESPONSE_MASK		0xffff
#define VCU_RESPONSE_SUCCESS		0x40
#define VCU_RESPONSE_BUSY		0x80
#define VCU_RESPONSE_THREAD_UNAVAILABLE	0x82
#define VCU_RESPONSE_ILLEGAL		0x90

/* FIXME: Use timer API */
static void send_vcu_command(const enum vcu_opcode opcode, const uint32_t data)
{
	if (opcode == VCU_INVALID_OPCODE)
		return;

	for (unsigned int i = 0; i < 10; i++) {
		mchbar_write32(VCU_MAILBOX_DATA, data);
		mchbar_write32(VCU_MAILBOX_INTERFACE, opcode | VCU_RUN_BUSY);
		uint32_t vcu_interface;
		for (unsigned int j = 0; j < 100; j++) {
			vcu_interface = mchbar_read32(VCU_MAILBOX_INTERFACE);
			if (!(vcu_interface & VCU_RUN_BUSY))
				break;

			udelay(10);
		}
		if (vcu_interface & VCU_RUN_BUSY)
			continue;

		if ((vcu_interface & VCU_RESPONSE_MASK) == VCU_RESPONSE_SUCCESS)
			return;
	}
	printk(BIOS_ERR, "VCU: Failed to send command\n");
}

static enum vcu_opcode get_register_opcode(enum vcu_sequence seq)
{
	switch (seq) {
	case SEQ_ID_READ_CSR:
		return VCU_OPCODE_READ_CSR;
	case SEQ_ID_WRITE_CSR:
		return VCU_OPCODE_WRITE_CSR;
	case SEQ_ID_READ_MMIO:
		return VCU_OPCODE_READ_MMIO;
	case SEQ_ID_WRITE_MMIO:
		return VCU_OPCODE_WRITE_MMIO;
	default:
		BUG();
		return VCU_INVALID_OPCODE;
	}
}

static enum vcu_opcode get_data_opcode(enum vcu_sequence seq)
{
	switch (seq) {
	case SEQ_ID_READ_CSR:
	case SEQ_ID_READ_MMIO:
		return VCU_OPCODE_READ_DATA;
	case SEQ_ID_WRITE_CSR:
	case SEQ_ID_WRITE_MMIO:
		return VCU_OPCODE_WRITE_DATA;
	default:
		BUG();
		return VCU_INVALID_OPCODE;
	}
}

static uint32_t send_vcu_sequence(uint32_t addr, enum vcu_sequence seq, uint32_t wr_data)
{
	send_vcu_command(VCU_OPCODE_OPEN_SEQ, seq);

	send_vcu_command(get_register_opcode(seq), addr);

	send_vcu_command(get_data_opcode(seq), wr_data);

	const uint32_t rd_data = mchbar_read32(VCU_MAILBOX_DATA);

	send_vcu_command(VCU_OPCODE_CLOSE_SEQ, seq);

	return rd_data;
}

#define VCU_WRITE_IGNORED	0

uint32_t vcu_read_csr(uint32_t addr)
{
	return send_vcu_sequence(addr, SEQ_ID_READ_CSR, VCU_WRITE_IGNORED);
}

void vcu_write_csr(uint32_t addr, uint32_t data)
{
	send_vcu_sequence(addr, SEQ_ID_WRITE_CSR, data);
}

void vcu_update_csr(uint32_t addr, uint32_t andvalue, uint32_t orvalue)
{
	vcu_write_csr(addr, (vcu_read_csr(addr) & andvalue) | orvalue);
}

uint32_t vcu_read_mmio(uint32_t addr)
{
	return send_vcu_sequence(addr, SEQ_ID_READ_MMIO, VCU_WRITE_IGNORED);
}

void vcu_write_mmio(uint32_t addr, uint32_t data)
{
	send_vcu_sequence(addr, SEQ_ID_WRITE_MMIO, data);
}

void vcu_update_mmio(uint32_t addr, uint32_t andvalue, uint32_t orvalue)
{
	vcu_write_mmio(addr, (vcu_read_mmio(addr) & andvalue) | orvalue);
}