diff options
Diffstat (limited to 'drivers/rpi3')
-rw-r--r-- | drivers/rpi3/gpio/rpi3_gpio.c | 164 | ||||
-rw-r--r-- | drivers/rpi3/mailbox/rpi3_mbox.c | 82 | ||||
-rw-r--r-- | drivers/rpi3/rng/rpi3_rng.c | 75 | ||||
-rw-r--r-- | drivers/rpi3/sdhost/rpi3_sdhost.c | 679 |
4 files changed, 1000 insertions, 0 deletions
diff --git a/drivers/rpi3/gpio/rpi3_gpio.c b/drivers/rpi3/gpio/rpi3_gpio.c new file mode 100644 index 0000000..55a8832 --- /dev/null +++ b/drivers/rpi3/gpio/rpi3_gpio.c @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2019, Linaro Limited + * Copyright (c) 2019, Ying-Chun Liu (PaulLiu) <paul.liu@linaro.org> + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <string.h> +#include <assert.h> +#include <lib/mmio.h> +#include <drivers/delay_timer.h> +#include <drivers/rpi3/gpio/rpi3_gpio.h> +#include <platform_def.h> + +static uintptr_t reg_base; + +static int rpi3_gpio_get_direction(int gpio); +static void rpi3_gpio_set_direction(int gpio, int direction); +static int rpi3_gpio_get_value(int gpio); +static void rpi3_gpio_set_value(int gpio, int value); +static void rpi3_gpio_set_pull(int gpio, int pull); + +static const gpio_ops_t rpi3_gpio_ops = { + .get_direction = rpi3_gpio_get_direction, + .set_direction = rpi3_gpio_set_direction, + .get_value = rpi3_gpio_get_value, + .set_value = rpi3_gpio_set_value, + .set_pull = rpi3_gpio_set_pull, +}; + +/** + * Get selection of GPIO pinmux settings. + * + * @param gpio The pin number of GPIO. From 0 to 53. + * @return The selection of pinmux. RPI3_GPIO_FUNC_INPUT: input, + * RPI3_GPIO_FUNC_OUTPUT: output, + * RPI3_GPIO_FUNC_ALT0: alt-0, + * RPI3_GPIO_FUNC_ALT1: alt-1, + * RPI3_GPIO_FUNC_ALT2: alt-2, + * RPI3_GPIO_FUNC_ALT3: alt-3, + * RPI3_GPIO_FUNC_ALT4: alt-4, + * RPI3_GPIO_FUNC_ALT5: alt-5 + */ +int rpi3_gpio_get_select(int gpio) +{ + int ret; + int regN = gpio / 10; + int shift = 3 * (gpio % 10); + uintptr_t reg_sel = reg_base + RPI3_GPIO_GPFSEL(regN); + uint32_t sel = mmio_read_32(reg_sel); + + ret = (sel >> shift) & 0x07; + + return ret; +} + +/** + * Set selection of GPIO pinmux settings. + * + * @param gpio The pin number of GPIO. From 0 to 53. + * @param fsel The selection of pinmux. RPI3_GPIO_FUNC_INPUT: input, + * RPI3_GPIO_FUNC_OUTPUT: output, + * RPI3_GPIO_FUNC_ALT0: alt-0, + * RPI3_GPIO_FUNC_ALT1: alt-1, + * RPI3_GPIO_FUNC_ALT2: alt-2, + * RPI3_GPIO_FUNC_ALT3: alt-3, + * RPI3_GPIO_FUNC_ALT4: alt-4, + * RPI3_GPIO_FUNC_ALT5: alt-5 + */ +void rpi3_gpio_set_select(int gpio, int fsel) +{ + int regN = gpio / 10; + int shift = 3 * (gpio % 10); + uintptr_t reg_sel = reg_base + RPI3_GPIO_GPFSEL(regN); + uint32_t sel = mmio_read_32(reg_sel); + uint32_t mask = U(0x07) << shift; + + sel = (sel & (~mask)) | ((fsel << shift) & mask); + mmio_write_32(reg_sel, sel); +} + +static int rpi3_gpio_get_direction(int gpio) +{ + int result = rpi3_gpio_get_select(gpio); + + if (result == RPI3_GPIO_FUNC_INPUT) + return GPIO_DIR_IN; + else if (result == RPI3_GPIO_FUNC_OUTPUT) + return GPIO_DIR_OUT; + + return GPIO_DIR_IN; +} + +static void rpi3_gpio_set_direction(int gpio, int direction) +{ + switch (direction) { + case GPIO_DIR_IN: + rpi3_gpio_set_select(gpio, RPI3_GPIO_FUNC_INPUT); + break; + case GPIO_DIR_OUT: + rpi3_gpio_set_select(gpio, RPI3_GPIO_FUNC_OUTPUT); + break; + } +} + +static int rpi3_gpio_get_value(int gpio) +{ + int regN = gpio / 32; + int shift = gpio % 32; + uintptr_t reg_lev = reg_base + RPI3_GPIO_GPLEV(regN); + uint32_t value = mmio_read_32(reg_lev); + + if ((value >> shift) & 0x01) + return GPIO_LEVEL_HIGH; + return GPIO_LEVEL_LOW; +} + +static void rpi3_gpio_set_value(int gpio, int value) +{ + int regN = gpio / 32; + int shift = gpio % 32; + uintptr_t reg_set = reg_base + RPI3_GPIO_GPSET(regN); + uintptr_t reg_clr = reg_base + RPI3_GPIO_GPSET(regN); + + switch (value) { + case GPIO_LEVEL_LOW: + mmio_write_32(reg_clr, U(1) << shift); + break; + case GPIO_LEVEL_HIGH: + mmio_write_32(reg_set, U(1) << shift); + break; + } +} + +static void rpi3_gpio_set_pull(int gpio, int pull) +{ + int regN = gpio / 32; + int shift = gpio % 32; + uintptr_t reg_pud = reg_base + RPI3_GPIO_GPPUD; + uintptr_t reg_clk = reg_base + RPI3_GPIO_GPPUDCLK(regN); + + switch (pull) { + case GPIO_PULL_NONE: + mmio_write_32(reg_pud, 0x0); + break; + case GPIO_PULL_UP: + mmio_write_32(reg_pud, 0x2); + break; + case GPIO_PULL_DOWN: + mmio_write_32(reg_pud, 0x1); + break; + } + mdelay(150); + mmio_write_32(reg_clk, U(1) << shift); + mdelay(150); + mmio_write_32(reg_clk, 0x0); + mmio_write_32(reg_pud, 0x0); +} + +void rpi3_gpio_init(void) +{ + reg_base = RPI3_GPIO_BASE; + gpio_init(&rpi3_gpio_ops); +} diff --git a/drivers/rpi3/mailbox/rpi3_mbox.c b/drivers/rpi3/mailbox/rpi3_mbox.c new file mode 100644 index 0000000..aef1f39 --- /dev/null +++ b/drivers/rpi3/mailbox/rpi3_mbox.c @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2018-2019, ARM Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <platform_def.h> + +#include <arch_helpers.h> +#include <common/debug.h> +#include <lib/mmio.h> + +#include <rpi_hw.h> + +#include <drivers/rpi3/mailbox/rpi3_mbox.h> + +#define RPI3_MAILBOX_MAX_RETRIES U(1000000) + +/******************************************************************************* + * Routine to send requests to the VideoCore using the mailboxes. + ******************************************************************************/ +void rpi3_vc_mailbox_request_send(rpi3_mbox_request_t *req, int req_size) +{ + uint32_t st, data; + uintptr_t resp_addr, addr; + unsigned int retries; + + /* This is the location of the request buffer */ + addr = (uintptr_t)req; + + /* Make sure that the changes are seen by the VideoCore */ + flush_dcache_range(addr, req_size); + + /* Wait until the outbound mailbox is empty */ + retries = 0U; + + do { + st = mmio_read_32(RPI3_MBOX_BASE + RPI3_MBOX1_STATUS_OFFSET); + + retries++; + if (retries == RPI3_MAILBOX_MAX_RETRIES) { + ERROR("rpi3: mbox: Send request timeout\n"); + return; + } + + } while ((st & RPI3_MBOX_STATUS_EMPTY_MASK) == 0U); + + /* Send base address of this message to start request */ + mmio_write_32(RPI3_MBOX_BASE + RPI3_MBOX1_WRITE_OFFSET, + RPI3_CHANNEL_ARM_TO_VC | (uint32_t) addr); + + /* Wait until the inbound mailbox isn't empty */ + retries = 0U; + + do { + st = mmio_read_32(RPI3_MBOX_BASE + RPI3_MBOX0_STATUS_OFFSET); + + retries++; + if (retries == RPI3_MAILBOX_MAX_RETRIES) { + ERROR("rpi3: mbox: Receive response timeout\n"); + return; + } + + } while ((st & RPI3_MBOX_STATUS_EMPTY_MASK) != 0U); + + /* Get location and channel */ + data = mmio_read_32(RPI3_MBOX_BASE + RPI3_MBOX0_READ_OFFSET); + + if ((data & RPI3_CHANNEL_MASK) != RPI3_CHANNEL_ARM_TO_VC) { + ERROR("rpi3: mbox: Wrong channel: 0x%08x\n", data); + panic(); + } + + resp_addr = (uintptr_t)(data & ~RPI3_CHANNEL_MASK); + if (addr != resp_addr) { + ERROR("rpi3: mbox: Unexpected address: 0x%08x\n", data); + panic(); + } + + /* Make sure that the data seen by the CPU is up to date */ + inv_dcache_range(addr, req_size); +} diff --git a/drivers/rpi3/rng/rpi3_rng.c b/drivers/rpi3/rng/rpi3_rng.c new file mode 100644 index 0000000..b6bf005 --- /dev/null +++ b/drivers/rpi3/rng/rpi3_rng.c @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2018, ARM Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <assert.h> +#include <string.h> + +#include <lib/mmio.h> + +#include <rpi_hw.h> + +/* Initial amount of values to discard */ +#define RNG_WARMUP_COUNT U(0x40000) + +static void rpi3_rng_initialize(void) +{ + uint32_t int_mask, ctrl; + + /* Return if it is already enabled */ + ctrl = mmio_read_32(RPI3_RNG_BASE + RPI3_RNG_CTRL_OFFSET); + if ((ctrl & RPI3_RNG_CTRL_ENABLE) != 0U) { + return; + } + + /* Mask interrupts */ + int_mask = mmio_read_32(RPI3_RNG_BASE + RPI3_RNG_INT_MASK_OFFSET); + int_mask |= RPI3_RNG_INT_MASK_DISABLE; + mmio_write_32(RPI3_RNG_BASE + RPI3_RNG_INT_MASK_OFFSET, int_mask); + + /* Discard several values when initializing to give it time to warmup */ + mmio_write_32(RPI3_RNG_BASE + RPI3_RNG_STATUS_OFFSET, RNG_WARMUP_COUNT); + + mmio_write_32(RPI3_RNG_BASE + RPI3_RNG_CTRL_OFFSET, + RPI3_RNG_CTRL_ENABLE); +} + +static uint32_t rpi3_rng_get_word(void) +{ + size_t nwords; + + do { + /* Get number of available words to read */ + nwords = (mmio_read_32(RPI3_RNG_BASE + RPI3_RNG_STATUS_OFFSET) + >> RPI3_RNG_STATUS_NUM_WORDS_SHIFT) + & RPI3_RNG_STATUS_NUM_WORDS_MASK; + } while (nwords == 0U); + + return mmio_read_32(RPI3_RNG_BASE + RPI3_RNG_DATA_OFFSET); +} + +void rpi3_rng_read(void *buf, size_t len) +{ + uint32_t data; + size_t left = len; + uint32_t *dst = buf; + + assert(buf != NULL); + assert(len != 0U); + assert(check_uptr_overflow((uintptr_t) buf, (uintptr_t) len) == 0); + + rpi3_rng_initialize(); + + while (left >= sizeof(uint32_t)) { + data = rpi3_rng_get_word(); + *dst++ = data; + left -= sizeof(uint32_t); + } + + if (left > 0U) { + data = rpi3_rng_get_word(); + memcpy(dst, &data, left); + } +} diff --git a/drivers/rpi3/sdhost/rpi3_sdhost.c b/drivers/rpi3/sdhost/rpi3_sdhost.c new file mode 100644 index 0000000..c4b6fca --- /dev/null +++ b/drivers/rpi3/sdhost/rpi3_sdhost.c @@ -0,0 +1,679 @@ +/* + * Copyright (c) 2019, Linaro Limited + * Copyright (c) 2019, Ying-Chun Liu (PaulLiu) <paul.liu@linaro.org> + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <arch.h> +#include <arch_helpers.h> +#include <assert.h> +#include <common/debug.h> +#include <lib/mmio.h> +#include <drivers/delay_timer.h> +#include <drivers/rpi3/sdhost/rpi3_sdhost.h> +#include <drivers/mmc.h> +#include <drivers/rpi3/gpio/rpi3_gpio.h> +#include <errno.h> +#include <string.h> + +static void rpi3_sdhost_initialize(void); +static int rpi3_sdhost_send_cmd(struct mmc_cmd *cmd); +static int rpi3_sdhost_set_ios(unsigned int clk, unsigned int width); +static int rpi3_sdhost_prepare(int lba, uintptr_t buf, size_t size); +static int rpi3_sdhost_read(int lba, uintptr_t buf, size_t size); +static int rpi3_sdhost_write(int lba, uintptr_t buf, size_t size); + +static const struct mmc_ops rpi3_sdhost_ops = { + .init = rpi3_sdhost_initialize, + .send_cmd = rpi3_sdhost_send_cmd, + .set_ios = rpi3_sdhost_set_ios, + .prepare = rpi3_sdhost_prepare, + .read = rpi3_sdhost_read, + .write = rpi3_sdhost_write, +}; + +static struct rpi3_sdhost_params rpi3_sdhost_params; + +/** + * Wait for command being processed. + * + * This function waits the command being processed. It compares + * the ENABLE flag of the HC_COMMAND register. When ENABLE flag disappeared + * it means the command is processed by the SDHOST. + * The timeout is currently 1000*100 us = 100 ms. + * + * @return 0: command finished. 1: command timed out. + */ +static int rpi3_sdhost_waitcommand(void) +{ + uintptr_t reg_base = rpi3_sdhost_params.reg_base; + + volatile int timeout = 1000; + + while ((mmio_read_32(reg_base + HC_COMMAND) & HC_CMD_ENABLE) + && (--timeout > 0)) { + udelay(100); + } + + return ((timeout > 0) ? 0 : (-(ETIMEDOUT))); +} + +/** + * Send the command and argument to the SDHOST + * + * This function will wait for the previous command finished. And then + * clear any error status of previous command. And then + * send out the command and args. The command will be turned on the ENABLE + * flag before sending out. + */ +static void send_command_raw(unsigned int cmd, unsigned int arg) +{ + unsigned int status; + uintptr_t reg_base = rpi3_sdhost_params.reg_base; + + /* wait for previous command finish */ + rpi3_sdhost_waitcommand(); + + /* clean error status */ + status = mmio_read_32(reg_base + HC_HOSTSTATUS); + if (status & HC_HSTST_MASK_ERROR_ALL) + mmio_write_32(reg_base + HC_HOSTSTATUS, status); + + /* recording the command */ + rpi3_sdhost_params.current_cmd = cmd & HC_CMD_COMMAND_MASK; + + /* send the argument and command */ + mmio_write_32(reg_base + HC_ARGUMENT, arg); + mmio_write_32(reg_base + HC_COMMAND, cmd | HC_CMD_ENABLE); +} + +/** + * Send the command and argument to the SDHOST, decorated with control + * flags. + * + * This function will use send_command_raw to send the commands to SDHOST. + * But before sending it will decorate the command with control flags specific + * to SDHOST. + */ +static void send_command_decorated(unsigned int cmd, unsigned int arg) +{ + unsigned int cmd_flags = 0; + + switch (cmd & HC_CMD_COMMAND_MASK) { + case MMC_CMD(0): + cmd_flags |= HC_CMD_RESPONSE_NONE; + break; + case MMC_ACMD(51): + cmd_flags |= HC_CMD_READ; + break; + case MMC_CMD(8): + case MMC_CMD(11): + case MMC_CMD(17): + case MMC_CMD(18): + cmd_flags |= HC_CMD_READ; + break; + case MMC_CMD(20): + case MMC_CMD(24): + case MMC_CMD(25): + cmd_flags |= HC_CMD_WRITE; + break; + case MMC_CMD(12): + cmd_flags |= HC_CMD_BUSY; + break; + default: + break; + } + send_command_raw(cmd | cmd_flags, arg); +} + +/** + * drains the FIFO on DATA port + * + * This function drains any data left in the DATA port. + */ +static void rpi3_drain_fifo(void) +{ + uintptr_t reg_base = rpi3_sdhost_params.reg_base; + volatile int timeout = 100000; + + rpi3_sdhost_waitcommand(); + + while (mmio_read_32(reg_base + HC_HOSTSTATUS) & HC_HSTST_HAVEDATA) { + mmio_read_32(reg_base + HC_DATAPORT); + udelay(100); + } + + while (1) { + uint32_t edm, fsm; + + edm = mmio_read_32(reg_base + HC_DEBUG); + fsm = edm & HC_DBG_FSM_MASK; + + if ((fsm == HC_DBG_FSM_IDENTMODE) || + (fsm == HC_DBG_FSM_DATAMODE)) + break; + + if ((fsm == HC_DBG_FSM_READWAIT) || + (fsm == HC_DBG_FSM_WRITESTART1) || + (fsm == HC_DBG_FSM_READDATA)) { + mmio_write_32(reg_base + HC_DEBUG, + edm | HC_DBG_FORCE_DATA_MODE); + break; + } + + if (--timeout <= 0) { + ERROR("rpi3_sdhost: %s cannot recover stat\n", + __func__); + return; + } + } +} + +/** + * Dump SDHOST registers + */ +static void rpi3_sdhost_print_regs(void) +{ + uintptr_t reg_base = rpi3_sdhost_params.reg_base; + + INFO("rpi3_sdhost: HC_COMMAND: 0x%08x\n", + mmio_read_32(reg_base + HC_COMMAND)); + INFO("rpi3_sdhost: HC_ARGUMENT: 0x%08x\n", + mmio_read_32(reg_base + HC_ARGUMENT)); + INFO("rpi3_sdhost: HC_TIMEOUTCOUNTER: 0x%08x\n", + mmio_read_32(reg_base + HC_TIMEOUTCOUNTER)); + INFO("rpi3_sdhost: HC_CLOCKDIVISOR: 0x%08x\n", + mmio_read_32(reg_base + HC_CLOCKDIVISOR)); + INFO("rpi3_sdhost: HC_RESPONSE_0: 0x%08x\n", + mmio_read_32(reg_base + HC_RESPONSE_0)); + INFO("rpi3_sdhost: HC_RESPONSE_1: 0x%08x\n", + mmio_read_32(reg_base + HC_RESPONSE_1)); + INFO("rpi3_sdhost: HC_RESPONSE_2: 0x%08x\n", + mmio_read_32(reg_base + HC_RESPONSE_2)); + INFO("rpi3_sdhost: HC_RESPONSE_3: 0x%08x\n", + mmio_read_32(reg_base + HC_RESPONSE_3)); + INFO("rpi3_sdhost: HC_HOSTSTATUS: 0x%08x\n", + mmio_read_32(reg_base + HC_HOSTSTATUS)); + INFO("rpi3_sdhost: HC_POWER: 0x%08x\n", + mmio_read_32(reg_base + HC_POWER)); + INFO("rpi3_sdhost: HC_DEBUG: 0x%08x\n", + mmio_read_32(reg_base + HC_DEBUG)); + INFO("rpi3_sdhost: HC_HOSTCONFIG: 0x%08x\n", + mmio_read_32(reg_base + HC_HOSTCONFIG)); + INFO("rpi3_sdhost: HC_BLOCKSIZE: 0x%08x\n", + mmio_read_32(reg_base + HC_BLOCKSIZE)); + INFO("rpi3_sdhost: HC_BLOCKCOUNT: 0x%08x\n", + mmio_read_32(reg_base + HC_BLOCKCOUNT)); +} + +/** + * Reset SDHOST + */ +static void rpi3_sdhost_reset(void) +{ + uintptr_t reg_base = rpi3_sdhost_params.reg_base; + unsigned int dbg; + uint32_t tmp1; + + mmio_write_32(reg_base + HC_POWER, 0); + mmio_write_32(reg_base + HC_COMMAND, 0); + mmio_write_32(reg_base + HC_ARGUMENT, 0); + + mmio_write_32(reg_base + HC_TIMEOUTCOUNTER, HC_TIMEOUT_DEFAULT); + mmio_write_32(reg_base + HC_CLOCKDIVISOR, 0); + mmio_write_32(reg_base + HC_HOSTSTATUS, HC_HSTST_RESET); + mmio_write_32(reg_base + HC_HOSTCONFIG, 0); + mmio_write_32(reg_base + HC_BLOCKSIZE, 0); + mmio_write_32(reg_base + HC_BLOCKCOUNT, 0); + + dbg = mmio_read_32(reg_base + HC_DEBUG); + dbg &= ~((HC_DBG_FIFO_THRESH_MASK << HC_DBG_FIFO_THRESH_READ_SHIFT) | + (HC_DBG_FIFO_THRESH_MASK << HC_DBG_FIFO_THRESH_WRITE_SHIFT)); + dbg |= (HC_FIFO_THRESH_READ << HC_DBG_FIFO_THRESH_READ_SHIFT) | + (HC_FIFO_THRESH_WRITE << HC_DBG_FIFO_THRESH_WRITE_SHIFT); + mmio_write_32(reg_base + HC_DEBUG, dbg); + mdelay(250); + mmio_write_32(reg_base + HC_POWER, 1); + mdelay(250); + rpi3_sdhost_params.clk_rate = 0; + + mmio_write_32(reg_base + HC_CLOCKDIVISOR, HC_CLOCKDIVISOR_MAXVAL); + tmp1 = mmio_read_32(reg_base + HC_HOSTCONFIG); + mmio_write_32(reg_base + HC_HOSTCONFIG, tmp1 | HC_HSTCF_INT_BUSY); +} + +static void rpi3_sdhost_initialize(void) +{ + uintptr_t reg_base = rpi3_sdhost_params.reg_base; + + assert((rpi3_sdhost_params.reg_base & MMC_BLOCK_MASK) == 0); + + rpi3_sdhost_reset(); + + mmio_write_32(reg_base + HC_CLOCKDIVISOR, HC_CLOCKDIVISOR_PREFERVAL); + udelay(300); +} + +static int rpi3_sdhost_send_cmd(struct mmc_cmd *cmd) +{ + uintptr_t reg_base = rpi3_sdhost_params.reg_base; + int err = 0; + uint32_t cmd_idx; + uint32_t cmd_arg; + uint32_t cmd_flags = 0; + uint32_t intmask; + + /* Wait for the command done */ + err = rpi3_sdhost_waitcommand(); + if (err != 0) { + WARN("previous command not done yet\n"); + return err; + } + + cmd_idx = cmd->cmd_idx & HC_CMD_COMMAND_MASK; + + cmd_arg = cmd->cmd_arg; + if (cmd_idx == MMC_ACMD(51)) { + /* if previous cmd send to SDHOST is not MMC_CMD(55). + * It means this MMC_ACMD(51) is a resend. + * And we must also resend MMC_CMD(55) in this case + */ + if (rpi3_sdhost_params.current_cmd != MMC_CMD(55)) { + send_command_decorated( + MMC_CMD(55), + rpi3_sdhost_params.sdcard_rca << + RCA_SHIFT_OFFSET); + rpi3_sdhost_params.mmc_app_cmd = 1; + rpi3_sdhost_waitcommand(); + + /* Also we need to call prepare to clean the buffer */ + rpi3_sdhost_prepare(0, (uintptr_t)NULL, 8); + } + } + + /* We ignore MMC_CMD(12) sending from the TF-A's MMC driver + * because we send MMC_CMD(12) by ourselves. + */ + if (cmd_idx == MMC_CMD(12)) + return 0; + + if ((cmd->resp_type & MMC_RSP_136) && + (cmd->resp_type & MMC_RSP_BUSY)) { + ERROR("rpi3_sdhost: unsupported response type!\n"); + return -(EOPNOTSUPP); + } + + if (cmd->resp_type & MMC_RSP_48 && cmd->resp_type != MMC_RESPONSE_R2) { + /* 48-bit command + * We don't need to set any flags here because it is default. + */ + } else if (cmd->resp_type & MMC_RSP_136) { + /* 136-bit command */ + cmd_flags |= HC_CMD_RESPONSE_LONG; + } else { + /* no respond command */ + cmd_flags |= HC_CMD_RESPONSE_NONE; + } + + rpi3_sdhost_params.cmdbusy = 0; + if (cmd->resp_type & MMC_RSP_BUSY) { + cmd_flags |= HC_CMD_BUSY; + rpi3_sdhost_params.cmdbusy = 1; + } + + if (rpi3_sdhost_params.mmc_app_cmd) { + switch (cmd_idx) { + case MMC_ACMD(41): + if (cmd_arg == OCR_HCS) + cmd_arg |= OCR_3_3_3_4; + break; + default: + break; + } + rpi3_sdhost_params.mmc_app_cmd = 0; + } + + if (cmd_idx == MMC_CMD(55)) + rpi3_sdhost_params.mmc_app_cmd = 1; + + send_command_decorated(cmd_idx | cmd_flags, cmd_arg); + + intmask = mmio_read_32(reg_base + HC_HOSTSTATUS); + if (rpi3_sdhost_params.cmdbusy && (intmask & HC_HSTST_INT_BUSY)) { + mmio_write_32(reg_base + HC_HOSTSTATUS, HC_HSTST_INT_BUSY); + rpi3_sdhost_params.cmdbusy = 0; + } + + if (!(cmd_flags & HC_CMD_RESPONSE_NONE)) { + err = rpi3_sdhost_waitcommand(); + if (err != 0) + ERROR("rpi3_sdhost: cmd cannot be finished\n"); + } + + cmd->resp_data[0] = mmio_read_32(reg_base + HC_RESPONSE_0); + cmd->resp_data[1] = mmio_read_32(reg_base + HC_RESPONSE_1); + cmd->resp_data[2] = mmio_read_32(reg_base + HC_RESPONSE_2); + cmd->resp_data[3] = mmio_read_32(reg_base + HC_RESPONSE_3); + + if (mmio_read_32(reg_base + HC_COMMAND) & HC_CMD_FAILED) { + uint32_t sdhsts = mmio_read_32(reg_base + HC_HOSTSTATUS); + + mmio_write_32(reg_base + HC_HOSTSTATUS, + HC_HSTST_MASK_ERROR_ALL); + + /* + * If the command SEND_OP_COND returns with CRC7 error, + * it can be considered as having completed successfully. + */ + if (!(sdhsts & HC_HSTST_ERROR_CRC7) + || (cmd_idx != MMC_CMD(1))) { + if (sdhsts & HC_HSTST_TIMEOUT_CMD) { + ERROR("rpi3_sdhost: timeout status 0x%x\n", + sdhsts); + err = -(ETIMEDOUT); + } else { + ERROR("rpi3_sdhost: unknown err, cmd = 0x%x\n", + mmio_read_32(reg_base + HC_COMMAND)); + ERROR("rpi3_sdhost status: 0x%x\n", sdhsts); + err = -(EILSEQ); + } + } + } + + if ((!err) && (cmd_idx == MMC_CMD(3))) { + /* we keep the RCA in case to send MMC_CMD(55) ourselves */ + rpi3_sdhost_params.sdcard_rca = (cmd->resp_data[0] + & 0xFFFF0000U) >> 16; + } + + return err; +} + +static int rpi3_sdhost_set_clock(unsigned int clk) +{ + uintptr_t reg_base = rpi3_sdhost_params.reg_base; + uint32_t max_clk = 250000000; + uint32_t div; + + if (clk < 100000) { + mmio_write_32(reg_base + HC_CLOCKDIVISOR, + HC_CLOCKDIVISOR_MAXVAL); + return 0; + } + + div = max_clk / clk; + if (div < 2) + div = 2; + + if ((max_clk / div) > clk) + div++; + + div -= 2; + if (div > HC_CLOCKDIVISOR_MAXVAL) + div = HC_CLOCKDIVISOR_MAXVAL; + + rpi3_sdhost_params.clk_rate = max_clk / (div + 2); + rpi3_sdhost_params.ns_per_fifo_word = (1000000000 / + rpi3_sdhost_params.clk_rate) + * 8; + + mmio_write_32(reg_base + HC_CLOCKDIVISOR, div); + return 0; +} + +static int rpi3_sdhost_set_ios(unsigned int clk, unsigned int width) +{ + uintptr_t reg_base = rpi3_sdhost_params.reg_base; + uint32_t tmp1; + + rpi3_sdhost_set_clock(clk); + VERBOSE("rpi3_sdhost: Changing clock to %dHz for data mode\n", clk); + + if (width != MMC_BUS_WIDTH_4 && width != MMC_BUS_WIDTH_1) { + ERROR("rpi3_sdhost: width %d not supported\n", width); + return -(EOPNOTSUPP); + } + rpi3_sdhost_params.bus_width = width; + + tmp1 = mmio_read_32(reg_base + HC_HOSTCONFIG); + tmp1 &= ~(HC_HSTCF_EXTBUS_4BIT); + if (rpi3_sdhost_params.bus_width == MMC_BUS_WIDTH_4) + tmp1 |= HC_HSTCF_EXTBUS_4BIT; + + mmio_write_32(reg_base + HC_HOSTCONFIG, tmp1); + tmp1 = mmio_read_32(reg_base + HC_HOSTCONFIG); + mmio_write_32(reg_base + HC_HOSTCONFIG, tmp1 | + HC_HSTCF_SLOW_CARD | HC_HSTCF_INTBUS_WIDE); + + return 0; +} + +static int rpi3_sdhost_prepare(int lba, uintptr_t buf, size_t size) +{ + uintptr_t reg_base = rpi3_sdhost_params.reg_base; + size_t blocks; + size_t blocksize; + + if (size < 512) { + blocksize = size; + blocks = 1; + } else { + blocksize = 512; + blocks = size / blocksize; + if (size % blocksize != 0) + blocks++; + } + + rpi3_drain_fifo(); + + mmio_write_32(reg_base + HC_BLOCKSIZE, blocksize); + mmio_write_32(reg_base + HC_BLOCKCOUNT, blocks); + udelay(100); + return 0; +} + +static int rpi3_sdhost_read(int lba, uintptr_t buf, size_t size) +{ + int err = 0; + uint32_t *buf1 = ((uint32_t *) buf); + uintptr_t reg_base = rpi3_sdhost_params.reg_base; + int timeout = 100000; + int remaining_words = 0; + + for (int i = 0; i < size / 4; i++) { + volatile int t = timeout; + uint32_t hsts_err; + + while ((mmio_read_32(reg_base + HC_HOSTSTATUS) + & HC_HSTST_HAVEDATA) == 0) { + if (t == 0) { + ERROR("rpi3_sdhost: fifo timeout after %dus\n", + timeout); + err = -(ETIMEDOUT); + break; + } + t--; + udelay(10); + } + if (t == 0) + break; + + uint32_t data = mmio_read_32(reg_base + HC_DATAPORT); + + hsts_err = mmio_read_32(reg_base + HC_HOSTSTATUS) + & HC_HSTST_MASK_ERROR_ALL; + if (hsts_err) { + ERROR("rpi3_sdhost: transfer FIFO word %d: 0x%x\n", + i, + mmio_read_32(reg_base + HC_HOSTSTATUS)); + rpi3_sdhost_print_regs(); + + err = -(EILSEQ); + + /* clean the error status */ + mmio_write_32(reg_base + HC_HOSTSTATUS, hsts_err); + } + + if (buf1) + buf1[i] = data; + + /* speeding up if the remaining words are still a lot */ + remaining_words = (mmio_read_32(reg_base + HC_DEBUG) >> 4) + & HC_DBG_FIFO_THRESH_MASK; + if (remaining_words >= 7) + continue; + + /* delay. slowing down the read process */ + udelay(100); + } + + /* We decide to stop by ourselves. + * It is because MMC_CMD(18) -> MMC_CMD(13) -> MMC_CMD(12) + * doesn't work for RPi3 SDHost. + */ + if (rpi3_sdhost_params.current_cmd == MMC_CMD(18)) + send_command_decorated(MMC_CMD(12), 0); + + return err; +} + +static int rpi3_sdhost_write(int lba, uintptr_t buf, size_t size) +{ + uint32_t *buf1 = ((uint32_t *) buf); + uintptr_t reg_base = rpi3_sdhost_params.reg_base; + int err = 0; + int remaining_words = 0; + + for (int i = 0; i < size / 4; i++) { + uint32_t hsts_err; + uint32_t data = buf1[i]; + uint32_t dbg; + uint32_t fsm_state; + + mmio_write_32(reg_base + HC_DATAPORT, data); + + dbg = mmio_read_32(reg_base + HC_DEBUG); + fsm_state = dbg & HC_DBG_FSM_MASK; + if (fsm_state != HC_DBG_FSM_WRITEDATA + && fsm_state != HC_DBG_FSM_WRITESTART1 + && fsm_state != HC_DBG_FSM_WRITESTART2 + && fsm_state != HC_DBG_FSM_WRITECRC + && fsm_state != HC_DBG_FSM_WRITEWAIT1 + && fsm_state != HC_DBG_FSM_WRITEWAIT2) { + hsts_err = mmio_read_32(reg_base + HC_HOSTSTATUS) + & HC_HSTST_MASK_ERROR_ALL; + if (hsts_err) + err = -(EILSEQ); + } + + /* speeding up if the remaining words are not many */ + remaining_words = (mmio_read_32(reg_base + HC_DEBUG) >> 4) + & HC_DBG_FIFO_THRESH_MASK; + if (remaining_words <= 4) + continue; + + udelay(100); + } + + /* We decide to stop by ourselves. + * It is because MMC_CMD(25) -> MMC_CMD(13) -> MMC_CMD(12) + * doesn't work for RPi3 SDHost. + */ + if (rpi3_sdhost_params.current_cmd == MMC_CMD(25)) + send_command_decorated(MMC_CMD(12), 0); + + return err; +} + +void rpi3_sdhost_init(struct rpi3_sdhost_params *params, + struct mmc_device_info *mmc_dev_info) +{ + assert((params != 0) && + ((params->reg_base & MMC_BLOCK_MASK) == 0)); + + memcpy(&rpi3_sdhost_params, params, sizeof(struct rpi3_sdhost_params)); + + /* backup GPIO 48 to 53 configurations */ + for (int i = 48; i <= 53; i++) { + rpi3_sdhost_params.gpio48_pinselect[i - 48] + = rpi3_gpio_get_select(i); + VERBOSE("rpi3_sdhost: Original GPIO state %d: %d\n", + i, + rpi3_sdhost_params.gpio48_pinselect[i - 48]); + } + + /* setting pull resistors for 48 to 53. + * It is debatable to set SD_CLK to UP or NONE. We massively + * tested different brands of SD Cards and found NONE works + * most stable. + * + * GPIO 48 (SD_CLK) to GPIO_PULL_NONE + * GPIO 49 (SD_CMD) to GPIO_PULL_UP + * GPIO 50 (SD_D0) to GPIO_PULL_UP + * GPIO 51 (SD_D1) to GPIO_PULL_UP + * GPIO 52 (SD_D2) to GPIO_PULL_UP + * GPIO 53 (SD_D3) to GPIO_PULL_UP + */ + gpio_set_pull(48, GPIO_PULL_NONE); + for (int i = 49; i <= 53; i++) + gpio_set_pull(i, GPIO_PULL_UP); + + /* Set pin 48-53 to alt-0. It means route SDHOST to card slot */ + for (int i = 48; i <= 53; i++) + rpi3_gpio_set_select(i, RPI3_GPIO_FUNC_ALT0); + + mmc_init(&rpi3_sdhost_ops, params->clk_rate, params->bus_width, + params->flags, mmc_dev_info); +} + +void rpi3_sdhost_stop(void) +{ + uintptr_t reg_base = rpi3_sdhost_params.reg_base; + + VERBOSE("rpi3_sdhost: Shutting down: drain FIFO out\n"); + rpi3_drain_fifo(); + + VERBOSE("rpi3_sdhost: Shutting down: slowing down the clock\n"); + mmio_write_32(reg_base+HC_CLOCKDIVISOR, HC_CLOCKDIVISOR_SLOWVAL); + udelay(500); + + VERBOSE("rpi3_sdhost: Shutting down: put SDHost into idle state\n"); + send_command_decorated(MMC_CMD(0), 0); + udelay(500); + + mmio_write_32(reg_base + HC_COMMAND, 0); + mmio_write_32(reg_base + HC_ARGUMENT, 0); + mmio_write_32(reg_base + HC_TIMEOUTCOUNTER, HC_TIMEOUT_IDLE); + mmio_write_32(reg_base + HC_CLOCKDIVISOR, HC_CLOCKDIVISOR_STOPVAL); + + udelay(100); + + mmio_write_32(reg_base + HC_POWER, 0); + mmio_write_32(reg_base + HC_HOSTCONFIG, 0); + mmio_write_32(reg_base + HC_BLOCKSIZE, 0x400); + mmio_write_32(reg_base + HC_BLOCKCOUNT, 0); + mmio_write_32(reg_base + HC_HOSTSTATUS, 0x7f8); + + mmio_write_32(reg_base + HC_COMMAND, 0); + mmio_write_32(reg_base + HC_ARGUMENT, 0); + + udelay(100); + + /* Restore the pinmux to original state */ + for (int i = 48; i <= 53; i++) { + rpi3_gpio_set_select(i, + rpi3_sdhost_params.gpio48_pinselect[i-48]); + } + + /* Reset the pull resistors before entering BL33. + * GPIO 48 (SD_CLK) to GPIO_PULL_UP + * GPIO 49 (SD_CMD) to GPIO_PULL_UP + * GPIO 50 (SD_D0) to GPIO_PULL_UP + * GPIO 51 (SD_D1) to GPIO_PULL_UP + * GPIO 52 (SD_D2) to GPIO_PULL_UP + * GPIO 53 (SD_D3) to GPIO_PULL_UP + */ + for (int i = 48; i <= 53; i++) + gpio_set_pull(i, GPIO_PULL_UP); +} |