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>
diff --git a/src/soc/ti/am335x/mmc.c b/src/soc/ti/am335x/mmc.c
new file mode 100644
index 0000000..395cf4f
--- /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 0000000..757795d
--- /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