summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulius Werner <jwerner@chromium.org>2018-03-27 16:28:52 -0700
committerJulius Werner <jwerner@chromium.org>2018-04-03 00:34:52 +0000
commit4783db2cf18c31ab48b219dabe1e04bf7f311d52 (patch)
treed68c3a022c55adc6ed0c7530983185325ac252d9
parentffeee420912eecf525564bb35b095b6bfa5d0de6 (diff)
spi: Add helper functions for bit-banging
Sometimes when bringing up a new board it can take a while until you have all the peripheral drivers ready. For those cases it is nice to be able to bitbang certain protocols so that you can already get further in the boot flow while those drivers are still being worked on. We already have this support for I2C, but it would be nice to have something for SPI as well, since without SPI you're not going to boot very far. This patch adds a couple of helper functions that platforms can use to implement bit-banging SPI with minimal effort. It also adds a proof of concept implementation using the RK3399. Change-Id: Ie3551f51cc9a9f8bf3a47fd5cea6d9c064da8a62 Signed-off-by: Julius Werner <jwerner@chromium.org> Reviewed-on: https://review.coreboot.org/25394 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Aaron Durbin <adurbin@chromium.org>
-rw-r--r--src/drivers/spi/Makefile.inc6
-rw-r--r--src/drivers/spi/bitbang.c84
-rw-r--r--src/include/spi_bitbang.h32
-rw-r--r--src/soc/rockchip/rk3399/spi_bitbang.c157
4 files changed, 279 insertions, 0 deletions
diff --git a/src/drivers/spi/Makefile.inc b/src/drivers/spi/Makefile.inc
index 3f68541448..edea392db1 100644
--- a/src/drivers/spi/Makefile.inc
+++ b/src/drivers/spi/Makefile.inc
@@ -18,6 +18,7 @@ smm-$(CONFIG_DEBUG_SMI) += flashconsole.c
endif
bootblock-y += spi-generic.c
+bootblock-y += bitbang.c
bootblock-$(CONFIG_COMMON_CBFS_SPI_WRAPPER) += cbfs_spi.c
bootblock-$(CONFIG_SPI_FLASH) += spi_flash.c
bootblock-$(CONFIG_BOOT_DEVICE_SPI_FLASH_RW_NOMMAP_EARLY) += boot_device_rw_nommap.c
@@ -34,6 +35,7 @@ bootblock-$(CONFIG_SPI_FLASH_WINBOND) += winbond.c
bootblock-$(CONFIG_SPI_FRAM_RAMTRON) += ramtron.c
romstage-y += spi-generic.c
+romstage-y += bitbang.c
romstage-$(CONFIG_COMMON_CBFS_SPI_WRAPPER) += cbfs_spi.c
romstage-$(CONFIG_SPI_FLASH) += spi_flash.c
romstage-$(CONFIG_BOOT_DEVICE_SPI_FLASH_RW_NOMMAP_EARLY) += boot_device_rw_nommap.c
@@ -50,6 +52,7 @@ romstage-$(CONFIG_SPI_FLASH_WINBOND) += winbond.c
romstage-$(CONFIG_SPI_FRAM_RAMTRON) += ramtron.c
verstage-y += spi-generic.c
+verstage-y += bitbang.c
verstage-$(CONFIG_COMMON_CBFS_SPI_WRAPPER) += cbfs_spi.c
verstage-$(CONFIG_SPI_FLASH) += spi_flash.c
verstage-$(CONFIG_BOOT_DEVICE_SPI_FLASH_RW_NOMMAP_EARLY) += boot_device_rw_nommap.c
@@ -66,6 +69,7 @@ verstage-$(CONFIG_SPI_FLASH_WINBOND) += winbond.c
verstage-$(CONFIG_SPI_FRAM_RAMTRON) += ramtron.c
ramstage-y += spi-generic.c
+ramstage-y += bitbang.c
ramstage-$(CONFIG_COMMON_CBFS_SPI_WRAPPER) += cbfs_spi.c
ramstage-$(CONFIG_SPI_FLASH) += spi_flash.c
ramstage-$(CONFIG_BOOT_DEVICE_SPI_FLASH_RW_NOMMAP) += boot_device_rw_nommap.c
@@ -83,6 +87,7 @@ ramstage-$(CONFIG_SPI_FRAM_RAMTRON) += ramtron.c
ifeq ($(CONFIG_SPI_FLASH_SMM),y)
smm-y += spi-generic.c
+smm-y += bitbang.c
# SPI flash driver interface
smm-$(CONFIG_SPI_FLASH) += spi_flash.c
smm-$(CONFIG_BOOT_DEVICE_SPI_FLASH_RW_NOMMAP) += boot_device_rw_nommap.c
@@ -102,5 +107,6 @@ smm-$(CONFIG_SPI_FRAM_RAMTRON) += ramtron.c
endif
postcar-y += spi-generic.c
+postcar-y += bitbang.c
postcar-$(CONFIG_BOOT_DEVICE_SPI_FLASH_RW_NOMMAP_EARLY) += boot_device_rw_nommap.c
postcar-$(CONFIG_SPI_FLASH) += spi_flash.c
diff --git a/src/drivers/spi/bitbang.c b/src/drivers/spi/bitbang.c
new file mode 100644
index 0000000000..d858345859
--- /dev/null
+++ b/src/drivers/spi/bitbang.c
@@ -0,0 +1,84 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright 2018 Google LLC
+ *
+ * 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 <console/console.h>
+#include <delay.h>
+#include <spi_bitbang.h>
+
+/* Set to 1 to dump all SPI transfers to the UART. */
+#define TRACE 0
+/*
+ * In theory, this should work fine with 0 delay since the bus is fully clocked
+ * by the master and the slave just needs to follow clock transitions whenever
+ * they happen. We're not going to be "too fast" by bit-banging anyway. However,
+ * if something doesn't work right, try increasing this value to slow it down.
+ */
+#define HALF_CLOCK_US 0
+
+int spi_bitbang_claim_bus(const struct spi_bitbang_ops *ops)
+{
+ ops->set_cs(ops, 0);
+ return 0;
+}
+
+void spi_bitbang_release_bus(const struct spi_bitbang_ops *ops)
+{
+ ops->set_cs(ops, 1);
+}
+
+/* Implements a CPOL=0, CPH=0, MSB first 8-bit controller. */
+int spi_bitbang_xfer(const struct spi_bitbang_ops *ops, const void *dout,
+ size_t bytes_out, void *din, size_t bytes_in)
+{
+ if (TRACE) {
+ if (bytes_in && bytes_out)
+ printk(BIOS_SPEW, "!");
+ else if (bytes_in)
+ printk(BIOS_SPEW, "<");
+ else if (bytes_out)
+ printk(BIOS_SPEW, ">");
+ }
+
+ while (bytes_out || bytes_in) {
+ int i;
+ uint8_t in_byte = 0, out_byte = 0;
+ if (bytes_out) {
+ out_byte = *(const uint8_t *)dout++;
+ bytes_out--;
+ if (TRACE)
+ printk(BIOS_SPEW, "%02x", out_byte);
+ }
+ for (i = 7; i >= 0; i--) {
+ ops->set_mosi(ops, !!(out_byte & (1 << i)));
+ if (HALF_CLOCK_US)
+ udelay(HALF_CLOCK_US);
+ ops->set_clk(ops, 1);
+ in_byte |= !!ops->get_miso(ops) << i;
+ if (HALF_CLOCK_US)
+ udelay(HALF_CLOCK_US);
+ ops->set_clk(ops, 0);
+ }
+ if (bytes_in) {
+ *(uint8_t *)din++ = in_byte;
+ bytes_in--;
+ if (TRACE)
+ printk(BIOS_SPEW, "%02x", in_byte);
+ }
+ }
+
+ if (TRACE)
+ printk(BIOS_SPEW, "\n");
+ return 0;
+}
diff --git a/src/include/spi_bitbang.h b/src/include/spi_bitbang.h
new file mode 100644
index 0000000000..710fefb1bf
--- /dev/null
+++ b/src/include/spi_bitbang.h
@@ -0,0 +1,32 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright 2018 Google LLC
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * 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.
+ */
+#ifndef _SPI_BITBANG_H_
+#define _SPI_BITBANG_H_
+
+#include <types.h>
+
+struct spi_bitbang_ops {
+ int (*get_miso)(const struct spi_bitbang_ops *ops);
+ void (*set_mosi)(const struct spi_bitbang_ops *ops, int value);
+ void (*set_clk)(const struct spi_bitbang_ops *ops, int value);
+ void (*set_cs)(const struct spi_bitbang_ops *ops, int value);
+};
+
+int spi_bitbang_claim_bus(const struct spi_bitbang_ops *ops);
+void spi_bitbang_release_bus(const struct spi_bitbang_ops *ops);
+int spi_bitbang_xfer(const struct spi_bitbang_ops *ops, const void *dout,
+ size_t bytes_out, void *din, size_t bytes_in);
+
+#endif /* _SPI_BITBANG_H_ */
diff --git a/src/soc/rockchip/rk3399/spi_bitbang.c b/src/soc/rockchip/rk3399/spi_bitbang.c
new file mode 100644
index 0000000000..628cc16764
--- /dev/null
+++ b/src/soc/rockchip/rk3399/spi_bitbang.c
@@ -0,0 +1,157 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright 2018 Google LLC
+ *
+ * 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.
+ */
+
+/* Compile this driver in place of common/spi.c for bitbang testing.
+ NOTE: Also need to adjust board-specific code for GPIO pinmux! */
+
+#include <assert.h>
+#include <gpio.h>
+#include <soc/spi.h>
+#include <spi_bitbang.h>
+#include <spi_flash.h>
+
+struct rockchip_bitbang_slave {
+ struct spi_bitbang_ops ops;
+ gpio_t miso;
+ gpio_t mosi;
+ gpio_t clk;
+ gpio_t cs;
+};
+
+static int get_miso(const struct spi_bitbang_ops *ops)
+{
+ const struct rockchip_bitbang_slave *slave =
+ container_of(ops, const struct rockchip_bitbang_slave, ops);
+ return gpio_get(slave->miso);
+}
+
+static void set_mosi(const struct spi_bitbang_ops *ops, int value)
+{
+ const struct rockchip_bitbang_slave *slave =
+ container_of(ops, const struct rockchip_bitbang_slave, ops);
+ gpio_set(slave->mosi, value);
+}
+
+static void set_clk(const struct spi_bitbang_ops *ops, int value)
+{
+ const struct rockchip_bitbang_slave *slave =
+ container_of(ops, const struct rockchip_bitbang_slave, ops);
+ gpio_set(slave->clk, value);
+}
+
+static void set_cs(const struct spi_bitbang_ops *ops, int value)
+{
+ const struct rockchip_bitbang_slave *slave =
+ container_of(ops, const struct rockchip_bitbang_slave, ops);
+ gpio_set(slave->cs, value);
+}
+
+/* Can't use GPIO() here because of bug in GCC version used by Chromium OS. */
+static const struct rockchip_bitbang_slave slaves[] = {
+ [0] = {
+ .ops = { get_miso, set_mosi, set_clk, set_cs },
+ .miso = { .port = 3, .bank = GPIO_A, .idx = 4 },
+ .mosi = { .port = 3, .bank = GPIO_A, .idx = 5 },
+ .clk = { .port = 3, .bank = GPIO_A, .idx = 6 },
+ .cs = { .port = 3, .bank = GPIO_A, .idx = 7 },
+ },
+ [1] = {
+ .ops = { get_miso, set_mosi, set_clk, set_cs },
+ .miso = { .port = 1, .bank = GPIO_A, .idx = 7 },
+ .mosi = { .port = 1, .bank = GPIO_B, .idx = 0 },
+ .clk = { .port = 1, .bank = GPIO_B, .idx = 1 },
+ .cs = { .port = 1, .bank = GPIO_B, .idx = 2 },
+ },
+ [2] = {
+ .ops = { get_miso, set_mosi, set_clk, set_cs },
+ .miso = { .port = 2, .bank = GPIO_B, .idx = 1 },
+ .mosi = { .port = 2, .bank = GPIO_B, .idx = 2 },
+ .clk = { .port = 2, .bank = GPIO_B, .idx = 3 },
+ .cs = { .port = 2, .bank = GPIO_B, .idx = 4 },
+ },
+ [3] = {
+ .ops = { get_miso, set_mosi, set_clk, set_cs },
+ .miso = { .port = 1, .bank = GPIO_B, .idx = 7 },
+ .mosi = { .port = 1, .bank = GPIO_C, .idx = 0 },
+ .clk = { .port = 1, .bank = GPIO_C, .idx = 1 },
+ .cs = { .port = 1, .bank = GPIO_C, .idx = 2 },
+ },
+ [4] = {
+ .ops = { get_miso, set_mosi, set_clk, set_cs },
+ .miso = { .port = 3, .bank = GPIO_A, .idx = 0 },
+ .mosi = { .port = 3, .bank = GPIO_A, .idx = 1 },
+ .clk = { .port = 3, .bank = GPIO_A, .idx = 2 },
+ .cs = { .port = 3, .bank = GPIO_A, .idx = 3 },
+ },
+ [5] = {
+ .ops = { get_miso, set_mosi, set_clk, set_cs },
+ .miso = { .port = 2, .bank = GPIO_C, .idx = 4 },
+ .mosi = { .port = 2, .bank = GPIO_C, .idx = 5 },
+ .clk = { .port = 2, .bank = GPIO_C, .idx = 6 },
+ .cs = { .port = 2, .bank = GPIO_C, .idx = 7 },
+ },
+};
+
+void rockchip_spi_init(unsigned int bus, unsigned int ignored_speed_hz)
+{
+ assert(bus >= 0 && bus < ARRAY_SIZE(slaves));
+
+ gpio_output(slaves[bus].cs, 1);
+ gpio_output(slaves[bus].clk, 0);
+ gpio_input(slaves[bus].miso);
+ gpio_output(slaves[bus].mosi, 0);
+}
+
+void rockchip_spi_set_sample_delay(unsigned int bus, unsigned int delay_ns)
+{
+ /* not supported, and not necessary for slow bitbang speeds */
+}
+
+static int spi_ctrlr_claim_bus(const struct spi_slave *slave)
+{
+ assert(slave->bus >= 0 && slave->bus < ARRAY_SIZE(slaves));
+ return spi_bitbang_claim_bus(&slaves[slave->bus].ops);
+}
+
+static void spi_ctrlr_release_bus(const struct spi_slave *slave)
+{
+ assert(slave->bus >= 0 && slave->bus < ARRAY_SIZE(slaves));
+ spi_bitbang_release_bus(&slaves[slave->bus].ops);
+}
+
+static int spi_ctrlr_xfer(const struct spi_slave *slave, const void *dout,
+ size_t bytes_out, void *din, size_t bytes_in)
+{
+ assert(slave->bus >= 0 && slave->bus < ARRAY_SIZE(slaves));
+ return spi_bitbang_xfer(&slaves[slave->bus].ops,
+ dout, bytes_out, din, bytes_in);
+}
+
+static const struct spi_ctrlr spi_ctrlr = {
+ .claim_bus = spi_ctrlr_claim_bus,
+ .release_bus = spi_ctrlr_release_bus,
+ .xfer = spi_ctrlr_xfer,
+ .max_xfer_size = 65535,
+};
+
+const struct spi_ctrlr_buses spi_ctrlr_bus_map[] = {
+ {
+ .ctrlr = &spi_ctrlr,
+ .bus_start = 0,
+ .bus_end = ARRAY_SIZE(slaves) - 1,
+ },
+};
+
+const size_t spi_ctrlr_bus_map_count = ARRAY_SIZE(spi_ctrlr_bus_map);