aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Kconfig2
-rw-r--r--src/Makefile1
-rw-r--r--src/sdiocmds.c166
-rw-r--r--src/sdiocmds.h8
-rw-r--r--src/stm32/Kconfig1
-rw-r--r--src/stm32/Makefile2
-rw-r--r--src/stm32/gpioperiph.c7
-rw-r--r--src/stm32/internal.h1
-rw-r--r--src/stm32/sdio.c414
-rw-r--r--src/stm32/sdio.h25
-rw-r--r--src/stm32/stm32f4.c12
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
}