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
|
/*
* This file is part of the flashrom project.
*
* Copyright (C) 2010 Carl-Daniel Hailfinger
*
* 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; version 2 of the License.
*
* 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.
*/
/* Driver for the NVIDIA MCP6x/MCP7x MCP6X_SPI controller.
* Based on clean room reverse engineered docs from
* https://flashrom.org/pipermail/flashrom/2009-December/001180.html
* created by Michael Karcher.
*/
#include <stdlib.h>
#include <ctype.h>
#include "flash.h"
#include "programmer.h"
#include "hwaccess_physmap.h"
#include "platform/pci.h"
/* Bit positions for each pin. */
#define MCP6X_SPI_CS 1
#define MCP6X_SPI_SCK 2
#define MCP6X_SPI_MOSI 3
#define MCP6X_SPI_MISO 4
#define MCP6X_SPI_REQUEST 0
#define MCP6X_SPI_GRANT 8
struct mcp6x_spi_data {
void *spibar;
/* Cached value of last GPIO state. */
uint8_t gpiostate;
};
static void mcp6x_request_spibus(void *spi_data)
{
struct mcp6x_spi_data *data = spi_data;
data->gpiostate = mmio_readb(data->spibar + 0x530);
data->gpiostate |= 1 << MCP6X_SPI_REQUEST;
mmio_writeb(data->gpiostate, data->spibar + 0x530);
/* Wait until we are allowed to use the SPI bus. */
while (!(mmio_readw(data->spibar + 0x530) & (1 << MCP6X_SPI_GRANT))) ;
/* Update the cache. */
data->gpiostate = mmio_readb(data->spibar + 0x530);
}
static void mcp6x_release_spibus(void *spi_data)
{
struct mcp6x_spi_data *data = spi_data;
data->gpiostate &= ~(1 << MCP6X_SPI_REQUEST);
mmio_writeb(data->gpiostate, data->spibar + 0x530);
}
static void mcp6x_bitbang_set_cs(int val, void *spi_data)
{
struct mcp6x_spi_data *data = spi_data;
data->gpiostate &= ~(1 << MCP6X_SPI_CS);
data->gpiostate |= (val << MCP6X_SPI_CS);
mmio_writeb(data->gpiostate, data->spibar + 0x530);
}
static void mcp6x_bitbang_set_sck(int val, void *spi_data)
{
struct mcp6x_spi_data *data = spi_data;
data->gpiostate &= ~(1 << MCP6X_SPI_SCK);
data->gpiostate |= (val << MCP6X_SPI_SCK);
mmio_writeb(data->gpiostate, data->spibar + 0x530);
}
static void mcp6x_bitbang_set_mosi(int val, void *spi_data)
{
struct mcp6x_spi_data *data = spi_data;
data->gpiostate &= ~(1 << MCP6X_SPI_MOSI);
data->gpiostate |= (val << MCP6X_SPI_MOSI);
mmio_writeb(data->gpiostate, data->spibar + 0x530);
}
static int mcp6x_bitbang_get_miso(void *spi_data)
{
struct mcp6x_spi_data *data = spi_data;
data->gpiostate = mmio_readb(data->spibar + 0x530);
return (data->gpiostate >> MCP6X_SPI_MISO) & 0x1;
}
static const struct bitbang_spi_master bitbang_spi_master_mcp6x = {
.set_cs = mcp6x_bitbang_set_cs,
.set_sck = mcp6x_bitbang_set_sck,
.set_mosi = mcp6x_bitbang_set_mosi,
.get_miso = mcp6x_bitbang_get_miso,
.request_bus = mcp6x_request_spibus,
.release_bus = mcp6x_release_spibus,
.half_period = 0,
};
static int mcp6x_shutdown(void *spi_data)
{
free(spi_data);
return 0;
}
int mcp6x_spi_init(int want_spi)
{
uint16_t status;
uint32_t mcp6x_spibaraddr;
struct pci_dev *smbusdev;
void *mcp6x_spibar = NULL;
uint8_t mcp_gpiostate;
/* Look for the SMBus device (SMBus PCI class) */
smbusdev = pcidev_find_vendorclass(0x10de, 0x0c05);
if (!smbusdev) {
if (want_spi) {
msg_perr("ERROR: SMBus device not found. Not enabling "
"SPI.\n");
return 1;
} else {
msg_pinfo("Odd. SMBus device not found.\n");
return 0;
}
}
msg_pdbg("Found SMBus device %04x:%04x at %02x:%02x:%01x\n",
smbusdev->vendor_id, smbusdev->device_id,
smbusdev->bus, smbusdev->dev, smbusdev->func);
/* Locate the BAR where the SPI interface lives. */
mcp6x_spibaraddr = pci_read_long(smbusdev, 0x74);
/* BAR size is 64k, bits 15..4 are zero, bit 3..0 declare a
* 32-bit non-prefetchable memory BAR.
*/
mcp6x_spibaraddr &= ~0xffff;
msg_pdbg("MCP SPI BAR is at 0x%08"PRIx32"\n", mcp6x_spibaraddr);
/* Accessing a NULL pointer BAR is evil. Don't do it. */
if (!mcp6x_spibaraddr && want_spi) {
msg_perr("Error: Chipset is strapped for SPI, but MCP SPI BAR is invalid.\n");
return 1;
} else if (!mcp6x_spibaraddr && !want_spi) {
msg_pdbg("MCP SPI is not used.\n");
return 0;
} else if (mcp6x_spibaraddr && !want_spi) {
msg_pdbg("Strange. MCP SPI BAR is valid, but chipset apparently doesn't have SPI enabled.\n");
/* FIXME: Should we enable SPI anyway? */
return 0;
}
/* Map the BAR. Bytewise/wordwise access at 0x530 and 0x540. */
mcp6x_spibar = rphysmap("NVIDIA MCP6x SPI", mcp6x_spibaraddr, 0x544);
if (mcp6x_spibar == ERROR_PTR)
return 1;
status = mmio_readw(mcp6x_spibar + 0x530);
msg_pdbg("SPI control is 0x%04x, req=%i, gnt=%i\n",
status, (status >> MCP6X_SPI_REQUEST) & 0x1,
(status >> MCP6X_SPI_GRANT) & 0x1);
mcp_gpiostate = status & 0xff;
struct mcp6x_spi_data *data = calloc(1, sizeof(*data));
if (!data) {
msg_perr("Unable to allocate space for SPI master data\n");
return 1;
}
data->spibar = mcp6x_spibar;
data->gpiostate = mcp_gpiostate;
if (register_shutdown(mcp6x_shutdown, data)) {
free(data);
return 1;
}
if (register_spi_bitbang_master(&bitbang_spi_master_mcp6x, data)) {
/* This should never happen. */
msg_perr("MCP6X bitbang SPI master init failed!\n");
return 1;
}
return 0;
}
|