[FL-2060] FuriHal: SPI refactoring, flexible bus reconfiguration on fly, same design as i2c. (#853)
* FuriHal: SPI refactoring, flexible bus reconfigration on fly, same desiag as i2c. * Lib: update CC1101 driver documentation * FuriHal: update spi symbol names to match naming convention.
This commit is contained in:
		| @@ -1,10 +1,9 @@ | ||||
| #include <furi-hal-spi-config.h> | ||||
| #include <furi-hal-resources.h> | ||||
|  | ||||
| #define SPI_R SPI1 | ||||
| #define SPI_D SPI2 | ||||
| /* SPI Presets */ | ||||
|  | ||||
| const LL_SPI_InitTypeDef furi_hal_spi_config_nfc = { | ||||
| const LL_SPI_InitTypeDef furi_hal_spi_preset_2edge_low_8m = { | ||||
|     .Mode = LL_SPI_MODE_MASTER, | ||||
|     .TransferDirection = LL_SPI_FULL_DUPLEX, | ||||
|     .DataWidth = LL_SPI_DATAWIDTH_8BIT, | ||||
| @@ -17,7 +16,7 @@ const LL_SPI_InitTypeDef furi_hal_spi_config_nfc = { | ||||
|     .CRCPoly = 7, | ||||
| }; | ||||
|  | ||||
| const LL_SPI_InitTypeDef furi_hal_spi_config_subghz = { | ||||
| const LL_SPI_InitTypeDef furi_hal_spi_preset_1edge_low_8m = { | ||||
|     .Mode = LL_SPI_MODE_MASTER, | ||||
|     .TransferDirection = LL_SPI_FULL_DUPLEX, | ||||
|     .DataWidth = LL_SPI_DATAWIDTH_8BIT, | ||||
| @@ -30,7 +29,7 @@ const LL_SPI_InitTypeDef furi_hal_spi_config_subghz = { | ||||
|     .CRCPoly = 7, | ||||
| }; | ||||
|  | ||||
| const LL_SPI_InitTypeDef furi_hal_spi_config_display = { | ||||
| const LL_SPI_InitTypeDef furi_hal_spi_preset_1edge_low_4m = { | ||||
|     .Mode = LL_SPI_MODE_MASTER, | ||||
|     .TransferDirection = LL_SPI_FULL_DUPLEX, | ||||
|     .DataWidth = LL_SPI_DATAWIDTH_8BIT, | ||||
| @@ -43,10 +42,7 @@ const LL_SPI_InitTypeDef furi_hal_spi_config_display = { | ||||
|     .CRCPoly = 7, | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * SD Card in fast mode (after init) | ||||
|  */ | ||||
| const LL_SPI_InitTypeDef furi_hal_spi_config_sd_fast = { | ||||
| const LL_SPI_InitTypeDef furi_hal_spi_preset_1edge_low_16m = { | ||||
|     .Mode = LL_SPI_MODE_MASTER, | ||||
|     .TransferDirection = LL_SPI_FULL_DUPLEX, | ||||
|     .DataWidth = LL_SPI_DATAWIDTH_8BIT, | ||||
| @@ -59,10 +55,7 @@ const LL_SPI_InitTypeDef furi_hal_spi_config_sd_fast = { | ||||
|     .CRCPoly = 7, | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * SD Card in slow mode (before init) | ||||
|  */ | ||||
| const LL_SPI_InitTypeDef furi_hal_spi_config_sd_slow = { | ||||
| const LL_SPI_InitTypeDef furi_hal_spi_preset_1edge_low_2m = { | ||||
|     .Mode = LL_SPI_MODE_MASTER, | ||||
|     .TransferDirection = LL_SPI_FULL_DUPLEX, | ||||
|     .DataWidth = LL_SPI_DATAWIDTH_8BIT, | ||||
| @@ -75,40 +68,223 @@ const LL_SPI_InitTypeDef furi_hal_spi_config_sd_slow = { | ||||
|     .CRCPoly = 7, | ||||
| }; | ||||
|  | ||||
| const FuriHalSpiBus spi_r = { | ||||
|     .spi = SPI_R, | ||||
| /* SPI Buses */ | ||||
|  | ||||
| static void furi_hal_spi_bus_r_event_callback(FuriHalSpiBus* bus, FuriHalSpiBusEvent event) { | ||||
|     if(event == FuriHalSpiBusEventInit) { | ||||
|         LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_SPI1); | ||||
|         LL_APB2_GRP1_ForceReset(LL_APB2_GRP1_PERIPH_SPI1); | ||||
|         bus->current_handle = NULL; | ||||
|     } else if(event == FuriHalSpiBusEventDeinit) { | ||||
|     } else if(event == FuriHalSpiBusEventLock) { | ||||
|     } else if(event == FuriHalSpiBusEventUnlock) { | ||||
|     } else if(event == FuriHalSpiBusEventActivate) { | ||||
|         LL_APB2_GRP1_ReleaseReset(LL_APB2_GRP1_PERIPH_SPI1); | ||||
|     } else if(event == FuriHalSpiBusEventDeactivate) { | ||||
|         LL_APB2_GRP1_ForceReset(LL_APB2_GRP1_PERIPH_SPI1); | ||||
|     } | ||||
| } | ||||
|  | ||||
| FuriHalSpiBus furi_hal_spi_bus_r = { | ||||
|     .spi = SPI1, | ||||
|     .callback = furi_hal_spi_bus_r_event_callback, | ||||
| }; | ||||
|  | ||||
| static void furi_hal_spi_bus_d_event_callback(FuriHalSpiBus* bus, FuriHalSpiBusEvent event) { | ||||
|     if(event == FuriHalSpiBusEventInit) { | ||||
|         LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_SPI2); | ||||
|         LL_APB1_GRP1_ForceReset(LL_APB1_GRP1_PERIPH_SPI2); | ||||
|         bus->current_handle = NULL; | ||||
|     } else if(event == FuriHalSpiBusEventDeinit) { | ||||
|     } else if(event == FuriHalSpiBusEventLock) { | ||||
|     } else if(event == FuriHalSpiBusEventUnlock) { | ||||
|     } else if(event == FuriHalSpiBusEventActivate) { | ||||
|         LL_APB1_GRP1_ReleaseReset(LL_APB1_GRP1_PERIPH_SPI2); | ||||
|     } else if(event == FuriHalSpiBusEventDeactivate) { | ||||
|         LL_APB1_GRP1_ForceReset(LL_APB1_GRP1_PERIPH_SPI2); | ||||
|     } | ||||
| } | ||||
|  | ||||
| FuriHalSpiBus furi_hal_spi_bus_d = { | ||||
|     .spi = SPI2, | ||||
|     .callback = furi_hal_spi_bus_d_event_callback, | ||||
| }; | ||||
|  | ||||
| /* SPI Bus Handles */ | ||||
|  | ||||
| inline static void furi_hal_spi_bus_r_handle_event_callback( | ||||
|     FuriHalSpiBusHandle* handle, | ||||
|     FuriHalSpiBusHandleEvent event, | ||||
|     const LL_SPI_InitTypeDef* preset) { | ||||
|     if(event == FuriHalSpiBusHandleEventInit) { | ||||
|         hal_gpio_write(handle->cs, true); | ||||
|         hal_gpio_init(handle->cs, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); | ||||
|     } else if(event == FuriHalSpiBusHandleEventDeinit) { | ||||
|         hal_gpio_write(handle->cs, true); | ||||
|         hal_gpio_init(handle->cs, GpioModeAnalog, GpioPullNo, GpioSpeedLow); | ||||
|     } else if(event == FuriHalSpiBusHandleEventActivate) { | ||||
|         LL_SPI_Init(handle->bus->spi, (LL_SPI_InitTypeDef*)preset); | ||||
|         LL_SPI_SetRxFIFOThreshold(handle->bus->spi, LL_SPI_RX_FIFO_TH_QUARTER); | ||||
|         LL_SPI_Enable(handle->bus->spi); | ||||
|  | ||||
|         hal_gpio_init_ex( | ||||
|             handle->miso, | ||||
|             GpioModeAltFunctionPushPull, | ||||
|             GpioPullNo, | ||||
|             GpioSpeedVeryHigh, | ||||
|             GpioAltFn5SPI1); | ||||
|         hal_gpio_init_ex( | ||||
|             handle->mosi, | ||||
|             GpioModeAltFunctionPushPull, | ||||
|             GpioPullNo, | ||||
|             GpioSpeedVeryHigh, | ||||
|             GpioAltFn5SPI1); | ||||
|         hal_gpio_init_ex( | ||||
|             handle->sck, | ||||
|             GpioModeAltFunctionPushPull, | ||||
|             GpioPullNo, | ||||
|             GpioSpeedVeryHigh, | ||||
|             GpioAltFn5SPI1); | ||||
|  | ||||
|         hal_gpio_write(handle->cs, false); | ||||
|     } else if(event == FuriHalSpiBusHandleEventDeactivate) { | ||||
|         hal_gpio_write(handle->cs, true); | ||||
|  | ||||
|         hal_gpio_init(handle->miso, GpioModeAnalog, GpioPullNo, GpioSpeedLow); | ||||
|         hal_gpio_init(handle->mosi, GpioModeAnalog, GpioPullNo, GpioSpeedLow); | ||||
|         hal_gpio_init(handle->sck, GpioModeAnalog, GpioPullNo, GpioSpeedLow); | ||||
|  | ||||
|         LL_SPI_Disable(handle->bus->spi); | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void furi_hal_spi_bus_handle_subghz_event_callback( | ||||
|     FuriHalSpiBusHandle* handle, | ||||
|     FuriHalSpiBusHandleEvent event) { | ||||
|     furi_hal_spi_bus_r_handle_event_callback(handle, event, &furi_hal_spi_preset_1edge_low_8m); | ||||
| } | ||||
|  | ||||
| FuriHalSpiBusHandle furi_hal_spi_bus_handle_subghz = { | ||||
|     .bus = &furi_hal_spi_bus_r, | ||||
|     .callback = furi_hal_spi_bus_handle_subghz_event_callback, | ||||
|     .miso = &gpio_spi_r_miso, | ||||
|     .mosi = &gpio_spi_r_mosi, | ||||
|     .clk = &gpio_spi_r_sck, | ||||
|     .sck = &gpio_spi_r_sck, | ||||
|     .cs = &gpio_subghz_cs, | ||||
| }; | ||||
|  | ||||
| const FuriHalSpiBus spi_d = { | ||||
|     .spi = SPI_D, | ||||
| static void furi_hal_spi_bus_handle_nfc_event_callback( | ||||
|     FuriHalSpiBusHandle* handle, | ||||
|     FuriHalSpiBusHandleEvent event) { | ||||
|     furi_hal_spi_bus_r_handle_event_callback(handle, event, &furi_hal_spi_preset_2edge_low_8m); | ||||
| } | ||||
|  | ||||
| FuriHalSpiBusHandle furi_hal_spi_bus_handle_nfc = { | ||||
|     .bus = &furi_hal_spi_bus_r, | ||||
|     .callback = furi_hal_spi_bus_handle_nfc_event_callback, | ||||
|     .miso = &gpio_spi_r_miso, | ||||
|     .mosi = &gpio_spi_r_mosi, | ||||
|     .sck = &gpio_spi_r_sck, | ||||
|     .cs = &gpio_nfc_cs, | ||||
| }; | ||||
|  | ||||
| static void furi_hal_spi_bus_handle_external_event_callback( | ||||
|     FuriHalSpiBusHandle* handle, | ||||
|     FuriHalSpiBusHandleEvent event) { | ||||
|     furi_hal_spi_bus_r_handle_event_callback(handle, event, &furi_hal_spi_preset_1edge_low_2m); | ||||
| } | ||||
|  | ||||
| FuriHalSpiBusHandle furi_hal_spi_bus_handle_external = { | ||||
|     .bus = &furi_hal_spi_bus_r, | ||||
|     .callback = furi_hal_spi_bus_handle_external_event_callback, | ||||
|     .miso = &gpio_ext_pa6, | ||||
|     .mosi = &gpio_ext_pa7, | ||||
|     .sck = &gpio_ext_pb3, | ||||
|     .cs = &gpio_ext_pa4, | ||||
| }; | ||||
|  | ||||
| inline static void furi_hal_spi_bus_d_handle_event_callback( | ||||
|     FuriHalSpiBusHandle* handle, | ||||
|     FuriHalSpiBusHandleEvent event, | ||||
|     const LL_SPI_InitTypeDef* preset) { | ||||
|     if(event == FuriHalSpiBusHandleEventInit) { | ||||
|         hal_gpio_write(handle->cs, true); | ||||
|         hal_gpio_init(handle->cs, GpioModeOutputPushPull, GpioPullUp, GpioSpeedVeryHigh); | ||||
|  | ||||
|         hal_gpio_init_ex( | ||||
|             handle->miso, | ||||
|             GpioModeAltFunctionPushPull, | ||||
|             GpioPullNo, | ||||
|             GpioSpeedVeryHigh, | ||||
|             GpioAltFn5SPI2); | ||||
|         hal_gpio_init_ex( | ||||
|             handle->mosi, | ||||
|             GpioModeAltFunctionPushPull, | ||||
|             GpioPullNo, | ||||
|             GpioSpeedVeryHigh, | ||||
|             GpioAltFn5SPI2); | ||||
|         hal_gpio_init_ex( | ||||
|             handle->sck, | ||||
|             GpioModeAltFunctionPushPull, | ||||
|             GpioPullNo, | ||||
|             GpioSpeedVeryHigh, | ||||
|             GpioAltFn5SPI2); | ||||
|  | ||||
|     } else if(event == FuriHalSpiBusHandleEventDeinit) { | ||||
|         hal_gpio_write(handle->cs, true); | ||||
|         hal_gpio_init(handle->cs, GpioModeAnalog, GpioPullUp, GpioSpeedLow); | ||||
|     } else if(event == FuriHalSpiBusHandleEventActivate) { | ||||
|         LL_SPI_Init(handle->bus->spi, (LL_SPI_InitTypeDef*)preset); | ||||
|         LL_SPI_SetRxFIFOThreshold(handle->bus->spi, LL_SPI_RX_FIFO_TH_QUARTER); | ||||
|         LL_SPI_Enable(handle->bus->spi); | ||||
|         hal_gpio_write(handle->cs, false); | ||||
|     } else if(event == FuriHalSpiBusHandleEventDeactivate) { | ||||
|         hal_gpio_write(handle->cs, true); | ||||
|         LL_SPI_Disable(handle->bus->spi); | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void furi_hal_spi_bus_handle_display_event_callback( | ||||
|     FuriHalSpiBusHandle* handle, | ||||
|     FuriHalSpiBusHandleEvent event) { | ||||
|     furi_hal_spi_bus_d_handle_event_callback(handle, event, &furi_hal_spi_preset_1edge_low_4m); | ||||
| } | ||||
|  | ||||
| FuriHalSpiBusHandle furi_hal_spi_bus_handle_display = { | ||||
|     .bus = &furi_hal_spi_bus_d, | ||||
|     .callback = furi_hal_spi_bus_handle_display_event_callback, | ||||
|     .miso = &gpio_spi_d_miso, | ||||
|     .mosi = &gpio_spi_d_mosi, | ||||
|     .clk = &gpio_spi_d_sck, | ||||
|     .sck = &gpio_spi_d_sck, | ||||
|     .cs = &gpio_display_cs, | ||||
| }; | ||||
|  | ||||
| const FuriHalSpiDevice furi_hal_spi_devices[FuriHalSpiDeviceIdMax] = { | ||||
|     { | ||||
|         .bus = &spi_r, | ||||
|         .config = &furi_hal_spi_config_subghz, | ||||
|         .chip_select = &gpio_subghz_cs, | ||||
|     }, | ||||
|     { | ||||
|         .bus = &spi_d, | ||||
|         .config = &furi_hal_spi_config_display, | ||||
|         .chip_select = &gpio_display_cs, | ||||
|     }, | ||||
|     { | ||||
|         .bus = &spi_d, | ||||
|         .config = &furi_hal_spi_config_sd_fast, | ||||
|         .chip_select = &gpio_sdcard_cs, | ||||
|     }, | ||||
|     { | ||||
|         .bus = &spi_d, | ||||
|         .config = &furi_hal_spi_config_sd_slow, | ||||
|         .chip_select = &gpio_sdcard_cs, | ||||
|     }, | ||||
|     {.bus = &spi_r, .config = &furi_hal_spi_config_nfc, .chip_select = &gpio_nfc_cs}, | ||||
| static void furi_hal_spi_bus_handle_sd_fast_event_callback( | ||||
|     FuriHalSpiBusHandle* handle, | ||||
|     FuriHalSpiBusHandleEvent event) { | ||||
|     furi_hal_spi_bus_d_handle_event_callback(handle, event, &furi_hal_spi_preset_1edge_low_16m); | ||||
| } | ||||
|  | ||||
| FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_fast = { | ||||
|     .bus = &furi_hal_spi_bus_d, | ||||
|     .callback = furi_hal_spi_bus_handle_sd_fast_event_callback, | ||||
|     .miso = &gpio_spi_d_miso, | ||||
|     .mosi = &gpio_spi_d_mosi, | ||||
|     .sck = &gpio_spi_d_sck, | ||||
|     .cs = &gpio_sdcard_cs, | ||||
| }; | ||||
|  | ||||
| static void furi_hal_spi_bus_handle_sd_slow_event_callback( | ||||
|     FuriHalSpiBusHandle* handle, | ||||
|     FuriHalSpiBusHandleEvent event) { | ||||
|     furi_hal_spi_bus_d_handle_event_callback(handle, event, &furi_hal_spi_preset_1edge_low_2m); | ||||
| } | ||||
|  | ||||
| FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_slow = { | ||||
|     .bus = &furi_hal_spi_bus_d, | ||||
|     .callback = furi_hal_spi_bus_handle_sd_slow_event_callback, | ||||
|     .miso = &gpio_spi_d_miso, | ||||
|     .mosi = &gpio_spi_d_mosi, | ||||
|     .sck = &gpio_spi_d_sck, | ||||
|     .cs = &gpio_sdcard_cs, | ||||
| }; | ||||
|   | ||||
| @@ -1,60 +1,60 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <furi-hal-gpio.h> | ||||
| #include <stm32wbxx_ll_spi.h> | ||||
| #include <furi-hal-spi-types.h> | ||||
|  | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
|  | ||||
| extern const LL_SPI_InitTypeDef furi_hal_spi_config_nfc; | ||||
| extern const LL_SPI_InitTypeDef furi_hal_spi_config_subghz; | ||||
| extern const LL_SPI_InitTypeDef furi_hal_spi_config_display; | ||||
| extern const LL_SPI_InitTypeDef furi_hal_spi_config_sd_fast; | ||||
| extern const LL_SPI_InitTypeDef furi_hal_spi_config_sd_slow; | ||||
| /** Preset for ST25R916 */ | ||||
| extern const LL_SPI_InitTypeDef furi_hal_spi_preset_2edge_low_8m; | ||||
|  | ||||
| /** FURI HAL SPI BUS handler | ||||
|  * Structure content may change at some point | ||||
| /** Preset for CC1101 */ | ||||
| extern const LL_SPI_InitTypeDef furi_hal_spi_preset_1edge_low_8m; | ||||
|  | ||||
| /** Preset for ST7567 (Display) */ | ||||
| extern const LL_SPI_InitTypeDef furi_hal_spi_preset_1edge_low_4m; | ||||
|  | ||||
| /** Preset for SdCard in fast mode */ | ||||
| extern const LL_SPI_InitTypeDef furi_hal_spi_preset_1edge_low_16m; | ||||
|  | ||||
| /** Preset for SdCard in slow mode */ | ||||
| extern const LL_SPI_InitTypeDef furi_hal_spi_preset_1edge_low_2m; | ||||
|  | ||||
| /** Furi Hal Spi Bus R (Radio: CC1101, Nfc, External)*/ | ||||
| extern FuriHalSpiBus furi_hal_spi_bus_r; | ||||
|  | ||||
| /** Furi Hal Spi Bus D (Display, SdCard) */ | ||||
| extern FuriHalSpiBus furi_hal_spi_bus_d; | ||||
|  | ||||
| /** CC1101 on `furi_hal_spi_bus_r` */ | ||||
| extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_subghz; | ||||
|  | ||||
| /** ST25R3916 on `furi_hal_spi_bus_r` */ | ||||
| extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_nfc; | ||||
|  | ||||
| /** External on `furi_hal_spi_bus_r` | ||||
|  * Preset: `furi_hal_spi_preset_1edge_low_2m` | ||||
|  *  | ||||
|  * miso: pa6 | ||||
|  * mosi: pa7 | ||||
|  * sck: pb3 | ||||
|  * cs:  pa4 (software controlled) | ||||
|  *  | ||||
|  * @warning not initialized by default, call `furi_hal_spi_bus_handle_init` to initialize | ||||
|  * Bus pins are floating on inactive state, CS high after initialization | ||||
|  *  | ||||
|  */ | ||||
| typedef struct { | ||||
|     const SPI_TypeDef* spi; | ||||
|     const GpioPin* miso; | ||||
|     const GpioPin* mosi; | ||||
|     const GpioPin* clk; | ||||
| } FuriHalSpiBus; | ||||
| extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_external; | ||||
|  | ||||
| /** FURI HAL SPI Device handler | ||||
|  * Structure content may change at some point | ||||
|  */ | ||||
| typedef struct { | ||||
|     const FuriHalSpiBus* bus; | ||||
|     const LL_SPI_InitTypeDef* config; | ||||
|     const GpioPin* chip_select; | ||||
| } FuriHalSpiDevice; | ||||
| /** ST7567(Display) on `furi_hal_spi_bus_d` */ | ||||
| extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_display; | ||||
|  | ||||
| /** FURI HAL SPI Standard Device IDs */ | ||||
| typedef enum { | ||||
|     FuriHalSpiDeviceIdSubGhz, /** SubGhz: CC1101, non-standard SPI usage */ | ||||
|     FuriHalSpiDeviceIdDisplay, /** Display: ERC12864, only have MOSI */ | ||||
|     FuriHalSpiDeviceIdSdCardFast, /** SDCARD: fast mode, after initialization */ | ||||
|     FuriHalSpiDeviceIdSdCardSlow, /** SDCARD: slow mode, before initialization */ | ||||
|     FuriHalSpiDeviceIdNfc, /** NFC: ST25R3916, pretty standard, but RFAL makes it complex */ | ||||
| /** SdCard in fast mode on `furi_hal_spi_bus_d` */ | ||||
| extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_fast; | ||||
|  | ||||
|     FuriHalSpiDeviceIdMax, /** Service Value, do not use */ | ||||
| } FuriHalSpiDeviceId; | ||||
|  | ||||
| /** Furi Hal Spi Bus R | ||||
|  * CC1101, Nfc | ||||
|  */ | ||||
| extern const FuriHalSpiBus spi_r; | ||||
|  | ||||
| /** Furi Hal Spi Bus D | ||||
|  * Display, SdCard | ||||
|  */ | ||||
| extern const FuriHalSpiBus spi_d; | ||||
|  | ||||
| /** Furi Hal Spi devices */ | ||||
| extern const FuriHalSpiDevice furi_hal_spi_devices[FuriHalSpiDeviceIdMax]; | ||||
| /** SdCard in slow mode on `furi_hal_spi_bus_d` */ | ||||
| extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_slow; | ||||
|  | ||||
| #ifdef __cplusplus | ||||
| } | ||||
|   | ||||
							
								
								
									
										64
									
								
								bootloader/targets/f6/furi-hal/furi-hal-spi-types.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								bootloader/targets/f6/furi-hal/furi-hal-spi-types.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <stdint.h> | ||||
| #include <stddef.h> | ||||
|  | ||||
| #include <furi-hal-gpio.h> | ||||
|  | ||||
| #include <stm32wbxx_ll_spi.h> | ||||
| #include <stm32wbxx_ll_rcc.h> | ||||
| #include <stm32wbxx_ll_bus.h> | ||||
|  | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
|  | ||||
| typedef struct FuriHalSpiBus FuriHalSpiBus; | ||||
| typedef struct FuriHalSpiBusHandle FuriHalSpiBusHandle; | ||||
|  | ||||
| /** FuriHal spi bus states */ | ||||
| typedef enum { | ||||
|     FuriHalSpiBusEventInit, /**< Bus initialization event, called on system start */ | ||||
|     FuriHalSpiBusEventDeinit, /**< Bus deinitialization event, called on system stop */ | ||||
|     FuriHalSpiBusEventLock, /**< Bus lock event, called before activation */ | ||||
|     FuriHalSpiBusEventUnlock, /**< Bus unlock event, called after deactivation */ | ||||
|     FuriHalSpiBusEventActivate, /**< Bus activation event, called before handle activation */ | ||||
|     FuriHalSpiBusEventDeactivate, /**< Bus deactivation event, called after handle deactivation  */ | ||||
| } FuriHalSpiBusEvent; | ||||
|  | ||||
| /** FuriHal spi bus event callback */ | ||||
| typedef void (*FuriHalSpiBusEventCallback)(FuriHalSpiBus* bus, FuriHalSpiBusEvent event); | ||||
|  | ||||
| /** FuriHal spi bus */ | ||||
| struct FuriHalSpiBus { | ||||
|     SPI_TypeDef* spi; | ||||
|     FuriHalSpiBusEventCallback callback; | ||||
|     FuriHalSpiBusHandle* current_handle; | ||||
| }; | ||||
|  | ||||
| /** FuriHal spi handle states */ | ||||
| typedef enum { | ||||
|     FuriHalSpiBusHandleEventInit, /**< Handle init, called on system start, initialize gpio for idle state */ | ||||
|     FuriHalSpiBusHandleEventDeinit, /**< Handle deinit, called on system stop, deinitialize gpio for default state */ | ||||
|     FuriHalSpiBusHandleEventActivate, /**< Handle activate: connect gpio and apply bus config */ | ||||
|     FuriHalSpiBusHandleEventDeactivate, /**< Handle deactivate: disconnect gpio and reset bus config */ | ||||
| } FuriHalSpiBusHandleEvent; | ||||
|  | ||||
| /** FuriHal spi handle event callback */ | ||||
| typedef void (*FuriHalSpiBusHandleEventCallback)( | ||||
|     FuriHalSpiBusHandle* handle, | ||||
|     FuriHalSpiBusHandleEvent event); | ||||
|  | ||||
| /** FuriHal spi handle */ | ||||
| struct FuriHalSpiBusHandle { | ||||
|     FuriHalSpiBus* bus; | ||||
|     FuriHalSpiBusHandleEventCallback callback; | ||||
|     const GpioPin* miso; | ||||
|     const GpioPin* mosi; | ||||
|     const GpioPin* sck; | ||||
|     const GpioPin* cs; | ||||
| }; | ||||
|  | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
| @@ -2,239 +2,150 @@ | ||||
| #include "furi-hal-resources.h" | ||||
|  | ||||
| #include <stdbool.h> | ||||
| #include <string.h> | ||||
| #include <assert.h> | ||||
|  | ||||
| #include <stm32wbxx_ll_spi.h> | ||||
| #include <stm32wbxx_ll_utils.h> | ||||
| #include <stm32wbxx_ll_cortex.h> | ||||
|  | ||||
| extern void Enable_SPI(SPI_TypeDef* spi); | ||||
|  | ||||
| void furi_hal_spi_init() { | ||||
|     for(size_t i = 0; i < FuriHalSpiDeviceIdMax; ++i) { | ||||
|         hal_gpio_write(furi_hal_spi_devices[i].chip_select, true); | ||||
|         hal_gpio_init( | ||||
|             furi_hal_spi_devices[i].chip_select, | ||||
|             GpioModeOutputPushPull, | ||||
|             GpioPullNo, | ||||
|             GpioSpeedVeryHigh); | ||||
|     } | ||||
|     furi_hal_spi_bus_init(&furi_hal_spi_bus_r); | ||||
|     furi_hal_spi_bus_init(&furi_hal_spi_bus_d); | ||||
|  | ||||
|     hal_gpio_init_ex( | ||||
|         &gpio_spi_r_miso, | ||||
|         GpioModeAltFunctionPushPull, | ||||
|         GpioPullNo, | ||||
|         GpioSpeedVeryHigh, | ||||
|         GpioAltFn5SPI1); | ||||
|     hal_gpio_init_ex( | ||||
|         &gpio_spi_r_mosi, | ||||
|         GpioModeAltFunctionPushPull, | ||||
|         GpioPullNo, | ||||
|         GpioSpeedVeryHigh, | ||||
|         GpioAltFn5SPI1); | ||||
|     hal_gpio_init_ex( | ||||
|         &gpio_spi_r_sck, | ||||
|         GpioModeAltFunctionPushPull, | ||||
|         GpioPullNo, | ||||
|         GpioSpeedVeryHigh, | ||||
|         GpioAltFn5SPI1); | ||||
|  | ||||
|     hal_gpio_init_ex( | ||||
|         &gpio_spi_d_miso, | ||||
|         GpioModeAltFunctionPushPull, | ||||
|         GpioPullUp, | ||||
|         GpioSpeedVeryHigh, | ||||
|         GpioAltFn5SPI2); | ||||
|     hal_gpio_init_ex( | ||||
|         &gpio_spi_d_mosi, | ||||
|         GpioModeAltFunctionPushPull, | ||||
|         GpioPullUp, | ||||
|         GpioSpeedVeryHigh, | ||||
|         GpioAltFn5SPI2); | ||||
|     hal_gpio_init_ex( | ||||
|         &gpio_spi_d_sck, | ||||
|         GpioModeAltFunctionPushPull, | ||||
|         GpioPullUp, | ||||
|         GpioSpeedVeryHigh, | ||||
|         GpioAltFn5SPI2); | ||||
|     furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_subghz); | ||||
|     furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_nfc); | ||||
|     furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_display); | ||||
|     furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_sd_fast); | ||||
|     furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_sd_slow); | ||||
| } | ||||
|  | ||||
| void furi_hal_spi_bus_lock(const FuriHalSpiBus* bus) { | ||||
| void furi_hal_spi_bus_init(FuriHalSpiBus* bus) { | ||||
|     assert(bus); | ||||
|     bus->callback(bus, FuriHalSpiBusEventInit); | ||||
| } | ||||
|  | ||||
| void furi_hal_spi_bus_unlock(const FuriHalSpiBus* bus) { | ||||
| void furi_hal_spi_bus_deinit(FuriHalSpiBus* bus) { | ||||
|     assert(bus); | ||||
|     bus->callback(bus, FuriHalSpiBusEventDeinit); | ||||
| } | ||||
|  | ||||
| void furi_hal_spi_bus_configure(const FuriHalSpiBus* bus, const LL_SPI_InitTypeDef* config) { | ||||
|     assert(bus); | ||||
|     LL_SPI_DeInit((SPI_TypeDef*)bus->spi); | ||||
|     LL_SPI_Init((SPI_TypeDef*)bus->spi, (LL_SPI_InitTypeDef*)config); | ||||
|     LL_SPI_SetRxFIFOThreshold((SPI_TypeDef*)bus->spi, LL_SPI_RX_FIFO_TH_QUARTER); | ||||
|     LL_SPI_Enable((SPI_TypeDef*)bus->spi); | ||||
| void furi_hal_spi_bus_handle_init(FuriHalSpiBusHandle* handle) { | ||||
|     assert(handle); | ||||
|     handle->callback(handle, FuriHalSpiBusHandleEventInit); | ||||
| } | ||||
|  | ||||
| void furi_hal_spi_bus_end_txrx(const FuriHalSpiBus* bus, uint32_t timeout) { | ||||
|     while(LL_SPI_GetTxFIFOLevel((SPI_TypeDef*)bus->spi) != LL_SPI_TX_FIFO_EMPTY) | ||||
| void furi_hal_spi_bus_handle_deinit(FuriHalSpiBusHandle* handle) { | ||||
|     assert(handle); | ||||
|     handle->callback(handle, FuriHalSpiBusHandleEventDeinit); | ||||
| } | ||||
|  | ||||
| void furi_hal_spi_acquire(FuriHalSpiBusHandle* handle) { | ||||
|     assert(handle); | ||||
|  | ||||
|     handle->bus->callback(handle->bus, FuriHalSpiBusEventLock); | ||||
|     handle->bus->callback(handle->bus, FuriHalSpiBusEventActivate); | ||||
|  | ||||
|     assert(handle->bus->current_handle == NULL); | ||||
|  | ||||
|     handle->bus->current_handle = handle; | ||||
|     handle->callback(handle, FuriHalSpiBusHandleEventActivate); | ||||
| } | ||||
|  | ||||
| void furi_hal_spi_release(FuriHalSpiBusHandle* handle) { | ||||
|     assert(handle); | ||||
|     assert(handle->bus->current_handle == handle); | ||||
|  | ||||
|     // Handle event and unset handle | ||||
|     handle->callback(handle, FuriHalSpiBusHandleEventDeactivate); | ||||
|     handle->bus->current_handle = NULL; | ||||
|  | ||||
|     // Bus events | ||||
|     handle->bus->callback(handle->bus, FuriHalSpiBusEventDeactivate); | ||||
|     handle->bus->callback(handle->bus, FuriHalSpiBusEventUnlock); | ||||
| } | ||||
|  | ||||
| static void furi_hal_spi_bus_end_txrx(FuriHalSpiBusHandle* handle, uint32_t timeout) { | ||||
|     while(LL_SPI_GetTxFIFOLevel(handle->bus->spi) != LL_SPI_TX_FIFO_EMPTY) | ||||
|         ; | ||||
|     while(LL_SPI_IsActiveFlag_BSY((SPI_TypeDef*)bus->spi)) | ||||
|     while(LL_SPI_IsActiveFlag_BSY(handle->bus->spi)) | ||||
|         ; | ||||
|     while(LL_SPI_GetRxFIFOLevel((SPI_TypeDef*)bus->spi) != LL_SPI_RX_FIFO_EMPTY) { | ||||
|         LL_SPI_ReceiveData8((SPI_TypeDef*)bus->spi); | ||||
|     while(LL_SPI_GetRxFIFOLevel(handle->bus->spi) != LL_SPI_RX_FIFO_EMPTY) { | ||||
|         LL_SPI_ReceiveData8(handle->bus->spi); | ||||
|     } | ||||
| } | ||||
|  | ||||
| bool furi_hal_spi_bus_rx(const FuriHalSpiBus* bus, uint8_t* buffer, size_t size, uint32_t timeout) { | ||||
|     assert(bus); | ||||
| bool furi_hal_spi_bus_rx( | ||||
|     FuriHalSpiBusHandle* handle, | ||||
|     uint8_t* buffer, | ||||
|     size_t size, | ||||
|     uint32_t timeout) { | ||||
|     assert(handle); | ||||
|     assert(handle->bus->current_handle == handle); | ||||
|     assert(buffer); | ||||
|     assert(size > 0); | ||||
|  | ||||
|     return furi_hal_spi_bus_trx(bus, buffer, buffer, size, timeout); | ||||
|     return furi_hal_spi_bus_trx(handle, buffer, buffer, size, timeout); | ||||
| } | ||||
|  | ||||
| bool furi_hal_spi_bus_tx(const FuriHalSpiBus* bus, uint8_t* buffer, size_t size, uint32_t timeout) { | ||||
|     assert(bus); | ||||
| bool furi_hal_spi_bus_tx( | ||||
|     FuriHalSpiBusHandle* handle, | ||||
|     uint8_t* buffer, | ||||
|     size_t size, | ||||
|     uint32_t timeout) { | ||||
|     assert(handle); | ||||
|     assert(handle->bus->current_handle == handle); | ||||
|     assert(buffer); | ||||
|     assert(size > 0); | ||||
|     bool ret = true; | ||||
|  | ||||
|     while(size > 0) { | ||||
|         if(LL_SPI_IsActiveFlag_TXE((SPI_TypeDef*)bus->spi)) { | ||||
|             LL_SPI_TransmitData8((SPI_TypeDef*)bus->spi, *buffer); | ||||
|         if(LL_SPI_IsActiveFlag_TXE(handle->bus->spi)) { | ||||
|             LL_SPI_TransmitData8(handle->bus->spi, *buffer); | ||||
|             buffer++; | ||||
|             size--; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     furi_hal_spi_bus_end_txrx(bus, timeout); | ||||
|     LL_SPI_ClearFlag_OVR((SPI_TypeDef*)bus->spi); | ||||
|     furi_hal_spi_bus_end_txrx(handle, timeout); | ||||
|     LL_SPI_ClearFlag_OVR(handle->bus->spi); | ||||
|  | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
| bool furi_hal_spi_bus_trx( | ||||
|     const FuriHalSpiBus* bus, | ||||
|     FuriHalSpiBusHandle* handle, | ||||
|     uint8_t* tx_buffer, | ||||
|     uint8_t* rx_buffer, | ||||
|     size_t size, | ||||
|     uint32_t timeout) { | ||||
|     assert(bus); | ||||
|     assert(handle); | ||||
|     assert(handle->bus->current_handle == handle); | ||||
|     assert(tx_buffer); | ||||
|     assert(rx_buffer); | ||||
|     assert(size > 0); | ||||
|  | ||||
|     bool ret = true; | ||||
|     size_t tx_size = size; | ||||
|     bool tx_allowed = true; | ||||
|  | ||||
|     while(size > 0) { | ||||
|         if(tx_size > 0 && LL_SPI_IsActiveFlag_TXE((SPI_TypeDef*)bus->spi) && tx_allowed) { | ||||
|             LL_SPI_TransmitData8((SPI_TypeDef*)bus->spi, *tx_buffer); | ||||
|         if(tx_size > 0 && LL_SPI_IsActiveFlag_TXE(handle->bus->spi) && tx_allowed) { | ||||
|             LL_SPI_TransmitData8(handle->bus->spi, *tx_buffer); | ||||
|             tx_buffer++; | ||||
|             tx_size--; | ||||
|             tx_allowed = false; | ||||
|         } | ||||
|  | ||||
|         if(LL_SPI_IsActiveFlag_RXNE((SPI_TypeDef*)bus->spi)) { | ||||
|             *rx_buffer = LL_SPI_ReceiveData8((SPI_TypeDef*)bus->spi); | ||||
|         if(LL_SPI_IsActiveFlag_RXNE(handle->bus->spi)) { | ||||
|             *rx_buffer = LL_SPI_ReceiveData8(handle->bus->spi); | ||||
|             rx_buffer++; | ||||
|             size--; | ||||
|             tx_allowed = true; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     furi_hal_spi_bus_end_txrx(bus, timeout); | ||||
|  | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
| void furi_hal_spi_device_configure(const FuriHalSpiDevice* device) { | ||||
|     assert(device); | ||||
|     assert(device->config); | ||||
|  | ||||
|     furi_hal_spi_bus_configure(device->bus, device->config); | ||||
| } | ||||
|  | ||||
| const FuriHalSpiDevice* furi_hal_spi_device_get(FuriHalSpiDeviceId device_id) { | ||||
|     assert(device_id < FuriHalSpiDeviceIdMax); | ||||
|  | ||||
|     const FuriHalSpiDevice* device = &furi_hal_spi_devices[device_id]; | ||||
|     assert(device); | ||||
|  | ||||
|     furi_hal_spi_bus_lock(device->bus); | ||||
|     furi_hal_spi_device_configure(device); | ||||
|  | ||||
|     return device; | ||||
| } | ||||
|  | ||||
| void furi_hal_spi_device_return(const FuriHalSpiDevice* device) { | ||||
|     furi_hal_spi_bus_unlock(device->bus); | ||||
| } | ||||
|  | ||||
| bool furi_hal_spi_device_rx( | ||||
|     const FuriHalSpiDevice* device, | ||||
|     uint8_t* buffer, | ||||
|     size_t size, | ||||
|     uint32_t timeout) { | ||||
|     assert(device); | ||||
|     assert(buffer); | ||||
|     assert(size > 0); | ||||
|  | ||||
|     if(device->chip_select) { | ||||
|         hal_gpio_write(device->chip_select, false); | ||||
|     } | ||||
|  | ||||
|     bool ret = furi_hal_spi_bus_rx(device->bus, buffer, size, timeout); | ||||
|  | ||||
|     if(device->chip_select) { | ||||
|         hal_gpio_write(device->chip_select, true); | ||||
|     } | ||||
|  | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
| bool furi_hal_spi_device_tx( | ||||
|     const FuriHalSpiDevice* device, | ||||
|     uint8_t* buffer, | ||||
|     size_t size, | ||||
|     uint32_t timeout) { | ||||
|     assert(device); | ||||
|     assert(buffer); | ||||
|     assert(size > 0); | ||||
|  | ||||
|     if(device->chip_select) { | ||||
|         hal_gpio_write(device->chip_select, false); | ||||
|     } | ||||
|  | ||||
|     bool ret = furi_hal_spi_bus_tx(device->bus, buffer, size, timeout); | ||||
|  | ||||
|     if(device->chip_select) { | ||||
|         hal_gpio_write(device->chip_select, true); | ||||
|     } | ||||
|  | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
| bool furi_hal_spi_device_trx( | ||||
|     const FuriHalSpiDevice* device, | ||||
|     uint8_t* tx_buffer, | ||||
|     uint8_t* rx_buffer, | ||||
|     size_t size, | ||||
|     uint32_t timeout) { | ||||
|     assert(device); | ||||
|     assert(tx_buffer); | ||||
|     assert(rx_buffer); | ||||
|     assert(size > 0); | ||||
|  | ||||
|     if(device->chip_select) { | ||||
|         hal_gpio_write(device->chip_select, false); | ||||
|     } | ||||
|  | ||||
|     bool ret = furi_hal_spi_bus_trx(device->bus, tx_buffer, rx_buffer, size, timeout); | ||||
|  | ||||
|     if(device->chip_select) { | ||||
|         hal_gpio_write(device->chip_select, true); | ||||
|     } | ||||
|     furi_hal_spi_bus_end_txrx(handle, timeout); | ||||
|  | ||||
|     return ret; | ||||
| } | ||||
|   | ||||
| @@ -1,129 +0,0 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "main.h" | ||||
|  | ||||
| #include "furi-hal-spi-config.h" | ||||
| #include <furi-hal-gpio.h> | ||||
|  | ||||
| #include <stdbool.h> | ||||
| #include <stddef.h> | ||||
|  | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
|  | ||||
| /** | ||||
|  * Init SPI API | ||||
|  */ | ||||
| void furi_hal_spi_init(); | ||||
|  | ||||
| /* Bus Level API */ | ||||
|  | ||||
| /** Lock SPI bus | ||||
|  * Takes bus mutex, if used | ||||
|  */ | ||||
| void furi_hal_spi_bus_lock(const FuriHalSpiBus* bus); | ||||
|  | ||||
| /** Unlock SPI bus | ||||
|  * Releases BUS mutex, if used | ||||
|  */ | ||||
| void furi_hal_spi_bus_unlock(const FuriHalSpiBus* bus); | ||||
|  | ||||
| /** | ||||
|  * Configure SPI bus | ||||
|  * @param bus - spi bus handler | ||||
|  * @param config - spi configuration structure | ||||
|  */ | ||||
| void furi_hal_spi_bus_configure(const FuriHalSpiBus* bus, const LL_SPI_InitTypeDef* config); | ||||
|  | ||||
| /** SPI Receive | ||||
|  * @param bus - spi bus handler | ||||
|  * @param buffer - receive buffer | ||||
|  * @param size - transaction size | ||||
|  * @param timeout - bus operation timeout in ms | ||||
|  */ | ||||
| bool furi_hal_spi_bus_rx(const FuriHalSpiBus* bus, uint8_t* buffer, size_t size, uint32_t timeout); | ||||
|  | ||||
| /** SPI Transmit | ||||
|  * @param bus - spi bus handler | ||||
|  * @param buffer - transmit buffer | ||||
|  * @param size - transaction size | ||||
|  * @param timeout - bus operation timeout in ms | ||||
|  */ | ||||
| bool furi_hal_spi_bus_tx(const FuriHalSpiBus* bus, uint8_t* buffer, size_t size, uint32_t timeout); | ||||
|  | ||||
| /** SPI Transmit and Receive | ||||
|  * @param bus - spi bus handlere | ||||
|  * @param tx_buffer - device handle | ||||
|  * @param rx_buffer - device handle | ||||
|  * @param size - transaction size | ||||
|  * @param timeout - bus operation timeout in ms | ||||
|  */ | ||||
| bool furi_hal_spi_bus_trx( | ||||
|     const FuriHalSpiBus* bus, | ||||
|     uint8_t* tx_buffer, | ||||
|     uint8_t* rx_buffer, | ||||
|     size_t size, | ||||
|     uint32_t timeout); | ||||
|  | ||||
| /* Device Level API */ | ||||
|  | ||||
| /** Reconfigure SPI bus for device | ||||
|  * @param device - device description | ||||
|  */ | ||||
| void furi_hal_spi_device_configure(const FuriHalSpiDevice* device); | ||||
|  | ||||
| /** Get Device handle | ||||
|  * And lock access to the corresponding SPI BUS | ||||
|  * @param device_id - device identifier | ||||
|  * @return device handle | ||||
|  */ | ||||
| const FuriHalSpiDevice* furi_hal_spi_device_get(FuriHalSpiDeviceId device_id); | ||||
|  | ||||
| /** Return Device handle | ||||
|  * And unlock access to the corresponding SPI BUS | ||||
|  * @param device - device handle | ||||
|  */ | ||||
| void furi_hal_spi_device_return(const FuriHalSpiDevice* device); | ||||
|  | ||||
| /** SPI Recieve | ||||
|  * @param device - device handle | ||||
|  * @param buffer - receive buffer | ||||
|  * @param size - transaction size | ||||
|  * @param timeout - bus operation timeout in ms | ||||
|  */ | ||||
| bool furi_hal_spi_device_rx( | ||||
|     const FuriHalSpiDevice* device, | ||||
|     uint8_t* buffer, | ||||
|     size_t size, | ||||
|     uint32_t timeout); | ||||
|  | ||||
| /** SPI Transmit | ||||
|  * @param device - device handle | ||||
|  * @param buffer - transmit buffer | ||||
|  * @param size - transaction size | ||||
|  * @param timeout - bus operation timeout in ms | ||||
|  */ | ||||
| bool furi_hal_spi_device_tx( | ||||
|     const FuriHalSpiDevice* device, | ||||
|     uint8_t* buffer, | ||||
|     size_t size, | ||||
|     uint32_t timeout); | ||||
|  | ||||
| /** SPI Transmit and Receive | ||||
|  * @param device - device handle | ||||
|  * @param tx_buffer - device handle | ||||
|  * @param rx_buffer - device handle | ||||
|  * @param size - transaction size | ||||
|  * @param timeout - bus operation timeout in ms | ||||
|  */ | ||||
| bool furi_hal_spi_device_trx( | ||||
|     const FuriHalSpiDevice* device, | ||||
|     uint8_t* tx_buffer, | ||||
|     uint8_t* rx_buffer, | ||||
|     size_t size, | ||||
|     uint32_t timeout); | ||||
|  | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
		Reference in New Issue
	
	Block a user