mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-08 12:37:01 +03:00
add SPI ethernet
This commit is contained in:
@@ -25,10 +25,10 @@
|
|||||||
#define CONFIG_AIRPLAY_NAME "ESP32-AirPlay"
|
#define CONFIG_AIRPLAY_NAME "ESP32-AirPlay"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef struct {
|
static EXT_RAM_ATTR struct raop_cb_s {
|
||||||
raop_cmd_vcb_t cmd;
|
raop_cmd_vcb_t cmd;
|
||||||
raop_data_cb_t data;
|
raop_data_cb_t data;
|
||||||
} raop_cb_t;
|
} raop_cbs;
|
||||||
|
|
||||||
log_level raop_loglevel = lINFO;
|
log_level raop_loglevel = lINFO;
|
||||||
log_level util_loglevel;
|
log_level util_loglevel;
|
||||||
@@ -204,10 +204,8 @@ static bool raop_sink_start(raop_cmd_vcb_t cmd_cb, raop_data_cb_t data_cb) {
|
|||||||
* Airplay sink timer handler
|
* Airplay sink timer handler
|
||||||
*/
|
*/
|
||||||
static void raop_start_handler( TimerHandle_t xTimer ) {
|
static void raop_start_handler( TimerHandle_t xTimer ) {
|
||||||
raop_cb_t *cbs = (raop_cb_t*) pvTimerGetTimerID (xTimer);
|
if (raop_sink_start(raop_cbs.cmd, raop_cbs.data)) {
|
||||||
if (raop_sink_start(cbs->cmd, cbs->data)) {
|
|
||||||
xTimerDelete(xTimer, portMAX_DELAY);
|
xTimerDelete(xTimer, portMAX_DELAY);
|
||||||
free(cbs);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,10 +214,9 @@ static void raop_start_handler( TimerHandle_t xTimer ) {
|
|||||||
*/
|
*/
|
||||||
void raop_sink_init(raop_cmd_vcb_t cmd_cb, raop_data_cb_t data_cb) {
|
void raop_sink_init(raop_cmd_vcb_t cmd_cb, raop_data_cb_t data_cb) {
|
||||||
if (!raop_sink_start(cmd_cb, data_cb)) {
|
if (!raop_sink_start(cmd_cb, data_cb)) {
|
||||||
raop_cb_t *cbs = (raop_cb_t*) malloc(sizeof(raop_cb_t));
|
raop_cbs.cmd = cmd_cb;
|
||||||
cbs->cmd = cmd_cb;
|
raop_cbs.data = data_cb;
|
||||||
cbs->data = data_cb;
|
TimerHandle_t timer = xTimerCreate("raopStart", 5000 / portTICK_RATE_MS, pdTRUE, NULL, raop_start_handler);
|
||||||
TimerHandle_t timer = xTimerCreate("raopStart", 1000 / portTICK_RATE_MS, pdTRUE, cbs, raop_start_handler);
|
|
||||||
xTimerStart(timer, portMAX_DELAY);
|
xTimerStart(timer, portMAX_DELAY);
|
||||||
LOG_INFO( "delaying AirPlay start");
|
LOG_INFO( "delaying AirPlay start");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ static void set_i2s_pin(char *config, i2s_pin_config_t *pin_config) {
|
|||||||
* Get i2s config structure from config string
|
* Get i2s config structure from config string
|
||||||
*/
|
*/
|
||||||
const i2s_platform_config_t * config_get_i2s_from_str(char * dac_config ){
|
const i2s_platform_config_t * config_get_i2s_from_str(char * dac_config ){
|
||||||
static i2s_platform_config_t i2s_dac_pin = {
|
static EXT_RAM_ATTR i2s_platform_config_t i2s_dac_pin = {
|
||||||
.i2c_addr = -1,
|
.i2c_addr = -1,
|
||||||
.sda= -1,
|
.sda= -1,
|
||||||
.scl = -1,
|
.scl = -1,
|
||||||
@@ -146,16 +146,26 @@ const i2s_platform_config_t * config_get_i2s_from_str(char * dac_config ){
|
|||||||
* Get eth config structure from config string
|
* Get eth config structure from config string
|
||||||
*/
|
*/
|
||||||
const eth_config_t * config_get_eth_from_str(char * eth_config ){
|
const eth_config_t * config_get_eth_from_str(char * eth_config ){
|
||||||
static eth_config_t eth_pin = {
|
static EXT_RAM_ATTR eth_config_t eth_pin = {
|
||||||
.mdc = -1,
|
.rmii = false,
|
||||||
.mdio = -1,
|
.model = "",
|
||||||
.rst = -1,
|
|
||||||
};
|
};
|
||||||
char * p=NULL;
|
char * p=NULL;
|
||||||
|
|
||||||
|
if ((p = strcasestr(eth_config, "model")) != NULL) sscanf(p, "%*[^=]=%15[^,]", eth_pin.model);
|
||||||
if ((p = strcasestr(eth_config, "mdc")) != NULL) eth_pin.mdc = atoi(strchr(p, '=') + 1);
|
if ((p = strcasestr(eth_config, "mdc")) != NULL) eth_pin.mdc = atoi(strchr(p, '=') + 1);
|
||||||
if ((p = strcasestr(eth_config, "mdio")) != NULL) eth_pin.mdio = atoi(strchr(p, '=') + 1);
|
if ((p = strcasestr(eth_config, "mdio")) != NULL) eth_pin.mdio = atoi(strchr(p, '=') + 1);
|
||||||
if ((p = strcasestr(eth_config, "rst")) != NULL) eth_pin.rst = atoi(strchr(p, '=') + 1);
|
if ((p = strcasestr(eth_config, "rst")) != NULL) eth_pin.rst = atoi(strchr(p, '=') + 1);
|
||||||
|
if ((p = strcasestr(eth_config, "mosi")) != NULL) eth_pin.mosi = atoi(strchr(p, '=') + 1);
|
||||||
|
if ((p = strcasestr(eth_config, "miso")) != NULL) eth_pin.miso = atoi(strchr(p, '=') + 1);
|
||||||
|
if ((p = strcasestr(eth_config, "intr")) != NULL) eth_pin.intr = atoi(strchr(p, '=') + 1);
|
||||||
|
if ((p = strcasestr(eth_config, "cs")) != NULL) eth_pin.cs = atoi(strchr(p, '=') + 1);
|
||||||
|
if ((p = strcasestr(eth_config, "speed")) != NULL) eth_pin.speed = atoi(strchr(p, '=') + 1);
|
||||||
|
if ((p = strcasestr(eth_config, "clk")) != NULL) eth_pin.clk = atoi(strchr(p, '=') + 1);
|
||||||
|
if ((p = strcasestr(eth_config, "host")) != NULL) eth_pin.host = atoi(strchr(p, '=') + 1);
|
||||||
|
|
||||||
|
if (strcasestr(eth_pin.model, "lan8720")) eth_pin.rmii = true;
|
||||||
|
|
||||||
return ð_pin;
|
return ð_pin;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,7 +174,7 @@ const eth_config_t * config_get_eth_from_str(char * eth_config ){
|
|||||||
*/
|
*/
|
||||||
const i2s_platform_config_t * config_spdif_get( ){
|
const i2s_platform_config_t * config_spdif_get( ){
|
||||||
char * spdif_config = config_spdif_get_string();
|
char * spdif_config = config_spdif_get_string();
|
||||||
static i2s_platform_config_t i2s_dac_config;
|
static EXT_RAM_ATTR i2s_platform_config_t i2s_dac_config;
|
||||||
memcpy(&i2s_dac_config, config_get_i2s_from_str(spdif_config), sizeof(i2s_dac_config));
|
memcpy(&i2s_dac_config, config_get_i2s_from_str(spdif_config), sizeof(i2s_dac_config));
|
||||||
free(spdif_config);
|
free(spdif_config);
|
||||||
return &i2s_dac_config;
|
return &i2s_dac_config;
|
||||||
@@ -175,7 +185,7 @@ const i2s_platform_config_t * config_spdif_get( ){
|
|||||||
*/
|
*/
|
||||||
const i2s_platform_config_t * config_dac_get(){
|
const i2s_platform_config_t * config_dac_get(){
|
||||||
char * spdif_config = get_dac_config_string();
|
char * spdif_config = get_dac_config_string();
|
||||||
static i2s_platform_config_t i2s_dac_config;
|
static EXT_RAM_ATTR i2s_platform_config_t i2s_dac_config;
|
||||||
memcpy(&i2s_dac_config, config_get_i2s_from_str(spdif_config), sizeof(i2s_dac_config));
|
memcpy(&i2s_dac_config, config_get_i2s_from_str(spdif_config), sizeof(i2s_dac_config));
|
||||||
free(spdif_config);
|
free(spdif_config);
|
||||||
return &i2s_dac_config;
|
return &i2s_dac_config;
|
||||||
@@ -185,9 +195,19 @@ const i2s_platform_config_t * config_dac_get(){
|
|||||||
* Get ethernet config structure
|
* Get ethernet config structure
|
||||||
*/
|
*/
|
||||||
const eth_config_t * config_eth_get( ){
|
const eth_config_t * config_eth_get( ){
|
||||||
char * config = config_alloc_get_str("eth_config", CONFIG_ETH_CONFIG, "mdc=" STR(CONFIG_MDC_IO)
|
char * config = config_alloc_get_str("eth_config", CONFIG_ETH_CONFIG, "rst=" STR(CONFIG_ETH_PHY_RST_IO)
|
||||||
",mdio=" STR(CONFIG_MDIO_IO) ",do=" STR(CONFIG_PHY_RST_IO));
|
#if defined(CONFIG_ETH_LAN8720)
|
||||||
static eth_config_t eth_config;
|
",model=lan8720"
|
||||||
|
#elif defined(CONFIG_ETH_DM9051)
|
||||||
|
",model=dm9051"
|
||||||
|
#endif
|
||||||
|
",mdc=" STR(CONFIG_ETH_MDC_IO) ",mdio=" STR(CONFIG_ETH_MDIO_IO)
|
||||||
|
",host=" STR(CONFIG_ETH_SPI_HOST) ",cs=" STR(CONFIG_ETH_SPI_CS_IO)
|
||||||
|
",mosi=" STR(CONFIG_ETH_SPI_MOSI_IO) ",miso=" STR(CONFIG_ETH_SPI_MISO_IO)
|
||||||
|
",intr=" STR(CONFIG_ETH_SPI_INTR_IO)
|
||||||
|
",clk=" STR(CONFIG_ETH_SPI_CLK_IO) ",speed=" STR(CONFIG_ETH_SPI_SPEED) );
|
||||||
|
static EXT_RAM_ATTR eth_config_t eth_config;
|
||||||
|
ESP_LOGD(TAG, "Ethernet config string %s", config);
|
||||||
memcpy(ð_config, config_get_eth_from_str(config), sizeof(eth_config));
|
memcpy(ð_config, config_get_eth_from_str(config), sizeof(eth_config));
|
||||||
free(config);
|
free(config);
|
||||||
return ð_config;
|
return ð_config;
|
||||||
|
|||||||
@@ -31,9 +31,13 @@ typedef struct {
|
|||||||
} display_config_t;
|
} display_config_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int mdc;
|
bool rmii;
|
||||||
int mdio;
|
char model[16];
|
||||||
int rst;
|
int rst;
|
||||||
|
int mdc, mdio;
|
||||||
|
int host;
|
||||||
|
int cs, mosi, miso, intr, clk;
|
||||||
|
int speed;
|
||||||
} eth_config_t;
|
} eth_config_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|||||||
@@ -295,6 +295,94 @@ static void eth_event_handler(void *arg, esp_event_base_t event_base,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void eth_init(void) {
|
||||||
|
esp_eth_mac_t *mac;
|
||||||
|
esp_eth_phy_t *phy;
|
||||||
|
esp_err_t err = ESP_OK;
|
||||||
|
eth_config_t const *eth = config_eth_get( );
|
||||||
|
|
||||||
|
// quick check if we have a valid ethernet configuration
|
||||||
|
if ((eth->mdc == -1 && eth->mosi == -1) || !*eth->model) {
|
||||||
|
ESP_LOGI(TAG, "No ethernet");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tcpip_adapter_set_default_eth_handlers();
|
||||||
|
esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, ð_event_handler, NULL);
|
||||||
|
|
||||||
|
eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
|
||||||
|
eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
|
||||||
|
phy_config.phy_addr = 1;
|
||||||
|
phy_config.reset_gpio_num = eth->rst;
|
||||||
|
|
||||||
|
if (eth->rmii) {
|
||||||
|
#ifdef CONFIG_ETH_USE_ESP32_EMAC
|
||||||
|
mac_config.smi_mdc_gpio_num = eth->mdc;
|
||||||
|
mac_config.smi_mdio_gpio_num = eth->mdio;
|
||||||
|
mac = esp_eth_mac_new_esp32(&mac_config);
|
||||||
|
phy = esp_eth_phy_new_lan8720(&phy_config);
|
||||||
|
ESP_LOGI(TAG, "Adding ethernet RMII with mdc %d and mdio %d", eth->mdc, eth->mdio);
|
||||||
|
#else
|
||||||
|
ESP_LOGE(TAG, "Ethernet RMII set but not included in compilation");
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
} else {
|
||||||
|
#ifdef CONFIG_ETH_SPI_ETHERNET_DM9051
|
||||||
|
spi_device_handle_t spi_handle = NULL;
|
||||||
|
spi_host_device_t host = SPI3_HOST;
|
||||||
|
|
||||||
|
if (eth->host != -1) {
|
||||||
|
// don't use system's shared SPI
|
||||||
|
spi_bus_config_t buscfg = {
|
||||||
|
.miso_io_num = eth->miso,
|
||||||
|
.mosi_io_num = eth->mosi,
|
||||||
|
.sclk_io_num = eth->clk,
|
||||||
|
.quadwp_io_num = -1,
|
||||||
|
.quadhd_io_num = -1,
|
||||||
|
};
|
||||||
|
|
||||||
|
// can't use SPI0
|
||||||
|
if (eth->host == 1) host = SPI2_HOST;
|
||||||
|
err |= spi_bus_initialize(host, &buscfg, 1);
|
||||||
|
} else {
|
||||||
|
// when we use shared SPI, we assume it has been initialized
|
||||||
|
host = spi_system_host;
|
||||||
|
}
|
||||||
|
|
||||||
|
spi_device_interface_config_t devcfg = {
|
||||||
|
.command_bits = 1,
|
||||||
|
.address_bits = 7,
|
||||||
|
.mode = 0,
|
||||||
|
.clock_speed_hz = eth->speed,
|
||||||
|
.spics_io_num = eth->cs,
|
||||||
|
.queue_size = 20
|
||||||
|
};
|
||||||
|
|
||||||
|
err |= spi_bus_add_device(host, &devcfg, &spi_handle);
|
||||||
|
|
||||||
|
// dm9051 ethernet driver is based on spi driver
|
||||||
|
eth_dm9051_config_t dm9051_config = ETH_DM9051_DEFAULT_CONFIG(spi_handle);
|
||||||
|
// we assume that isr has been installed already
|
||||||
|
dm9051_config.int_gpio_num = eth->intr;
|
||||||
|
mac = esp_eth_mac_new_dm9051(&dm9051_config, &mac_config);
|
||||||
|
phy = esp_eth_phy_new_dm9051(&phy_config);
|
||||||
|
ESP_LOGI(TAG, "Adding ethernet SPI on host %d with mosi %d and miso %d", host, eth->mosi, eth->miso);
|
||||||
|
#else
|
||||||
|
ESP_LOGE(TAG, "Ethernet SPI set but not included in compilation");
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_eth_config_t config = ETH_DEFAULT_CONFIG(mac, phy);
|
||||||
|
esp_eth_handle_t eth_handle = NULL;
|
||||||
|
err |= esp_eth_driver_install(&config, ð_handle);
|
||||||
|
err |= esp_eth_start(eth_handle);
|
||||||
|
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Can't install Ethernet driver %d", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void wifi_manager_init_wifi(){
|
void wifi_manager_init_wifi(){
|
||||||
/* event handler and event group for the wifi driver */
|
/* event handler and event group for the wifi driver */
|
||||||
ESP_LOGD(TAG, "Initializing wifi. Creating event group");
|
ESP_LOGD(TAG, "Initializing wifi. Creating event group");
|
||||||
@@ -317,31 +405,7 @@ void wifi_manager_init_wifi(){
|
|||||||
ESP_LOGD(TAG, "Initializing wifi. Starting wifi");
|
ESP_LOGD(TAG, "Initializing wifi. Starting wifi");
|
||||||
ESP_ERROR_CHECK( esp_wifi_start() );
|
ESP_ERROR_CHECK( esp_wifi_start() );
|
||||||
|
|
||||||
//ETH
|
eth_init();
|
||||||
|
|
||||||
eth_config_t const *eth = config_eth_get( );
|
|
||||||
ESP_LOGE(TAG, "ETH MDC %d", eth->mdc);
|
|
||||||
ESP_LOGE(TAG, "ETH MDIO %d", eth->mdio);
|
|
||||||
ESP_LOGE(TAG, "ETH RST %d", eth->rst);
|
|
||||||
|
|
||||||
ESP_ERROR_CHECK(tcpip_adapter_set_default_eth_handlers());
|
|
||||||
ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, ð_event_handler, NULL));
|
|
||||||
|
|
||||||
eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
|
|
||||||
eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
|
|
||||||
phy_config.phy_addr = 1;
|
|
||||||
phy_config.reset_gpio_num = eth->rst;
|
|
||||||
|
|
||||||
mac_config.smi_mdc_gpio_num = eth->mdc;
|
|
||||||
mac_config.smi_mdio_gpio_num = eth->mdio;
|
|
||||||
esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&mac_config);
|
|
||||||
esp_eth_phy_t *phy = esp_eth_phy_new_lan8720(&phy_config);
|
|
||||||
|
|
||||||
esp_eth_config_t config = ETH_DEFAULT_CONFIG(mac, phy);
|
|
||||||
esp_eth_handle_t eth_handle = NULL;
|
|
||||||
ESP_ERROR_CHECK(esp_eth_driver_install(&config, ð_handle));
|
|
||||||
ESP_ERROR_CHECK(esp_eth_start(eth_handle));
|
|
||||||
//END_ETH
|
|
||||||
|
|
||||||
taskYIELD();
|
taskYIELD();
|
||||||
ESP_LOGD(TAG, "Initializing wifi. done");
|
ESP_LOGD(TAG, "Initializing wifi. done");
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ menu "Squeezelite-ESP32"
|
|||||||
default ""
|
default ""
|
||||||
config ETH_CONFIG
|
config ETH_CONFIG
|
||||||
string
|
string
|
||||||
default ""
|
default ""
|
||||||
config DAC_CONTROLSET
|
config DAC_CONTROLSET
|
||||||
string
|
string
|
||||||
default "{ \"init\": [ {\"reg\":41, \"val\":128}, {\"reg\":18, \"val\":255} ], \"poweron\": [ {\"reg\":18, \"val\":64, \"mode\":\"or\"} ], \"poweroff\": [ {\"reg\":18, \"val\":191, \"mode\":\"and\"} ] }" if TWATCH2020
|
default "{ \"init\": [ {\"reg\":41, \"val\":128}, {\"reg\":18, \"val\":255} ], \"poweron\": [ {\"reg\":18, \"val\":64, \"mode\":\"or\"} ], \"poweroff\": [ {\"reg\":18, \"val\":191, \"mode\":\"and\"} ] }" if TWATCH2020
|
||||||
@@ -107,23 +107,57 @@ menu "Squeezelite-ESP32"
|
|||||||
endmenu
|
endmenu
|
||||||
|
|
||||||
menu "Ethernet Options"
|
menu "Ethernet Options"
|
||||||
visible if BASIC_I2C_BT && ETH_USE_ESP32_EMAC
|
visible if BASIC_I2C_BT && (ETH_USE_ESP32_EMAC || ETH_USE_SPI_ETHERNET)
|
||||||
config ETH_MDC_IO
|
choice
|
||||||
int "SMI MDC GPIO number"
|
prompt "Ethernet Chipset"
|
||||||
default 23
|
default ETH_NODRIVER
|
||||||
help
|
config ETH_NODRIVER
|
||||||
Set the GPIO number used by SMI MDC.
|
bool "Defined in NVS"
|
||||||
config ETH_MDIO_IO
|
config ETH_LAN8720
|
||||||
int "SMI MDIO GPIO number"
|
bool "Microchip LAN8720 (RMII)"
|
||||||
default 18
|
config ETH_DM9051
|
||||||
help
|
bool "Davicom 9051 (SPI)"
|
||||||
Set the GPIO number used by SMI MDIO.
|
endchoice
|
||||||
config ETH_PHY_RST_IO
|
config ETH_PHY_RST_IO
|
||||||
int "PHY Reset GPIO number"
|
int "PHY Reset GPIO number" if !ETH_NODRIVER
|
||||||
default 4
|
default -1
|
||||||
help
|
help
|
||||||
Set the GPIO number used to reset PHY chip.
|
Set the GPIO number used to reset PHY chip.
|
||||||
Set to -1 to disable PHY chip hardware reset.
|
Set to -1 to disable PHY chip hardware reset.
|
||||||
|
config ETH_MDC_IO
|
||||||
|
int "SMI MDC GPIO number" if ETH_LAN8720
|
||||||
|
default -1
|
||||||
|
help
|
||||||
|
Set the GPIO number used by SMI MDC.
|
||||||
|
config ETH_MDIO_IO
|
||||||
|
int "SMI MDIO GPIO number" if ETH_LAN8720
|
||||||
|
default -1
|
||||||
|
help
|
||||||
|
Set the GPIO number used by SMI MDIO.
|
||||||
|
config ETH_SPI_HOST
|
||||||
|
int "SPI host number (-1,1 or 2)" if ETH_DM9051
|
||||||
|
default -1
|
||||||
|
help
|
||||||
|
Set to -1 to use system's SPI config (see Various I/O)
|
||||||
|
Set to 2 or 3 to use a dedicated bus
|
||||||
|
config ETH_SPI_INTR_IO
|
||||||
|
int "interrupt" if ETH_DM9051
|
||||||
|
default -1
|
||||||
|
config ETH_SPI_CS_IO
|
||||||
|
int "Chip Select" if ETH_DM9051
|
||||||
|
default -1
|
||||||
|
config ETH_SPI_CLK_IO
|
||||||
|
int "SPI clock" if ETH_SPI_HOST != -1 && ETH_DM9051
|
||||||
|
default -1
|
||||||
|
config ETH_SPI_MOSI_IO
|
||||||
|
int "Data Out" if ETH_SPI_HOST != -1 && ETH_DM9051
|
||||||
|
default -1
|
||||||
|
config ETH_SPI_MISO_IO
|
||||||
|
int "Data In" if ETH_SPI_HOST != -1 && ETH_DM9051
|
||||||
|
default -1
|
||||||
|
config ETH_SPI_SPEED
|
||||||
|
int "SPI speed (Hz)" if ETH_SPI_HOST != -1 && ETH_DM9051
|
||||||
|
default 20000000
|
||||||
endmenu
|
endmenu
|
||||||
|
|
||||||
menu "Audio settings"
|
menu "Audio settings"
|
||||||
|
|||||||
Reference in New Issue
Block a user