1 /*
2 * This file is part of the flashrom project.
3 *
4 * Copyright (C) 2010 Carl-Daniel Hailfinger
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; version 2 of the License.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 */
15
16 /* Driver for the NVIDIA MCP6x/MCP7x MCP6X_SPI controller.
17 * Based on clean room reverse engineered docs from
18 * https://flashrom.org/pipermail/flashrom/2009-December/001180.html
19 * created by Michael Karcher.
20 */
21
22 #include <stdlib.h>
23 #include <ctype.h>
24 #include "flash.h"
25 #include "programmer.h"
26 #include "hwaccess_physmap.h"
27 #include "platform/pci.h"
28
29 /* Bit positions for each pin. */
30
31 #define MCP6X_SPI_CS 1
32 #define MCP6X_SPI_SCK 2
33 #define MCP6X_SPI_MOSI 3
34 #define MCP6X_SPI_MISO 4
35 #define MCP6X_SPI_REQUEST 0
36 #define MCP6X_SPI_GRANT 8
37
38 struct mcp6x_spi_data {
39 void *spibar;
40 /* Cached value of last GPIO state. */
41 uint8_t gpiostate;
42 };
43
mcp6x_request_spibus(void * spi_data)44 static void mcp6x_request_spibus(void *spi_data)
45 {
46 struct mcp6x_spi_data *data = spi_data;
47
48 data->gpiostate = mmio_readb(data->spibar + 0x530);
49 data->gpiostate |= 1 << MCP6X_SPI_REQUEST;
50 mmio_writeb(data->gpiostate, data->spibar + 0x530);
51
52 /* Wait until we are allowed to use the SPI bus. */
53 while (!(mmio_readw(data->spibar + 0x530) & (1 << MCP6X_SPI_GRANT))) ;
54
55 /* Update the cache. */
56 data->gpiostate = mmio_readb(data->spibar + 0x530);
57 }
58
mcp6x_release_spibus(void * spi_data)59 static void mcp6x_release_spibus(void *spi_data)
60 {
61 struct mcp6x_spi_data *data = spi_data;
62
63 data->gpiostate &= ~(1 << MCP6X_SPI_REQUEST);
64 mmio_writeb(data->gpiostate, data->spibar + 0x530);
65 }
66
mcp6x_bitbang_set_cs(int val,void * spi_data)67 static void mcp6x_bitbang_set_cs(int val, void *spi_data)
68 {
69 struct mcp6x_spi_data *data = spi_data;
70
71 data->gpiostate &= ~(1 << MCP6X_SPI_CS);
72 data->gpiostate |= (val << MCP6X_SPI_CS);
73 mmio_writeb(data->gpiostate, data->spibar + 0x530);
74 }
75
mcp6x_bitbang_set_sck(int val,void * spi_data)76 static void mcp6x_bitbang_set_sck(int val, void *spi_data)
77 {
78 struct mcp6x_spi_data *data = spi_data;
79
80 data->gpiostate &= ~(1 << MCP6X_SPI_SCK);
81 data->gpiostate |= (val << MCP6X_SPI_SCK);
82 mmio_writeb(data->gpiostate, data->spibar + 0x530);
83 }
84
mcp6x_bitbang_set_mosi(int val,void * spi_data)85 static void mcp6x_bitbang_set_mosi(int val, void *spi_data)
86 {
87 struct mcp6x_spi_data *data = spi_data;
88
89 data->gpiostate &= ~(1 << MCP6X_SPI_MOSI);
90 data->gpiostate |= (val << MCP6X_SPI_MOSI);
91 mmio_writeb(data->gpiostate, data->spibar + 0x530);
92 }
93
mcp6x_bitbang_get_miso(void * spi_data)94 static int mcp6x_bitbang_get_miso(void *spi_data)
95 {
96 struct mcp6x_spi_data *data = spi_data;
97
98 data->gpiostate = mmio_readb(data->spibar + 0x530);
99 return (data->gpiostate >> MCP6X_SPI_MISO) & 0x1;
100 }
101
102 static const struct bitbang_spi_master bitbang_spi_master_mcp6x = {
103 .set_cs = mcp6x_bitbang_set_cs,
104 .set_sck = mcp6x_bitbang_set_sck,
105 .set_mosi = mcp6x_bitbang_set_mosi,
106 .get_miso = mcp6x_bitbang_get_miso,
107 .request_bus = mcp6x_request_spibus,
108 .release_bus = mcp6x_release_spibus,
109 .half_period = 0,
110 };
111
mcp6x_shutdown(void * spi_data)112 static int mcp6x_shutdown(void *spi_data)
113 {
114 free(spi_data);
115 return 0;
116 }
117
mcp6x_spi_init(int want_spi)118 int mcp6x_spi_init(int want_spi)
119 {
120 uint16_t status;
121 uint32_t mcp6x_spibaraddr;
122 struct pci_dev *smbusdev;
123 void *mcp6x_spibar = NULL;
124 uint8_t mcp_gpiostate;
125
126 /* Look for the SMBus device (SMBus PCI class) */
127 smbusdev = pcidev_find_vendorclass(0x10de, 0x0c05);
128 if (!smbusdev) {
129 if (want_spi) {
130 msg_perr("ERROR: SMBus device not found. Not enabling "
131 "SPI.\n");
132 return 1;
133 } else {
134 msg_pinfo("Odd. SMBus device not found.\n");
135 return 0;
136 }
137 }
138 msg_pdbg("Found SMBus device %04x:%04x at %02x:%02x:%01x\n",
139 smbusdev->vendor_id, smbusdev->device_id,
140 smbusdev->bus, smbusdev->dev, smbusdev->func);
141
142
143 /* Locate the BAR where the SPI interface lives. */
144 mcp6x_spibaraddr = pci_read_long(smbusdev, 0x74);
145 /* BAR size is 64k, bits 15..4 are zero, bit 3..0 declare a
146 * 32-bit non-prefetchable memory BAR.
147 */
148 mcp6x_spibaraddr &= ~0xffff;
149 msg_pdbg("MCP SPI BAR is at 0x%08"PRIx32"\n", mcp6x_spibaraddr);
150
151 /* Accessing a NULL pointer BAR is evil. Don't do it. */
152 if (!mcp6x_spibaraddr && want_spi) {
153 msg_perr("Error: Chipset is strapped for SPI, but MCP SPI BAR is invalid.\n");
154 return 1;
155 } else if (!mcp6x_spibaraddr && !want_spi) {
156 msg_pdbg("MCP SPI is not used.\n");
157 return 0;
158 } else if (mcp6x_spibaraddr && !want_spi) {
159 msg_pdbg("Strange. MCP SPI BAR is valid, but chipset apparently doesn't have SPI enabled.\n");
160 /* FIXME: Should we enable SPI anyway? */
161 return 0;
162 }
163 /* Map the BAR. Bytewise/wordwise access at 0x530 and 0x540. */
164 mcp6x_spibar = rphysmap("NVIDIA MCP6x SPI", mcp6x_spibaraddr, 0x544);
165 if (mcp6x_spibar == ERROR_PTR)
166 return 1;
167
168 status = mmio_readw(mcp6x_spibar + 0x530);
169 msg_pdbg("SPI control is 0x%04x, req=%i, gnt=%i\n",
170 status, (status >> MCP6X_SPI_REQUEST) & 0x1,
171 (status >> MCP6X_SPI_GRANT) & 0x1);
172 mcp_gpiostate = status & 0xff;
173
174 struct mcp6x_spi_data *data = calloc(1, sizeof(*data));
175 if (!data) {
176 msg_perr("Unable to allocate space for SPI master data\n");
177 return 1;
178 }
179 data->spibar = mcp6x_spibar;
180 data->gpiostate = mcp_gpiostate;
181
182 if (register_shutdown(mcp6x_shutdown, data)) {
183 free(data);
184 return 1;
185 }
186 if (register_spi_bitbang_master(&bitbang_spi_master_mcp6x, data)) {
187 /* This should never happen. */
188 msg_perr("MCP6X bitbang SPI master init failed!\n");
189 return 1;
190 }
191
192 return 0;
193 }
194