mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-08-18 16:05:05 +00:00
Fixes
This commit is contained in:
parent
4909b7075b
commit
fb6f1c31d2
@ -45,8 +45,8 @@
|
||||
compatible = "espressif,esp32-spi";
|
||||
host = <SPI2_HOST>;
|
||||
cs-gpios = <&gpio0 12 GPIO_FLAG_NONE>, // Display
|
||||
<&gpio0 9 GPIO_FLAG_NONE>, // Radio
|
||||
<&gpio0 39 GPIO_FLAG_NONE>; // SD card
|
||||
<&gpio0 39 GPIO_FLAG_NONE>, // SD card
|
||||
<&gpio0 9 GPIO_FLAG_NONE>; // Radio
|
||||
pin-mosi = <&gpio0 41 GPIO_FLAG_NONE>;
|
||||
pin-miso = <&gpio0 38 GPIO_FLAG_NONE>;
|
||||
pin-sclk = <&gpio0 40 GPIO_FLAG_NONE>;
|
||||
@ -55,8 +55,9 @@
|
||||
compatible = "display-placeholder";
|
||||
};
|
||||
|
||||
sdcard@2 {
|
||||
sdcard@1 {
|
||||
compatible = "espressif,esp32-sdspi";
|
||||
status = "disabled"; // Must be started after display
|
||||
frequency-khz = <20000>;
|
||||
};
|
||||
};
|
||||
|
||||
@ -35,20 +35,21 @@
|
||||
spi0 {
|
||||
compatible = "espressif,esp32-spi";
|
||||
host = <SPI2_HOST>;
|
||||
cs-gpios = <&gpio0 21 GPIO_FLAG_NONE>, // SD card
|
||||
<&gpio0 36 GPIO_FLAG_NONE>; // Display
|
||||
cs-gpios = <&gpio0 36 GPIO_FLAG_NONE>, // Display
|
||||
<&gpio0 21 GPIO_FLAG_NONE>; // SD card
|
||||
pin-mosi = <&gpio0 34 GPIO_FLAG_NONE>;
|
||||
pin-miso = <&gpio0 33 GPIO_FLAG_NONE>;
|
||||
pin-sclk = <&gpio0 35 GPIO_FLAG_NONE>;
|
||||
|
||||
sdcard@0 {
|
||||
compatible = "espressif,esp32-sdspi";
|
||||
frequency-khz = <20000>;
|
||||
};
|
||||
|
||||
display@1 {
|
||||
|
||||
display@0 {
|
||||
compatible = "display-placeholder";
|
||||
};
|
||||
|
||||
sdcard@1 {
|
||||
compatible = "espressif,esp32-sdspi";
|
||||
status = "disabled"; // Must be started after display
|
||||
frequency-khz = <20000>;
|
||||
};
|
||||
};
|
||||
|
||||
// ES8311
|
||||
|
||||
@ -53,20 +53,21 @@
|
||||
spi0 {
|
||||
compatible = "espressif,esp32-spi";
|
||||
host = <SPI2_HOST>;
|
||||
cs-gpios = <&gpio0 4 GPIO_FLAG_NONE>, // SD card
|
||||
<&gpio0 5 GPIO_FLAG_NONE>; // Display
|
||||
cs-gpios = <&gpio0 5 GPIO_FLAG_NONE>, // Display
|
||||
<&gpio0 4 GPIO_FLAG_NONE>; // SD card
|
||||
pin-mosi = <&gpio0 23 GPIO_FLAG_NONE>;
|
||||
pin-miso = <&gpio0 38 GPIO_FLAG_NONE>;
|
||||
pin-sclk = <&gpio0 18 GPIO_FLAG_NONE>;
|
||||
|
||||
sdcard@0 {
|
||||
compatible = "espressif,esp32-sdspi";
|
||||
frequency-khz = <20000>;
|
||||
};
|
||||
|
||||
display@1 {
|
||||
display@0 {
|
||||
compatible = "display-placeholder";
|
||||
};
|
||||
|
||||
sdcard@1 {
|
||||
compatible = "espressif,esp32-sdspi";
|
||||
status = "disabled"; // Must be started after display
|
||||
frequency-khz = <20000>;
|
||||
};
|
||||
};
|
||||
|
||||
// NS4168: Speaker and microphone
|
||||
|
||||
@ -78,20 +78,21 @@
|
||||
spi0 {
|
||||
compatible = "espressif,esp32-spi";
|
||||
host = <SPI2_HOST>;
|
||||
cs-gpios = <&gpio0 4 GPIO_FLAG_NONE>, // SD card
|
||||
<&gpio0 3 GPIO_FLAG_NONE>; // Display
|
||||
cs-gpios = <&gpio0 3 GPIO_FLAG_NONE>, // Display
|
||||
<&gpio0 4 GPIO_FLAG_NONE>; // SD card
|
||||
pin-mosi = <&gpio0 37 GPIO_FLAG_NONE>;
|
||||
pin-miso = <&gpio0 35 GPIO_FLAG_NONE>;
|
||||
pin-sclk = <&gpio0 36 GPIO_FLAG_NONE>;
|
||||
|
||||
sdcard@0 {
|
||||
compatible = "espressif,esp32-sdspi";
|
||||
frequency-khz = <20000>;
|
||||
};
|
||||
|
||||
display@1 {
|
||||
|
||||
display@0 {
|
||||
compatible = "display-placeholder";
|
||||
};
|
||||
|
||||
sdcard@1 {
|
||||
compatible = "espressif,esp32-sdspi";
|
||||
status = "disabled"; // Must be started after display
|
||||
frequency-khz = <20000>;
|
||||
};
|
||||
};
|
||||
|
||||
// TODO: Enable speaker via ES7210 I2C: https://github.com/m5stack/M5Unified/blob/a6256725481f1bc366655fa48cf03b6095e30ad1/src/M5Unified.cpp#L417
|
||||
|
||||
@ -112,19 +112,20 @@
|
||||
spi0 {
|
||||
compatible = "espressif,esp32-spi";
|
||||
host = <SPI2_HOST>;
|
||||
cs-gpios = <&gpio0 4 GPIO_FLAG_NONE>, // SD card
|
||||
<&gpio0 3 GPIO_FLAG_NONE>; // Display
|
||||
cs-gpios = <&gpio0 3 GPIO_FLAG_NONE>, // Display
|
||||
<&gpio0 4 GPIO_FLAG_NONE>; // SD card
|
||||
pin-mosi = <&gpio0 37 GPIO_FLAG_NONE>;
|
||||
pin-miso = <&gpio0 35 GPIO_FLAG_NONE>;
|
||||
pin-sclk = <&gpio0 36 GPIO_FLAG_NONE>;
|
||||
|
||||
sdcard@0 {
|
||||
compatible = "espressif,esp32-sdspi";
|
||||
frequency-khz = <20000>;
|
||||
display@0 {
|
||||
compatible = "display-placeholder";
|
||||
};
|
||||
|
||||
display@1 {
|
||||
compatible = "display-placeholder";
|
||||
sdcard@1 {
|
||||
compatible = "espressif,esp32-sdspi";
|
||||
status = "disabled"; // Must be started after display
|
||||
frequency-khz = <20000>;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
#include "hal/SdlDisplay.h"
|
||||
#include "hal/SdlKeyboard.h"
|
||||
#include "hal/SimulatorPower.h"
|
||||
#include "hal/SimulatorSdCard.h"
|
||||
|
||||
#include <src/lv_init.h> // LVGL
|
||||
#include <Tactility/hal/Configuration.h>
|
||||
|
||||
#define TAG "hardware"
|
||||
@ -15,7 +13,6 @@ static std::vector<std::shared_ptr<tt::hal::Device>> createDevices() {
|
||||
std::make_shared<SdlDisplay>(),
|
||||
std::make_shared<SdlKeyboard>(),
|
||||
std::make_shared<SimulatorPower>(),
|
||||
std::make_shared<SimulatorSdCard>()
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,42 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "Tactility/hal/sdcard/SdCardDevice.h"
|
||||
#include <Tactility/RecursiveMutex.h>
|
||||
#include <memory>
|
||||
|
||||
using tt::hal::sdcard::SdCardDevice;
|
||||
|
||||
class SimulatorSdCard final : public SdCardDevice {
|
||||
|
||||
State state;
|
||||
std::shared_ptr<tt::Lock> lock;
|
||||
std::string mountPath;
|
||||
|
||||
public:
|
||||
|
||||
SimulatorSdCard() : SdCardDevice(MountBehaviour::AtBoot),
|
||||
state(State::Unmounted),
|
||||
lock(std::make_shared<tt::RecursiveMutex>())
|
||||
{}
|
||||
|
||||
std::string getName() const override { return "Mock SD Card"; }
|
||||
std::string getDescription() const override { return ""; }
|
||||
|
||||
bool mount(const std::string& newMountPath) override {
|
||||
state = State::Mounted;
|
||||
mountPath = newMountPath;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool unmount() override {
|
||||
state = State::Unmounted;
|
||||
mountPath = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string getMountPath() const override { return mountPath; }
|
||||
|
||||
std::shared_ptr<tt::Lock> getLock() const override { return lock; }
|
||||
|
||||
State getState(TickType_t timeout) const override { return state; }
|
||||
};
|
||||
@ -10,6 +10,7 @@
|
||||
#include <tactility/drivers/esp32_sdmmc.h>
|
||||
#include <tactility/drivers/esp32_sdmmc_fs.h>
|
||||
#include <tactility/drivers/gpio_descriptor.h>
|
||||
#include <tactility/drivers/sdcard.h>
|
||||
#include <tactility/filesystem/file_system.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
@ -177,7 +178,7 @@ Driver esp32_sdmmc_driver = {
|
||||
.start_device = start,
|
||||
.stop_device = stop,
|
||||
.api = nullptr,
|
||||
.device_type = nullptr,
|
||||
.device_type = &SDCARD_TYPE,
|
||||
.owner = &platform_esp32_module,
|
||||
.internal = nullptr
|
||||
};
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
#include <tactility/drivers/esp32_spi.h>
|
||||
#include <tactility/drivers/gpio_descriptor.h>
|
||||
#include <tactility/drivers/spi_controller.h>
|
||||
#include <tactility/drivers/sdcard.h>
|
||||
#include <tactility/filesystem/file_system.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
@ -165,7 +166,7 @@ Driver esp32_sdspi_driver = {
|
||||
.start_device = start,
|
||||
.stop_device = stop,
|
||||
.api = nullptr,
|
||||
.device_type = nullptr,
|
||||
.device_type = &SDCARD_TYPE,
|
||||
.owner = &platform_esp32_module,
|
||||
.internal = nullptr
|
||||
};
|
||||
|
||||
@ -1,84 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <tactility/hal/Device.h>
|
||||
|
||||
#include <Tactility/Lock.h>
|
||||
#include <Tactility/TactilityCore.h>
|
||||
|
||||
|
||||
struct FileSystem;
|
||||
namespace tt::hal::sdcard {
|
||||
|
||||
/**
|
||||
* Warning: getLock() does not have to be used when calling any of the functions of this class.
|
||||
* The lock is only used for file access on the path where the SD card is mounted.
|
||||
* This is mainly used when accessing the SD card on a shared SPI bus.
|
||||
*/
|
||||
class SdCardDevice : public Device {
|
||||
|
||||
public:
|
||||
|
||||
enum class State {
|
||||
Mounted,
|
||||
Unmounted,
|
||||
Error,
|
||||
Timeout // Failed to retrieve state due to timeout
|
||||
};
|
||||
|
||||
enum class MountBehaviour {
|
||||
AtBoot, /** Only mount at boot */
|
||||
Anytime /** Mount/dismount any time */
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
MountBehaviour mountBehaviour;
|
||||
FileSystem* fileSystem;
|
||||
|
||||
public:
|
||||
|
||||
explicit SdCardDevice(MountBehaviour mountBehaviour);
|
||||
~SdCardDevice() override;
|
||||
|
||||
Type getType() const final { return Type::SdCard; };
|
||||
|
||||
/**
|
||||
* Mount the device.
|
||||
* @param mountPath the path to mount at
|
||||
* @return true on successful mount
|
||||
*/
|
||||
virtual bool mount(const std::string& mountPath) = 0;
|
||||
|
||||
/**
|
||||
* Unmount the device.
|
||||
* @return true on successful unmount
|
||||
*/
|
||||
virtual bool unmount() = 0;
|
||||
|
||||
virtual State getState(TickType_t timeout = kernel::MAX_TICKS) const = 0;
|
||||
|
||||
/** @return empty string when not mounted or the mount path if mounted */
|
||||
virtual std::string getMountPath() const = 0;
|
||||
|
||||
/** @return non-null lock, used by code that wants to access files on the mount path of this SD card */
|
||||
virtual std::shared_ptr<Lock> getLock() const = 0;
|
||||
|
||||
/** @return the MountBehaviour of this device */
|
||||
virtual MountBehaviour getMountBehaviour() const { return mountBehaviour; }
|
||||
|
||||
/** @return true if the SD card was mounted, returns false when it was not or when a timeout happened. */
|
||||
bool isMounted(TickType_t timeout = kernel::MAX_TICKS) const { return getState(timeout) == State::Mounted; }
|
||||
};
|
||||
|
||||
/** Return the SdCard device if the path is within the SdCard mounted path (path std::string::starts_with() check), otherwise return nullptr */
|
||||
std::shared_ptr<SdCardDevice> find(const std::string& path);
|
||||
|
||||
/**
|
||||
* Attempt to find an SD card that the specified belongs to,
|
||||
* and returns its lock if the SD card is mounted. Otherwise it returns nullptr.
|
||||
* @param[in] a path on a file system (e.g. file, directory, etc.)
|
||||
* @return the lock of a mounted SD card or otherwise null
|
||||
*/
|
||||
std::shared_ptr<Lock> findSdCardLock(const std::string& path);
|
||||
|
||||
} // namespace tt::hal
|
||||
20
Tactility/Private/Tactility/hal/SdCard.h
Normal file
20
Tactility/Private/Tactility/hal/SdCard.h
Normal file
@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <Tactility/Lock.h>
|
||||
|
||||
namespace tt::hal::sdcard {
|
||||
|
||||
/**
|
||||
* Attempt to find an SD card that the specified belongs to,
|
||||
* and returns its lock if the SD card is mounted. Otherwise it returns nullptr.
|
||||
* @param[in] a path on a file system (e.g. file, directory, etc.)
|
||||
* @return the lock of a mounted SD card or otherwise null
|
||||
*/
|
||||
std::shared_ptr<Lock> findSdCardLock(const std::string& path);
|
||||
|
||||
void mountAll();
|
||||
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace tt::hal::sdcard {
|
||||
|
||||
void mountAll();
|
||||
|
||||
}
|
||||
@ -2,7 +2,6 @@
|
||||
|
||||
#include <Tactility/file/File.h>
|
||||
#include <Tactility/file/FileLock.h>
|
||||
#include <Tactility/hal/sdcard/SdCardDevice.h>
|
||||
#include <Tactility/Logger.h>
|
||||
#include <Tactility/LogMessages.h>
|
||||
#include <Tactility/MountPoints.h>
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
#include "Tactility/app/fileselection/State.h"
|
||||
|
||||
#include <Tactility/file/File.h>
|
||||
#include "Tactility/hal/sdcard/SdCardDevice.h"
|
||||
#include <Tactility/Logger.h>
|
||||
#include <Tactility/MountPoints.h>
|
||||
#include <Tactility/kernel/Platform.h>
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
#include "Tactility/file/FileLock.h"
|
||||
|
||||
#include <Tactility/hal/sdcard/SdCardDevice.h>
|
||||
#include <Tactility/Mutex.h>
|
||||
#include <Tactility/hal/SdCard.h>
|
||||
|
||||
namespace tt::file {
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
#include <tactility/hal/Device.h>
|
||||
|
||||
#include <Tactility/hal/display/DisplayDevice.h>
|
||||
#include <Tactility/hal/sdcard/SdCardMounting.h>
|
||||
#include <Tactility/hal/SdCard.h>
|
||||
#include <Tactility/hal/touch/TouchDevice.h>
|
||||
#include <Tactility/kernel/SystemEvents.h>
|
||||
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
#include "Tactility/hal/sdcard/SdCardDevice.h"
|
||||
|
||||
#include <Tactility/lvgl/LvglSync.h>
|
||||
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/drivers/sdcard.h>
|
||||
#include <tactility/filesystem/file_system.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
namespace tt::hal::sdcard {
|
||||
|
||||
@ -38,4 +37,14 @@ std::shared_ptr<Lock> findSdCardLock(const std::string& path) {
|
||||
return ctx.result;
|
||||
}
|
||||
|
||||
void mountAll() {
|
||||
device_for_each_of_type(&SDCARD_TYPE, nullptr, [](::Device* device, void*) -> bool {
|
||||
if (!device_is_ready(device)) {
|
||||
if (device_start(device) != ERROR_NONE) {
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,52 +0,0 @@
|
||||
#include <Tactility/hal/sdcard/SdCardDevice.h>
|
||||
|
||||
#include <tactility/filesystem/file_system.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace tt::hal::sdcard {
|
||||
|
||||
static error_t mount(void* data) {
|
||||
auto* device = static_cast<SdCardDevice*>(data);
|
||||
auto path = device->getMountPath();
|
||||
if (!device->mount(path)) return ERROR_UNDEFINED;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t unmount(void* data) {
|
||||
auto* device = static_cast<SdCardDevice*>(data);
|
||||
if (!device->unmount()) return ERROR_UNDEFINED;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static bool is_mounted(void* data) {
|
||||
auto* device = static_cast<SdCardDevice*>(data);
|
||||
return device->isMounted();
|
||||
}
|
||||
|
||||
static error_t get_path(void* data, char* out_path, size_t out_path_size) {
|
||||
auto* device = static_cast<SdCardDevice*>(data);
|
||||
const auto mount_path = device->getMountPath();
|
||||
if (mount_path.size() >= out_path_size) return ERROR_BUFFER_OVERFLOW;
|
||||
if (mount_path.empty()) return ERROR_INVALID_STATE;
|
||||
strncpy(out_path, mount_path.c_str(), out_path_size);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
FileSystemApi sdCardDeviceApi = {
|
||||
.mount = mount,
|
||||
.unmount = unmount,
|
||||
.is_mounted = is_mounted,
|
||||
.get_path = get_path
|
||||
};
|
||||
|
||||
SdCardDevice::SdCardDevice(MountBehaviour mountBehaviour) : mountBehaviour(mountBehaviour) {
|
||||
fileSystem = file_system_add(&sdCardDeviceApi, this);
|
||||
check(fileSystem != nullptr);
|
||||
}
|
||||
|
||||
SdCardDevice::~SdCardDevice() {
|
||||
file_system_remove(fileSystem);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,36 +0,0 @@
|
||||
#include <Tactility/hal/sdcard/SdCardMounting.h>
|
||||
#include <Tactility/hal/sdcard/SdCardDevice.h>
|
||||
|
||||
#include <Tactility/Logger.h>
|
||||
|
||||
#include <format>
|
||||
|
||||
namespace tt::hal::sdcard {
|
||||
|
||||
static const auto LOGGER = Logger("SdCardMounting");
|
||||
constexpr auto* TT_SDCARD_MOUNT_POINT = "/sdcard";
|
||||
|
||||
static void mount(const std::shared_ptr<SdCardDevice>& sdcard, const std::string& path) {
|
||||
LOGGER.info("Mounting sdcard at {}", path);
|
||||
if (!sdcard->mount(path)) {
|
||||
LOGGER.warn("SD card mount failed for {} (init can continue)", path);
|
||||
}
|
||||
}
|
||||
|
||||
static std::string getMountPath(int index, int count) {
|
||||
return (count == 1) ? TT_SDCARD_MOUNT_POINT : std::format("{}{}", TT_SDCARD_MOUNT_POINT, index);
|
||||
}
|
||||
|
||||
void mountAll() {
|
||||
const auto sdcards = hal::findDevices<SdCardDevice>(Device::Type::SdCard);
|
||||
// Numbered mount path name
|
||||
for (int i = 0; i < sdcards.size(); i++) {
|
||||
auto sdcard = sdcards[i];
|
||||
if (!sdcard->isMounted() && sdcard->getMountBehaviour() == SdCardDevice::MountBehaviour::AtBoot) {
|
||||
std::string mount_path = getMountPath(i, sdcards.size());
|
||||
mount(sdcard, mount_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -16,7 +16,6 @@
|
||||
#include <Tactility/app/AppRegistration.h>
|
||||
#include <Tactility/app/AppManifest.h>
|
||||
#include <Tactility/app/App.h>
|
||||
#include <Tactility/hal/sdcard/SdCardDevice.h>
|
||||
#include <Tactility/service/wifi/Wifi.h>
|
||||
#include <esp_wifi_default.h>
|
||||
#include <Tactility/network/HttpdReq.h>
|
||||
|
||||
@ -8,7 +8,6 @@
|
||||
|
||||
#include <Tactility/Paths.h>
|
||||
#include <Tactility/Tactility.h>
|
||||
#include <Tactility/hal/sdcard/SdCardDevice.h>
|
||||
#include <dirent.h>
|
||||
#include <format>
|
||||
#include <map>
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
#include <Tactility/MountPoints.h>
|
||||
#include <Tactility/file/File.h>
|
||||
#include <Tactility/file/PropertiesFile.h>
|
||||
#include <Tactility/hal/sdcard/SdCardDevice.h>
|
||||
#include <Tactility/Logger.h>
|
||||
#include <Tactility/settings/BootSettings.h>
|
||||
|
||||
|
||||
14
TactilityKernel/include/tactility/drivers/sdcard.h
Normal file
14
TactilityKernel/include/tactility/drivers/sdcard.h
Normal file
@ -0,0 +1,14 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <tactility/device.h>
|
||||
|
||||
extern const struct DeviceType SDCARD_TYPE;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
10
TactilityKernel/source/drivers/sdcard.cpp
Normal file
10
TactilityKernel/source/drivers/sdcard.cpp
Normal file
@ -0,0 +1,10 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <tactility/drivers/sdcard.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
const struct DeviceType SDCARD_TYPE {
|
||||
.name = "sdcard"
|
||||
};
|
||||
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user