Registers tweaks
Prepare for new registers structure used in v1
This commit is contained in:
parent
e1a77fc1ca
commit
cdee81d3ff
@ -145,6 +145,7 @@ extern volatile uint32_t systicks_counter;
|
||||
extern volatile uint8_t pmu_irq;
|
||||
extern volatile uint8_t stop_mode_active;
|
||||
|
||||
extern volatile uint8_t rtc_reg_xor_events;
|
||||
extern volatile RTC_TimeTypeDef_u rtc_alarm_time;
|
||||
extern volatile RTC_DateTypeDef_u rtc_alarm_date;
|
||||
|
||||
|
20
Core/Inc/i2cs.h
Normal file
20
Core/Inc/i2cs.h
Normal file
@ -0,0 +1,20 @@
|
||||
#include "stm32f1xx_hal.h"
|
||||
|
||||
|
||||
#ifndef I2CS_H_
|
||||
#define I2CS_H_
|
||||
|
||||
#define I2CS_REARM_TIMEOUT 500
|
||||
|
||||
|
||||
enum i2cs_state {
|
||||
//I2CS_STATE_HALT,
|
||||
I2CS_STATE_IDLE,
|
||||
I2CS_STATE_REG_REQUEST,
|
||||
I2CS_STATE_REG_ANSWER
|
||||
};
|
||||
|
||||
extern enum i2cs_state i2cs_state;
|
||||
extern uint32_t i2cs_rearm_counter;
|
||||
|
||||
#endif /* I2CS_H_ */
|
@ -8,7 +8,7 @@ enum reg_id {
|
||||
REG_ID_TYP = 0x00, //!< firmware type (0=official, others=custom)
|
||||
REG_ID_VER = 0x01, //!< fw version (7:4=Major, 3:0=Minor)
|
||||
#ifdef I2C_REGS_COMPAT
|
||||
REG_ID_CFG = 0x02, // config
|
||||
REG_ID_SYS_CFG = 0x02, // config
|
||||
REG_ID_INT = 0x03, // interrupt status
|
||||
REG_ID_KEY = 0x04, // key status
|
||||
REG_ID_BKL = 0x05, // backlight steps (0-9)
|
||||
@ -27,31 +27,54 @@ enum reg_id {
|
||||
REG_ID_RTC_ALARM_DATE = 0x12, // RTC alarm date
|
||||
REG_ID_RTC_ALARM_TIME = 0x13, // RTC alarm time
|
||||
#else
|
||||
REG_ID_CFG = 0x02, //!< config
|
||||
// TODO: REG_ID_CFG_0 - 32b (RW)
|
||||
REG_ID_SYS_CFG = 0x02, //!< config
|
||||
REG_ID_INT_CFG = 0x03, //!< IRQ config
|
||||
REG_ID_INT = 0x04, //!< interrupt status
|
||||
REG_ID_BKL = 0x05, //!< backlight steps (0-9)
|
||||
REG_ID_BK2 = 0x06, //!< keyboard backlight (0-9)
|
||||
REG_ID_DEB = 0x07, //!< debounce cfg (time in ms)
|
||||
REG_ID_FRQ = 0x08, //!< poll freq cfg (time in ms)
|
||||
REG_ID_PWR_CTRL = 0x09, //!< Power control (0: idle, 1: pico reset, 2: system reset, 3: reserved, 4: sleep, 5: full-shutdown)
|
||||
REG_ID_PWR_CTRL = 0x04, //!< Power control (0: idle, 1: pico reset, 2: system reset, 3: reserved, 4: sleep, 5: full-shutdown)
|
||||
REG_ID_RTC_CFG = 0x05, //!< RTC general config
|
||||
|
||||
REG_ID_RTC_CFG = 0x0A, //!< RTC general config
|
||||
REG_ID_RTC_DATE = 0x0B, //!< RTC date
|
||||
REG_ID_RTC_TIME = 0x0C, //!< RTC time
|
||||
REG_ID_RTC_ALARM_DATE = 0x0D, //!< RTC alarm date
|
||||
REG_ID_RTC_ALARM_TIME = 0x0E, //!< RTC alarm time
|
||||
// TODO: REG_ID_CFG_1 - 32b (RW)
|
||||
REG_ID_DEB = 0x06, //!< debounce cfg (time in ms)
|
||||
REG_ID_FRQ = 0x07, //!< poll freq cfg (time in ms)
|
||||
REG_ID_BKL = 0x08, //!< backlight steps (0-9)
|
||||
REG_ID_BK2 = 0x09, //!< keyboard backlight (0-9)
|
||||
|
||||
REG_ID_KEY = 0x10, //!< key status
|
||||
REG_ID_FIF = 0x11, //!< fifo
|
||||
REG_ID_C64_MTX = 0x12, //!< read c64 matrix
|
||||
REG_ID_C64_JS = 0x13, //!< joystick io bits
|
||||
// TODO: REG_ID_RTC_DATE - 32b (RW)
|
||||
REG_ID_RTC_DATE = 0x0A, //!< RTC date
|
||||
// TODO: REG_ID_RTC_TIME - 32b (RW)
|
||||
REG_ID_RTC_TIME = 0x0B, //!< RTC time
|
||||
// TODO: REG_ID_RTC_ALARM_DATE - 32b (RW)
|
||||
REG_ID_RTC_ALARM_DATE = 0x0C, //!< RTC alarm date
|
||||
// TODO: REG_ID_RTC_ALARM_TIME - 32b (RW)
|
||||
REG_ID_RTC_ALARM_TIME = 0x0D, //!< RTC alarm time
|
||||
|
||||
REG_ID_BAT = 0x30, //!< battery
|
||||
// TODO: REG_ID_INT - 32b (RO)
|
||||
REG_ID_INT = 0x10, //!< interrupt flags status
|
||||
|
||||
// TODO: REG_ID_KBD - 32b (RO)
|
||||
REG_ID_KEY = 0x14, //!< key status - 8b
|
||||
REG_ID_C64_JS = 0x15, //!< joystick io bits - 8b
|
||||
REG_ID_FIF = 0x16, //!< fifo - 16b
|
||||
|
||||
// TODO: REG_ID_BAT - 32b (RO)
|
||||
REG_ID_BAT = 0x18, //!< battery percentage - 16b
|
||||
//REG_ID_BAT_RAW //!< battery voltage value in mV - 16b
|
||||
|
||||
// TODO: REG_ID_C64_MTX_0 - 32b (RO)
|
||||
REG_ID_C64_MTX = 0x1A, //!< read c64 matrix
|
||||
// TODO: REG_ID_C64_MTX_1 - 32b (RO)
|
||||
// TODO: REG_ID_C64_MTX_2 - 32b (RO)
|
||||
#endif
|
||||
REG_ID_LAST
|
||||
};
|
||||
|
||||
#define REGS_GLOBAL_ENTRY()
|
||||
|
||||
typedef struct {
|
||||
const uint8_t addr;
|
||||
uint8_t* value[];
|
||||
} REGS_GLOBAL_ENTRY;
|
||||
|
||||
#define CFG_OVERFLOW_ON (1 << 0) //When a FIFO overflow happens, should the new entry still be pushed, overwriting the oldest one. If 0 then new entry is lost.
|
||||
#define CFG_REPORT_MODS (1 << 6) // Should Alt, Sym and Shifts be reported as well
|
||||
#define CFG_USE_MODS (1 << 7) // Should Alt, Sym and Shifts modify the keys reported
|
||||
|
@ -2,6 +2,6 @@
|
||||
#define VERSION_H_
|
||||
|
||||
#define VERSION_MAJOR (0)
|
||||
#define VERSION_MINOR (5)
|
||||
#define VERSION_MINOR (6)
|
||||
|
||||
#endif /* VERSION_H_ */
|
||||
|
@ -37,6 +37,7 @@ UART_HandleTypeDef huart1;
|
||||
UART_HandleTypeDef huart3;
|
||||
#endif
|
||||
|
||||
volatile uint8_t rtc_reg_xor_events = 0;
|
||||
volatile RTC_TimeTypeDef_u rtc_alarm_time = {.raw = 0x00000000};
|
||||
volatile RTC_DateTypeDef_u rtc_alarm_date = {.raw = 0x00010101};
|
||||
|
||||
|
221
Core/Src/i2cs.c
Normal file
221
Core/Src/i2cs.c
Normal file
@ -0,0 +1,221 @@
|
||||
#include "i2cs.h"
|
||||
|
||||
#include "hal_interface.h"
|
||||
#include "backlight.h"
|
||||
#include "fifo.h"
|
||||
#include "rtc.h"
|
||||
#include "regs.h"
|
||||
|
||||
|
||||
extern I2C_HandleTypeDef hi2c1;
|
||||
extern I2C_HandleTypeDef hi2c2;
|
||||
|
||||
extern RTC_HandleTypeDef hrtc;
|
||||
|
||||
static uint8_t i2cs_r_buff[5];
|
||||
static volatile uint8_t i2cs_r_idx = 0;
|
||||
static uint8_t i2cs_w_buff[10];
|
||||
static volatile uint8_t i2cs_w_idx = 0;
|
||||
static volatile uint8_t i2cs_w_len = 0;
|
||||
|
||||
enum i2cs_state i2cs_state = I2CS_STATE_IDLE;
|
||||
uint32_t i2cs_rearm_counter = 0;
|
||||
|
||||
|
||||
extern void HAL_I2C_AddrCallback(I2C_HandleTypeDef *hi2c, uint8_t TransferDirection, uint16_t AddrMatchCode) {
|
||||
if (hi2c == &hi2c1) {
|
||||
// I2C slave addr match error detection
|
||||
if (AddrMatchCode != 0x3E) // 0x1F << 1
|
||||
return;
|
||||
|
||||
if (TransferDirection == I2C_DIRECTION_TRANSMIT) {
|
||||
if (i2cs_state == I2CS_STATE_IDLE) {
|
||||
i2cs_state = I2CS_STATE_REG_REQUEST;
|
||||
|
||||
i2cs_r_idx = 0;
|
||||
HAL_I2C_Slave_Sequential_Receive_IT(hi2c, i2cs_r_buff, 1, I2C_FIRST_FRAME); // This write the first received byte to i2cs_r_buff[0]
|
||||
|
||||
i2cs_rearm_counter = uptime_ms();
|
||||
}
|
||||
}
|
||||
|
||||
if (TransferDirection == I2C_DIRECTION_RECEIVE) {
|
||||
if (i2cs_state == I2CS_STATE_REG_REQUEST) {
|
||||
const uint8_t is_write = (uint8_t)(i2cs_r_buff[0] & (1 << 7));
|
||||
const uint8_t reg = (uint8_t)(i2cs_r_buff[0] & ~(1 << 7));
|
||||
|
||||
i2cs_w_buff[0] = reg;
|
||||
i2cs_w_len = 2;
|
||||
|
||||
if (reg == REG_ID_BKL) { // We wait an another byte for these registers
|
||||
if (is_write)
|
||||
lcd_backlight_update(i2cs_r_buff[1]);
|
||||
i2cs_w_buff[1] = reg_get_value(REG_ID_BKL);
|
||||
} else if (reg == REG_ID_BK2) {
|
||||
if (is_write)
|
||||
kbd_backlight_update(i2cs_r_buff[1]);
|
||||
i2cs_w_buff[1] = reg_get_value(REG_ID_BK2);
|
||||
} else if (reg == REG_ID_SYS_CFG) {
|
||||
if (is_write)
|
||||
reg_set_value(REG_ID_SYS_CFG, i2cs_r_buff[1]);
|
||||
i2cs_w_buff[1] = reg_get_value(REG_ID_SYS_CFG);
|
||||
} else if (reg == REG_ID_INT_CFG) {
|
||||
if (is_write)
|
||||
reg_set_value(REG_ID_INT_CFG, i2cs_r_buff[1]);
|
||||
i2cs_w_buff[1] = reg_get_value(REG_ID_INT_CFG);
|
||||
} else if (reg == REG_ID_DEB) {
|
||||
if (is_write) {
|
||||
keyboard_set_hold_period(*((uint16_t*)&i2cs_r_buff[1]));
|
||||
reg_set_value(REG_ID_DEB, 0); // Trig async flag for EEPROM saving
|
||||
}
|
||||
*((uint16_t*)&i2cs_w_buff[1]) = keyboard_get_hold_period();
|
||||
|
||||
i2cs_w_len = 3;
|
||||
} else if (reg == REG_ID_FRQ) {
|
||||
if (is_write)
|
||||
reg_set_value(REG_ID_FRQ, i2cs_r_buff[1]);
|
||||
i2cs_w_buff[1] = reg_get_value(REG_ID_FRQ);
|
||||
} else if (reg == REG_ID_FIF) {
|
||||
struct fifo_item item = {0};
|
||||
fifo_dequeue(&item);
|
||||
i2cs_w_buff[0] = item.state;
|
||||
i2cs_w_buff[1] = item.key;
|
||||
} else if (reg == REG_ID_INT) {
|
||||
if (is_write)
|
||||
reg_set_value(REG_ID_INT, 0);
|
||||
i2cs_w_buff[1] = reg_get_value(REG_ID_INT);
|
||||
LL_GPIO_SetOutputPin(PICO_IRQ_GPIO_Port, PICO_IRQ_Pin); // De-assert the IRQ signal
|
||||
} else if (reg == REG_ID_VER) {
|
||||
i2cs_w_buff[1] = reg_get_value(REG_ID_VER);
|
||||
} else if (reg == REG_ID_TYP) {
|
||||
i2cs_w_buff[1] = reg_get_value(REG_ID_TYP);
|
||||
} else if (reg == REG_ID_BAT) {
|
||||
i2cs_w_buff[1] = reg_get_value(REG_ID_BAT);
|
||||
} else if (reg == REG_ID_RTC_CFG) {
|
||||
if (is_write) {
|
||||
rtc_reg_xor_events |= reg_get_value(REG_ID_RTC_CFG) ^ i2cs_r_buff[1]; // Using a "OR" set style to avoid loosing change before processing it
|
||||
reg_set_value(REG_ID_RTC_CFG, i2cs_r_buff[1]);
|
||||
}
|
||||
i2cs_w_buff[1] = reg_get_value(REG_ID_RTC_CFG);
|
||||
} else if (reg == REG_ID_RTC_DATE) {
|
||||
RTC_DateTypeDef date_s = {0};
|
||||
if (is_write) {
|
||||
i2cs_RTC_date_from_buffer(&date_s, &i2cs_r_buff[1]);
|
||||
|
||||
HAL_RTC_SetDate(&hrtc, &date_s, RTC_FORMAT_BIN);
|
||||
}
|
||||
|
||||
HAL_RTC_GetDate(&hrtc, &date_s, RTC_FORMAT_BIN);
|
||||
i2cs_fill_buffer_RTC_date(&i2cs_w_buff[1], &date_s);
|
||||
|
||||
i2cs_w_len = 5;
|
||||
} else if (reg == REG_ID_RTC_TIME) {
|
||||
RTC_TimeTypeDef time_s = {0};
|
||||
if (is_write) {
|
||||
i2cs_RTC_time_from_buffer(&time_s, &i2cs_r_buff[1]);
|
||||
|
||||
HAL_RTC_SetTime(&hrtc, &time_s, RTC_FORMAT_BIN);
|
||||
}
|
||||
|
||||
HAL_RTC_GetTime(&hrtc, &time_s, RTC_FORMAT_BIN);
|
||||
i2cs_fill_buffer_RTC_time(&i2cs_w_buff[1], &time_s);
|
||||
|
||||
i2cs_w_len = 4;
|
||||
} else if (reg == REG_ID_RTC_ALARM_DATE) {
|
||||
if (is_write)
|
||||
i2cs_RTC_date_from_buffer(&rtc_alarm_date._s, &i2cs_r_buff[1]);
|
||||
|
||||
i2cs_fill_buffer_RTC_date(&i2cs_w_buff[1], &rtc_alarm_date._s);
|
||||
|
||||
i2cs_w_len = 5;
|
||||
} else if (reg == REG_ID_RTC_ALARM_TIME) {
|
||||
if (is_write)
|
||||
i2cs_RTC_time_from_buffer(&rtc_alarm_time._s, &i2cs_r_buff[1]);
|
||||
|
||||
i2cs_fill_buffer_RTC_time(&i2cs_w_buff[1], &rtc_alarm_time._s);
|
||||
|
||||
i2cs_w_len = 4;
|
||||
} else if (reg == REG_ID_KEY) {
|
||||
i2cs_w_buff[0] = fifo_count();
|
||||
i2cs_w_buff[0] |= keyboard_get_numlock() ? KEY_NUMLOCK : 0x00;
|
||||
i2cs_w_buff[0] |= keyboard_get_capslock() ? KEY_CAPSLOCK : 0x00;
|
||||
} else if (reg == REG_ID_C64_MTX) {
|
||||
//memcpy(write_buffer + 1, io_matrix, sizeof(io_matrix));
|
||||
*((uint32_t*)(&i2cs_w_buff[1]) + 0) = *((uint32_t*)(io_matrix) + 0);
|
||||
*((uint32_t*)(&i2cs_w_buff[1]) + 1) = *((uint32_t*)(io_matrix) + 1);
|
||||
i2cs_w_buff[9] = io_matrix[8];
|
||||
|
||||
i2cs_w_len = 10;
|
||||
} else if (reg == REG_ID_C64_JS) {
|
||||
i2cs_w_buff[1] = js_bits;
|
||||
} else if (reg == REG_ID_PWR_CTRL) {
|
||||
if (is_write)
|
||||
reg_set_value(REG_ID_PWR_CTRL, i2cs_r_buff[1]);
|
||||
i2cs_w_buff[1] = reg_get_value(REG_ID_PWR_CTRL);
|
||||
} else {
|
||||
i2cs_w_buff[0] = 0;
|
||||
i2cs_w_buff[1] = 0;
|
||||
}
|
||||
|
||||
i2cs_state = I2CS_STATE_REG_ANSWER;
|
||||
i2cs_w_idx = 0;
|
||||
|
||||
HAL_I2C_Slave_Sequential_Transmit_IT(hi2c, i2cs_w_buff, i2cs_w_len, I2C_FIRST_AND_LAST_FRAME);
|
||||
|
||||
i2cs_rearm_counter = uptime_ms();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef *hi2c) {
|
||||
if (hi2c == &hi2c1) {
|
||||
i2cs_r_idx++;
|
||||
|
||||
if (i2cs_state == I2CS_STATE_REG_REQUEST) {
|
||||
const uint8_t is_write = (uint8_t)(i2cs_r_buff[0] & (1 << 7));
|
||||
const uint8_t reg = (uint8_t)(i2cs_r_buff[0] & ~(1 << 7));
|
||||
uint8_t bytes_needed = 0;
|
||||
|
||||
// Check for another mandatories bytes depending on register requested
|
||||
if (reg == REG_ID_BKL ||
|
||||
reg == REG_ID_BK2 ||
|
||||
reg == REG_ID_SYS_CFG ||
|
||||
reg == REG_ID_INT_CFG ||
|
||||
reg == REG_ID_FRQ) {
|
||||
if (is_write)
|
||||
bytes_needed = 1;
|
||||
} else if (reg == REG_ID_DEB) {
|
||||
if (is_write)
|
||||
bytes_needed = 2;
|
||||
} else if (reg == REG_ID_RTC_DATE ||
|
||||
reg == REG_ID_RTC_ALARM_DATE ||
|
||||
reg == REG_ID_RTC_TIME ||
|
||||
reg == REG_ID_RTC_ALARM_TIME) {
|
||||
if (is_write)
|
||||
bytes_needed = 3;
|
||||
}
|
||||
|
||||
if (bytes_needed > 0)
|
||||
HAL_I2C_Slave_Sequential_Receive_IT(hi2c, i2cs_r_buff + i2cs_r_idx, bytes_needed, I2C_NEXT_FRAME); // This write the second or more received byte to i2cs_r_buff[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern void HAL_I2C_ListenCpltCallback (I2C_HandleTypeDef *hi2c) {
|
||||
if (hi2c == &hi2c1) {
|
||||
if (i2cs_state == I2CS_STATE_REG_ANSWER)
|
||||
i2cs_state = I2CS_STATE_IDLE;
|
||||
|
||||
HAL_I2C_EnableListen_IT(hi2c);
|
||||
}
|
||||
}
|
||||
|
||||
extern void HAL_I2C_ErrorCallback(I2C_HandleTypeDef *hi2c) {
|
||||
if (hi2c == &hi2c1)
|
||||
if (HAL_I2C_GetError(hi2c) != HAL_I2C_ERROR_AF) {
|
||||
//Error_Handler();
|
||||
// Actually this will trigger the watchdog and restart the system... That can ruin the day of the user.
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
}
|
@ -152,27 +152,27 @@ static void transition_to(struct list_item * const p_item, const enum key_state
|
||||
|
||||
switch (p_entry->mod) {
|
||||
case MOD_ALT:
|
||||
if (reg_is_bit_set(REG_ID_CFG, CFG_REPORT_MODS))
|
||||
if (reg_is_bit_set(REG_ID_SYS_CFG, CFG_REPORT_MODS))
|
||||
chr = KEY_MOD_ALT;
|
||||
break;
|
||||
|
||||
case MOD_SHL:
|
||||
if (reg_is_bit_set(REG_ID_CFG, CFG_REPORT_MODS))
|
||||
if (reg_is_bit_set(REG_ID_SYS_CFG, CFG_REPORT_MODS))
|
||||
chr = KEY_MOD_SHL;
|
||||
break;
|
||||
|
||||
case MOD_SHR:
|
||||
if (reg_is_bit_set(REG_ID_CFG, CFG_REPORT_MODS))
|
||||
if (reg_is_bit_set(REG_ID_SYS_CFG, CFG_REPORT_MODS))
|
||||
chr = KEY_MOD_SHR;
|
||||
break;
|
||||
|
||||
case MOD_SYM:
|
||||
if (reg_is_bit_set(REG_ID_CFG, CFG_REPORT_MODS))
|
||||
if (reg_is_bit_set(REG_ID_SYS_CFG, CFG_REPORT_MODS))
|
||||
chr = KEY_MOD_SYM;
|
||||
break;
|
||||
|
||||
case MOD_CTRL:
|
||||
if (reg_is_bit_set(REG_ID_CFG, CFG_REPORT_MODS))
|
||||
if (reg_is_bit_set(REG_ID_SYS_CFG, CFG_REPORT_MODS))
|
||||
chr = KEY_MOD_CTRL;
|
||||
break;
|
||||
|
||||
@ -182,7 +182,7 @@ static void transition_to(struct list_item * const p_item, const enum key_state
|
||||
capslock_changed = 1;
|
||||
}
|
||||
|
||||
if (reg_is_bit_set(REG_ID_CFG, CFG_USE_MODS)) {
|
||||
if (reg_is_bit_set(REG_ID_SYS_CFG, CFG_USE_MODS)) {
|
||||
const uint8_t shift = (mods[MOD_SHL] || mods[MOD_SHR]);
|
||||
const uint8_t alt = mods[MOD_ALT] | numlock;
|
||||
//const uint8_t ctrl = mods[MOD_CTRL];
|
||||
|
218
Core/Src/main.c
218
Core/Src/main.c
@ -27,6 +27,7 @@
|
||||
#include "eeprom.h"
|
||||
#include "fifo.h"
|
||||
#include "keyboard.h"
|
||||
#include "i2cs.h"
|
||||
#include "regs.h"
|
||||
#include "rtc.h"
|
||||
|
||||
@ -40,9 +41,6 @@
|
||||
#define DEFAULT_KBD_DEB (KEY_HOLD_TIME)
|
||||
#define DEFAULT_RCT_CFG (0x00)
|
||||
|
||||
#define I2CS_REARM_TIMEOUT 500
|
||||
#define I2CS_W_BUFF_LEN 31+1 // The last one must be only a 0 value, TODO: another cleaner way?
|
||||
|
||||
#ifdef DEBUG
|
||||
#define DEBUG_UART_MSG(msg) HAL_UART_Transmit(&huart1, (uint8_t*)msg, sizeof(msg)-1, 1000)
|
||||
//#define DEBUG_UART_MSG2(d,s) HAL_UART_Transmit(&huart1, (uint8_t*)d, s, 200)
|
||||
@ -51,12 +49,6 @@
|
||||
|
||||
|
||||
// Private typedef -----------------------------------------------------------
|
||||
enum i2cs_state {
|
||||
//I2CS_STATE_HALT,
|
||||
I2CS_STATE_IDLE,
|
||||
I2CS_STATE_REG_REQUEST,
|
||||
I2CS_STATE_REG_ANSWER
|
||||
};
|
||||
|
||||
|
||||
// Private variables ---------------------------------------------------------
|
||||
@ -79,20 +71,10 @@ extern UART_HandleTypeDef huart3;
|
||||
|
||||
volatile uint32_t systicks_counter = 0; // 1 MHz systick counter
|
||||
static uint32_t pmu_check_counter = 0;
|
||||
static uint32_t i2cs_rearm_counter = 0;
|
||||
|
||||
static uint8_t i2cs_r_buff[5];
|
||||
static volatile uint8_t i2cs_r_idx = 0;
|
||||
static uint8_t i2cs_w_buff[I2CS_W_BUFF_LEN];
|
||||
static volatile uint8_t i2cs_w_idx = 0;
|
||||
static volatile uint8_t i2cs_w_len = 0;
|
||||
static enum i2cs_state i2cs_state = I2CS_STATE_IDLE;
|
||||
|
||||
static uint8_t keycb_start = 0;
|
||||
static uint32_t head_phone_status = 0; // TODO: Combine status registers
|
||||
|
||||
static volatile uint8_t rtc_reg_xor_events = 0;
|
||||
|
||||
volatile uint8_t stop_mode_active = 0;
|
||||
|
||||
volatile uint8_t pmu_irq = 0;
|
||||
@ -117,202 +99,6 @@ extern void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
|
||||
}
|
||||
}
|
||||
|
||||
extern void HAL_I2C_AddrCallback(I2C_HandleTypeDef *hi2c, uint8_t TransferDirection, uint16_t AddrMatchCode) {
|
||||
if (hi2c == &hi2c1) {
|
||||
// I2C slave addr match error detection
|
||||
if (AddrMatchCode != 0x3E) // 0x1F << 1
|
||||
return;
|
||||
|
||||
if (TransferDirection == I2C_DIRECTION_TRANSMIT) {
|
||||
if (i2cs_state == I2CS_STATE_IDLE) {
|
||||
i2cs_state = I2CS_STATE_REG_REQUEST;
|
||||
|
||||
i2cs_r_idx = 0;
|
||||
HAL_I2C_Slave_Sequential_Receive_IT(hi2c, i2cs_r_buff, 1, I2C_FIRST_FRAME); // This write the first received byte to i2cs_r_buff[0]
|
||||
|
||||
i2cs_rearm_counter = uptime_ms();
|
||||
}
|
||||
}
|
||||
|
||||
if (TransferDirection == I2C_DIRECTION_RECEIVE) {
|
||||
if (i2cs_state == I2CS_STATE_REG_REQUEST) {
|
||||
const uint8_t is_write = (uint8_t)(i2cs_r_buff[0] & (1 << 7));
|
||||
const uint8_t reg = (uint8_t)(i2cs_r_buff[0] & ~(1 << 7));
|
||||
|
||||
i2cs_w_buff[0] = reg;
|
||||
i2cs_w_len = 2;
|
||||
|
||||
if (reg == REG_ID_BKL) { // We wait an another byte for these registers
|
||||
if (is_write)
|
||||
lcd_backlight_update(i2cs_r_buff[1]);
|
||||
i2cs_w_buff[1] = reg_get_value(REG_ID_BKL);
|
||||
} else if (reg == REG_ID_BK2) {
|
||||
if (is_write)
|
||||
kbd_backlight_update(i2cs_r_buff[1]);
|
||||
i2cs_w_buff[1] = reg_get_value(REG_ID_BK2);
|
||||
} else if (reg == REG_ID_CFG) {
|
||||
if (is_write)
|
||||
reg_set_value(REG_ID_CFG, i2cs_r_buff[1]);
|
||||
i2cs_w_buff[1] = reg_get_value(REG_ID_CFG);
|
||||
} else if (reg == REG_ID_INT_CFG) {
|
||||
if (is_write)
|
||||
reg_set_value(REG_ID_INT_CFG, i2cs_r_buff[1]);
|
||||
i2cs_w_buff[1] = reg_get_value(REG_ID_INT_CFG);
|
||||
} else if (reg == REG_ID_DEB) {
|
||||
if (is_write) {
|
||||
keyboard_set_hold_period(*((uint16_t*)&i2cs_r_buff[1]));
|
||||
reg_set_value(REG_ID_DEB, 0); // Trig async flag for EEPROM saving
|
||||
}
|
||||
*((uint16_t*)&i2cs_w_buff[1]) = keyboard_get_hold_period();
|
||||
|
||||
i2cs_w_len = 3;
|
||||
} else if (reg == REG_ID_FRQ) {
|
||||
if (is_write)
|
||||
reg_set_value(REG_ID_FRQ, i2cs_r_buff[1]);
|
||||
i2cs_w_buff[1] = reg_get_value(REG_ID_FRQ);
|
||||
} else if (reg == REG_ID_FIF) {
|
||||
struct fifo_item item = {0};
|
||||
fifo_dequeue(&item);
|
||||
i2cs_w_buff[0] = item.state;
|
||||
i2cs_w_buff[1] = item.key;
|
||||
} else if (reg == REG_ID_INT) {
|
||||
if (is_write)
|
||||
reg_set_value(REG_ID_INT, 0);
|
||||
i2cs_w_buff[1] = reg_get_value(REG_ID_INT);
|
||||
LL_GPIO_SetOutputPin(PICO_IRQ_GPIO_Port, PICO_IRQ_Pin); // De-assert the IRQ signal
|
||||
} else if (reg == REG_ID_VER) {
|
||||
i2cs_w_buff[1] = reg_get_value(REG_ID_VER);
|
||||
} else if (reg == REG_ID_TYP) {
|
||||
i2cs_w_buff[1] = reg_get_value(REG_ID_TYP);
|
||||
} else if (reg == REG_ID_BAT) {
|
||||
i2cs_w_buff[1] = reg_get_value(REG_ID_BAT);
|
||||
} else if (reg == REG_ID_RTC_CFG) {
|
||||
if (is_write) {
|
||||
rtc_reg_xor_events |= reg_get_value(REG_ID_RTC_CFG) ^ i2cs_r_buff[1]; // Using a "OR" set style to avoid loosing change before processing it
|
||||
reg_set_value(REG_ID_RTC_CFG, i2cs_r_buff[1]);
|
||||
}
|
||||
i2cs_w_buff[1] = reg_get_value(REG_ID_RTC_CFG);
|
||||
} else if (reg == REG_ID_RTC_DATE) {
|
||||
RTC_DateTypeDef date_s = {0};
|
||||
if (is_write) {
|
||||
i2cs_RTC_date_from_buffer(&date_s, &i2cs_r_buff[1]);
|
||||
|
||||
HAL_RTC_SetDate(&hrtc, &date_s, RTC_FORMAT_BIN);
|
||||
}
|
||||
|
||||
HAL_RTC_GetDate(&hrtc, &date_s, RTC_FORMAT_BIN);
|
||||
i2cs_fill_buffer_RTC_date(&i2cs_w_buff[1], &date_s);
|
||||
|
||||
i2cs_w_len = 5;
|
||||
} else if (reg == REG_ID_RTC_TIME) {
|
||||
RTC_TimeTypeDef time_s = {0};
|
||||
if (is_write) {
|
||||
i2cs_RTC_time_from_buffer(&time_s, &i2cs_r_buff[1]);
|
||||
|
||||
HAL_RTC_SetTime(&hrtc, &time_s, RTC_FORMAT_BIN);
|
||||
}
|
||||
|
||||
HAL_RTC_GetTime(&hrtc, &time_s, RTC_FORMAT_BIN);
|
||||
i2cs_fill_buffer_RTC_time(&i2cs_w_buff[1], &time_s);
|
||||
|
||||
i2cs_w_len = 4;
|
||||
} else if (reg == REG_ID_RTC_ALARM_DATE) {
|
||||
if (is_write)
|
||||
i2cs_RTC_date_from_buffer(&rtc_alarm_date._s, &i2cs_r_buff[1]);
|
||||
|
||||
i2cs_fill_buffer_RTC_date(&i2cs_w_buff[1], &rtc_alarm_date._s);
|
||||
|
||||
i2cs_w_len = 5;
|
||||
} else if (reg == REG_ID_RTC_ALARM_TIME) {
|
||||
if (is_write)
|
||||
i2cs_RTC_time_from_buffer(&rtc_alarm_time._s, &i2cs_r_buff[1]);
|
||||
|
||||
i2cs_fill_buffer_RTC_time(&i2cs_w_buff[1], &rtc_alarm_time._s);
|
||||
|
||||
i2cs_w_len = 4;
|
||||
} else if (reg == REG_ID_KEY) {
|
||||
i2cs_w_buff[0] = fifo_count();
|
||||
i2cs_w_buff[0] |= keyboard_get_numlock() ? KEY_NUMLOCK : 0x00;
|
||||
i2cs_w_buff[0] |= keyboard_get_capslock() ? KEY_CAPSLOCK : 0x00;
|
||||
} else if (reg == REG_ID_C64_MTX) {
|
||||
//memcpy(write_buffer + 1, io_matrix, sizeof(io_matrix));
|
||||
*((uint32_t*)(&i2cs_w_buff[1]) + 0) = *((uint32_t*)(io_matrix) + 0);
|
||||
*((uint32_t*)(&i2cs_w_buff[1]) + 1) = *((uint32_t*)(io_matrix) + 1);
|
||||
i2cs_w_buff[9] = io_matrix[8];
|
||||
|
||||
i2cs_w_len = 10;
|
||||
} else if (reg == REG_ID_C64_JS) {
|
||||
i2cs_w_buff[1] = js_bits;
|
||||
} else if (reg == REG_ID_PWR_CTRL) {
|
||||
if (is_write)
|
||||
reg_set_value(REG_ID_PWR_CTRL, i2cs_r_buff[1]);
|
||||
i2cs_w_buff[1] = reg_get_value(REG_ID_PWR_CTRL);
|
||||
} else {
|
||||
i2cs_w_buff[0] = 0;
|
||||
i2cs_w_buff[1] = 0;
|
||||
}
|
||||
|
||||
i2cs_state = I2CS_STATE_REG_ANSWER;
|
||||
i2cs_w_idx = 0;
|
||||
|
||||
HAL_I2C_Slave_Sequential_Transmit_IT(hi2c, i2cs_w_buff, i2cs_w_len, I2C_FIRST_AND_LAST_FRAME);
|
||||
|
||||
i2cs_rearm_counter = uptime_ms();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef *hi2c) {
|
||||
if (hi2c == &hi2c1) {
|
||||
i2cs_r_idx++;
|
||||
|
||||
if (i2cs_state == I2CS_STATE_REG_REQUEST) {
|
||||
const uint8_t is_write = (uint8_t)(i2cs_r_buff[0] & (1 << 7));
|
||||
const uint8_t reg = (uint8_t)(i2cs_r_buff[0] & ~(1 << 7));
|
||||
uint8_t bytes_needed = 0;
|
||||
|
||||
// Check for another mandatories bytes depending on register requested
|
||||
if (reg == REG_ID_BKL ||
|
||||
reg == REG_ID_BK2 ||
|
||||
reg == REG_ID_CFG ||
|
||||
reg == REG_ID_INT_CFG ||
|
||||
reg == REG_ID_FRQ) {
|
||||
if (is_write)
|
||||
bytes_needed = 1;
|
||||
} else if (reg == REG_ID_DEB) {
|
||||
if (is_write)
|
||||
bytes_needed = 2;
|
||||
} else if (reg == REG_ID_RTC_DATE ||
|
||||
reg == REG_ID_RTC_ALARM_DATE ||
|
||||
reg == REG_ID_RTC_TIME ||
|
||||
reg == REG_ID_RTC_ALARM_TIME) {
|
||||
if (is_write)
|
||||
bytes_needed = 3;
|
||||
}
|
||||
|
||||
if (bytes_needed > 0)
|
||||
HAL_I2C_Slave_Sequential_Receive_IT(hi2c, i2cs_r_buff + i2cs_r_idx, bytes_needed, I2C_NEXT_FRAME); // This write the second or more received byte to i2cs_r_buff[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern void HAL_I2C_ListenCpltCallback (I2C_HandleTypeDef *hi2c) {
|
||||
if (hi2c == &hi2c1) {
|
||||
if (i2cs_state == I2CS_STATE_REG_ANSWER)
|
||||
i2cs_state = I2CS_STATE_IDLE;
|
||||
|
||||
HAL_I2C_EnableListen_IT(hi2c);
|
||||
}
|
||||
}
|
||||
|
||||
extern void HAL_I2C_ErrorCallback(I2C_HandleTypeDef *hi2c) {
|
||||
if (hi2c == &hi2c1)
|
||||
if (HAL_I2C_GetError(hi2c) != HAL_I2C_ERROR_AF)
|
||||
Error_Handler();
|
||||
// Actually this will trigger the watchdog and restart the system... That can ruin the day of the user.
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
void uart_rawdata_write(uint32_t c, size_t s, uint8_t swap) {
|
||||
uint8_t r[4];
|
||||
@ -626,7 +412,7 @@ static void key_cb(char key, enum key_state state) {
|
||||
int_trig = 1;
|
||||
}
|
||||
|
||||
if (reg_is_bit_set(REG_ID_CFG, CFG_OVERFLOW_ON)) fifo_enqueue_force(item);
|
||||
if (reg_is_bit_set(REG_ID_SYS_CFG, CFG_OVERFLOW_ON)) fifo_enqueue_force(item);
|
||||
}
|
||||
|
||||
#ifndef UART_PICO_INTERFACE
|
||||
|
@ -9,9 +9,13 @@
|
||||
extern RTC_HandleTypeDef hrtc;
|
||||
|
||||
static uint8_t regs[REG_ID_LAST] = {0};
|
||||
static uint8_t regs_unsync[REG_ID_LAST] = {0};
|
||||
static uint32_t eeprom_refresh_counter;
|
||||
|
||||
static uint32_t regs_unsync[(REG_ID_LAST / 32) + 1] = {0};
|
||||
#define REGS_UNSYNC_SET(x) regs_unsync[(x / 32)] |= (uint32_t)(1U << (x % 32))
|
||||
#define REGS_UNSYNC_UNSET(x) regs_unsync[(x / 32)] &= ~((uint32_t)(1U << (x % 32)))
|
||||
#define REGS_UNSYNC_GET(x) ((regs_unsync[(x / 32)] >> (x % 32)) & (uint32_t)0x1)
|
||||
|
||||
inline uint8_t reg_get_value(enum reg_id reg) {
|
||||
if (reg >= REG_ID_LAST)
|
||||
return 0;
|
||||
@ -28,7 +32,7 @@ inline void reg_set_value(enum reg_id reg, uint8_t value) {
|
||||
return;
|
||||
|
||||
regs[reg] = value;
|
||||
regs_unsync[reg] = 1;
|
||||
REGS_UNSYNC_SET(reg);
|
||||
eeprom_refresh_counter = uptime_ms();
|
||||
}
|
||||
|
||||
@ -44,29 +48,30 @@ inline void reg_set_bit(enum reg_id reg, uint8_t bit) {
|
||||
return;
|
||||
|
||||
regs[reg] |= bit;
|
||||
regs_unsync[reg] = 1;
|
||||
REGS_UNSYNC_SET(reg);
|
||||
eeprom_refresh_counter = uptime_ms();
|
||||
}
|
||||
|
||||
/*
|
||||
* | Bit | Name | Description |
|
||||
| ------ |:----------------:| ------------------------------------------------------------------:|
|
||||
| 7 | CFG_USE_MODS | Should Alt, Sym and the Shift keys modify the keys being reported. |
|
||||
| 6 | CFG_REPORT_MODS | Should Alt, Sym and the Shift keys be reported as well. |
|
||||
| 5 | CFG_PANIC_INT | Currently not implemented. |
|
||||
| 4 | CFG_KEY_INT | Should an interrupt be generated when a key is pressed. |
|
||||
| 3 | CFG_NUMLOCK_INT | Should an interrupt be generated when Num Lock is toggled. |
|
||||
| 2 | CFG_CAPSLOCK_INT | Should an interrupt be generated when Caps Lock is toggled. |
|
||||
| 1 | CFG_OVERFLOW_INT | Should an interrupt be generated when a FIFO overflow happens. |
|
||||
| 0 | CFG_OVERFLOW_ON | When a FIFO overflow happens, should the new entry still be pushed, overwriting the oldest one. If 0 then new entry is lost. |
|
||||
* | ------ |:----------------:| ------------------------------------------------------------------:|
|
||||
* | 7 | CFG_USE_MODS | Should Alt, Sym and the Shift keys modify the keys being reported. |
|
||||
* | 6 | CFG_REPORT_MODS | Should Alt, Sym and the Shift keys be reported as well. |
|
||||
* | 5 | CFG_PANIC_INT | Currently not implemented. |
|
||||
* | 4 | CFG_KEY_INT | Should an interrupt be generated when a key is pressed. |
|
||||
* | 3 | CFG_NUMLOCK_INT | Should an interrupt be generated when Num Lock is toggled. |
|
||||
* | 2 | CFG_CAPSLOCK_INT | Should an interrupt be generated when Caps Lock is toggled. |
|
||||
* | 1 | CFG_OVERFLOW_INT | Should an interrupt be generated when a FIFO overflow happens. |
|
||||
* | 0 | CFG_OVERFLOW_ON | When a FIFO overflow happens, should the new entry still be pushed,|
|
||||
* | | | overwriting the oldest one. If 0 then new entry is lost. |
|
||||
*/
|
||||
void reg_init(void) {
|
||||
uint32_t buff;
|
||||
|
||||
regs[REG_ID_VER] = (uint8_t)((VERSION_MAJOR << 4) | VERSION_MINOR); // 1.2 => (0x1 << 4) | 0x2
|
||||
|
||||
EEPROM_ReadVariable(EEPROM_VAR_CFG, (EEPROM_Value*)&buff);
|
||||
regs[REG_ID_CFG] = (uint8_t)((buff >> 8) & 0xFF);
|
||||
EEPROM_ReadVariable(REG_ID_SYS_CFG, (EEPROM_Value*)&buff);
|
||||
regs[REG_ID_SYS_CFG] = (uint8_t)((buff >> 8) & 0xFF);
|
||||
regs[REG_ID_INT_CFG] = (uint8_t)(buff & 0xFF);
|
||||
|
||||
EEPROM_ReadVariable(EEPROM_VAR_KBD, (EEPROM_Value*)&buff);
|
||||
@ -100,33 +105,33 @@ uint32_t reg_check_and_save_eeprom(void) {
|
||||
uint8_t need_save = 0;
|
||||
|
||||
for (size_t i = 0; i < REG_ID_LAST; i++)
|
||||
if (regs_unsync[i] == 1) {
|
||||
if (REGS_UNSYNC_GET(i) == 1) {
|
||||
need_save = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (need_save == 1) {
|
||||
if (regs_unsync[REG_ID_CFG] == 1)
|
||||
result |= EEPROM_WriteVariable(EEPROM_VAR_CFG, (EEPROM_Value)(uint16_t)((regs[REG_ID_CFG] << 8) | regs[REG_ID_INT_CFG]), EEPROM_SIZE16);
|
||||
if (REGS_UNSYNC_GET(REG_ID_SYS_CFG) == 1)
|
||||
result |= EEPROM_WriteVariable(EEPROM_VAR_CFG, (EEPROM_Value)(uint16_t)((regs[REG_ID_SYS_CFG] << 8) | regs[REG_ID_INT_CFG]), EEPROM_SIZE16);
|
||||
|
||||
if (regs_unsync[REG_ID_DEB] == 1 || regs_unsync[REG_ID_FRQ] == 1)
|
||||
if (REGS_UNSYNC_GET(REG_ID_DEB) == 1 || REGS_UNSYNC_GET(REG_ID_FRQ) == 1)
|
||||
result |= EEPROM_WriteVariable(EEPROM_VAR_KBD, (EEPROM_Value)(uint32_t)((keyboard_get_hold_period() << 16) | regs[REG_ID_FRQ]), EEPROM_SIZE32);
|
||||
|
||||
if (regs_unsync[REG_ID_BKL] == 1 || regs_unsync[REG_ID_BK2] == 1)
|
||||
if (REGS_UNSYNC_GET(REG_ID_BKL) == 1 || REGS_UNSYNC_GET(REG_ID_BK2) == 1)
|
||||
result |= EEPROM_WriteVariable(EEPROM_VAR_BCKL, (EEPROM_Value)(uint16_t)((regs[REG_ID_BKL] << 8) | regs[REG_ID_BK2]), EEPROM_SIZE16);
|
||||
|
||||
if (regs_unsync[REG_ID_RTC_ALARM_TIME] == 1 || regs_unsync[REG_ID_RTC_CFG] == 1) {
|
||||
if (REGS_UNSYNC_GET(REG_ID_RTC_ALARM_TIME) == 1 || REGS_UNSYNC_GET(REG_ID_RTC_CFG) == 1) {
|
||||
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR2, ((rtc_alarm_time.raw & 0xFF) << 8) | regs[REG_ID_RTC_CFG]);
|
||||
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR3, rtc_alarm_time.raw >> 16);
|
||||
}
|
||||
|
||||
if (regs_unsync[REG_ID_RTC_ALARM_DATE] == 1) {
|
||||
if (REGS_UNSYNC_GET(REG_ID_RTC_ALARM_DATE) == 1) {
|
||||
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR4, rtc_alarm_date.raw & 0xFFFF);
|
||||
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR5, rtc_alarm_date.raw >> 16);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < REG_ID_LAST; i++)
|
||||
regs_unsync[i] = 0;
|
||||
REGS_UNSYNC_UNSET(i);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
18
README.md
18
README.md
@ -9,11 +9,12 @@ The main differences with the original firmware are the followings:
|
||||
- drastic reduction in the STM32's electricity consumption when running (~3.5 mA),
|
||||
- clean up (by removing stm32duino dependencies, use STM32HAL instead, maybe I'll switch to libopencm3 someday...) to reduce binary size (~25 KB) and allow more features to be implemented,
|
||||
- added configuration saving solution (using internal flash, including backlight option),
|
||||
- new I2C registers and structure (keeping compatible access to REG_ID_TYP and REG_ID_VER registers to check how to handle comm from pico board side),
|
||||
- new I2C registers memory allocation and structure (keeping compatible access to REG_ID_TYP and REG_ID_VER registers to check how to handle comm from pico board side)(WIP),
|
||||
- interrupt event output to pico board can be configured during runtime (for keyboard event or RTC alarm),
|
||||
- rewriten or added some debug UART interface message,
|
||||
- internal RTC access through dedicated I2C registers (WIP),
|
||||
- lighten AXP2101 PMIC driver.
|
||||
- rewriten or added some debug UART interface message (only when compiled in DEBUG release type),
|
||||
- internal RTC access through dedicated I2C registers,
|
||||
- auto wake-up using RTC (WIP),
|
||||
- lighten AXP2101 PMIC driver (based on X-PowersLib).
|
||||
|
||||
## Compile
|
||||
This source code can be compiled using ARM gcc toolchain (using v13) in path and using make program.
|
||||
@ -23,9 +24,18 @@ But this makes them incompatible with official firmware. (More details to come a
|
||||
|
||||
If you plan using pico official firmware (PicoMite, etc.), you should set I2C_REGS_COMPAT = 1 in the Makefile.
|
||||
|
||||
## TODO
|
||||
- Registers memory structure/allocation rewrite
|
||||
- Auto wake-up
|
||||
- IRQ to Pico management (register exist but does nothing)
|
||||
- Add a Pico test program for registers/features implemented
|
||||
- add few wiki page to detail the I2C protocol, added features, etc.
|
||||
|
||||
## Important notes
|
||||
The current implementation of this firmware is subject to change until the v1 release.
|
||||
|
||||
Some features can be unstable or buggy and are marked as pre-release. A test program will be developped soon to provide some regression testing.
|
||||
|
||||
The permanent settings (EEPROM) save can be broken between 0.x version, it is recommanded to make a full flash erase before updating, as
|
||||
EEPROM configuration survives update.
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user