circuitpython/ports/broadcom/common-hal/sdioio/SDCard.c
2022-05-27 12:59:54 -07:00

402 lines
14 KiB
C

/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2020 Jeff Epler for Adafruit Industries
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <stdbool.h>
#include "mphalport.h"
#include "py/mperrno.h"
#include "py/runtime.h"
#include "common-hal/microcontroller/Pin.h"
#include "shared-bindings/sdioio/SDCard.h"
#include "shared-bindings/microcontroller/Pin.h"
#include "shared-bindings/microcontroller/__init__.h"
#include "supervisor/port.h"
#include "supervisor/shared/translate/translate.h"
#include "peripherals/broadcom/cpu.h"
#include "peripherals/broadcom/defines.h"
#include "peripherals/broadcom/gpio.h"
#include "lib/sdmmc/include/sdmmc_cmd.h"
void osal_task_delay(uint32_t msec) {
mp_hal_delay_ms(msec);
}
/*!< Host function to initialize the driver */
STATIC sdmmc_err_t _init(void) {
return SDMMC_OK;
}
/*!< host function to set bus width */
STATIC sdmmc_err_t _set_bus_width(int slot, size_t width) {
if (width == 4) {
EMMC->CONTROL0_b.HCTL_DWIDTH = true;
} else if (width == 8) {
EMMC->CONTROL0_b.HCTL_8BIT = true;
}
return SDMMC_OK;
}
/*!< host function to get the maximum bus width of a particular slot */
STATIC size_t _get_bus_width(int slot) {
return 4;
}
/*!< host function to set card clock frequency */
STATIC sdmmc_err_t _set_card_clk(int slot, uint32_t freq_khz) {
uint32_t base_clock = 125000000;
uint32_t frequency = freq_khz * 1000;
uint64_t start_ticks = port_get_raw_ticks(NULL);
while ((EMMC->STATUS_b.CMD_INHIBIT || EMMC->STATUS_b.DAT_INHIBIT) && (port_get_raw_ticks(NULL) - start_ticks) < 1024) {
}
if (EMMC->STATUS_b.CMD_INHIBIT || EMMC->STATUS_b.DAT_INHIBIT) {
return SDMMC_ERR_TIMEOUT;
}
EMMC->CONTROL1_b.CLK_EN = false;
uint32_t divisor = base_clock / frequency;
// + 1 to ensure we round the frequency down.
if (base_clock % frequency > 0) {
divisor += 1;
}
// Only even divisors are supported
uint32_t register_value = divisor / 2;
if (divisor % 2 == 1) {
register_value += 1;
}
// The divisor is stored with bits swapped.
EMMC->CONTROL1_b.CLK_FREQ8 = divisor & 0xff;
EMMC->CONTROL1_b.CLK_FREQ_MS2 = (divisor >> 8) & 0x3;
EMMC->CONTROL1_b.CLK_EN = true;
start_ticks = port_get_raw_ticks(NULL);
while (!EMMC->CONTROL1_b.CLK_STABLE && (port_get_raw_ticks(NULL) - start_ticks) < 1024) {
}
if (!EMMC->CONTROL1_b.CLK_STABLE) {
return SDMMC_ERR_TIMEOUT;
}
return SDMMC_OK;
}
/*!< host function to do a transaction */
STATIC sdmmc_err_t _do_transaction(int slot, sdmmc_command_t *cmdinfo) {
if (EMMC->STATUS_b.CMD_INHIBIT) {
return SDMMC_ERR_BUSY;
}
// Clear interrupts and then send the command
EMMC->INTERRUPT = 0xffffffff;
EMMC->ARG1 = cmdinfo->arg;
uint32_t cmd_flags = 0;
bool read = (cmdinfo->flags & SCF_CMD_READ) != 0;
if (SCF_CMD(cmdinfo->flags) == SCF_CMD_ADTC ||
SCF_CMD(cmdinfo->flags) == (SCF_CMD_ADTC | SCF_CMD_READ)) {
if (EMMC->STATUS_b.DAT_INHIBIT) {
return SDMMC_ERR_BUSY;
}
cmd_flags = EMMC_CMDTM_TM_BLKCNT_EN_Msk | EMMC_CMDTM_CMD_ISDATA_Msk;
if (cmdinfo->datalen > cmdinfo->blklen) {
cmd_flags |= EMMC_CMDTM_TM_MULTI_BLOCK_Msk;
if ((cmdinfo->flags & SCF_AUTO_STOP) != 0) {
cmd_flags |= 1 << EMMC_CMDTM_TM_AUTO_CMD_EN_Pos;
}
}
if (read) {
cmd_flags |= EMMC_CMDTM_TM_DAT_DIR_Msk;
}
EMMC->BLKSIZECNT = (cmdinfo->datalen / cmdinfo->blklen) << EMMC_BLKSIZECNT_BLKCNT_Pos |
cmdinfo->blklen << EMMC_BLKSIZECNT_BLKSIZE_Pos;
}
uint32_t response_type = EMMC_CMDTM_CMD_RSPNS_TYPE_RESPONSE_48BITS;
uint32_t crc = 0;
if ((cmdinfo->flags & SCF_RSP_CRC) != 0) {
crc |= EMMC_CMDTM_CMD_CRCCHK_EN_Msk;
}
if ((cmdinfo->flags & SCF_RSP_IDX) != 0) {
crc |= EMMC_CMDTM_CMD_IXCHK_EN_Msk;
}
if ((cmdinfo->flags & SCF_RSP_136) != 0) {
response_type = EMMC_CMDTM_CMD_RSPNS_TYPE_RESPONSE_136BITS;
} else if ((cmdinfo->flags & SCF_RSP_BSY) != 0) {
response_type = EMMC_CMDTM_CMD_RSPNS_TYPE_RESPONSE_48BITS_USING_BUSY;
} else if ((cmdinfo->flags & SCF_RSP_PRESENT) == 0) {
response_type = EMMC_CMDTM_CMD_RSPNS_TYPE_RESPONSE_NONE;
}
uint32_t full_cmd = cmd_flags | crc |
cmdinfo->opcode << EMMC_CMDTM_CMD_INDEX_Pos |
response_type << EMMC_CMDTM_CMD_RSPNS_TYPE_Pos;
EMMC->CMDTM = full_cmd;
// Wait for an interrupt to indicate completion of the command.
uint64_t start_ticks = port_get_raw_ticks(NULL);
while (EMMC->INTERRUPT == 0 && (port_get_raw_ticks(NULL) - start_ticks) < (size_t)cmdinfo->timeout_ms) {
}
if (!EMMC->INTERRUPT_b.CMD_DONE) {
cmdinfo->response[0] = 0;
// Reset the command circuit
EMMC->CONTROL1_b.SRST_CMD = true;
// Wait for the bit to clear
start_ticks = port_get_raw_ticks(NULL);
while (EMMC->CONTROL1_b.SRST_CMD && (port_get_raw_ticks(NULL) - start_ticks) < (size_t)cmdinfo->timeout_ms) {
}
return SDMMC_ERR_TIMEOUT;
} else {
EMMC->INTERRUPT = EMMC_INTERRUPT_CMD_DONE_Msk;
}
// Transfer the data.
// TODO: Use DMA
if (cmd_flags != 0) {
if (read) {
for (size_t i = 0; i < cmdinfo->datalen / sizeof(uint32_t); i++) {
start_ticks = port_get_raw_ticks(NULL);
while (!EMMC->INTERRUPT_b.READ_RDY && (port_get_raw_ticks(NULL) - start_ticks) < (size_t)cmdinfo->timeout_ms) {
}
if (!EMMC->INTERRUPT_b.READ_RDY) {
return SDMMC_ERR_TIMEOUT;
}
((uint32_t *)cmdinfo->data)[i] = EMMC->DATA;
}
} else {
for (size_t i = 0; i < cmdinfo->datalen / sizeof(uint32_t); i++) {
start_ticks = port_get_raw_ticks(NULL);
while (!EMMC->INTERRUPT_b.WRITE_RDY && (port_get_raw_ticks(NULL) - start_ticks) < (size_t)cmdinfo->timeout_ms) {
}
if (!EMMC->INTERRUPT_b.WRITE_RDY) {
return SDMMC_ERR_TIMEOUT;
}
EMMC->DATA = ((uint32_t *)cmdinfo->data)[i];
}
}
uint32_t data_done_mask = EMMC_INTERRUPT_ERR_Msk | EMMC_INTERRUPT_DATA_DONE_Msk;
start_ticks = port_get_raw_ticks(NULL);
while ((EMMC->INTERRUPT & data_done_mask) == 0 && (port_get_raw_ticks(NULL) - start_ticks) < (size_t)cmdinfo->timeout_ms) {
}
if (!EMMC->INTERRUPT_b.DATA_DONE) {
cmdinfo->response[0] = 0;
// Reset the command circuit
EMMC->CONTROL1_b.SRST_DATA = true;
// Wait for the bit to clear
start_ticks = port_get_raw_ticks(NULL);
while (EMMC->CONTROL1_b.SRST_DATA && (port_get_raw_ticks(NULL) - start_ticks) < (size_t)cmdinfo->timeout_ms) {
}
return SDMMC_ERR_TIMEOUT;
}
}
cmdinfo->response[0] = EMMC->RESP0;
if (response_type == EMMC_CMDTM_CMD_RSPNS_TYPE_RESPONSE_136BITS) {
// Rotate one more byte for some reason.
cmdinfo->response[3] = EMMC->RESP3 << 8 | ((EMMC->RESP2 >> 24) & 0xff);
cmdinfo->response[2] = EMMC->RESP2 << 8 | ((EMMC->RESP1 >> 24) & 0xff);
cmdinfo->response[1] = EMMC->RESP1 << 8 | ((EMMC->RESP0 >> 24) & 0xff);
cmdinfo->response[0] <<= 8;
}
return SDMMC_OK;
}
/*!< host function to deinitialize the driver, called with the `slot` */
STATIC sdmmc_err_t _deinit(int slot) {
return SDMMC_OK;
}
void common_hal_sdioio_sdcard_construct(sdioio_sdcard_obj_t *self,
const mcu_pin_obj_t *clock, const mcu_pin_obj_t *command,
uint8_t num_data, const mcu_pin_obj_t **data, uint32_t frequency) {
if (clock != NULL) {
gpio_set_function(clock->number, GPIO_GPFSEL4_FSEL48_SD1_CLK);
gpio_set_pull(clock->number, BP_PULL_NONE);
self->clock_pin = clock->number;
gpio_set_function(command->number, GPIO_GPFSEL4_FSEL49_SD1_CMD);
gpio_set_pull(command->number, BP_PULL_UP);
self->command_pin = command->number;
gpio_set_function(data[0]->number, GPIO_GPFSEL5_FSEL50_SD1_DAT0);
gpio_set_pull(data[0]->number, BP_PULL_UP);
self->data_pins[0] = data[0]->number;
gpio_set_function(data[1]->number, GPIO_GPFSEL5_FSEL51_SD1_DAT1);
gpio_set_pull(data[1]->number, BP_PULL_UP);
self->data_pins[1] = data[1]->number;
gpio_set_function(data[2]->number, GPIO_GPFSEL5_FSEL52_SD1_DAT2);
gpio_set_pull(data[2]->number, BP_PULL_UP);
self->data_pins[2] = data[2]->number;
gpio_set_function(data[3]->number, GPIO_GPFSEL5_FSEL53_SD1_DAT3);
gpio_set_pull(data[3]->number, BP_PULL_UP);
self->data_pins[3] = data[3]->number;
} else {
// Switch the sdcard to use the old arasan interface.
GPIO->EXTRA_MUX_b.SDIO = GPIO_EXTRA_MUX_SDIO_ARASAN;
}
COMPLETE_MEMORY_READS;
self->host_info = (sdmmc_host_t) {
.flags = SDMMC_HOST_FLAG_1BIT | SDMMC_HOST_FLAG_4BIT | SDMMC_HOST_FLAG_DEINIT_ARG,
.slot = 0,
.max_freq_khz = SDMMC_FREQ_HIGHSPEED,
.io_voltage = 3.3f,
.command_timeout_ms = 0,
.init = _init,
.set_bus_width = _set_bus_width,
.get_bus_width = _get_bus_width,
.set_bus_ddr_mode = NULL,
.set_card_clk = _set_card_clk,
.do_transaction = _do_transaction,
.deinit_p = _deinit,
};
// Reset the peripheral.
EMMC->CONTROL0 = 0;
EMMC->CONTROL1_b.SRST_HC = true;
// Wait for the bit to clear
while (EMMC->CONTROL1_b.SRST_HC) {
}
// Set max timeout
EMMC->CONTROL1 |= EMMC_CONTROL1_CLK_INTLEN_Msk | (0xe << EMMC_CONTROL1_DATA_TOUNIT_Pos);
EMMC->IRPT_MASK = 0xffffffff;
// Start clocking the card.
_set_card_clk(0, 400);
sdmmc_err_t err = SDMMC_ERR_INVALID_RESPONSE;
size_t tries = 3;
while (err == SDMMC_ERR_INVALID_RESPONSE && tries > 0) {
err = sdmmc_card_init(&self->host_info, &self->card_info);
if (err != SDMMC_OK) {
mp_printf(&mp_plat_print, "SD card init failed %d\n", err);
} else if (tries < 3) {
mp_printf(&mp_plat_print, "SD card init success\n");
}
tries--;
}
self->init = err == SDMMC_OK;
self->capacity = self->card_info.csd.capacity;
COMPLETE_MEMORY_READS;
}
uint32_t common_hal_sdioio_sdcard_get_count(sdioio_sdcard_obj_t *self) {
return self->capacity;
}
uint32_t common_hal_sdioio_sdcard_get_frequency(sdioio_sdcard_obj_t *self) {
return self->frequency;
}
uint8_t common_hal_sdioio_sdcard_get_width(sdioio_sdcard_obj_t *self) {
return self->num_data;
}
STATIC void check_whole_block(mp_buffer_info_t *bufinfo) {
if (bufinfo->len % 512) {
mp_raise_ValueError(translate("Buffer length must be a multiple of 512"));
}
}
int common_hal_sdioio_sdcard_writeblocks(sdioio_sdcard_obj_t *self, uint32_t start_block, mp_buffer_info_t *bufinfo) {
if (!self->init) {
return -EIO;
}
check_whole_block(bufinfo);
self->state_programming = true;
COMPLETE_MEMORY_READS;
sdmmc_err_t error = sdmmc_write_sectors(&self->card_info, bufinfo->buf,
start_block, bufinfo->len / 512);
COMPLETE_MEMORY_READS;
if (error != SDMMC_OK) {
mp_printf(&mp_plat_print, "write sectors result %d\n", error);
return -EIO;
}
return 0;
}
int common_hal_sdioio_sdcard_readblocks(sdioio_sdcard_obj_t *self, uint32_t start_block, mp_buffer_info_t *bufinfo) {
if (!self->init) {
return -EIO;
}
check_whole_block(bufinfo);
COMPLETE_MEMORY_READS;
sdmmc_err_t error = sdmmc_read_sectors(&self->card_info, bufinfo->buf,
start_block, bufinfo->len / 512);
COMPLETE_MEMORY_READS;
if (error != SDMMC_OK) {
mp_printf(&mp_plat_print, "read sectors result %d when reading block %d for %d\n", error, start_block, bufinfo->len / 512);
return -EIO;
}
return 0;
}
bool common_hal_sdioio_sdcard_configure(sdioio_sdcard_obj_t *self, uint32_t frequency, uint8_t bits) {
if (!self->init) {
return false;
}
return true;
}
bool common_hal_sdioio_sdcard_deinited(sdioio_sdcard_obj_t *self) {
return self->command_pin == COMMON_HAL_MCU_NO_PIN;
}
void common_hal_sdioio_sdcard_deinit(sdioio_sdcard_obj_t *self) {
reset_pin_number(self->command_pin);
reset_pin_number(self->clock_pin);
reset_pin_number(self->data_pins[0]);
reset_pin_number(self->data_pins[1]);
reset_pin_number(self->data_pins[2]);
reset_pin_number(self->data_pins[3]);
self->command_pin = COMMON_HAL_MCU_NO_PIN;
self->clock_pin = COMMON_HAL_MCU_NO_PIN;
self->data_pins[0] = COMMON_HAL_MCU_NO_PIN;
self->data_pins[1] = COMMON_HAL_MCU_NO_PIN;
self->data_pins[2] = COMMON_HAL_MCU_NO_PIN;
self->data_pins[3] = COMMON_HAL_MCU_NO_PIN;
self->init = false;
}
void common_hal_sdioio_sdcard_never_reset(sdioio_sdcard_obj_t *self) {
never_reset_pin_number(self->command_pin);
never_reset_pin_number(self->clock_pin);
never_reset_pin_number(self->data_pins[0]);
never_reset_pin_number(self->data_pins[1]);
never_reset_pin_number(self->data_pins[2]);
never_reset_pin_number(self->data_pins[3]);
}