diff options
author | Ionela Voinescu <ionela.voinescu@imgtec.com> | 2014-09-09 20:18:55 +0100 |
---|---|---|
committer | Patrick Georgi <pgeorgi@google.com> | 2015-03-27 08:06:30 +0100 |
commit | 49aad6b387b3885f43d75aa50eceab50c6ac4aa9 (patch) | |
tree | c29f8507eb1d7be3d47bf66a1d135b766f93b5c0 /src/soc/imgtec/pistachio/spi.c | |
parent | 2d510d01d1eb45af0f8ba2b060748c465243c099 (diff) |
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 <pgeorgi@chromium.org>
Original-Commit-Id: 621849942e27f7d6cf2c8ade7f2c4d18d2318b91
Original-Change-Id: Ib257eb6236bd2895281175871b4ab979660f1239
Original-Signed-off-by: Ionela Voinescu <ionela.voinescu@imgtec.com>
Original-Reviewed-on: https://chromium-review.googlesource.com/217320
Original-Reviewed-by: Vadim Bendebury <vbendeb@chromium.org>
Reviewed-on: http://review.coreboot.org/9049
Tested-by: build bot (Jenkins)
Reviewed-by: Stefan Reinauer <stefan.reinauer@coreboot.org>
Diffstat (limited to 'src/soc/imgtec/pistachio/spi.c')
-rw-r--r-- | src/soc/imgtec/pistachio/spi.c | 523 |
1 files changed, 513 insertions, 10 deletions
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 <stddef.h> +#include <string.h> +#include <stdlib.h> +#include <timer.h> #include <spi-generic.h> +#include <spi_flash.h> +#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); } |