Allow buffering of small messages (e.g. peripheral info, who-am-i, and hw/fw version) over USB
This commit is contained in:
parent
3688db710a
commit
919523a465
14
ads1298.c
14
ads1298.c
@ -76,10 +76,11 @@ void ads1298_initialize(ads1298_info_t *p_info) {
|
|||||||
spi_config.miso_pin = ADS1298_MISO_PIN;
|
spi_config.miso_pin = ADS1298_MISO_PIN;
|
||||||
spi_config.mosi_pin = ADS1298_MOSI_PIN;
|
spi_config.mosi_pin = ADS1298_MOSI_PIN;
|
||||||
spi_config.sck_pin = ADS1298_SCK_PIN;
|
spi_config.sck_pin = ADS1298_SCK_PIN;
|
||||||
//spi_config.frequency = NRF_DRV_SPI_FREQ_125K;
|
spi_config.frequency = NRF_DRV_SPI_FREQ_250K;
|
||||||
//spi_config.frequency = NRF_DRV_SPI_FREQ_500K;
|
//spi_config.frequency = NRF_DRV_SPI_FREQ_500K;
|
||||||
spi_config.frequency = NRF_DRV_SPI_FREQ_1M;
|
//spi_config.frequency = NRF_DRV_SPI_FREQ_1M;
|
||||||
//spi_config.frequency = NRF_DRV_SPI_FREQ_2M;
|
//spi_config.frequency = NRF_DRV_SPI_FREQ_2M;
|
||||||
|
spi_config.irq_priority = APP_IRQ_PRIORITY_HIGH;
|
||||||
//spi_config.irq_priority = APP_IRQ_PRIORITY_HIGHEST;
|
//spi_config.irq_priority = APP_IRQ_PRIORITY_HIGHEST;
|
||||||
spi_config.mode = NRF_DRV_SPI_MODE_1;
|
spi_config.mode = NRF_DRV_SPI_MODE_1;
|
||||||
spi_config.orc = 0x55;
|
spi_config.orc = 0x55;
|
||||||
@ -487,6 +488,15 @@ __STATIC_INLINE void copy_relevant_data(ads1298_info_t* p_info) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ads1298_get_data_fast(ads1298_info_t* p_info) {
|
||||||
|
spi_xfer_done = false;
|
||||||
|
// necessary?
|
||||||
|
memset(rx_buffer, 0, 27);
|
||||||
|
nrf_drv_spi_transfer(&spi0, rx_buffer, 27, rx_buffer, 27);
|
||||||
|
while (!spi_xfer_done) { __WFE(); }
|
||||||
|
memcpy(&p_info->usb_buffer[p_info->usb_buffer_count], &rx_buffer[3], 8 * 3);
|
||||||
|
p_info->usb_buffer_count += 3 * 8;
|
||||||
|
}
|
||||||
|
|
||||||
// Buffer size depends on version (27 bytes for ADS1298, 21 for ADS1296, and 15 for ADS1294)
|
// Buffer size depends on version (27 bytes for ADS1298, 21 for ADS1296, and 15 for ADS1294)
|
||||||
void ads1298_get_data(ads1298_info_t* p_info) {
|
void ads1298_get_data(ads1298_info_t* p_info) {
|
||||||
|
|||||||
@ -65,7 +65,8 @@
|
|||||||
#define ADS129x_8CH_BITMASK 0x02 // 0x[...]10[010]
|
#define ADS129x_8CH_BITMASK 0x02 // 0x[...]10[010]
|
||||||
|
|
||||||
/** DEFAULT REGISTER VALUES **/
|
/** DEFAULT REGISTER VALUES **/
|
||||||
#define ADS1298_REGDEFAULT_CONFIG1 0xC6 // High-res mode, Multiple readback mode, clk output disabled, LP: 250 SPS
|
//
|
||||||
|
#define ADS1298_REGDEFAULT_CONFIG1 0xC5 // High-res mode, Multiple readback mode, clk output disabled, LP: 250 SPS
|
||||||
#define ADS1298_REGDEFAULT_CONFIG2 0x00 // Test signals
|
#define ADS1298_REGDEFAULT_CONFIG2 0x00 // Test signals
|
||||||
#define ADS1298_REGDEFAULT_CONFIG3 0xCE //
|
#define ADS1298_REGDEFAULT_CONFIG3 0xCE //
|
||||||
#define ADS1298_REGDEFAULT_LOFF 0x00
|
#define ADS1298_REGDEFAULT_LOFF 0x00
|
||||||
@ -112,6 +113,7 @@ typedef struct {
|
|||||||
#if ADS1298_STATS
|
#if ADS1298_STATS
|
||||||
uint32_t drdy_trigger_count;
|
uint32_t drdy_trigger_count;
|
||||||
uint32_t seconds_elapsed;
|
uint32_t seconds_elapsed;
|
||||||
|
uint32_t bytes_sent_usb;
|
||||||
#endif
|
#endif
|
||||||
} ads1298_info_t;
|
} ads1298_info_t;
|
||||||
|
|
||||||
@ -147,6 +149,7 @@ void ads1294_get_data(ads1298_info_t* p_info);
|
|||||||
void ads1296_get_data(ads1298_info_t* p_info);
|
void ads1296_get_data(ads1298_info_t* p_info);
|
||||||
|
|
||||||
void ads1298_get_data(ads1298_info_t* p_info);
|
void ads1298_get_data(ads1298_info_t* p_info);
|
||||||
|
void ads1298_get_data_fast(ads1298_info_t* p_info);
|
||||||
|
|
||||||
uint16_t ads1298_sampling_rate(ads1298_info_t* p_info);
|
uint16_t ads1298_sampling_rate(ads1298_info_t* p_info);
|
||||||
|
|
||||||
|
|||||||
132
main.c
132
main.c
@ -42,7 +42,8 @@ char g_WhoAmI[] = "multimodal_cv_dev0_ecg";
|
|||||||
uint8_t g_HWFW_Ver[] = {1,0,0,1}; // HW Maj, HW Min, FW Maj, FW Min
|
uint8_t g_HWFW_Ver[] = {1,0,0,1}; // HW Maj, HW Min, FW Maj, FW Min
|
||||||
|
|
||||||
static volatile bool run_throughput_test = false;
|
static volatile bool run_throughput_test = false;
|
||||||
static char usb_tx_buffer[NRF_DRV_USBD_EPSIZE];
|
static char usb_send_buffer[NRF_DRV_USBD_EPSIZE * 2]; // * 2 is just for safety margin in case we overrun!
|
||||||
|
static uint8_t usb_send_buffer_offset = 0;
|
||||||
|
|
||||||
#define READ_SIZE 64
|
#define READ_SIZE 64
|
||||||
static char usb_read_buffer[READ_SIZE];
|
static char usb_read_buffer[READ_SIZE];
|
||||||
@ -84,30 +85,55 @@ void reset_counters(void) {
|
|||||||
#if ADS1298
|
#if ADS1298
|
||||||
m_info.usb_buffer_count = ADS1298_PACKET_OFFSET;
|
m_info.usb_buffer_count = ADS1298_PACKET_OFFSET;
|
||||||
reset_buffer_count(m_info.usb_buffer);
|
reset_buffer_count(m_info.usb_buffer);
|
||||||
// #TODO: Clear stats:
|
#if ADS1298_STATS
|
||||||
m_info.drdy_trigger_count = 0;
|
m_info.drdy_trigger_count = 0;
|
||||||
m_info.seconds_elapsed = 0;
|
m_info.seconds_elapsed = 0;
|
||||||
|
m_info.bytes_sent_usb = 0;
|
||||||
|
#endif
|
||||||
// #TODO: elapsed time Milliseconds (need last second timer).
|
// #TODO: elapsed time Milliseconds (need last second timer).
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void usb_transmit_message_ex(uint8_t message_prefix, uint8_t message_part_two, uint8_t* message, uint8_t message_length) {
|
// For sending two-byte prefixed messages
|
||||||
usb_tx_buffer[0] = message_prefix;
|
void usb_send_push_ex(uint8_t message_prefix, uint8_t message_part_two, uint8_t* message, uint8_t message_length) {
|
||||||
usb_tx_buffer[1] = message_part_two;
|
usb_send_buffer[0] = message_prefix;
|
||||||
usb_tx_buffer[2] = message_length + 3;
|
usb_send_buffer[1] = message_part_two;
|
||||||
memcpy(&usb_tx_buffer[3], message, message_length);
|
usb_send_buffer[2] = message_length + 3;
|
||||||
ret_code_t ret = app_usbd_cdc_acm_write(&m_app_cdc_acm, usb_tx_buffer, message_length + 3);
|
memcpy(&usb_send_buffer[3], message, message_length);
|
||||||
|
ret_code_t ret = app_usbd_cdc_acm_write(&m_app_cdc_acm, usb_send_buffer, message_length + 3);
|
||||||
|
|
||||||
NRF_LOG_INFO("[ex]Writing message of length %d, ret: %d", message_length + 3, ret);
|
NRF_LOG_INFO("[ex]Writing message of length %d, ret: %d", message_length + 3, ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
void usb_transmit_message(uint8_t message_prefix, uint8_t* message, uint8_t message_length) {
|
// void usb_transmit_message(uint8_t message_prefix, uint8_t* message, uint8_t message_length) {
|
||||||
usb_tx_buffer[0] = message_prefix;
|
// usb_send_buffer[0] = message_prefix;
|
||||||
usb_tx_buffer[1] = message_length + 2;
|
// usb_send_buffer[1] = message_length + 2;
|
||||||
memcpy(&usb_tx_buffer[2], message, message_length);
|
// memcpy(&usb_send_buffer[2], message, message_length);
|
||||||
ret_code_t ret = app_usbd_cdc_acm_write(&m_app_cdc_acm, usb_tx_buffer, message_length + 2);
|
// ret_code_t ret = app_usbd_cdc_acm_write(&m_app_cdc_acm, usb_send_buffer, message_length + 2);
|
||||||
|
|
||||||
NRF_LOG_INFO("[0]Writing message of length %d, ret: %d", message_length + 2, ret);
|
// NRF_LOG_INFO("[0]Writing message of length %d, ret: %d", message_length + 2, ret);
|
||||||
|
// }
|
||||||
|
|
||||||
|
void usb_send_push(void) {
|
||||||
|
ret_code_t ret = app_usbd_cdc_acm_write(&m_app_cdc_acm, usb_send_buffer, usb_send_buffer_offset);
|
||||||
|
|
||||||
|
NRF_LOG_INFO("[1]Writing message of length %d, ret: %d", usb_send_buffer_offset, ret);
|
||||||
|
|
||||||
|
usb_send_buffer_offset = 0; // reset.
|
||||||
|
}
|
||||||
|
|
||||||
|
// For mushing together small messages into a 64-byte packet:
|
||||||
|
void usb_send_append_message(uint8_t message_prefix, uint8_t* message, uint8_t message_length) {
|
||||||
|
usb_send_buffer[usb_send_buffer_offset] = message_prefix;
|
||||||
|
usb_send_buffer[usb_send_buffer_offset + 1] = message_length + 2;
|
||||||
|
memcpy(&usb_send_buffer[usb_send_buffer_offset + 2], message, message_length);
|
||||||
|
|
||||||
|
usb_send_buffer_offset += (2 + message_length);
|
||||||
|
|
||||||
|
if (usb_send_buffer_offset >= 64) { // end of buffer
|
||||||
|
NRF_LOG_INFO("[WARNING] exceeded limit of usb_send_buffer\n");
|
||||||
|
usb_send_buffer_offset = 64;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// length is always `READ_SIZE`
|
// length is always `READ_SIZE`
|
||||||
@ -142,11 +168,8 @@ void read_ic_settings(uint8_t* new_packet) {
|
|||||||
// read all registers into m_info.registers
|
// read all registers into m_info.registers
|
||||||
ads1298_readback_registers(&m_info);
|
ads1298_readback_registers(&m_info);
|
||||||
// Send back over USB.
|
// Send back over USB.
|
||||||
usb_transmit_message_ex(REGISTER_READBACK_PREFIX, REGISTER_READBACK_ADS1298,
|
usb_send_push_ex(REGISTER_READBACK_PREFIX, REGISTER_READBACK_ADS1298,
|
||||||
m_info.registers, ADS1298_REGISTER_COUNT);
|
m_info.registers, ADS1298_REGISTER_COUNT);
|
||||||
// This is totally unnecessary:
|
|
||||||
// usb_transmit_message_ex(ID_REGISTER_READBACK_PREFIX, REGISTER_READBACK_ADS1298,
|
|
||||||
// m_info.id_buffer, sizeof(m_info.id_buffer));
|
|
||||||
ads1298_start_rdatac();
|
ads1298_start_rdatac();
|
||||||
ads1298_standby();
|
ads1298_standby();
|
||||||
} break;
|
} break;
|
||||||
@ -185,13 +208,19 @@ static void process_new_packet(uint8_t* new_packet) {
|
|||||||
m_info.state = 0;
|
m_info.state = 0;
|
||||||
ads1298_standby();
|
ads1298_standby();
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
#if ADS1298_STATS
|
||||||
|
NRF_LOG_INFO("Bytes sent via USB: %lu\n", m_info.bytes_sent_usb);
|
||||||
#endif
|
#endif
|
||||||
reset_counters();
|
reset_counters();
|
||||||
}
|
}
|
||||||
if (SECOND_NIBBLE(new_packet[0]) == TN_STREAM_REQUEST_INFO) {
|
if (SECOND_NIBBLE(new_packet[0]) == TN_STREAM_REQUEST_PERIPHERAL_INFO) {
|
||||||
usb_transmit_message(CTRL_STAT_INFO_PERIPHERALS, g_Peripheral_Info, strlen(g_Peripheral_Info));
|
// #TODO: we should assert that the length of these messages does not exceed the packet
|
||||||
usb_transmit_message(CTRL_STAT_INFO_WHO_AM_I, g_WhoAmI, strlen(g_WhoAmI));
|
// size!
|
||||||
usb_transmit_message(CTRL_STAT_INFO_HW_FW_VERSION, g_HWFW_Ver, sizeof(g_HWFW_Ver));
|
usb_send_append_message(CTRL_STAT_INFO_PERIPHERALS, g_Peripheral_Info, strlen(g_Peripheral_Info));
|
||||||
|
usb_send_append_message(CTRL_STAT_INFO_WHO_AM_I, g_WhoAmI, strlen(g_WhoAmI));
|
||||||
|
usb_send_append_message(CTRL_STAT_INFO_HW_FW_VERSION, g_HWFW_Ver, sizeof(g_HWFW_Ver));
|
||||||
|
usb_send_push();
|
||||||
}
|
}
|
||||||
if (SECOND_NIBBLE(new_packet[0]) == TN_STREAM_REQUEST_BATTERY_LEVEL) {
|
if (SECOND_NIBBLE(new_packet[0]) == TN_STREAM_REQUEST_BATTERY_LEVEL) {
|
||||||
// #NOTE: Will not use. This is not a battery-powered device. (I mean, it can
|
// #NOTE: Will not use. This is not a battery-powered device. (I mean, it can
|
||||||
@ -289,7 +318,9 @@ void drdy_pin_handler(nrfx_gpiote_pin_t pin, nrf_gpiote_polarity_t action) {
|
|||||||
UNUSED_PARAMETER(pin);
|
UNUSED_PARAMETER(pin);
|
||||||
UNUSED_PARAMETER(action);
|
UNUSED_PARAMETER(action);
|
||||||
drdy_flag = true;
|
drdy_flag = true;
|
||||||
|
#if ADS1298_STATS
|
||||||
m_info.drdy_trigger_count += 1;
|
m_info.drdy_trigger_count += 1;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void ads1298_interrupt_setup(void) {
|
void ads1298_interrupt_setup(void) {
|
||||||
@ -393,15 +424,29 @@ int main(void) {
|
|||||||
ads1298_initialize(&m_info);
|
ads1298_initialize(&m_info);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
memset(usb_tx_buffer, 0x41, NRF_DRV_USBD_EPSIZE); // #TEMP
|
memset(usb_send_buffer, 0x41, NRF_DRV_USBD_EPSIZE); // #TEMP
|
||||||
|
|
||||||
reset_counters();
|
reset_counters();
|
||||||
|
|
||||||
// #TEMP: application_timers_start
|
// #TEMP: application_timers_start
|
||||||
application_timers_start();
|
application_timers_start();
|
||||||
|
|
||||||
|
#if AUTO_START_ADS1298 // for debugging
|
||||||
|
ads1298_stop_rdatac();
|
||||||
|
NRF_LOG_INFO("Writing new ADS1298 registers:");
|
||||||
|
uint8_t new_registers[] = {0xC5,0x00,0xCE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,0x00,0x00};
|
||||||
|
memcpy(m_info.registers, new_registers, ADS1298_REGISTER_COUNT);
|
||||||
|
ads1298_update_registers(&m_info);
|
||||||
|
ads1298_set_data_buffer_length(&m_info);
|
||||||
|
ads1298_start_rdatac();
|
||||||
|
ads1298_wakeup();
|
||||||
|
recording_mode = RECORDING_MODE_ALL;
|
||||||
|
#endif
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
#if !DISABLE_USBD_IN_MAIN_LOOP
|
||||||
while (app_usbd_event_queue_process()) { /* Nothing to do */ }
|
while (app_usbd_event_queue_process()) { /* Nothing to do */ }
|
||||||
|
#endif
|
||||||
|
|
||||||
if (recording_mode == RECORDING_MODE_ALL) {
|
if (recording_mode == RECORDING_MODE_ALL) {
|
||||||
// if (recording_mode & drdy_flag) { // may be faster if we're just using ADS1298
|
// if (recording_mode & drdy_flag) { // may be faster if we're just using ADS1298
|
||||||
@ -417,23 +462,30 @@ int main(void) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
switch (m_info.nChs) {
|
// :ADS1298_FAST_PATH
|
||||||
case 4:
|
ads1298_get_data_fast(&m_info);
|
||||||
ads1294_get_data(&m_info);
|
// switch (m_info.nChs) {
|
||||||
break;
|
// case 4:
|
||||||
case 6:
|
// ads1294_get_data(&m_info);
|
||||||
ads1296_get_data(&m_info);
|
// break;
|
||||||
break;
|
// case 6:
|
||||||
case 8:
|
// ads1296_get_data(&m_info);
|
||||||
ads1298_get_data(&m_info);
|
// break;
|
||||||
break;
|
// case 8:
|
||||||
default:
|
// ads1298_get_data(&m_info);
|
||||||
break;
|
// break;
|
||||||
}
|
// default:
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
|
||||||
if (m_info.usb_buffer_count >= m_info.usb_buffer_size_max) {
|
if (m_info.usb_buffer_count >= m_info.usb_buffer_size_max) {
|
||||||
m_info.usb_buffer_count = ADS1298_PACKET_OFFSET;
|
m_info.usb_buffer_count = ADS1298_PACKET_OFFSET;
|
||||||
|
#if !DISABLE_USBD_IN_MAIN_LOOP
|
||||||
app_usbd_cdc_acm_write(&m_app_cdc_acm, m_info.usb_buffer, m_info.usb_buffer_size_max);
|
app_usbd_cdc_acm_write(&m_app_cdc_acm, m_info.usb_buffer, m_info.usb_buffer_size_max);
|
||||||
|
#if ADS1298_STATS
|
||||||
|
m_info.bytes_sent_usb += m_info.usb_buffer_size_max;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
increment_packet(m_info.usb_buffer);
|
increment_packet(m_info.usb_buffer);
|
||||||
|
|
||||||
// NRF_LOG_INFO("Current time tick: %lu", app_timer_cnt_get());
|
// NRF_LOG_INFO("Current time tick: %lu", app_timer_cnt_get());
|
||||||
@ -441,14 +493,16 @@ int main(void) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
#if !DISABLE_USBD_IN_MAIN_LOOP
|
||||||
if (run_throughput_test) {
|
if (run_throughput_test) {
|
||||||
/*ret = */app_usbd_cdc_acm_write(&m_app_cdc_acm, usb_tx_buffer, NRF_DRV_USBD_EPSIZE);
|
/*ret = */app_usbd_cdc_acm_write(&m_app_cdc_acm, usb_send_buffer, NRF_DRV_USBD_EPSIZE);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
UNUSED_RETURN_VALUE(NRF_LOG_PROCESS());
|
UNUSED_RETURN_VALUE(NRF_LOG_PROCESS());
|
||||||
/* Sleep CPU only if there was no interrupt since last loop processing */
|
/* Sleep CPU only if there was no interrupt since last loop processing */
|
||||||
|
#if !DISABLE_USBD_IN_MAIN_LOOP
|
||||||
__WFE();
|
__WFE();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,9 +4,13 @@
|
|||||||
#define NRF52840_BREAKOUT_BOARD 1
|
#define NRF52840_BREAKOUT_BOARD 1
|
||||||
#define ADS1298 1
|
#define ADS1298 1
|
||||||
#if ADS1298
|
#if ADS1298
|
||||||
#define ADS1298_STATS 1
|
#define AUTO_START_ADS1298 0
|
||||||
|
#define ADS1298_STATS 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Debugging flags
|
||||||
|
#define DISABLE_USBD_IN_MAIN_LOOP 0
|
||||||
|
|
||||||
// Testing with nRF52840:
|
// Testing with nRF52840:
|
||||||
#if NRF52840_BREAKOUT_BOARD
|
#if NRF52840_BREAKOUT_BOARD
|
||||||
#define ADS1298_DRDY_PIN 11
|
#define ADS1298_DRDY_PIN 11
|
||||||
@ -35,7 +39,8 @@
|
|||||||
#define TN_STREAM_START 0x2
|
#define TN_STREAM_START 0x2
|
||||||
#define TN_STREAM_STOP 0xF
|
#define TN_STREAM_STOP 0xF
|
||||||
#define TN_STREAM_REQUEST_BATTERY_LEVEL 0xB
|
#define TN_STREAM_REQUEST_BATTERY_LEVEL 0xB
|
||||||
#define TN_STREAM_REQUEST_INFO 0xE
|
// #define TN_STREAM_REQUEST_WHO_AM_I 0xD
|
||||||
|
#define TN_STREAM_REQUEST_PERIPHERAL_INFO 0xE
|
||||||
|
|
||||||
#define TN_MISC_THROUGHPUT_TEST 0xF
|
#define TN_MISC_THROUGHPUT_TEST 0xF
|
||||||
#define TN_MISC_RTT_REQUEST 0x1
|
#define TN_MISC_RTT_REQUEST 0x1
|
||||||
|
|||||||
@ -1628,7 +1628,7 @@
|
|||||||
// <i> This may limit throughput if a lot of binary data is sent, but in terminal mode operation it makes sure that the data is always displayed right after it is sent.
|
// <i> This may limit throughput if a lot of binary data is sent, but in terminal mode operation it makes sure that the data is always displayed right after it is sent.
|
||||||
|
|
||||||
#ifndef APP_USBD_CDC_ACM_ZLP_ON_EPSIZE_WRITE
|
#ifndef APP_USBD_CDC_ACM_ZLP_ON_EPSIZE_WRITE
|
||||||
#define APP_USBD_CDC_ACM_ZLP_ON_EPSIZE_WRITE 1
|
#define APP_USBD_CDC_ACM_ZLP_ON_EPSIZE_WRITE 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// </h>
|
// </h>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user