[FL-2435] SD over SPI improvements (#2204)
* get rid of BSP layer * sector_cache: init in any case * new sd-spi driver: init * Delete stm32_adafruit_sd.c.old * Delete spi_sd_hal.c.old * Storage: faster api lock primitive * Threads: priority control * Flags: correct error code * Spi: dma mode * SD-card: use DMA for big blocks of SPI data * Fix wrong SD_TOKEN_START_DATA_MULTIPLE_BLOCK_WRITE value * do not memset cache if it is NULL * remove top-level timeouts * sd hal: disable debug * Furi HAL: DMA * Furi HAL RFID: use furi_hal_dma * Furi HAL DMA: tests * Furi HAL DMA: docs * rollback "Furi HAL RFID: use furi_hal_dma" * 4 channels taken from DMA manager for core HAL. * Furi HAL DMA: live fast, die young * RPC tests: increase message buffer * SPI HAL: use second DMA instance * sd hal: new CID getter * SPI hal: use non-DMA version if kernel is not running * IR hal: generalize DMA definition * storage: add CID data to sd info * RFID hal: generalize DMA definition * SUBGHZ hal: generalize DMA definition. Core hal moved to DMA2. * Storage: small optimizations, removal of extra mutex * Storage: redundant macro * SD hal: more timeouts * SPI hal: DMA init * Target: make furi_hal_spi_dma_init symbol private * Update F18 api symbols Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
This commit is contained in:
		
							parent
							
								
									224d0aefe4
								
							
						
					
					
						commit
						e3d473bf42
					
				| @ -89,7 +89,7 @@ static void test_rpc_setup(void) { | ||||
|     } | ||||
|     furi_check(rpc_session[0].session); | ||||
| 
 | ||||
|     rpc_session[0].output_stream = furi_stream_buffer_alloc(1000, 1); | ||||
|     rpc_session[0].output_stream = furi_stream_buffer_alloc(4096, 1); | ||||
|     rpc_session_set_send_bytes_callback(rpc_session[0].session, output_bytes_callback); | ||||
|     rpc_session[0].close_session_semaphore = xSemaphoreCreateBinary(); | ||||
|     rpc_session[0].terminate_semaphore = xSemaphoreCreateBinary(); | ||||
|  | ||||
| @ -1,6 +1,5 @@ | ||||
| #include <furi.h> | ||||
| #include <furi_hal.h> | ||||
| #include <stm32_adafruit_sd.h> | ||||
| 
 | ||||
| #include <cli/cli.h> | ||||
| #include <lib/toolbox/args.h> | ||||
| @ -61,28 +60,26 @@ static void storage_cli_info(Cli* cli, FuriString* path) { | ||||
|         } | ||||
|     } else if(furi_string_cmp_str(path, STORAGE_EXT_PATH_PREFIX) == 0) { | ||||
|         SDInfo sd_info; | ||||
|         SD_CID sd_cid; | ||||
|         FS_Error error = storage_sd_info(api, &sd_info); | ||||
|         BSP_SD_GetCIDRegister(&sd_cid); | ||||
| 
 | ||||
|         if(error != FSE_OK) { | ||||
|             storage_cli_print_error(error); | ||||
|         } else { | ||||
|             printf( | ||||
|                 "Label: %s\r\nType: %s\r\n%luKiB total\r\n%luKiB free\r\n" | ||||
|                 "%02x%2.2s %5.5s %i.%i\r\nSN:%04lx %02i/%i\r\n", | ||||
|                 "%02x%s %s v%i.%i\r\nSN:%04lx %02i/%i\r\n", | ||||
|                 sd_info.label, | ||||
|                 sd_api_get_fs_type_text(sd_info.fs_type), | ||||
|                 sd_info.kb_total, | ||||
|                 sd_info.kb_free, | ||||
|                 sd_cid.ManufacturerID, | ||||
|                 sd_cid.OEM_AppliID, | ||||
|                 sd_cid.ProdName, | ||||
|                 sd_cid.ProdRev >> 4, | ||||
|                 sd_cid.ProdRev & 0xf, | ||||
|                 sd_cid.ProdSN, | ||||
|                 sd_cid.ManufactMonth, | ||||
|                 sd_cid.ManufactYear + 2000); | ||||
|                 sd_info.manufacturer_id, | ||||
|                 sd_info.oem_id, | ||||
|                 sd_info.product_name, | ||||
|                 sd_info.product_revision_major, | ||||
|                 sd_info.product_revision_minor, | ||||
|                 sd_info.product_serial_number, | ||||
|                 sd_info.manufacturing_month, | ||||
|                 sd_info.manufacturing_year); | ||||
|         } | ||||
|     } else { | ||||
|         storage_cli_print_usage(); | ||||
|  | ||||
| @ -12,9 +12,7 @@ | ||||
| 
 | ||||
| #define TAG "StorageAPI" | ||||
| 
 | ||||
| #define S_API_PROLOGUE                                     \ | ||||
|     FuriSemaphore* semaphore = furi_semaphore_alloc(1, 0); \ | ||||
|     furi_check(semaphore != NULL); | ||||
| #define S_API_PROLOGUE FuriApiLock lock = api_lock_alloc_locked(); | ||||
| 
 | ||||
| #define S_FILE_API_PROLOGUE           \ | ||||
|     Storage* storage = file->storage; \ | ||||
| @ -24,13 +22,12 @@ | ||||
|     furi_check(                                                                      \ | ||||
|         furi_message_queue_put(storage->message_queue, &message, FuriWaitForever) == \ | ||||
|         FuriStatusOk);                                                               \ | ||||
|     furi_semaphore_acquire(semaphore, FuriWaitForever);                              \ | ||||
|     furi_semaphore_free(semaphore); | ||||
|     api_lock_wait_unlock_and_free(lock) | ||||
| 
 | ||||
| #define S_API_MESSAGE(_command)      \ | ||||
|     SAReturn return_data;            \ | ||||
|     StorageMessage message = {       \ | ||||
|         .semaphore = semaphore,      \ | ||||
|         .lock = lock,                \ | ||||
|         .command = _command,         \ | ||||
|         .data = &data,               \ | ||||
|         .return_data = &return_data, \ | ||||
|  | ||||
| @ -31,29 +31,13 @@ void storage_file_clear(StorageFile* obj) { | ||||
| /****************** storage data ******************/ | ||||
| 
 | ||||
| void storage_data_init(StorageData* storage) { | ||||
|     storage->mutex = furi_mutex_alloc(FuriMutexTypeNormal); | ||||
|     furi_check(storage->mutex != NULL); | ||||
|     storage->data = NULL; | ||||
|     storage->status = StorageStatusNotReady; | ||||
|     StorageFileList_init(storage->files); | ||||
| } | ||||
| 
 | ||||
| bool storage_data_lock(StorageData* storage) { | ||||
|     return (furi_mutex_acquire(storage->mutex, FuriWaitForever) == FuriStatusOk); | ||||
| } | ||||
| 
 | ||||
| bool storage_data_unlock(StorageData* storage) { | ||||
|     return (furi_mutex_release(storage->mutex) == FuriStatusOk); | ||||
| } | ||||
| 
 | ||||
| StorageStatus storage_data_status(StorageData* storage) { | ||||
|     StorageStatus status; | ||||
| 
 | ||||
|     storage_data_lock(storage); | ||||
|     status = storage->status; | ||||
|     storage_data_unlock(storage); | ||||
| 
 | ||||
|     return status; | ||||
|     return storage->status; | ||||
| } | ||||
| 
 | ||||
| const char* storage_data_status_text(StorageData* storage) { | ||||
|  | ||||
| @ -38,8 +38,6 @@ void storage_file_set(StorageFile* obj, const StorageFile* src); | ||||
| void storage_file_clear(StorageFile* obj); | ||||
| 
 | ||||
| void storage_data_init(StorageData* storage); | ||||
| bool storage_data_lock(StorageData* storage); | ||||
| bool storage_data_unlock(StorageData* storage); | ||||
| StorageStatus storage_data_status(StorageData* storage); | ||||
| const char* storage_data_status_text(StorageData* storage); | ||||
| void storage_data_timestamp(StorageData* storage); | ||||
| @ -57,7 +55,6 @@ struct StorageData { | ||||
|     const FS_Api* fs_api; | ||||
|     StorageApi api; | ||||
|     void* data; | ||||
|     FuriMutex* mutex; | ||||
|     StorageStatus status; | ||||
|     StorageFileList_t files; | ||||
|     uint32_t timestamp; | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| #pragma once | ||||
| #include <furi.h> | ||||
| #include <toolbox/api_lock.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| @ -130,7 +131,7 @@ typedef enum { | ||||
| } StorageCommand; | ||||
| 
 | ||||
| typedef struct { | ||||
|     FuriSemaphore* semaphore; | ||||
|     FuriApiLock lock; | ||||
|     StorageCommand command; | ||||
|     SAData* data; | ||||
|     SAReturn* return_data; | ||||
|  | ||||
| @ -2,15 +2,7 @@ | ||||
| #include <m-list.h> | ||||
| #include <m-dict.h> | ||||
| 
 | ||||
| #define FS_CALL(_storage, _fn)   \ | ||||
|     storage_data_lock(_storage); \ | ||||
|     ret = _storage->fs_api->_fn; \ | ||||
|     storage_data_unlock(_storage); | ||||
| 
 | ||||
| #define ST_CALL(_storage, _fn)   \ | ||||
|     storage_data_lock(_storage); \ | ||||
|     ret = _storage->api._fn;     \ | ||||
|     storage_data_unlock(_storage); | ||||
| #define FS_CALL(_storage, _fn) ret = _storage->fs_api->_fn; | ||||
| 
 | ||||
| static StorageData* storage_get_storage_by_type(Storage* app, StorageType type) { | ||||
|     furi_check(type == ST_EXT || type == ST_INT); | ||||
| @ -44,16 +36,11 @@ static const char* remove_vfs(const char* path) { | ||||
| 
 | ||||
| static StorageType storage_get_type_by_path(Storage* app, const char* path) { | ||||
|     StorageType type = ST_ERROR; | ||||
|     if(strlen(path) >= strlen(STORAGE_EXT_PATH_PREFIX) && | ||||
|        memcmp(path, STORAGE_EXT_PATH_PREFIX, strlen(STORAGE_EXT_PATH_PREFIX)) == 0) { | ||||
|     if(memcmp(path, STORAGE_EXT_PATH_PREFIX, strlen(STORAGE_EXT_PATH_PREFIX)) == 0) { | ||||
|         type = ST_EXT; | ||||
|     } else if( | ||||
|         strlen(path) >= strlen(STORAGE_INT_PATH_PREFIX) && | ||||
|         memcmp(path, STORAGE_INT_PATH_PREFIX, strlen(STORAGE_INT_PATH_PREFIX)) == 0) { | ||||
|     } else if(memcmp(path, STORAGE_INT_PATH_PREFIX, strlen(STORAGE_INT_PATH_PREFIX)) == 0) { | ||||
|         type = ST_INT; | ||||
|     } else if( | ||||
|         strlen(path) >= strlen(STORAGE_ANY_PATH_PREFIX) && | ||||
|         memcmp(path, STORAGE_ANY_PATH_PREFIX, strlen(STORAGE_ANY_PATH_PREFIX)) == 0) { | ||||
|     } else if(memcmp(path, STORAGE_ANY_PATH_PREFIX, strlen(STORAGE_ANY_PATH_PREFIX)) == 0) { | ||||
|         type = ST_ANY; | ||||
|     } | ||||
| 
 | ||||
| @ -68,21 +55,15 @@ static StorageType storage_get_type_by_path(Storage* app, const char* path) { | ||||
| } | ||||
| 
 | ||||
| static void storage_path_change_to_real_storage(FuriString* path, StorageType real_storage) { | ||||
|     if(memcmp( | ||||
|            furi_string_get_cstr(path), STORAGE_ANY_PATH_PREFIX, strlen(STORAGE_ANY_PATH_PREFIX)) == | ||||
|        0) { | ||||
|     if(furi_string_search(path, STORAGE_ANY_PATH_PREFIX) == 0) { | ||||
|         switch(real_storage) { | ||||
|         case ST_EXT: | ||||
|             furi_string_set_char(path, 0, STORAGE_EXT_PATH_PREFIX[0]); | ||||
|             furi_string_set_char(path, 1, STORAGE_EXT_PATH_PREFIX[1]); | ||||
|             furi_string_set_char(path, 2, STORAGE_EXT_PATH_PREFIX[2]); | ||||
|             furi_string_set_char(path, 3, STORAGE_EXT_PATH_PREFIX[3]); | ||||
|             furi_string_replace_at( | ||||
|                 path, 0, strlen(STORAGE_EXT_PATH_PREFIX), STORAGE_EXT_PATH_PREFIX); | ||||
|             break; | ||||
|         case ST_INT: | ||||
|             furi_string_set_char(path, 0, STORAGE_INT_PATH_PREFIX[0]); | ||||
|             furi_string_set_char(path, 1, STORAGE_INT_PATH_PREFIX[1]); | ||||
|             furi_string_set_char(path, 2, STORAGE_INT_PATH_PREFIX[2]); | ||||
|             furi_string_set_char(path, 3, STORAGE_INT_PATH_PREFIX[3]); | ||||
|             furi_string_replace_at( | ||||
|                 path, 0, strlen(STORAGE_INT_PATH_PREFIX), STORAGE_INT_PATH_PREFIX); | ||||
|             break; | ||||
|         default: | ||||
|             break; | ||||
| @ -604,7 +585,7 @@ void storage_process_message_internal(Storage* app, StorageMessage* message) { | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     furi_semaphore_release(message->semaphore); | ||||
|     api_lock_unlock(message->lock); | ||||
| } | ||||
| 
 | ||||
| void storage_process_message(Storage* app, StorageMessage* message) { | ||||
|  | ||||
| @ -23,6 +23,16 @@ typedef struct { | ||||
|     uint16_t cluster_size; | ||||
|     uint16_t sector_size; | ||||
|     char label[SD_LABEL_LENGTH]; | ||||
| 
 | ||||
|     uint8_t manufacturer_id; | ||||
|     char oem_id[3]; | ||||
|     char product_name[6]; | ||||
|     uint8_t product_revision_major; | ||||
|     uint8_t product_revision_minor; | ||||
|     uint32_t product_serial_number; | ||||
|     uint8_t manufacturing_month; | ||||
|     uint16_t manufacturing_year; | ||||
| 
 | ||||
|     FS_Error error; | ||||
| } SDInfo; | ||||
| 
 | ||||
|  | ||||
| @ -26,12 +26,10 @@ static FS_Error storage_ext_parse_error(SDError error); | ||||
| 
 | ||||
| static bool sd_mount_card(StorageData* storage, bool notify) { | ||||
|     bool result = false; | ||||
|     uint8_t counter = BSP_SD_MaxMountRetryCount(); | ||||
|     uint8_t counter = sd_max_mount_retry_count(); | ||||
|     uint8_t bsp_result; | ||||
|     SDData* sd_data = storage->data; | ||||
| 
 | ||||
|     storage_data_lock(storage); | ||||
| 
 | ||||
|     while(result == false && counter > 0 && hal_sd_detect()) { | ||||
|         if(notify) { | ||||
|             NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); | ||||
| @ -41,9 +39,9 @@ static bool sd_mount_card(StorageData* storage, bool notify) { | ||||
| 
 | ||||
|         if((counter % 2) == 0) { | ||||
|             // power reset sd card
 | ||||
|             bsp_result = BSP_SD_Init(true); | ||||
|             bsp_result = sd_init(true); | ||||
|         } else { | ||||
|             bsp_result = BSP_SD_Init(false); | ||||
|             bsp_result = sd_init(false); | ||||
|         } | ||||
| 
 | ||||
|         if(bsp_result) { | ||||
| @ -91,7 +89,6 @@ static bool sd_mount_card(StorageData* storage, bool notify) { | ||||
|     } | ||||
| 
 | ||||
|     storage_data_timestamp(storage); | ||||
|     storage_data_unlock(storage); | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| @ -100,14 +97,12 @@ FS_Error sd_unmount_card(StorageData* storage) { | ||||
|     SDData* sd_data = storage->data; | ||||
|     SDError error; | ||||
| 
 | ||||
|     storage_data_lock(storage); | ||||
|     storage->status = StorageStatusNotReady; | ||||
|     error = FR_DISK_ERR; | ||||
| 
 | ||||
|     // TODO do i need to close the files?
 | ||||
| 
 | ||||
|     f_mount(0, sd_data->path, 0); | ||||
|     storage_data_unlock(storage); | ||||
| 
 | ||||
|     return storage_ext_parse_error(error); | ||||
| } | ||||
| 
 | ||||
| @ -120,8 +115,6 @@ FS_Error sd_format_card(StorageData* storage) { | ||||
|     SDData* sd_data = storage->data; | ||||
|     SDError error; | ||||
| 
 | ||||
|     storage_data_lock(storage); | ||||
| 
 | ||||
|     work_area = malloc(_MAX_SS); | ||||
|     error = f_mkfs(sd_data->path, FM_ANY, 0, work_area, _MAX_SS); | ||||
|     free(work_area); | ||||
| @ -138,8 +131,6 @@ FS_Error sd_format_card(StorageData* storage) { | ||||
|         storage->status = StorageStatusOK; | ||||
|     } while(false); | ||||
| 
 | ||||
|     storage_data_unlock(storage); | ||||
| 
 | ||||
|     return storage_ext_parse_error(error); | ||||
| #endif | ||||
| } | ||||
| @ -156,14 +147,12 @@ FS_Error sd_card_info(StorageData* storage, SDInfo* sd_info) { | ||||
|     memset(sd_info, 0, sizeof(SDInfo)); | ||||
| 
 | ||||
|     // get fs info
 | ||||
|     storage_data_lock(storage); | ||||
|     error = f_getlabel(sd_data->path, sd_info->label, NULL); | ||||
|     if(error == FR_OK) { | ||||
| #ifndef FURI_RAM_EXEC | ||||
|         error = f_getfree(sd_data->path, &free_clusters, &fs); | ||||
| #endif | ||||
|     } | ||||
|     storage_data_unlock(storage); | ||||
| 
 | ||||
|     if(error == FR_OK) { | ||||
|         // calculate size
 | ||||
| @ -210,6 +199,20 @@ FS_Error sd_card_info(StorageData* storage, SDInfo* sd_info) { | ||||
| #endif | ||||
|     } | ||||
| 
 | ||||
|     SD_CID cid; | ||||
|     SdSpiStatus status = sd_get_cid(&cid); | ||||
| 
 | ||||
|     if(status == SdSpiStatusOK) { | ||||
|         sd_info->manufacturer_id = cid.ManufacturerID; | ||||
|         memcpy(sd_info->oem_id, cid.OEM_AppliID, sizeof(cid.OEM_AppliID)); | ||||
|         memcpy(sd_info->product_name, cid.ProdName, sizeof(cid.ProdName)); | ||||
|         sd_info->product_revision_major = cid.ProdRev >> 4; | ||||
|         sd_info->product_revision_minor = cid.ProdRev & 0x0F; | ||||
|         sd_info->product_serial_number = cid.ProdSN; | ||||
|         sd_info->manufacturing_year = 2000 + cid.ManufactYear; | ||||
|         sd_info->manufacturing_month = cid.ManufactMonth; | ||||
|     } | ||||
| 
 | ||||
|     return storage_ext_parse_error(error); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -1,5 +1,4 @@ | ||||
| #include "../storage_settings.h" | ||||
| #include <stm32_adafruit_sd.h> | ||||
| 
 | ||||
| static void storage_settings_scene_sd_info_dialog_callback(DialogExResult result, void* context) { | ||||
|     StorageSettings* app = context; | ||||
| @ -12,9 +11,7 @@ void storage_settings_scene_sd_info_on_enter(void* context) { | ||||
|     DialogEx* dialog_ex = app->dialog_ex; | ||||
| 
 | ||||
|     SDInfo sd_info; | ||||
|     SD_CID sd_cid; | ||||
|     FS_Error sd_status = storage_sd_info(app->fs_api, &sd_info); | ||||
|     BSP_SD_GetCIDRegister(&sd_cid); | ||||
| 
 | ||||
|     scene_manager_set_scene_state(app->scene_manager, StorageSettingsSDInfo, sd_status); | ||||
| 
 | ||||
| @ -31,19 +28,19 @@ void storage_settings_scene_sd_info_on_enter(void* context) { | ||||
|         furi_string_printf( | ||||
|             app->text_string, | ||||
|             "Label: %s\nType: %s\n%lu KiB total\n%lu KiB free\n" | ||||
|             "%02X%2.2s %5.5s %i.%i\nSN:%04lX %02i/%i", | ||||
|             "%02X%s %s v%i.%i\nSN:%04lX %02i/%i", | ||||
|             sd_info.label, | ||||
|             sd_api_get_fs_type_text(sd_info.fs_type), | ||||
|             sd_info.kb_total, | ||||
|             sd_info.kb_free, | ||||
|             sd_cid.ManufacturerID, | ||||
|             sd_cid.OEM_AppliID, | ||||
|             sd_cid.ProdName, | ||||
|             sd_cid.ProdRev >> 4, | ||||
|             sd_cid.ProdRev & 0xf, | ||||
|             sd_cid.ProdSN, | ||||
|             sd_cid.ManufactMonth, | ||||
|             sd_cid.ManufactYear + 2000); | ||||
|             sd_info.manufacturer_id, | ||||
|             sd_info.oem_id, | ||||
|             sd_info.product_name, | ||||
|             sd_info.product_revision_major, | ||||
|             sd_info.product_revision_minor, | ||||
|             sd_info.product_serial_number, | ||||
|             sd_info.manufacturing_month, | ||||
|             sd_info.manufacturing_year); | ||||
|         dialog_ex_set_text( | ||||
|             dialog_ex, furi_string_get_cstr(app->text_string), 4, 1, AlignLeft, AlignTop); | ||||
|     } | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| entry,status,name,type,params | ||||
| Version,+,12.1,, | ||||
| Version,+,12.2,, | ||||
| Header,+,applications/services/bt/bt_service/bt.h,, | ||||
| Header,+,applications/services/cli/cli.h,, | ||||
| Header,+,applications/services/cli/cli_vcp.h,, | ||||
| @ -1062,10 +1062,12 @@ Function,+,furi_hal_spi_bus_handle_init,void,FuriHalSpiBusHandle* | ||||
| Function,+,furi_hal_spi_bus_init,void,FuriHalSpiBus* | ||||
| Function,+,furi_hal_spi_bus_rx,_Bool,"FuriHalSpiBusHandle*, uint8_t*, size_t, uint32_t" | ||||
| Function,+,furi_hal_spi_bus_trx,_Bool,"FuriHalSpiBusHandle*, uint8_t*, uint8_t*, size_t, uint32_t" | ||||
| Function,+,furi_hal_spi_bus_trx_dma,_Bool,"FuriHalSpiBusHandle*, uint8_t*, uint8_t*, size_t, uint32_t" | ||||
| Function,+,furi_hal_spi_bus_tx,_Bool,"FuriHalSpiBusHandle*, uint8_t*, size_t, uint32_t" | ||||
| Function,-,furi_hal_spi_config_deinit_early,void, | ||||
| Function,-,furi_hal_spi_config_init,void, | ||||
| Function,-,furi_hal_spi_config_init_early,void, | ||||
| Function,-,furi_hal_spi_dma_init,void, | ||||
| Function,+,furi_hal_spi_release,void,FuriHalSpiBusHandle* | ||||
| Function,+,furi_hal_switch,void,void* | ||||
| Function,+,furi_hal_uart_deinit,void,FuriHalUartId | ||||
| @ -1231,6 +1233,7 @@ Function,+,furi_thread_flags_wait,uint32_t,"uint32_t, uint32_t, uint32_t" | ||||
| Function,+,furi_thread_free,void,FuriThread* | ||||
| Function,+,furi_thread_get_current,FuriThread*, | ||||
| Function,+,furi_thread_get_current_id,FuriThreadId, | ||||
| Function,+,furi_thread_get_current_priority,FuriThreadPriority, | ||||
| Function,+,furi_thread_get_heap_size,size_t,FuriThread* | ||||
| Function,+,furi_thread_get_id,FuriThreadId,FuriThread* | ||||
| Function,+,furi_thread_get_name,const char*,FuriThreadId | ||||
| @ -1244,6 +1247,7 @@ Function,+,furi_thread_mark_as_service,void,FuriThread* | ||||
| Function,+,furi_thread_resume,void,FuriThreadId | ||||
| Function,+,furi_thread_set_callback,void,"FuriThread*, FuriThreadCallback" | ||||
| Function,+,furi_thread_set_context,void,"FuriThread*, void*" | ||||
| Function,+,furi_thread_set_current_priority,void,FuriThreadPriority | ||||
| Function,+,furi_thread_set_name,void,"FuriThread*, const char*" | ||||
| Function,+,furi_thread_set_priority,void,"FuriThread*, FuriThreadPriority" | ||||
| Function,+,furi_thread_set_stack_size,void,"FuriThread*, size_t" | ||||
|  | ||||
| 
 | 
| @ -51,6 +51,7 @@ void furi_hal_init() { | ||||
|     furi_hal_version_init(); | ||||
| 
 | ||||
|     furi_hal_spi_config_init(); | ||||
|     furi_hal_spi_dma_init(); | ||||
| 
 | ||||
|     furi_hal_speaker_init(); | ||||
|     FURI_LOG_I(TAG, "Speaker OK"); | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| entry,status,name,type,params | ||||
| Version,+,12.1,, | ||||
| Version,+,12.2,, | ||||
| Header,+,applications/services/bt/bt_service/bt.h,, | ||||
| Header,+,applications/services/cli/cli.h,, | ||||
| Header,+,applications/services/cli/cli_vcp.h,, | ||||
| @ -1325,10 +1325,12 @@ Function,+,furi_hal_spi_bus_handle_init,void,FuriHalSpiBusHandle* | ||||
| Function,+,furi_hal_spi_bus_init,void,FuriHalSpiBus* | ||||
| Function,+,furi_hal_spi_bus_rx,_Bool,"FuriHalSpiBusHandle*, uint8_t*, size_t, uint32_t" | ||||
| Function,+,furi_hal_spi_bus_trx,_Bool,"FuriHalSpiBusHandle*, uint8_t*, uint8_t*, size_t, uint32_t" | ||||
| Function,+,furi_hal_spi_bus_trx_dma,_Bool,"FuriHalSpiBusHandle*, uint8_t*, uint8_t*, size_t, uint32_t" | ||||
| Function,+,furi_hal_spi_bus_tx,_Bool,"FuriHalSpiBusHandle*, uint8_t*, size_t, uint32_t" | ||||
| Function,-,furi_hal_spi_config_deinit_early,void, | ||||
| Function,-,furi_hal_spi_config_init,void, | ||||
| Function,-,furi_hal_spi_config_init_early,void, | ||||
| Function,-,furi_hal_spi_dma_init,void, | ||||
| Function,+,furi_hal_spi_release,void,FuriHalSpiBusHandle* | ||||
| Function,-,furi_hal_subghz_dump_state,void, | ||||
| Function,+,furi_hal_subghz_flush_rx,void, | ||||
| @ -1524,6 +1526,7 @@ Function,+,furi_thread_flags_wait,uint32_t,"uint32_t, uint32_t, uint32_t" | ||||
| Function,+,furi_thread_free,void,FuriThread* | ||||
| Function,+,furi_thread_get_current,FuriThread*, | ||||
| Function,+,furi_thread_get_current_id,FuriThreadId, | ||||
| Function,+,furi_thread_get_current_priority,FuriThreadPriority, | ||||
| Function,+,furi_thread_get_heap_size,size_t,FuriThread* | ||||
| Function,+,furi_thread_get_id,FuriThreadId,FuriThread* | ||||
| Function,+,furi_thread_get_name,const char*,FuriThreadId | ||||
| @ -1537,6 +1540,7 @@ Function,+,furi_thread_mark_as_service,void,FuriThread* | ||||
| Function,+,furi_thread_resume,void,FuriThreadId | ||||
| Function,+,furi_thread_set_callback,void,"FuriThread*, FuriThreadCallback" | ||||
| Function,+,furi_thread_set_context,void,"FuriThread*, void*" | ||||
| Function,+,furi_thread_set_current_priority,void,FuriThreadPriority | ||||
| Function,+,furi_thread_set_name,void,"FuriThread*, const char*" | ||||
| Function,+,furi_thread_set_priority,void,"FuriThread*, FuriThreadPriority" | ||||
| Function,+,furi_thread_set_stack_size,void,"FuriThread*, size_t" | ||||
|  | ||||
| 
 | 
							
								
								
									
										877
									
								
								firmware/targets/f7/fatfs/sd_spi_io.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										877
									
								
								firmware/targets/f7/fatfs/sd_spi_io.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,877 @@ | ||||
| #include "sd_spi_io.h" | ||||
| #include "sector_cache.h" | ||||
| #include <furi.h> | ||||
| #include <furi_hal.h> | ||||
| #include <furi/core/core_defines.h> | ||||
| 
 | ||||
| // #define SD_SPI_DEBUG 1
 | ||||
| #define TAG "SdSpi" | ||||
| 
 | ||||
| #ifdef SD_SPI_DEBUG | ||||
| #define sd_spi_debug(...) FURI_LOG_I(TAG, __VA_ARGS__) | ||||
| #else | ||||
| #define sd_spi_debug(...) | ||||
| #endif | ||||
| 
 | ||||
| #define SD_CMD_LENGTH 6 | ||||
| #define SD_DUMMY_BYTE 0xFF | ||||
| #define SD_ANSWER_RETRY_COUNT 8 | ||||
| #define SD_IDLE_RETRY_COUNT 100 | ||||
| #define SD_BLOCK_SIZE 512 | ||||
| 
 | ||||
| #define FLAG_SET(x, y) (((x) & (y)) == (y)) | ||||
| 
 | ||||
| static bool sd_high_capacity = false; | ||||
| 
 | ||||
| typedef enum { | ||||
|     SdSpiDataResponceOK = 0x05, | ||||
|     SdSpiDataResponceCRCError = 0x0B, | ||||
|     SdSpiDataResponceWriteError = 0x0D, | ||||
|     SdSpiDataResponceOtherError = 0xFF, | ||||
| } SdSpiDataResponce; | ||||
| 
 | ||||
| typedef struct { | ||||
|     uint8_t r1; | ||||
|     uint8_t r2; | ||||
|     uint8_t r3; | ||||
|     uint8_t r4; | ||||
|     uint8_t r5; | ||||
| } SdSpiCmdAnswer; | ||||
| 
 | ||||
| typedef enum { | ||||
|     SdSpiCmdAnswerTypeR1, | ||||
|     SdSpiCmdAnswerTypeR1B, | ||||
|     SdSpiCmdAnswerTypeR2, | ||||
|     SdSpiCmdAnswerTypeR3, | ||||
|     SdSpiCmdAnswerTypeR4R5, | ||||
|     SdSpiCmdAnswerTypeR7, | ||||
| } SdSpiCmdAnswerType; | ||||
| 
 | ||||
| /*
 | ||||
|     SdSpiCmd and SdSpiToken use non-standard enum value names convention, | ||||
|     because it is more convenient to look for documentation on a specific command. | ||||
|     For example, to find out what the SD_CMD23_SET_BLOCK_COUNT command does, you need to look for | ||||
|     SET_BLOCK_COUNT or CMD23 in the "Part 1 Physical Layer Simplified Specification". | ||||
| 
 | ||||
|     Do not use that naming convention in other places. | ||||
| */ | ||||
| 
 | ||||
| typedef enum { | ||||
|     SD_CMD0_GO_IDLE_STATE = 0, | ||||
|     SD_CMD1_SEND_OP_COND = 1, | ||||
|     SD_CMD8_SEND_IF_COND = 8, | ||||
|     SD_CMD9_SEND_CSD = 9, | ||||
|     SD_CMD10_SEND_CID = 10, | ||||
|     SD_CMD12_STOP_TRANSMISSION = 12, | ||||
|     SD_CMD13_SEND_STATUS = 13, | ||||
|     SD_CMD16_SET_BLOCKLEN = 16, | ||||
|     SD_CMD17_READ_SINGLE_BLOCK = 17, | ||||
|     SD_CMD18_READ_MULT_BLOCK = 18, | ||||
|     SD_CMD23_SET_BLOCK_COUNT = 23, | ||||
|     SD_CMD24_WRITE_SINGLE_BLOCK = 24, | ||||
|     SD_CMD25_WRITE_MULT_BLOCK = 25, | ||||
|     SD_CMD27_PROG_CSD = 27, | ||||
|     SD_CMD28_SET_WRITE_PROT = 28, | ||||
|     SD_CMD29_CLR_WRITE_PROT = 29, | ||||
|     SD_CMD30_SEND_WRITE_PROT = 30, | ||||
|     SD_CMD32_SD_ERASE_GRP_START = 32, | ||||
|     SD_CMD33_SD_ERASE_GRP_END = 33, | ||||
|     SD_CMD34_UNTAG_SECTOR = 34, | ||||
|     SD_CMD35_ERASE_GRP_START = 35, | ||||
|     SD_CMD36_ERASE_GRP_END = 36, | ||||
|     SD_CMD37_UNTAG_ERASE_GROUP = 37, | ||||
|     SD_CMD38_ERASE = 38, | ||||
|     SD_CMD41_SD_APP_OP_COND = 41, | ||||
|     SD_CMD55_APP_CMD = 55, | ||||
|     SD_CMD58_READ_OCR = 58, | ||||
| } SdSpiCmd; | ||||
| 
 | ||||
| /** Data tokens */ | ||||
| typedef enum { | ||||
|     SD_TOKEN_START_DATA_SINGLE_BLOCK_READ = 0xFE, | ||||
|     SD_TOKEN_START_DATA_MULTIPLE_BLOCK_READ = 0xFE, | ||||
|     SD_TOKEN_START_DATA_SINGLE_BLOCK_WRITE = 0xFE, | ||||
|     SD_TOKEN_START_DATA_MULTIPLE_BLOCK_WRITE = 0xFC, | ||||
|     SD_TOKEN_STOP_DATA_MULTIPLE_BLOCK_WRITE = 0xFD, | ||||
| } SdSpiToken; | ||||
| 
 | ||||
| /** R1 answer value */ | ||||
| typedef enum { | ||||
|     SdSpi_R1_NO_ERROR = 0x00, | ||||
|     SdSpi_R1_IN_IDLE_STATE = 0x01, | ||||
|     SdSpi_R1_ERASE_RESET = 0x02, | ||||
|     SdSpi_R1_ILLEGAL_COMMAND = 0x04, | ||||
|     SdSpi_R1_COM_CRC_ERROR = 0x08, | ||||
|     SdSpi_R1_ERASE_SEQUENCE_ERROR = 0x10, | ||||
|     SdSpi_R1_ADDRESS_ERROR = 0x20, | ||||
|     SdSpi_R1_PARAMETER_ERROR = 0x40, | ||||
| } SdSpiR1; | ||||
| 
 | ||||
| /** R2 answer value */ | ||||
| typedef enum { | ||||
|     /* R2 answer value */ | ||||
|     SdSpi_R2_NO_ERROR = 0x00, | ||||
|     SdSpi_R2_CARD_LOCKED = 0x01, | ||||
|     SdSpi_R2_LOCKUNLOCK_ERROR = 0x02, | ||||
|     SdSpi_R2_ERROR = 0x04, | ||||
|     SdSpi_R2_CC_ERROR = 0x08, | ||||
|     SdSpi_R2_CARD_ECC_FAILED = 0x10, | ||||
|     SdSpi_R2_WP_VIOLATION = 0x20, | ||||
|     SdSpi_R2_ERASE_PARAM = 0x40, | ||||
|     SdSpi_R2_OUTOFRANGE = 0x80, | ||||
| } SdSpiR2; | ||||
| 
 | ||||
| static inline void sd_spi_select_card() { | ||||
|     furi_hal_gpio_write(furi_hal_sd_spi_handle->cs, false); | ||||
|     furi_delay_us(10); // Entry guard time for some SD cards
 | ||||
| } | ||||
| 
 | ||||
| static inline void sd_spi_deselect_card() { | ||||
|     furi_delay_us(10); // Exit guard time for some SD cards
 | ||||
|     furi_hal_gpio_write(furi_hal_sd_spi_handle->cs, true); | ||||
| } | ||||
| 
 | ||||
| static void sd_spi_bus_to_ground() { | ||||
|     furi_hal_gpio_init_ex( | ||||
|         furi_hal_sd_spi_handle->miso, | ||||
|         GpioModeOutputPushPull, | ||||
|         GpioPullNo, | ||||
|         GpioSpeedVeryHigh, | ||||
|         GpioAltFnUnused); | ||||
|     furi_hal_gpio_init_ex( | ||||
|         furi_hal_sd_spi_handle->mosi, | ||||
|         GpioModeOutputPushPull, | ||||
|         GpioPullNo, | ||||
|         GpioSpeedVeryHigh, | ||||
|         GpioAltFnUnused); | ||||
|     furi_hal_gpio_init_ex( | ||||
|         furi_hal_sd_spi_handle->sck, | ||||
|         GpioModeOutputPushPull, | ||||
|         GpioPullNo, | ||||
|         GpioSpeedVeryHigh, | ||||
|         GpioAltFnUnused); | ||||
| 
 | ||||
|     sd_spi_select_card(); | ||||
|     furi_hal_gpio_write(furi_hal_sd_spi_handle->miso, false); | ||||
|     furi_hal_gpio_write(furi_hal_sd_spi_handle->mosi, false); | ||||
|     furi_hal_gpio_write(furi_hal_sd_spi_handle->sck, false); | ||||
| } | ||||
| 
 | ||||
| static void sd_spi_bus_rise_up() { | ||||
|     sd_spi_deselect_card(); | ||||
| 
 | ||||
|     furi_hal_gpio_init_ex( | ||||
|         furi_hal_sd_spi_handle->miso, | ||||
|         GpioModeAltFunctionPushPull, | ||||
|         GpioPullUp, | ||||
|         GpioSpeedVeryHigh, | ||||
|         GpioAltFn5SPI2); | ||||
|     furi_hal_gpio_init_ex( | ||||
|         furi_hal_sd_spi_handle->mosi, | ||||
|         GpioModeAltFunctionPushPull, | ||||
|         GpioPullUp, | ||||
|         GpioSpeedVeryHigh, | ||||
|         GpioAltFn5SPI2); | ||||
|     furi_hal_gpio_init_ex( | ||||
|         furi_hal_sd_spi_handle->sck, | ||||
|         GpioModeAltFunctionPushPull, | ||||
|         GpioPullUp, | ||||
|         GpioSpeedVeryHigh, | ||||
|         GpioAltFn5SPI2); | ||||
| } | ||||
| 
 | ||||
| static inline uint8_t sd_spi_read_byte(void) { | ||||
|     uint8_t responce; | ||||
|     furi_check(furi_hal_spi_bus_trx(furi_hal_sd_spi_handle, NULL, &responce, 1, SD_TIMEOUT_MS)); | ||||
|     return responce; | ||||
| } | ||||
| 
 | ||||
| static inline void sd_spi_write_byte(uint8_t data) { | ||||
|     furi_check(furi_hal_spi_bus_trx(furi_hal_sd_spi_handle, &data, NULL, 1, SD_TIMEOUT_MS)); | ||||
| } | ||||
| 
 | ||||
| static inline uint8_t sd_spi_write_and_read_byte(uint8_t data) { | ||||
|     uint8_t responce; | ||||
|     furi_check(furi_hal_spi_bus_trx(furi_hal_sd_spi_handle, &data, &responce, 1, SD_TIMEOUT_MS)); | ||||
|     return responce; | ||||
| } | ||||
| 
 | ||||
| static inline void sd_spi_write_bytes(uint8_t* data, uint32_t size) { | ||||
|     furi_check(furi_hal_spi_bus_trx(furi_hal_sd_spi_handle, data, NULL, size, SD_TIMEOUT_MS)); | ||||
| } | ||||
| 
 | ||||
| static inline void sd_spi_read_bytes(uint8_t* data, uint32_t size) { | ||||
|     furi_check(furi_hal_spi_bus_trx(furi_hal_sd_spi_handle, NULL, data, size, SD_TIMEOUT_MS)); | ||||
| } | ||||
| 
 | ||||
| static inline void sd_spi_write_bytes_dma(uint8_t* data, uint32_t size) { | ||||
|     uint32_t timeout_mul = (size / 512) + 1; | ||||
|     furi_check(furi_hal_spi_bus_trx_dma( | ||||
|         furi_hal_sd_spi_handle, data, NULL, size, SD_TIMEOUT_MS * timeout_mul)); | ||||
| } | ||||
| 
 | ||||
| static inline void sd_spi_read_bytes_dma(uint8_t* data, uint32_t size) { | ||||
|     uint32_t timeout_mul = (size / 512) + 1; | ||||
|     furi_check(furi_hal_spi_bus_trx_dma( | ||||
|         furi_hal_sd_spi_handle, NULL, data, size, SD_TIMEOUT_MS * timeout_mul)); | ||||
| } | ||||
| 
 | ||||
| static uint8_t sd_spi_wait_for_data_and_read(void) { | ||||
|     uint8_t retry_count = SD_ANSWER_RETRY_COUNT; | ||||
|     uint8_t responce; | ||||
| 
 | ||||
|     // Wait until we get a valid data
 | ||||
|     do { | ||||
|         responce = sd_spi_read_byte(); | ||||
|         retry_count--; | ||||
| 
 | ||||
|     } while((responce == SD_DUMMY_BYTE) && retry_count); | ||||
| 
 | ||||
|     return responce; | ||||
| } | ||||
| 
 | ||||
| static SdSpiStatus sd_spi_wait_for_data(uint8_t data, uint32_t timeout_ms) { | ||||
|     FuriHalCortexTimer timer = furi_hal_cortex_timer_get(timeout_ms * 1000); | ||||
|     uint8_t byte; | ||||
| 
 | ||||
|     do { | ||||
|         byte = sd_spi_read_byte(); | ||||
|         if(furi_hal_cortex_timer_is_expired(timer)) { | ||||
|             return SdSpiStatusTimeout; | ||||
|         } | ||||
|     } while((byte != data)); | ||||
| 
 | ||||
|     return SdSpiStatusOK; | ||||
| } | ||||
| 
 | ||||
| static inline void sd_spi_deselect_card_and_purge() { | ||||
|     sd_spi_deselect_card(); | ||||
|     sd_spi_read_byte(); | ||||
| } | ||||
| 
 | ||||
| static inline void sd_spi_purge_crc() { | ||||
|     sd_spi_read_byte(); | ||||
|     sd_spi_read_byte(); | ||||
| } | ||||
| 
 | ||||
| static SdSpiCmdAnswer | ||||
|     sd_spi_send_cmd(SdSpiCmd cmd, uint32_t arg, uint8_t crc, SdSpiCmdAnswerType answer_type) { | ||||
|     uint8_t frame[SD_CMD_LENGTH]; | ||||
|     SdSpiCmdAnswer cmd_answer = { | ||||
|         .r1 = SD_DUMMY_BYTE, | ||||
|         .r2 = SD_DUMMY_BYTE, | ||||
|         .r3 = SD_DUMMY_BYTE, | ||||
|         .r4 = SD_DUMMY_BYTE, | ||||
|         .r5 = SD_DUMMY_BYTE, | ||||
|     }; | ||||
| 
 | ||||
|     // R1 Length = NCS(0)+ 6 Bytes command + NCR(min1 max8) + 1 Bytes answer + NEC(0) = 15bytes
 | ||||
|     // R1b identical to R1 + Busy information
 | ||||
|     // R2 Length = NCS(0)+ 6 Bytes command + NCR(min1 max8) + 2 Bytes answer + NEC(0) = 16bytes
 | ||||
| 
 | ||||
|     frame[0] = ((uint8_t)cmd | 0x40); | ||||
|     frame[1] = (uint8_t)(arg >> 24); | ||||
|     frame[2] = (uint8_t)(arg >> 16); | ||||
|     frame[3] = (uint8_t)(arg >> 8); | ||||
|     frame[4] = (uint8_t)(arg); | ||||
|     frame[5] = (crc | 0x01); | ||||
| 
 | ||||
|     sd_spi_select_card(); | ||||
|     sd_spi_write_bytes(frame, sizeof(frame)); | ||||
| 
 | ||||
|     switch(answer_type) { | ||||
|     case SdSpiCmdAnswerTypeR1: | ||||
|         cmd_answer.r1 = sd_spi_wait_for_data_and_read(); | ||||
|         break; | ||||
|     case SdSpiCmdAnswerTypeR1B: | ||||
|         // TODO: can be wrong, at least for SD_CMD12_STOP_TRANSMISSION you need to purge one byte before reading R1
 | ||||
|         cmd_answer.r1 = sd_spi_wait_for_data_and_read(); | ||||
| 
 | ||||
|         // In general this shenenigans seems suspicious, please double check SD specs if you are using SdSpiCmdAnswerTypeR1B
 | ||||
|         // reassert card
 | ||||
|         sd_spi_deselect_card(); | ||||
|         furi_delay_us(1000); | ||||
|         sd_spi_deselect_card(); | ||||
| 
 | ||||
|         // and wait for it to be ready
 | ||||
|         while(sd_spi_read_byte() != 0xFF) { | ||||
|         }; | ||||
| 
 | ||||
|         break; | ||||
|     case SdSpiCmdAnswerTypeR2: | ||||
|         cmd_answer.r1 = sd_spi_wait_for_data_and_read(); | ||||
|         cmd_answer.r2 = sd_spi_read_byte(); | ||||
|         break; | ||||
|     case SdSpiCmdAnswerTypeR3: | ||||
|     case SdSpiCmdAnswerTypeR7: | ||||
|         cmd_answer.r1 = sd_spi_wait_for_data_and_read(); | ||||
|         cmd_answer.r2 = sd_spi_read_byte(); | ||||
|         cmd_answer.r3 = sd_spi_read_byte(); | ||||
|         cmd_answer.r4 = sd_spi_read_byte(); | ||||
|         cmd_answer.r5 = sd_spi_read_byte(); | ||||
|         break; | ||||
|     default: | ||||
|         break; | ||||
|     } | ||||
|     return cmd_answer; | ||||
| } | ||||
| 
 | ||||
| static SdSpiDataResponce sd_spi_get_data_response(uint32_t timeout_ms) { | ||||
|     SdSpiDataResponce responce = sd_spi_read_byte(); | ||||
|     // read busy response byte
 | ||||
|     sd_spi_read_byte(); | ||||
| 
 | ||||
|     switch(responce & 0x1F) { | ||||
|     case SdSpiDataResponceOK: | ||||
|         // TODO: check timings
 | ||||
|         sd_spi_deselect_card(); | ||||
|         sd_spi_select_card(); | ||||
| 
 | ||||
|         // wait for 0xFF
 | ||||
|         if(sd_spi_wait_for_data(0xFF, timeout_ms) == SdSpiStatusOK) { | ||||
|             return SdSpiDataResponceOK; | ||||
|         } else { | ||||
|             return SdSpiDataResponceOtherError; | ||||
|         } | ||||
|     case SdSpiDataResponceCRCError: | ||||
|         return SdSpiDataResponceCRCError; | ||||
|     case SdSpiDataResponceWriteError: | ||||
|         return SdSpiDataResponceWriteError; | ||||
|     default: | ||||
|         return SdSpiDataResponceOtherError; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static SdSpiStatus sd_spi_init_spi_mode_v1(void) { | ||||
|     SdSpiCmdAnswer response; | ||||
|     uint8_t retry_count = 0; | ||||
| 
 | ||||
|     sd_spi_debug("Init SD card in SPI mode v1"); | ||||
| 
 | ||||
|     do { | ||||
|         retry_count++; | ||||
| 
 | ||||
|         // CMD55 (APP_CMD) before any ACMD command: R1 response (0x00: no errors)
 | ||||
|         sd_spi_send_cmd(SD_CMD55_APP_CMD, 0, 0xFF, SdSpiCmdAnswerTypeR1); | ||||
|         sd_spi_deselect_card_and_purge(); | ||||
| 
 | ||||
|         // ACMD41 (SD_APP_OP_COND) to initialize SDHC or SDXC cards: R1 response (0x00: no errors)
 | ||||
|         response = sd_spi_send_cmd(SD_CMD41_SD_APP_OP_COND, 0, 0xFF, SdSpiCmdAnswerTypeR1); | ||||
|         sd_spi_deselect_card_and_purge(); | ||||
| 
 | ||||
|         if(retry_count >= SD_IDLE_RETRY_COUNT) { | ||||
|             return SdSpiStatusError; | ||||
|         } | ||||
|     } while(response.r1 == SdSpi_R1_IN_IDLE_STATE); | ||||
| 
 | ||||
|     sd_spi_debug("Init SD card in SPI mode v1 done"); | ||||
| 
 | ||||
|     return SdSpiStatusOK; | ||||
| } | ||||
| 
 | ||||
| static SdSpiStatus sd_spi_init_spi_mode_v2(void) { | ||||
|     SdSpiCmdAnswer response; | ||||
|     uint8_t retry_count = 0; | ||||
| 
 | ||||
|     sd_spi_debug("Init SD card in SPI mode v2"); | ||||
| 
 | ||||
|     do { | ||||
|         retry_count++; | ||||
|         // CMD55 (APP_CMD) before any ACMD command: R1 response (0x00: no errors)
 | ||||
|         sd_spi_send_cmd(SD_CMD55_APP_CMD, 0, 0xFF, SdSpiCmdAnswerTypeR1); | ||||
|         sd_spi_deselect_card_and_purge(); | ||||
| 
 | ||||
|         // ACMD41 (APP_OP_COND) to initialize SDHC or SDXC cards: R1 response (0x00: no errors)
 | ||||
|         response = | ||||
|             sd_spi_send_cmd(SD_CMD41_SD_APP_OP_COND, 0x40000000, 0xFF, SdSpiCmdAnswerTypeR1); | ||||
|         sd_spi_deselect_card_and_purge(); | ||||
| 
 | ||||
|         if(retry_count >= SD_IDLE_RETRY_COUNT) { | ||||
|             sd_spi_debug("ACMD41 failed"); | ||||
|             return SdSpiStatusError; | ||||
|         } | ||||
|     } while(response.r1 == SdSpi_R1_IN_IDLE_STATE); | ||||
| 
 | ||||
|     if(FLAG_SET(response.r1, SdSpi_R1_ILLEGAL_COMMAND)) { | ||||
|         sd_spi_debug("ACMD41 is illegal command"); | ||||
|         retry_count = 0; | ||||
|         do { | ||||
|             retry_count++; | ||||
|             // CMD55 (APP_CMD) before any ACMD command: R1 response (0x00: no errors)
 | ||||
|             response = sd_spi_send_cmd(SD_CMD55_APP_CMD, 0, 0xFF, SdSpiCmdAnswerTypeR1); | ||||
|             sd_spi_deselect_card_and_purge(); | ||||
| 
 | ||||
|             if(response.r1 != SdSpi_R1_IN_IDLE_STATE) { | ||||
|                 sd_spi_debug("CMD55 failed"); | ||||
|                 return SdSpiStatusError; | ||||
|             } | ||||
|             // ACMD41 (SD_APP_OP_COND) to initialize SDHC or SDXC cards: R1 response (0x00: no errors)
 | ||||
|             response = sd_spi_send_cmd(SD_CMD41_SD_APP_OP_COND, 0, 0xFF, SdSpiCmdAnswerTypeR1); | ||||
|             sd_spi_deselect_card_and_purge(); | ||||
| 
 | ||||
|             if(retry_count >= SD_IDLE_RETRY_COUNT) { | ||||
|                 sd_spi_debug("ACMD41 failed"); | ||||
|                 return SdSpiStatusError; | ||||
|             } | ||||
|         } while(response.r1 == SdSpi_R1_IN_IDLE_STATE); | ||||
|     } | ||||
| 
 | ||||
|     sd_spi_debug("Init SD card in SPI mode v2 done"); | ||||
| 
 | ||||
|     return SdSpiStatusOK; | ||||
| } | ||||
| 
 | ||||
| static SdSpiStatus sd_spi_init_spi_mode(void) { | ||||
|     SdSpiCmdAnswer response; | ||||
|     uint8_t retry_count; | ||||
| 
 | ||||
|     // CMD0 (GO_IDLE_STATE) to put SD in SPI mode and
 | ||||
|     // wait for In Idle State Response (R1 Format) equal to 0x01
 | ||||
|     retry_count = 0; | ||||
|     do { | ||||
|         retry_count++; | ||||
|         response = sd_spi_send_cmd(SD_CMD0_GO_IDLE_STATE, 0, 0x95, SdSpiCmdAnswerTypeR1); | ||||
|         sd_spi_deselect_card_and_purge(); | ||||
| 
 | ||||
|         if(retry_count >= SD_IDLE_RETRY_COUNT) { | ||||
|             sd_spi_debug("CMD0 failed"); | ||||
|             return SdSpiStatusError; | ||||
|         } | ||||
|     } while(response.r1 != SdSpi_R1_IN_IDLE_STATE); | ||||
| 
 | ||||
|     // CMD8 (SEND_IF_COND) to check the power supply status
 | ||||
|     // and wait until response (R7 Format) equal to 0xAA and
 | ||||
|     response = sd_spi_send_cmd(SD_CMD8_SEND_IF_COND, 0x1AA, 0x87, SdSpiCmdAnswerTypeR7); | ||||
|     sd_spi_deselect_card_and_purge(); | ||||
| 
 | ||||
|     if(FLAG_SET(response.r1, SdSpi_R1_ILLEGAL_COMMAND)) { | ||||
|         if(sd_spi_init_spi_mode_v1() != SdSpiStatusOK) { | ||||
|             sd_spi_debug("Init mode v1 failed"); | ||||
|             return SdSpiStatusError; | ||||
|         } | ||||
|         sd_high_capacity = 0; | ||||
|     } else if(response.r1 == SdSpi_R1_IN_IDLE_STATE) { | ||||
|         if(sd_spi_init_spi_mode_v2() != SdSpiStatusOK) { | ||||
|             sd_spi_debug("Init mode v2 failed"); | ||||
|             return SdSpiStatusError; | ||||
|         } | ||||
| 
 | ||||
|         // CMD58 (READ_OCR) to initialize SDHC or SDXC cards: R3 response
 | ||||
|         response = sd_spi_send_cmd(SD_CMD58_READ_OCR, 0, 0xFF, SdSpiCmdAnswerTypeR3); | ||||
|         sd_spi_deselect_card_and_purge(); | ||||
| 
 | ||||
|         if(response.r1 != SdSpi_R1_NO_ERROR) { | ||||
|             sd_spi_debug("CMD58 failed"); | ||||
|             return SdSpiStatusError; | ||||
|         } | ||||
|         sd_high_capacity = (response.r2 & 0x40) >> 6; | ||||
|     } else { | ||||
|         return SdSpiStatusError; | ||||
|     } | ||||
| 
 | ||||
|     sd_spi_debug("SD card is %s", sd_high_capacity ? "SDHC or SDXC" : "SDSC"); | ||||
|     return SdSpiStatusOK; | ||||
| } | ||||
| 
 | ||||
| static SdSpiStatus sd_spi_get_csd(SD_CSD* csd) { | ||||
|     uint16_t counter = 0; | ||||
|     uint8_t csd_data[16]; | ||||
|     SdSpiStatus ret = SdSpiStatusError; | ||||
|     SdSpiCmdAnswer response; | ||||
| 
 | ||||
|     // CMD9 (SEND_CSD): R1 format (0x00 is no errors)
 | ||||
|     response = sd_spi_send_cmd(SD_CMD9_SEND_CSD, 0, 0xFF, SdSpiCmdAnswerTypeR1); | ||||
| 
 | ||||
|     if(response.r1 == SdSpi_R1_NO_ERROR) { | ||||
|         if(sd_spi_wait_for_data(SD_TOKEN_START_DATA_SINGLE_BLOCK_READ, SD_TIMEOUT_MS) == | ||||
|            SdSpiStatusOK) { | ||||
|             // read CSD data
 | ||||
|             for(counter = 0; counter < 16; counter++) { | ||||
|                 csd_data[counter] = sd_spi_read_byte(); | ||||
|             } | ||||
| 
 | ||||
|             sd_spi_purge_crc(); | ||||
| 
 | ||||
|             /*************************************************************************
 | ||||
|             CSD header decoding  | ||||
|             *************************************************************************/ | ||||
| 
 | ||||
|             csd->CSDStruct = (csd_data[0] & 0xC0) >> 6; | ||||
|             csd->Reserved1 = csd_data[0] & 0x3F; | ||||
|             csd->TAAC = csd_data[1]; | ||||
|             csd->NSAC = csd_data[2]; | ||||
|             csd->MaxBusClkFrec = csd_data[3]; | ||||
|             csd->CardComdClasses = (csd_data[4] << 4) | ((csd_data[5] & 0xF0) >> 4); | ||||
|             csd->RdBlockLen = csd_data[5] & 0x0F; | ||||
|             csd->PartBlockRead = (csd_data[6] & 0x80) >> 7; | ||||
|             csd->WrBlockMisalign = (csd_data[6] & 0x40) >> 6; | ||||
|             csd->RdBlockMisalign = (csd_data[6] & 0x20) >> 5; | ||||
|             csd->DSRImpl = (csd_data[6] & 0x10) >> 4; | ||||
| 
 | ||||
|             /*************************************************************************
 | ||||
|             CSD v1/v2 decoding   | ||||
|             *************************************************************************/ | ||||
| 
 | ||||
|             if(sd_high_capacity == 0) { | ||||
|                 csd->version.v1.Reserved1 = ((csd_data[6] & 0x0C) >> 2); | ||||
|                 csd->version.v1.DeviceSize = ((csd_data[6] & 0x03) << 10) | (csd_data[7] << 2) | | ||||
|                                              ((csd_data[8] & 0xC0) >> 6); | ||||
|                 csd->version.v1.MaxRdCurrentVDDMin = (csd_data[8] & 0x38) >> 3; | ||||
|                 csd->version.v1.MaxRdCurrentVDDMax = (csd_data[8] & 0x07); | ||||
|                 csd->version.v1.MaxWrCurrentVDDMin = (csd_data[9] & 0xE0) >> 5; | ||||
|                 csd->version.v1.MaxWrCurrentVDDMax = (csd_data[9] & 0x1C) >> 2; | ||||
|                 csd->version.v1.DeviceSizeMul = ((csd_data[9] & 0x03) << 1) | | ||||
|                                                 ((csd_data[10] & 0x80) >> 7); | ||||
|             } else { | ||||
|                 csd->version.v2.Reserved1 = ((csd_data[6] & 0x0F) << 2) | | ||||
|                                             ((csd_data[7] & 0xC0) >> 6); | ||||
|                 csd->version.v2.DeviceSize = ((csd_data[7] & 0x3F) << 16) | (csd_data[8] << 8) | | ||||
|                                              csd_data[9]; | ||||
|                 csd->version.v2.Reserved2 = ((csd_data[10] & 0x80) >> 8); | ||||
|             } | ||||
| 
 | ||||
|             csd->EraseSingleBlockEnable = (csd_data[10] & 0x40) >> 6; | ||||
|             csd->EraseSectorSize = ((csd_data[10] & 0x3F) << 1) | ((csd_data[11] & 0x80) >> 7); | ||||
|             csd->WrProtectGrSize = (csd_data[11] & 0x7F); | ||||
|             csd->WrProtectGrEnable = (csd_data[12] & 0x80) >> 7; | ||||
|             csd->Reserved2 = (csd_data[12] & 0x60) >> 5; | ||||
|             csd->WrSpeedFact = (csd_data[12] & 0x1C) >> 2; | ||||
|             csd->MaxWrBlockLen = ((csd_data[12] & 0x03) << 2) | ((csd_data[13] & 0xC0) >> 6); | ||||
|             csd->WriteBlockPartial = (csd_data[13] & 0x20) >> 5; | ||||
|             csd->Reserved3 = (csd_data[13] & 0x1F); | ||||
|             csd->FileFormatGrouop = (csd_data[14] & 0x80) >> 7; | ||||
|             csd->CopyFlag = (csd_data[14] & 0x40) >> 6; | ||||
|             csd->PermWrProtect = (csd_data[14] & 0x20) >> 5; | ||||
|             csd->TempWrProtect = (csd_data[14] & 0x10) >> 4; | ||||
|             csd->FileFormat = (csd_data[14] & 0x0C) >> 2; | ||||
|             csd->Reserved4 = (csd_data[14] & 0x03); | ||||
|             csd->crc = (csd_data[15] & 0xFE) >> 1; | ||||
|             csd->Reserved5 = (csd_data[15] & 0x01); | ||||
| 
 | ||||
|             ret = SdSpiStatusOK; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     sd_spi_deselect_card_and_purge(); | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| static SdSpiStatus sd_spi_get_cid(SD_CID* Cid) { | ||||
|     uint16_t counter = 0; | ||||
|     uint8_t cid_data[16]; | ||||
|     SdSpiStatus ret = SdSpiStatusError; | ||||
|     SdSpiCmdAnswer response; | ||||
| 
 | ||||
|     // CMD10 (SEND_CID): R1 format (0x00 is no errors)
 | ||||
|     response = sd_spi_send_cmd(SD_CMD10_SEND_CID, 0, 0xFF, SdSpiCmdAnswerTypeR1); | ||||
| 
 | ||||
|     if(response.r1 == SdSpi_R1_NO_ERROR) { | ||||
|         if(sd_spi_wait_for_data(SD_TOKEN_START_DATA_SINGLE_BLOCK_READ, SD_TIMEOUT_MS) == | ||||
|            SdSpiStatusOK) { | ||||
|             // read CID data
 | ||||
|             for(counter = 0; counter < 16; counter++) { | ||||
|                 cid_data[counter] = sd_spi_read_byte(); | ||||
|             } | ||||
| 
 | ||||
|             sd_spi_purge_crc(); | ||||
| 
 | ||||
|             Cid->ManufacturerID = cid_data[0]; | ||||
|             memcpy(Cid->OEM_AppliID, cid_data + 1, 2); | ||||
|             memcpy(Cid->ProdName, cid_data + 3, 5); | ||||
|             Cid->ProdRev = cid_data[8]; | ||||
|             Cid->ProdSN = cid_data[9] << 24; | ||||
|             Cid->ProdSN |= cid_data[10] << 16; | ||||
|             Cid->ProdSN |= cid_data[11] << 8; | ||||
|             Cid->ProdSN |= cid_data[12]; | ||||
|             Cid->Reserved1 = (cid_data[13] & 0xF0) >> 4; | ||||
|             Cid->ManufactYear = (cid_data[13] & 0x0F) << 4; | ||||
|             Cid->CID_CRC = (cid_data[15] & 0xFE) >> 1; | ||||
|             Cid->Reserved2 = 1; | ||||
| 
 | ||||
|             ret = SdSpiStatusOK; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     sd_spi_deselect_card_and_purge(); | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| static inline bool sd_cache_get(uint32_t address, uint32_t* data) { | ||||
|     uint8_t* cached_data = sector_cache_get(address); | ||||
|     if(cached_data) { | ||||
|         memcpy(data, cached_data, SD_BLOCK_SIZE); | ||||
|         return true; | ||||
|     } | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| static inline void sd_cache_put(uint32_t address, uint32_t* data) { | ||||
|     sector_cache_put(address, (uint8_t*)data); | ||||
| } | ||||
| 
 | ||||
| static inline void sd_cache_invalidate_range(uint32_t start_sector, uint32_t end_sector) { | ||||
|     sector_cache_invalidate_range(start_sector, end_sector); | ||||
| } | ||||
| 
 | ||||
| static SdSpiStatus | ||||
|     sd_spi_cmd_read_blocks(uint32_t* data, uint32_t address, uint32_t blocks, uint32_t timeout_ms) { | ||||
|     uint32_t block_address = address; | ||||
|     uint32_t offset = 0; | ||||
| 
 | ||||
|     // CMD16 (SET_BLOCKLEN): R1 response (0x00: no errors)
 | ||||
|     SdSpiCmdAnswer response = | ||||
|         sd_spi_send_cmd(SD_CMD16_SET_BLOCKLEN, SD_BLOCK_SIZE, 0xFF, SdSpiCmdAnswerTypeR1); | ||||
|     sd_spi_deselect_card_and_purge(); | ||||
| 
 | ||||
|     if(response.r1 != SdSpi_R1_NO_ERROR) { | ||||
|         return SdSpiStatusError; | ||||
|     } | ||||
| 
 | ||||
|     if(!sd_high_capacity) { | ||||
|         block_address = address * SD_BLOCK_SIZE; | ||||
|     } | ||||
| 
 | ||||
|     while(blocks--) { | ||||
|         // CMD17 (READ_SINGLE_BLOCK): R1 response (0x00: no errors)
 | ||||
|         response = | ||||
|             sd_spi_send_cmd(SD_CMD17_READ_SINGLE_BLOCK, block_address, 0xFF, SdSpiCmdAnswerTypeR1); | ||||
|         if(response.r1 != SdSpi_R1_NO_ERROR) { | ||||
|             sd_spi_deselect_card_and_purge(); | ||||
|             return SdSpiStatusError; | ||||
|         } | ||||
| 
 | ||||
|         // Wait for the data start token
 | ||||
|         if(sd_spi_wait_for_data(SD_TOKEN_START_DATA_SINGLE_BLOCK_READ, timeout_ms) == | ||||
|            SdSpiStatusOK) { | ||||
|             // Read the data block
 | ||||
|             sd_spi_read_bytes_dma((uint8_t*)data + offset, SD_BLOCK_SIZE); | ||||
|             sd_spi_purge_crc(); | ||||
| 
 | ||||
|             // increase offset
 | ||||
|             offset += SD_BLOCK_SIZE; | ||||
| 
 | ||||
|             // increase block address
 | ||||
|             if(sd_high_capacity) { | ||||
|                 block_address += 1; | ||||
|             } else { | ||||
|                 block_address += SD_BLOCK_SIZE; | ||||
|             } | ||||
|         } else { | ||||
|             sd_spi_deselect_card_and_purge(); | ||||
|             return SdSpiStatusError; | ||||
|         } | ||||
| 
 | ||||
|         sd_spi_deselect_card_and_purge(); | ||||
|     } | ||||
| 
 | ||||
|     return SdSpiStatusOK; | ||||
| } | ||||
| 
 | ||||
| static SdSpiStatus sd_spi_cmd_write_blocks( | ||||
|     uint32_t* data, | ||||
|     uint32_t address, | ||||
|     uint32_t blocks, | ||||
|     uint32_t timeout_ms) { | ||||
|     uint32_t block_address = address; | ||||
|     uint32_t offset = 0; | ||||
| 
 | ||||
|     // CMD16 (SET_BLOCKLEN): R1 response (0x00: no errors)
 | ||||
|     SdSpiCmdAnswer response = | ||||
|         sd_spi_send_cmd(SD_CMD16_SET_BLOCKLEN, SD_BLOCK_SIZE, 0xFF, SdSpiCmdAnswerTypeR1); | ||||
|     sd_spi_deselect_card_and_purge(); | ||||
| 
 | ||||
|     if(response.r1 != SdSpi_R1_NO_ERROR) { | ||||
|         return SdSpiStatusError; | ||||
|     } | ||||
| 
 | ||||
|     if(!sd_high_capacity) { | ||||
|         block_address = address * SD_BLOCK_SIZE; | ||||
|     } | ||||
| 
 | ||||
|     while(blocks--) { | ||||
|         // CMD24 (WRITE_SINGLE_BLOCK): R1 response (0x00: no errors)
 | ||||
|         response = sd_spi_send_cmd( | ||||
|             SD_CMD24_WRITE_SINGLE_BLOCK, block_address, 0xFF, SdSpiCmdAnswerTypeR1); | ||||
|         if(response.r1 != SdSpi_R1_NO_ERROR) { | ||||
|             sd_spi_deselect_card_and_purge(); | ||||
|             return SdSpiStatusError; | ||||
|         } | ||||
| 
 | ||||
|         // Send dummy byte for NWR timing : one byte between CMD_WRITE and TOKEN
 | ||||
|         // TODO: check bytes count
 | ||||
|         sd_spi_write_byte(SD_DUMMY_BYTE); | ||||
|         sd_spi_write_byte(SD_DUMMY_BYTE); | ||||
| 
 | ||||
|         // Send the data start token
 | ||||
|         sd_spi_write_byte(SD_TOKEN_START_DATA_SINGLE_BLOCK_WRITE); | ||||
|         sd_spi_write_bytes_dma((uint8_t*)data + offset, SD_BLOCK_SIZE); | ||||
|         sd_spi_purge_crc(); | ||||
| 
 | ||||
|         // Read data response
 | ||||
|         SdSpiDataResponce data_responce = sd_spi_get_data_response(timeout_ms); | ||||
|         sd_spi_deselect_card_and_purge(); | ||||
| 
 | ||||
|         if(data_responce != SdSpiDataResponceOK) { | ||||
|             return SdSpiStatusError; | ||||
|         } | ||||
| 
 | ||||
|         // increase offset
 | ||||
|         offset += SD_BLOCK_SIZE; | ||||
| 
 | ||||
|         // increase block address
 | ||||
|         if(sd_high_capacity) { | ||||
|             block_address += 1; | ||||
|         } else { | ||||
|             block_address += SD_BLOCK_SIZE; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return SdSpiStatusOK; | ||||
| } | ||||
| 
 | ||||
| uint8_t sd_max_mount_retry_count() { | ||||
|     return 10; | ||||
| } | ||||
| 
 | ||||
| SdSpiStatus sd_init(bool power_reset) { | ||||
|     // Slow speed init
 | ||||
|     furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_slow); | ||||
|     furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_slow; | ||||
| 
 | ||||
|     // We reset card in spi_lock context, so it is safe to disturb spi bus
 | ||||
|     if(power_reset) { | ||||
|         sd_spi_debug("Power reset"); | ||||
| 
 | ||||
|         // disable power and set low on all bus pins
 | ||||
|         furi_hal_power_disable_external_3_3v(); | ||||
|         sd_spi_bus_to_ground(); | ||||
|         hal_sd_detect_set_low(); | ||||
|         furi_delay_ms(250); | ||||
| 
 | ||||
|         // reinit bus and enable power
 | ||||
|         sd_spi_bus_rise_up(); | ||||
|         hal_sd_detect_init(); | ||||
|         furi_hal_power_enable_external_3_3v(); | ||||
|         furi_delay_ms(100); | ||||
|     } | ||||
| 
 | ||||
|     SdSpiStatus status = SdSpiStatusError; | ||||
| 
 | ||||
|     // Send 80 dummy clocks with CS high
 | ||||
|     sd_spi_deselect_card(); | ||||
|     for(uint8_t i = 0; i < 80; i++) { | ||||
|         sd_spi_write_byte(SD_DUMMY_BYTE); | ||||
|     } | ||||
| 
 | ||||
|     for(uint8_t i = 0; i < 128; i++) { | ||||
|         status = sd_spi_init_spi_mode(); | ||||
|         if(status == SdSpiStatusOK) { | ||||
|             // SD initialized and init to SPI mode properly
 | ||||
|             sd_spi_debug("SD init OK after %d retries", i); | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     furi_hal_sd_spi_handle = NULL; | ||||
|     furi_hal_spi_release(&furi_hal_spi_bus_handle_sd_slow); | ||||
| 
 | ||||
|     // Init sector cache
 | ||||
|     sector_cache_init(); | ||||
| 
 | ||||
|     return status; | ||||
| } | ||||
| 
 | ||||
| SdSpiStatus sd_get_card_state(void) { | ||||
|     SdSpiCmdAnswer response; | ||||
| 
 | ||||
|     // Send CMD13 (SEND_STATUS) to get SD status
 | ||||
|     response = sd_spi_send_cmd(SD_CMD13_SEND_STATUS, 0, 0xFF, SdSpiCmdAnswerTypeR2); | ||||
|     sd_spi_deselect_card_and_purge(); | ||||
| 
 | ||||
|     // Return status OK if response is valid
 | ||||
|     if((response.r1 == SdSpi_R1_NO_ERROR) && (response.r2 == SdSpi_R2_NO_ERROR)) { | ||||
|         return SdSpiStatusOK; | ||||
|     } | ||||
| 
 | ||||
|     return SdSpiStatusError; | ||||
| } | ||||
| 
 | ||||
| SdSpiStatus sd_get_card_info(SD_CardInfo* card_info) { | ||||
|     SdSpiStatus status; | ||||
| 
 | ||||
|     status = sd_spi_get_csd(&(card_info->Csd)); | ||||
| 
 | ||||
|     if(status != SdSpiStatusOK) { | ||||
|         return status; | ||||
|     } | ||||
| 
 | ||||
|     status = sd_spi_get_cid(&(card_info->Cid)); | ||||
| 
 | ||||
|     if(status != SdSpiStatusOK) { | ||||
|         return status; | ||||
|     } | ||||
| 
 | ||||
|     if(sd_high_capacity == 1) { | ||||
|         card_info->LogBlockSize = 512; | ||||
|         card_info->CardBlockSize = 512; | ||||
|         card_info->CardCapacity = ((uint64_t)card_info->Csd.version.v2.DeviceSize + 1UL) * 1024UL * | ||||
|                                   (uint64_t)card_info->LogBlockSize; | ||||
|         card_info->LogBlockNbr = (card_info->CardCapacity) / (card_info->LogBlockSize); | ||||
|     } else { | ||||
|         card_info->CardCapacity = (card_info->Csd.version.v1.DeviceSize + 1); | ||||
|         card_info->CardCapacity *= (1UL << (card_info->Csd.version.v1.DeviceSizeMul + 2)); | ||||
|         card_info->LogBlockSize = 512; | ||||
|         card_info->CardBlockSize = 1UL << (card_info->Csd.RdBlockLen); | ||||
|         card_info->CardCapacity *= card_info->CardBlockSize; | ||||
|         card_info->LogBlockNbr = (card_info->CardCapacity) / (card_info->LogBlockSize); | ||||
|     } | ||||
| 
 | ||||
|     return status; | ||||
| } | ||||
| 
 | ||||
| SdSpiStatus | ||||
|     sd_read_blocks(uint32_t* data, uint32_t address, uint32_t blocks, uint32_t timeout_ms) { | ||||
|     SdSpiStatus status = SdSpiStatusError; | ||||
| 
 | ||||
|     bool single_sector_read = (blocks == 1); | ||||
| 
 | ||||
|     if(single_sector_read) { | ||||
|         if(sd_cache_get(address, data)) { | ||||
|             return SdSpiStatusOK; | ||||
|         } | ||||
| 
 | ||||
|         status = sd_spi_cmd_read_blocks(data, address, blocks, timeout_ms); | ||||
| 
 | ||||
|         if(status == SdSpiStatusOK) { | ||||
|             sd_cache_put(address, data); | ||||
|         } | ||||
|     } else { | ||||
|         status = sd_spi_cmd_read_blocks(data, address, blocks, timeout_ms); | ||||
|     } | ||||
| 
 | ||||
|     return status; | ||||
| } | ||||
| 
 | ||||
| SdSpiStatus | ||||
|     sd_write_blocks(uint32_t* data, uint32_t address, uint32_t blocks, uint32_t timeout_ms) { | ||||
|     sd_cache_invalidate_range(address, address + blocks); | ||||
|     SdSpiStatus status = sd_spi_cmd_write_blocks(data, address, blocks, timeout_ms); | ||||
|     return status; | ||||
| } | ||||
| 
 | ||||
| SdSpiStatus sd_get_cid(SD_CID* cid) { | ||||
|     SdSpiStatus status; | ||||
| 
 | ||||
|     furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast); | ||||
|     furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast; | ||||
| 
 | ||||
|     memset(cid, 0, sizeof(SD_CID)); | ||||
|     status = sd_spi_get_cid(cid); | ||||
| 
 | ||||
|     furi_hal_sd_spi_handle = NULL; | ||||
|     furi_hal_spi_release(&furi_hal_spi_bus_handle_sd_fast); | ||||
| 
 | ||||
|     return status; | ||||
| } | ||||
							
								
								
									
										157
									
								
								firmware/targets/f7/fatfs/sd_spi_io.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								firmware/targets/f7/fatfs/sd_spi_io.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,157 @@ | ||||
| #pragma once | ||||
| #include <stdint.h> | ||||
| #include <stdbool.h> | ||||
| 
 | ||||
| #define __IO volatile | ||||
| 
 | ||||
| #define SD_TIMEOUT_MS (1000) | ||||
| 
 | ||||
| typedef enum { | ||||
|     SdSpiStatusOK, | ||||
|     SdSpiStatusError, | ||||
|     SdSpiStatusTimeout, | ||||
| } SdSpiStatus; | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Card Specific Data: CSD Register | ||||
|  */ | ||||
| typedef struct { | ||||
|     /* Header part */ | ||||
|     uint8_t CSDStruct : 2; /* CSD structure */ | ||||
|     uint8_t Reserved1 : 6; /* Reserved */ | ||||
|     uint8_t TAAC : 8; /* Data read access-time 1 */ | ||||
|     uint8_t NSAC : 8; /* Data read access-time 2 in CLK cycles */ | ||||
|     uint8_t MaxBusClkFrec : 8; /* Max. bus clock frequency */ | ||||
|     uint16_t CardComdClasses : 12; /* Card command classes */ | ||||
|     uint8_t RdBlockLen : 4; /* Max. read data block length */ | ||||
|     uint8_t PartBlockRead : 1; /* Partial blocks for read allowed */ | ||||
|     uint8_t WrBlockMisalign : 1; /* Write block misalignment */ | ||||
|     uint8_t RdBlockMisalign : 1; /* Read block misalignment */ | ||||
|     uint8_t DSRImpl : 1; /* DSR implemented */ | ||||
| 
 | ||||
|     /* v1 or v2 struct */ | ||||
|     union csd_version { | ||||
|         struct { | ||||
|             uint8_t Reserved1 : 2; /* Reserved */ | ||||
|             uint16_t DeviceSize : 12; /* Device Size */ | ||||
|             uint8_t MaxRdCurrentVDDMin : 3; /* Max. read current @ VDD min */ | ||||
|             uint8_t MaxRdCurrentVDDMax : 3; /* Max. read current @ VDD max */ | ||||
|             uint8_t MaxWrCurrentVDDMin : 3; /* Max. write current @ VDD min */ | ||||
|             uint8_t MaxWrCurrentVDDMax : 3; /* Max. write current @ VDD max */ | ||||
|             uint8_t DeviceSizeMul : 3; /* Device size multiplier */ | ||||
|         } v1; | ||||
|         struct { | ||||
|             uint8_t Reserved1 : 6; /* Reserved */ | ||||
|             uint32_t DeviceSize : 22; /* Device Size */ | ||||
|             uint8_t Reserved2 : 1; /* Reserved */ | ||||
|         } v2; | ||||
|     } version; | ||||
| 
 | ||||
|     uint8_t EraseSingleBlockEnable : 1; /* Erase single block enable */ | ||||
|     uint8_t EraseSectorSize : 7; /* Erase group size multiplier */ | ||||
|     uint8_t WrProtectGrSize : 7; /* Write protect group size */ | ||||
|     uint8_t WrProtectGrEnable : 1; /* Write protect group enable */ | ||||
|     uint8_t Reserved2 : 2; /* Reserved */ | ||||
|     uint8_t WrSpeedFact : 3; /* Write speed factor */ | ||||
|     uint8_t MaxWrBlockLen : 4; /* Max. write data block length */ | ||||
|     uint8_t WriteBlockPartial : 1; /* Partial blocks for write allowed */ | ||||
|     uint8_t Reserved3 : 5; /* Reserved */ | ||||
|     uint8_t FileFormatGrouop : 1; /* File format group */ | ||||
|     uint8_t CopyFlag : 1; /* Copy flag (OTP) */ | ||||
|     uint8_t PermWrProtect : 1; /* Permanent write protection */ | ||||
|     uint8_t TempWrProtect : 1; /* Temporary write protection */ | ||||
|     uint8_t FileFormat : 2; /* File Format */ | ||||
|     uint8_t Reserved4 : 2; /* Reserved */ | ||||
|     uint8_t crc : 7; /* Reserved */ | ||||
|     uint8_t Reserved5 : 1; /* always 1*/ | ||||
| 
 | ||||
| } SD_CSD; | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Card Identification Data: CID Register | ||||
|  */ | ||||
| typedef struct { | ||||
|     uint8_t ManufacturerID; /* ManufacturerID */ | ||||
|     char OEM_AppliID[2]; /* OEM/Application ID */ | ||||
|     char ProdName[5]; /* Product Name */ | ||||
|     uint8_t ProdRev; /* Product Revision */ | ||||
|     uint32_t ProdSN; /* Product Serial Number */ | ||||
|     uint8_t Reserved1; /* Reserved1 */ | ||||
|     uint8_t ManufactYear; /* Manufacturing Year */ | ||||
|     uint8_t ManufactMonth; /* Manufacturing Month */ | ||||
|     uint8_t CID_CRC; /* CID CRC */ | ||||
|     uint8_t Reserved2; /* always 1 */ | ||||
| } SD_CID; | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief SD Card information structure | ||||
|  */ | ||||
| typedef struct { | ||||
|     SD_CSD Csd; | ||||
|     SD_CID Cid; | ||||
|     uint64_t CardCapacity; /*!< Card Capacity */ | ||||
|     uint32_t CardBlockSize; /*!< Card Block Size */ | ||||
|     uint32_t LogBlockNbr; /*!< Specifies the Card logical Capacity in blocks   */ | ||||
|     uint32_t LogBlockSize; /*!< Specifies logical block size in bytes           */ | ||||
| } SD_CardInfo; | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief SD card max mount retry count | ||||
|  *  | ||||
|  * @return uint8_t  | ||||
|  */ | ||||
| uint8_t sd_max_mount_retry_count(); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Init sd card | ||||
|  *  | ||||
|  * @param power_reset reset card power | ||||
|  * @return SdSpiStatus  | ||||
|  */ | ||||
| SdSpiStatus sd_init(bool power_reset); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Get card state | ||||
|  *  | ||||
|  * @return SdSpiStatus  | ||||
|  */ | ||||
| SdSpiStatus sd_get_card_state(void); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Get card info | ||||
|  *  | ||||
|  * @param card_info  | ||||
|  * @return SdSpiStatus  | ||||
|  */ | ||||
| SdSpiStatus sd_get_card_info(SD_CardInfo* card_info); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Read blocks | ||||
|  *  | ||||
|  * @param data  | ||||
|  * @param address  | ||||
|  * @param blocks  | ||||
|  * @param timeout_ms  | ||||
|  * @return SdSpiStatus  | ||||
|  */ | ||||
| SdSpiStatus sd_read_blocks(uint32_t* data, uint32_t address, uint32_t blocks, uint32_t timeout_ms); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Write blocks | ||||
|  *  | ||||
|  * @param data  | ||||
|  * @param address  | ||||
|  * @param blocks  | ||||
|  * @param timeout_ms  | ||||
|  * @return SdSpiStatus  | ||||
|  */ | ||||
| SdSpiStatus | ||||
|     sd_write_blocks(uint32_t* data, uint32_t address, uint32_t blocks, uint32_t timeout_ms); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Get card CSD register | ||||
|  *  | ||||
|  * @param Cid  | ||||
|  * @return SdSpiStatus  | ||||
|  */ | ||||
| SdSpiStatus sd_get_cid(SD_CID* cid); | ||||
| @ -8,7 +8,6 @@ | ||||
| 
 | ||||
| #define SECTOR_SIZE 512 | ||||
| #define N_SECTORS 8 | ||||
| #define TAG "SDCache" | ||||
| 
 | ||||
| typedef struct { | ||||
|     uint32_t itr; | ||||
| @ -20,15 +19,11 @@ static SectorCache* cache = NULL; | ||||
| 
 | ||||
| void sector_cache_init() { | ||||
|     if(cache == NULL) { | ||||
|         // TODO: tuneup allocation order, to place cache in mem pool (MEM2)
 | ||||
|         cache = memmgr_alloc_from_pool(sizeof(SectorCache)); | ||||
|     } | ||||
| 
 | ||||
|     if(cache != NULL) { | ||||
|         FURI_LOG_I(TAG, "Init"); | ||||
|         memset(cache, 0, sizeof(SectorCache)); | ||||
|     } else { | ||||
|         FURI_LOG_E(TAG, "Init failed"); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -1,98 +0,0 @@ | ||||
| #include <furi_hal.h> | ||||
| #include <furi.h> | ||||
| 
 | ||||
| #define SD_DUMMY_BYTE 0xFF | ||||
| 
 | ||||
| const uint32_t SpiTimeout = 1000; | ||||
| uint8_t SD_IO_WriteByte(uint8_t Data); | ||||
| 
 | ||||
| /******************************************************************************
 | ||||
|                             BUS OPERATIONS | ||||
|  *******************************************************************************/ | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief  SPI Write byte(s) to device | ||||
|  * @param  DataIn: Pointer to data buffer to write | ||||
|  * @param  DataOut: Pointer to data buffer for read data | ||||
|  * @param  DataLength: number of bytes to write | ||||
|  * @retval None | ||||
|  */ | ||||
| static void SPIx_WriteReadData(const uint8_t* DataIn, uint8_t* DataOut, uint16_t DataLength) { | ||||
|     furi_check(furi_hal_spi_bus_trx( | ||||
|         furi_hal_sd_spi_handle, (uint8_t*)DataIn, DataOut, DataLength, SpiTimeout)); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief  SPI Write a byte to device | ||||
|  * @param  Value: value to be written | ||||
|  * @retval None | ||||
|  */ | ||||
| __attribute__((unused)) static void SPIx_Write(uint8_t Value) { | ||||
|     furi_check(furi_hal_spi_bus_tx(furi_hal_sd_spi_handle, (uint8_t*)&Value, 1, SpiTimeout)); | ||||
| } | ||||
| 
 | ||||
| /******************************************************************************
 | ||||
|                             LINK OPERATIONS | ||||
|  *******************************************************************************/ | ||||
| 
 | ||||
| /********************************* LINK SD ************************************/ | ||||
| /**
 | ||||
|  * @brief  Initialize the SD Card and put it into StandBy State (Ready for | ||||
|  *         data transfer). | ||||
|  * @retval None | ||||
|  */ | ||||
| void SD_IO_Init(void) { | ||||
|     uint8_t counter = 0; | ||||
| 
 | ||||
|     /* SD chip select high */ | ||||
|     furi_hal_gpio_write(furi_hal_sd_spi_handle->cs, true); | ||||
|     furi_delay_us(10); | ||||
| 
 | ||||
|     /* Send dummy byte 0xFF, 10 times with CS high */ | ||||
|     /* Rise CS and MOSI for 80 clocks cycles */ | ||||
|     for(counter = 0; counter <= 200; counter++) { | ||||
|         /* Send dummy byte 0xFF */ | ||||
|         SD_IO_WriteByte(SD_DUMMY_BYTE); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief  Set SD interface Chip Select state | ||||
|  * @param  val: 0 (low) or 1 (high) state | ||||
|  * @retval None | ||||
|  */ | ||||
| void SD_IO_CSState(uint8_t val) { | ||||
|     /* Some SD Cards are prone to fail if CLK-ed too soon after CS transition. Worst case found: 8us */ | ||||
|     if(val == 1) { | ||||
|         furi_delay_us(10); // Exit guard time for some SD cards
 | ||||
|         furi_hal_gpio_write(furi_hal_sd_spi_handle->cs, true); | ||||
|     } else { | ||||
|         furi_hal_gpio_write(furi_hal_sd_spi_handle->cs, false); | ||||
|         furi_delay_us(10); // Entry guard time for some SD cards
 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief  Write byte(s) on the SD | ||||
|  * @param  DataIn: Pointer to data buffer to write | ||||
|  * @param  DataOut: Pointer to data buffer for read data | ||||
|  * @param  DataLength: number of bytes to write | ||||
|  * @retval None | ||||
|  */ | ||||
| void SD_IO_WriteReadData(const uint8_t* DataIn, uint8_t* DataOut, uint16_t DataLength) { | ||||
|     /* Send the byte */ | ||||
|     SPIx_WriteReadData(DataIn, DataOut, DataLength); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief  Write a byte on the SD. | ||||
|  * @param  Data: byte to send. | ||||
|  * @retval Data written | ||||
|  */ | ||||
| uint8_t SD_IO_WriteByte(uint8_t Data) { | ||||
|     uint8_t tmp; | ||||
| 
 | ||||
|     /* Send the byte */ | ||||
|     SPIx_WriteReadData(&Data, &tmp, 1); | ||||
|     return tmp; | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -1,245 +0,0 @@ | ||||
| /**
 | ||||
|   ****************************************************************************** | ||||
|   * @file    stm32_adafruit_sd.h | ||||
|   * @author  MCD Application Team | ||||
|   * @version V3.0.0 | ||||
|   * @date    23-December-2016 | ||||
|   * @brief   This file contains the common defines and functions prototypes for | ||||
|   *          the stm32_adafruit_sd.c driver. | ||||
|   ****************************************************************************** | ||||
|   * @attention | ||||
|   * | ||||
|   * <h2><center>© COPYRIGHT(c) 2016 STMicroelectronics</center></h2> | ||||
|   * | ||||
|   * Redistribution and use in source and binary forms, with or without modification, | ||||
|   * are permitted provided that the following conditions are met: | ||||
|   *   1. Redistributions of source code must retain the above copyright notice, | ||||
|   *      this list of conditions and the following disclaimer. | ||||
|   *   2. Redistributions in binary form must reproduce the above copyright notice, | ||||
|   *      this list of conditions and the following disclaimer in the documentation | ||||
|   *      and/or other materials provided with the distribution. | ||||
|   *   3. Neither the name of STMicroelectronics nor the names of its contributors | ||||
|   *      may be used to endorse or promote products derived from this software | ||||
|   *      without specific prior written permission. | ||||
|   * | ||||
|   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||||
|   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||||
|   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||||
|   * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||||
|   * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||||
|   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||||
|   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||||
|   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||||
|   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
|   * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
|   * | ||||
|   ****************************************************************************** | ||||
|   */ | ||||
| 
 | ||||
| /* Define to prevent recursive inclusion -------------------------------------*/ | ||||
| #ifndef __STM32_ADAFRUIT_SD_H | ||||
| #define __STM32_ADAFRUIT_SD_H | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| /* Includes ------------------------------------------------------------------*/ | ||||
| #include <stdint.h> | ||||
| #include <stdbool.h> | ||||
| 
 | ||||
| /** @addtogroup BSP
 | ||||
|   * @{ | ||||
|   */ | ||||
| #define __IO volatile | ||||
| 
 | ||||
| /** @addtogroup STM32_ADAFRUIT
 | ||||
|   * @{ | ||||
|   */ | ||||
| 
 | ||||
| /** @defgroup STM32_ADAFRUIT_SD
 | ||||
|   * @{ | ||||
|   */ | ||||
| 
 | ||||
| /** @defgroup STM32_ADAFRUIT_SD_Exported_Types
 | ||||
|   * @{ | ||||
|   */ | ||||
| 
 | ||||
| /** 
 | ||||
|   * @brief  SD status structure definition   | ||||
|   */ | ||||
| enum { BSP_SD_OK = 0x00, MSD_OK = 0x00, BSP_SD_ERROR = 0x01, BSP_SD_TIMEOUT }; | ||||
| 
 | ||||
| typedef struct { | ||||
|     uint8_t Reserved1 : 2; /* Reserved */ | ||||
|     uint16_t DeviceSize : 12; /* Device Size */ | ||||
|     uint8_t MaxRdCurrentVDDMin : 3; /* Max. read current @ VDD min */ | ||||
|     uint8_t MaxRdCurrentVDDMax : 3; /* Max. read current @ VDD max */ | ||||
|     uint8_t MaxWrCurrentVDDMin : 3; /* Max. write current @ VDD min */ | ||||
|     uint8_t MaxWrCurrentVDDMax : 3; /* Max. write current @ VDD max */ | ||||
|     uint8_t DeviceSizeMul : 3; /* Device size multiplier */ | ||||
| } struct_v1; | ||||
| 
 | ||||
| typedef struct { | ||||
|     uint8_t Reserved1 : 6; /* Reserved */ | ||||
|     uint32_t DeviceSize : 22; /* Device Size */ | ||||
|     uint8_t Reserved2 : 1; /* Reserved */ | ||||
| } struct_v2; | ||||
| 
 | ||||
| /** 
 | ||||
|   * @brief  Card Specific Data: CSD Register | ||||
|   */ | ||||
| typedef struct { | ||||
|     /* Header part */ | ||||
|     uint8_t CSDStruct : 2; /* CSD structure */ | ||||
|     uint8_t Reserved1 : 6; /* Reserved */ | ||||
|     uint8_t TAAC : 8; /* Data read access-time 1 */ | ||||
|     uint8_t NSAC : 8; /* Data read access-time 2 in CLK cycles */ | ||||
|     uint8_t MaxBusClkFrec : 8; /* Max. bus clock frequency */ | ||||
|     uint16_t CardComdClasses : 12; /* Card command classes */ | ||||
|     uint8_t RdBlockLen : 4; /* Max. read data block length */ | ||||
|     uint8_t PartBlockRead : 1; /* Partial blocks for read allowed */ | ||||
|     uint8_t WrBlockMisalign : 1; /* Write block misalignment */ | ||||
|     uint8_t RdBlockMisalign : 1; /* Read block misalignment */ | ||||
|     uint8_t DSRImpl : 1; /* DSR implemented */ | ||||
| 
 | ||||
|     /* v1 or v2 struct */ | ||||
|     union csd_version { | ||||
|         struct_v1 v1; | ||||
|         struct_v2 v2; | ||||
|     } version; | ||||
| 
 | ||||
|     uint8_t EraseSingleBlockEnable : 1; /* Erase single block enable */ | ||||
|     uint8_t EraseSectorSize : 7; /* Erase group size multiplier */ | ||||
|     uint8_t WrProtectGrSize : 7; /* Write protect group size */ | ||||
|     uint8_t WrProtectGrEnable : 1; /* Write protect group enable */ | ||||
|     uint8_t Reserved2 : 2; /* Reserved */ | ||||
|     uint8_t WrSpeedFact : 3; /* Write speed factor */ | ||||
|     uint8_t MaxWrBlockLen : 4; /* Max. write data block length */ | ||||
|     uint8_t WriteBlockPartial : 1; /* Partial blocks for write allowed */ | ||||
|     uint8_t Reserved3 : 5; /* Reserved */ | ||||
|     uint8_t FileFormatGrouop : 1; /* File format group */ | ||||
|     uint8_t CopyFlag : 1; /* Copy flag (OTP) */ | ||||
|     uint8_t PermWrProtect : 1; /* Permanent write protection */ | ||||
|     uint8_t TempWrProtect : 1; /* Temporary write protection */ | ||||
|     uint8_t FileFormat : 2; /* File Format */ | ||||
|     uint8_t Reserved4 : 2; /* Reserved */ | ||||
|     uint8_t crc : 7; /* Reserved */ | ||||
|     uint8_t Reserved5 : 1; /* always 1*/ | ||||
| 
 | ||||
| } SD_CSD; | ||||
| 
 | ||||
| /** 
 | ||||
|   * @brief  Card Identification Data: CID Register    | ||||
|   */ | ||||
| typedef struct { | ||||
|     uint8_t ManufacturerID; /* ManufacturerID */ | ||||
|     char OEM_AppliID[2]; /* OEM/Application ID */ | ||||
|     char ProdName[5]; /* Product Name */ | ||||
|     uint8_t ProdRev; /* Product Revision */ | ||||
|     uint32_t ProdSN; /* Product Serial Number */ | ||||
|     uint8_t Reserved1; /* Reserved1 */ | ||||
|     uint8_t ManufactYear; /* Manufacturing Year */ | ||||
|     uint8_t ManufactMonth; /* Manufacturing Month */ | ||||
|     uint8_t CID_CRC; /* CID CRC */ | ||||
|     uint8_t Reserved2; /* always 1 */ | ||||
| } SD_CID; | ||||
| 
 | ||||
| /** 
 | ||||
|   * @brief SD Card information  | ||||
|   */ | ||||
| typedef struct { | ||||
|     SD_CSD Csd; | ||||
|     SD_CID Cid; | ||||
|     uint64_t CardCapacity; /*!< Card Capacity */ | ||||
|     uint32_t CardBlockSize; /*!< Card Block Size */ | ||||
|     uint32_t LogBlockNbr; /*!< Specifies the Card logical Capacity in blocks   */ | ||||
|     uint32_t LogBlockSize; /*!< Specifies logical block size in bytes           */ | ||||
| } SD_CardInfo; | ||||
| 
 | ||||
| /**
 | ||||
|   * @} | ||||
|   */ | ||||
| 
 | ||||
| /** @defgroup STM32_ADAFRUIT_SPI_SD_Exported_Constants
 | ||||
|   * @{ | ||||
|   */ | ||||
| 
 | ||||
| /**
 | ||||
|   * @brief  Block Size | ||||
|   */ | ||||
| #define SD_BLOCK_SIZE 0x200 | ||||
| 
 | ||||
| /**
 | ||||
|   * @brief  SD detection on its memory slot | ||||
|   */ | ||||
| #define SD_PRESENT ((uint8_t)0x01) | ||||
| #define SD_NOT_PRESENT ((uint8_t)0x00) | ||||
| 
 | ||||
| #define SD_DATATIMEOUT ((uint32_t)100000000) | ||||
| 
 | ||||
| /** 
 | ||||
|   * @brief SD Card information structure  | ||||
|   */ | ||||
| #define BSP_SD_CardInfo SD_CardInfo | ||||
| 
 | ||||
| /**
 | ||||
|   * @} | ||||
|   */ | ||||
| 
 | ||||
| /** @defgroup STM32_ADAFRUIT_SD_Exported_Macro
 | ||||
|   * @{ | ||||
|   */ | ||||
| 
 | ||||
| /**
 | ||||
|   * @} | ||||
|   */ | ||||
| 
 | ||||
| /** @defgroup STM32_ADAFRUIT_SD_Exported_Functions
 | ||||
|   * @{ | ||||
|   */ | ||||
| uint8_t BSP_SD_MaxMountRetryCount(); | ||||
| uint8_t BSP_SD_Init(bool reset_card); | ||||
| uint8_t | ||||
|     BSP_SD_ReadBlocks(uint32_t* pData, uint32_t ReadAddr, uint32_t NumOfBlocks, uint32_t Timeout); | ||||
| uint8_t | ||||
|     BSP_SD_WriteBlocks(uint32_t* pData, uint32_t WriteAddr, uint32_t NumOfBlocks, uint32_t Timeout); | ||||
| uint8_t BSP_SD_Erase(uint32_t StartAddr, uint32_t EndAddr); | ||||
| uint8_t BSP_SD_GetCardState(void); | ||||
| uint8_t BSP_SD_GetCardInfo(SD_CardInfo* pCardInfo); | ||||
| uint8_t BSP_SD_GetCIDRegister(SD_CID* Cid); | ||||
| 
 | ||||
| /* Link functions for SD Card peripheral*/ | ||||
| void SD_SPI_Slow_Init(void); | ||||
| void SD_SPI_Fast_Init(void); | ||||
| void SD_IO_Init(void); | ||||
| void SD_IO_CSState(uint8_t state); | ||||
| void SD_IO_WriteReadData(const uint8_t* DataIn, uint8_t* DataOut, uint16_t DataLength); | ||||
| uint8_t SD_IO_WriteByte(uint8_t Data); | ||||
| 
 | ||||
| /* Link function for HAL delay */ | ||||
| void HAL_Delay(__IO uint32_t Delay); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| #endif /* __STM32_ADAFRUIT_SD_H */ | ||||
| 
 | ||||
| /**
 | ||||
|   * @} | ||||
|   */ | ||||
| 
 | ||||
| /**
 | ||||
|   * @} | ||||
|   */ | ||||
| 
 | ||||
| /**
 | ||||
|   * @} | ||||
|   */ | ||||
| 
 | ||||
| /**
 | ||||
|   * @} | ||||
|   */ | ||||
| 
 | ||||
| /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ | ||||
| @ -46,7 +46,7 @@ static volatile DSTATUS Stat = STA_NOINIT; | ||||
| static DSTATUS User_CheckStatus(BYTE lun) { | ||||
|     UNUSED(lun); | ||||
|     Stat = STA_NOINIT; | ||||
|     if(BSP_SD_GetCardState() == MSD_OK) { | ||||
|     if(sd_get_card_state() == SdSpiStatusOK) { | ||||
|         Stat &= ~STA_NOINIT; | ||||
|     } | ||||
| 
 | ||||
| @ -128,11 +128,18 @@ DRESULT USER_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count) { | ||||
|     furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast); | ||||
|     furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast; | ||||
| 
 | ||||
|     if(BSP_SD_ReadBlocks((uint32_t*)buff, (uint32_t)(sector), count, SD_DATATIMEOUT) == MSD_OK) { | ||||
|     if(sd_read_blocks((uint32_t*)buff, (uint32_t)(sector), count, SD_TIMEOUT_MS) == | ||||
|        SdSpiStatusOK) { | ||||
|         FuriHalCortexTimer timer = furi_hal_cortex_timer_get(SD_TIMEOUT_MS * 1000); | ||||
| 
 | ||||
|         /* wait until the read operation is finished */ | ||||
|         while(BSP_SD_GetCardState() != MSD_OK) { | ||||
|         } | ||||
|         res = RES_OK; | ||||
|         while(sd_get_card_state() != SdSpiStatusOK) { | ||||
|             if(furi_hal_cortex_timer_is_expired(timer)) { | ||||
|                 res = RES_ERROR; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     furi_hal_sd_spi_handle = NULL; | ||||
| @ -160,11 +167,18 @@ DRESULT USER_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT count) { | ||||
|     furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast); | ||||
|     furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast; | ||||
| 
 | ||||
|     if(BSP_SD_WriteBlocks((uint32_t*)buff, (uint32_t)(sector), count, SD_DATATIMEOUT) == MSD_OK) { | ||||
|     if(sd_write_blocks((uint32_t*)buff, (uint32_t)(sector), count, SD_TIMEOUT_MS) == | ||||
|        SdSpiStatusOK) { | ||||
|         FuriHalCortexTimer timer = furi_hal_cortex_timer_get(SD_TIMEOUT_MS * 1000); | ||||
| 
 | ||||
|         /* wait until the Write operation is finished */ | ||||
|         while(BSP_SD_GetCardState() != MSD_OK) { | ||||
|         } | ||||
|         res = RES_OK; | ||||
|         while(sd_get_card_state() != SdSpiStatusOK) { | ||||
|             if(furi_hal_cortex_timer_is_expired(timer)) { | ||||
|                 res = RES_ERROR; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     furi_hal_sd_spi_handle = NULL; | ||||
| @ -187,7 +201,7 @@ DRESULT USER_ioctl(BYTE pdrv, BYTE cmd, void* buff) { | ||||
|     /* USER CODE BEGIN IOCTL */ | ||||
|     UNUSED(pdrv); | ||||
|     DRESULT res = RES_ERROR; | ||||
|     BSP_SD_CardInfo CardInfo; | ||||
|     SD_CardInfo CardInfo; | ||||
| 
 | ||||
|     if(Stat & STA_NOINIT) return RES_NOTRDY; | ||||
| 
 | ||||
| @ -202,21 +216,21 @@ DRESULT USER_ioctl(BYTE pdrv, BYTE cmd, void* buff) { | ||||
| 
 | ||||
|     /* Get number of sectors on the disk (DWORD) */ | ||||
|     case GET_SECTOR_COUNT: | ||||
|         BSP_SD_GetCardInfo(&CardInfo); | ||||
|         sd_get_card_info(&CardInfo); | ||||
|         *(DWORD*)buff = CardInfo.LogBlockNbr; | ||||
|         res = RES_OK; | ||||
|         break; | ||||
| 
 | ||||
|     /* Get R/W sector size (WORD) */ | ||||
|     case GET_SECTOR_SIZE: | ||||
|         BSP_SD_GetCardInfo(&CardInfo); | ||||
|         sd_get_card_info(&CardInfo); | ||||
|         *(WORD*)buff = CardInfo.LogBlockSize; | ||||
|         res = RES_OK; | ||||
|         break; | ||||
| 
 | ||||
|     /* Get erase block size in unit of sector (DWORD) */ | ||||
|     case GET_BLOCK_SIZE: | ||||
|         BSP_SD_GetCardInfo(&CardInfo); | ||||
|         sd_get_card_info(&CardInfo); | ||||
|         *(DWORD*)buff = CardInfo.LogBlockSize; | ||||
|         res = RES_OK; | ||||
|         break; | ||||
|  | ||||
| @ -30,7 +30,7 @@ extern "C" { | ||||
| /* USER CODE BEGIN 0 */ | ||||
| 
 | ||||
| /* Includes ------------------------------------------------------------------*/ | ||||
| #include "stm32_adafruit_sd.h" | ||||
| #include "sd_spi_io.h" | ||||
| #include "fatfs/ff_gen_drv.h" | ||||
| /* Exported types ------------------------------------------------------------*/ | ||||
| /* Exported constants --------------------------------------------------------*/ | ||||
|  | ||||
| @ -53,6 +53,7 @@ void furi_hal_init() { | ||||
|     furi_hal_region_init(); | ||||
| 
 | ||||
|     furi_hal_spi_config_init(); | ||||
|     furi_hal_spi_dma_init(); | ||||
| 
 | ||||
|     furi_hal_ibutton_init(); | ||||
|     FURI_LOG_I(TAG, "iButton OK"); | ||||
|  | ||||
| @ -28,6 +28,15 @@ const GpioPin gpio_infrared_tx_debug = {.port = GPIOA, .pin = GPIO_PIN_7}; | ||||
| #define INFRARED_TX_CCMR_LOW \ | ||||
|     (TIM_CCMR2_OC3PE | LL_TIM_OCMODE_FORCED_INACTIVE) /* Space time - force low */ | ||||
| 
 | ||||
| /* DMA Channels definition */ | ||||
| #define IR_DMA DMA2 | ||||
| #define IR_DMA_CH1_CHANNEL LL_DMA_CHANNEL_1 | ||||
| #define IR_DMA_CH2_CHANNEL LL_DMA_CHANNEL_2 | ||||
| #define IR_DMA_CH1_IRQ FuriHalInterruptIdDma2Ch1 | ||||
| #define IR_DMA_CH2_IRQ FuriHalInterruptIdDma2Ch2 | ||||
| #define IR_DMA_CH1_DEF IR_DMA, IR_DMA_CH1_CHANNEL | ||||
| #define IR_DMA_CH2_DEF IR_DMA, IR_DMA_CH2_CHANNEL | ||||
| 
 | ||||
| typedef struct { | ||||
|     FuriHalInfraredRxCaptureCallback capture_callback; | ||||
|     void* capture_context; | ||||
| @ -213,15 +222,15 @@ void furi_hal_infrared_async_rx_set_timeout_isr_callback( | ||||
| } | ||||
| 
 | ||||
| static void furi_hal_infrared_tx_dma_terminate(void) { | ||||
|     LL_DMA_DisableIT_TC(DMA1, LL_DMA_CHANNEL_1); | ||||
|     LL_DMA_DisableIT_HT(DMA1, LL_DMA_CHANNEL_2); | ||||
|     LL_DMA_DisableIT_TC(DMA1, LL_DMA_CHANNEL_2); | ||||
|     LL_DMA_DisableIT_TC(IR_DMA_CH1_DEF); | ||||
|     LL_DMA_DisableIT_HT(IR_DMA_CH2_DEF); | ||||
|     LL_DMA_DisableIT_TC(IR_DMA_CH2_DEF); | ||||
| 
 | ||||
|     furi_assert(furi_hal_infrared_state == InfraredStateAsyncTxStopInProgress); | ||||
| 
 | ||||
|     LL_DMA_DisableIT_TC(DMA1, LL_DMA_CHANNEL_1); | ||||
|     LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2); | ||||
|     LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1); | ||||
|     LL_DMA_DisableIT_TC(IR_DMA_CH1_DEF); | ||||
|     LL_DMA_DisableChannel(IR_DMA_CH2_DEF); | ||||
|     LL_DMA_DisableChannel(IR_DMA_CH1_DEF); | ||||
|     LL_TIM_DisableCounter(TIM1); | ||||
|     FuriStatus status = furi_semaphore_release(infrared_tim_tx.stop_semaphore); | ||||
|     furi_check(status == FuriStatusOk); | ||||
| @ -230,7 +239,7 @@ static void furi_hal_infrared_tx_dma_terminate(void) { | ||||
| 
 | ||||
| static uint8_t furi_hal_infrared_get_current_dma_tx_buffer(void) { | ||||
|     uint8_t buf_num = 0; | ||||
|     uint32_t buffer_adr = LL_DMA_GetMemoryAddress(DMA1, LL_DMA_CHANNEL_2); | ||||
|     uint32_t buffer_adr = LL_DMA_GetMemoryAddress(IR_DMA_CH2_DEF); | ||||
|     if(buffer_adr == (uint32_t)infrared_tim_tx.buffer[0].data) { | ||||
|         buf_num = 0; | ||||
|     } else if(buffer_adr == (uint32_t)infrared_tim_tx.buffer[1].data) { | ||||
| @ -242,12 +251,13 @@ static uint8_t furi_hal_infrared_get_current_dma_tx_buffer(void) { | ||||
| } | ||||
| 
 | ||||
| static void furi_hal_infrared_tx_dma_polarity_isr() { | ||||
|     if(LL_DMA_IsActiveFlag_TE1(DMA1)) { | ||||
|         LL_DMA_ClearFlag_TE1(DMA1); | ||||
| #if IR_DMA_CH1_CHANNEL == LL_DMA_CHANNEL_1 | ||||
|     if(LL_DMA_IsActiveFlag_TE1(IR_DMA)) { | ||||
|         LL_DMA_ClearFlag_TE1(IR_DMA); | ||||
|         furi_crash(NULL); | ||||
|     } | ||||
|     if(LL_DMA_IsActiveFlag_TC1(DMA1) && LL_DMA_IsEnabledIT_TC(DMA1, LL_DMA_CHANNEL_1)) { | ||||
|         LL_DMA_ClearFlag_TC1(DMA1); | ||||
|     if(LL_DMA_IsActiveFlag_TC1(IR_DMA) && LL_DMA_IsEnabledIT_TC(IR_DMA_CH1_DEF)) { | ||||
|         LL_DMA_ClearFlag_TC1(IR_DMA); | ||||
| 
 | ||||
|         furi_check( | ||||
|             (furi_hal_infrared_state == InfraredStateAsyncTx) || | ||||
| @ -257,25 +267,29 @@ static void furi_hal_infrared_tx_dma_polarity_isr() { | ||||
|         uint8_t next_buf_num = furi_hal_infrared_get_current_dma_tx_buffer(); | ||||
|         furi_hal_infrared_tx_dma_set_polarity(next_buf_num, 0); | ||||
|     } | ||||
| #else | ||||
| #error Update this code. Would you kindly? | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| static void furi_hal_infrared_tx_dma_isr() { | ||||
|     if(LL_DMA_IsActiveFlag_TE2(DMA1)) { | ||||
|         LL_DMA_ClearFlag_TE2(DMA1); | ||||
| #if IR_DMA_CH2_CHANNEL == LL_DMA_CHANNEL_2 | ||||
|     if(LL_DMA_IsActiveFlag_TE2(IR_DMA)) { | ||||
|         LL_DMA_ClearFlag_TE2(IR_DMA); | ||||
|         furi_crash(NULL); | ||||
|     } | ||||
|     if(LL_DMA_IsActiveFlag_HT2(DMA1) && LL_DMA_IsEnabledIT_HT(DMA1, LL_DMA_CHANNEL_2)) { | ||||
|         LL_DMA_ClearFlag_HT2(DMA1); | ||||
|     if(LL_DMA_IsActiveFlag_HT2(IR_DMA) && LL_DMA_IsEnabledIT_HT(IR_DMA_CH2_DEF)) { | ||||
|         LL_DMA_ClearFlag_HT2(IR_DMA); | ||||
|         uint8_t buf_num = furi_hal_infrared_get_current_dma_tx_buffer(); | ||||
|         uint8_t next_buf_num = !buf_num; | ||||
|         if(infrared_tim_tx.buffer[buf_num].last_packet_end) { | ||||
|             LL_DMA_DisableIT_HT(DMA1, LL_DMA_CHANNEL_2); | ||||
|             LL_DMA_DisableIT_HT(IR_DMA_CH2_DEF); | ||||
|         } else if( | ||||
|             !infrared_tim_tx.buffer[buf_num].packet_end || | ||||
|             (furi_hal_infrared_state == InfraredStateAsyncTx)) { | ||||
|             furi_hal_infrared_tx_fill_buffer(next_buf_num, 0); | ||||
|             if(infrared_tim_tx.buffer[next_buf_num].last_packet_end) { | ||||
|                 LL_DMA_DisableIT_HT(DMA1, LL_DMA_CHANNEL_2); | ||||
|                 LL_DMA_DisableIT_HT(IR_DMA_CH2_DEF); | ||||
|             } | ||||
|         } else if(furi_hal_infrared_state == InfraredStateAsyncTxStopReq) { | ||||
|             /* fallthrough */ | ||||
| @ -283,8 +297,8 @@ static void furi_hal_infrared_tx_dma_isr() { | ||||
|             furi_crash(NULL); | ||||
|         } | ||||
|     } | ||||
|     if(LL_DMA_IsActiveFlag_TC2(DMA1) && LL_DMA_IsEnabledIT_TC(DMA1, LL_DMA_CHANNEL_2)) { | ||||
|         LL_DMA_ClearFlag_TC2(DMA1); | ||||
|     if(LL_DMA_IsActiveFlag_TC2(IR_DMA) && LL_DMA_IsEnabledIT_TC(IR_DMA_CH2_DEF)) { | ||||
|         LL_DMA_ClearFlag_TC2(IR_DMA); | ||||
|         furi_check( | ||||
|             (furi_hal_infrared_state == InfraredStateAsyncTxStopInProgress) || | ||||
|             (furi_hal_infrared_state == InfraredStateAsyncTxStopReq) || | ||||
| @ -310,6 +324,9 @@ static void furi_hal_infrared_tx_dma_isr() { | ||||
|             infrared_tim_tx.signal_sent_callback(infrared_tim_tx.signal_sent_context); | ||||
|         } | ||||
|     } | ||||
| #else | ||||
| #error Update this code. Would you kindly? | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| static void furi_hal_infrared_configure_tim_pwm_tx(uint32_t freq, float duty_cycle) { | ||||
| @ -369,16 +386,19 @@ static void furi_hal_infrared_configure_tim_cmgr2_dma_tx(void) { | ||||
|     dma_config.NbData = 0; | ||||
|     dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM1_UP; | ||||
|     dma_config.Priority = LL_DMA_PRIORITY_VERYHIGH; | ||||
|     LL_DMA_Init(DMA1, LL_DMA_CHANNEL_1, &dma_config); | ||||
|     LL_DMA_Init(IR_DMA_CH1_DEF, &dma_config); | ||||
| 
 | ||||
|     LL_DMA_ClearFlag_TE1(DMA1); | ||||
|     LL_DMA_ClearFlag_TC1(DMA1); | ||||
| #if IR_DMA_CH1_CHANNEL == LL_DMA_CHANNEL_1 | ||||
|     LL_DMA_ClearFlag_TE1(IR_DMA); | ||||
|     LL_DMA_ClearFlag_TC1(IR_DMA); | ||||
| #else | ||||
| #error Update this code. Would you kindly? | ||||
| #endif | ||||
| 
 | ||||
|     LL_DMA_EnableIT_TE(DMA1, LL_DMA_CHANNEL_1); | ||||
|     LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_1); | ||||
|     LL_DMA_EnableIT_TE(IR_DMA_CH1_DEF); | ||||
|     LL_DMA_EnableIT_TC(IR_DMA_CH1_DEF); | ||||
| 
 | ||||
|     furi_hal_interrupt_set_isr_ex( | ||||
|         FuriHalInterruptIdDma1Ch1, 4, furi_hal_infrared_tx_dma_polarity_isr, NULL); | ||||
|     furi_hal_interrupt_set_isr_ex(IR_DMA_CH1_IRQ, 4, furi_hal_infrared_tx_dma_polarity_isr, NULL); | ||||
| } | ||||
| 
 | ||||
| static void furi_hal_infrared_configure_tim_rcr_dma_tx(void) { | ||||
| @ -394,18 +414,21 @@ static void furi_hal_infrared_configure_tim_rcr_dma_tx(void) { | ||||
|     dma_config.NbData = 0; | ||||
|     dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM1_UP; | ||||
|     dma_config.Priority = LL_DMA_PRIORITY_MEDIUM; | ||||
|     LL_DMA_Init(DMA1, LL_DMA_CHANNEL_2, &dma_config); | ||||
|     LL_DMA_Init(IR_DMA_CH2_DEF, &dma_config); | ||||
| 
 | ||||
|     LL_DMA_ClearFlag_TC2(DMA1); | ||||
|     LL_DMA_ClearFlag_HT2(DMA1); | ||||
|     LL_DMA_ClearFlag_TE2(DMA1); | ||||
| #if IR_DMA_CH2_CHANNEL == LL_DMA_CHANNEL_2 | ||||
|     LL_DMA_ClearFlag_TC2(IR_DMA); | ||||
|     LL_DMA_ClearFlag_HT2(IR_DMA); | ||||
|     LL_DMA_ClearFlag_TE2(IR_DMA); | ||||
| #else | ||||
| #error Update this code. Would you kindly? | ||||
| #endif | ||||
| 
 | ||||
|     LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_2); | ||||
|     LL_DMA_EnableIT_HT(DMA1, LL_DMA_CHANNEL_2); | ||||
|     LL_DMA_EnableIT_TE(DMA1, LL_DMA_CHANNEL_2); | ||||
|     LL_DMA_EnableIT_TC(IR_DMA_CH2_DEF); | ||||
|     LL_DMA_EnableIT_HT(IR_DMA_CH2_DEF); | ||||
|     LL_DMA_EnableIT_TE(IR_DMA_CH2_DEF); | ||||
| 
 | ||||
|     furi_hal_interrupt_set_isr_ex( | ||||
|         FuriHalInterruptIdDma1Ch2, 5, furi_hal_infrared_tx_dma_isr, NULL); | ||||
|     furi_hal_interrupt_set_isr_ex(IR_DMA_CH2_IRQ, 5, furi_hal_infrared_tx_dma_isr, NULL); | ||||
| } | ||||
| 
 | ||||
| static void furi_hal_infrared_tx_fill_buffer_last(uint8_t buf_num) { | ||||
| @ -507,14 +530,14 @@ static void furi_hal_infrared_tx_dma_set_polarity(uint8_t buf_num, uint8_t polar | ||||
|     furi_assert(buffer->polarity != NULL); | ||||
| 
 | ||||
|     FURI_CRITICAL_ENTER(); | ||||
|     bool channel_enabled = LL_DMA_IsEnabledChannel(DMA1, LL_DMA_CHANNEL_1); | ||||
|     bool channel_enabled = LL_DMA_IsEnabledChannel(IR_DMA_CH1_DEF); | ||||
|     if(channel_enabled) { | ||||
|         LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1); | ||||
|         LL_DMA_DisableChannel(IR_DMA_CH1_DEF); | ||||
|     } | ||||
|     LL_DMA_SetMemoryAddress(DMA1, LL_DMA_CHANNEL_1, (uint32_t)buffer->polarity); | ||||
|     LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_1, buffer->size + polarity_shift); | ||||
|     LL_DMA_SetMemoryAddress(IR_DMA_CH1_DEF, (uint32_t)buffer->polarity); | ||||
|     LL_DMA_SetDataLength(IR_DMA_CH1_DEF, buffer->size + polarity_shift); | ||||
|     if(channel_enabled) { | ||||
|         LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1); | ||||
|         LL_DMA_EnableChannel(IR_DMA_CH1_DEF); | ||||
|     } | ||||
|     FURI_CRITICAL_EXIT(); | ||||
| } | ||||
| @ -527,14 +550,14 @@ static void furi_hal_infrared_tx_dma_set_buffer(uint8_t buf_num) { | ||||
| 
 | ||||
|     /* non-circular mode requires disabled channel before setup */ | ||||
|     FURI_CRITICAL_ENTER(); | ||||
|     bool channel_enabled = LL_DMA_IsEnabledChannel(DMA1, LL_DMA_CHANNEL_2); | ||||
|     bool channel_enabled = LL_DMA_IsEnabledChannel(IR_DMA_CH2_DEF); | ||||
|     if(channel_enabled) { | ||||
|         LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2); | ||||
|         LL_DMA_DisableChannel(IR_DMA_CH2_DEF); | ||||
|     } | ||||
|     LL_DMA_SetMemoryAddress(DMA1, LL_DMA_CHANNEL_2, (uint32_t)buffer->data); | ||||
|     LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_2, buffer->size); | ||||
|     LL_DMA_SetMemoryAddress(IR_DMA_CH2_DEF, (uint32_t)buffer->data); | ||||
|     LL_DMA_SetDataLength(IR_DMA_CH2_DEF, buffer->size); | ||||
|     if(channel_enabled) { | ||||
|         LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2); | ||||
|         LL_DMA_EnableChannel(IR_DMA_CH2_DEF); | ||||
|     } | ||||
|     FURI_CRITICAL_EXIT(); | ||||
| } | ||||
| @ -545,8 +568,8 @@ static void furi_hal_infrared_async_tx_free_resources(void) { | ||||
|         (furi_hal_infrared_state == InfraredStateAsyncTxStopped)); | ||||
| 
 | ||||
|     furi_hal_gpio_init(&gpio_infrared_tx, GpioModeOutputOpenDrain, GpioPullDown, GpioSpeedLow); | ||||
|     furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch1, NULL, NULL); | ||||
|     furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch2, NULL, NULL); | ||||
|     furi_hal_interrupt_set_isr(IR_DMA_CH1_IRQ, NULL, NULL); | ||||
|     furi_hal_interrupt_set_isr(IR_DMA_CH2_IRQ, NULL, NULL); | ||||
|     LL_TIM_DeInit(TIM1); | ||||
| 
 | ||||
|     furi_semaphore_free(infrared_tim_tx.stop_semaphore); | ||||
| @ -597,8 +620,8 @@ void furi_hal_infrared_async_tx_start(uint32_t freq, float duty_cycle) { | ||||
|     furi_hal_infrared_state = InfraredStateAsyncTx; | ||||
| 
 | ||||
|     LL_TIM_ClearFlag_UPDATE(TIM1); | ||||
|     LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1); | ||||
|     LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2); | ||||
|     LL_DMA_EnableChannel(IR_DMA_CH1_DEF); | ||||
|     LL_DMA_EnableChannel(IR_DMA_CH2_DEF); | ||||
|     furi_delay_us(5); | ||||
|     LL_TIM_GenerateEvent_UPDATE(TIM1); /* DMA -> TIMx_RCR */ | ||||
|     furi_delay_us(5); | ||||
|  | ||||
| @ -21,6 +21,14 @@ | ||||
| #define RFID_CAPTURE_IND_CH LL_TIM_CHANNEL_CH3 | ||||
| #define RFID_CAPTURE_DIR_CH LL_TIM_CHANNEL_CH4 | ||||
| 
 | ||||
| /* DMA Channels definition */ | ||||
| #define RFID_DMA DMA2 | ||||
| #define RFID_DMA_CH1_CHANNEL LL_DMA_CHANNEL_1 | ||||
| #define RFID_DMA_CH2_CHANNEL LL_DMA_CHANNEL_2 | ||||
| #define RFID_DMA_CH1_IRQ FuriHalInterruptIdDma2Ch1 | ||||
| #define RFID_DMA_CH1_DEF RFID_DMA, RFID_DMA_CH1_CHANNEL | ||||
| #define RFID_DMA_CH2_DEF RFID_DMA, RFID_DMA_CH2_CHANNEL | ||||
| 
 | ||||
| typedef struct { | ||||
|     FuriHalRfidEmulateCallback callback; | ||||
|     FuriHalRfidDMACallback dma_callback; | ||||
| @ -302,15 +310,19 @@ void furi_hal_rfid_tim_read_capture_stop() { | ||||
| } | ||||
| 
 | ||||
| static void furi_hal_rfid_dma_isr() { | ||||
|     if(LL_DMA_IsActiveFlag_HT1(DMA1)) { | ||||
|         LL_DMA_ClearFlag_HT1(DMA1); | ||||
| #if RFID_DMA_CH1_CHANNEL == LL_DMA_CHANNEL_1 | ||||
|     if(LL_DMA_IsActiveFlag_HT1(RFID_DMA)) { | ||||
|         LL_DMA_ClearFlag_HT1(RFID_DMA); | ||||
|         furi_hal_rfid->dma_callback(true, furi_hal_rfid->context); | ||||
|     } | ||||
| 
 | ||||
|     if(LL_DMA_IsActiveFlag_TC1(DMA1)) { | ||||
|         LL_DMA_ClearFlag_TC1(DMA1); | ||||
|     if(LL_DMA_IsActiveFlag_TC1(RFID_DMA)) { | ||||
|         LL_DMA_ClearFlag_TC1(RFID_DMA); | ||||
|         furi_hal_rfid->dma_callback(false, furi_hal_rfid->context); | ||||
|     } | ||||
| #else | ||||
| #error Update this code. Would you kindly? | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| void furi_hal_rfid_tim_emulate_dma_start( | ||||
| @ -347,8 +359,8 @@ void furi_hal_rfid_tim_emulate_dma_start( | ||||
|     dma_config.NbData = length; | ||||
|     dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; | ||||
|     dma_config.Priority = LL_DMA_MODE_NORMAL; | ||||
|     LL_DMA_Init(DMA1, LL_DMA_CHANNEL_1, &dma_config); | ||||
|     LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1); | ||||
|     LL_DMA_Init(RFID_DMA_CH1_DEF, &dma_config); | ||||
|     LL_DMA_EnableChannel(RFID_DMA_CH1_DEF); | ||||
| 
 | ||||
|     // configure DMA "mem -> CCR3" channel
 | ||||
| #if FURI_HAL_RFID_EMULATE_TIMER_CHANNEL == LL_TIM_CHANNEL_CH3 | ||||
| @ -366,13 +378,13 @@ void furi_hal_rfid_tim_emulate_dma_start( | ||||
|     dma_config.NbData = length; | ||||
|     dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; | ||||
|     dma_config.Priority = LL_DMA_MODE_NORMAL; | ||||
|     LL_DMA_Init(DMA1, LL_DMA_CHANNEL_2, &dma_config); | ||||
|     LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2); | ||||
|     LL_DMA_Init(RFID_DMA_CH2_DEF, &dma_config); | ||||
|     LL_DMA_EnableChannel(RFID_DMA_CH2_DEF); | ||||
| 
 | ||||
|     // attach interrupt to one of DMA channels
 | ||||
|     furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch1, furi_hal_rfid_dma_isr, NULL); | ||||
|     LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_1); | ||||
|     LL_DMA_EnableIT_HT(DMA1, LL_DMA_CHANNEL_1); | ||||
|     furi_hal_interrupt_set_isr(RFID_DMA_CH1_IRQ, furi_hal_rfid_dma_isr, NULL); | ||||
|     LL_DMA_EnableIT_TC(RFID_DMA_CH1_DEF); | ||||
|     LL_DMA_EnableIT_HT(RFID_DMA_CH1_DEF); | ||||
| 
 | ||||
|     // start
 | ||||
|     LL_TIM_EnableAllOutputs(FURI_HAL_RFID_EMULATE_TIMER); | ||||
| @ -385,14 +397,14 @@ void furi_hal_rfid_tim_emulate_dma_stop() { | ||||
|     LL_TIM_DisableCounter(FURI_HAL_RFID_EMULATE_TIMER); | ||||
|     LL_TIM_DisableAllOutputs(FURI_HAL_RFID_EMULATE_TIMER); | ||||
| 
 | ||||
|     furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch1, NULL, NULL); | ||||
|     LL_DMA_DisableIT_TC(DMA1, LL_DMA_CHANNEL_1); | ||||
|     LL_DMA_DisableIT_HT(DMA1, LL_DMA_CHANNEL_1); | ||||
|     furi_hal_interrupt_set_isr(RFID_DMA_CH1_IRQ, NULL, NULL); | ||||
|     LL_DMA_DisableIT_TC(RFID_DMA_CH1_DEF); | ||||
|     LL_DMA_DisableIT_HT(RFID_DMA_CH1_DEF); | ||||
| 
 | ||||
|     FURI_CRITICAL_ENTER(); | ||||
| 
 | ||||
|     LL_DMA_DeInit(DMA1, LL_DMA_CHANNEL_1); | ||||
|     LL_DMA_DeInit(DMA1, LL_DMA_CHANNEL_2); | ||||
|     LL_DMA_DeInit(RFID_DMA_CH1_DEF); | ||||
|     LL_DMA_DeInit(RFID_DMA_CH2_DEF); | ||||
|     LL_TIM_DeInit(FURI_HAL_RFID_EMULATE_TIMER); | ||||
| 
 | ||||
|     FURI_CRITICAL_EXIT(); | ||||
|  | ||||
| @ -1,14 +1,33 @@ | ||||
| #include <furi.h> | ||||
| #include <furi_hal_spi.h> | ||||
| #include <furi_hal_resources.h> | ||||
| #include <furi_hal_power.h> | ||||
| #include <furi_hal_interrupt.h> | ||||
| 
 | ||||
| #include <stdbool.h> | ||||
| #include <string.h> | ||||
| 
 | ||||
| #include <stm32wbxx_ll_dma.h> | ||||
| #include <stm32wbxx_ll_spi.h> | ||||
| #include <stm32wbxx_ll_utils.h> | ||||
| #include <stm32wbxx_ll_cortex.h> | ||||
| 
 | ||||
| #define TAG "FuriHalSpi" | ||||
| 
 | ||||
| #define SPI_DMA DMA2 | ||||
| #define SPI_DMA_RX_CHANNEL LL_DMA_CHANNEL_3 | ||||
| #define SPI_DMA_TX_CHANNEL LL_DMA_CHANNEL_4 | ||||
| #define SPI_DMA_RX_IRQ FuriHalInterruptIdDma2Ch3 | ||||
| #define SPI_DMA_TX_IRQ FuriHalInterruptIdDma2Ch4 | ||||
| #define SPI_DMA_RX_DEF SPI_DMA, SPI_DMA_RX_CHANNEL | ||||
| #define SPI_DMA_TX_DEF SPI_DMA, SPI_DMA_TX_CHANNEL | ||||
| 
 | ||||
| // For simplicity, I assume that only one SPI DMA transaction can occur at a time.
 | ||||
| static FuriSemaphore* spi_dma_lock = NULL; | ||||
| static FuriSemaphore* spi_dma_completed = NULL; | ||||
| 
 | ||||
| void furi_hal_spi_dma_init() { | ||||
|     spi_dma_lock = furi_semaphore_alloc(1, 1); | ||||
|     spi_dma_completed = furi_semaphore_alloc(1, 1); | ||||
| } | ||||
| 
 | ||||
| void furi_hal_spi_bus_init(FuriHalSpiBus* bus) { | ||||
|     furi_assert(bus); | ||||
|     bus->callback(bus, FuriHalSpiBusEventInit); | ||||
| @ -149,3 +168,209 @@ bool furi_hal_spi_bus_trx( | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| static void spi_dma_isr() { | ||||
| #if SPI_DMA_RX_CHANNEL == LL_DMA_CHANNEL_3 | ||||
|     if(LL_DMA_IsActiveFlag_TC3(SPI_DMA) && LL_DMA_IsEnabledIT_TC(SPI_DMA_RX_DEF)) { | ||||
|         LL_DMA_ClearFlag_TC3(SPI_DMA); | ||||
|         furi_check(furi_semaphore_release(spi_dma_completed) == FuriStatusOk); | ||||
|     } | ||||
| #else | ||||
| #error Update this code. Would you kindly? | ||||
| #endif | ||||
| 
 | ||||
| #if SPI_DMA_TX_CHANNEL == LL_DMA_CHANNEL_4 | ||||
|     if(LL_DMA_IsActiveFlag_TC4(SPI_DMA) && LL_DMA_IsEnabledIT_TC(SPI_DMA_TX_DEF)) { | ||||
|         LL_DMA_ClearFlag_TC4(SPI_DMA); | ||||
|         furi_check(furi_semaphore_release(spi_dma_completed) == FuriStatusOk); | ||||
|     } | ||||
| #else | ||||
| #error Update this code. Would you kindly? | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| bool furi_hal_spi_bus_trx_dma( | ||||
|     FuriHalSpiBusHandle* handle, | ||||
|     uint8_t* tx_buffer, | ||||
|     uint8_t* rx_buffer, | ||||
|     size_t size, | ||||
|     uint32_t timeout_ms) { | ||||
|     furi_assert(handle); | ||||
|     furi_assert(handle->bus->current_handle == handle); | ||||
|     furi_assert(size > 0); | ||||
| 
 | ||||
|     // If scheduler is not running, use blocking mode
 | ||||
|     if(xTaskGetSchedulerState() != taskSCHEDULER_RUNNING) { | ||||
|         return furi_hal_spi_bus_trx(handle, tx_buffer, rx_buffer, size, timeout_ms); | ||||
|     } | ||||
| 
 | ||||
|     // Lock DMA
 | ||||
|     furi_check(furi_semaphore_acquire(spi_dma_lock, FuriWaitForever) == FuriStatusOk); | ||||
| 
 | ||||
|     const uint32_t dma_dummy_u32 = 0xFFFFFFFF; | ||||
| 
 | ||||
|     bool ret = true; | ||||
|     SPI_TypeDef* spi = handle->bus->spi; | ||||
|     uint32_t dma_rx_req; | ||||
|     uint32_t dma_tx_req; | ||||
| 
 | ||||
|     if(spi == SPI1) { | ||||
|         dma_rx_req = LL_DMAMUX_REQ_SPI1_RX; | ||||
|         dma_tx_req = LL_DMAMUX_REQ_SPI1_TX; | ||||
|     } else if(spi == SPI2) { | ||||
|         dma_rx_req = LL_DMAMUX_REQ_SPI2_RX; | ||||
|         dma_tx_req = LL_DMAMUX_REQ_SPI2_TX; | ||||
|     } else { | ||||
|         furi_crash(NULL); | ||||
|     } | ||||
| 
 | ||||
|     if(rx_buffer == NULL) { | ||||
|         // Only TX mode, do not use RX channel
 | ||||
| 
 | ||||
|         LL_DMA_InitTypeDef dma_config = {0}; | ||||
|         dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (spi->DR); | ||||
|         dma_config.MemoryOrM2MDstAddress = (uint32_t)tx_buffer; | ||||
|         dma_config.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; | ||||
|         dma_config.Mode = LL_DMA_MODE_NORMAL; | ||||
|         dma_config.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; | ||||
|         dma_config.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; | ||||
|         dma_config.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_BYTE; | ||||
|         dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_BYTE; | ||||
|         dma_config.NbData = size; | ||||
|         dma_config.PeriphRequest = dma_tx_req; | ||||
|         dma_config.Priority = LL_DMA_PRIORITY_MEDIUM; | ||||
|         LL_DMA_Init(SPI_DMA_TX_DEF, &dma_config); | ||||
| 
 | ||||
| #if SPI_DMA_TX_CHANNEL == LL_DMA_CHANNEL_4 | ||||
|         LL_DMA_ClearFlag_TC4(SPI_DMA); | ||||
| #else | ||||
| #error Update this code. Would you kindly? | ||||
| #endif | ||||
| 
 | ||||
|         furi_hal_interrupt_set_isr(SPI_DMA_TX_IRQ, spi_dma_isr, NULL); | ||||
| 
 | ||||
|         bool dma_tx_was_enabled = LL_SPI_IsEnabledDMAReq_TX(spi); | ||||
|         if(!dma_tx_was_enabled) { | ||||
|             LL_SPI_EnableDMAReq_TX(spi); | ||||
|         } | ||||
| 
 | ||||
|         // acquire semaphore before enabling DMA
 | ||||
|         furi_check(furi_semaphore_acquire(spi_dma_completed, timeout_ms) == FuriStatusOk); | ||||
| 
 | ||||
|         LL_DMA_EnableIT_TC(SPI_DMA_TX_DEF); | ||||
|         LL_DMA_EnableChannel(SPI_DMA_TX_DEF); | ||||
| 
 | ||||
|         // and wait for it to be released (DMA transfer complete)
 | ||||
|         if(furi_semaphore_acquire(spi_dma_completed, timeout_ms) != FuriStatusOk) { | ||||
|             ret = false; | ||||
|             FURI_LOG_E(TAG, "DMA timeout\r\n"); | ||||
|         } | ||||
|         // release semaphore, because we are using it as a flag
 | ||||
|         furi_semaphore_release(spi_dma_completed); | ||||
| 
 | ||||
|         LL_DMA_DisableIT_TC(SPI_DMA_TX_DEF); | ||||
|         LL_DMA_DisableChannel(SPI_DMA_TX_DEF); | ||||
|         if(!dma_tx_was_enabled) { | ||||
|             LL_SPI_DisableDMAReq_TX(spi); | ||||
|         } | ||||
|         furi_hal_interrupt_set_isr(SPI_DMA_TX_IRQ, NULL, NULL); | ||||
| 
 | ||||
|         LL_DMA_DeInit(SPI_DMA_TX_DEF); | ||||
|     } else { | ||||
|         // TRX or RX mode, use both channels
 | ||||
|         uint32_t tx_mem_increase_mode; | ||||
| 
 | ||||
|         if(tx_buffer == NULL) { | ||||
|             // RX mode, use dummy data instead of TX buffer
 | ||||
|             tx_buffer = (uint8_t*)&dma_dummy_u32; | ||||
|             tx_mem_increase_mode = LL_DMA_PERIPH_NOINCREMENT; | ||||
|         } else { | ||||
|             tx_mem_increase_mode = LL_DMA_MEMORY_INCREMENT; | ||||
|         } | ||||
| 
 | ||||
|         LL_DMA_InitTypeDef dma_config = {0}; | ||||
|         dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (spi->DR); | ||||
|         dma_config.MemoryOrM2MDstAddress = (uint32_t)tx_buffer; | ||||
|         dma_config.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; | ||||
|         dma_config.Mode = LL_DMA_MODE_NORMAL; | ||||
|         dma_config.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; | ||||
|         dma_config.MemoryOrM2MDstIncMode = tx_mem_increase_mode; | ||||
|         dma_config.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_BYTE; | ||||
|         dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_BYTE; | ||||
|         dma_config.NbData = size; | ||||
|         dma_config.PeriphRequest = dma_tx_req; | ||||
|         dma_config.Priority = LL_DMA_PRIORITY_MEDIUM; | ||||
|         LL_DMA_Init(SPI_DMA_TX_DEF, &dma_config); | ||||
| 
 | ||||
|         dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (spi->DR); | ||||
|         dma_config.MemoryOrM2MDstAddress = (uint32_t)rx_buffer; | ||||
|         dma_config.Direction = LL_DMA_DIRECTION_PERIPH_TO_MEMORY; | ||||
|         dma_config.Mode = LL_DMA_MODE_NORMAL; | ||||
|         dma_config.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; | ||||
|         dma_config.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; | ||||
|         dma_config.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_BYTE; | ||||
|         dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_BYTE; | ||||
|         dma_config.NbData = size; | ||||
|         dma_config.PeriphRequest = dma_rx_req; | ||||
|         dma_config.Priority = LL_DMA_PRIORITY_MEDIUM; | ||||
|         LL_DMA_Init(SPI_DMA_RX_DEF, &dma_config); | ||||
| 
 | ||||
| #if SPI_DMA_RX_CHANNEL == LL_DMA_CHANNEL_3 | ||||
|         LL_DMA_ClearFlag_TC3(SPI_DMA); | ||||
| #else | ||||
| #error Update this code. Would you kindly? | ||||
| #endif | ||||
| 
 | ||||
|         furi_hal_interrupt_set_isr(SPI_DMA_RX_IRQ, spi_dma_isr, NULL); | ||||
| 
 | ||||
|         bool dma_tx_was_enabled = LL_SPI_IsEnabledDMAReq_TX(spi); | ||||
|         bool dma_rx_was_enabled = LL_SPI_IsEnabledDMAReq_RX(spi); | ||||
| 
 | ||||
|         if(!dma_tx_was_enabled) { | ||||
|             LL_SPI_EnableDMAReq_TX(spi); | ||||
|         } | ||||
| 
 | ||||
|         if(!dma_rx_was_enabled) { | ||||
|             LL_SPI_EnableDMAReq_RX(spi); | ||||
|         } | ||||
| 
 | ||||
|         // acquire semaphore before enabling DMA
 | ||||
|         furi_check(furi_semaphore_acquire(spi_dma_completed, timeout_ms) == FuriStatusOk); | ||||
| 
 | ||||
|         LL_DMA_EnableIT_TC(SPI_DMA_RX_DEF); | ||||
|         LL_DMA_EnableChannel(SPI_DMA_RX_DEF); | ||||
|         LL_DMA_EnableChannel(SPI_DMA_TX_DEF); | ||||
| 
 | ||||
|         // and wait for it to be released (DMA transfer complete)
 | ||||
|         if(furi_semaphore_acquire(spi_dma_completed, timeout_ms) != FuriStatusOk) { | ||||
|             ret = false; | ||||
|             FURI_LOG_E(TAG, "DMA timeout\r\n"); | ||||
|         } | ||||
|         // release semaphore, because we are using it as a flag
 | ||||
|         furi_semaphore_release(spi_dma_completed); | ||||
| 
 | ||||
|         LL_DMA_DisableIT_TC(SPI_DMA_RX_DEF); | ||||
| 
 | ||||
|         LL_DMA_DisableChannel(SPI_DMA_TX_DEF); | ||||
|         LL_DMA_DisableChannel(SPI_DMA_RX_DEF); | ||||
| 
 | ||||
|         if(!dma_tx_was_enabled) { | ||||
|             LL_SPI_DisableDMAReq_TX(spi); | ||||
|         } | ||||
| 
 | ||||
|         if(!dma_rx_was_enabled) { | ||||
|             LL_SPI_DisableDMAReq_RX(spi); | ||||
|         } | ||||
| 
 | ||||
|         furi_hal_interrupt_set_isr(SPI_DMA_RX_IRQ, NULL, NULL); | ||||
| 
 | ||||
|         LL_DMA_DeInit(SPI_DMA_TX_DEF); | ||||
|         LL_DMA_DeInit(SPI_DMA_RX_DEF); | ||||
|     } | ||||
| 
 | ||||
|     furi_hal_spi_bus_end_txrx(handle, timeout_ms); | ||||
| 
 | ||||
|     furi_check(furi_semaphore_release(spi_dma_lock) == FuriStatusOk); | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| @ -18,6 +18,14 @@ | ||||
| 
 | ||||
| static uint32_t furi_hal_subghz_debug_gpio_buff[2]; | ||||
| 
 | ||||
| /* DMA Channels definition */ | ||||
| #define SUBGHZ_DMA DMA2 | ||||
| #define SUBGHZ_DMA_CH1_CHANNEL LL_DMA_CHANNEL_1 | ||||
| #define SUBGHZ_DMA_CH2_CHANNEL LL_DMA_CHANNEL_2 | ||||
| #define SUBGHZ_DMA_CH1_IRQ FuriHalInterruptIdDma2Ch1 | ||||
| #define SUBGHZ_DMA_CH1_DEF SUBGHZ_DMA, SUBGHZ_DMA_CH1_CHANNEL | ||||
| #define SUBGHZ_DMA_CH2_DEF SUBGHZ_DMA, SUBGHZ_DMA_CH2_CHANNEL | ||||
| 
 | ||||
| typedef struct { | ||||
|     volatile SubGhzState state; | ||||
|     volatile SubGhzRegulation regulation; | ||||
| @ -525,8 +533,8 @@ static void furi_hal_subghz_async_tx_refill(uint32_t* buffer, size_t samples) { | ||||
|             *buffer = 0; | ||||
|             buffer++; | ||||
|             samples--; | ||||
|             LL_DMA_DisableIT_HT(DMA1, LL_DMA_CHANNEL_1); | ||||
|             LL_DMA_DisableIT_TC(DMA1, LL_DMA_CHANNEL_1); | ||||
|             LL_DMA_DisableIT_HT(SUBGHZ_DMA_CH1_DEF); | ||||
|             LL_DMA_DisableIT_TC(SUBGHZ_DMA_CH1_DEF); | ||||
|             LL_TIM_EnableIT_UPDATE(TIM2); | ||||
|             break; | ||||
|         } else { | ||||
| @ -567,17 +575,22 @@ static void furi_hal_subghz_async_tx_refill(uint32_t* buffer, size_t samples) { | ||||
| 
 | ||||
| static void furi_hal_subghz_async_tx_dma_isr() { | ||||
|     furi_assert(furi_hal_subghz.state == SubGhzStateAsyncTx); | ||||
|     if(LL_DMA_IsActiveFlag_HT1(DMA1)) { | ||||
|         LL_DMA_ClearFlag_HT1(DMA1); | ||||
| 
 | ||||
| #if SUBGHZ_DMA_CH1_CHANNEL == LL_DMA_CHANNEL_1 | ||||
|     if(LL_DMA_IsActiveFlag_HT1(SUBGHZ_DMA)) { | ||||
|         LL_DMA_ClearFlag_HT1(SUBGHZ_DMA); | ||||
|         furi_hal_subghz_async_tx_refill( | ||||
|             furi_hal_subghz_async_tx.buffer, API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF); | ||||
|     } | ||||
|     if(LL_DMA_IsActiveFlag_TC1(DMA1)) { | ||||
|         LL_DMA_ClearFlag_TC1(DMA1); | ||||
|     if(LL_DMA_IsActiveFlag_TC1(SUBGHZ_DMA)) { | ||||
|         LL_DMA_ClearFlag_TC1(SUBGHZ_DMA); | ||||
|         furi_hal_subghz_async_tx_refill( | ||||
|             furi_hal_subghz_async_tx.buffer + API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF, | ||||
|             API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF); | ||||
|     } | ||||
| #else | ||||
| #error Update this code. Would you kindly? | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| static void furi_hal_subghz_async_tx_timer_isr() { | ||||
| @ -586,7 +599,7 @@ static void furi_hal_subghz_async_tx_timer_isr() { | ||||
|         if(LL_TIM_GetAutoReload(TIM2) == 0) { | ||||
|             if(furi_hal_subghz.state == SubGhzStateAsyncTx) { | ||||
|                 furi_hal_subghz.state = SubGhzStateAsyncTxLast; | ||||
|                 LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1); | ||||
|                 LL_DMA_DisableChannel(SUBGHZ_DMA_CH1_DEF); | ||||
|             } else if(furi_hal_subghz.state == SubGhzStateAsyncTxLast) { | ||||
|                 furi_hal_subghz.state = SubGhzStateAsyncTxEnd; | ||||
|                 //forcibly pulls the pin to the ground so that there is no carrier
 | ||||
| @ -634,11 +647,11 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* | ||||
|     dma_config.NbData = API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL; | ||||
|     dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; | ||||
|     dma_config.Priority = LL_DMA_MODE_NORMAL; | ||||
|     LL_DMA_Init(DMA1, LL_DMA_CHANNEL_1, &dma_config); | ||||
|     furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch1, furi_hal_subghz_async_tx_dma_isr, NULL); | ||||
|     LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_1); | ||||
|     LL_DMA_EnableIT_HT(DMA1, LL_DMA_CHANNEL_1); | ||||
|     LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1); | ||||
|     LL_DMA_Init(SUBGHZ_DMA_CH1_DEF, &dma_config); | ||||
|     furi_hal_interrupt_set_isr(SUBGHZ_DMA_CH1_IRQ, furi_hal_subghz_async_tx_dma_isr, NULL); | ||||
|     LL_DMA_EnableIT_TC(SUBGHZ_DMA_CH1_DEF); | ||||
|     LL_DMA_EnableIT_HT(SUBGHZ_DMA_CH1_DEF); | ||||
|     LL_DMA_EnableChannel(SUBGHZ_DMA_CH1_DEF); | ||||
| 
 | ||||
|     // Configure TIM2
 | ||||
|     LL_TIM_InitTypeDef TIM_InitStruct = {0}; | ||||
| @ -696,9 +709,9 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* | ||||
|         dma_config.NbData = 2; | ||||
|         dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; | ||||
|         dma_config.Priority = LL_DMA_PRIORITY_VERYHIGH; | ||||
|         LL_DMA_Init(DMA1, LL_DMA_CHANNEL_2, &dma_config); | ||||
|         LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_2, 2); | ||||
|         LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2); | ||||
|         LL_DMA_Init(SUBGHZ_DMA_CH2_DEF, &dma_config); | ||||
|         LL_DMA_SetDataLength(SUBGHZ_DMA_CH2_DEF, 2); | ||||
|         LL_DMA_EnableChannel(SUBGHZ_DMA_CH2_DEF); | ||||
|     } | ||||
| 
 | ||||
|     return true; | ||||
| @ -726,16 +739,16 @@ void furi_hal_subghz_stop_async_tx() { | ||||
|     furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, NULL, NULL); | ||||
| 
 | ||||
|     // Deinitialize DMA
 | ||||
|     LL_DMA_DeInit(DMA1, LL_DMA_CHANNEL_1); | ||||
|     LL_DMA_DeInit(SUBGHZ_DMA_CH1_DEF); | ||||
| 
 | ||||
|     furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch1, NULL, NULL); | ||||
|     furi_hal_interrupt_set_isr(SUBGHZ_DMA_CH1_IRQ, NULL, NULL); | ||||
| 
 | ||||
|     // Deinitialize GPIO
 | ||||
|     furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); | ||||
| 
 | ||||
|     // Stop debug
 | ||||
|     if(furi_hal_subghz_stop_debug()) { | ||||
|         LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2); | ||||
|         LL_DMA_DisableChannel(SUBGHZ_DMA_CH2_DEF); | ||||
|     } | ||||
| 
 | ||||
|     FURI_CRITICAL_EXIT(); | ||||
|  | ||||
| @ -23,8 +23,8 @@ static FATFS* pfs = NULL; | ||||
|     } | ||||
| 
 | ||||
| static bool flipper_update_mount_sd() { | ||||
|     for(int i = 0; i < BSP_SD_MaxMountRetryCount(); ++i) { | ||||
|         if(BSP_SD_Init((i % 2) == 0) != MSD_OK) { | ||||
|     for(int i = 0; i < sd_max_mount_retry_count(); ++i) { | ||||
|         if(sd_init((i % 2) == 0) != SdSpiStatusOK) { | ||||
|             /* Next attempt will be without card reset, let it settle */ | ||||
|             furi_delay_ms(1000); | ||||
|             continue; | ||||
|  | ||||
| @ -16,6 +16,9 @@ void furi_hal_spi_config_deinit_early(); | ||||
| /** Initialize SPI HAL */ | ||||
| void furi_hal_spi_config_init(); | ||||
| 
 | ||||
| /** Initialize SPI DMA HAL */ | ||||
| void furi_hal_spi_dma_init(); | ||||
| 
 | ||||
| /** Initialize SPI Bus
 | ||||
|  * | ||||
|  * @param      handle  pointer to FuriHalSpiBus instance | ||||
| @ -103,6 +106,23 @@ bool furi_hal_spi_bus_trx( | ||||
|     size_t size, | ||||
|     uint32_t timeout); | ||||
| 
 | ||||
| /** SPI Transmit and Receive with DMA
 | ||||
|  * | ||||
|  * @param      handle     pointer to FuriHalSpiBusHandle instance | ||||
|  * @param      tx_buffer  pointer to tx buffer | ||||
|  * @param      rx_buffer  pointer to rx buffer | ||||
|  * @param      size       transaction size (buffer size) | ||||
|  * @param      timeout_ms operation timeout in ms | ||||
|  * | ||||
|  * @return     true on success | ||||
|  */ | ||||
| bool furi_hal_spi_bus_trx_dma( | ||||
|     FuriHalSpiBusHandle* handle, | ||||
|     uint8_t* tx_buffer, | ||||
|     uint8_t* rx_buffer, | ||||
|     size_t size, | ||||
|     uint32_t timeout_ms); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
|  | ||||
| @ -32,7 +32,7 @@ uint32_t furi_event_flag_set(FuriEventFlag* instance, uint32_t flags) { | ||||
|     if(FURI_IS_IRQ_MODE()) { | ||||
|         yield = pdFALSE; | ||||
|         if(xEventGroupSetBitsFromISR(hEventGroup, (EventBits_t)flags, &yield) == pdFAIL) { | ||||
|             rflags = (uint32_t)FuriStatusErrorResource; | ||||
|             rflags = (uint32_t)FuriFlagErrorResource; | ||||
|         } else { | ||||
|             rflags = flags; | ||||
|             portYIELD_FROM_ISR(yield); | ||||
|  | ||||
| @ -195,6 +195,15 @@ void furi_thread_set_priority(FuriThread* thread, FuriThreadPriority priority) { | ||||
|     thread->priority = priority; | ||||
| } | ||||
| 
 | ||||
| void furi_thread_set_current_priority(FuriThreadPriority priority) { | ||||
|     UBaseType_t new_priority = priority ? priority : FuriThreadPriorityNormal; | ||||
|     vTaskPrioritySet(NULL, new_priority); | ||||
| } | ||||
| 
 | ||||
| FuriThreadPriority furi_thread_get_current_priority() { | ||||
|     return (FuriThreadPriority)uxTaskPriorityGet(NULL); | ||||
| } | ||||
| 
 | ||||
| void furi_thread_set_state_callback(FuriThread* thread, FuriThreadStateCallback callback) { | ||||
|     furi_assert(thread); | ||||
|     furi_assert(thread->state == FuriThreadStateStopped); | ||||
|  | ||||
| @ -122,6 +122,18 @@ void furi_thread_set_context(FuriThread* thread, void* context); | ||||
|  */ | ||||
| void furi_thread_set_priority(FuriThread* thread, FuriThreadPriority priority); | ||||
| 
 | ||||
| /** Set current thread priority
 | ||||
|  * | ||||
|  * @param      priority FuriThreadPriority value | ||||
|  */ | ||||
| void furi_thread_set_current_priority(FuriThreadPriority priority); | ||||
| 
 | ||||
| /** Get current thread priority
 | ||||
|  * | ||||
|  * @return     FuriThreadPriority value | ||||
|  */ | ||||
| FuriThreadPriority furi_thread_get_current_priority(); | ||||
| 
 | ||||
| /** Set FuriThread state change callback
 | ||||
|  * | ||||
|  * @param      thread    FuriThread instance | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Sergey Gavrilov
						Sergey Gavrilov