blob: 21f22f64096f98cc0e2f39ebf969410b2318b5a4 [file] [log] [blame]
Jason Wanga3f04be2008-11-28 21:36:51 +00001/*
2 * This file is part of the flashrom project.
3 *
Jason Wang13f98ce2008-11-29 15:07:15 +00004 * Copyright (C) 2008 Wang Qingpei <Qingpei.Wang@amd.com>
5 * Copyright (C) 2008 Joe Bao <Zheng.Bao@amd.com>
Uwe Hermann97e8f222009-04-13 21:35:49 +00006 * Copyright (C) 2008 Advanced Micro Devices, Inc.
Wei Hu31402ee2014-05-16 21:39:33 +00007 * Copyright (C) 2009, 2010, 2013 Carl-Daniel Hailfinger
8 * Copyright (C) 2013 Stefan Tauner
Jason Wanga3f04be2008-11-28 21:36:51 +00009 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
Jason Wanga3f04be2008-11-28 21:36:51 +000019 */
20
Carl-Daniel Hailfingercceafa22010-05-26 01:45:41 +000021#if defined(__i386__) || defined(__x86_64__)
22
Rudolf Marek70e14592013-07-25 22:58:56 +000023#include <string.h>
24#include <stdlib.h>
Jason Wanga3f04be2008-11-28 21:36:51 +000025#include "flash.h"
Carl-Daniel Hailfinger5b997c32010-07-27 22:41:39 +000026#include "programmer.h"
Patrick Georgi32508eb2012-07-20 20:35:14 +000027#include "hwaccess.h"
Jason Wanga3f04be2008-11-28 21:36:51 +000028#include "spi.h"
29
Carl-Daniel Hailfinger2c7ba8c2009-06-23 00:47:26 +000030/* This struct is unused, but helps visualize the SB600 SPI BAR layout.
31 *struct sb600_spi_controller {
32 * unsigned int spi_cntrl0; / * 00h * /
33 * unsigned int restrictedcmd1; / * 04h * /
34 * unsigned int restrictedcmd2; / * 08h * /
35 * unsigned int spi_cntrl1; / * 0ch * /
36 * unsigned int spi_cmdvalue0; / * 10h * /
37 * unsigned int spi_cmdvalue1; / * 14h * /
38 * unsigned int spi_cmdvalue2; / * 18h * /
39 * unsigned int spi_fakeid; / * 1Ch * /
40 *};
41 */
Jason Wanga3f04be2008-11-28 21:36:51 +000042
Michael Karcherb05b9e12010-07-22 18:04:19 +000043static uint8_t *sb600_spibar = NULL;
Stefan Tauner463dd692013-08-08 12:00:19 +000044enum amd_chipset {
45 CHIPSET_AMD_UNKNOWN,
46 CHIPSET_SB6XX,
47 CHIPSET_SB7XX, /* SP5100 too */
48 CHIPSET_SB89XX, /* Hudson-1 too */
49 CHIPSET_HUDSON234,
Martin Roth82b6ec12014-07-15 13:50:58 +000050 CHIPSET_BOLTON,
Stefan Tauner463dd692013-08-08 12:00:19 +000051 CHIPSET_YANGTZE,
Edward O'Callaghan93737bc2019-10-29 18:30:01 +110052 CHIPSET_PROMONTORY,
Stefan Tauner463dd692013-08-08 12:00:19 +000053};
54static enum amd_chipset amd_gen = CHIPSET_AMD_UNKNOWN;
55
Stefan Taunerd5b2aef2014-05-16 21:39:28 +000056#define FIFO_SIZE_OLD 8
Wei Hu31402ee2014-05-16 21:39:33 +000057#define FIFO_SIZE_YANGTZE 71
Stefan Taunerd5b2aef2014-05-16 21:39:28 +000058
Edward O'Callaghan5eca4272020-04-12 17:27:53 +100059static int sb600_spi_send_command(const struct flashctx *flash, unsigned int writecnt, unsigned int readcnt,
Stefan Taunerd5b2aef2014-05-16 21:39:28 +000060 const unsigned char *writearr, unsigned char *readarr);
Edward O'Callaghan5eca4272020-04-12 17:27:53 +100061static int spi100_spi_send_command(const struct flashctx *flash, unsigned int writecnt, unsigned int readcnt,
Wei Hu31402ee2014-05-16 21:39:33 +000062 const unsigned char *writearr, unsigned char *readarr);
Stefan Taunerd5b2aef2014-05-16 21:39:28 +000063
Carl-Daniel Hailfingera5bcbce2014-07-19 22:03:29 +000064static struct spi_master spi_master_sb600 = {
Stefan Taunerd5b2aef2014-05-16 21:39:28 +000065 .max_data_read = FIFO_SIZE_OLD,
66 .max_data_write = FIFO_SIZE_OLD - 3,
67 .command = sb600_spi_send_command,
68 .multicommand = default_spi_send_multicommand,
69 .read = default_spi_read,
70 .write_256 = default_spi_write_256,
71 .write_aai = default_spi_write_aai,
72};
73
Carl-Daniel Hailfingera5bcbce2014-07-19 22:03:29 +000074static struct spi_master spi_master_yangtze = {
Wei Hu31402ee2014-05-16 21:39:33 +000075 .max_data_read = FIFO_SIZE_YANGTZE - 3, /* Apparently the big SPI 100 buffer is not a ring buffer. */
76 .max_data_write = FIFO_SIZE_YANGTZE - 3,
77 .command = spi100_spi_send_command,
78 .multicommand = default_spi_send_multicommand,
79 .read = default_spi_read,
80 .write_256 = default_spi_write_256,
81 .write_aai = default_spi_write_aai,
82};
83
Edward O'Callaghanc0a27e12019-10-29 17:05:39 +110084static int find_smbus_dev_rev(uint16_t vendor, uint16_t device)
85{
86 struct pci_dev *smbus_dev = pci_dev_find(vendor, device);
87 if (!smbus_dev) {
88 msg_pdbg("No SMBus device with ID %04X:%04X found.\n", vendor, device);
89 msg_perr("ERROR: SMBus device not found. Not enabling SPI.\n");
90 return -1;
91 }
92 return pci_read_byte(smbus_dev, PCI_REVISION_ID);
93}
94
Edward O'Callaghan9355e6f2019-10-29 18:18:18 +110095/* Determine the chipset's version and identify the respective SMBUS device. */
96static int determine_generation(struct pci_dev *dev)
Stefan Tauner463dd692013-08-08 12:00:19 +000097{
98 amd_gen = CHIPSET_AMD_UNKNOWN;
Stefan Tauner4442b812013-09-12 15:48:35 +000099 msg_pdbg2("Trying to determine the generation of the SPI interface... ");
100 if (dev->device_id == 0x438d) {
101 amd_gen = CHIPSET_SB6XX;
102 msg_pdbg("SB6xx detected.\n");
103 } else if (dev->device_id == 0x439d) {
Edward O'Callaghanc0a27e12019-10-29 17:05:39 +1100104 int rev = find_smbus_dev_rev(0x1002, 0x4385);
105 if (rev < 0)
Edward O'Callaghan9355e6f2019-10-29 18:18:18 +1100106 return -1;
Stefan Tauner4442b812013-09-12 15:48:35 +0000107 if (rev >= 0x39 && rev <= 0x3D) {
108 amd_gen = CHIPSET_SB7XX;
109 msg_pdbg("SB7xx/SP5100 detected.\n");
110 } else if (rev >= 0x40 && rev <= 0x42) {
111 amd_gen = CHIPSET_SB89XX;
112 msg_pdbg("SB8xx/SB9xx/Hudson-1 detected.\n");
113 } else {
114 msg_pwarn("SB device found but SMBus revision 0x%02x does not match known values.\n"
115 "Assuming SB8xx/SB9xx/Hudson-1. Please send a log to flashrom@flashrom.org\n",
116 rev);
117 amd_gen = CHIPSET_SB89XX;
118 }
119 } else if (dev->device_id == 0x780e) {
Stefan Tauner463dd692013-08-08 12:00:19 +0000120 /* The PCI ID of the LPC bridge doesn't change between Hudson-2/3/4 and Yangtze (Kabini/Temash)
121 * although they use different SPI interfaces. */
Edward O'Callaghanc0a27e12019-10-29 17:05:39 +1100122 int rev = find_smbus_dev_rev(0x1022, 0x780B);
123 if (rev < 0)
Edward O'Callaghan9355e6f2019-10-29 18:18:18 +1100124 return -1;
Stefan Tauner463dd692013-08-08 12:00:19 +0000125 if (rev >= 0x11 && rev <= 0x15) {
126 amd_gen = CHIPSET_HUDSON234;
127 msg_pdbg("Hudson-2/3/4 detected.\n");
Martin Roth82b6ec12014-07-15 13:50:58 +0000128 } else if (rev == 0x16) {
129 amd_gen = CHIPSET_BOLTON;
130 msg_pdbg("Bolton detected.\n");
Stefan Tauner5c316f92015-02-08 21:57:52 +0000131 } else if ((rev >= 0x39 && rev <= 0x3A) || rev == 0x42) {
Stefan Tauner463dd692013-08-08 12:00:19 +0000132 amd_gen = CHIPSET_YANGTZE;
133 msg_pdbg("Yangtze detected.\n");
134 } else {
135 msg_pwarn("FCH device found but SMBus revision 0x%02x does not match known values.\n"
136 "Please report this to flashrom@flashrom.org and include this log and\n"
137 "the output of lspci -nnvx, thanks!.\n", rev);
138 }
Ricardo Ribalda Delgado7b629bc2017-03-22 14:08:31 +0100139 } else if (dev->device_id == 0x790e) {
Edward O'Callaghanc0a27e12019-10-29 17:05:39 +1100140 int rev = find_smbus_dev_rev(0x1022, 0x790B);
141 if (rev < 0)
Edward O'Callaghan9355e6f2019-10-29 18:18:18 +1100142 return -1;
Ricardo Ribalda Delgado7b629bc2017-03-22 14:08:31 +0100143 if (rev == 0x4a) {
144 amd_gen = CHIPSET_YANGTZE;
145 msg_pdbg("Yangtze detected.\n");
Edward O'Callaghan93737bc2019-10-29 18:30:01 +1100146 } else if (rev == 0x4b) {
147 amd_gen = CHIPSET_PROMONTORY;
148 msg_pdbg("Promontory detected.\n");
Ricardo Ribalda Delgado7b629bc2017-03-22 14:08:31 +0100149 } else {
150 msg_pwarn("FCH device found but SMBus revision 0x%02x does not match known values.\n"
151 "Please report this to flashrom@flashrom.org and include this log and\n"
152 "the output of lspci -nnvx, thanks!.\n", rev);
153 }
154
155
Stefan Tauner4442b812013-09-12 15:48:35 +0000156 } else
157 msg_pwarn("%s: Unknown LPC device %" PRIx16 ":%" PRIx16 ".\n"
158 "Please report this to flashrom@flashrom.org and include this log and\n"
159 "the output of lspci -nnvx, thanks!\n",
160 __func__, dev->vendor_id, dev->device_id);
Edward O'Callaghan9355e6f2019-10-29 18:18:18 +1100161 if (amd_gen == CHIPSET_AMD_UNKNOWN) {
162 msg_perr("Could not determine chipset generation.");
163 return -1;
164 }
165 return 0;
Stefan Tauner463dd692013-08-08 12:00:19 +0000166}
Jason Wanga3f04be2008-11-28 21:36:51 +0000167
Carl-Daniel Hailfinger2c7ba8c2009-06-23 00:47:26 +0000168static void reset_internal_fifo_pointer(void)
Jason Wanga3f04be2008-11-28 21:36:51 +0000169{
Carl-Daniel Hailfinger78185dc2009-05-17 15:49:24 +0000170 mmio_writeb(mmio_readb(sb600_spibar + 2) | 0x10, sb600_spibar + 2);
Jason Wanga3f04be2008-11-28 21:36:51 +0000171
Rudolf Marek70e14592013-07-25 22:58:56 +0000172 /* FIXME: This loop needs a timeout and a clearer message. */
Carl-Daniel Hailfinger78185dc2009-05-17 15:49:24 +0000173 while (mmio_readb(sb600_spibar + 0xD) & 0x7)
Carl-Daniel Hailfinger643415b2010-01-10 01:59:50 +0000174 msg_pspew("reset\n");
Jason Wanga3f04be2008-11-28 21:36:51 +0000175}
176
Carl-Daniel Hailfingereb0e7fc2010-08-18 15:12:43 +0000177static int compare_internal_fifo_pointer(uint8_t want)
178{
Stefan Taunerd5b2aef2014-05-16 21:39:28 +0000179 uint8_t have = mmio_readb(sb600_spibar + 0xd) & 0x07;
180 want %= FIFO_SIZE_OLD;
181 if (have != want) {
182 msg_perr("AMD SPI FIFO pointer corruption! Pointer is %d, wanted %d\n", have, want);
183 msg_perr("Something else is accessing the flash chip and causes random corruption.\n"
184 "Please stop all applications and drivers and IPMI which access the flash chip.\n");
Carl-Daniel Hailfingereb0e7fc2010-08-18 15:12:43 +0000185 return 1;
186 } else {
Stefan Taunerd5b2aef2014-05-16 21:39:28 +0000187 msg_pspew("AMD SPI FIFO pointer is %d, wanted %d\n", have, want);
Carl-Daniel Hailfingereb0e7fc2010-08-18 15:12:43 +0000188 return 0;
189 }
190}
191
Stefan Taunerd5b2aef2014-05-16 21:39:28 +0000192/* Check the number of bytes to be transmitted and extract opcode. */
Edward O'Callaghan5eca4272020-04-12 17:27:53 +1000193static int check_readwritecnt(const struct flashctx *flash, unsigned int writecnt, unsigned int readcnt)
Carl-Daniel Hailfingereb0e7fc2010-08-18 15:12:43 +0000194{
Carl-Daniel Hailfingera5bcbce2014-07-19 22:03:29 +0000195 unsigned int maxwritecnt = flash->mst->spi.max_data_write + 3;
Stefan Taunerd5b2aef2014-05-16 21:39:28 +0000196 if (writecnt > maxwritecnt) {
197 msg_pinfo("%s: SPI controller can not send %d bytes, it is limited to %d bytes\n",
198 __func__, writecnt, maxwritecnt);
199 return SPI_INVALID_LENGTH;
200 }
Carl-Daniel Hailfingereb0e7fc2010-08-18 15:12:43 +0000201
Stefan Tauner6697f712014-08-06 15:09:15 +0000202 unsigned int maxreadcnt = flash->mst->spi.max_data_read;
Stefan Taunerd5b2aef2014-05-16 21:39:28 +0000203 if (readcnt > maxreadcnt) {
204 msg_pinfo("%s: SPI controller can not receive %d bytes, it is limited to %d bytes\n",
205 __func__, readcnt, maxreadcnt);
206 return SPI_INVALID_LENGTH;
207 }
208 return 0;
Carl-Daniel Hailfingereb0e7fc2010-08-18 15:12:43 +0000209}
210
Carl-Daniel Hailfinger2c7ba8c2009-06-23 00:47:26 +0000211static void execute_command(void)
Jason Wanga3f04be2008-11-28 21:36:51 +0000212{
Stefan Taunerd5b2aef2014-05-16 21:39:28 +0000213 msg_pspew("Executing... ");
Carl-Daniel Hailfinger78185dc2009-05-17 15:49:24 +0000214 mmio_writeb(mmio_readb(sb600_spibar + 2) | 1, sb600_spibar + 2);
Carl-Daniel Hailfinger78185dc2009-05-17 15:49:24 +0000215 while (mmio_readb(sb600_spibar + 2) & 1)
Jason Wanga3f04be2008-11-28 21:36:51 +0000216 ;
Stefan Taunerd5b2aef2014-05-16 21:39:28 +0000217 msg_pspew("done\n");
Jason Wanga3f04be2008-11-28 21:36:51 +0000218}
219
Edward O'Callaghan5eca4272020-04-12 17:27:53 +1000220static int sb600_spi_send_command(const struct flashctx *flash, unsigned int writecnt,
Carl-Daniel Hailfinger8a3c60c2011-12-18 15:01:24 +0000221 unsigned int readcnt,
222 const unsigned char *writearr,
223 unsigned char *readarr)
Jason Wanga3f04be2008-11-28 21:36:51 +0000224{
Stefan Taunerd5b2aef2014-05-16 21:39:28 +0000225 /* First byte is cmd which can not be sent through the FIFO. */
Jason Wanga3f04be2008-11-28 21:36:51 +0000226 unsigned char cmd = *writearr++;
Jason Wanga3f04be2008-11-28 21:36:51 +0000227 writecnt--;
Stefan Taunerd5b2aef2014-05-16 21:39:28 +0000228 msg_pspew("%s, cmd=0x%02x, writecnt=%d, readcnt=%d\n", __func__, cmd, writecnt, readcnt);
229 mmio_writeb(cmd, sb600_spibar + 0);
Jason Wanga3f04be2008-11-28 21:36:51 +0000230
Stefan Taunerd5b2aef2014-05-16 21:39:28 +0000231 int ret = check_readwritecnt(flash, writecnt, readcnt);
232 if (ret != 0)
233 return ret;
Jason Wanga3f04be2008-11-28 21:36:51 +0000234
Stefan Taunerd5b2aef2014-05-16 21:39:28 +0000235 /* This is a workaround for a bug in SPI controller. If we only send
Carl-Daniel Hailfingerf8555e22009-07-23 01:36:08 +0000236 * an opcode and no additional data/address, the SPI controller will
237 * read one byte too few from the chip. Basically, the last byte of
238 * the chip response is discarded and will not end up in the FIFO.
239 * It is unclear if the CS# line is set high too early as well.
240 */
Stefan Taunerd5b2aef2014-05-16 21:39:28 +0000241 unsigned int readoffby1 = (writecnt > 0) ? 0 : 1;
242 uint8_t readwrite = (readcnt + readoffby1) << 4 | (writecnt);
Carl-Daniel Hailfingereb0e7fc2010-08-18 15:12:43 +0000243 mmio_writeb(readwrite, sb600_spibar + 1);
Jason Wanga3f04be2008-11-28 21:36:51 +0000244
Jason Wanga3f04be2008-11-28 21:36:51 +0000245 reset_internal_fifo_pointer();
Stefan Taunerd5b2aef2014-05-16 21:39:28 +0000246 msg_pspew("Filling FIFO: ");
Nico Huber519be662018-12-23 20:03:35 +0100247 unsigned int count;
Stefan Taunerd5b2aef2014-05-16 21:39:28 +0000248 for (count = 0; count < writecnt; count++) {
249 msg_pspew("[%02x]", writearr[count]);
250 mmio_writeb(writearr[count], sb600_spibar + 0xC);
Jason Wanga3f04be2008-11-28 21:36:51 +0000251 }
Carl-Daniel Hailfinger643415b2010-01-10 01:59:50 +0000252 msg_pspew("\n");
Stefan Taunerd5b2aef2014-05-16 21:39:28 +0000253 if (compare_internal_fifo_pointer(writecnt))
Carl-Daniel Hailfingereb0e7fc2010-08-18 15:12:43 +0000254 return SPI_PROGRAMMER_ERROR;
Jason Wanga3f04be2008-11-28 21:36:51 +0000255
Stefan Taunerd5b2aef2014-05-16 21:39:28 +0000256 /*
257 * We should send the data in sequence, which means we need to reset
258 * the FIFO pointer to the first byte we want to send.
259 */
260 reset_internal_fifo_pointer();
Jason Wanga3f04be2008-11-28 21:36:51 +0000261 execute_command();
Stefan Taunerd5b2aef2014-05-16 21:39:28 +0000262 if (compare_internal_fifo_pointer(writecnt + readcnt))
263 return SPI_PROGRAMMER_ERROR;
Jason Wanga3f04be2008-11-28 21:36:51 +0000264
265 /*
266 * After the command executed, we should find out the index of the
Carl-Daniel Hailfingerf8555e22009-07-23 01:36:08 +0000267 * received byte. Here we just reset the FIFO pointer and skip the
268 * writecnt.
269 * It would be possible to increase the FIFO pointer by one instead
270 * of reading and discarding one byte from the FIFO.
271 * The FIFO is implemented on top of an 8 byte ring buffer and the
272 * buffer is never cleared. For every byte that is shifted out after
273 * the opcode, the FIFO already stores the response from the chip.
274 * Usually, the chip will respond with 0x00 or 0xff.
Jason Wanga3f04be2008-11-28 21:36:51 +0000275 */
Stefan Taunerd5b2aef2014-05-16 21:39:28 +0000276 reset_internal_fifo_pointer();
Jason Wanga3f04be2008-11-28 21:36:51 +0000277
Carl-Daniel Hailfingerf8555e22009-07-23 01:36:08 +0000278 /* Skip the bytes we sent. */
Carl-Daniel Hailfingereb0e7fc2010-08-18 15:12:43 +0000279 msg_pspew("Skipping: ");
Jason Wanga3f04be2008-11-28 21:36:51 +0000280 for (count = 0; count < writecnt; count++) {
Stefan Taunerd5b2aef2014-05-16 21:39:28 +0000281 msg_pspew("[%02x]", mmio_readb(sb600_spibar + 0xC));
Jason Wanga3f04be2008-11-28 21:36:51 +0000282 }
Carl-Daniel Hailfingereb0e7fc2010-08-18 15:12:43 +0000283 msg_pspew("\n");
284 if (compare_internal_fifo_pointer(writecnt))
285 return SPI_PROGRAMMER_ERROR;
Jason Wanga3f04be2008-11-28 21:36:51 +0000286
Stefan Taunerd5b2aef2014-05-16 21:39:28 +0000287 msg_pspew("Reading FIFO: ");
288 for (count = 0; count < readcnt; count++) {
289 readarr[count] = mmio_readb(sb600_spibar + 0xC);
290 msg_pspew("[%02x]", readarr[count]);
Jason Wanga3f04be2008-11-28 21:36:51 +0000291 }
Carl-Daniel Hailfinger643415b2010-01-10 01:59:50 +0000292 msg_pspew("\n");
Stefan Taunerd5b2aef2014-05-16 21:39:28 +0000293 if (compare_internal_fifo_pointer(writecnt+readcnt))
Carl-Daniel Hailfingereb0e7fc2010-08-18 15:12:43 +0000294 return SPI_PROGRAMMER_ERROR;
295
296 if (mmio_readb(sb600_spibar + 1) != readwrite) {
Stefan Taunerd5b2aef2014-05-16 21:39:28 +0000297 msg_perr("Unexpected change in AMD SPI read/write count!\n");
298 msg_perr("Something else is accessing the flash chip and causes random corruption.\n"
299 "Please stop all applications and drivers and IPMI which access the flash chip.\n");
Carl-Daniel Hailfingereb0e7fc2010-08-18 15:12:43 +0000300 return SPI_PROGRAMMER_ERROR;
301 }
Jason Wanga3f04be2008-11-28 21:36:51 +0000302
303 return 0;
304}
Carl-Daniel Hailfingercceafa22010-05-26 01:45:41 +0000305
Edward O'Callaghan5eca4272020-04-12 17:27:53 +1000306static int spi100_spi_send_command(const struct flashctx *flash, unsigned int writecnt,
Wei Hu31402ee2014-05-16 21:39:33 +0000307 unsigned int readcnt,
308 const unsigned char *writearr,
309 unsigned char *readarr)
310{
311 /* First byte is cmd which can not be sent through the buffer. */
312 unsigned char cmd = *writearr++;
313 writecnt--;
314 msg_pspew("%s, cmd=0x%02x, writecnt=%d, readcnt=%d\n", __func__, cmd, writecnt, readcnt);
315 mmio_writeb(cmd, sb600_spibar + 0);
316
317 int ret = check_readwritecnt(flash, writecnt, readcnt);
318 if (ret != 0)
319 return ret;
320
321 /* Use the extended TxByteCount and RxByteCount registers. */
322 mmio_writeb(writecnt, sb600_spibar + 0x48);
323 mmio_writeb(readcnt, sb600_spibar + 0x4b);
324
325 msg_pspew("Filling buffer: ");
Nico Huber519be662018-12-23 20:03:35 +0100326 unsigned int count;
Wei Hu31402ee2014-05-16 21:39:33 +0000327 for (count = 0; count < writecnt; count++) {
328 msg_pspew("[%02x]", writearr[count]);
329 mmio_writeb(writearr[count], sb600_spibar + 0x80 + count);
330 }
331 msg_pspew("\n");
332
333 execute_command();
334
335 msg_pspew("Reading buffer: ");
336 for (count = 0; count < readcnt; count++) {
337 readarr[count] = mmio_readb(sb600_spibar + 0x80 + (writecnt + count) % FIFO_SIZE_YANGTZE);
338 msg_pspew("[%02x]", readarr[count]);
339 }
340 msg_pspew("\n");
341
342 return 0;
343}
344
Stefan Taunera6a0d202013-09-15 14:17:39 +0000345struct spispeed {
346 const char *const name;
347 const uint8_t speed;
348};
349
350static const struct spispeed spispeeds[] = {
351 { "66 MHz", 0x00 },
352 { "33 MHz", 0x01 },
353 { "22 MHz", 0x02 },
354 { "16.5 MHz", 0x03 },
Wei Hu31402ee2014-05-16 21:39:33 +0000355 { "100 MHz", 0x04 },
356 { "Reserved", 0x05 },
357 { "Reserved", 0x06 },
358 { "800 kHz", 0x07 },
Stefan Taunera6a0d202013-09-15 14:17:39 +0000359};
360
361static int set_speed(struct pci_dev *dev, const struct spispeed *spispeed)
362{
363 bool success = false;
364 uint8_t speed = spispeed->speed;
365
366 msg_pdbg("Setting SPI clock to %s (0x%x).\n", spispeed->name, speed);
Wei Hu31402ee2014-05-16 21:39:33 +0000367 if (amd_gen >= CHIPSET_YANGTZE) {
368 rmmio_writew((speed << 12) | (speed << 8) | (speed << 4) | speed, sb600_spibar + 0x22);
369 uint16_t tmp = mmio_readw(sb600_spibar + 0x22);
370 success = (((tmp >> 12) & 0xf) == speed && ((tmp >> 8) & 0xf) == speed &&
371 ((tmp >> 4) & 0xf) == speed && ((tmp >> 0) & 0xf) == speed);
372 } else {
Stefan Taunera6a0d202013-09-15 14:17:39 +0000373 rmmio_writeb((mmio_readb(sb600_spibar + 0xd) & ~(0x3 << 4)) | (speed << 4), sb600_spibar + 0xd);
374 success = (speed == ((mmio_readb(sb600_spibar + 0xd) >> 4) & 0x3));
375 }
376
377 if (!success) {
378 msg_perr("Setting SPI clock failed.\n");
379 return 1;
380 }
381 return 0;
382}
383
Wei Hu31402ee2014-05-16 21:39:33 +0000384static int set_mode(struct pci_dev *dev, uint8_t read_mode)
385{
386 uint32_t tmp = mmio_readl(sb600_spibar + 0x00);
387 tmp &= ~(0x6 << 28 | 0x1 << 18); /* Clear mode bits */
388 tmp |= ((read_mode & 0x6) << 28) | ((read_mode & 0x1) << 18);
389 rmmio_writel(tmp, sb600_spibar + 0x00);
390 if (tmp != mmio_readl(sb600_spibar + 0x00))
391 return 1;
392 return 0;
393}
394
Stefan Taunera6a0d202013-09-15 14:17:39 +0000395static int handle_speed(struct pci_dev *dev)
396{
397 uint32_t tmp;
Carl-Daniel Hailfinger57cdd6b2016-03-12 19:49:14 +0000398 uint8_t spispeed_idx = 3; /* Default to 16.5 MHz */
Stefan Taunera6a0d202013-09-15 14:17:39 +0000399
Stefan Tauner21071b02014-05-16 21:39:48 +0000400 char *spispeed = extract_programmer_param("spispeed");
401 if (spispeed != NULL) {
Carl-Daniel Hailfinger57cdd6b2016-03-12 19:49:14 +0000402 unsigned int i;
403 for (i = 0; i < ARRAY_SIZE(spispeeds); i++) {
404 if (strcasecmp(spispeeds[i].name, spispeed) == 0) {
405 spispeed_idx = i;
406 break;
Stefan Tauner21071b02014-05-16 21:39:48 +0000407 }
Stefan Tauner21071b02014-05-16 21:39:48 +0000408 }
Carl-Daniel Hailfinger57cdd6b2016-03-12 19:49:14 +0000409 /* "reserved" is not a valid speed.
410 * Error out on speeds not present in the spispeeds array.
411 * Only Yangtze supports the second half of indices.
412 * No 66 MHz before SB8xx. */
413 if ((strcasecmp(spispeed, "reserved") == 0) ||
414 (i == ARRAY_SIZE(spispeeds)) ||
415 (amd_gen < CHIPSET_YANGTZE && spispeed_idx > 3) ||
416 (amd_gen < CHIPSET_SB89XX && spispeed_idx == 0)) {
Stefan Tauner21071b02014-05-16 21:39:48 +0000417 msg_perr("Error: Invalid spispeed value: '%s'.\n", spispeed);
418 free(spispeed);
419 return 1;
420 }
421 free(spispeed);
422 }
423
Stefan Taunera6a0d202013-09-15 14:17:39 +0000424 /* See the chipset support matrix for SPI Base_Addr below for an explanation of the symbols used.
Martin Roth82b6ec12014-07-15 13:50:58 +0000425 * bit 6xx 7xx/SP5100 8xx 9xx hudson1 hudson234 bolton/yangtze
Stefan Taunera6a0d202013-09-15 14:17:39 +0000426 * 18 rsvd <- fastReadEnable ? <- ? SpiReadMode[0]
427 * 29:30 rsvd <- <- ? <- ? SpiReadMode[2:1]
428 */
Martin Roth82b6ec12014-07-15 13:50:58 +0000429 if (amd_gen >= CHIPSET_BOLTON) {
Wei Hu31402ee2014-05-16 21:39:33 +0000430 static const char *spireadmodes[] = {
431 "Normal (up to 33 MHz)", /* 0 */
432 "Reserved", /* 1 */
433 "Dual IO (1-1-2)", /* 2 */
434 "Quad IO (1-1-4)", /* 3 */
435 "Dual IO (1-2-2)", /* 4 */
436 "Quad IO (1-4-4)", /* 5 */
437 "Normal (up to 66 MHz)", /* 6 */
Martin Roth82b6ec12014-07-15 13:50:58 +0000438 "Fast Read", /* 7 (Not defined in the Bolton datasheet.) */
Wei Hu31402ee2014-05-16 21:39:33 +0000439 };
440 tmp = mmio_readl(sb600_spibar + 0x00);
441 uint8_t read_mode = ((tmp >> 28) & 0x6) | ((tmp >> 18) & 0x1);
442 msg_pdbg("SpiReadMode=%s (%i)\n", spireadmodes[read_mode], read_mode);
443 if (read_mode != 6) {
444 read_mode = 6; /* Default to "Normal (up to 66 MHz)" */
445 if (set_mode(dev, read_mode) != 0) {
446 msg_perr("Setting read mode to \"%s\" failed.\n", spireadmodes[read_mode]);
447 return 1;
448 }
449 msg_pdbg("Setting read mode to \"%s\" succeeded.\n", spireadmodes[read_mode]);
450 }
451
Martin Roth82b6ec12014-07-15 13:50:58 +0000452 if (amd_gen >= CHIPSET_YANGTZE) {
453 tmp = mmio_readb(sb600_spibar + 0x20);
454 msg_pdbg("UseSpi100 is %sabled\n", (tmp & 0x1) ? "en" : "dis");
455 if ((tmp & 0x1) == 0) {
456 rmmio_writeb(tmp | 0x1, sb600_spibar + 0x20);
457 tmp = mmio_readb(sb600_spibar + 0x20) & 0x1;
458 if (tmp == 0) {
459 msg_perr("Enabling Spi100 failed.\n");
460 return 1;
461 }
462 msg_pdbg("Enabling Spi100 succeeded.\n");
463 }
464
465 tmp = mmio_readw(sb600_spibar + 0x22); /* SPI 100 Speed Config */
466 msg_pdbg("NormSpeedNew is %s\n", spispeeds[(tmp >> 12) & 0xf].name);
467 msg_pdbg("FastSpeedNew is %s\n", spispeeds[(tmp >> 8) & 0xf].name);
468 msg_pdbg("AltSpeedNew is %s\n", spispeeds[(tmp >> 4) & 0xf].name);
469 msg_pdbg("TpmSpeedNew is %s\n", spispeeds[(tmp >> 0) & 0xf].name);
470 }
Wei Hu31402ee2014-05-16 21:39:33 +0000471 } else {
Stefan Taunera6a0d202013-09-15 14:17:39 +0000472 if (amd_gen >= CHIPSET_SB89XX && amd_gen <= CHIPSET_HUDSON234) {
473 bool fast_read = (mmio_readl(sb600_spibar + 0x00) >> 18) & 0x1;
474 msg_pdbg("Fast Reads are %sabled\n", fast_read ? "en" : "dis");
475 if (fast_read) {
476 msg_pdbg("Disabling them temporarily.\n");
477 rmmio_writel(mmio_readl(sb600_spibar + 0x00) & ~(0x1 << 18),
478 sb600_spibar + 0x00);
479 }
480 }
481 tmp = (mmio_readb(sb600_spibar + 0xd) >> 4) & 0x3;
482 msg_pdbg("NormSpeed is %s\n", spispeeds[tmp].name);
483 }
484 return set_speed(dev, &spispeeds[spispeed_idx]);
485}
486
Stefan Taunerd5b2aef2014-05-16 21:39:28 +0000487static int handle_imc(struct pci_dev *dev)
Rudolf Marek70e14592013-07-25 22:58:56 +0000488{
489 /* Handle IMC everywhere but sb600 which does not have one. */
Stefan Tauner4442b812013-09-12 15:48:35 +0000490 if (amd_gen == CHIPSET_SB6XX)
Rudolf Marek70e14592013-07-25 22:58:56 +0000491 return 0;
492
Stefan Taunerd5b2aef2014-05-16 21:39:28 +0000493 bool amd_imc_force = false;
494 char *arg = extract_programmer_param("amd_imc_force");
495 if (arg && !strcmp(arg, "yes")) {
496 amd_imc_force = true;
497 msg_pspew("amd_imc_force enabled.\n");
498 } else if (arg && !strlen(arg)) {
499 msg_perr("Missing argument for amd_imc_force.\n");
500 free(arg);
501 return 1;
502 } else if (arg) {
503 msg_perr("Unknown argument for amd_imc_force: \"%s\" (not \"yes\").\n", arg);
504 free(arg);
505 return 1;
506 }
507 free(arg);
508
Rudolf Marek70e14592013-07-25 22:58:56 +0000509 /* TODO: we should not only look at IntegratedImcPresent (LPC Dev 20, Func 3, 40h) but also at
Stefan Tauner5c316f92015-02-08 21:57:52 +0000510 * IMCEnable(Strap) and Override EcEnable(Strap) (sb8xx, sb9xx?, a50, Bolton: Misc_Reg: 80h-87h;
Rudolf Marek70e14592013-07-25 22:58:56 +0000511 * sb7xx, sp5100: PM_Reg: B0h-B1h) etc. */
512 uint8_t reg = pci_read_byte(dev, 0x40);
513 if ((reg & (1 << 7)) == 0) {
514 msg_pdbg("IMC is not active.\n");
515 return 0;
516 }
517
518 if (!amd_imc_force)
519 programmer_may_write = 0;
Stefan Tauner463dd692013-08-08 12:00:19 +0000520 msg_pinfo("Writes have been disabled for safety reasons because the presence of the IMC\n"
521 "was detected and it could interfere with accessing flash memory. Flashrom will\n"
522 "try to disable it temporarily but even then this might not be safe:\n"
Stefan Tauner0be072c2016-03-13 15:16:30 +0000523 "when it is re-enabled and after a reboot it expects to find working code\n"
Rudolf Marek70e14592013-07-25 22:58:56 +0000524 "in the flash and it is unpredictable what happens if there is none.\n"
525 "\n"
526 "To be safe make sure that there is a working IMC firmware at the right\n"
527 "location in the image you intend to write and do not attempt to erase.\n"
528 "\n"
529 "You can enforce write support with the amd_imc_force programmer option.\n");
530 if (amd_imc_force)
531 msg_pinfo("Continuing with write support because the user forced us to!\n");
532
533 return amd_imc_shutdown(dev);
534}
535
Michael Karcherb05b9e12010-07-22 18:04:19 +0000536int sb600_probe_spi(struct pci_dev *dev)
537{
538 struct pci_dev *smbus_dev;
539 uint32_t tmp;
540 uint8_t reg;
Rudolf Marek70e14592013-07-25 22:58:56 +0000541
Michael Karcherb05b9e12010-07-22 18:04:19 +0000542 /* Read SPI_BaseAddr */
543 tmp = pci_read_long(dev, 0xa0);
544 tmp &= 0xffffffe0; /* remove bits 4-0 (reserved) */
545 msg_pdbg("SPI base address is at 0x%x\n", tmp);
546
547 /* If the BAR has address 0, it is unlikely SPI is used. */
548 if (!tmp)
549 return 0;
550
551 /* Physical memory has to be mapped at page (4k) boundaries. */
Stefan Tauner7fb5aa02013-08-14 15:48:44 +0000552 sb600_spibar = rphysmap("SB600 SPI registers", tmp & 0xfffff000, 0x1000);
553 if (sb600_spibar == ERROR_PTR)
Niklas Söderlund5d307202013-09-14 09:02:27 +0000554 return ERROR_FATAL;
Stefan Tauner7fb5aa02013-08-14 15:48:44 +0000555
Michael Karcherb05b9e12010-07-22 18:04:19 +0000556 /* The low bits of the SPI base address are used as offset into
557 * the mapped page.
558 */
559 sb600_spibar += tmp & 0xfff;
560
Edward O'Callaghan9355e6f2019-10-29 18:18:18 +1100561 if (determine_generation(dev) < 0)
Stefan Tauner4442b812013-09-12 15:48:35 +0000562 return ERROR_NONFATAL;
Stefan Tauner463dd692013-08-08 12:00:19 +0000563
Stefan Tauner4442b812013-09-12 15:48:35 +0000564 /* How to read the following table and similar ones in this file:
565 * "?" means we have no datasheet for this chipset generation or it doesn't have any relevant info.
566 * "<-" means the bit/register meaning is identical to the next non-"?" chipset to the left. "<-" thus
567 * never refers to another "?".
568 * If a "?" chipset is between two chipsets with identical meaning, we assume the meaning didn't change
569 * twice in between, i.e. the meaning is unchanged for the "?" chipset. Usually we assume that
570 * succeeding hardware supports the same functionality as its predecessor unless proven different by
571 * tests or documentation, hence "?" will often be implemented equally to "<-".
572 *
573 * Chipset support matrix for SPI Base_Addr (LPC PCI reg 0xa0)
574 * bit 6xx 7xx/SP5100 8xx 9xx hudson1 hudson2+ yangtze
575 * 3 rsvd <- <- ? <- ? RouteTpm2Spi
576 * 2 rsvd AbortEnable rsvd ? <- ? <-
577 * 1 rsvd SpiRomEnable <- ? <- ? <-
578 * 0 rsvd AltSpiCSEnable rsvd ? <- ? <-
579 */
580 if (amd_gen >= CHIPSET_SB7XX) {
581 tmp = pci_read_long(dev, 0xa0);
582 msg_pdbg("SpiRomEnable=%i", (tmp >> 1) & 0x1);
583 if (amd_gen == CHIPSET_SB7XX)
584 msg_pdbg(", AltSpiCSEnable=%i, AbortEnable=%i", tmp & 0x1, (tmp >> 2) & 0x1);
Edward O'Callaghan93737bc2019-10-29 18:30:01 +1100585 else if (amd_gen >= CHIPSET_YANGTZE)
Wei Hu31402ee2014-05-16 21:39:33 +0000586 msg_pdbg(", RouteTpm2Sp=%i", (tmp >> 3) & 0x1);
Michael Karcherb05b9e12010-07-22 18:04:19 +0000587
Stefan Tauner4442b812013-09-12 15:48:35 +0000588 tmp = pci_read_byte(dev, 0xba);
589 msg_pdbg(", PrefetchEnSPIFromIMC=%i", (tmp & 0x4) >> 2);
590
591 tmp = pci_read_byte(dev, 0xbb);
592 /* FIXME: Set bit 3,6,7 if not already set.
593 * Set bit 5, otherwise SPI accesses are pointless in LPC mode.
594 * See doc 42413 AMD SB700/710/750 RPR.
595 */
596 if (amd_gen == CHIPSET_SB7XX)
597 msg_pdbg(", SpiOpEnInLpcMode=%i", (tmp >> 5) & 0x1);
598 msg_pdbg(", PrefetchEnSPIFromHost=%i\n", tmp & 0x1);
599 }
600
601 /* Chipset support matrix for SPI_Cntrl0 (spibar + 0x0)
602 * See the chipset support matrix for SPI Base_Addr above for an explanation of the symbols used.
603 * bit 6xx 7xx/SP5100 8xx 9xx hudson1 hudson2+ yangtze
604 * 17 rsvd <- <- ? <- ? <-
Stefan Taunera6a0d202013-09-15 14:17:39 +0000605 * 18 rsvd <- fastReadEnable<1> ? <- ? SpiReadMode[0]<1>
Stefan Tauner4442b812013-09-12 15:48:35 +0000606 * 19 SpiArbEnable <- <- ? <- ? <-
607 * 20 (FifoPtrClr) <- <- ? <- ? <-
608 * 21 (FifoPtrInc) <- <- ? <- ? IllegalAccess
609 * 22 SpiAccessMacRomEn <- <- ? <- ? <-
610 * 23 SpiHostAccessRomEn <- <- ? <- ? <-
611 * 24:26 ArbWaitCount <- <- ? <- ? <-
612 * 27 SpiBridgeDisable <- <- ? <- ? rsvd
613 * 28 rsvd DropOneClkOnRd = SPIClkGate ? <- ? <-
Stefan Taunera6a0d202013-09-15 14:17:39 +0000614 * 29:30 rsvd <- <- ? <- ? SpiReadMode[2:1]<1>
Stefan Tauner4442b812013-09-12 15:48:35 +0000615 * 31 rsvd <- SpiBusy ? <- ? <-
Stefan Taunera6a0d202013-09-15 14:17:39 +0000616 *
617 * <1> see handle_speed
Carl-Daniel Hailfingereb0e7fc2010-08-18 15:12:43 +0000618 */
Stefan Tauner4442b812013-09-12 15:48:35 +0000619 tmp = mmio_readl(sb600_spibar + 0x00);
620 msg_pdbg("(0x%08" PRIx32 ") SpiArbEnable=%i", tmp, (tmp >> 19) & 0x1);
Edward O'Callaghan93737bc2019-10-29 18:30:01 +1100621 if (amd_gen >= CHIPSET_YANGTZE)
Wei Hu31402ee2014-05-16 21:39:33 +0000622 msg_pdbg(", IllegalAccess=%i", (tmp >> 21) & 0x1);
Stefan Tauner4442b812013-09-12 15:48:35 +0000623
624 msg_pdbg(", SpiAccessMacRomEn=%i, SpiHostAccessRomEn=%i, ArbWaitCount=%i",
625 (tmp >> 22) & 0x1, (tmp >> 23) & 0x1, (tmp >> 24) & 0x7);
626
Edward O'Callaghan93737bc2019-10-29 18:30:01 +1100627 if (amd_gen < CHIPSET_YANGTZE)
Stefan Tauner4442b812013-09-12 15:48:35 +0000628 msg_pdbg(", SpiBridgeDisable=%i", (tmp >> 27) & 0x1);
629
630 switch (amd_gen) {
631 case CHIPSET_SB7XX:
632 msg_pdbg(", DropOneClkOnRd/SpiClkGate=%i", (tmp >> 28) & 0x1);
Richard Hughesdb7482b2018-12-19 12:04:30 +0000633 /* Fall through. */
Stefan Tauner4442b812013-09-12 15:48:35 +0000634 case CHIPSET_SB89XX:
635 case CHIPSET_HUDSON234:
Wei Hu31402ee2014-05-16 21:39:33 +0000636 case CHIPSET_YANGTZE:
Edward O'Callaghan93737bc2019-10-29 18:30:01 +1100637 case CHIPSET_PROMONTORY:
Stefan Tauner4442b812013-09-12 15:48:35 +0000638 msg_pdbg(", SpiBusy=%i", (tmp >> 31) & 0x1);
639 default: break;
640 }
641 msg_pdbg("\n");
642
643 if (((tmp >> 22) & 0x1) == 0 || ((tmp >> 23) & 0x1) == 0) {
644 msg_perr("ERROR: State of SpiAccessMacRomEn or SpiHostAccessRomEn prohibits full access.\n");
645 return ERROR_NONFATAL;
646 }
647
Stefan Tauner4442b812013-09-12 15:48:35 +0000648 if (amd_gen >= CHIPSET_SB89XX) {
649 tmp = mmio_readb(sb600_spibar + 0x1D);
650 msg_pdbg("Using SPI_CS%d\n", tmp & 0x3);
Wei Hu31402ee2014-05-16 21:39:33 +0000651 /* FIXME: Handle SpiProtect* configuration on Yangtze. */
Stefan Tauner4442b812013-09-12 15:48:35 +0000652 }
653
Michael Karcherb05b9e12010-07-22 18:04:19 +0000654 /* Look for the SMBus device. */
655 smbus_dev = pci_dev_find(0x1002, 0x4385);
Ricardo Ribalda Delgado7b629bc2017-03-22 14:08:31 +0100656 if (!smbus_dev)
Stefan Tauner463dd692013-08-08 12:00:19 +0000657 smbus_dev = pci_dev_find(0x1022, 0x780b); /* AMD FCH */
Ricardo Ribalda Delgado7b629bc2017-03-22 14:08:31 +0100658 if (!smbus_dev)
659 smbus_dev = pci_dev_find(0x1022, 0x790b); /* AMD FP4 */
660 if (!smbus_dev) {
661 msg_perr("ERROR: SMBus device not found. Not enabling SPI.\n");
662 return ERROR_NONFATAL;
Michael Karcherb05b9e12010-07-22 18:04:19 +0000663 }
664
665 /* Note about the bit tests below: If a bit is zero, the GPIO is SPI. */
666 /* GPIO11/SPI_DO and GPIO12/SPI_DI status */
667 reg = pci_read_byte(smbus_dev, 0xAB);
668 reg &= 0xC0;
669 msg_pdbg("GPIO11 used for %s\n", (reg & (1 << 6)) ? "GPIO" : "SPI_DO");
670 msg_pdbg("GPIO12 used for %s\n", (reg & (1 << 7)) ? "GPIO" : "SPI_DI");
671 if (reg != 0x00) {
672 msg_pdbg("Not enabling SPI");
673 return 0;
674 }
675 /* GPIO31/SPI_HOLD and GPIO32/SPI_CS status */
676 reg = pci_read_byte(smbus_dev, 0x83);
677 reg &= 0xC0;
678 msg_pdbg("GPIO31 used for %s\n", (reg & (1 << 6)) ? "GPIO" : "SPI_HOLD");
679 msg_pdbg("GPIO32 used for %s\n", (reg & (1 << 7)) ? "GPIO" : "SPI_CS");
680 /* SPI_HOLD is not used on all boards, filter it out. */
681 if ((reg & 0x80) != 0x00) {
682 msg_pdbg("Not enabling SPI");
683 return 0;
684 }
685 /* GPIO47/SPI_CLK status */
686 reg = pci_read_byte(smbus_dev, 0xA7);
687 reg &= 0x40;
688 msg_pdbg("GPIO47 used for %s\n", (reg & (1 << 6)) ? "GPIO" : "SPI_CLK");
689 if (reg != 0x00) {
690 msg_pdbg("Not enabling SPI");
691 return 0;
692 }
693
Stefan Taunera6a0d202013-09-15 14:17:39 +0000694 if (handle_speed(dev) != 0)
695 return ERROR_FATAL;
696
Stefan Taunerd5b2aef2014-05-16 21:39:28 +0000697 if (handle_imc(dev) != 0)
Rudolf Marek70e14592013-07-25 22:58:56 +0000698 return ERROR_FATAL;
Carl-Daniel Hailfinger39446e32010-09-15 12:02:07 +0000699
Wei Hu31402ee2014-05-16 21:39:33 +0000700 /* Starting with Yangtze the SPI controller got a different interface with a much bigger buffer. */
Edward O'Callaghan93737bc2019-10-29 18:30:01 +1100701 if (amd_gen < CHIPSET_YANGTZE)
Carl-Daniel Hailfingera5bcbce2014-07-19 22:03:29 +0000702 register_spi_master(&spi_master_sb600);
Wei Hu31402ee2014-05-16 21:39:33 +0000703 else
Carl-Daniel Hailfingera5bcbce2014-07-19 22:03:29 +0000704 register_spi_master(&spi_master_yangtze);
Michael Karcherb05b9e12010-07-22 18:04:19 +0000705 return 0;
706}
707
Carl-Daniel Hailfingercceafa22010-05-26 01:45:41 +0000708#endif