diff --git a/drivers/include/mtd_sdmmc.h b/drivers/include/mtd_sdmmc.h new file mode 100644 index 0000000000..4143813177 --- /dev/null +++ b/drivers/include/mtd_sdmmc.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2017 HAW-Hamburg + * 2023 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup drivers_mtd_sdmmc mtd wrapper for sdmmc + * @ingroup drivers_storage + * @brief Driver for SD Memory Cards and MMCs/eMMCs using mtd interface + * + * @{ + * + * @file + * @brief Interface definition for mtd_sdmmc driver + * + * @author Michel Rottleuthner + * @author Gunar Schorcht + */ + +#ifndef MTD_SDMMC_H +#define MTD_SDMMC_H + +#include + +#include "mtd.h" +#include "sdmmc/sdmmc.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @brief Device descriptor for a mtd_sdmmc device + * + * This is an extension of the @c mtd_dev_t struct + */ +typedef struct { + mtd_dev_t base; /**< inherit mtd_dev_t object */ + sdmmc_dev_t *sdmmc; /**< SD/MMC device descriptor */ + uint8_t sdmmc_idx; /**< SD/MMC peripheral index */ +} mtd_sdmmc_t; + +/** + * @defgroup drivers_mtd_sdmmc_config SD Memory Card driver compile configuration + * @ingroup config_drivers_storage + * @{ + */ +/** + * @brief Enable SD Memory Card Erase + * + * SD Memory Cards and MMCs/eMMCs handle sector erase internally + * so it's possible to directly write to the card without erasing + * the sector first. + * + * @note An erase call will NOT touch the content if `CONFIG_MTD_SDMMC_ERASE` + * is not set, so enable this feature to ensure overriding the data. + * + * @pre This feature requires the `mtd_write_page` module. + */ +#ifdef DOXYGEN +#define CONFIG_MTD_SDMMC_ERASE +#endif +/** @} */ + +/** + * @brief sdcard device operations table for mtd + */ +extern const mtd_desc_t mtd_sdmmc_driver; + +#ifdef __cplusplus +} +#endif + +#endif /* MTD_SDMMC_H */ +/** @} */ diff --git a/drivers/mtd/Makefile.dep b/drivers/mtd/Makefile.dep index 6c0bce41d7..a951c62d03 100644 --- a/drivers/mtd/Makefile.dep +++ b/drivers/mtd/Makefile.dep @@ -13,3 +13,11 @@ endif ifneq (,$(filter mtd_sdcard,$(USEMODULE))) USEMODULE += sdcard_spi endif + +ifneq (,$(filter mtd_sdmmc_default,$(USEMODULE))) + USEMODULE += mtd_sdmmc +endif + +ifneq (,$(filter mtd_sdmmc,$(USEMODULE))) + USEMODULE += sdmmc +endif diff --git a/drivers/mtd_sdmmc/Kconfig b/drivers/mtd_sdmmc/Kconfig new file mode 100644 index 0000000000..ff0134a0c7 --- /dev/null +++ b/drivers/mtd_sdmmc/Kconfig @@ -0,0 +1,35 @@ +# Copyright (c) 2020 Freie Universitaet Berlin +# 2020 HAW Hamburg +# 2023 Gunar Schorcht +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. +# +menuconfig KCONFIG_USEMODULE_MTD_SDMMC + bool "Configure MTD_SDMMC driver" + depends on USEMODULE_MTD_SDMMC + help + Configure the MTD_SDMMC driver using Kconfig. + +if KCONFIG_USEMODULE_MTD_SDMMC + +config SDMMC_GENERIC_MTD_OFFSET + depends on MODULE_MTD_SDMMC_DEFAULT + int "Index of first auto-configured MTD SD/MMC device" + default 0 + help + If you have other MTD devices defined, set this number so that + the auto-configured SD Memory Card(s) or MMCs/eMMCs from + mtd_sdmmc_default will come after them. + +config MTD_SDMMC_ERASE + bool "Enable SD Memory Card erase" + help + Enable this to erase sector before a data write operation (SD Memory + Cards only). + SD Memory Cards and MMCs/eMMCs handle sector erase internally + so it's possible to directly write to the card without erasing + the sector first hence this feature is disabled by default. + +endif # KCONFIG_USEMODULE_MTD_SDMMC diff --git a/drivers/mtd_sdmmc/Makefile b/drivers/mtd_sdmmc/Makefile new file mode 100644 index 0000000000..e644f4a4c9 --- /dev/null +++ b/drivers/mtd_sdmmc/Makefile @@ -0,0 +1,3 @@ +MODULE = mtd_sdmmc + +include $(RIOTBASE)/Makefile.base diff --git a/drivers/mtd_sdmmc/Makefile.include b/drivers/mtd_sdmmc/Makefile.include new file mode 100644 index 0000000000..a7a441e75a --- /dev/null +++ b/drivers/mtd_sdmmc/Makefile.include @@ -0,0 +1 @@ +PSEUDOMODULES += mtd_sdmmc_default diff --git a/drivers/mtd_sdmmc/mtd_sdmmc.c b/drivers/mtd_sdmmc/mtd_sdmmc.c new file mode 100644 index 0000000000..624c95f250 --- /dev/null +++ b/drivers/mtd_sdmmc/mtd_sdmmc.c @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2017 HAW-Hamburg + * 2023 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + * + */ + +/** + * @ingroup drivers_mtd_sdmmc + * @{ + * + * @file + * @brief Driver for using sdmmc via mtd interface + * + * @author Michel Rottleuthner + * @author Gunar Schorcht + * + * @} + */ + +#include +#include +#include + +#define ENABLE_DEBUG 0 +#include "debug.h" +#include "kernel_defines.h" +#include "macros/utils.h" +#include "mtd.h" +#include "mtd_sdmmc.h" +#include "sdmmc/sdmmc.h" + +static int mtd_sdmmc_init(mtd_dev_t *dev) +{ + DEBUG("mtd_sdmmc_init\n"); + mtd_sdmmc_t *mtd_sd = (mtd_sdmmc_t*)dev; + + /* get the SDMMC device descriptor from SDMMC peripheral index */ + mtd_sd->sdmmc = sdmmc_get_dev(mtd_sd->sdmmc_idx); + + if ((mtd_sd->sdmmc->init_done == true) || + (sdmmc_card_init(mtd_sd->sdmmc) == 0)) { + /* erasing whole sectors is handled internally by the card so you can + delete single blocks (i.e. pages) */ + dev->pages_per_sector = 1; + dev->sector_count = (uint32_t)(sdmmc_get_capacity(mtd_sd->sdmmc) / + SDMMC_SDHC_BLOCK_SIZE); + + /* sdcard uses the fixed block size of SD-HC cards */ + dev->page_size = SDMMC_SDHC_BLOCK_SIZE; + dev->write_size = SDMMC_SDHC_BLOCK_SIZE; + return 0; + } + + return -EIO; +} + +static int mtd_sdmmc_read_page(mtd_dev_t *dev, void *buff, uint32_t page, + uint32_t offset, uint32_t size) +{ + mtd_sdmmc_t *mtd_sd = (mtd_sdmmc_t*)dev; + + DEBUG("mtd_sdmmc_read_page: page:%" PRIu32 " offset:%" PRIu32 " size:%" PRIu32 "\n", + page, offset, size); + + if (offset || size % SDMMC_SDHC_BLOCK_SIZE) { +#if IS_USED(MODULE_MTD_WRITE_PAGE) + if (dev->work_area == NULL) { + DEBUG("mtd_sdmmc_read_page: no work area\n"); + return -ENOTSUP; + } + + if (sdmmc_read_blocks(mtd_sd->sdmmc, page, SDMMC_SDHC_BLOCK_SIZE, + 1, dev->work_area, NULL)) { + return -EIO; + } + size = MIN(size, SDMMC_SDHC_BLOCK_SIZE - offset); + DEBUG("mtd_sdmmc_read_page: read %" PRIu32 " bytes at offset %" PRIu32 "\n", + size, offset); + memcpy(buff, (uint8_t *)dev->work_area + offset, size); + return size; +#else + return -ENOTSUP; +#endif + } + + int err = sdmmc_read_blocks(mtd_sd->sdmmc, page, SDMMC_SDHC_BLOCK_SIZE, + size / SDMMC_SDHC_BLOCK_SIZE, buff, NULL); + if (err) { + DEBUG("mtd_sdmmc_read_page: error %d\n", err); + return -EIO; + } + return size; +} + +static int mtd_sdmmc_write_page(mtd_dev_t *dev, const void *buff, uint32_t page, + uint32_t offset, uint32_t size) +{ + mtd_sdmmc_t *mtd_sd = (mtd_sdmmc_t*)dev; + + DEBUG("mtd_sdmmc_write_page: page:%" PRIu32 " offset:%" PRIu32 " size:%" PRIu32 "\n", + page, offset, size); + + if (offset || size % SDMMC_SDHC_BLOCK_SIZE) { +#if IS_USED(MODULE_MTD_WRITE_PAGE) + if (dev->work_area == NULL) { + DEBUG("mtd_sdmmc_write_page: no work area\n"); + return -ENOTSUP; + } + + if (sdmmc_read_blocks(mtd_sd->sdmmc, page, SDMMC_SDHC_BLOCK_SIZE, + 1, dev->work_area, NULL)) { + return -EIO; + } + + size = MIN(size, SDMMC_SDHC_BLOCK_SIZE - offset); + DEBUG("mtd_sdmmc_write_page: write %" PRIu32 " bytes at offset %" PRIu32 "\n", + size, offset); + memcpy((uint8_t *)dev->work_area + offset, buff, size); + if (sdmmc_write_blocks(mtd_sd->sdmmc, page, SDMMC_SDHC_BLOCK_SIZE, + 1, dev->work_area, NULL)) { + return -EIO; + } +#else + return -ENOTSUP; +#endif + } + else { + int err = sdmmc_write_blocks(mtd_sd->sdmmc, page, SDMMC_SDHC_BLOCK_SIZE, + size / SDMMC_SDHC_BLOCK_SIZE, buff, NULL); + if (err) { + DEBUG("mtd_sdmmc_write_page: error %d\n", err); + return -EIO; + } + } + return size; +} + +static int mtd_sdmmc_erase_sector(mtd_dev_t *dev, uint32_t sector, uint32_t count) +{ +#if IS_ACTIVE(CONFIG_MTD_SDMMC_ERASE) && IS_USED(MODULE_MTD_WRITE_PAGE) + mtd_sdmmc_t *mtd_sd = (mtd_sdmmc_t*)dev; + + DEBUG("mtd_sdmmc_erase_sector: sector: %" PRIu32 " count: %" PRIu32 "\n", + sector, count); + + if (dev->work_area == NULL) { + DEBUG("mtd_sdmmc_erase_sector: no work area\n"); + return -ENOTSUP; + } + memset(dev->work_area, 0, SDMMC_SDHC_BLOCK_SIZE); + while (count) { + if (sdmmc_write_blocks(mtd_sd->sdmmc, sector, SDMMC_SDHC_BLOCK_SIZE, + 1, dev->work_area, NULL)) { + return -EIO; + } + --count; + ++sector; + } +#else + (void)dev; + (void)sector; + (void)count; + mtd_sdmmc_t *mtd_sd = (mtd_sdmmc_t*)dev; + if (IS_ACTIVE(CONFIG_MTD_SDMMC_ERASE)) { + return sdmmc_erase_blocks(mtd_sd->sdmmc, sector, count); + } +#endif + return 0; +} + +static int mtd_sdmmc_power(mtd_dev_t *dev, enum mtd_power_state power) +{ + (void)dev; + (void)power; + + /* TODO: implement power down of sdcard in sdcard_spi + (make use of sdcard_spi_params_t.power pin) */ + return -ENOTSUP; /* currently not supported */ +} + +static int mtd_sdmmc_read(mtd_dev_t *dev, void *buff, uint32_t addr, + uint32_t size) +{ + int res = mtd_sdmmc_read_page(dev, buff, addr / SDMMC_SDHC_BLOCK_SIZE, + addr % SDMMC_SDHC_BLOCK_SIZE, size); + if (res < 0) { + return res; + } + if (res == (int)size) { + return 0; + } + return -EOVERFLOW; +} + +static int mtd_sdmmc_write(mtd_dev_t *dev, const void *buff, uint32_t addr, + uint32_t size) +{ + int res = mtd_sdmmc_write_page(dev, buff, addr / SDMMC_SDHC_BLOCK_SIZE, + addr % SDMMC_SDHC_BLOCK_SIZE, size); + if (res < 0) { + return res; + } + if (res == (int)size) { + return 0; + } + return -EOVERFLOW; +} + +const mtd_desc_t mtd_sdmmc_driver = { + .init = mtd_sdmmc_init, + .read = mtd_sdmmc_read, + .read_page = mtd_sdmmc_read_page, + .write = mtd_sdmmc_write, + .write_page = mtd_sdmmc_write_page, + .erase_sector = mtd_sdmmc_erase_sector, + .power = mtd_sdmmc_power, +}; + +#if IS_USED(MODULE_MTD_SDMMC_DEFAULT) +#include "vfs_default.h" + +#ifndef CONFIG_SDMMC_GENERIC_MTD_OFFSET +#define CONFIG_SDMMC_GENERIC_MTD_OFFSET 0 +#endif + +#define MTD_SDMMC_DEV(n, m) \ + mtd_sdmmc_t mtd_sdmmc_dev ##n = { \ + .base = { \ + .driver = &mtd_sdmmc_driver, \ + }, \ + .sdmmc_idx = n, \ + }; \ + \ + mtd_dev_t CONCAT(*mtd, m) = (mtd_dev_t *)&mtd_sdmmc_dev ##n + +#if IS_USED(MODULE_MTD_SDCARD_DEFAULT) +/* we use /sd1 as default mount point for coexistence with mtd_sdcard */ +#define MTD_SDMMC_DEV_FS(n, m, filesystem) \ + VFS_AUTO_MOUNT(filesystem, VFS_MTD(mtd_sdmmc_dev ##n), VFS_DEFAULT_SD(1), m) +#else +#define MTD_SDMMC_DEV_FS(n, m, filesystem) \ + VFS_AUTO_MOUNT(filesystem, VFS_MTD(mtd_sdmmc_dev ##n), VFS_DEFAULT_SD(n), m) +#endif + +MTD_SDMMC_DEV(0, CONFIG_SDMMC_GENERIC_MTD_OFFSET); +#ifdef MODULE_FATFS_VFS +MTD_SDMMC_DEV_FS(0, CONFIG_SDMMC_GENERIC_MTD_OFFSET, fatfs); +#endif + +#endif diff --git a/sys/include/vfs_default.h b/sys/include/vfs_default.h index b34ed9d35a..f314b5b160 100644 --- a/sys/include/vfs_default.h +++ b/sys/include/vfs_default.h @@ -71,7 +71,8 @@ extern "C" { * This can be written to by applications */ #ifndef VFS_DEFAULT_DATA -#if IS_USED(MODULE_MTD_MCI) || IS_USED(MODULE_MTD_SDCARD) || IS_USED(MODULE_SAM0_SDHC) +#if IS_USED(MODULE_MTD_MCI) || IS_USED(MODULE_MTD_SDCARD) || \ + IS_USED(MODULE_SAM0_SDHC) || IS_USED(MODULE_MTD_SDMMC) #define VFS_DEFAULT_DATA VFS_DEFAULT_SD(0) #else #define VFS_DEFAULT_DATA VFS_DEFAULT_NVM(0)