diff options
author | Sam Lewis <sam.vr.lewis@gmail.com> | 2020-08-19 21:13:19 +1000 |
---|---|---|
committer | Arthur Heymans <arthur@aheymans.xyz> | 2021-03-30 11:20:46 +0000 |
commit | db3fbf22c2cfd9778092ccbb3d7a97ec872a5ad0 (patch) | |
tree | 1226c2435ea0852da57882c94c71170a97bee6c9 /src/soc/ti | |
parent | cbd675173c3739b483f863aef85e02d8da95acc0 (diff) |
soc/ti/am335x: Add MMC/SD driver
Adds a driver for the am335x MMC peripheral. This has only been tested
with SD cards and probably needs some modification to use eMMC or MMC
cards.
It's also currently a little slow as it only supports reading a block at
a time.
Change-Id: I5c2b250782cddca17aa46cc8222b9aebef505fb2
Signed-off-by: Sam Lewis <sam.vr.lewis@gmail.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/44384
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Arthur Heymans <arthur@aheymans.xyz>
Diffstat (limited to 'src/soc/ti')
-rw-r--r-- | src/soc/ti/am335x/mmc.c | 283 | ||||
-rw-r--r-- | src/soc/ti/am335x/mmc.h | 50 |
2 files changed, 333 insertions, 0 deletions
diff --git a/src/soc/ti/am335x/mmc.c b/src/soc/ti/am335x/mmc.c new file mode 100644 index 0000000000..395cf4fe0b --- /dev/null +++ b/src/soc/ti/am335x/mmc.c @@ -0,0 +1,283 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include <inttypes.h> +#include <string.h> +#include <console/console.h> +#include <commonlib/sd_mmc_ctrlr.h> +#include <device/mmio.h> +#include <timer.h> +#include "mmc.h" + +#define AM335X_TIMEOUT_MSEC 1000 + +#define SYSCONFIG_SOFTRESET (0x1 << 1) + +#define SYSSTATUS_RESETDONE (0x01 << 0) + +#define CON_INIT (0x1 << 1) + +#define CMD_INDEX(x) (x << 24) +#define CMD_RSP_TYPE_NO_RESP (0x0 << 16) +#define CMD_RSP_TYPE_136B (0x1 << 16) +#define CMD_RSP_TYPE_48B (0x2 << 16) +#define CMD_RSP_TYPE_48B_BUSY (0x3 << 16) +#define CMD_DP_DATA (0x1 << 21) +#define CMD_DDIR_READ (0x1 << 4) + +#define HCTL_DTW_1BIT (0x0 << 1) +#define HCTL_SDBP (0x1 << 8) +#define HCTL_SDVS_VS30 (0x6 << 9) + +#define SYSCTL_ICE (0x1 << 0) +#define SYSCTL_ICS (0x1 << 1) +#define SYSCTL_CEN (0x1 << 2) +#define SYSCTL_DTO_15 (0xE << 16) + +#define STAT_ERRI (0x01 << 15) +#define STAT_ERROR_MASK (0xff << 15 | 0x3 << 24 | 0x03 << 28) +#define STAT_CC (0x1 << 0) + +#define IE_CC (0x1 << 0) +#define IE_TC (0x1 << 1) +#define IE_BRR (0x1 << 5) +#define IE_ERRORS (0xff << 15 | 0x3 << 24 | 0x03 << 28) + +#define CAPA_VS18 (0x01 << 26) +#define CAPA_VS30 (0x01 << 25) + +static int am335x_wait_for_reg(const void *addr, uint32_t mask, unsigned long timeout) +{ + struct mono_time current, end; + + timer_monotonic_get(¤t); + end = current; + mono_time_add_msecs(&end, timeout); + + do { + if ((read32(addr) & mask)) + return 0; + + timer_monotonic_get(¤t); + } while (!mono_time_after(¤t, &end)); + + printk(BIOS_DEBUG, "am335x MMC timeout: %ld msec\n", timeout); + return -1; +} + +static int am335x_mmc_init(struct am335x_mmc *mmc) +{ + // Follows the initialisiation from the AM335X technical reference manual + setbits32(&mmc->sysconfig, SYSCONFIG_SOFTRESET); + + if (am335x_wait_for_reg(&mmc->sysstatus, SYSSTATUS_RESETDONE, AM335X_TIMEOUT_MSEC)) + return -1; + + setbits32(&mmc->capa, CAPA_VS30); + setbits32(&mmc->hctl, HCTL_SDVS_VS30 | HCTL_DTW_1BIT); + setbits32(&mmc->hctl, HCTL_SDBP); + + if (am335x_wait_for_reg(&mmc->hctl, HCTL_SDBP, AM335X_TIMEOUT_MSEC)) + return -1; + + // Assumes the default input clock speed of 96MHz to set a minimum SD + // speed of 400 KHz + write32(&mmc->sysctl, read32(&mmc->sysctl) | 240 << 6 | SYSCTL_DTO_15); + + setbits32(&mmc->sysctl, SYSCTL_ICE | SYSCTL_CEN); + + if (am335x_wait_for_reg(&mmc->sysctl, SYSCTL_ICS, AM335X_TIMEOUT_MSEC)) + return -1; + + write32(&mmc->ie, IE_ERRORS | IE_TC | IE_CC); + + // Clear interrupts + write32(&mmc->stat, 0xffffffffu); + + setbits32(&mmc->con, CON_INIT); + write32(&mmc->cmd, 0x00); + + if (am335x_wait_for_reg(&mmc->stat, STAT_CC, AM335X_TIMEOUT_MSEC)) + return -1; + + write32(&mmc->stat, 0xffffffffu); + clrbits32(&mmc->con, CON_INIT); + + return 0; +} + +static int am335x_send_cmd(struct sd_mmc_ctrlr *ctrlr, struct mmc_command *cmd, + struct mmc_data *data) +{ + struct am335x_mmc_host *mmc; + struct am335x_mmc *reg; + + mmc = container_of(ctrlr, struct am335x_mmc_host, sd_mmc_ctrlr); + reg = mmc->reg; + + if (read32(®->stat)) { + printk(BIOS_WARNING, "AM335X MMC: Interrupt already raised\n"); + return 1; + } + + uint32_t transfer_type = 0; + + if (data) { + if (data->flags & DATA_FLAG_READ) { + setbits32(&mmc->reg->ie, IE_BRR); + write32(&mmc->reg->blk, data->blocksize); + transfer_type |= CMD_DP_DATA | CMD_DDIR_READ; + } + + if (data->flags & DATA_FLAG_WRITE) { + printk(BIOS_ERR, "AM335X MMC: Writes currently not supported\n"); + return 1; + } + } + + switch (cmd->resp_type) { + case CARD_RSP_R1b: + transfer_type |= CMD_RSP_TYPE_48B_BUSY; + break; + case CARD_RSP_R1: + case CARD_RSP_R3: + transfer_type |= CMD_RSP_TYPE_48B; + break; + case CARD_RSP_R2: + transfer_type |= CMD_RSP_TYPE_136B; + break; + case CARD_RSP_NONE: + transfer_type |= CMD_RSP_TYPE_NO_RESP; + break; + default: + printk(BIOS_ERR, "AM335X MMC: Unknown response type\n"); + return 1; + } + + if (cmd->cmdidx == MMC_CMD_SET_BLOCKLEN) { + // todo: Support bigger blocks for faster transfers + return 0; + } + + write32(®->arg, cmd->cmdarg); + write32(®->cmd, CMD_INDEX(cmd->cmdidx) | transfer_type); + + // Wait for any interrupt + if (am335x_wait_for_reg(®->stat, 0xffffffff, AM335X_TIMEOUT_MSEC)) + return -1; + + // Check to ensure that there was not any errors + if (read32(®->stat) & STAT_ERRI) { + printk(BIOS_WARNING, "AM335X MMC: Error while reading %08x\n", + read32(®->stat)); + + // Clear the errors + write32(®->stat, STAT_ERROR_MASK); + return 1; + } + + if (cmd->resp_type == CARD_RSP_R1b) { + if (am335x_wait_for_reg(®->stat, IE_TC, AM335X_TIMEOUT_MSEC)) + return -1; + + write32(®->stat, IE_TC); + } + + write32(®->stat, IE_CC); + + switch (cmd->resp_type) { + case CARD_RSP_R1: + case CARD_RSP_R1b: + case CARD_RSP_R3: + cmd->response[0] = read32(®->rsp10); + break; + case CARD_RSP_R2: + cmd->response[3] = read32(®->rsp10); + cmd->response[2] = read32(®->rsp32); + cmd->response[1] = read32(®->rsp54); + cmd->response[0] = read32(®->rsp76); + break; + case CARD_RSP_NONE: + break; + } + + if (data != NULL && data->flags & DATA_FLAG_READ) { + if (am335x_wait_for_reg(®->stat, IE_BRR, AM335X_TIMEOUT_MSEC)) + return -1; + + uint32_t *dest32 = (uint32_t *)data->dest; + + for (int count = 0; count < data->blocksize; count += 4) { + *dest32 = read32(®->data); + dest32++; + } + + write32(®->stat, IE_TC); + write32(®->stat, IE_BRR); + clrbits32(®->ie, IE_BRR); + } + + return 0; +} + +static void set_ios(struct sd_mmc_ctrlr *ctrlr) +{ + struct am335x_mmc_host *mmc; + struct am335x_mmc *reg; + + mmc = container_of(ctrlr, struct am335x_mmc_host, sd_mmc_ctrlr); + reg = mmc->reg; + + if (ctrlr->request_hz != ctrlr->bus_hz) { + uint32_t requested_hz = ctrlr->request_hz; + + requested_hz = MIN(requested_hz, ctrlr->f_min); + requested_hz = MAX(requested_hz, ctrlr->f_max); + + uint32_t divisor = mmc->sd_clock_hz / requested_hz; + uint32_t actual = mmc->sd_clock_hz * divisor; + + if (actual != ctrlr->bus_hz) { + clrbits32(®->sysctl, SYSCTL_CEN); + + uint32_t new_sysctl = read32(®->sysctl); + new_sysctl &= ~(0x3ff << 6); + new_sysctl |= ((divisor & 0x3ff) << 6); + + write32(®->sysctl, new_sysctl); + + // Wait for clock stability + am335x_wait_for_reg(®->sysctl, SYSCTL_ICS, AM335X_TIMEOUT_MSEC); + + setbits32(®->sysctl, SYSCTL_CEN); + + ctrlr->bus_hz = mmc->sd_clock_hz / divisor; + } + } +} + +int am335x_mmc_init_storage(struct am335x_mmc_host *mmc_host) +{ + int err = 0; + + struct sd_mmc_ctrlr *mmc_ctrlr = &mmc_host->sd_mmc_ctrlr; + memset(mmc_ctrlr, 0, sizeof(mmc_ctrlr)); + + + err = am335x_mmc_init(mmc_host->reg); + if (err != 0) { + printk(BIOS_ERR, "ERROR: Initialising AM335X SD failed.\n"); + return err; + } + + mmc_ctrlr->send_cmd = &am335x_send_cmd; + mmc_ctrlr->set_ios = &set_ios; + + mmc_ctrlr->voltages = MMC_VDD_30_31; + mmc_ctrlr->b_max = 1; + mmc_ctrlr->bus_width = 1; + mmc_ctrlr->f_max = 48000000; + mmc_ctrlr->f_min = 400000; + mmc_ctrlr->bus_hz = 400000; + + return 0; +} diff --git a/src/soc/ti/am335x/mmc.h b/src/soc/ti/am335x/mmc.h new file mode 100644 index 0000000000..757795d313 --- /dev/null +++ b/src/soc/ti/am335x/mmc.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef AM335X_MMC_H +#define AM335X_MMC_H + +#include <inttypes.h> +#include <commonlib/sd_mmc_ctrlr.h> + +#define MMCHS0_BASE 0x48060000 + +struct am335x_mmc { + uint8_t res1[0x110]; + uint32_t sysconfig; + uint32_t sysstatus; + uint8_t res2[0x14]; + uint32_t con; + uint32_t pwcnt; + uint32_t dll; + uint8_t res3[0xcc]; + uint32_t blk; + uint32_t arg; + uint32_t cmd; + uint32_t rsp10; + uint32_t rsp32; + uint32_t rsp54; + uint32_t rsp76; + uint32_t data; + uint32_t pstate; + uint32_t hctl; + uint32_t sysctl; + uint32_t stat; + uint32_t ie; + uint8_t res4[0x4]; + uint32_t ac12; + uint32_t capa; + uint32_t capa2; + uint8_t res5[0xc]; + uint32_t admaes; + uint32_t admasal; +} __packed; + +struct am335x_mmc_host { + struct sd_mmc_ctrlr sd_mmc_ctrlr; + struct am335x_mmc *reg; + uint32_t sd_clock_hz; +}; + +int am335x_mmc_init_storage(struct am335x_mmc_host *mmc_host); + +#endif |