2015-07-31 11:10:46 +02:00
|
|
|
/*
|
|
|
|
* This file is part of the coreboot project.
|
|
|
|
*
|
|
|
|
* Copyright 2015 MediaTek Inc.
|
|
|
|
*
|
|
|
|
* 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 <arch/io.h>
|
|
|
|
#include <assert.h>
|
|
|
|
#include <console/console.h>
|
|
|
|
#include <delay.h>
|
|
|
|
#include <endian.h>
|
|
|
|
#include <spi_flash.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <timer.h>
|
|
|
|
#include <soc/addressmap.h>
|
2015-10-16 07:42:49 +02:00
|
|
|
#include <soc/flash_controller.h>
|
2015-07-31 11:10:46 +02:00
|
|
|
#include <soc/gpio.h>
|
|
|
|
#include <soc/pinmux.h>
|
|
|
|
#include <soc/pll.h>
|
|
|
|
#include <soc/spi.h>
|
|
|
|
|
|
|
|
enum {
|
|
|
|
MTK_FIFO_DEPTH = 32,
|
|
|
|
MTK_TXRX_TIMEOUT_US = 1000 * 1000,
|
|
|
|
MTK_ARBITRARY_VALUE = 0xdeaddead
|
|
|
|
};
|
|
|
|
|
|
|
|
enum {
|
|
|
|
MTK_SPI_IDLE = 0,
|
|
|
|
MTK_SPI_PAUSE_IDLE = 1
|
|
|
|
};
|
|
|
|
|
|
|
|
enum {
|
|
|
|
MTK_SPI_BUSY_STATUS = 1,
|
|
|
|
MTK_SPI_PAUSE_FINISH_INT_STATUS = 3
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct mtk_spi_bus spi_bus[1] = {
|
|
|
|
{
|
|
|
|
.slave = {
|
|
|
|
.bus = 0,
|
|
|
|
},
|
|
|
|
.regs = (void *)SPI_BASE,
|
|
|
|
.state = MTK_SPI_IDLE,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
static inline struct mtk_spi_bus *to_mtk_spi(struct spi_slave *slave)
|
|
|
|
{
|
|
|
|
return container_of(slave, struct mtk_spi_bus, slave);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void spi_sw_reset(struct mtk_spi_regs *regs)
|
|
|
|
{
|
|
|
|
setbits_le32(®s->spi_cmd_reg, SPI_CMD_RST_EN);
|
|
|
|
clrbits_le32(®s->spi_cmd_reg, SPI_CMD_RST_EN);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void mtk_spi_set_gpio_pinmux(enum spi_pad_mask pad_select)
|
|
|
|
{
|
|
|
|
/* TODO: implement support for other pads when needed */
|
|
|
|
assert(pad_select == SPI_PAD1_MASK);
|
|
|
|
gpio_set_mode(PAD_MSDC2_DAT2, PAD_MSDC2_DAT2_FUNC_SPI_CK_1);
|
|
|
|
gpio_set_mode(PAD_MSDC2_DAT3, PAD_MSDC2_DAT3_FUNC_SPI_MI_1);
|
|
|
|
gpio_set_mode(PAD_MSDC2_CLK, PAD_MSDC2_CLK_FUNC_SPI_MO_1);
|
|
|
|
gpio_set_mode(PAD_MSDC2_CMD, PAD_MSDC2_CMD_FUNC_SPI_CS_1);
|
|
|
|
}
|
|
|
|
|
|
|
|
void mtk_spi_init(unsigned int bus, unsigned int pad_select,
|
|
|
|
unsigned int speed_hz)
|
|
|
|
{
|
|
|
|
u32 div, sck_ticks, cs_ticks, reg_val;
|
2016-11-18 17:54:23 +01:00
|
|
|
|
|
|
|
/* mtk spi HW just supports bus 0 */
|
|
|
|
if (bus != 0)
|
|
|
|
die("Error: Only SPI bus 0 is supported.\n");
|
|
|
|
|
2015-07-31 11:10:46 +02:00
|
|
|
struct mtk_spi_bus *slave = &spi_bus[bus];
|
|
|
|
struct mtk_spi_regs *regs = slave->regs;
|
|
|
|
|
|
|
|
if (speed_hz < SPI_HZ / 2)
|
|
|
|
div = div_round_up(SPI_HZ, speed_hz);
|
|
|
|
else
|
|
|
|
div = 1;
|
|
|
|
|
|
|
|
sck_ticks = div_round_up(div, 2);
|
|
|
|
cs_ticks = sck_ticks * 2;
|
|
|
|
|
|
|
|
printk(BIOS_DEBUG, "SPI%u initialized at %u Hz",
|
|
|
|
pad_select, SPI_HZ / (sck_ticks * 2));
|
|
|
|
|
|
|
|
/* set the timing */
|
|
|
|
write32(®s->spi_cfg0_reg,
|
|
|
|
((sck_ticks - 1) << SPI_CFG0_SCK_HIGH_SHIFT) |
|
|
|
|
((sck_ticks - 1) << SPI_CFG0_SCK_LOW_SHIFT) |
|
|
|
|
((cs_ticks - 1) << SPI_CFG0_CS_HOLD_SHIFT) |
|
|
|
|
((cs_ticks - 1) << SPI_CFG0_CS_SETUP_SHIFT));
|
|
|
|
clrsetbits_le32(®s->spi_cfg1_reg, SPI_CFG1_CS_IDLE_MASK,
|
|
|
|
((cs_ticks - 1) << SPI_CFG1_CS_IDLE_SHIFT));
|
|
|
|
|
|
|
|
reg_val = read32(®s->spi_cmd_reg);
|
|
|
|
|
|
|
|
reg_val &= ~SPI_CMD_CPHA_EN;
|
|
|
|
reg_val &= ~SPI_CMD_CPOL_EN;
|
|
|
|
|
|
|
|
/* set the mlsbx and mlsbtx */
|
|
|
|
reg_val |= SPI_CMD_TXMSBF_EN;
|
|
|
|
reg_val |= SPI_CMD_RXMSBF_EN;
|
|
|
|
|
|
|
|
/* set the tx/rx endian */
|
|
|
|
#ifdef __LITTLE_ENDIAN
|
|
|
|
reg_val &= ~SPI_CMD_TX_ENDIAN_EN;
|
|
|
|
reg_val &= ~SPI_CMD_RX_ENDIAN_EN;
|
|
|
|
#else
|
|
|
|
reg_val |= SPI_CMD_TX_ENDIAN_EN;
|
|
|
|
reg_val |= SPI_CMD_RX_ENDIAN_EN;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* clear pause mode */
|
|
|
|
reg_val &= ~SPI_CMD_PAUSE_EN;
|
|
|
|
|
|
|
|
/* set finish interrupt always enable */
|
|
|
|
reg_val |= SPI_CMD_FINISH_IE_EN;
|
|
|
|
|
|
|
|
/* set pause interrupt always enable */
|
|
|
|
reg_val |= SPI_CMD_PAUSE_IE_EN;
|
|
|
|
|
|
|
|
/* disable dma mode */
|
|
|
|
reg_val &= ~(SPI_CMD_TX_DMA_EN | SPI_CMD_RX_DMA_EN);
|
|
|
|
|
|
|
|
/* set deassert mode */
|
|
|
|
reg_val &= ~SPI_CMD_DEASSERT_EN;
|
|
|
|
|
|
|
|
write32(®s->spi_cmd_reg, reg_val);
|
|
|
|
|
|
|
|
mtk_spi_set_gpio_pinmux(pad_select);
|
|
|
|
/* pad select */
|
|
|
|
clrsetbits_le32(®s->spi_pad_macro_sel_reg, SPI_PAD_SEL_MASK,
|
|
|
|
pad_select);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void mtk_spi_dump_data(const char *name, const uint8_t *data,
|
|
|
|
int size)
|
|
|
|
{
|
|
|
|
#ifdef MTK_SPI_DEBUG
|
|
|
|
int i;
|
|
|
|
|
|
|
|
printk(BIOS_DEBUG, "%s: 0x ", name);
|
|
|
|
for (i = 0; i < size; i++)
|
|
|
|
printk(BIOS_INFO, "%#x ", data[i]);
|
|
|
|
printk(BIOS_DEBUG, "\n");
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs)
|
|
|
|
{
|
|
|
|
struct mtk_spi_bus *eslave;
|
2015-10-16 07:42:49 +02:00
|
|
|
static struct spi_slave slave;
|
2015-07-31 11:10:46 +02:00
|
|
|
|
|
|
|
switch (bus) {
|
|
|
|
case CONFIG_EC_GOOGLE_CHROMEEC_SPI_BUS:
|
|
|
|
eslave = &spi_bus[bus];
|
|
|
|
assert(read32(&eslave->regs->spi_cfg0_reg) != 0);
|
|
|
|
spi_sw_reset(eslave->regs);
|
|
|
|
return &eslave->slave;
|
2016-08-11 21:40:09 +02:00
|
|
|
case CONFIG_BOOT_DEVICE_SPI_FLASH_BUS:
|
2015-10-16 07:42:49 +02:00
|
|
|
slave.bus = bus;
|
|
|
|
slave.cs = cs;
|
|
|
|
return &slave;
|
2015-07-31 11:10:46 +02:00
|
|
|
default:
|
|
|
|
die ("wrong bus number.\n");
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
int spi_claim_bus(struct spi_slave *slave)
|
|
|
|
{
|
|
|
|
struct mtk_spi_bus *mtk_slave = to_mtk_spi(slave);
|
|
|
|
struct mtk_spi_regs *regs = mtk_slave->regs;
|
|
|
|
|
|
|
|
setbits_le32(®s->spi_cmd_reg, 1 << SPI_CMD_PAUSE_EN_SHIFT);
|
|
|
|
mtk_slave->state = MTK_SPI_IDLE;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int mtk_spi_fifo_transfer(struct spi_slave *slave, void *in,
|
|
|
|
const void *out, u32 size)
|
|
|
|
{
|
|
|
|
struct mtk_spi_bus *mtk_slave = to_mtk_spi(slave);
|
|
|
|
struct mtk_spi_regs *regs = mtk_slave->regs;
|
|
|
|
uint8_t *inb = (uint8_t *)in;
|
|
|
|
const uint32_t *outb = (const uint32_t *)out;
|
|
|
|
uint32_t reg_val = 0;
|
|
|
|
uint32_t i, word_count;
|
|
|
|
struct stopwatch sw;
|
|
|
|
|
|
|
|
if (!size || size > MTK_FIFO_DEPTH)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
clrsetbits_le32(®s->spi_cfg1_reg,
|
|
|
|
SPI_CFG1_PACKET_LENGTH_MASK | SPI_CFG1_PACKET_LOOP_MASK,
|
|
|
|
((size - 1) << SPI_CFG1_PACKET_LENGTH_SHIFT) |
|
|
|
|
(0 << SPI_CFG1_PACKET_LOOP_SHIFT));
|
|
|
|
|
|
|
|
word_count = div_round_up(size, sizeof(u32));
|
|
|
|
if (inb) {
|
|
|
|
/* The SPI controller will transmit in full-duplex for RX,
|
|
|
|
* therefore we need arbitrary data on MOSI which the slave
|
|
|
|
* must ignore.
|
|
|
|
*/
|
|
|
|
for (i = 0; i < word_count; i++)
|
|
|
|
write32(®s->spi_tx_data_reg, MTK_ARBITRARY_VALUE);
|
|
|
|
}
|
|
|
|
if (outb) {
|
|
|
|
for (i = 0; i < word_count; i++)
|
|
|
|
write32(®s->spi_tx_data_reg, outb[i]);
|
|
|
|
mtk_spi_dump_data("the outb data is",
|
|
|
|
(const uint8_t *)outb, size);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mtk_slave->state == MTK_SPI_IDLE) {
|
|
|
|
setbits_le32(®s->spi_cmd_reg, SPI_CMD_ACT_EN);
|
|
|
|
mtk_slave->state = MTK_SPI_PAUSE_IDLE;
|
|
|
|
} else if (mtk_slave->state == MTK_SPI_PAUSE_IDLE) {
|
|
|
|
setbits_le32(®s->spi_cmd_reg, SPI_CMD_RESUME_EN);
|
|
|
|
}
|
|
|
|
|
|
|
|
stopwatch_init_usecs_expire(&sw, MTK_TXRX_TIMEOUT_US);
|
|
|
|
while ((read32(®s->spi_status1_reg) & MTK_SPI_BUSY_STATUS) == 0) {
|
|
|
|
if (stopwatch_expired(&sw)) {
|
|
|
|
printk(BIOS_ERR,
|
|
|
|
"Timeout waiting for status1 status.\n");
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
stopwatch_init_usecs_expire(&sw, MTK_TXRX_TIMEOUT_US);
|
|
|
|
while ((read32(®s->spi_status0_reg) &
|
|
|
|
MTK_SPI_PAUSE_FINISH_INT_STATUS) == 0) {
|
|
|
|
if (stopwatch_expired(&sw)) {
|
|
|
|
printk(BIOS_ERR,
|
|
|
|
"Timeout waiting for status0 status.\n");
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (inb) {
|
|
|
|
for (i = 0; i < size; i++) {
|
|
|
|
if (i % 4 == 0)
|
|
|
|
reg_val = read32(®s->spi_rx_data_reg);
|
|
|
|
*(inb + i) = (reg_val >> ((i % 4) * 8)) & 0xff;
|
|
|
|
}
|
|
|
|
mtk_spi_dump_data("the inb data is", inb, size);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
error:
|
|
|
|
spi_sw_reset(regs);
|
|
|
|
mtk_slave->state = MTK_SPI_IDLE;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int spi_xfer(struct spi_slave *slave, const void *dout, unsigned int bytes_out,
|
|
|
|
void *din, unsigned int bytes_in)
|
|
|
|
{
|
|
|
|
uint32_t min_size = 0;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
while (bytes_out || bytes_in) {
|
|
|
|
if (bytes_in && bytes_out)
|
|
|
|
min_size = MIN(MIN(bytes_out, bytes_in), MTK_FIFO_DEPTH);
|
|
|
|
else if (bytes_out)
|
|
|
|
min_size = MIN(bytes_out, MTK_FIFO_DEPTH);
|
|
|
|
else if (bytes_in)
|
|
|
|
min_size = MIN(bytes_in, MTK_FIFO_DEPTH);
|
|
|
|
|
|
|
|
ret = mtk_spi_fifo_transfer(slave, din, dout, min_size);
|
|
|
|
if (ret != 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
if (bytes_out) {
|
|
|
|
bytes_out -= min_size;
|
|
|
|
dout = (const uint8_t *)dout + min_size;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bytes_in) {
|
|
|
|
bytes_in -= min_size;
|
|
|
|
din = (uint8_t *)din + min_size;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void spi_release_bus(struct spi_slave *slave)
|
|
|
|
{
|
|
|
|
struct mtk_spi_bus *mtk_slave = to_mtk_spi(slave);
|
|
|
|
struct mtk_spi_regs *regs = mtk_slave->regs;
|
|
|
|
|
|
|
|
clrbits_le32(®s->spi_cmd_reg, SPI_CMD_PAUSE_EN);
|
|
|
|
spi_sw_reset(regs);
|
|
|
|
mtk_slave->state = MTK_SPI_IDLE;
|
|
|
|
}
|