From 49aad6b387b3885f43d75aa50eceab50c6ac4aa9 Mon Sep 17 00:00:00 2001 From: Ionela Voinescu Date: Tue, 9 Sep 2014 20:18:55 +0100 Subject: soc/imgtec/pistachio: Add IMGTEC SPI controller driver The Serial Peripheral Flash Interface (SPFI) block allows communication with various devices over the SPI bus. It uses a configurable transaction interface and it clocks the bus according to the configured command, address, gap (aka dummy) and data lengths. This controller requires the SPI_ATOMIC_SEQUENCING flag set (write and read done in the same transaction) as it cannot directly control CS and will assert/de-assert CS at the beginning/end of a transaction itself. Note that the size of any transfer cannot be greater than 64KB - 1, as this is configured in a 16-bit field. The SOC has 2 SPFI interfaces each of them providing 5 slave select lines. SPFI 0 supports single and dual modes, SPFI 1 supports single, dual and quad modes. For SPFI interface 0: - The block needs the system PLL and the following top level SPI clock registers to be set: - CR_cr_top_spi0clkinternal_CTRL[2:0] with division value - CR_MIPS_CLOCK_GATE[19]: bit cr_top_SPI0CLKOUT_MIPS set - CR_cr_top_SPI0CLKOUT_CTRL[6:0] with division value - The following MFIO configuration parameters are also required: Signal name Pad name MFIO mode spim0_d0_txd MFIO_MIPS_10 0 spim0_d1_rxd MFIO_MIPS_9 0 spim0_mclk MFIO_MIPS_8 0 spim0_cs0 MFIO_MIPS_2 1 spim0_cs1 MFIO_MIPS_1 1 spim0_cs2 MFIO_MIPS_55 1 MFIO_MIPS_28 1 spim0_cs3 MFIO_MIPS_56 1 MFIO_MIPS_29 1 spim0_cs4 MFIO_MIPS_57 1 MFIO_MIMPS_30 1 For SPFI interface 1: - The block needs the system PLL and the following top level SPI clock registers to be set: - CR_cr_top_spi1clkinternal_CTRL[2:0] with division value - CR_MIPS_CLOCK_GATE[20]: bit cr_top_SPI1CLKOUT_MIPS set - CR_cr_top_SPI1CLKOUT_CTRL[6:0] with division value - The following MFIO configuration parameters are also required: Signal name Pad name MFIO mode spim1_d0_txd MFIO_MIPS_5 0 spim1_d1_rxd MFIO_MIPS_4 0 spim1_mclk MFIO_MIPS_3 0 spim1_d2 MFIO_MIPS_6 0 spim1_d3 MFIO_MIPS_7 0 spim1_cs0 MFIO_MIPS_0 0 spim1_cs1 MFIO_MIPS_1 0 MFIO_MIPS_58 1 spim1_cs2 MFIO_MIPS_2 0 MFIO_MIPS_55 2 MFIO_MIPS_31 1 spim1_cs3 MFIO_MIPS_56 2 spim1_cs4 MFIO_MIPS_57 2 BUG=chrome-os-partner:31438, chrome-os-partner:32441 TEST=Tested as bare-metal driver on Pistachio FPGA Change-Id: I3b3e4475976e6fba58cef93b12d997ec5cb26341 Signed-off-by: Patrick Georgi Original-Commit-Id: 621849942e27f7d6cf2c8ade7f2c4d18d2318b91 Original-Change-Id: Ib257eb6236bd2895281175871b4ab979660f1239 Original-Signed-off-by: Ionela Voinescu Original-Reviewed-on: https://chromium-review.googlesource.com/217320 Original-Reviewed-by: Vadim Bendebury Reviewed-on: http://review.coreboot.org/9049 Tested-by: build bot (Jenkins) Reviewed-by: Stefan Reinauer --- src/soc/imgtec/pistachio/Kconfig | 1 + src/soc/imgtec/pistachio/Makefile.inc | 1 + src/soc/imgtec/pistachio/cpu.h | 24 ++ src/soc/imgtec/pistachio/spi.c | 523 +++++++++++++++++++++++++++++++++- src/soc/imgtec/pistachio/spi.h | 358 +++++++++++++++++++++++ 5 files changed, 897 insertions(+), 10 deletions(-) create mode 100644 src/soc/imgtec/pistachio/cpu.h create mode 100644 src/soc/imgtec/pistachio/spi.h (limited to 'src') diff --git a/src/soc/imgtec/pistachio/Kconfig b/src/soc/imgtec/pistachio/Kconfig index 1acfe39a8c..83408bd8a1 100644 --- a/src/soc/imgtec/pistachio/Kconfig +++ b/src/soc/imgtec/pistachio/Kconfig @@ -26,6 +26,7 @@ config CPU_IMGTEC_PISTACHIO select HAVE_MONOTONIC_TIMER select HAVE_UART_MEMORY_MAPPED select HAVE_UART_SPECIAL + select SPI_ATOMIC_SEQUENCING bool if CPU_IMGTEC_PISTACHIO diff --git a/src/soc/imgtec/pistachio/Makefile.inc b/src/soc/imgtec/pistachio/Makefile.inc index 1d5e0c2b16..e695abd5bb 100644 --- a/src/soc/imgtec/pistachio/Makefile.inc +++ b/src/soc/imgtec/pistachio/Makefile.inc @@ -39,6 +39,7 @@ ramstage-y += timestamp.c romstage-y += cbmem.c romstage-y += romstage.c +romstage-y += monotonic_timer.c romstage-y += timestamp.c # Generate the actual coreboot bootblock code diff --git a/src/soc/imgtec/pistachio/cpu.h b/src/soc/imgtec/pistachio/cpu.h new file mode 100644 index 0000000000..c61c58dfca --- /dev/null +++ b/src/soc/imgtec/pistachio/cpu.h @@ -0,0 +1,24 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2014 Imagination Technologies + * + * 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. + * + */ + +#ifndef __SOC_IMGTEC_DANUBE_CPU_H__ +#define __SOC_IMGTEC_DANUBE_CPU_H__ + +#define IMG_SPIM0_BASE_ADDRESS 0xB8100F00 +#define IMG_SPIM1_BASE_ADDRESS 0xB8101000 + +#endif diff --git a/src/soc/imgtec/pistachio/spi.c b/src/soc/imgtec/pistachio/spi.c index 95bf8272a8..2b21475d0a 100644 --- a/src/soc/imgtec/pistachio/spi.c +++ b/src/soc/imgtec/pistachio/spi.c @@ -1,9 +1,11 @@ /* - * Copyright (C) 2014 Google, Inc. + * This file is part of the coreboot project. * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. + * Copyright (C) 2014 Imagination Technologies + * + * 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 @@ -11,25 +13,526 @@ * GNU General Public License for more details. */ -#include +#include +#include +#include #include +#include +#include "cpu.h" +#include "spi.h" + +#if !CONFIG_SPI_ATOMIC_SEQUENCING +#error "Unsupported SPI driver API" +#endif + +struct img_spi_slave { + struct spi_slave slave; + /* SPIM instance device parameters */ + struct spim_device_parameters device_parameters; + /* SPIM instance base address */ + u32 base; + /* Boolean property that is TRUE if API has been initialised */ + int initialised; +}; + +/* Allocate memory for the maximum number of devices */ +static struct +img_spi_slave img_spi_slaves[SPIM_NUM_BLOCKS*SPIM_NUM_PORTS_PER_BLOCK]; + +/* + * Wait for the bit at the shift position to be set in reg + * If the bit is not set in SPI_TIMEOUT_VALUE_US return with error + */ +static int wait_status(u32 reg, u32 shift) +{ + struct stopwatch sw; + + stopwatch_init_usecs_expire(&sw, SPI_TIMEOUT_VALUE_US); + while (!(read32(reg) & (1 << shift))) { + if (stopwatch_expired(&sw)) + return -SPIM_TIMEOUT; + } + return SPIM_OK; +} + +/* Transmitter function. Fills TX FIFO with data before enabling SPIM */ +static int transmitdata(struct spi_slave *slave, u8 *buffer, u32 size) +{ + u32 blocksize, base, write_data; + int ret; + + base = container_of(slave, struct img_spi_slave, slave)->base; + while (size) { + /* Wait until FIFO empty */ + write32(base + SPFI_INT_CLEAR_REG_OFFSET, SPFI_SDE_MASK); + ret = wait_status(base + SPFI_INT_STATUS_REG_OFFSET, + SPFI_SDE_SHIFT); + if (ret) + return ret; + + /* + * Write to FIFO in blocks of 16 words (64 bytes) + * Do 32bit writes first. + */ + blocksize = SPIM_MAX_BLOCK_BYTES; + while ((size >= sizeof(u32)) && blocksize) { + memcpy(&write_data, buffer, sizeof(u32)); + write32(base + SPFI_SEND_LONG_REG_OFFSET, write_data); + buffer += sizeof(u32); + size -= sizeof(u32); + blocksize -= sizeof(u32); + } + while (size && blocksize) { + write32(base + SPFI_SEND_BYTE_REG_OFFSET, *buffer); + buffer++; + size--; + blocksize--; + } + } + return SPIM_OK; +} + +/* Receiver function */ +static int receivedata(struct spi_slave *slave, u8 *buffer, u32 size) +{ + u32 read_data, base; + int ret; + + base = container_of(slave, struct img_spi_slave, slave)->base; + /* + * Do 32bit reads first. Clear status GDEX32BIT here so that the first + * status reg. read gets the actual bit state + */ + write32(base + SPFI_INT_CLEAR_REG_OFFSET, SPFI_GDEX32BIT_MASK); + while (size >= sizeof(u32)) { + ret = wait_status(base + SPFI_INT_STATUS_REG_OFFSET, + SPFI_GDEX32BIT_SHIFT); + if (ret) + return ret; + read_data = read32(base + SPFI_GET_LONG_REG_OFFSET); + memcpy(buffer, &read_data, sizeof(u32)); + buffer += sizeof(u32); + size -= sizeof(u32); + /* Clear interrupt status on GDEX32BITL */ + write32(base + SPFI_INT_CLEAR_REG_OFFSET, SPFI_GDEX32BIT_MASK); + } + + /* + * Do the remaining 8bit reads. Clear status GDEX8BIT here so that + * the first status reg. read gets the actual bit state + */ + write32(base + SPFI_INT_CLEAR_REG_OFFSET, SPFI_GDEX8BIT_MASK); + while (size) { + ret = wait_status(base + SPFI_INT_STATUS_REG_OFFSET, + SPFI_GDEX8BIT_SHIFT); + if (ret) + return ret; + *buffer = read32(base + SPFI_GET_BYTE_REG_OFFSET); + buffer++; + size--; + /* Clear interrupt status on SPFI_GDEX8BIT */ + write32(base + SPFI_INT_CLEAR_REG_OFFSET, SPFI_GDEX8BIT_MASK); + } + return SPIM_OK; +} + +/* Sets port parameters in port state register. */ +static void setparams(struct spi_slave *slave, u32 port, + struct spim_device_parameters *params) +{ + u32 spim_parameters, port_state, base; + + spim_parameters = 0; + + base = container_of(slave, struct img_spi_slave, slave)->base; + port_state = read32(base + SPFI_PORT_STATE_REG_OFFSET); + port_state &= ~((SPIM_PORT0_MASK>>port)|SPFI_PORT_SELECT_MASK); + port_state |= params->cs_idle_level<<(SPIM_CS0_IDLE_SHIFT-port); + port_state |= + params->data_idle_level<<(SPIM_DATA0_IDLE_SHIFT-port); + + /* Clock idle level and phase */ + switch (params->spi_mode) { + case SPIM_MODE_0: + break; + case SPIM_MODE_1: + port_state |= (1 << (SPIM_CLOCK0_PHASE_SHIFT - port)); + break; + case SPIM_MODE_2: + port_state |= (1 << (SPIM_CLOCK0_IDLE_SHIFT - port)); + break; + case SPIM_MODE_3: + port_state |= (1 << (SPIM_CLOCK0_IDLE_SHIFT - port)) | + (1 << (SPIM_CLOCK0_PHASE_SHIFT - port)); + break; + } + /* Set port state register */ + write32(base + SPFI_PORT_STATE_REG_OFFSET, port_state); + + /* Set up values to be written to device parameter register */ + spim_parameters |= params->bitrate << SPIM_CLK_DIVIDE_SHIFT; + spim_parameters |= params->cs_setup << SPIM_CS_SETUP_SHIFT; + spim_parameters |= params->cs_hold << SPIM_CS_HOLD_SHIFT; + spim_parameters |= params->cs_delay << SPIM_CS_DELAY_SHIFT; + + write32(base + SPFI_PORT_0_PARAM_REG_OFFSET + 4 * port, + spim_parameters); +} + +/* Sets up transaction register */ +static u32 transaction_reg_setup(struct spim_buffer *first, + struct spim_buffer *second) +{ + u32 reg = 0; + + /* 2nd transfer exists? */ + if (second) { + /* + * If second transfer exists, it's a "command followed by data" + * type of transfer and first transfer is defined by + * CMD_LENGTH, ADDR_LENGTH, DUMMY_LENGTH... fields of + * transaction register + */ + reg = spi_write_reg_field(reg, SPFI_CMD_LENGTH, 1); + reg = spi_write_reg_field(reg, SPFI_ADDR_LENGTH, + first->size - 1); + reg = spi_write_reg_field(reg, SPFI_DUMMY_LENGTH, 0); + /* Set data size (size of the second transfer) */ + reg = spi_write_reg_field(reg, SPFI_TSIZE, second->size); + } else { + /* Set data size, in this case size of the 1st transfer */ + reg = spi_write_reg_field(reg, SPFI_TSIZE, first->size); + } + return reg; +} + +/* Sets up control register */ +static u32 control_reg_setup(struct spim_buffer *first, + struct spim_buffer *second) +{ + u32 reg; + + /* Enable SPFI */ + reg = SPFI_EN_MASK; + reg |= first->inter_byte_delay ? SPIM_BYTE_DELAY_MASK : 0; + /* Set up the transfer mode */ + reg = spi_write_reg_field(reg, SPFI_TRNSFR_MODE_DQ, SPIM_CMD_MODE_0); + reg = spi_write_reg_field(reg, SPFI_TRNSFR_MODE, SPIM_DMODE_SINGLE); + + if (second) { + /* Set TX bit if the 2nd transaction is 'send' */ + reg = spi_write_reg_field(reg, SPFI_TX_RX, + second->isread ? 0 : 1); + /* + * Set send/get DMA for both transactions + * (first is always 'send') + */ + reg = spi_write_reg_field(reg, SPIM_SEND_DMA, 1); + if (second->isread) + reg = spi_write_reg_field(reg, SPIM_GET_DMA, 1); + + } else { + /* Set TX bit if the 1st transaction is 'send' */ + reg |= first->isread ? 0 : SPFI_TX_RX_MASK; + /* Set send/get DMA */ + reg |= first->isread ? SPIM_GET_DMA_MASK : SPIM_SEND_DMA_MASK; + } + return reg; +} + +/* Checks the given buffer information */ +static int check_buffers(struct spi_slave *slave, struct spim_buffer *first, + struct spim_buffer *second){ + + if (!(container_of(slave, struct img_spi_slave, slave)->initialised)) + return -SPIM_API_NOT_INITIALISED; + /* + * First operation must always be defined + * It can be either a read or a write and its size cannot be bigge + * than SPIM_MAX_TANSFER_BYTES = 64KB - 1 (0xFFFF) + */ + if (!first) + return -SPIM_INVALID_READ_WRITE; + if (first->size > SPIM_MAX_TRANSFER_BYTES) + return -SPIM_INVALID_SIZE; + if (first->isread > 1) + return -SPIM_INVALID_READ_WRITE; + /* Check operation parameters for 'second' */ + if (second) { + /* + * If the second operation is defined it must be a read + * operation and its size must not be bigger than + * SPIM_MAX_TANSFER_BYTES = 64KB - 1 (0xFFFF) + */ + if (second->size > SPIM_MAX_TRANSFER_BYTES) + return -SPIM_INVALID_SIZE; + if (!second->isread) + return -SPIM_INVALID_READ_WRITE; + /* + * If the second operations is defined, the first operation + * must be a write and its size cannot be bigger than + * SPIM_MAX_FLASH_COMMAND_BYTES(8): command size (1) + + * address size (7). + */ + if (first->isread) + return -SPIM_INVALID_READ_WRITE; + if (first->size > SPIM_MAX_FLASH_COMMAND_BYTES) + return -SPIM_INVALID_SIZE; + + } + return SPIM_OK; +} + +/* Checks the set bitrate */ +static int check_bitrate(u32 rate) +{ + /* Bitrate must be 1, 2, 4, 8, 16, 32, 64, or 128 */ + switch (rate) { + case 1: + case 2: + case 4: + case 8: + case 16: + case 32: + case 64: + case 128: + return SPIM_OK; + default: + return -SPIM_INVALID_BIT_RATE; + } + return -SPIM_INVALID_BIT_RATE; +} + +/* Checks device parameters for errors */ +static int check_device_params(struct spim_device_parameters *pdev_param) +{ + if (pdev_param->spi_mode < SPIM_MODE_0 || + pdev_param->spi_mode > SPIM_MODE_3) + return -SPIM_INVALID_SPI_MODE; + if (check_bitrate(pdev_param->bitrate) != SPIM_OK) + return -SPIM_INVALID_BIT_RATE; + if (pdev_param->cs_idle_level > 1) + return -SPIM_INVALID_CS_IDLE_LEVEL; + if (pdev_param->data_idle_level > 1) + return -SPIM_INVALID_DATA_IDLE_LEVEL; + return SPIM_OK; +} + +/* Function that carries out read/write operations */ +static int spim_io(struct spi_slave *slave, struct spim_buffer *first, + struct spim_buffer *second) +{ + u32 reg, base; + int i, trans_count, ret; + struct spim_buffer *transaction[2]; + + base = container_of(slave, struct img_spi_slave, slave)->base; + + ret = check_buffers(slave, first, second); + if (ret) + return ret; + + /* + * Soft reset peripheral internals, this will terminate any + * pending transactions + */ + write32(base + SPFI_CONTROL_REG_OFFSET, SPIM_SOFT_RESET_MASK); + write32(base + SPFI_CONTROL_REG_OFFSET, 0); + /* Port state register */ + reg = read32(base + SPFI_PORT_STATE_REG_OFFSET); + reg = spi_write_reg_field(reg, SPFI_PORT_SELECT, slave->cs); + write32(base + SPFI_PORT_STATE_REG_OFFSET, reg); + /* Set transaction register */ + reg = transaction_reg_setup(first, second); + write32(base + SPFI_TRANSACTION_REG_OFFSET, reg); + /* Clear status */ + write32(base + SPFI_INT_CLEAR_REG_OFFSET, 0xffffffff); + /* Set control register */ + reg = control_reg_setup(first, second); + write32(base + SPFI_CONTROL_REG_OFFSET, reg); + /* First transaction always exists */ + transaction[0] = first; + trans_count = 1; + /* Is there a second transaction? */ + if (second) { + transaction[1] = second; + trans_count++; + } + /* Now write/read FIFO's */ + for (i = 0; i < trans_count; i++) + /* Which transaction to execute, "Send" or "Get"? */ + if (transaction[i]->isread) { + /* Get */ + ret = receivedata(slave, transaction[i]->buffer, + transaction[i]->size); + if (ret) { + printk(BIOS_ERR, + "%s: Error: receive data failed.\n", + __func__); + return ret; + } + } else { + /* Send */ + ret = transmitdata(slave, transaction[i]->buffer, + transaction[i]->size); + if (ret) { + printk(BIOS_ERR, + "%s: Error: transmit data failed.\n", + __func__); + return ret; + } + } + + /* Wait for end of the transaction */ + ret = wait_status(base + SPFI_INT_STATUS_REG_OFFSET, + SPFI_ALLDONE_SHIFT); + if (ret) + return ret; + /* + * Soft reset peripheral internals, this will terminate any + * pending transactions + */ + write32(base + SPFI_CONTROL_REG_OFFSET, SPIM_SOFT_RESET_MASK); + write32(base + SPFI_CONTROL_REG_OFFSET, 0); + + return SPIM_OK; +} + +/* Initialization, must be called once on start up */ +void spi_init(void) +{ + /* Clear everything just in case */ + memset(img_spi_slaves, 0, sizeof(img_spi_slaves)); +} + +/* Set up communications parameters for a SPI slave. */ struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs) { - return NULL; + + struct img_spi_slave *img_slave = NULL; + struct spi_slave *slave; + struct spim_device_parameters *device_parameters; + u32 base; + + switch (bus) { + case 0: + base = IMG_SPIM0_BASE_ADDRESS; + break; + case 1: + base = IMG_SPIM1_BASE_ADDRESS; + break; + default: + printk(BIOS_ERR, "%s: Error: unsupported bus.\n", + __func__); + return NULL; + } + if (cs > SPIM_DEVICE4) { + printk(BIOS_ERR, "%s: Error: unsupported chipselect.\n", + __func__); + return NULL; + } + + img_slave = img_spi_slaves + bus * SPIM_NUM_PORTS_PER_BLOCK + cs; + slave = &(img_slave->slave); + device_parameters = &(img_slave->device_parameters); + + img_slave->base = base; + slave->bus = bus; + slave->cs = cs; + slave->rw = SPI_READ_FLAG | SPI_WRITE_FLAG; + device_parameters->bitrate = 64; + device_parameters->cs_setup = 0; + device_parameters->cs_hold = 0; + device_parameters->cs_delay = 0; + device_parameters->spi_mode = SPIM_MODE_0; + device_parameters->cs_idle_level = 1; + device_parameters->data_idle_level = 0; + img_slave->initialised = IMG_FALSE; + + return slave; } +/* Claim the bus and prepare it for communication */ int spi_claim_bus(struct spi_slave *slave) { - return 0; + int ret; + struct img_spi_slave *img_slave; + + if (!slave) { + printk(BIOS_ERR, "%s: Error: slave was not set up.\n", + __func__); + return -SPIM_API_NOT_INITIALISED; + } + img_slave = container_of(slave, struct img_spi_slave, slave); + if (img_slave->initialised) + return SPIM_OK; + /* Check device parameters */ + ret = check_device_params(&(img_slave->device_parameters)); + if (ret) { + printk(BIOS_ERR, "%s: Error: incorrect device parameters.\n", + __func__); + return ret; + } + /* Set device parameters */ + setparams(slave, slave->cs, &(img_slave->device_parameters)); + /* Soft reset peripheral internals */ + write32(img_slave->base + SPFI_CONTROL_REG_OFFSET, + SPIM_SOFT_RESET_MASK); + write32(img_slave->base + SPFI_CONTROL_REG_OFFSET, 0); + img_slave->initialised = IMG_TRUE; + return SPIM_OK; } +/* Release the SPI bus */ void spi_release_bus(struct spi_slave *slave) { + struct img_spi_slave *img_slave; + + if (!slave) { + printk(BIOS_ERR, "%s: Error: slave was not set up.\n", + __func__); + return; + } + img_slave = container_of(slave, struct img_spi_slave, slave); + img_slave->initialised = IMG_FALSE; + /* Soft reset peripheral internals */ + write32(img_slave->base + SPFI_CONTROL_REG_OFFSET, + SPIM_SOFT_RESET_MASK); + write32(img_slave->base + SPFI_CONTROL_REG_OFFSET, 0); } -int spi_xfer(struct spi_slave *slave, const void *dout, - unsigned out_bytes, void *din, unsigned in_bytes) +/* SPI transfer */ +int spi_xfer(struct spi_slave *slave, const void *dout, unsigned int bytesout, + void *din, unsigned int bytesin) { - return 0; + struct spim_buffer buff_0; + struct spim_buffer buff_1; + + if (!slave) { + printk(BIOS_ERR, "%s: Error: slave was not set up.\n", + __func__); + return -SPIM_API_NOT_INITIALISED; + } + if (!dout && !din) { + printk(BIOS_ERR, "%s: Error: both buffers are NULL.\n", + __func__); + return -SPIM_INVALID_TRANSFER_DESC; + } + /* If we only have a read or a write operation + * the parameters for it will be put in the first buffer + */ + buff_0.buffer = (dout) ? (void *)dout : (void *)din; + buff_0.size = (dout) ? bytesout : bytesin; + buff_0.isread = (dout) ? IMG_FALSE : IMG_TRUE; + buff_0.inter_byte_delay = 0; + + if (dout && din) { + /* Set up the read buffer to receive our data */ + buff_1.buffer = din; + buff_1.size = bytesin; + buff_1.isread = IMG_TRUE; + buff_1.inter_byte_delay = 0; + } + return spim_io(slave, &buff_0, (dout && din) ? &buff_1 : NULL); } diff --git a/src/soc/imgtec/pistachio/spi.h b/src/soc/imgtec/pistachio/spi.h new file mode 100644 index 0000000000..91d6d558e5 --- /dev/null +++ b/src/soc/imgtec/pistachio/spi.h @@ -0,0 +1,358 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2014 Imagination Technologies + * + * 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. + */ + + +#ifndef __SOC_IMGTEC_DANUBE_SPI_H__ +#define __SOC_IMGTEC_DANUBE_SPI_H__ + +#include +#include + +#define spi_read_reg_field(regval, field) \ +( \ + ((field##_MASK) == 0xFFFFFFFF) ? \ + (regval) : \ + (((regval) & (field##_MASK)) >> (field##_SHIFT))\ +) + +#define spi_write_reg_field(regval, field, val) \ +( \ + ((field##_MASK) == 0xFFFFFFFF) ? \ + (val) : \ + (((regval) & ~(field##_MASK)) | \ + (((val) << (field##_SHIFT)) & (field##_MASK))) \ +) + +/* + * Parameter register + * Each of these corresponds to a single port (ie CS line) in the interface + * Fields Name Description + * ====== ==== =========== + * b31:24 CLK_RATE Bit Clock rate = (24.576 * value / 512) MHz + * b23:16 CS_SETUP Chip Select setup = (40 * value) ns + * b15:8 CS_HOLD Chip Select hold = (40 * value) ns + * b7:0 CS_DELAY Chip Select delay = (40 * value) ns + */ + +#define SPIM_CLK_DIVIDE_MASK (0xFF000000) +#define SPIM_CS_SETUP_MASK (0x00FF0000) +#define SPIM_CS_HOLD_MASK (0x0000FF00) +#define SPIM_CS_DELAY_MASK (0x000000FF) +#define SPIM_CS_PARAM_MASK (SPIM_CS_SETUP_MASK \ + | SPIM_CS_HOLD_MASK \ + | SPIM_CS_DELAY_MASK) + +#define SPIM_CLK_DIVIDE_SHIFT (24) +#define SPIM_CS_SETUP_SHIFT (16) +#define SPIM_CS_HOLD_SHIFT (8) +#define SPIM_CS_DELAY_SHIFT (0) +#define SPIM_CS_PARAM_SHIFT (0) + +/* Control register */ + +#define SPFI_DRIBBLE_COUNT_MASK (0x000e0000) +#define SPFI_MEMORY_IF_MASK (0x00008000) +#define SPIM_BYTE_DELAY_MASK (0x00004000) +#define SPIM_CS_DEASSERT_MASK (0x00002000) +#define SPIM_CONTINUE_MASK (0x00001000) +#define SPIM_SOFT_RESET_MASK (0x00000800) +#define SPIM_SEND_DMA_MASK (0x00000400) +#define SPIM_GET_DMA_MASK (0x00000200) +#define SPIM_EDGE_TX_RX_MASK (0x00000100) +#define SPFI_TRNSFR_MODE_MASK (0x000000e0) +#define SPFI_TRNSFR_MODE_DQ_MASK (0x0000001c) +#define SPFI_TX_RX_MASK (0x00000002) +#define SPFI_EN_MASK (0x00000001) + +#define SPFI_DRIBBLE_COUNT_SHIFT (17) +#define SPFI_MEMORY_IF_SHIFT (15) +#define SPIM_BYTE_DELAY_SHIFT (14) +#define SPIM_CS_DEASSERT_SHIFT (13) +#define SPIM_CONTINUE_SHIFT (12) +#define SPIM_SOFT_RESET_SHIFT (11) +#define SPIM_SEND_DMA_SHIFT (10) +#define SPIM_GET_DMA_SHIFT (9) +#define SPIM_EDGE_TX_RX_SHIFT (8) +#define SPFI_TRNSFR_MODE_SHIFT (5) +#define SPFI_TRNSFR_MODE_DQ_SHIFT (2) +#define SPFI_TX_RX_SHIFT (1) +#define SPFI_EN_SHIFT (0) + +/* Transaction register */ + +#define SPFI_TSIZE_MASK (0xffff0000) +#define SPFI_CMD_LENGTH_MASK (0x0000e000) +#define SPFI_ADDR_LENGTH_MASK (0x00001c00) +#define SPFI_DUMMY_LENGTH_MASK (0x000003e0) +#define SPFI_PI_LENGTH_MASK (0x0000001c) + +#define SPFI_TSIZE_SHIFT (16) +#define SPFI_CMD_LENGTH_SHIFT (13) +#define SPFI_ADDR_LENGTH_SHIFT (10) +#define SPFI_DUMMY_LENGTH_SHIFT (5) +#define SPFI_PI_LENGTH_SHIFT (2) + +/* Port state register */ + +#define SPFI_PORT_SELECT_MASK (0x00700000) +/* WARNING the following bits are reversed */ +#define SPFI_CLOCK0_IDLE_MASK (0x000f8000) +#define SPFI_CLOCK0_PHASE_MASK (0x00007c00) +#define SPFI_CS0_IDLE_MASK (0x000003e0) +#define SPFI_DATA0_IDLE_MASK (0x0000001f) + +#define SPIM_CLOCK0_IDLE_MASK (0x000f8000) +#define SPIM_CLOCK0_PHASE_MASK (0x00007c00) +#define SPIM_CS0_IDLE_MASK (0x000003e0) +#define SPIM_DATA0_IDLE_MASK (0x0000001f) + +#define SPIM_PORT0_MASK (0x00084210) + +#define SPFI_PORT_SELECT_SHIFT (20) +/* WARNING the following bits are reversed, bit 0 is highest */ +#define SPFI_CLOCK0_IDLE_SHIFT (19) +#define SPFI_CLOCK0_PHASE_SHIFT (14) +#define SPFI_CS0_IDLE_SHIFT (9) +#define SPFI_DATA0_IDLE_SHIFT (4) + +#define SPIM_CLOCK0_IDLE_SHIFT (19) +#define SPIM_CLOCK0_PHASE_SHIFT (14) +#define SPIM_CS0_IDLE_SHIFT (9) +#define SPIM_DATA0_IDLE_SHIFT (4) + + +/* + * Interrupt registers + * SPFI_GDOF_MASK means Rx buffer full, not an overflow, because clock stalls + * SPFI_SDUF_MASK means Tx buffer empty, not an underflow, because clock stalls + */ +#define SPFI_IACCESS_MASK (0x00001000) +#define SPFI_GDEX8BIT_MASK (0x00000800) +#define SPFI_ALLDONE_MASK (0x00000200) +#define SPFI_GDFUL_MASK (0x00000100) +#define SPFI_GDHF_MASK (0x00000080) +#define SPFI_GDEX32BIT_MASK (0x00000040) +#define SPFI_GDTRIG_MASK (0x00000020) +#define SPFI_SDFUL_MASK (0x00000008) +#define SPFI_SDHF_MASK (0x00000004) +#define SPFI_SDE_MASK (0x00000002) +#define SPFI_SDTRIG_MASK (0x00000001) + +#define SPFI_IACCESS_SHIFT (12) +#define SPFI_GDEX8BIT_SHIFT (11) +#define SPFI_ALLDONE_SHIFT (9) +#define SPFI_GDFUL_SHIFT (8) +#define SPFI_GDHF_SHIFT (7) +#define SPFI_GDEX32BIT_SHIFT (6) +#define SPFI_GDTRIG_SHIFT (5) +#define SPFI_SDFUL_SHIFT (3) +#define SPFI_SDHF_SHIFT (2) +#define SPFI_SDE_SHIFT (1) +#define SPFI_SDTRIG_SHIFT (0) + + +/* SPFI register block */ + +#define SPFI_PORT_0_PARAM_REG_OFFSET (0x00) +#define SPFI_PORT_1_PARAM_REG_OFFSET (0x04) +#define SPFI_PORT_2_PARAM_REG_OFFSET (0x08) +#define SPFI_PORT_3_PARAM_REG_OFFSET (0x0C) +#define SPFI_PORT_4_PARAM_REG_OFFSET (0x10) +#define SPFI_CONTROL_REG_OFFSET (0x14) +#define SPFI_TRANSACTION_REG_OFFSET (0x18) +#define SPFI_PORT_STATE_REG_OFFSET (0x1C) + +#define SPFI_SEND_LONG_REG_OFFSET (0x20) +#define SPFI_SEND_BYTE_REG_OFFSET (0x24) +#define SPFI_GET_LONG_REG_OFFSET (0x28) +#define SPFI_GET_BYTE_REG_OFFSET (0x2C) + +#define SPFI_INT_STATUS_REG_OFFSET (0x30) +#define SPFI_INT_ENABLE_REG_OFFSET (0x34) +#define SPFI_INT_CLEAR_REG_OFFSET (0x38) + +#define SPFI_IMMEDIATE_STATUS_REG_OFFSET (0x3c) + +#define SPFI_FLASH_BASE_ADDRESS_REG_OFFSET (0x48) +#define SPFI_FLASH_STATUS_REG_OFFSET (0x4C) + +#define IMG_FALSE 0 +#define IMG_TRUE 1 + +/* Number of SPIM interfaces*/ +#define SPIM_NUM_BLOCKS 2 +/* Number of chip select lines supported by the SPI master port. */ +#define SPIM_NUM_PORTS_PER_BLOCK (SPIM_DUMMY_CS) +/* Maximum transfer size (in bytes) for the SPI master port. */ +#define SPIM_MAX_TRANSFER_BYTES (0xFFFF) +/* Maximum size of a flash command: command bytes+address_bytes. */ +#define SPIM_MAX_FLASH_COMMAND_BYTES (0x8) +/* Write operation to fifo done in blocks of 16 words (64 bytes) */ +#define SPIM_MAX_BLOCK_BYTES (0x40) +/* Number of tries until timeout error is returned*/ +#define SPI_TIMEOUT_VALUE_US 500000 + +/* SPIM initialisation function return value.*/ +enum spim_return { + /* Initialisation parameters are valid. */ + SPIM_OK = 0, + /* Mode parameter is invalid. */ + SPIM_INVALID_SPI_MODE, + /* Chip select idle level is invalid. */ + SPIM_INVALID_CS_IDLE_LEVEL, + /* Data idle level is invalid. */ + SPIM_INVALID_DATA_IDLE_LEVEL, + /* Chip select line parameter is invalid. */ + SPIM_INVALID_CS_LINE, + /* Transfer size parameter is invalid. */ + SPIM_INVALID_SIZE, + /* Read/write parameter is invalid. */ + SPIM_INVALID_READ_WRITE, + /* Continue parameter is invalid. */ + SPIM_INVALID_CONTINUE, + /* Invalid block index */ + SPIM_INVALID_BLOCK_INDEX, + /* Extended error values */ + /* Invalid bit rate */ + SPIM_INVALID_BIT_RATE, + /* Invalid CS hold value */ + SPIM_INVALID_CS_HOLD_VALUE, + /* API function called before API is initialised */ + SPIM_API_NOT_INITIALISED, + /* SPI driver initialisation failed */ + SPIM_DRIVER_INIT_ERROR, + /* Invalid transfer description */ + SPIM_INVALID_TRANSFER_DESC, + /* Timeout */ + SPIM_TIMEOUT + +}; + +/* This type defines the SPI Mode.*/ +enum spim_mode { + /* Mode 0 (clock idle low, data valid on first clock transition). */ + SPIM_MODE_0 = 0, + /* Mode 1 (clock idle low, data valid on second clock transition). */ + SPIM_MODE_1, + /* Mode 2 (clock idle high, data valid on first clock transition). */ + SPIM_MODE_2, + /* Mode 3 (clock idle high, data valid on second clock transition). */ + SPIM_MODE_3 + +}; + +/* This type defines the SPIM device numbers (chip select lines). */ +enum spim_device { + /* Device 0 (CS0). */ + SPIM_DEVICE0 = 0, + /* Device 1 (CS1). */ + SPIM_DEVICE1, + /* Device 2 (CS2). */ + SPIM_DEVICE2, + /* Device 3 (CS3). */ + SPIM_DEVICE3, + /* Device 4 (CS4). */ + SPIM_DEVICE4, + /* Dummy chip select. */ + SPIM_DUMMY_CS + +}; + +/* This structure defines communication parameters for a slave device */ +struct spim_device_parameters { + /* Bit rate value.*/ + unsigned char bitrate; + /* + * Chip select set up time. + * Time taken between chip select going active and activity occurring + * on the clock, calculated by dividing the desired set up time in ns + * by the Input clock period. (setup time / Input clock freq) + */ + unsigned char cs_setup; + /* + * Chip select hold time. + * Time after the last clock pulse before chip select goes inactive, + * calculated by dividing the desired hold time in ns by the + * Input clock period (hold time / Input clock freq). + */ + unsigned char cs_hold; + /* + * Chip select delay time (CS minimum inactive time). + * Minimum time after chip select goes inactive before chip select + * can go active again, calculated by dividing the desired delay time + * in ns by the Input clock period (delay time / Input clock freq). + */ + unsigned char cs_delay; + /* SPI Mode. */ + enum spim_mode spi_mode; + /* Chip select idle level (0=low, 1=high, Others=invalid). */ + unsigned int cs_idle_level; + /* Data idle level (0=low, 1=high, Others=invalid). */ + unsigned int data_idle_level; + +}; + +/* Command transfer mode */ +enum command_mode { + /* Command, address, dummy and PI cycles are transferred on sio0 */ + SPIM_CMD_MODE_0 = 0, + /* + * Command and Address are transferred on sio0 port only but dummy + * cycles and PI is transferred on all the interface ports. + */ + SPIM_CMD_MODE_1, + /* + * Command is transferred on sio0 port only but address, dummy + * and PI is transferred on all the interface portS + */ + SPIM_CMD_MODE_2, + /* + * Command, address, dummy and PI bytes are transferred on all + * the interfaces + */ + SPIM_CMD_MODE_3 +}; + +/* Data transfer mode */ +enum transfer_mode { + /* Transfer data in single mode */ + SPIM_DMODE_SINGLE = 0, + /* Transfer data in dual mode */ + SPIM_DMODE_DUAL, + /* Transfer data in quad mode */ + SPIM_DMODE_QUAD +}; + +/* This structure contains parameters that describe an SPIM operation. */ +struct spim_buffer { + /* The buffer to read from or write to. */ + unsigned char *buffer; + + /* Number of bytes to read/write. Valid range is 0 to 65536 bytes. */ + unsigned int size; + + /* Read/write select. TRUE for read, FALSE for write, Others-invalid.*/ + int isread; + + /* + * ByteDelay select. + * Selects whether or not a delay is inserted between bytes. + * 0 - Minimum inter-byte delay + * 1 - Inter-byte delay of (cs_hold/master_clk half period)*master_clk. + */ + int inter_byte_delay; +}; + +#endif /* __SOC_IMGTEC_DANUBE_SPI_H__ */ -- cgit v1.2.3