summaryrefslogtreecommitdiff
path: root/baseboard/kukui/emmc_ite.c
diff options
context:
space:
mode:
Diffstat (limited to 'baseboard/kukui/emmc_ite.c')
-rw-r--r--baseboard/kukui/emmc_ite.c242
1 files changed, 242 insertions, 0 deletions
diff --git a/baseboard/kukui/emmc_ite.c b/baseboard/kukui/emmc_ite.c
new file mode 100644
index 0000000000..79019164ec
--- /dev/null
+++ b/baseboard/kukui/emmc_ite.c
@@ -0,0 +1,242 @@
+/* Copyright 2021 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "chipset.h"
+#include "console.h"
+#include "endian.h"
+#include "gpio.h"
+#include "hooks.h"
+#include "hwtimer.h"
+#include "intc.h"
+#include "system.h"
+#include "task.h"
+#include "timer.h"
+#include "util.h"
+
+#include "bootblock_data.h"
+
+#define CPRINTS(format, args...) cprints(CC_SPI, format, ## args)
+
+enum emmc_cmd {
+ EMMC_ERROR = -1,
+ EMMC_IDLE = 0,
+ EMMC_PRE_IDLE,
+ EMMC_BOOT,
+};
+
+static void emmc_reset_spi_tx(void)
+{
+ /* Reset TX FIFO and count monitor */
+ IT83XX_SPI_TXFCR = IT83XX_SPI_TXFR | IT83XX_SPI_TXFCMR;
+ /* Send idle state (high/0xff) if master clocks in data. */
+ IT83XX_SPI_FCR = 0;
+}
+
+static void emmc_reset_spi_rx(void)
+{
+ /* End Rx FIFO access */
+ IT83XX_SPI_TXRXFAR = 0;
+ /* Reset RX FIFO and count monitor */
+ IT83XX_SPI_FCR = IT83XX_SPI_RXFR | IT83XX_SPI_RXFCMR;
+}
+
+/*
+ * Set SPI module work as eMMC Alternative Boot Mode.
+ * (CS# pin isn't required, and dropping data until CMD goes low)
+ */
+static void emmc_enable_spi(void)
+{
+ /* Set SPI pin mux to eMMC (GPM2:CLK, GPM3:CMD, GPM6:DATA0) */
+ IT83XX_GCTRL_PIN_MUX0 |= BIT(7);
+ /* Enable eMMC Alternative Boot Mode */
+ IT83XX_SPI_EMMCBMR |= IT83XX_SPI_EMMCABM;
+ /* Reset TX and RX FIFO */
+ emmc_reset_spi_tx();
+ emmc_reset_spi_rx();
+ /* Response idle state (high) */
+ IT83XX_SPI_SPISRDR = 0xff;
+ /* FIFO will be overwritten once it's full */
+ IT83XX_SPI_GCR2 = 0;
+ /* Write to clear pending interrupt bits */
+ IT83XX_SPI_ISR = 0xff;
+ IT83XX_SPI_RX_VLISR = IT83XX_SPI_RVLI;
+ /* Enable RX fifo full interrupt */
+ IT83XX_SPI_IMR = 0xff;
+ IT83XX_SPI_RX_VLISMR |= IT83XX_SPI_RVLIM;
+ IT83XX_SPI_IMR &= ~IT83XX_SPI_RX_FIFO_FULL;
+ /*
+ * Enable interrupt to detect AP's BOOTBLOCK_EN_L. So EC is able to
+ * switch SPI module back to communication mode once BOOTBLOCK_EN_L
+ * goes high (AP Jumped to bootloader).
+ */
+ gpio_clear_pending_interrupt(GPIO_BOOTBLOCK_EN_L);
+ gpio_enable_interrupt(GPIO_BOOTBLOCK_EN_L);
+
+ disable_sleep(SLEEP_MASK_EMMC);
+ CPRINTS("eMMC emulation enabled");
+}
+DECLARE_HOOK(HOOK_CHIPSET_STARTUP, emmc_enable_spi, HOOK_PRIO_FIRST);
+
+static void emmc_disable_spi(void)
+{
+ /* Set SPI pin mux to AP communication mode */
+ IT83XX_GCTRL_PIN_MUX0 &= ~BIT(7);
+ /* Disable eMMC Alternative Boot Mode */
+ IT83XX_SPI_EMMCBMR &= ~IT83XX_SPI_EMMCABM;
+ /* Reset TX and RX FIFO */
+ emmc_reset_spi_tx();
+ emmc_reset_spi_rx();
+ /* Restore setting of SPI slave for communication */
+ /* EC responses "ready to receive" while AP clock in bytes. */
+ IT83XX_SPI_SPISRDR = EC_SPI_OLD_READY;
+ /* FIFO won't be overwritten once it's full */
+ IT83XX_SPI_GCR2 = IT83XX_SPI_RXF2OC | IT83XX_SPI_RXF1OC
+ | IT83XX_SPI_RXFAR;
+ /* Write to clear pending interrupt bits */
+ IT83XX_SPI_ISR = 0xff;
+ IT83XX_SPI_RX_VLISR = IT83XX_SPI_RVLI;
+ /* Enable RX valid length interrupt */
+ IT83XX_SPI_IMR = 0xff;
+ IT83XX_SPI_RX_VLISMR &= ~IT83XX_SPI_RVLIM;
+ /* Disable interrupt of detection of AP's BOOTBLOCK_EN_L */
+ gpio_disable_interrupt(GPIO_BOOTBLOCK_EN_L);
+
+ enable_sleep(SLEEP_MASK_EMMC);
+ CPRINTS("eMMC emulation disabled");
+}
+
+static void emmc_init_spi(void)
+{
+ /* Enable alternate function */
+ gpio_config_module(MODULE_SPI_FLASH, 1);
+}
+DECLARE_HOOK(HOOK_INIT, emmc_init_spi, HOOK_PRIO_INIT_SPI + 1);
+
+static void emmc_send_data_over_spi(uint8_t *tx, int tx_size, int rst_tx)
+{
+ int i;
+
+ /* Reset TX FIFO and count monitor */
+ if (rst_tx)
+ IT83XX_SPI_TXFCR = IT83XX_SPI_TXFR | IT83XX_SPI_TXFCMR;
+ /* CPU access TX FIFO1 and FIFO2 */
+ IT83XX_SPI_TXRXFAR = IT83XX_SPI_CPUTFA;
+
+ /* Write response data to TX FIFO */
+ for (i = 0; i < tx_size; i += 4)
+ IT83XX_SPI_CPUWTFDB0 = *(uint32_t *)(tx + i);
+ /*
+ * After writing data to TX FIFO is finished, this bit will
+ * be to indicate the SPI slave controller.
+ */
+ IT83XX_SPI_TXFCR = IT83XX_SPI_TXFS;
+ /* End CPU access TX FIFO */
+ IT83XX_SPI_TXRXFAR = 0;
+ /* SPI module access TX FIFO */
+ IT83XX_SPI_FCR = IT83XX_SPI_SPISRTXF;
+}
+
+static void emmc_bootblock_transfer(void)
+{
+ int tx_size, sent = 0, remaining = sizeof(bootblock_raw_data);
+ uint8_t *raw = (uint8_t *)bootblock_raw_data;
+ const uint32_t timeout_us = 200;
+ uint32_t start;
+
+ /*
+ * HW will transmit the data of FIFO1 or FIFO2 in turn.
+ * So when a FIFO is empty, we need to fill the FIFO out
+ * immediately.
+ */
+ emmc_send_data_over_spi(&raw[sent], 256, 1);
+ sent += 256;
+
+ while (sent < remaining) {
+ /* Wait for FIFO1 or FIFO2 have been transmitted */
+ start = __hw_clock_source_read();
+ while (!(IT83XX_SPI_TXFSR & BIT(0)) &&
+ (__hw_clock_source_read() - start < timeout_us))
+ ;
+ /* Abort an ongoing transfer due to a command is received. */
+ if (IT83XX_SPI_ISR & IT83XX_SPI_RX_FIFO_FULL)
+ break;
+ /* fill out next 128 bytes to FIFO1 or FIFO2 */
+ tx_size = (remaining - sent) < 128 ? (remaining - sent) : 128;
+ emmc_send_data_over_spi(&raw[sent], tx_size, 0);
+ sent += tx_size;
+ }
+}
+
+static enum emmc_cmd emmc_parse_command(int index, uint32_t *cmd0)
+{
+ int32_t shift0;
+ uint32_t data[3];
+
+ data[0] = htobe32(cmd0[index]);
+ data[1] = htobe32(cmd0[index+1]);
+ data[2] = htobe32(cmd0[index+2]);
+
+ if ((data[0] & 0xff000000) != 0x40000000) {
+ /* Figure out alignment (cmd starts with 01) */
+ /* Number of leading ones. */
+ shift0 = __builtin_clz(~data[0]);
+
+ data[0] = (data[0] << shift0) | (data[1] >> (32-shift0));
+ data[1] = (data[1] << shift0) | (data[2] >> (32-shift0));
+ }
+
+ if (data[0] == 0x40000000 && data[1] == 0x0095ffff) {
+ /* 400000000095 GO_IDLE_STATE */
+ CPRINTS("goIdle");
+ return EMMC_IDLE;
+ }
+
+ if (data[0] == 0x40f0f0f0 && data[1] == 0xf0fdffff) {
+ /* 40f0f0f0f0fd GO_PRE_IDLE_STATE */
+ CPRINTS("goPreIdle");
+ return EMMC_PRE_IDLE;
+ }
+
+ if (data[0] == 0x40ffffff && data[1] == 0xfae5ffff) {
+ /* 40fffffffae5 BOOT_INITIATION */
+ CPRINTS("bootInit");
+ return EMMC_BOOT;
+ }
+
+ CPRINTS("eMMC error");
+ return EMMC_ERROR;
+}
+
+/* AP has booted */
+void emmc_ap_jump_to_bl(enum gpio_signal signal)
+{
+ /* Transmission completed. Set SPI communication mode */
+ emmc_disable_spi();
+
+ CPRINTS("AP Jumped to BL");
+}
+
+void spi_emmc_cmd0_isr(uint32_t *cmd0_payload)
+{
+ enum emmc_cmd cmd;
+
+ for (int i = 0; i < 8; i++) {
+ if (cmd0_payload[i] == 0xffffffff)
+ continue;
+
+ cmd = emmc_parse_command(i, &cmd0_payload[i]);
+
+ if (cmd == EMMC_IDLE || cmd == EMMC_PRE_IDLE) {
+ /* Abort an ongoing transfer. */
+ emmc_reset_spi_tx();
+ break;
+ }
+
+ if (cmd == EMMC_BOOT) {
+ emmc_bootblock_transfer();
+ break;
+ }
+ }
+}