blob: 30dc1c3387cdec3c35e69e844910d95ce2e8ffb9 [file] [log] [blame]
/*
* This file is part of the coreboot project.
*
* Copyright (C) 2019, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* 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 <spi-generic.h>
#include <spi_flash.h>
#include <arch/cache.h>
#include <device/mmio.h>
#include <soc/addressmap.h>
#include <soc/qspi.h>
#include <soc/gpio.h>
#include <soc/clock.h>
#include <symbols.h>
#include <assert.h>
#include <gpio.h>
#include <string.h>
#define CACHE_LINE_SIZE 64
static int curr_desc_idx = -1;
struct cmd_desc {
uint32_t data_address;
uint32_t next_descriptor;
uint32_t direction:1;
uint32_t multi_io_mode:3;
uint32_t reserved1:4;
uint32_t fragment:1;
uint32_t reserved2:7;
uint32_t length:16;
//------------------------//
uint32_t bounce_src;
uint32_t bounce_dst;
uint32_t bounce_length;
uint64_t padding[5];
};
enum qspi_mode {
SDR_1BIT = 1,
SDR_2BIT = 2,
SDR_4BIT = 3,
DDR_1BIT = 5,
DDR_2BIT = 6,
DDR_4BIT = 7,
};
enum cs_state {
CS_DEASSERT,
CS_ASSERT
};
struct xfer_cfg {
enum qspi_mode mode;
};
enum bus_xfer_direction {
MASTER_READ = 0,
MASTER_WRITE = 1,
};
struct {
struct cmd_desc descriptors[3];
uint8_t buffers[3][CACHE_LINE_SIZE];
} *dma = (void *)_dma_coherent;
static void dma_transfer_chain(struct cmd_desc *chain)
{
uint32_t mstr_int_status;
write32(&sc7180_qspi->mstr_int_sts, 0xFFFFFFFF);
write32(&sc7180_qspi->next_dma_desc_addr, (uint32_t)(uintptr_t) chain);
while (1) {
mstr_int_status = read32(&sc7180_qspi->mstr_int_sts);
if (mstr_int_status & DMA_CHAIN_DONE)
break;
}
}
static void flush_chain(void)
{
struct cmd_desc *desc = &dma->descriptors[0];
uint8_t *src;
uint8_t *dst;
dma_transfer_chain(desc);
while (desc) {
if (desc->direction == MASTER_READ) {
if (desc->bounce_length == 0)
dcache_invalidate_by_mva(
(void *)(uintptr_t) desc->data_address,
desc->length);
else {
src = (void *)(uintptr_t) desc->bounce_src;
dst = (void *)(uintptr_t) desc->bounce_dst;
memcpy(dst, src, desc->bounce_length);
}
}
desc = (void *)(uintptr_t) desc->next_descriptor;
}
curr_desc_idx = -1;
}
static struct cmd_desc *allocate_descriptor(void)
{
struct cmd_desc *current;
struct cmd_desc *next;
uint8_t index;
current = (curr_desc_idx == -1) ?
NULL : &dma->descriptors[curr_desc_idx];
index = ++curr_desc_idx;
next = &dma->descriptors[index];
next->data_address = (uint32_t) (uintptr_t) dma->buffers[index];
next->next_descriptor = 0;
next->direction = MASTER_READ;
next->multi_io_mode = 0;
next->reserved1 = 0;
next->fragment = 0;
next->reserved2 = 0;
next->length = 0;
next->bounce_src = 0;
next->bounce_dst = 0;
next->bounce_length = 0;
if (current) {
current->next_descriptor = (uint32_t)(uintptr_t) next;
current->fragment = 1;
}
return next;
}
static void cs_change(enum cs_state state)
{
gpio_set(GPIO(68), state == CS_DEASSERT);
}
static void configure_gpios(void)
{
gpio_output(GPIO(68), 1);
gpio_configure(GPIO(64), GPIO64_FUNC_QSPI_DATA_0,
GPIO_NO_PULL, GPIO_2MA, GPIO_OUTPUT_ENABLE);
gpio_configure(GPIO(65), GPIO65_FUNC_QSPI_DATA_1,
GPIO_NO_PULL, GPIO_2MA, GPIO_OUTPUT_ENABLE);
gpio_configure(GPIO(63), GPIO63_FUNC_QSPI_CLK,
GPIO_NO_PULL, GPIO_2MA, GPIO_OUTPUT_ENABLE);
}
static void queue_bounce_data(uint8_t *data, uint32_t data_bytes,
enum qspi_mode data_mode, bool write)
{
struct cmd_desc *desc;
uint8_t *ptr;
desc = allocate_descriptor();
desc->direction = write;
desc->multi_io_mode = data_mode;
ptr = (void *)(uintptr_t) desc->data_address;
if (write) {
memcpy(ptr, data, data_bytes);
} else {
desc->bounce_src = (uint32_t)(uintptr_t) ptr;
desc->bounce_dst = (uint32_t)(uintptr_t) data;
desc->bounce_length = data_bytes;
}
desc->length = data_bytes;
}
static void queue_direct_data(uint8_t *data, uint32_t data_bytes,
enum qspi_mode data_mode, bool write)
{
struct cmd_desc *desc;
desc = allocate_descriptor();
desc->direction = write;
desc->multi_io_mode = data_mode;
desc->data_address = (uint32_t)(uintptr_t) data;
desc->length = data_bytes;
if (write)
dcache_clean_by_mva(data, data_bytes);
else
dcache_invalidate_by_mva(data, data_bytes);
}
static void queue_data(uint8_t *data, uint32_t data_bytes,
enum qspi_mode data_mode, bool write)
{
uint8_t *aligned_ptr;
uint8_t *epilog_ptr;
uint32_t prolog_bytes, aligned_bytes, epilog_bytes;
if (data_bytes == 0)
return;
aligned_ptr =
(uint8_t *)ALIGN_UP((uintptr_t)data, CACHE_LINE_SIZE);
prolog_bytes = MIN(data_bytes, aligned_ptr - data);
aligned_bytes = ALIGN_DOWN(data_bytes - prolog_bytes, CACHE_LINE_SIZE);
epilog_bytes = data_bytes - prolog_bytes - aligned_bytes;
epilog_ptr = data + prolog_bytes + aligned_bytes;
if (prolog_bytes)
queue_bounce_data(data, prolog_bytes, data_mode, write);
if (aligned_bytes)
queue_direct_data(aligned_ptr, aligned_bytes, data_mode, write);
if (epilog_bytes)
queue_bounce_data(epilog_ptr, epilog_bytes, data_mode, write);
}
static void reg_init(void)
{
uint32_t spi_mode;
uint32_t tx_data_oe_delay, tx_data_delay;
uint32_t mstr_config;
spi_mode = 0;
tx_data_oe_delay = 0;
tx_data_delay = 0;
mstr_config = (tx_data_oe_delay << TX_DATA_OE_DELAY_SHIFT) |
(tx_data_delay << TX_DATA_DELAY_SHIFT) | (SBL_EN) |
(spi_mode << SPI_MODE_SHIFT) |
(PIN_HOLDN) |
(FB_CLK_EN) |
(DMA_ENABLE) |
(FULL_CYCLE_MODE);
write32(&sc7180_qspi->mstr_cfg, mstr_config);
write32(&sc7180_qspi->ahb_mstr_cfg, 0xA42);
write32(&sc7180_qspi->mstr_int_en, 0x0);
write32(&sc7180_qspi->mstr_int_sts, 0xFFFFFFFF);
write32(&sc7180_qspi->rd_fifo_cfg, 0x0);
write32(&sc7180_qspi->rd_fifo_rst, RESET_FIFO);
}
void quadspi_init(uint32_t hz)
{
assert(dcache_line_bytes() == CACHE_LINE_SIZE);
clock_configure_qspi(hz * 4);
configure_gpios();
reg_init();
}
int sc7180_claim_bus(const struct spi_slave *slave)
{
cs_change(CS_ASSERT);
return 0;
}
void sc7180_release_bus(const struct spi_slave *slave)
{
cs_change(CS_DEASSERT);
}
static int xfer(enum qspi_mode mode, const void *dout, size_t out_bytes,
void *din, size_t in_bytes)
{
if ((out_bytes && !dout) || (in_bytes && !din) ||
(in_bytes && out_bytes)) {
return -1;
}
queue_data((uint8_t *) (out_bytes ? dout : din),
in_bytes | out_bytes, mode, !!out_bytes);
flush_chain();
return 0;
}
int sc7180_xfer(const struct spi_slave *slave, const void *dout,
size_t out_bytes, void *din, size_t in_bytes)
{
return xfer(SDR_1BIT, dout, out_bytes, din, in_bytes);
}
int sc7180_xfer_dual(const struct spi_slave *slave, const void *dout,
size_t out_bytes, void *din, size_t in_bytes)
{
return xfer(SDR_2BIT, dout, out_bytes, din, in_bytes);
}