aboutsummaryrefslogtreecommitdiff
path: root/src/soc/imgtec/pistachio/spi.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/soc/imgtec/pistachio/spi.c')
-rw-r--r--src/soc/imgtec/pistachio/spi.c523
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);
}