summaryrefslogtreecommitdiffstats
path: root/src/soc/mediatek/mt8173/spi.c
diff options
context:
space:
mode:
authorLeilk Liu <leilk.liu@mediatek.com>2015-07-31 17:10:46 +0800
committerMartin Roth <martinroth@google.com>2016-01-22 19:27:36 +0100
commit1dda32bb13c6c92a1c3ab94110a42ddc8b12dac9 (patch)
treeed6c00f11abf68216458cbf07be880ebc2bcfbb8 /src/soc/mediatek/mt8173/spi.c
parent5d7cbc272e3c94dd58e26e3a17ce2805e51f0e09 (diff)
downloadcoreboot-1dda32bb13c6c92a1c3ab94110a42ddc8b12dac9.tar.gz
coreboot-1dda32bb13c6c92a1c3ab94110a42ddc8b12dac9.tar.bz2
coreboot-1dda32bb13c6c92a1c3ab94110a42ddc8b12dac9.zip
mediatek/mt8173: Add SPI support
BUG=none TEST=emerge-oak coreboot BRANCH=none [pg: split into multiple commits] Change-Id: I82d982b40dd0bfaa7770a6b08c70b20337a46955 Signed-off-by: Patrick Georgi <pgeorgi@chromium.org> Original-Commit-Id: 41acc14e9fe54924d20e4e5a2d1519251f0e1c87 Original-Change-Id: I2559be4191da9af523944563729171bd92a86cd0 Original-Signed-off-by: Leilk Liu <leilk.liu@mediatek.com> Original-Reviewed-on: https://chromium-review.googlesource.com/292661 Original-Commit-Ready: Yidi Lin <yidi.lin@mediatek.com> Original-Tested-by: Yidi Lin <yidi.lin@mediatek.com> Original-Reviewed-by: Julius Werner <jwerner@chromium.org> Reviewed-on: https://review.coreboot.org/12611 Tested-by: build bot (Jenkins) Reviewed-by: Martin Roth <martinroth@google.com>
Diffstat (limited to 'src/soc/mediatek/mt8173/spi.c')
-rw-r--r--src/soc/mediatek/mt8173/spi.c303
1 files changed, 303 insertions, 0 deletions
diff --git a/src/soc/mediatek/mt8173/spi.c b/src/soc/mediatek/mt8173/spi.c
new file mode 100644
index 000000000000..c3c71c3db2c4
--- /dev/null
+++ b/src/soc/mediatek/mt8173/spi.c
@@ -0,0 +1,303 @@
+/*
+ * 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>
+#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(&regs->spi_cmd_reg, SPI_CMD_RST_EN);
+ clrbits_le32(&regs->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;
+ /* mtk spi HW just support bus 0 */
+ assert(bus == 0);
+ 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(&regs->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(&regs->spi_cfg1_reg, SPI_CFG1_CS_IDLE_MASK,
+ ((cs_ticks - 1) << SPI_CFG1_CS_IDLE_SHIFT));
+
+ reg_val = read32(&regs->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(&regs->spi_cmd_reg, reg_val);
+
+ mtk_spi_set_gpio_pinmux(pad_select);
+ /* pad select */
+ clrsetbits_le32(&regs->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;
+
+ 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;
+ 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(&regs->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(&regs->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(&regs->spi_tx_data_reg, MTK_ARBITRARY_VALUE);
+ }
+ if (outb) {
+ for (i = 0; i < word_count; i++)
+ write32(&regs->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(&regs->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(&regs->spi_cmd_reg, SPI_CMD_RESUME_EN);
+ }
+
+ stopwatch_init_usecs_expire(&sw, MTK_TXRX_TIMEOUT_US);
+ while ((read32(&regs->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(&regs->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(&regs->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(&regs->spi_cmd_reg, SPI_CMD_PAUSE_EN);
+ spi_sw_reset(regs);
+ mtk_slave->state = MTK_SPI_IDLE;
+}