diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Kconfig | 2 | ||||
-rw-r--r-- | src/Makefile | 1 | ||||
-rw-r--r-- | src/sdiocmds.c | 166 | ||||
-rw-r--r-- | src/sdiocmds.h | 8 | ||||
-rw-r--r-- | src/stm32/Kconfig | 1 | ||||
-rw-r--r-- | src/stm32/Makefile | 2 | ||||
-rw-r--r-- | src/stm32/gpioperiph.c | 7 | ||||
-rw-r--r-- | src/stm32/internal.h | 1 | ||||
-rw-r--r-- | src/stm32/sdio.c | 414 | ||||
-rw-r--r-- | src/stm32/sdio.h | 25 | ||||
-rw-r--r-- | src/stm32/stm32f4.c | 12 |
11 files changed, 634 insertions, 5 deletions
diff --git a/src/Kconfig b/src/Kconfig index 936e3410..283639b7 100644 --- a/src/Kconfig +++ b/src/Kconfig @@ -115,6 +115,8 @@ config HAVE_GPIO_ADC bool config HAVE_GPIO_SPI bool +config HAVE_GPIO_SDIO + bool config HAVE_GPIO_I2C bool config HAVE_GPIO_HARD_PWM diff --git a/src/Makefile b/src/Makefile index f5c32f1a..dc0427c7 100644 --- a/src/Makefile +++ b/src/Makefile @@ -5,6 +5,7 @@ src-$(CONFIG_HAVE_GPIO) += initial_pins.c gpiocmds.c stepper.c endstop.c \ trsync.c src-$(CONFIG_HAVE_GPIO_ADC) += adccmds.c src-$(CONFIG_HAVE_GPIO_SPI) += spicmds.c thermocouple.c +src-$(CONFIG_HAVE_GPIO_SDIO) += sdiocmds.c src-$(CONFIG_HAVE_GPIO_I2C) += i2ccmds.c src-$(CONFIG_HAVE_GPIO_HARD_PWM) += pwmcmds.c bb-src-$(CONFIG_HAVE_GPIO_SPI) := spi_software.c sensor_adxl345.c sensor_angle.c diff --git a/src/sdiocmds.c b/src/sdiocmds.c new file mode 100644 index 00000000..d9c020b4 --- /dev/null +++ b/src/sdiocmds.c @@ -0,0 +1,166 @@ +// Commands for sending messages on an SDIO bus +// +// Copyright (C) 2022 H. Gregor Molter <gregor.molter@secretlab.de> +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include <string.h> // memcpy +#include "board/gpio.h" // gpio_in_setup +#include "board/sdio.h" // sdio_setup +#include "basecmd.h" // oid_alloc +#include "command.h" // DECL_COMMAND +#include "sched.h" // DECL_SHUTDOWN + +struct sdiodev_s { + struct sdio_config sdio_config; + uint32_t blocksize; + uint32_t speed; + uint8_t data_buffer[4096]; +}; + +#define TIMEOUT_MSEC 500 + +void +command_config_sdio(uint32_t *args) +{ + struct sdiodev_s *sdio = oid_alloc(args[0], command_config_sdio + , sizeof(*sdio)); + sdio->blocksize = args[1]; + sdio->speed = 400000; // Initial speed set to ~400khz +} +DECL_COMMAND(command_config_sdio, "config_sdio oid=%c blocksize=%u"); + +struct sdiodev_s * +sdiodev_oid_lookup(uint8_t oid) +{ + return oid_lookup(oid, command_config_sdio); +} + +void +command_sdio_set_bus(uint32_t *args) +{ + struct sdiodev_s *sdio = sdiodev_oid_lookup(args[0]); + sdio->sdio_config = sdio_setup(args[1]); +} +DECL_COMMAND(command_sdio_set_bus, "sdio_set_bus oid=%c sdio_bus=%u"); + +void +command_sdio_set_speed(uint32_t *args) +{ + struct sdiodev_s *sdio = sdiodev_oid_lookup(args[0]); + sdio->speed = args[1]; + sdio_set_speed(sdio->sdio_config, sdio->speed); +} +DECL_COMMAND(command_sdio_set_speed, "sdio_set_speed oid=%c speed=%u"); + +void +command_sdio_send_command(uint32_t *args) +{ + uint8_t oid = args[0]; + uint8_t cmd = args[1]; + uint32_t argument = args[2]; + uint8_t wait = args[3]; + struct sdiodev_s *sdio = sdiodev_oid_lookup(oid); + uint8_t response[16]; + uint8_t response_len = 0; + uint8_t err = sdio_send_command(sdio->sdio_config, cmd, argument, wait + , response, &response_len); + sendf("sdio_send_command_response oid=%c error=%c response=%*s" + , oid, err, response_len, response); +} +DECL_COMMAND(command_sdio_send_command + , "sdio_send_command oid=%c cmd=%c argument=%u wait=%c"); + +void +command_sdio_read_data(uint32_t *args) +{ + uint8_t oid = args[0]; + uint8_t cmd = args[1]; + uint32_t argument = args[2]; + uint32_t data_len = 0; + struct sdiodev_s *sdio = sdiodev_oid_lookup(oid); + uint32_t timeout = TIMEOUT_MSEC*sdio->speed/1000; + uint8_t err = sdio_prepare_data_transfer(sdio->sdio_config, 1, 1 + , sdio->blocksize, timeout); + if (err == 0) { + err = sdio_send_command(sdio->sdio_config, cmd, argument + , 1, NULL, NULL); + if (err == 0) { + data_len = sdio->blocksize; + if (data_len <= sizeof(sdio->data_buffer)) { + err = sdio_read_data(sdio->sdio_config, sdio->data_buffer + , 1, sdio->blocksize); + } else { + data_len = 0; + } + } + } + sendf("sdio_read_data_response oid=%c error=%c read=%u" + , oid, err, data_len); +} +DECL_COMMAND(command_sdio_read_data + , "sdio_read_data oid=%c cmd=%c argument=%u"); + +void +command_sdio_write_data(uint32_t *args) +{ + uint8_t oid = args[0]; + uint8_t cmd = args[1]; + uint32_t argument = args[2]; + uint32_t data_len = 0; + struct sdiodev_s *sdio = sdiodev_oid_lookup(oid); + uint32_t timeout = TIMEOUT_MSEC*sdio->speed/1000; + uint8_t err = sdio_prepare_data_transfer(sdio->sdio_config, 0, 1 + , sdio->blocksize, timeout); + if (err == 0) { + err = sdio_send_command(sdio->sdio_config, cmd, argument + , 1, NULL, NULL); + if (err == 0) { + data_len = sdio->blocksize; + if (data_len <= sizeof(sdio->data_buffer)) { + err = sdio_write_data(sdio->sdio_config, sdio->data_buffer + , 1, sdio->blocksize); + } else { + data_len = 0; + } + } + } + sendf("sdio_write_data_response oid=%c error=%c write=%u" + , oid, err, data_len); +} +DECL_COMMAND(command_sdio_write_data + , "sdio_write_data oid=%c cmd=%c argument=%u"); + +void +command_sdio_read_data_buffer(uint32_t *args) +{ + uint8_t oid = args[0]; + uint32_t offset = args[1]; + uint8_t len = args[2]; + struct sdiodev_s *sdio = sdiodev_oid_lookup(oid); + uint8_t *buf = &(sdio->data_buffer[offset]); + + if (offset + len > sizeof(sdio->data_buffer)) { + len = 0; + } + sendf("sdio_read_data_buffer_response oid=%c data=%*s", oid, len, buf); +} +DECL_COMMAND(command_sdio_read_data_buffer + , "sdio_read_data_buffer oid=%c offset=%u len=%c"); + +void +command_sdio_write_data_buffer(uint32_t *args) +{ + uint8_t oid = args[0]; + uint32_t offset = args[1]; + uint8_t write_data_len = args[2]; + uint8_t *write_data = command_decode_ptr(args[3]); + struct sdiodev_s *sdio = sdiodev_oid_lookup(oid); + uint8_t *buf = &(sdio->data_buffer[offset]); + + if (offset + write_data_len <= sizeof(sdio->data_buffer)) { + memcpy(buf, write_data, write_data_len); + } +} +DECL_COMMAND(command_sdio_write_data_buffer + , "sdio_write_data_buffer oid=%c offset=%u data=%*s"); diff --git a/src/sdiocmds.h b/src/sdiocmds.h new file mode 100644 index 00000000..24ec9811 --- /dev/null +++ b/src/sdiocmds.h @@ -0,0 +1,8 @@ +#ifndef __SDIOCMDS_H +#define __SDIOCMDS_H + +#include <stdint.h> // uint8_t + +struct sdiodev_s *sdiodev_oid_lookup(uint8_t oid); + +#endif // sdiocmds.h diff --git a/src/stm32/Kconfig b/src/stm32/Kconfig index 0597f63d..50db29d2 100644 --- a/src/stm32/Kconfig +++ b/src/stm32/Kconfig @@ -9,6 +9,7 @@ config STM32_SELECT select HAVE_GPIO_ADC select HAVE_GPIO_I2C if !(MACH_STM32F031 || MACH_STM32H7) select HAVE_GPIO_SPI if !MACH_STM32F031 + select HAVE_GPIO_SDIO if MACH_STM32F4 select HAVE_GPIO_HARD_PWM if MACH_STM32F1 || MACH_STM32F4 || MACH_STM32G0 || MACH_STM32H7 select HAVE_GPIO_BITBANGING if !MACH_STM32F031 select HAVE_STRICT_TIMING diff --git a/src/stm32/Makefile b/src/stm32/Makefile index 15ea4e2b..390ad8d5 100644 --- a/src/stm32/Makefile +++ b/src/stm32/Makefile @@ -64,6 +64,8 @@ src-$(CONFIG_MACH_STM32L4) += stm32/stm32h7_adc.c stm32/stm32f0_i2c.c spi-src-y := stm32/spi.c spi-src-$(CONFIG_MACH_STM32H7) := stm32/stm32h7_spi.c src-$(CONFIG_HAVE_GPIO_SPI) += $(spi-src-y) +sdio-src-y := stm32/sdio.c +src-$(CONFIG_HAVE_GPIO_SDIO) += $(sdio-src-y) usb-src-$(CONFIG_HAVE_STM32_USBFS) := stm32/usbfs.c usb-src-$(CONFIG_HAVE_STM32_USBOTG) := stm32/usbotg.c src-$(CONFIG_USBSERIAL) += $(usb-src-y) stm32/chipid.c generic/usb_cdc.c diff --git a/src/stm32/gpioperiph.c b/src/stm32/gpioperiph.c index 06cdaa05..ef421c77 100644 --- a/src/stm32/gpioperiph.c +++ b/src/stm32/gpioperiph.c @@ -6,7 +6,7 @@ #include "internal.h" // gpio_peripheral -// Set the mode and extended function of a pin +// Set the mode, extended function and speed of a pin void gpio_peripheral(uint32_t gpio, uint32_t mode, int pullup) { @@ -16,7 +16,8 @@ gpio_peripheral(uint32_t gpio, uint32_t mode, int pullup) gpio_clock_enable(regs); // Configure GPIO - uint32_t mode_bits = mode & 0xf, func = (mode >> 4) & 0xf, od = mode >> 8; + uint32_t mode_bits = mode & 0xf, func = (mode >> 4) & 0xf; + uint32_t od = (mode >> 8) & 0x1, hs = (mode >> 9) & 0x1; uint32_t pup = pullup ? (pullup > 0 ? 1 : 2) : 0; uint32_t pos = gpio % 16, af_reg = pos / 8; uint32_t af_shift = (pos % 8) * 4, af_msk = 0x0f << af_shift; @@ -33,6 +34,6 @@ gpio_peripheral(uint32_t gpio, uint32_t mode, int pullup) // stm32f4 is ~50Mhz at 40pF // stm32g0 is ~30Mhz at 50pF // stm32h7 is ~85Mhz at 50pF - uint32_t ospeed = CONFIG_MACH_STM32F0 ? 0x01 : 0x02; + uint32_t ospeed = hs ? 0x03 : (CONFIG_MACH_STM32F0 ? 0x01 : 0x02); regs->OSPEEDR = (regs->OSPEEDR & ~m_msk) | (ospeed << m_shift); } diff --git a/src/stm32/internal.h b/src/stm32/internal.h index b5971ed7..0422bf65 100644 --- a/src/stm32/internal.h +++ b/src/stm32/internal.h @@ -32,6 +32,7 @@ extern GPIO_TypeDef * const digital_regs[]; #define GPIO_INPUT 0 #define GPIO_OUTPUT 1 #define GPIO_OPEN_DRAIN 0x100 +#define GPIO_HIGH_SPEED 0x200 #define GPIO_FUNCTION(fn) (2 | ((fn) << 4)) #define GPIO_ANALOG 3 void gpio_peripheral(uint32_t gpio, uint32_t mode, int pullup); diff --git a/src/stm32/sdio.c b/src/stm32/sdio.c new file mode 100644 index 00000000..c64bca38 --- /dev/null +++ b/src/stm32/sdio.c @@ -0,0 +1,414 @@ +// SDIO functions on STM32 +// +// Copyright (C) 2022 H. Gregor Molter <gregor.molter@secretlab.de> +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include "board/io.h" // readb, writeb +#include "command.h" // shutdown +#include "sdio.h" // sdio_setup +#include "internal.h" // gpio_peripheral +#include "sched.h" // sched_shutdown +#include "generic/armcm_timer.h" // udelay + +struct sdio_info { + SDIO_TypeDef *sdio; + uint8_t clk_pin, cmd_pin, dat0_pin, dat1_pin, dat2_pin, dat3_pin, function; +}; + +enum { + SDIO_OK = 0, + SDIO_ERROR = 1, + SDIO_TIMEOUT = 2, + SDIO_CMD_RESPONSE_TIMEOUT = 3, + SDIO_CRC_FAIL = 4, + SDIO_WRONG_CMD_RESPONSE = 5, + SDIO_WRONG_BLOCKSIZE = 6, + SDIO_DATA_TIMEOUT = 7, + SDIO_READ_OVERRUN = 8, + SDIO_WRITE_UNDERRUN = 9, + SDIO_NO_DATA_MEMORY = 10, +}; + +enum { + SDIO_CARDVER_UNKNOWN = 0, + SDIO_CARDVER_1X = 1, + SDIO_CARDVER_2X = 2 +}; + +enum { + SDIO_CARDTYPE_UNKNOWN = 0, + SDIO_CARDTYPE_SDHC_SDXC = 1, + SDIO_CARDTYPE_SDSC = 2 +}; + +enum { + SDIO_WAIT_NO_RESPONSE = 0, + SDIO_WAIT_SHORT_RESPONSE = 1, + SDIO_WAIT_LONG_RESPONSE = 2 +}; + +// PINS: CLK -> PC12 , CMD -> PD2, +// DAT0 -> PC8, DAT1 -> PC9, DAT2 -> PC10, DAT3 -> PC11 +DECL_ENUMERATION("sdio_bus", "sdio", 0); +DECL_CONSTANT_STR("BUS_PINS_sdio", "PC12,PD2,PC8,PC9,PC10,PC11"); + +#define SDIO_FUNCTION GPIO_FUNCTION(12) + +static const struct sdio_info sdio_bus[] = { + { SDIO, GPIO('C', 12), GPIO('D', 2), GPIO('C', 8), + GPIO('C', 9), GPIO('C', 10), GPIO('C', 11), SDIO_FUNCTION }, +}; + +#define SDIO_CLK_FREQ 48000000 +#define SDIO_INIT_CLK 400000 +#define SDIO_MAX_TIMEOUT 500 // Wait for at least 500ms before a timeout occurs +#define CLKCR_CLEAR_MASK (SDIO_CLKCR_CLKDIV | SDIO_CLKCR_PWRSAV | \ + SDIO_CLKCR_BYPASS | SDIO_CLKCR_WIDBUS | SDIO_CLKCR_NEGEDGE | \ + SDIO_CLKCR_HWFC_EN) +#define DCTRL_CLEAR_MASK (SDIO_DCTRL_DTEN | SDIO_DCTRL_DTDIR | \ + SDIO_DCTRL_DTMODE | SDIO_DCTRL_DBLOCKSIZE) +#define CMD_CLEAR_MASK (SDIO_CMD_CMDINDEX | SDIO_CMD_WAITRESP | \ + SDIO_CMD_WAITINT | SDIO_CMD_WAITPEND | SDIO_CMD_CPSMEN | \ + SDIO_CMD_SDIOSUSPEND) +#define SDIO_CLOCK_BYPASS_DISABLE 0 +#define SDIO_CLOCK_EDGE_RISING 0 +#define SDIO_CLOCK_POWER_SAVE_DISABLE 0 +#define SDIO_BUS_WIDE_1B 0 +#define SDIO_BUS_WIDE_4B SDIO_CLKCR_WIDBUS_0 +#define SDIO_HARDWARE_FLOW_CONTROL_DISABLE 0 +#define SDIO_HARDWARE_FLOW_CONTROL_ENABLE SDIO_CLKCR_HWFC_EN +#define SDIO_CMD_FLAGS (SDIO_STA_CCRCFAIL | SDIO_STA_CTIMEOUT | \ + SDIO_STA_CMDREND | SDIO_STA_CMDSENT) +#define SDIO_STATIC_FLAGS (SDIO_STA_CCRCFAIL | SDIO_STA_DCRCFAIL | \ + SDIO_STA_CTIMEOUT | SDIO_STA_DTIMEOUT | SDIO_STA_TXUNDERR | \ + SDIO_STA_RXOVERR | SDIO_STA_CMDREND | SDIO_STA_CMDSENT | \ + SDIO_STA_DATAEND | SDIO_STA_DBCKEND | SDIO_STA_SDIOIT) + +struct sdio_config +sdio_setup(uint32_t bus) +{ + if (bus >= ARRAY_SIZE(sdio_bus)) + shutdown("Invalid sdio bus"); + + // Enable SDIO + SDIO_TypeDef *sdio = sdio_bus[bus].sdio; + if (!is_enabled_pclock((uint32_t)sdio)) { + // Enable clock + enable_pclock((uint32_t)sdio); + // Initialize pins + gpio_peripheral( + sdio_bus[bus].dat0_pin, sdio_bus[bus].function|GPIO_HIGH_SPEED, 1); + gpio_peripheral( + sdio_bus[bus].dat1_pin, sdio_bus[bus].function|GPIO_HIGH_SPEED, 1); + gpio_peripheral( + sdio_bus[bus].dat2_pin, sdio_bus[bus].function|GPIO_HIGH_SPEED, 1); + gpio_peripheral( + sdio_bus[bus].dat3_pin, sdio_bus[bus].function|GPIO_HIGH_SPEED, 1); + gpio_peripheral( + sdio_bus[bus].cmd_pin, sdio_bus[bus].function|GPIO_HIGH_SPEED, 1); + gpio_peripheral( + sdio_bus[bus].clk_pin, sdio_bus[bus].function|GPIO_HIGH_SPEED, 1); + } + + struct sdio_config sdio_config = { .sdio = sdio }; + + // Setup SDIO with 1 bit width first and slow clock ~400 kHz + sdio_set_speed(sdio_config, SDIO_INIT_CLK); + + // Disable clk + CLEAR_BIT(sdio->CLKCR, SDIO_CLKCR_CLKEN); + // Set power state to _on_ + sdio->POWER = SDIO_POWER_PWRCTRL; + // Wait for 2ms (standard: at least 1ms) to settle + udelay(2000); + // Enable Clk + SET_BIT(sdio->CLKCR, SDIO_CLKCR_CLKEN); + + return sdio_config; +} + +uint32_t +sdio_get_cmd_error(struct sdio_config sdio, uint32_t flags) +{ + SDIO_TypeDef *regs = sdio.sdio; + + // wait for a timeout (max. SDIO_MAX_TIMEOUT) in msec. + // 8 cycles is the instruction cycles for the loop below. + uint32_t sta; + uint32_t count = SDIO_MAX_TIMEOUT * (SystemCoreClock / 8U / 1000U); + do { + if (count-- == 0) { + return SDIO_TIMEOUT; + } + sta = regs->STA; + } while ((sta & flags) == 0 || (sta & SDIO_STA_CMDACT) != 0); + + regs->ICR = SDIO_CMD_FLAGS; + + if (sta & SDIO_STA_CTIMEOUT) { + return SDIO_CMD_RESPONSE_TIMEOUT; + } + if (sta & SDIO_STA_CCRCFAIL) { + return SDIO_CRC_FAIL; + } + + return SDIO_OK; +} + +uint8_t +sdio_send_command(struct sdio_config sdio_config, uint8_t cmd, + uint32_t argument, uint8_t wait, uint8_t *response_data, + uint8_t *response_data_len) +{ + SDIO_TypeDef *sdio = sdio_config.sdio; + uint32_t wait_flags = 0; // valid for SDIO_WAIT_NO_RESPONSE + uint32_t sta_flags = (wait == SDIO_WAIT_NO_RESPONSE) ? + SDIO_STA_CMDSENT : SDIO_STA_CCRCFAIL | SDIO_STA_CMDREND | \ + SDIO_STA_CTIMEOUT; + + if (wait == SDIO_WAIT_SHORT_RESPONSE) { + wait_flags = SDIO_CMD_WAITRESP_0; + } else if (wait == SDIO_WAIT_LONG_RESPONSE) { + wait_flags = SDIO_CMD_WAITRESP; + } + + // Step 1: Send command and argument + // CMD and State Machine enabled. + // Wait for response like specified by wait_flags. + uint32_t cmdreg = (cmd & 0x3F) | wait_flags | SDIO_CMD_CPSMEN; + sdio->ARG = argument; + MODIFY_REG(sdio->CMD, CMD_CLEAR_MASK, cmdreg); + + // Step 2: Wait until response + // wait for a timeout (max. SDIO_MAX_TIMEOUT) in msec. + // 8 cycles is the instruction cycles for the loop below. + uint32_t sta; + uint32_t count = SDIO_MAX_TIMEOUT * (SystemCoreClock / 8U / 1000U); + do { + if (count-- == 0) { + return SDIO_TIMEOUT; + } + sta = sdio->STA; + } while ((sta & sta_flags) == 0 || (sta & SDIO_STA_CMDACT) != 0); + + sdio->ICR = SDIO_CMD_FLAGS; + + // Step 3: Store response_data and check for short and long responses + // timeout and crc. + if (response_data != NULL) { + if (wait == SDIO_WAIT_SHORT_RESPONSE) { + response_data[0] = (uint8_t) ((sdio->RESP1 >> 24) & 0xFF); + response_data[1] = (uint8_t) ((sdio->RESP1 >> 16) & 0xFF); + response_data[2] = (uint8_t) ((sdio->RESP1 >> 8) & 0xFF); + response_data[3] = (uint8_t) ((sdio->RESP1) & 0xFF); + *response_data_len = 4; + } else if (wait == SDIO_WAIT_LONG_RESPONSE) { + // TODO Inverse? + response_data[0] = (uint8_t) ((sdio->RESP1 >> 24) & 0xFF); + response_data[1] = (uint8_t) ((sdio->RESP1 >> 16) & 0xFF); + response_data[2] = (uint8_t) ((sdio->RESP1 >> 8) & 0xFF); + response_data[3] = (uint8_t) ((sdio->RESP1) & 0xFF); + response_data[4] = (uint8_t) ((sdio->RESP2 >> 24) & 0xFF); + response_data[5] = (uint8_t) ((sdio->RESP2 >> 16) & 0xFF); + response_data[6] = (uint8_t) ((sdio->RESP2 >> 8) & 0xFF); + response_data[7] = (uint8_t) ((sdio->RESP2) & 0xFF); + response_data[8] = (uint8_t) ((sdio->RESP3 >> 24) & 0xFF); + response_data[9] = (uint8_t) ((sdio->RESP3 >> 16) & 0xFF); + response_data[10] = (uint8_t) ((sdio->RESP3 >> 8) & 0xFF); + response_data[11] = (uint8_t) ((sdio->RESP3) & 0xFF); + response_data[12] = (uint8_t) ((sdio->RESP4 >> 24) & 0xFF); + response_data[13] = (uint8_t) ((sdio->RESP4 >> 16) & 0xFF); + response_data[14] = (uint8_t) ((sdio->RESP4 >> 8) & 0xFF); + response_data[15] = (uint8_t) ((sdio->RESP4) & 0xFF); + *response_data_len = 16; + } + } + + if (wait != SDIO_WAIT_NO_RESPONSE) { + // CTIMEOUT and CCRCFAIL check only for short or long responses. + if (sta & SDIO_STA_CTIMEOUT) { + return SDIO_CMD_RESPONSE_TIMEOUT; + } + if (sta & SDIO_STA_CCRCFAIL) { + return SDIO_CRC_FAIL; + } + } + + // Step 4: For a short response check the response cmd field, too. + if (wait == SDIO_WAIT_SHORT_RESPONSE) { + if (sdio->RESPCMD != cmd) { + return SDIO_WRONG_CMD_RESPONSE; + } + } + + return SDIO_OK; +} + +uint32_t +sdio_get_dctrl_blocksize(uint32_t value) +{ + switch(value) { + case 1: return 0U; + case 2: return SDIO_DCTRL_DBLOCKSIZE_0; + case 4: return SDIO_DCTRL_DBLOCKSIZE_1; + case 8: return SDIO_DCTRL_DBLOCKSIZE_1|SDIO_DCTRL_DBLOCKSIZE_0; + case 16: return SDIO_DCTRL_DBLOCKSIZE_2; + case 32: return SDIO_DCTRL_DBLOCKSIZE_2|SDIO_DCTRL_DBLOCKSIZE_0; + case 64: return SDIO_DCTRL_DBLOCKSIZE_2|SDIO_DCTRL_DBLOCKSIZE_1; + case 128: return SDIO_DCTRL_DBLOCKSIZE_2|SDIO_DCTRL_DBLOCKSIZE_1| \ + SDIO_DCTRL_DBLOCKSIZE_0; + case 256: return SDIO_DCTRL_DBLOCKSIZE_3; + case 512: return SDIO_DCTRL_DBLOCKSIZE_3|SDIO_DCTRL_DBLOCKSIZE_0; + case 1024: return SDIO_DCTRL_DBLOCKSIZE_3|SDIO_DCTRL_DBLOCKSIZE_1; + case 2048: return SDIO_DCTRL_DBLOCKSIZE_3|SDIO_DCTRL_DBLOCKSIZE_1| \ + SDIO_DCTRL_DBLOCKSIZE_0; + case 4096: return SDIO_DCTRL_DBLOCKSIZE_3|SDIO_DCTRL_DBLOCKSIZE_2; + case 8192: return SDIO_DCTRL_DBLOCKSIZE_3|SDIO_DCTRL_DBLOCKSIZE_2| \ + SDIO_DCTRL_DBLOCKSIZE_0; + case 16384: return SDIO_DCTRL_DBLOCKSIZE_3|SDIO_DCTRL_DBLOCKSIZE_2| \ + SDIO_DCTRL_DBLOCKSIZE_1; + } + return SDIO_DCTRL_DBLOCKSIZE_Msk; +} + +uint8_t +sdio_prepare_data_transfer(struct sdio_config sdio_config, uint8_t read, + uint32_t numblocks, uint32_t blocksize, uint32_t timeout) +{ + SDIO_TypeDef *sdio = sdio_config.sdio; + uint32_t dctrl_blocksize = sdio_get_dctrl_blocksize(blocksize); + uint32_t reg = dctrl_blocksize | ((read > 0) ? + SDIO_DCTRL_DTDIR : 0U) | SDIO_DCTRL_DTEN; + + if (dctrl_blocksize == SDIO_DCTRL_DBLOCKSIZE_Msk) + return SDIO_WRONG_BLOCKSIZE; + + sdio->DCTRL = 0; + sdio->DTIMER = timeout; + sdio->DLEN = numblocks*blocksize; + MODIFY_REG(sdio->DCTRL, DCTRL_CLEAR_MASK, reg); + return SDIO_OK; +} + +uint8_t +sdio_read_data(struct sdio_config sdio_config, uint8_t *data, + uint32_t numblocks, uint32_t blocksize) +{ + // Read data by polling + SDIO_TypeDef *sdio = sdio_config.sdio; + uint32_t data_remaining = numblocks*blocksize; + uint8_t *buf = data; + + if (data == NULL) { + return SDIO_NO_DATA_MEMORY; + } + + while ((sdio->STA & (SDIO_STA_RXOVERR | SDIO_STA_DCRCFAIL | + SDIO_STA_DTIMEOUT | SDIO_STA_DATAEND)) == 0) { + if ((sdio->STA & SDIO_STA_RXDAVL) != 0) { + uint32_t tmp = sdio->FIFO; + for (uint8_t i=0; (i<4) && (data_remaining>0); i++) { + *buf = (uint8_t)(tmp & 0xFF); + buf++; + data_remaining--; + tmp >>= 8U; + } + } + } + + uint32_t sta = sdio->STA; + sdio->ICR = SDIO_STATIC_FLAGS; + + if (sta & SDIO_STA_DTIMEOUT) { + return SDIO_DATA_TIMEOUT; + } + if (sta & SDIO_STA_DCRCFAIL) { + return SDIO_CRC_FAIL; + } + if (sta & SDIO_STA_RXOVERR) { + return SDIO_READ_OVERRUN; + } + + // Empty FIFO and clear flags again + while (((sdio->STA & SDIO_STA_RXDAVL) != 0) && (data_remaining > 0)) { + uint32_t tmp = sdio->FIFO; + for (uint8_t i=0; (i<4) && (data_remaining>0); i++) { + *buf = (uint8_t)(tmp & 0xFF); + buf++; + data_remaining--; + tmp >>= 8U; + } + } + + sdio->ICR = SDIO_STATIC_FLAGS; + return SDIO_OK; +} + +uint8_t +sdio_write_data(struct sdio_config sdio_config, uint8_t *data, + uint32_t numblocks, uint32_t blocksize) +{ + // Write data by polling + SDIO_TypeDef *sdio = sdio_config.sdio; + uint32_t data_remaining = numblocks*blocksize; + uint8_t *buf = data; + + if (data == NULL) { + return SDIO_NO_DATA_MEMORY; + } + + while ((sdio->STA & (SDIO_STA_TXUNDERR | SDIO_STA_DCRCFAIL | + SDIO_STA_DTIMEOUT | SDIO_STA_DATAEND)) == 0) { + if ((sdio->STA & SDIO_STA_TXFIFOF) == 0) { + uint32_t tmp = 0; + for (uint8_t i=0; (i<4) && (data_remaining>0); i++) { + tmp |= ((uint32_t)(*buf) << (i<<3)); + buf++; + data_remaining--; + } + sdio->FIFO = tmp; + } + } + + uint32_t sta = sdio->STA; + sdio->ICR = SDIO_STATIC_FLAGS; + + if (sta & SDIO_STA_DTIMEOUT) { + return SDIO_DATA_TIMEOUT; + } + if (sta & SDIO_STA_DCRCFAIL) { + return SDIO_CRC_FAIL; + } + if (sta & SDIO_STA_TXUNDERR) { + return SDIO_WRITE_UNDERRUN; + } + + return SDIO_OK; +} + +void +sdio_send_cmd(struct sdio_config sdio_config, uint8_t cmd, uint32_t argument, + uint8_t wait) +{ + SDIO_TypeDef *sdio = sdio_config.sdio; + + sdio->ARG = argument; + //CMD and State Machine enabled. Wait for response like specified. + uint32_t cmdreg = (cmd & 0x3F) | ((wait & 0xC0)) | SDIO_CMD_CPSMEN; + + MODIFY_REG(sdio->CMD, CMD_CLEAR_MASK, cmdreg); +} + +void +sdio_set_speed(struct sdio_config sdio_config, uint32_t speed) +{ + SDIO_TypeDef *sdio = sdio_config.sdio; + + uint8_t clkdiv = (SDIO_CLK_FREQ/speed)-2; + + uint32_t sdio_confreg = SDIO_CLOCK_EDGE_RISING | \ + SDIO_CLOCK_BYPASS_DISABLE | SDIO_CLOCK_POWER_SAVE_DISABLE | \ + SDIO_BUS_WIDE_1B | SDIO_HARDWARE_FLOW_CONTROL_DISABLE | (clkdiv & 0xFF); + MODIFY_REG(sdio->CLKCR, CLKCR_CLEAR_MASK, sdio_confreg); +} diff --git a/src/stm32/sdio.h b/src/stm32/sdio.h new file mode 100644 index 00000000..eaf6596f --- /dev/null +++ b/src/stm32/sdio.h @@ -0,0 +1,25 @@ +#ifndef __STM32_SDIO_H +#define __STM32_SDIO_H + +#include <stdint.h> // uint32_t + +struct sdio_config { + void *sdio; +}; +struct sdio_config sdio_setup(uint32_t bus); +void sdio_send_cmd(struct sdio_config sdio, uint8_t cmd, uint32_t argument + , uint8_t wait); +uint8_t sdio_send_command(struct sdio_config sdio_config, uint8_t cmd + , uint32_t argument, uint8_t wait + , uint8_t *response_data + , uint8_t *response_data_len); +uint8_t sdio_prepare_data_transfer(struct sdio_config sdio_config, uint8_t read + , uint32_t numblocks, uint32_t blocksize + , uint32_t timeout); +uint8_t sdio_read_data(struct sdio_config sdio_config, uint8_t *data + , uint32_t numblocks, uint32_t blocksize); +uint8_t sdio_write_data(struct sdio_config sdio_config, uint8_t *data + , uint32_t numblocks, uint32_t blocksize); +void sdio_set_speed(struct sdio_config sdio_config, uint32_t speed); + +#endif // sdio.h diff --git a/src/stm32/stm32f4.c b/src/stm32/stm32f4.c index a2060d76..fd3eb0ba 100644 --- a/src/stm32/stm32f4.c +++ b/src/stm32/stm32f4.c @@ -138,8 +138,8 @@ enable_clock_stm32f446(void) while (!(PWR->CSR & PWR_CSR_ODSWRDY)) ; - // Enable 48Mhz USB clock - if (CONFIG_USB) { + // Enable 48Mhz USB clock for USB or for SDIO + if (CONFIG_USB || CONFIG_HAVE_GPIO_SDIO) { uint32_t ref = (CONFIG_STM32_CLOCK_REF_INTERNAL ? 16000000 : CONFIG_CLOCK_REF_FREQ); uint32_t plls_base = 2000000, plls_freq = FREQ_USB * 4; @@ -153,6 +153,14 @@ enable_clock_stm32f446(void) ; RCC->DCKCFGR2 = RCC_DCKCFGR2_CK48MSEL; + } else { + // Reset value just in case the booloader modified the default value + RCC->DCKCFGR2 = 0; + } + + // Set SDIO clk to PLL48CLK + if (CONFIG_HAVE_GPIO_SDIO) { + MODIFY_REG(RCC->DCKCFGR2, RCC_DCKCFGR2_SDIOSEL, 0); } #endif } |