Adafruit_CPFS/Adafruit_CPFS.cpp
2025-06-03 18:19:35 -04:00

227 lines
7.7 KiB
C++

// SPDX-FileCopyrightText: 2023 P Burgess for Adafruit Industries
//
// SPDX-License-Identifier: MIT
/*!
* @file Adafruit_CPFS.cpp
*
* This is a barebones library to:
*
* - Make a CircuitPython-capable board's flash filesystem accessible to
* Arduino code.
* - Make this same drive accessible to a host computer over USB.
*
* This is an "80/20" library to cover the most common use case, with least
* code and documentation, for non-technical users: if a board supports
* CircuitPython, then Arduino code and a host computer can both access that
* drive. Flash formatting is done by installing CircuitPython once
* (pre-built for just about everything), no special steps. That's it.
* NOT for SD cards, special flash partitioning, etc. Those can always be
* implemented manually using the Adafruit_TinyUSB library, but this is
* not the code for it. Keeping it really simple.
*
* Adafruit invests time and resources providing this open source code,
* please support Adafruit and open-source hardware by purchasing
* products from Adafruit!
*
* Written by Phil "Paint Your Dragon" Burgess for Adafruit Industries.
*
* MIT license, all text here must be included in any redistribution.
*
*/
#if defined(USE_TINYUSB) || defined(ESP32)
#include "Adafruit_CPFS.h"
#include <Adafruit_SPIFlash.h>
#include <Adafruit_TinyUSB.h>
#if defined(_SAMD21_)
#include <Adafruit_InternalFlash.h>
// These apply to M0 boards only, ignored elsewhere:
#define INTERNAL_FLASH_FS_SIZE (64 * 1024)
#define INTERNAL_FLASH_FS_START (0x00040000 - 256 - 0 - INTERNAL_FLASH_FS_SIZE)
#endif
// Library state is maintained in a few global variables (rather than in the
// Adafruit_CPFS class) as there's only one filesystem instance anyway, and
// also that the mass storage callbacks are C and require access to this info.
// For several major board types, the correct flash transport to use
// is known at compile-time:
#if defined(ARDUINO_ARCH_ESP32)
static Adafruit_FlashTransport_ESP32 _transport;
#elif defined(ARDUINO_ARCH_RP2040)
static Adafruit_FlashTransport_RP2040_CPY *_transport;
#elif defined(EXTERNAL_FLASH_USE_QSPI)
static Adafruit_FlashTransport_QSPI _transport;
#elif defined(EXTERNAL_FLASH_USE_CS) && defined(EXTERNAL_FLASH_USE_SPI)
static Adafruit_FlashTransport_SPI _transport(EXTERNAL_FLASH_USE_CS,
EXTERNAL_FLASH_USE_SPI);
#else
// If not one of the above board types, nor EXTERNAL_FLASH_USE_* defined,
// it's probably a SAMD21. Some can be "Haxpress" modified to add external
// SPI flash & run a special CircuitPython build, but Arduino IDE lacks a
// distinct special board select...at compile-time, indistinguishable from
// a stock M0 board, could go either way. Thus, the transport and flash
// members are pointers, initialized at run-time depending on arguments
// passed (or not) to the begin() function.
#define HAXPRESS
static Adafruit_FlashTransport_SPI *_transport; // Unused if internal
static void *_flash; // Is cast internal/SPI as needed in callbacks
#endif
#if !defined HAXPRESS
static Adafruit_SPIFlash *_flash;
#endif
static Adafruit_USBD_MSC _usb_msc;
static FatVolume _fatfs;
static bool _started = 0;
static bool _changed = 0;
#if defined(HAXPRESS)
// On Haxpress-capable boards, flash type (internal vs SPI) isn't known
// at compile time, so callbacks are provided for both, and one set or
// other is installed in begin().
static int32_t msc_read_cb_internal(uint32_t lba, void *buffer,
uint32_t bufsize) {
return ((Adafruit_InternalFlash *)_flash)
->readBlocks(lba, (uint8_t *)buffer, bufsize / 512)
? bufsize
: -1;
}
static int32_t msc_write_cb_internal(uint32_t lba, uint8_t *buffer,
uint32_t bufsize) {
_changed = 1;
return ((Adafruit_InternalFlash *)_flash)
->writeBlocks(lba, buffer, bufsize / 512)
? bufsize
: -1;
}
static void msc_flush_cb_internal(void) {
((Adafruit_InternalFlash *)_flash)->syncBlocks();
_fatfs.cacheClear();
}
static int32_t msc_read_cb_spi(uint32_t lba, void *buffer, uint32_t bufsize) {
return ((Adafruit_SPIFlash *)_flash)
->readBlocks(lba, (uint8_t *)buffer, bufsize / 512)
? bufsize
: -1;
}
static int32_t msc_write_cb_spi(uint32_t lba, uint8_t *buffer,
uint32_t bufsize) {
_changed = 1;
return ((Adafruit_SPIFlash *)_flash)->writeBlocks(lba, buffer, bufsize / 512)
? bufsize
: -1;
}
static void msc_flush_cb_spi(void) {
((Adafruit_SPIFlash *)_flash)->syncBlocks();
_fatfs.cacheClear();
}
#else
// Flash type is known at compile time. Simple callbacks.
static int32_t msc_read_cb(uint32_t lba, void *buffer, uint32_t bufsize) {
return _flash->readBlocks(lba, (uint8_t *)buffer, bufsize / 512) ? bufsize
: -1;
}
static int32_t msc_write_cb(uint32_t lba, uint8_t *buffer, uint32_t bufsize) {
_changed = 1;
return _flash->writeBlocks(lba, buffer, bufsize / 512) ? bufsize : -1;
}
static void msc_flush_cb(void) {
_flash->syncBlocks();
_fatfs.cacheClear();
}
#endif // end !HAXPRESS
FatVolume *Adafruit_CPFS::begin(bool msc, int cs, void *spi, bool idle) {
if (_started)
return &_fatfs; // Don't re-init if already running
_started = 1;
#if defined(HAXPRESS)
if ((cs >= 0) && (spi != NULL)) { // External flash
if ((_transport = new Adafruit_FlashTransport_SPI(cs, (SPIClass *)spi))) {
if ((_flash = (void *)new Adafruit_SPIFlash(_transport))) {
((Adafruit_SPIFlash *)_flash)->begin();
if (msc) {
_usb_msc.setID("Adafruit", "External Flash", "1.0");
_usb_msc.setReadWriteCallback(msc_read_cb_spi, msc_write_cb_spi,
msc_flush_cb_spi);
_usb_msc.setCapacity(((Adafruit_SPIFlash *)_flash)->size() / 512,
512);
_usb_msc.setUnitReady(true);
_usb_msc.begin();
}
if (_fatfs.begin((Adafruit_SPIFlash *)_flash))
return &_fatfs;
} // end if new flash
} // end if new transport
} else { // Internal flash
if ((_flash = (void *)new Adafruit_InternalFlash(INTERNAL_FLASH_FS_START,
INTERNAL_FLASH_FS_SIZE))) {
((Adafruit_InternalFlash *)_flash)->begin();
if (msc) {
_usb_msc.setID("Adafruit", "Internal Flash", "1.0");
_usb_msc.setReadWriteCallback(
msc_read_cb_internal, msc_write_cb_internal, msc_flush_cb_internal);
_usb_msc.setCapacity(((Adafruit_InternalFlash *)_flash)->size() / 512,
512);
_usb_msc.setUnitReady(true);
_usb_msc.begin();
}
if (_fatfs.begin((Adafruit_InternalFlash *)_flash))
return &_fatfs;
} // end if new flash
}
#else
#if defined(ARDUINO_ARCH_RP2040)
if ((_transport = new Adafruit_FlashTransport_RP2040_CPY(idle))) {
if ((_flash = new Adafruit_SPIFlash(_transport))) {
#else
{ // _transport is declared globally, no test needed, pass address-of
if ((_flash = new Adafruit_SPIFlash(&_transport))) {
#endif
_flash->begin();
if (msc) {
_usb_msc.setID("Adafruit", "Onboard Flash", "1.0");
_usb_msc.setReadWriteCallback(msc_read_cb, msc_write_cb, msc_flush_cb);
_usb_msc.setCapacity(_flash->size() / 512, 512);
_usb_msc.setUnitReady(true);
_usb_msc.begin();
}
if (_fatfs.begin(_flash))
return &_fatfs;
} // end if new flash
} // end if new transport
#endif // end HAXPRESS
_started = 0;
return NULL;
}
bool Adafruit_CPFS::changed(void) { return _changed; }
void Adafruit_CPFS::change_ack(void) { _changed = 0; }
#endif // end USE_TINYUSB || ESP32