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>
This commit is contained in:
parent
cbd675173c
commit
db3fbf22c2
|
@ -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;
|
||||
}
|
|
@ -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
|
Loading…
Reference in New Issue