/* * This file is part of the flashrom project. * * Copyright (C) 2013 Ricardo Ribalda - Qtechnology A/S * Copyright (C) 2011, 2014 Stefan Tauner * * Based on nicinctel_spi.c and ichspi.c * * 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. */ /* * Datasheet: Intel 82580 Quad/Dual Gigabit Ethernet LAN Controller Datasheet * 3.3.1.4: General EEPROM Software Access * 4.7: Access to shared resources (FIXME: we should probably use this semaphore interface) * 7.4: Register Descriptions */ /* * Datasheet: Intel Ethernet Controller I210: Datasheet * 8.4.3: EEPROM-Mode Read Register * 8.4.6: EEPROM-Mode Write Register * Write process inspired on kernel e1000_i210.c */ #include #include #include "flash.h" #include "spi.h" #include "programmer.h" #include "hwaccess.h" #define PCI_VENDOR_ID_INTEL 0x8086 #define MEMMAP_SIZE 0x1c /* Only EEC, EERD and EEWR are needed. */ #define EEC 0x10 /* EEPROM/Flash Control Register */ #define EERD 0x14 /* EEPROM Read Register */ #define EEWR 0x18 /* EEPROM Write Register */ /* EPROM/Flash Control Register bits */ #define EE_SCK 0 #define EE_CS 1 #define EE_SI 2 #define EE_SO 3 #define EE_REQ 6 #define EE_GNT 7 #define EE_PRES 8 #define EE_SIZE 11 #define EE_SIZE_MASK 0xf #define EE_FLUPD 23 #define EE_FLUDONE 26 /* EEPROM Read Register bits */ #define EERD_START 0 #define EERD_DONE 1 #define EERD_ADDR 2 #define EERD_DATA 16 /* EEPROM Write Register bits */ #define EEWR_CMDV 0 #define EEWR_DONE 1 #define EEWR_ADDR 2 #define EEWR_DATA 16 #define BIT(x) (1<chip->total_size = 4; flash->chip->page_size = flash->chip->total_size * 1024; flash->chip->tested = TEST_OK_PREW; flash->chip->gran = write_gran_1byte_implicit_erase; flash->chip->block_erasers->eraseblocks[0].size = flash->chip->page_size; flash->chip->block_erasers->eraseblocks[0].count = 1; return 1; } static int nicintel_ee_probe_82580(struct flashctx *flash) { if (nicintel_pci->device_id == UNPROG_DEVICE) flash->chip->total_size = 16; /* Fall back to minimum supported size. */ else { uint32_t tmp = pci_mmio_readl(nicintel_eebar + EEC); tmp = ((tmp >> EE_SIZE) & EE_SIZE_MASK); switch (tmp) { case 7: flash->chip->total_size = 16; break; case 8: flash->chip->total_size = 32; break; default: msg_cerr("Unsupported chip size 0x%x\n", tmp); return 0; } } flash->chip->page_size = EE_PAGE_MASK + 1; flash->chip->tested = TEST_OK_PREW; flash->chip->gran = write_gran_1byte_implicit_erase; flash->chip->block_erasers->eraseblocks[0].size = (EE_PAGE_MASK + 1); flash->chip->block_erasers->eraseblocks[0].count = (flash->chip->total_size * 1024) / (EE_PAGE_MASK + 1); return 1; } #define MAX_ATTEMPTS 10000000 static int nicintel_ee_read_word(unsigned int addr, uint16_t *data) { uint32_t tmp = BIT(EERD_START) | (addr << EERD_ADDR); pci_mmio_writel(tmp, nicintel_eebar + EERD); /* Poll done flag. 10.000.000 cycles seem to be enough. */ uint32_t i; for (i = 0; i < MAX_ATTEMPTS; i++) { tmp = pci_mmio_readl(nicintel_eebar + EERD); if (tmp & BIT(EERD_DONE)) { *data = (tmp >> EERD_DATA) & 0xffff; return 0; } } return -1; } static int nicintel_ee_read(struct flashctx *flash, uint8_t *buf, unsigned int addr, unsigned int len) { uint16_t data; /* The NIC interface always reads 16 b words so we need to convert the address and handle odd address * explicitly at the start (and also at the end in the loop below). */ if (addr & 1) { if (nicintel_ee_read_word(addr / 2, &data)) return -1; *buf++ = data & 0xff; addr++; len--; } while (len > 0) { if (nicintel_ee_read_word(addr / 2, &data)) return -1; *buf++ = data & 0xff; addr++; len--; if (len > 0) { *buf++ = (data >> 8) & 0xff; addr++; len--; } } return 0; } static int nicintel_ee_write_word_i210(unsigned int addr, uint16_t data) { uint32_t eewr; eewr = addr << EEWR_ADDR; eewr |= data << EEWR_DATA; eewr |= BIT(EEWR_CMDV); pci_mmio_writel(eewr, nicintel_eebar + EEWR); programmer_delay(5); int i; for (i = 0; i < MAX_ATTEMPTS; i++) if (pci_mmio_readl(nicintel_eebar + EEWR) & BIT(EEWR_DONE)) return 0; return -1; } static int nicintel_ee_write_i210(struct flashctx *flash, const uint8_t *buf, unsigned int addr, unsigned int len) { done_i20_write = true; if (addr & 1) { uint16_t data; if (nicintel_ee_read_word(addr / 2, &data)) { msg_perr("Timeout reading heading byte\n"); return -1; } data &= 0xff; data |= (buf ? (buf[0]) : 0xff) << 8; if (nicintel_ee_write_word_i210(addr / 2, data)) { msg_perr("Timeout writing heading word\n"); return -1; } if (buf) buf ++; addr ++; len --; } while (len > 0) { uint16_t data; if (len == 1) { if (nicintel_ee_read_word(addr / 2, &data)) { msg_perr("Timeout reading tail byte\n"); return -1; } data &= 0xff00; data |= buf ? (buf[0]) : 0xff; } else { if (buf) data = buf[0] | (buf[1] << 8); else data = 0xffff; } if (nicintel_ee_write_word_i210(addr / 2, data)) { msg_perr("Timeout writing Shadow RAM\n"); return -1; } if (buf) buf += 2; if (len > 2) len -= 2; else len = 0; addr += 2; } return 0; } static int nicintel_ee_erase_i210(struct flashctx *flash, unsigned int addr, unsigned int len) { return nicintel_ee_write_i210(flash, NULL, addr, len); } static int nicintel_ee_bitset(int reg, int bit, bool val) { uint32_t tmp; tmp = pci_mmio_readl(nicintel_eebar + reg); if (val) tmp |= BIT(bit); else tmp &= ~BIT(bit); pci_mmio_writel(tmp, nicintel_eebar + reg); return -1; } /* Shifts one byte out while receiving another one by bitbanging (denoted "direct access" in the datasheet). */ static int nicintel_ee_bitbang(uint8_t mosi, uint8_t *miso) { uint8_t out = 0x0; int i; for (i = 7; i >= 0; i--) { nicintel_ee_bitset(EEC, EE_SI, mosi & BIT(i)); nicintel_ee_bitset(EEC, EE_SCK, 1); if (miso != NULL) { uint32_t tmp = pci_mmio_readl(nicintel_eebar + EEC); if (tmp & BIT(EE_SO)) out |= BIT(i); } nicintel_ee_bitset(EEC, EE_SCK, 0); } if (miso != NULL) *miso = out; return 0; } /* Polls the WIP bit of the status register of the attached EEPROM via bitbanging. */ static int nicintel_ee_ready(void) { unsigned int i; for (i = 0; i < 1000; i++) { nicintel_ee_bitset(EEC, EE_CS, 0); nicintel_ee_bitbang(JEDEC_RDSR, NULL); uint8_t rdsr; nicintel_ee_bitbang(0x00, &rdsr); nicintel_ee_bitset(EEC, EE_CS, 1); programmer_delay(1); if (!(rdsr & SPI_SR_WIP)) { return 0; } } return -1; } /* Requests direct access to the SPI pins. */ static int nicintel_ee_req(void) { uint32_t tmp; nicintel_ee_bitset(EEC, EE_REQ, 1); tmp = pci_mmio_readl(nicintel_eebar + EEC); if (!(tmp & BIT(EE_GNT))) { msg_perr("Enabling eeprom access failed.\n"); return 1; } nicintel_ee_bitset(EEC, EE_SCK, 0); return 0; } static int nicintel_ee_write_82580(struct flashctx *flash, const uint8_t *buf, unsigned int addr, unsigned int len) { if (nicintel_ee_req()) return -1; int ret = -1; if (nicintel_ee_ready()) goto out; while (len > 0) { /* WREN */ nicintel_ee_bitset(EEC, EE_CS, 0); nicintel_ee_bitbang(JEDEC_WREN, NULL); nicintel_ee_bitset(EEC, EE_CS, 1); programmer_delay(1); /* data */ nicintel_ee_bitset(EEC, EE_CS, 0); nicintel_ee_bitbang(JEDEC_BYTE_PROGRAM, NULL); nicintel_ee_bitbang((addr >> 8) & 0xff, NULL); nicintel_ee_bitbang(addr & 0xff, NULL); while (len > 0) { nicintel_ee_bitbang((buf) ? *buf++ : 0xff, NULL); len--; addr++; if (!(addr & EE_PAGE_MASK)) break; } nicintel_ee_bitset(EEC, EE_CS, 1); programmer_delay(1); if (nicintel_ee_ready()) goto out; } ret = 0; out: nicintel_ee_bitset(EEC, EE_REQ, 0); /* Give up direct access. */ return ret; } static int nicintel_ee_erase_82580(struct flashctx *flash, unsigned int addr, unsigned int len) { return nicintel_ee_write_82580(flash, NULL, addr, len); } static const struct opaque_master opaque_master_nicintel_ee_82580 = { .probe = nicintel_ee_probe_82580, .read = nicintel_ee_read, .write = nicintel_ee_write_82580, .erase = nicintel_ee_erase_82580, }; static const struct opaque_master opaque_master_nicintel_ee_i210 = { .probe = nicintel_ee_probe_i210, .read = nicintel_ee_read, .write = nicintel_ee_write_i210, .erase = nicintel_ee_erase_i210, }; static int nicintel_ee_shutdown_i210(void *arg) { if (!done_i20_write) return 0; uint32_t flup = pci_mmio_readl(nicintel_eebar + EEC); flup |= BIT(EE_FLUPD); pci_mmio_writel(flup, nicintel_eebar + EEC); int i; for (i = 0; i < MAX_ATTEMPTS; i++) if (pci_mmio_readl(nicintel_eebar + EEC) & BIT(EE_FLUDONE)) return 0; msg_perr("Flash update failed\n"); return -1; } static int nicintel_ee_shutdown_82580(void *eecp) { uint32_t old_eec = *(uint32_t *)eecp; /* Request bitbanging and unselect the chip first to be safe. */ if (nicintel_ee_req() || nicintel_ee_bitset(EEC, EE_CS, 1)) return -1; /* Try to restore individual bits we care about. */ int ret = nicintel_ee_bitset(EEC, EE_SCK, old_eec & BIT(EE_SCK)); ret |= nicintel_ee_bitset(EEC, EE_SI, old_eec & BIT(EE_SI)); ret |= nicintel_ee_bitset(EEC, EE_CS, old_eec & BIT(EE_CS)); /* REQ will be cleared by hardware anyway after 2 seconds of inactivity on the SPI pins (3.3.2.1). */ ret |= nicintel_ee_bitset(EEC, EE_REQ, old_eec & BIT(EE_REQ)); free(eecp); return ret; } int nicintel_ee_init(void) { if (rget_io_perms()) return 1; struct pci_dev *dev = pcidev_init(nics_intel_ee, PCI_BASE_ADDRESS_0); if (!dev) return 1; uint32_t io_base_addr = pcidev_readbar(dev, PCI_BASE_ADDRESS_0); if (!io_base_addr) return 1; if (!is_i210(dev->device_id)) { nicintel_eebar = rphysmap("Intel Gigabit NIC w/ SPI EEPROM", io_base_addr, MEMMAP_SIZE); if (!nicintel_eebar) return 1; nicintel_pci = dev; if ((dev->device_id != UNPROG_DEVICE)) { uint32_t eec = pci_mmio_readl(nicintel_eebar + EEC); /* C.f. 3.3.1.5 for the detection mechanism (maybe? contradicting the EE_PRES definition), and 3.3.1.7 for possible recovery. */ if (!(eec & BIT(EE_PRES))) { msg_perr("Controller reports no EEPROM is present.\n"); return 1; } uint32_t *eecp = malloc(sizeof(uint32_t)); if (eecp == NULL) return 1; *eecp = eec; if (register_shutdown(nicintel_ee_shutdown_82580, eecp)) return 1; } return register_opaque_master(&opaque_master_nicintel_ee_82580); } else { nicintel_eebar = rphysmap("Intel i210 NIC w/ emulated EEPROM", io_base_addr + 0x12000, MEMMAP_SIZE); if (!nicintel_eebar) return 1; if (register_shutdown(nicintel_ee_shutdown_i210, NULL)) return 1; return register_opaque_master(&opaque_master_nicintel_ee_i210); } return 1; }