diff --git a/drivers/include/mtd.h b/drivers/include/mtd.h index 5ec7c18271..8431fef24e 100644 --- a/drivers/include/mtd.h +++ b/drivers/include/mtd.h @@ -101,6 +101,27 @@ struct mtd_desc { uint32_t addr, uint32_t size); + /** + * @brief Read from the Memory Technology Device (MTD) using + * pagewise addressing. + * + * @p offset should not exceed the page size + * + * @param[in] dev Pointer to the selected driver + * @param[out] buff Pointer to the data buffer to store read data + * @param[in] page Page number to start reading from + * @param[in] offset Byte offset from the start of the page + * @param[in] size Number of bytes + * + * @return number of bytes read on success + * @return < 0 value on error + */ + int (*read_page)(mtd_dev_t *dev, + void *buff, + uint32_t page, + uint32_t offset, + uint32_t size); + /** * @brief Write to the Memory Technology Device (MTD) * @@ -120,6 +141,27 @@ struct mtd_desc { uint32_t addr, uint32_t size); + /** + * @brief Write to the Memory Technology Device (MTD) using + * pagewise addressing. + * + * @p offset should not exceed the page size + * + * @param[in] dev Pointer to the selected driver + * @param[out] buff Pointer to the data to be written + * @param[in] page Page number to start writing to + * @param[in] offset Byte offset from the start of the page + * @param[in] size Number of bytes + * + * @return bytes written on success + * @return < 0 value on error + */ + int (*write_page)(mtd_dev_t *dev, + const void *buff, + uint32_t page, + uint32_t offset, + uint32_t size); + /** * @brief Erase sector(s) over the Memory Technology Device (MTD) * @@ -136,6 +178,21 @@ struct mtd_desc { uint32_t addr, uint32_t size); + /** + * @brief Erase sector(s) of the Memory Technology Device (MTD) + * + * @param[in] dev Pointer to the selected driver + * @param[in] sector the first sector number to erase + + * @param[in] count Number of sectors to erase + * + * @return 0 on success + * @return < 0 value on error + */ + int (*erase_sector)(mtd_dev_t *dev, + uint32_t sector, + uint32_t count); + /** * @brief Control power of Memory Technology Device (MTD) * @@ -176,6 +233,29 @@ int mtd_init(mtd_dev_t *mtd); */ int mtd_read(mtd_dev_t *mtd, void *dest, uint32_t addr, uint32_t count); +/** + * @brief Read data from a MTD device with pagewise addressing + * + * The MTD layer will take care of splitting up the transaction into multiple + * reads if it is required by the underlying storage media. + * + * @p offset must be smaller than the page size + * + * @param mtd the device to read from + * @param[out] dest the buffer to fill in + * @param[in] page Page number to start reading from + * @param[in] offset offset from the start of the page (in bytes) + * @param[in] size the number of bytes to read + * + * @return 0 on success + * @return < 0 if an error occurred + * @return -ENODEV if @p mtd is not a valid device + * @return -ENOTSUP if operation is not supported on @p mtd + * @return -EOVERFLOW if @p addr or @p count are not valid, i.e. outside memory + * @return -EIO if I/O error occurred + */ +int mtd_read_page(mtd_dev_t *mtd, void *dest, uint32_t page, uint32_t offset, uint32_t size); + /** * @brief Write data to a MTD device * @@ -199,6 +279,31 @@ int mtd_read(mtd_dev_t *mtd, void *dest, uint32_t addr, uint32_t count); */ int mtd_write(mtd_dev_t *mtd, const void *src, uint32_t addr, uint32_t count); +/** + * @brief Write data to a MTD device with pagewise addressing + * + * The MTD layer will take care of splitting up the transaction into multiple + * writes if it is required by the underlying storage media. + * + * @p offset must be smaller than the page size + * + * + * @param mtd the device to write to + * @param[in] src the buffer to write + * @param[in] page Page number to start writing to + * @param[in] offset byte offset from the start of the page + * @param[in] size the number of bytes to write + * + * @return 0 on success + * @return < 0 if an error occurred + * @return -ENODEV if @p mtd is not a valid device + * @return -ENOTSUP if operation is not supported on @p mtd + * @return -EOVERFLOW if @p addr or @p count are not valid, i.e. outside memory, + * @return -EIO if I/O error occurred + * @return -EINVAL if parameters are invalid + */ +int mtd_write_page(mtd_dev_t *mtd, const void *src, uint32_t page, uint32_t offset, uint32_t size); + /** * @brief Erase sectors of a MTD device * @@ -217,6 +322,22 @@ int mtd_write(mtd_dev_t *mtd, const void *src, uint32_t addr, uint32_t count); */ int mtd_erase(mtd_dev_t *mtd, uint32_t addr, uint32_t count); +/** + * @brief Erase sectors of a MTD device + * + * @param mtd the device to erase + * @param[in] sector the first sector number to erase + * @param[in] num the number of sectors to erase + * + * @return 0 if erase successful + * @return < 0 if an error occurred + * @return -ENODEV if @p mtd is not a valid device + * @return -ENOTSUP if operation is not supported on @p mtd + * @return -EOVERFLOW if @p addr or @p sector are not valid, i.e. outside memory + * @return -EIO if I/O error occurred + */ +int mtd_erase_sector(mtd_dev_t *mtd, uint32_t sector, uint32_t num); + /** * @brief Set power mode on a MTD device * diff --git a/drivers/mtd/mtd.c b/drivers/mtd/mtd.c index 7f8d8570a8..e19c233693 100644 --- a/drivers/mtd/mtd.c +++ b/drivers/mtd/mtd.c @@ -18,8 +18,12 @@ * @author Vincent Dupont */ +#include #include +#include +#include +#include "bitarithm.h" #include "mtd.h" int mtd_init(mtd_dev_t *mtd) @@ -42,12 +46,62 @@ int mtd_read(mtd_dev_t *mtd, void *dest, uint32_t addr, uint32_t count) return -ENODEV; } - if (mtd->driver->read) { - return mtd->driver->read(mtd, dest, addr, count); + /* page size is always a power of two */ + const uint32_t page_shift = bitarithm_msb(mtd->page_size); + const uint32_t page_mask = mtd->page_size - 1; + + return mtd_read_page(mtd, dest, addr >> page_shift, addr & page_mask, count); +} + +int mtd_read_page(mtd_dev_t *mtd, void *dest, uint32_t page, uint32_t offset, + uint32_t count) +{ + if (!mtd || !mtd->driver) { + return -ENODEV; } - else { - return -ENOTSUP; + + if (mtd->driver->read_page == NULL) { + /* TODO: remove when all backends implement read_page */ + if (mtd->driver->read) { + return mtd->driver->read(mtd, dest, mtd->page_size * page + offset, count); + } else { + return -ENOTSUP; + } } + + /* Implementation assumes page size is <= INT_MAX and a power of two. */ + /* We didn't find hardware yet where this is not true. */ + assert(mtd->page_size <= INT_MAX); + assert(bitarithm_bits_set(mtd->page_size) == 1); + + /* page size is always a power of two */ + const uint32_t page_shift = bitarithm_msb(mtd->page_size); + const uint32_t page_mask = mtd->page_size - 1; + + page += offset >> page_shift; + offset = offset & page_mask; + + char *_dst = dest; + + while (count) { + int read_bytes = mtd->driver->read_page(mtd, _dst, page, offset, count); + + if (read_bytes < 0) { + return read_bytes; + } + + count -= read_bytes; + + if (count == 0) { + break; + } + + _dst += read_bytes; + page += (offset + read_bytes) >> page_shift; + offset = (offset + read_bytes) & page_mask; + } + + return 0; } int mtd_write(mtd_dev_t *mtd, const void *src, uint32_t addr, uint32_t count) @@ -56,12 +110,62 @@ int mtd_write(mtd_dev_t *mtd, const void *src, uint32_t addr, uint32_t count) return -ENODEV; } - if (mtd->driver->write) { - return mtd->driver->write(mtd, src, addr, count); + /* page size is always a power of two */ + const uint32_t page_shift = bitarithm_msb(mtd->page_size); + const uint32_t page_mask = mtd->page_size - 1; + + return mtd_write_page(mtd, src, addr >> page_shift, addr & page_mask, count); +} + +int mtd_write_page(mtd_dev_t *mtd, const void *src, uint32_t page, uint32_t offset, + uint32_t count) +{ + if (!mtd || !mtd->driver) { + return -ENODEV; } - else { - return -ENOTSUP; + + if (mtd->driver->write_page == NULL) { + /* TODO: remove when all backends implement write_page */ + if (mtd->driver->write) { + return mtd->driver->write(mtd, src, mtd->page_size * page + offset, count); + } else { + return -ENOTSUP; + } } + + /* Implementation assumes page size is <= INT_MAX and a power of two. */ + /* We didn't find hardware yet where this is not true. */ + assert(mtd->page_size <= INT_MAX); + assert(bitarithm_bits_set(mtd->page_size) == 1); + + /* page size is always a power of two */ + const uint32_t page_shift = bitarithm_msb(mtd->page_size); + const uint32_t page_mask = mtd->page_size - 1; + + page += offset >> page_shift; + offset = offset & page_mask; + + const char *_src = src; + + while (count) { + int written = mtd->driver->write_page(mtd, _src, page, offset, count); + + if (written < 0) { + return written; + } + + count -= written; + + if (count == 0) { + break; + } + + _src += written; + page += (offset + written) >> page_shift; + offset = (offset + written) & page_mask; + } + + return 0; } int mtd_erase(mtd_dev_t *mtd, uint32_t addr, uint32_t count) @@ -70,12 +174,42 @@ int mtd_erase(mtd_dev_t *mtd, uint32_t addr, uint32_t count) return -ENODEV; } - if (mtd->driver->erase) { - return mtd->driver->erase(mtd, addr, count); + uint32_t sector_size = mtd->pages_per_sector * mtd->page_size; + + if (count % sector_size) { + return -EOVERFLOW; } - else { - return -ENOTSUP; + + if (addr % sector_size) { + return -EOVERFLOW; } + + return mtd_erase_sector(mtd, addr / sector_size, count / sector_size); +} + +int mtd_erase_sector(mtd_dev_t *mtd, uint32_t sector, uint32_t count) +{ + if (!mtd || !mtd->driver) { + return -ENODEV; + } + + if (sector >= mtd->sector_count) { + return -EOVERFLOW; + } + + if (mtd->driver->erase_sector == NULL) { + /* TODO: remove when all backends implement erase_sector */ + if (mtd->driver->erase) { + uint32_t sector_size = mtd->pages_per_sector * mtd->page_size; + return mtd->driver->erase(mtd, + sector * sector_size, + count * sector_size); + } else { + return -ENOTSUP; + } + } + + return mtd->driver->erase_sector(mtd, sector, count); } int mtd_power(mtd_dev_t *mtd, enum mtd_power_state power)