summaryrefslogtreecommitdiff
path: root/src/northbridge/via/vx900/sata.c
blob: 63295e56c353bcc88a60437d7855d6aa3d40b5b7 (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
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
/*
 * This file is part of the coreboot project.
 *
 * Copyright (C) 2012  Alexandru Gagniuc <mr.nuke.me@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <console/console.h>
#include <device/pci.h>
#include <device/pci_ids.h>

#include "vx900.h"

/**
 * @file sata.c
 *
 * STATUS: Pretty good
 * The only issue is the SATA EPHY configuration. We do not know if it is board
 * specific or not. Otherwise, the SATA controller works without issues.
 */

static void vx900_print_sata_errors(u32 flags)
{
	/* Status flags */
	printk(BIOS_DEBUG, "\tPhyRdy %s\n",
	       (flags & (1 << 16)) ? "changed" : "not changed");
	printk(BIOS_DEBUG, "\tCOMWAKE %s\n",
	       (flags & (1 << 16)) ? "detected" : "not detected");
	printk(BIOS_DEBUG, "\tExchange as determined by COMINIT %s\n",
	       (flags & (1 << 26)) ? "occured" : "not occured");
	printk(BIOS_DEBUG, "\tPort selector presence %s\n",
	       (flags & (1 << 27)) ? "detected" : "not detected");
	/* Errors */
	if (flags & (1 << 0))
		print_debug("\tRecovered data integrity ERROR\n");
	if (flags & (1 << 1))
		print_debug("\tRecovered data communication ERROR\n");
	if (flags & (1 << 8))
		print_debug("\tNon-recovered Transient Data Integrity ERROR\n");
	if (flags & (1 << 9))
		print_debug("\tNon-recovered Persistent Communication or"
			    "\tData Integrity ERROR\n");
	if (flags & (1 << 10))
		print_debug("\tProtocol ERROR\n");
	if (flags & (1 << 11))
		print_debug("\tInternal ERROR\n");
	if (flags & (1 << 17))
		print_debug("\tPHY Internal ERROR\n");
	if (flags & (1 << 19))
		print_debug("\t10B to 8B Decode ERROR\n");
	if (flags & (1 << 20))
		print_debug("\tDisparity ERROR\n");
	if (flags & (1 << 21))
		print_debug("\tCRC ERROR\n");
	if (flags & (1 << 22))
		print_debug("\tHandshake ERROR\n");
	if (flags & (1 << 23))
		print_debug("\tLink Sequence ERROR\n");
	if (flags & (1 << 24))
		print_debug("\tTransport State Transition ERROR\n");
	if (flags & (1 << 25))
		print_debug("\tUNRECOGNIZED FIS type\n");
}

static void vx900_dbg_sata_errors(device_t dev)
{
	/* Port 0 */
	if (pci_read_config8(dev, 0xa0) & (1 << 0)) {
		print_debug("Device detected in SATA port 0.\n");
		u32 flags = pci_read_config32(dev, 0xa8);
		vx900_print_sata_errors(flags);
	};
	/* Port 1 */
	if (pci_read_config8(dev, 0xa1) & (1 << 0)) {
		print_debug("Device detected in SATA port 1.\n");
		u32 flags = pci_read_config32(dev, 0xac);
		vx900_print_sata_errors(flags);
	};
}

typedef u8 sata_phy_config[64];

static sata_phy_config reference_ephy = {
	0x80, 0xb8, 0xf0, 0xfe, 0x40, 0x7e, 0xf6, 0xdd,
	0x1a, 0x22, 0xa0, 0x10, 0x02, 0xa9, 0x7c, 0x7e,
	0x00, 0x00, 0x00, 0x00, 0x40, 0x30, 0x84, 0x8c,
	0x75, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x20, 0x40, 0xd0, 0x41, 0x40, 0x00, 0x00, 0x08,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x20, 0x40, 0x50, 0x41, 0x40, 0x00, 0x00, 0x00,
	0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

static u32 sata_phy_read32(device_t dev, u8 index)
{
	/* The SATA PHY control registers are accessed by a funny index/value
	 * scheme. Each byte (0,1,2,3) has its own 4-bit index */
	index = (index >> 2) & 0xf;
	u16 i16 = index | (index << 4) | (index << 8) | (index << 12);
	/* The index */
	pci_write_config16(dev, 0x68, i16);
	/* The value */
	return pci_read_config32(dev, 0x64);
}

static void sata_phy_write32(device_t dev, u8 index, u32 val)
{
	/* The SATA PHY control registers are accessed by a funny index/value
	 * scheme. Each byte (0,1,2,3) has its own 4-bit index */
	index = (index >> 2) & 0xf;
	u16 i16 = index | (index << 4) | (index << 8) | (index << 12);
	/* The index */
	pci_write_config16(dev, 0x68, i16);
	/* The value */
	pci_write_config32(dev, 0x64, val);
}

static void vx900_sata_read_phy_config(device_t dev, sata_phy_config cfg)
{
	size_t i;
	u32 *data = (u32 *) cfg;
	for (i = 0; i < (sizeof(sata_phy_config)) >> 2; i++) {
		data[i] = sata_phy_read32(dev, i << 2);
	}
}

static void vx900_sata_write_phy_config(device_t dev, sata_phy_config cfg)
{
	size_t i;
	u32 *data = (u32 *) cfg;
	for (i = 0; i < (sizeof(sata_phy_config)) >> 2; i++) {
		sata_phy_write32(dev, i << 2, data[i]);
	}
}

static void vx900_sata_dump_phy_config(sata_phy_config cfg)
{
	print_debug("SATA PHY config:\n");
	int i;
	for (i = 0; i < sizeof(sata_phy_config); i++) {
		unsigned char val;
		if ((i & 0x0f) == 0) {
			print_debug_hex8(i);
			print_debug_char(':');
		}
		val = cfg[i];
		if ((i & 7) == 0)
			print_debug(" |");
		print_debug_char(' ');
		print_debug_hex8(val);
		if ((i & 0x0f) == 0x0f) {
			print_debug("\n");
		}
	}
}

/**
 * \brief VX900: Place the onboard SATA controller in Native IDE mode
 *
 * AHCI mode requires a sub-class of 0x06, and Interface of 0x0
 * SATA mode requires a sub-class of 0x06, and Interface of 0x00
 * Unfortunately, setting the class to SATA, will prevent us from modyfing the
 * interface register to an AHCI/SATA compliant value. Thus, payloads or OS may
 * not properly identify this as a SATA controller.
 * We could set the class code to 0x04, which would cause the interface register
 * to become 0x00, which represents a RAID controller. Unfortunately, when we do
 * this, SeaBIOS will skip this as a storage device, and we will not be able to
 * boot.
 * Our only option is to operate in IDE mode. We choose native IDE so that we
 * can freely assign an IRQ, and are not forced to use IRQ14
 */
static void vx900_native_ide_mode(device_t dev)
{
	/* Disable subclass write protect */
	pci_mod_config8(dev, 0x45, 1 << 7, 0);
	/* Change the device class to IDE */
	pci_write_config16(dev, PCI_CLASS_DEVICE, PCI_CLASS_STORAGE_IDE);
	/* Re-enable subclass write protect */
	pci_mod_config8(dev, 0x45, 0, 1 << 7);
	/* Put it in native IDE mode */
	pci_write_config8(dev, PCI_CLASS_PROG, 0x8f);
}

static void vx900_sata_init(device_t dev)
{
	/* Enable SATA primary channel IO access */
	pci_mod_config8(dev, 0x40, 0, 1 << 1);
	/* Just SATA, so it makes sense to be in native SATA mode */
	vx900_native_ide_mode(dev);

	/* TP Layer Idle at least 20us before the Following Command */
	pci_mod_config8(dev, 0x53, 0, 1 << 7);
	/* Resend COMRESET When Recovering SATA Gen2 Device Error */
	pci_mod_config8(dev, 0x62, 1 << 1, 1 << 7);

	/* Fix "PMP Device Can’t Detect HDD Normally" (VIA Porting Guide)
	 * SATA device detection will not work unless we clear these bits.
	 * Without doing this, SeaBIOS (and potentially other payloads) will
	 * timeout when detecting SATA devices */
	pci_mod_config8(dev, 0x89, (1 << 3) | (1 << 6), 0);

	/* 12.7 Two Software Resets May Affect the System
	 * When the software does the second reset before the first reset
	 * finishes, it may cause the system hang. It would do one software
	 * reset and check the BSY bit of one port only, and the BSY bit of
	 * other port would be 1, then it does another software reset
	 * immediately and causes the system hang.
	 * This is because the first software reset doesn’t finish, and the
	 * state machine of the host controller conflicts, it can’t finish the
	 * second one anymore. The BSY bit of slave port would be always 1 after
	 * the second software reset issues. BIOS should set the following
	 * bit to avoid this issue. */
	pci_mod_config8(dev, 0x80, 0, 1 << 6);

	/* We need to set the EPHY values before doing anything with the link */
	sata_phy_config ephy;
	vx900_sata_read_phy_config(dev, ephy);
	if (1) {
		vx900_sata_dump_phy_config(ephy);
		vx900_sata_write_phy_config(dev, reference_ephy);
	} else {
		/* Enable TX and RX driving resistance */
		/* TX - 50 Ohm */
		ephy[1] &= ~(0x1f << 3);
		ephy[1] |= (1 << 7) | (8 << 3);
		/* RX - 50 Ohm */
		ephy[2] &= ~(0x1f << 3);
		ephy[2] |= (1 << 7) | (8 << 3);
		vx900_sata_write_phy_config(dev, ephy);
	}

	vx900_sata_read_phy_config(dev, ephy);
	vx900_sata_dump_phy_config(ephy);

	/* Clear error flags */
	pci_write_config32(dev, 0xa8, 0xffffffff);
	pci_write_config32(dev, 0xac, 0xffffffff);

	/* Start OOB link negotiation sequence */
	pci_mod_config8(dev, 0xb9, 0, 3 << 4);

	/* FIXME: From now on, we are just doing DEBUG stuff
	 * Wait until PHY communication is enabled */
	u32 wloops = 0;
	while (!(pci_read_config8(dev, 0xa0) & (1 << 1)))
		wloops++;
	printk(BIOS_SPEW, "SATA wait loops: %u\n", wloops);

	vx900_dbg_sata_errors(dev);
}

static void vx900_sata_read_resources(device_t dev)
{
	pci_dev_read_resources(dev);
}

static struct device_operations vga_operations = {
	.read_resources = vx900_sata_read_resources,
	.set_resources = pci_dev_set_resources,
	.enable_resources = pci_dev_enable_resources,
	.init = vx900_sata_init,
};

static const struct pci_driver chrome9hd_driver __pci_driver = {
	.ops = &vga_operations,
	.vendor = PCI_VENDOR_ID_VIA,
	.device = PCI_DEVICE_ID_VIA_VX900_SATA,
};