Zephyr SDHC Implementation
This page provides details on the implementation of the SDHC Zephyr driver.
SD Layers with/without File System
Zephyr uses the SD subsystem along with the supported driver to get device info, configure SD, and read and write data to and from the SD card. The APIs defined in the SD subsystem are exposed to the application layer. Based on the compatible string, the corresponding driver APIs get called.
For FS based operation, two more subsystems are used: File System (FS) and Disk subsystem.
Zephyr SD driver stack with and without filesystem support.
Probe Data From Node
In the driver, we need to assign the compatible string to a zephyr macro,
DT_DRV_COMPAT. If the assigned string matches with any one of the DT nodes
along with the status property okay, then the driver needs to register the
driver API to the SD subsystem using a subsystem structure.
Probe
#define DT_DRV_COMPAT arasan_sdhci #define SDHCI_ARASAN_INIT(n) \ SDHCI_ARASAN_INTR_CONFIG(n) \ static struct sdhci_arasan_config sdhci_arasan_inst_##n = { \ DEVICE_MMIO_ROM_INIT(DT_DRV_INST(n)), \ .clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)), \ SDHCI_ARASAN_INTR_FUNC_REG(n) \ .nocd = DT_INST_PROP_OR(n, no_cd, 0), \ .slottype = DT_INST_PROP_OR(n, slot_type, 1), \ .powerdelay = DT_INST_PROP_OR(n, power_delay_ms, 0), \ .cachecoherent = DT_INST_PROP_OR(n, cache_coherent, 0), \ .hs200_mode = DT_INST_PROP_OR(n, mmc_hs200_1_8v, 0), \ .hs400_mode = DT_INST_PROP_OR(n, mmc_hs400_1_8v, 0), \ .itapdly_sdr_clk50 = DT_INST_PROP_OR(n, clk_50_sdr_itap_dly, 0), \ .otapdly_sdr_clk50 = DT_INST_PROP_OR(n, clk_50_sdr_otap_dly, 0), \ .itapdly_ddr_clk50 = DT_INST_PROP_OR(n, clk_50_ddr_itap_dly, 0), \ .otapdly_ddr_clk50 = DT_INST_PROP_OR(n, clk_50_ddr_otap_dly, 0), \ .otapdly_ddr_clk100 = DT_INST_PROP_OR(n, clk_100_sdr_itap_dly, 0), \ .otapdly_sdr_clk200 = DT_INST_PROP_OR(n, clk_200_sdr_itap_dly, 0), \ .otapdly_ddr_clk200 = DT_INST_PROP_OR(n, clk_200_ddr_itap_dly, 0), \ }; \ static struct sd_data data##n; \ \ DEVICE_DT_INST_DEFINE(n, sdhci_arasan_init, NULL, &data##n, &sdhci_arasan_inst_##n, POST_KERNEL, \ CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &sdhci_arasan_api); DT_INST_FOREACH_STATUS_OKAY(SDHCI_ARASAN_INIT)
DT_DRV_COMPAT
This macro holds the compatible string of the driver.
DT_INST_FOREACH_STATUS_OKAY
This macro calls
AMD_SDHC_INIT(n)on all node with compatibleDT_DRV_COMPATand statusokay.
DEVICE_DT_INST_DEFINE
This macro defines a device structure that is automatically configured by kernel during system init.
amd_sdps_init API is run by the kernel during system initialization.
device→name is set by “DEVICE_DT_NAME(n)” macro.
device→data is set with data##n variable address.
device→config is set with amd_sdps_inst_##n variable address.
POST_KERNEL defines the device initialization level.
CONFIG_KERNEL_INIT_PRIORITY_DEVICE device priority within the device initialization level.
sdps_api is a structure where we add all the api that need to be registered to subsystem.
DEVICE_DT_NAME(n)
This macro returns a string name for a device tree node.
DT_INST_REG_ADDR(n)
This macro returns instances register block address.
DT_INST_PROP(n, Property)
This macro returns node property value.
DT_INST_PROP_OR(n, Property, Default)
This macro returns node property value or default value.
SD Initialization by SD Subsystem
SD subsystem initialization sequence (CMD0 through CMD6).
SD Disk Driver for FS Operation
Probe
#define DT_DRV_COMPAT zephyr_sdmmc_disk #define DISK_ACCESS_SDMMC_INIT(n) \ static const struct sdmmc_config sdmmc_config_##n = { \ .host_controller = DEVICE_DT_GET(DT_INST_PARENT(n)), \ }; \ \ static struct sdmmc_data sdmmc_data_##n = { \ .name = CONFIG_SDMMC_VOLUME_NAME, \ }; \ \ DEVICE_DT_INST_DEFINE(n, \ &disk_sdmmc_init, \ NULL, \ &sdmmc_data_##n, \ &sdmmc_config_##n, \ POST_KERNEL, \ CONFIG_SD_INIT_PRIORITY, \ NULL); DT_INST_FOREACH_STATUS_OKAY(DISK_ACCESS_SDMMC_INIT)
The disk_sdmmc_init API register the sdmmc disk in to registries with name and functions.
All the possible functions are: init, write, read, ioctl, status.
User application need to mount the card with fs_mount.
fs_mount call registered filesystem subsystem API.
Registered Filesystem is found using fs_get_type.
For fatfs registered subsystem API for mount is fatfs_mount.
fatfs_mount call f_mount.
In case of read, write, ioctl, status, init operation disk_access_get_di API is used to call the corresponding disk API.
disk_access_get_di
This API compare the name of all the registered disk and requested disk for the operation after matched it calls the registered API done by the disk driver for the operation.
SD Subsystem
SD subsystem has a structure where drivers need to register their APIs by following the same protocol used in the structure that follows.
Structure
__subsystem struct sdhc_driver_api { int (*reset)(const struct device *dev); int (*request)(const struct device *dev, struct sdhc_command *cmd, struct sdhc_data *data); int (*set_io)(const struct device *dev, struct sdhc_io *ios); int (*get_card_present)(const struct device *dev); int (*execute_tuning)(const struct device *dev); int (*card_busy)(const struct device *dev); int (*get_host_props)(const struct device *dev, struct sdhc_host_props *props); int (*enable_interrupt)(const struct device *dev, sdhc_interrupt_cb_t callback, int sources, void *user_data); int (*disable_interrupt)(const struct device *dev, int sources); };
Functional Implementation
Request
SET IO
Setting clock in host controller.
Setting bus width for the host side.
Setting timing(mode) for the host side.
Setting power for the host side.
Setting voltage level for the host side.
get_host_props
Populates sdhc_host_props structure with the host details like bus width, voltage, mode, clock, ADMA support.
Get Card Present
This API check card is present or not by checking the host present stage register.
Card Busy
This API check card is busy or not by checking the host present stage register.
Reset
This API perform a software reset on command control registers
Execute Tuning
This API gets the tuning blocks by sending cmd19 or cmd21 based on card type.
Enable auto tuning and check sampling clock is set or not.
Testing Using Testsuite
Testing of driver can be done using the Testsuite subsystem. Test cases are
written using the ZTEST macro.
ZTEST EXPANTION
//Example ZTEST(sd_stack, test_0_init) { int ret; zassert_true(device_is_ready(sdhc_dev), "SDHC device is not ready"); ret = sd_init(sdhc_dev, &card); zassert_equal(ret, 0, "Card initialization failed"); } //Expansion struct ztest_unit_test_stats z_ztest_unit_test_stats_sd_stack_test_0_init; static void _sd_stack_test_0_init_wrapper(void *data); static void sd_stack_test_0_init((void))); static Z_DECL_ALIGN(struct ztest_unit_test) z_ztest_unit_test__sd_stack__test_0_init \ __in_section(_ztest_unit_test, static, z_ztest_unit_test__sd_stack__test_0_init_) __used __noasan = { .test_suite_name = sd_stack, .name = test_0_init, .test = (_sd_stack_test_0_init_wrapper), .thread_options = 0, .stats = &z_ztest_unit_test_stats_sd_stack_test_0_init }; static void _sd_stack_test_0_init_wrapper(void *wrapper_data) { ARG_UNUSED(wrapper_data); sd_stack_test_0_init(); } static inline void sd_stack_test_0_init((void)) { int ret; zassert_true(device_is_ready(sdhc_dev), "SDHC device is not ready"); ret = sd_is_card_present(sdhc_dev); zassert_equal(ret, 1, "SD card not present in slot"); ret = sd_init(sdhc_dev, &card); zassert_equal(ret, 0, "Card initialization failed"); }
Unit tests are written using the ZTEST_SUITE macro, which registers the
test cases with a unit test.
ZTEST_SUITE
//Example ZTEST_SUITE(sd_stack, NULL, NULL, NULL, NULL, NULL); //Expantion static Z_DECL_ALIGN(struct ztest_suite_node) z_ztest_test_node_sd_stack \ __in_section(_ztest_suite_node, static, z_ztest_test_node_sd_stack__) __used __noasan = { .name = sd_stack, .setup = 0, .before = 0, .after = 0, .teardown = 0, .predicate = 0, .stats = &z_ztest_suite_node_stats_sd_stack, }
Test suite is having the main and it holds the starting address of _ztest_suite_node structure.
It traverses through this structure and compare the name member of _ztest_suite_node with test_suite_name member of _ztest_unit_test.
If it matches it calls the API assigned in test member of _ztest_unit_test structure.
HW Tested Modes Using Testsuite
SD - Polled:
SDR104 - single and multi-block read/write, unaligned read/write.
SDR50 - single and multi-block read/write, unaligned read/write.
DDR50 - single and multi-block read/write, unaligned read/write.
SDR25 - single and multi-block read/write, unaligned read/write.
SDR12 - single and multi-block read/write, unaligned read/write.
HS - single and multi-block read/write, unaligned read/write, 1&4 bit bus width read/write.
Normal speed - single and multi-block read/write, unaligned read/write, 1&4 bit bus width read/write.
eMMC - Polled:
Legacy speed - single and multi-block read/write, unaligned read/write, 1/4 bit bus width.
SDR-HS - single and multi-block read/write, unaligned read/write, 1/8 bit bus width.
HS200 - single and multi-block read/write, unaligned read/write, 8 bit bus width.
HS400 - single and multi-block read/write, unaligned read/write.
eMMC - Interrupt
HS400 - single and multi-block read/write, unaligned read/write.
Legacy - single and multi-block read/write, unaligned read/write.
SD- Interrupt
SDR104 - single and multi-block read/write, unaligned read/write.
SDR25 - single and multi-block read/write, unaligned read/write.
User Space:
System Calls
__syscall is used to represent a system call.
System calls are not implemented manually; they get auto generated by gen-syscalls-py.
What gets generated is an inline function which either calls the Implementation function directly (if called from supervisor mode) or goes through privilege elevation and validation steps (if called from user mode).
The Implementation function for this API performs the functionality and assumes the arguments passed are valid if it is called from user mode.
For every system call there is a verification function which wraps the implementation function and does validation of all the arguments passed in.
The unmarshalling function gets auto generated and does packing, unpacking, and validation of arguments.
The unmarshalling and Implementation functions only get called when the system call is called in user mode.
Verification Function
static inline int z_vrfy_sdhc_get_host_props(const struct device *dev, struct sdhc_host_props *props) { K_OOPS(K_SYSCALL_SDHC_GET_HOST_PROPS(dev,props)); return z_impl_sdhc_get_host_props(dev, props); } #include <syscalls/sdhc_get_host_props_mrsh.c>
Generated System Call
extern int z_impl_sdhc_get_host_props(const struct device * dev, struct sdhc_host_props * props); __pinned_func static inline int sdhc_get_host_props(const struct device * dev, struct sdhc_host_props * props) { #ifdef CONFIG_USERSPACE if (z_syscall_trap()) { union { uintptr_t x; const struct device * val; } parm0 = { .val = dev }; union { uintptr_t x; struct sdhc_host_props * val; } parm1 = { .val = props }; return (int) arch_syscall_invoke2(parm0.x, parm1.x, K_SYSCALL_SDHC_GET_HOST_PROPS); } #endif compiler_barrier(); return z_impl_sdhc_get_host_props(dev, props); }
Unmarshalling Function
/* auto-generated by gen_syscalls.py, don't edit */ #include <syscalls/sdhc.h> extern int z_vrfy_sdhc_get_host_props(const struct device * dev, struct sdhc_config * cfg); uintptr_t z_mrsh_sdhc_get_host_props(uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, uintptr_t arg4, uintptr_t arg5, void *ssf) { _current->syscall_frame = ssf; (void) arg2; /* unused */ (void) arg3; /* unused */ (void) arg4; /* unused */ (void) arg5; /* unused */ union { uintptr_t x; const struct device * val; } parm0; parm0.x = arg0; union { uintptr_t x; struct sdhc_config * val; } parm1; parm1.x = arg1; int ret = z_vrfy_sdhc_get_host_props(parm0.val, parm1.val); _current->syscall_frame = NULL; return (uintptr_t) ret; }
Invocation Context
CONFIG_USERSPACE runtime check to see if the processor is currently running in user mode
__ZEPHYR_SUPERVISOR__ always runs in supervisor mode
__ZEPHYR_USER__ always runs in user mode