From 86d0d6e2cf83b73403823c69792a3ddfe1b00fde Mon Sep 17 00:00:00 2001 From: Tristan Shieh Date: Mon, 16 Jul 2018 20:16:40 +0800 Subject: mediatek: Refactor SPI code among similar SOCs Refactor SPI code which will be reused amon similar SOCs. BUG=b:80501386 BRANCH=none TEST=Boots correctly on Elm Change-Id: If5a6c554dc8361e729cf5c464325b97b2bfb7098 Signed-off-by: Tristan Shieh Reviewed-on: https://review.coreboot.org/27497 Tested-by: build bot (Jenkins) Reviewed-by: Julius Werner --- src/soc/mediatek/common/include/soc/spi_common.h | 91 ++++++++ src/soc/mediatek/common/spi.c | 261 +++++++++++++++++++++++ 2 files changed, 352 insertions(+) create mode 100644 src/soc/mediatek/common/include/soc/spi_common.h create mode 100644 src/soc/mediatek/common/spi.c (limited to 'src/soc/mediatek/common') diff --git a/src/soc/mediatek/common/include/soc/spi_common.h b/src/soc/mediatek/common/include/soc/spi_common.h new file mode 100644 index 0000000000..162db59f94 --- /dev/null +++ b/src/soc/mediatek/common/include/soc/spi_common.h @@ -0,0 +1,91 @@ +/* + * This file is part of the coreboot project. + * + * Copyright 2018 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. + */ + +#ifndef MTK_COMMON_SPI_H +#define MTK_COMMON_SPI_H + +#include + +enum { + SPI_CFG1_CS_IDLE_SHIFT = 0, + SPI_CFG1_PACKET_LOOP_SHIFT = 8, + SPI_CFG1_PACKET_LENGTH_SHIFT = 16, + + SPI_CFG1_CS_IDLE_MASK = 0xff << SPI_CFG1_CS_IDLE_SHIFT, + SPI_CFG1_PACKET_LOOP_MASK = 0xff << SPI_CFG1_PACKET_LOOP_SHIFT, + SPI_CFG1_PACKET_LENGTH_MASK = 0x3ff << SPI_CFG1_PACKET_LENGTH_SHIFT, +}; + +enum { + SPI_CMD_ACT_SHIFT = 0, + SPI_CMD_RESUME_SHIFT = 1, + SPI_CMD_RST_SHIFT = 2, + SPI_CMD_PAUSE_EN_SHIFT = 4, + SPI_CMD_DEASSERT_SHIFT = 5, + SPI_CMD_CPHA_SHIFT = 8, + SPI_CMD_CPOL_SHIFT = 9, + SPI_CMD_RX_DMA_SHIFT = 10, + SPI_CMD_TX_DMA_SHIFT = 11, + SPI_CMD_TXMSBF_SHIFT = 12, + SPI_CMD_RXMSBF_SHIFT = 13, + SPI_CMD_RX_ENDIAN_SHIFT = 14, + SPI_CMD_TX_ENDIAN_SHIFT = 15, + SPI_CMD_FINISH_IE_SHIFT = 16, + SPI_CMD_PAUSE_IE_SHIFT = 17, + + SPI_CMD_ACT_EN = BIT(SPI_CMD_ACT_SHIFT), + SPI_CMD_RESUME_EN = BIT(SPI_CMD_RESUME_SHIFT), + SPI_CMD_RST_EN = BIT(SPI_CMD_RST_SHIFT), + SPI_CMD_PAUSE_EN = BIT(SPI_CMD_PAUSE_EN_SHIFT), + SPI_CMD_DEASSERT_EN = BIT(SPI_CMD_DEASSERT_SHIFT), + SPI_CMD_CPHA_EN = BIT(SPI_CMD_CPHA_SHIFT), + SPI_CMD_CPOL_EN = BIT(SPI_CMD_CPOL_SHIFT), + SPI_CMD_RX_DMA_EN = BIT(SPI_CMD_RX_DMA_SHIFT), + SPI_CMD_TX_DMA_EN = BIT(SPI_CMD_TX_DMA_SHIFT), + SPI_CMD_TXMSBF_EN = BIT(SPI_CMD_TXMSBF_SHIFT), + SPI_CMD_RXMSBF_EN = BIT(SPI_CMD_RXMSBF_SHIFT), + SPI_CMD_RX_ENDIAN_EN = BIT(SPI_CMD_RX_ENDIAN_SHIFT), + SPI_CMD_TX_ENDIAN_EN = BIT(SPI_CMD_TX_ENDIAN_SHIFT), + SPI_CMD_FINISH_IE_EN = BIT(SPI_CMD_FINISH_IE_SHIFT), + SPI_CMD_PAUSE_IE_EN = BIT(SPI_CMD_PAUSE_IE_SHIFT), +}; + +enum spi_pad_mask { + SPI_PAD0_MASK = 0x0, + SPI_PAD1_MASK = 0x1, + SPI_PAD2_MASK = 0x2, + SPI_PAD3_MASK = 0x3, + SPI_PAD_SEL_MASK = 0x3 +}; + +struct mtk_spi_regs; + +struct mtk_spi_bus { + struct spi_slave slave; + struct mtk_spi_regs *regs; + int initialized; + int state; +}; + +extern const struct spi_ctrlr spi_ctrlr; +extern struct mtk_spi_bus spi_bus[]; + +void mtk_spi_set_gpio_pinmux(unsigned int bus, + enum spi_pad_mask pad_select); +void mtk_spi_set_timing(struct mtk_spi_regs *regs, u32 sck_ticks, u32 cs_ticks); +void mtk_spi_init(unsigned int bus, enum spi_pad_mask pad_select, + unsigned int speed_hz); + +#endif diff --git a/src/soc/mediatek/common/spi.c b/src/soc/mediatek/common/spi.c new file mode 100644 index 0000000000..5d2dab4b04 --- /dev/null +++ b/src/soc/mediatek/common/spi.c @@ -0,0 +1,261 @@ +/* + * This file is part of the coreboot project. + * + * Copyright 2018 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 +#include +#include +#include +#include +#include +#include + +#define MTK_SPI_DEBUG 0 + +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 inline struct mtk_spi_bus *to_mtk_spi(const struct spi_slave *slave) +{ + assert(slave->bus < SPI_BUS_NUMBER); + return &spi_bus[slave->bus]; +} + +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); +} + +void mtk_spi_init(unsigned int bus, enum spi_pad_mask pad_select, + unsigned int speed_hz) +{ + u32 div, sck_ticks, cs_ticks; + + assert(bus < SPI_BUS_NUMBER); + + 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(PAD%u) initialized at %u Hz\n", + bus, pad_select, SPI_HZ / (sck_ticks * 2)); + + mtk_spi_set_timing(regs, sck_ticks, cs_ticks); + + clrsetbits_le32(®s->spi_cmd_reg, + (SPI_CMD_CPHA_EN | SPI_CMD_CPOL_EN | + SPI_CMD_TX_ENDIAN_EN | SPI_CMD_RX_ENDIAN_EN | + SPI_CMD_TX_DMA_EN | SPI_CMD_RX_DMA_EN | + SPI_CMD_PAUSE_EN | SPI_CMD_DEASSERT_EN), + (SPI_CMD_TXMSBF_EN | SPI_CMD_RXMSBF_EN | + SPI_CMD_FINISH_IE_EN | SPI_CMD_PAUSE_IE_EN)); + + mtk_spi_set_gpio_pinmux(bus, 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) +{ + if (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"); + } +} + +static int spi_ctrlr_claim_bus(const 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 do_transfer(const struct spi_slave *slave, void *in, const void *out, + size_t *bytes_in, size_t *bytes_out) +{ + struct mtk_spi_bus *mtk_slave = to_mtk_spi(slave); + struct mtk_spi_regs *regs = mtk_slave->regs; + uint32_t reg_val = 0; + uint32_t i; + struct stopwatch sw; + size_t size; + + if (*bytes_out == 0) + size = *bytes_in; + else if (*bytes_in == 0) + size = *bytes_out; + else + size = MIN(*bytes_in, *bytes_out); + + 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)); + + if (*bytes_out) { + const uint8_t *outb = (const uint8_t *)out; + for (i = 0; i < size; i++) { + reg_val |= outb[i] << ((i % 4) * 8); + if (i % 4 == 3) { + write32(®s->spi_tx_data_reg, reg_val); + reg_val = 0; + } + } + + if (i % 4 != 0) + write32(®s->spi_tx_data_reg, reg_val); + + mtk_spi_dump_data("the outb data is", + (const uint8_t *)outb, size); + } else { + /* The SPI controller will transmit in full-duplex for RX, + * therefore we need arbitrary data on MOSI which the slave + * must ignore. + */ + uint32_t word_count = div_round_up(size, sizeof(u32)); + for (i = 0; i < word_count; i++) + write32(®s->spi_tx_data_reg, MTK_ARBITRARY_VALUE); + } + + 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 (*bytes_in) { + uint8_t *inb = (uint8_t *)in; + 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); + + *bytes_in -= size; + } + + if (*bytes_out) + *bytes_out -= size; + + return 0; +error: + spi_sw_reset(regs); + mtk_slave->state = MTK_SPI_IDLE; + return -1; +} + +static int spi_ctrlr_xfer(const struct spi_slave *slave, const void *dout, + size_t bytes_out, void *din, size_t bytes_in) +{ + while (bytes_out || bytes_in) { + size_t in_now = MIN(bytes_in, MTK_FIFO_DEPTH); + size_t out_now = MIN(bytes_out, MTK_FIFO_DEPTH); + size_t in_rem = in_now; + size_t out_rem = out_now; + + int ret = do_transfer(slave, din, dout, &in_rem, &out_rem); + if (ret != 0) + return ret; + + if (bytes_out) { + size_t sent = out_now - out_rem; + bytes_out -= sent; + dout += sent; + } + + if (bytes_in) { + size_t received = in_now - in_rem; + bytes_in -= received; + din += received; + } + } + + return 0; +} + +static void spi_ctrlr_release_bus(const 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; +} + +static int spi_ctrlr_setup(const struct spi_slave *slave) +{ + struct mtk_spi_bus *eslave = to_mtk_spi(slave); + assert(read32(&eslave->regs->spi_cfg0_reg) != 0); + spi_sw_reset(eslave->regs); + return 0; +} + +const struct spi_ctrlr spi_ctrlr = { + .setup = spi_ctrlr_setup, + .claim_bus = spi_ctrlr_claim_bus, + .release_bus = spi_ctrlr_release_bus, + .xfer = spi_ctrlr_xfer, + .max_xfer_size = 65535, +}; -- cgit v1.2.3