/* * 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. */ #include #include #include #include #include #include #include #if !CONFIG_SPI_ATOMIC_SEQUENCING #error "Unsupported SPI driver API" #endif /* Imgtec controller uses 16 bit packet length. */ #define IMGTEC_SPI_MAX_TRANSFER_SIZE ((1 << 16) - 1) 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) { 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; slave->max_transfer_size = IMGTEC_SPI_MAX_TRANSFER_SIZE; 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) { 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); } /* SPI transfer */ int spi_xfer(struct spi_slave *slave, const void *dout, unsigned int bytesout, void *din, unsigned int bytesin) { 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); }