Attention is currently required from: Jason Glenesk, Marshall Dawson, Felix Held. Raul Rangel has uploaded this change for review. ( https://review.coreboot.org/c/coreboot/+/56228 )
Change subject: soc/amd/common/block/lpc/spi_dma: Implement SPI DMA functionality ......................................................................
soc/amd/common/block/lpc/spi_dma: Implement SPI DMA functionality
This change will make it so the standard rdev readat call will use the SPI DMA controller if the alignment is correct, and the transfer size is larger than 64 bytes.
There is a magic bit that needs to be set for the SPI DMA controller to function correctly. This is only available in RN/CZN+.
BUG=b:179699789 TEST=Boot guybrush to OS. This reduces loading verstage by 40ms, verifying RW by 500us and loading romstage by 500 us.
Signed-off-by: Raul E Rangel rrangel@chromium.org Change-Id: I0be555956581fd82bbe1482d8afa8828c61aaa01 --- M src/soc/amd/common/block/include/amdblocks/lpc.h M src/soc/amd/common/block/lpc/spi_dma.c 2 files changed, 171 insertions(+), 5 deletions(-)
git pull ssh://review.coreboot.org:29418/coreboot refs/changes/28/56228/1
diff --git a/src/soc/amd/common/block/include/amdblocks/lpc.h b/src/soc/amd/common/block/include/amdblocks/lpc.h index 4e2660b..aa483e4 100644 --- a/src/soc/amd/common/block/include/amdblocks/lpc.h +++ b/src/soc/amd/common/block/include/amdblocks/lpc.h @@ -113,12 +113,24 @@
#define LPC_WIDEIO2_GENERIC_PORT 0x90
+#define LPC_ROM_DMA_SRC_ADDR 0xb0 +#define LPC_ROM_DMA_DST_ADDR 0xb4 /* LPC register 0xb8 is DWORD, here there are definitions for byte access. For example, bits 31-24 are accessed through byte access at register 0xbb. */ -#define LPC_ROM_DMA_EC_HOST_CONTROL 0xb8 -#define SPI_FROM_HOST_PREFETCH_EN BIT(24) -#define SPI_FROM_USB_PREFETCH_EN BIT(23) +#define LPC_ROM_DMA_EC_HOST_CONTROL 0xb8 +#define SPI_FROM_HOST_PREFETCH_EN BIT(24) +#define SPI_FROM_USB_PREFETCH_EN BIT(23) +#define LPC_ROM_DMA_CTRL_DW_COUNT_SHIFT 6 +#define LPC_ROM_DMA_CTRL_DW_COUNT_MASK (0x3ffUL << LPC_ROM_DMA_CTRL_DW_COUNT_SHIFT) +#define LPC_ROM_DMA_CTRL_ERROR BIT(1) +#define LPC_ROM_DMA_CTRL_START BIT(0) +#define LPC_ROM_DMA_MIN_ALIGNMENT (1 << 6) +#define LPC_ROM_DMA_CTRL_DW_COUNT(bytes) \ + (((bytes / LPC_ROM_DMA_MIN_ALIGNMENT) - 1) << LPC_ROM_DMA_CTRL_DW_COUNT_SHIFT) +#define LPC_ROM_DMA_CTRL_MAX_BYTES \ + (((LPC_ROM_DMA_CTRL_DW_COUNT_MASK >> LPC_ROM_DMA_CTRL_DW_COUNT_SHIFT) + 1) \ + * LPC_ROM_DMA_MIN_ALIGNMENT)
#define LPC_HOST_CONTROL 0xbb #define PREFETCH_EN_SPI_FROM_HOST BIT(0) diff --git a/src/soc/amd/common/block/lpc/spi_dma.c b/src/soc/amd/common/block/lpc/spi_dma.c index 470bfb1..9bee259 100644 --- a/src/soc/amd/common/block/lpc/spi_dma.c +++ b/src/soc/amd/common/block/lpc/spi_dma.c @@ -1,8 +1,16 @@ /* SPDX-License-Identifier: GPL-2.0-only */
+#include <amdblocks/lpc.h> +#include <amdblocks/spi.h> +#include <assert.h> #include <boot_device.h> -#include <commonlib/helpers.h> +#include <commonlib/bsd/cb_err.h> +#include <commonlib/bsd/helpers.h> #include <commonlib/region.h> +#include <console/console.h> +#include <delay.h> +#include <device/pci_ops.h> +#include <soc/pci_devs.h> #include <spi_flash.h> #include <string.h> #include <types.h> @@ -10,6 +18,14 @@ /* The ROM is memory mapped just below 4GiB. Form a pointer for the base. */ #define rom_base ((void *)(uintptr_t)(0x100000000ULL - CONFIG_ROM_SIZE))
+struct spi_dma_transaction { + uint8_t *target; + size_t offset; + size_t size; + size_t transfer_size; + size_t remaining; +}; + static void *spi_dma_mmap(const struct region_device *rd, size_t offset, size_t size __unused) { const struct mem_region_device *mdev; @@ -36,10 +52,134 @@ return size; }
+static bool spi_dma_is_busy(void) +{ + return pci_read_config32(SOC_LPC_DEV, LPC_ROM_DMA_EC_HOST_CONTROL) + & LPC_ROM_DMA_CTRL_START; +} + +static bool spi_dma_has_error(void) +{ + return pci_read_config32(SOC_LPC_DEV, LPC_ROM_DMA_EC_HOST_CONTROL) + & LPC_ROM_DMA_CTRL_ERROR; +} + +static bool can_use_dma(void *target, size_t offset, size_t size) +{ + if (!IS_ALIGNED((uintptr_t)target, LPC_ROM_DMA_MIN_ALIGNMENT)) + return false; + + if (!IS_ALIGNED(offset, LPC_ROM_DMA_MIN_ALIGNMENT)) + return false; + + return true; +} + +static void start_spi_dma_transaction(struct spi_dma_transaction *transaction) +{ + uint32_t ctrl; + + printk(BIOS_SPEW, "%s: dest: %p, offset: %#zx, remaining: %zu\n", __func__, + transaction->target, transaction->offset, transaction->remaining); + + /* + * We should have complete control over the DMA controller, so there shouldn't + * be any outstanding transactions. + */ + assert(!spi_dma_is_busy()); + assert(IS_ALIGNED((uintptr_t)transaction->target, LPC_ROM_DMA_MIN_ALIGNMENT)); + assert(IS_ALIGNED(transaction->offset, LPC_ROM_DMA_MIN_ALIGNMENT)); + assert(transaction->remaining >= LPC_ROM_DMA_MIN_ALIGNMENT); + + pci_write_config32(SOC_LPC_DEV, LPC_ROM_DMA_SRC_ADDR, transaction->offset); + pci_write_config32(SOC_LPC_DEV, LPC_ROM_DMA_DST_ADDR, (uintptr_t)transaction->target); + + ctrl = pci_read_config32(SOC_LPC_DEV, LPC_ROM_DMA_EC_HOST_CONTROL); + ctrl &= ~LPC_ROM_DMA_CTRL_DW_COUNT_MASK; + + transaction->transfer_size = MIN(LPC_ROM_DMA_CTRL_MAX_BYTES, transaction->remaining); + + ctrl |= LPC_ROM_DMA_CTRL_DW_COUNT(transaction->transfer_size); + ctrl |= LPC_ROM_DMA_CTRL_ERROR; /* Clear error */ + ctrl |= LPC_ROM_DMA_CTRL_START; + + pci_write_config32(SOC_LPC_DEV, LPC_ROM_DMA_EC_HOST_CONTROL, ctrl); +} + +/* Returns true of transaction is still in progress. */ +static bool continue_spi_dma_transaction(const struct region_device *rd, + struct spi_dma_transaction *transaction) +{ + /* Verify we are looking at the correct transaction */ + assert(pci_read_config32(SOC_LPC_DEV, LPC_ROM_DMA_SRC_ADDR) == transaction->offset); + + if (spi_dma_is_busy()) + return true; + + if (spi_dma_has_error()) + return false; + + transaction->target += transaction->transfer_size; + transaction->offset += transaction->transfer_size; + transaction->remaining -= transaction->transfer_size; + + if (transaction->remaining >= LPC_ROM_DMA_MIN_ALIGNMENT) { + start_spi_dma_transaction(transaction); + return true; + } else if (transaction->remaining > 0) { + /* Use mmap to finish off the transfer */ + spi_dma_readat_mmap(rd, transaction->target, transaction->offset, + transaction->remaining); + + transaction->target += transaction->remaining; + transaction->offset += transaction->remaining; + transaction->remaining -= transaction->remaining; + } + + return false; +} + +static ssize_t spi_dma_readat_dma(const struct region_device *rd, void *b, size_t offset, + size_t size) +{ + struct spi_dma_transaction transaction = { + .target = b, + .offset = offset, + .size = size, + .remaining = size, + }; + + printk(BIOS_SPEW, "%s: start: dest: %p, offset: %#zx, size: %zu\n", __func__, b, offset, + size); + + start_spi_dma_transaction(&transaction); + + do { + udelay(10); + } while (continue_spi_dma_transaction(rd, &transaction)); + + printk(BIOS_SPEW, "%s: end: dest: %p, offset: %#zx, size: %zu, remaining: %zu\n", + __func__, b, offset, size, transaction.remaining); + + return transaction.size - transaction.remaining; +} + +static ssize_t spi_dma_readat(const struct region_device *rd, void *b, size_t offset, + size_t size) +{ + assert(offset + size < CONFIG_ROM_SIZE); + assert(size); + + if (size < LPC_ROM_DMA_MIN_ALIGNMENT || !can_use_dma(b, offset, size)) + return spi_dma_readat_mmap(rd, b, offset, size); + else + return spi_dma_readat_dma(rd, b, offset, size); +} + const struct region_device_ops spi_dma_rdev_ro_ops = { .mmap = spi_dma_mmap, .munmap = spi_dma_munmap, - .readat = spi_dma_readat_mmap, + .readat = spi_dma_readat, };
static const struct mem_region_device boot_dev = { @@ -60,3 +200,17 @@
return 1; } + +/* Fix the SPI DMA controller */ +static void spi_dma_fix(void) +{ + /* Internal only registers */ + uint8_t val = spi_read8(0xfc); + val |= BIT(6); + spi_write8(0xfc, val); +} + +void boot_device_init(void) +{ + spi_dma_fix(); +}