mirror of
https://source.denx.de/u-boot/u-boot.git
synced 2025-12-23 02:12:08 +01:00
Merge branch 'master' of https://source.denx.de/u-boot/custodians/u-boot-samsung
- Enhanced e850-96 support
This commit is contained in:
commit
088d24eb96
@ -602,6 +602,7 @@ ARM SAMSUNG EXYNOS850 SOC
|
|||||||
M: Sam Protsenko <semen.protsenko@linaro.org>
|
M: Sam Protsenko <semen.protsenko@linaro.org>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: drivers/clk/exynos/clk-exynos850.c
|
F: drivers/clk/exynos/clk-exynos850.c
|
||||||
|
F: drivers/phy/phy-exynos-usbdrd.c
|
||||||
F: drivers/pinctrl/exynos/pinctrl-exynos850.c
|
F: drivers/pinctrl/exynos/pinctrl-exynos850.c
|
||||||
|
|
||||||
ARM SAMSUNG SOC DRIVERS
|
ARM SAMSUNG SOC DRIVERS
|
||||||
|
|||||||
@ -4,9 +4,57 @@
|
|||||||
* Author: Sam Protsenko <semen.protsenko@linaro.org>
|
* Author: Sam Protsenko <semen.protsenko@linaro.org>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <efi_loader.h>
|
||||||
|
#include <env.h>
|
||||||
#include <init.h>
|
#include <init.h>
|
||||||
|
#include <mapmem.h>
|
||||||
|
#include <asm/io.h>
|
||||||
#include "fw.h"
|
#include "fw.h"
|
||||||
|
|
||||||
|
/* OTP Controller base address and register offsets */
|
||||||
|
#define EXYNOS850_OTP_BASE 0x10000000
|
||||||
|
#define OTP_CHIPID0 0x4
|
||||||
|
#define OTP_CHIPID1 0x8
|
||||||
|
|
||||||
|
struct efi_fw_image fw_images[] = {
|
||||||
|
{
|
||||||
|
.image_type_id = E850_96_FWBL1_IMAGE_GUID,
|
||||||
|
.fw_name = u"E850-96-FWBL1",
|
||||||
|
.image_index = 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.image_type_id = E850_96_EPBL_IMAGE_GUID,
|
||||||
|
.fw_name = u"E850-96-EPBL",
|
||||||
|
.image_index = 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.image_type_id = E850_96_BL2_IMAGE_GUID,
|
||||||
|
.fw_name = u"E850-96-BL2",
|
||||||
|
.image_index = 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.image_type_id = E850_96_BOOTLOADER_IMAGE_GUID,
|
||||||
|
.fw_name = u"E850-96-BOOTLOADER",
|
||||||
|
.image_index = 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.image_type_id = E850_96_EL3_MON_IMAGE_GUID,
|
||||||
|
.fw_name = u"E850-96-EL3-MON",
|
||||||
|
.image_index = 5,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
struct efi_capsule_update_info update_info = {
|
||||||
|
.dfu_string = "mmc 0="
|
||||||
|
"fwbl1.img raw 0x0 0x18 mmcpart 1;"
|
||||||
|
"epbl.img raw 0x18 0x98 mmcpart 1;"
|
||||||
|
"bl2.img raw 0xb0 0x200 mmcpart 1;"
|
||||||
|
"bootloader.img raw 0x438 0x1000 mmcpart 1;"
|
||||||
|
"el3_mon.img raw 0x1438 0x200 mmcpart 1",
|
||||||
|
.num_images = ARRAY_SIZE(fw_images),
|
||||||
|
.images = fw_images,
|
||||||
|
};
|
||||||
|
|
||||||
int dram_init(void)
|
int dram_init(void)
|
||||||
{
|
{
|
||||||
return fdtdec_setup_mem_size_base();
|
return fdtdec_setup_mem_size_base();
|
||||||
@ -17,10 +65,39 @@ int dram_init_banksize(void)
|
|||||||
return fdtdec_setup_memory_banksize();
|
return fdtdec_setup_memory_banksize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Read the unique SoC ID from OTP registers */
|
||||||
|
static u64 get_chip_id(void)
|
||||||
|
{
|
||||||
|
void __iomem *otp_base;
|
||||||
|
u64 val;
|
||||||
|
|
||||||
|
otp_base = map_sysmem(EXYNOS850_OTP_BASE, 12);
|
||||||
|
val = readl(otp_base + OTP_CHIPID0);
|
||||||
|
val |= (u64)readl(otp_base + OTP_CHIPID1) << 32UL;
|
||||||
|
unmap_sysmem(otp_base);
|
||||||
|
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setup_serial(void)
|
||||||
|
{
|
||||||
|
char serial_str[17] = { 0 };
|
||||||
|
u64 serial_num;
|
||||||
|
|
||||||
|
if (env_get("serial#"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
serial_num = get_chip_id();
|
||||||
|
snprintf(serial_str, sizeof(serial_str), "%016llx", serial_num);
|
||||||
|
env_set("serial#", serial_str);
|
||||||
|
}
|
||||||
|
|
||||||
int board_late_init(void)
|
int board_late_init(void)
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
|
setup_serial();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Do this in board_late_init() to make sure MMC is not probed before
|
* Do this in board_late_init() to make sure MMC is not probed before
|
||||||
* efi_init_early().
|
* efi_init_early().
|
||||||
|
|||||||
@ -7,5 +7,49 @@ pxefile_addr_r=0x8c200000
|
|||||||
ramdisk_addr_r=0x8c300000
|
ramdisk_addr_r=0x8c300000
|
||||||
fdtfile=CONFIG_DEFAULT_FDT_FILE
|
fdtfile=CONFIG_DEFAULT_FDT_FILE
|
||||||
|
|
||||||
|
dfu_alt_info=
|
||||||
|
rawemmc raw 0 0x747c000 mmcpart 1;
|
||||||
|
esp part 0 1;
|
||||||
|
rootfs part 0 2;
|
||||||
|
fwbl1 raw 0x0 0x18 mmcpart 1;
|
||||||
|
epbl raw 0x18 0x98 mmcpart 1;
|
||||||
|
bl2 raw 0xb0 0x200 mmcpart 1;
|
||||||
|
dram_train raw 0x2b0 0x20 mmcpart 1;
|
||||||
|
ect_test raw 0x2d0 0x64 mmcpart 1;
|
||||||
|
acpm_test raw 0x334 0x104 mmcpart 1;
|
||||||
|
bootloader raw 0x438 0x1000 mmcpart 1;
|
||||||
|
el3_mon raw 0x1438 0x200 mmcpart 1
|
||||||
|
|
||||||
partitions=name=esp,start=512K,size=128M,bootable,type=system;
|
partitions=name=esp,start=512K,size=128M,bootable,type=system;
|
||||||
partitions+=name=rootfs,size=-,bootable,type=linux
|
partitions+=name=rootfs,size=-,bootable,type=linux
|
||||||
|
|
||||||
|
partitions_android=name=esp,start=512K,size=128M,bootable,type=system;
|
||||||
|
partitions_android+=name=efs,size=20M,uuid=${uuid_gpt_efs};
|
||||||
|
partitions_android+=name=env,size=16K,uuid=${uuid_gpt_env};
|
||||||
|
partitions_android+=name=kernel,size=30M,uuid=${uuid_gpt_kernel};
|
||||||
|
partitions_android+=name=ramdisk,size=26M,uuid=${uuid_gpt_ramdisk};
|
||||||
|
partitions_android+=name=dtbo_a,size=1M,uuid=${uuid_gpt_dtbo};
|
||||||
|
partitions_android+=name=dtbo_b,size=1M,uuid=${uuid_gpt_dtbo};
|
||||||
|
partitions_android+=name=ldfw,size=4016K,uuid=${uuid_gpt_ldfw};
|
||||||
|
partitions_android+=name=keystorage,size=8K,uuid=${uuid_gpt_keystorage};
|
||||||
|
partitions_android+=name=tzsw,size=1M,uuid=${uuid_gpt_tzsw};
|
||||||
|
partitions_android+=name=harx,size=2M,uuid=${uuid_gpt_harx};
|
||||||
|
partitions_android+=name=harx_rkp,size=2M,uuid=${uuid_gpt_harx_rkp};
|
||||||
|
partitions_android+=name=logo,size=40M,uuid=${uuid_gpt_logo};
|
||||||
|
partitions_android+=name=super,size=3600M,uuid=${uuid_gpt_super};
|
||||||
|
partitions_android+=name=cache,size=300M,uuid=${uuid_gpt_cache};
|
||||||
|
partitions_android+=name=modem,size=100M,uuid=${uuid_gpt_modem};
|
||||||
|
partitions_android+=name=boot_a,size=100M,uuid=${uuid_gpt_boot};
|
||||||
|
partitions_android+=name=boot_b,size=100M,uuid=${uuid_gpt_boot};
|
||||||
|
partitions_android+=name=persist,size=30M,uuid=${uuid_gpt_persist};
|
||||||
|
partitions_android+=name=recovery_a,size=40M,uuid=${uuid_gpt_recovery};
|
||||||
|
partitions_android+=name=recovery_b,size=40M,uuid=${uuid_gpt_recovery};
|
||||||
|
partitions_android+=name=misc,size=40M,uuid=${uuid_gpt_misc};
|
||||||
|
partitions_android+=name=mnv,size=20M,uuid=${uuid_gpt_mnv};
|
||||||
|
partitions_android+=name=frp,size=512K,uuid=${uuid_gpt_frp};
|
||||||
|
partitions_android+=name=vbmeta_a,size=64K,uuid=${uuid_gpt_vbmeta};
|
||||||
|
partitions_android+=name=vbmeta_b,size=64K,uuid=${uuid_gpt_vbmeta};
|
||||||
|
partitions_android+=name=metadata,size=16M,uuid=${uuid_gpt_metadata};
|
||||||
|
partitions_android+=name=dtb_a,size=1M,uuid=${uuid_gpt_dtb};
|
||||||
|
partitions_android+=name=dtb_b,size=1M,uuid=${uuid_gpt_dtb};
|
||||||
|
partitions_android+=name=userdata,size=-,uuid=${uuid_gpt_userdata}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ CONFIG_ARCH_CPU_INIT=y
|
|||||||
CONFIG_ARM_SMCCC=y
|
CONFIG_ARM_SMCCC=y
|
||||||
CONFIG_ARCH_EXYNOS=y
|
CONFIG_ARCH_EXYNOS=y
|
||||||
CONFIG_TEXT_BASE=0xf8800000
|
CONFIG_TEXT_BASE=0xf8800000
|
||||||
CONFIG_SYS_MALLOC_LEN=0x81f000
|
CONFIG_SYS_MALLOC_LEN=0x2000000
|
||||||
CONFIG_SYS_MALLOC_F_LEN=0x4000
|
CONFIG_SYS_MALLOC_F_LEN=0x4000
|
||||||
CONFIG_ARCH_EXYNOS9=y
|
CONFIG_ARCH_EXYNOS9=y
|
||||||
CONFIG_HAS_CUSTOM_SYS_INIT_SP_ADDR=y
|
CONFIG_HAS_CUSTOM_SYS_INIT_SP_ADDR=y
|
||||||
@ -15,7 +15,9 @@ CONFIG_SYS_LOAD_ADDR=0x80000000
|
|||||||
CONFIG_ENV_OFFSET_REDUND=0x10000
|
CONFIG_ENV_OFFSET_REDUND=0x10000
|
||||||
# CONFIG_PSCI_RESET is not set
|
# CONFIG_PSCI_RESET is not set
|
||||||
CONFIG_EFI_SET_TIME=y
|
CONFIG_EFI_SET_TIME=y
|
||||||
CONFIG_ANDROID_BOOT_IMAGE=y
|
CONFIG_EFI_RUNTIME_UPDATE_CAPSULE=y
|
||||||
|
CONFIG_EFI_CAPSULE_ON_DISK=y
|
||||||
|
CONFIG_EFI_CAPSULE_FIRMWARE_RAW=y
|
||||||
CONFIG_BOOTSTD_FULL=y
|
CONFIG_BOOTSTD_FULL=y
|
||||||
CONFIG_DEFAULT_FDT_FILE="exynos850-e850-96.dtb"
|
CONFIG_DEFAULT_FDT_FILE="exynos850-e850-96.dtb"
|
||||||
# CONFIG_DISPLAY_CPUINFO is not set
|
# CONFIG_DISPLAY_CPUINFO is not set
|
||||||
@ -25,6 +27,7 @@ CONFIG_CMD_BOOTEFI_SELFTEST=y
|
|||||||
CONFIG_CMD_ABOOTIMG=y
|
CONFIG_CMD_ABOOTIMG=y
|
||||||
CONFIG_CMD_NVEDIT_EFI=y
|
CONFIG_CMD_NVEDIT_EFI=y
|
||||||
CONFIG_CMD_CLK=y
|
CONFIG_CMD_CLK=y
|
||||||
|
CONFIG_CMD_DFU=y
|
||||||
CONFIG_CMD_GPT=y
|
CONFIG_CMD_GPT=y
|
||||||
CONFIG_CMD_MMC=y
|
CONFIG_CMD_MMC=y
|
||||||
CONFIG_CMD_EFIDEBUG=y
|
CONFIG_CMD_EFIDEBUG=y
|
||||||
@ -40,8 +43,18 @@ CONFIG_ENV_RELOC_GD_ENV_ADDR=y
|
|||||||
CONFIG_ENV_MMC_EMMC_HW_PARTITION=2
|
CONFIG_ENV_MMC_EMMC_HW_PARTITION=2
|
||||||
CONFIG_NO_NET=y
|
CONFIG_NO_NET=y
|
||||||
CONFIG_CLK_EXYNOS850=y
|
CONFIG_CLK_EXYNOS850=y
|
||||||
|
CONFIG_DFU_MMC=y
|
||||||
|
CONFIG_USB_FUNCTION_FASTBOOT=y
|
||||||
|
CONFIG_FASTBOOT_BUF_ADDR=0x8a000000
|
||||||
|
CONFIG_FASTBOOT_BUF_SIZE=0x30000000
|
||||||
|
CONFIG_FASTBOOT_FLASH=y
|
||||||
|
CONFIG_FASTBOOT_FLASH_MMC_DEV=0
|
||||||
|
CONFIG_FASTBOOT_MMC_BOOT_SUPPORT=y
|
||||||
|
CONFIG_FASTBOOT_CMD_OEM_FORMAT=y
|
||||||
CONFIG_SUPPORT_EMMC_BOOT=y
|
CONFIG_SUPPORT_EMMC_BOOT=y
|
||||||
CONFIG_MMC_DW=y
|
CONFIG_MMC_DW=y
|
||||||
|
CONFIG_PHY=y
|
||||||
|
CONFIG_PHY_EXYNOS_USBDRD=y
|
||||||
CONFIG_DM_RTC=y
|
CONFIG_DM_RTC=y
|
||||||
CONFIG_RTC_EMULATION=y
|
CONFIG_RTC_EMULATION=y
|
||||||
CONFIG_SOC_SAMSUNG=y
|
CONFIG_SOC_SAMSUNG=y
|
||||||
@ -49,3 +62,11 @@ CONFIG_EXYNOS_PMU=y
|
|||||||
CONFIG_EXYNOS_USI=y
|
CONFIG_EXYNOS_USI=y
|
||||||
CONFIG_SYSRESET=y
|
CONFIG_SYSRESET=y
|
||||||
CONFIG_SYSRESET_SYSCON=y
|
CONFIG_SYSRESET_SYSCON=y
|
||||||
|
CONFIG_USB=y
|
||||||
|
CONFIG_DM_USB_GADGET=y
|
||||||
|
CONFIG_USB_DWC3=y
|
||||||
|
CONFIG_USB_DWC3_GENERIC=y
|
||||||
|
CONFIG_USB_GADGET=y
|
||||||
|
CONFIG_USB_GADGET_MANUFACTURER="Samsung"
|
||||||
|
CONFIG_USB_GADGET_VENDOR_NUM=0x18d1
|
||||||
|
CONFIG_USB_GADGET_PRODUCT_NUM=0x0002
|
||||||
|
|||||||
@ -43,17 +43,19 @@ Legend:
|
|||||||
BL31 in terms of ARM boot flow
|
BL31 in terms of ARM boot flow
|
||||||
* ``LDFW``: Loadable Firmware
|
* ``LDFW``: Loadable Firmware
|
||||||
|
|
||||||
Build Procedure
|
Unbricking Note
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
.. warning::
|
In case the board is bricked for some reason, the ``dltool`` [8]_ can be used to
|
||||||
At the moment USB is not enabled in U-Boot for this board. Although eMMC is
|
unbrick and revive it. This tool performs USB boot, and uploads the LittleKernel
|
||||||
enabled, you won't be able to flash images over USB (fastboot). So flashing
|
bootloader over USB, which is then being executed on the board. The loaded
|
||||||
U-Boot binary **WILL** effectively brick your board. The ``dltool`` [8]_ can
|
bootloader further enters fastboot mode, so that the user can flash the
|
||||||
be used then to perform USB boot and flash LittleKernel bootloader binary [7]_
|
functional bootloader binary (U-Boot or LittleKernel [7]_) to eMMC using
|
||||||
to unbrick and revive the board. Flashing U-Boot binary might be helpful for
|
``fastboot`` tool. Please read the ``dltool`` README file for more details about
|
||||||
developers or anybody who want to check current state of U-Boot enablement on
|
the procedure.
|
||||||
E850-96 (which is mostly serial console, eMMC and related blocks).
|
|
||||||
|
Build Procedure
|
||||||
|
---------------
|
||||||
|
|
||||||
Build U-Boot binary from source code (using AArch64 baremetal GCC toolchain):
|
Build U-Boot binary from source code (using AArch64 baremetal GCC toolchain):
|
||||||
|
|
||||||
@ -64,8 +66,9 @@ Build U-Boot binary from source code (using AArch64 baremetal GCC toolchain):
|
|||||||
make e850-96_defconfig
|
make e850-96_defconfig
|
||||||
make
|
make
|
||||||
|
|
||||||
Boot E850-96 board into fastboot mode as described in board software doc [9]_,
|
The original E850-96 board is shipped with LittleKernel-based bootloader flashed
|
||||||
and flash U-Boot binary into ``bootloader`` eMMC partition:
|
in eMMC. To replace it with U-Boot, boot into fastboot mode (as described in
|
||||||
|
the board software documentation [9]_), and flash U-Boot binary:
|
||||||
|
|
||||||
.. prompt:: bash $
|
.. prompt:: bash $
|
||||||
|
|
||||||
@ -74,6 +77,66 @@ and flash U-Boot binary into ``bootloader`` eMMC partition:
|
|||||||
|
|
||||||
U-Boot will boot up to the shell.
|
U-Boot will boot up to the shell.
|
||||||
|
|
||||||
|
Flashing
|
||||||
|
--------
|
||||||
|
|
||||||
|
User area of eMMC contains GPT partition table (either Linux or Android). Boot
|
||||||
|
Partition A (``mmc0boot0``) contains all firmware/bootloaders. Boot Partition
|
||||||
|
B (``mmc0boot1``) contains U-Boot environment.
|
||||||
|
|
||||||
|
First make sure to format eMMC accordingly. Prepare the initial environment:
|
||||||
|
|
||||||
|
.. prompt:: bash =>
|
||||||
|
|
||||||
|
env default -f -a
|
||||||
|
env save
|
||||||
|
|
||||||
|
For Linux, just format eMMC using default ``$partitions`` definitions:
|
||||||
|
|
||||||
|
.. prompt:: bash =>
|
||||||
|
|
||||||
|
gpt write mmc 0 $partitions
|
||||||
|
|
||||||
|
For Android, use ``$partitions_android`` instead:
|
||||||
|
|
||||||
|
.. prompt:: bash =>
|
||||||
|
|
||||||
|
setenv partitions_linux $partitions
|
||||||
|
setenv partitions $partitions_android
|
||||||
|
env save
|
||||||
|
gpt write mmc 0 $partitions
|
||||||
|
|
||||||
|
In case of Linux, there are two partitions available: ``esp`` (EFI System
|
||||||
|
Partition) and ``rootfs``. It is recommended to use fastboot to flash images to
|
||||||
|
those partitions. Enter fastboot mode on your device:
|
||||||
|
|
||||||
|
.. prompt:: bash =>
|
||||||
|
|
||||||
|
fastboot usb 0
|
||||||
|
|
||||||
|
And then flash the images:
|
||||||
|
|
||||||
|
.. prompt:: bash $
|
||||||
|
|
||||||
|
fastboot flash esp esp.img
|
||||||
|
fastboot flash rootfs rootfs.img
|
||||||
|
|
||||||
|
To update the firmware, it's easier to use DFU. Enter DFU mode on the board:
|
||||||
|
|
||||||
|
.. prompt:: bash =>
|
||||||
|
|
||||||
|
dfu 0 mmc 0
|
||||||
|
|
||||||
|
To update U-Boot:
|
||||||
|
|
||||||
|
.. prompt:: bash $
|
||||||
|
|
||||||
|
dfu-util -D u-boot.bin -a bootloader
|
||||||
|
|
||||||
|
It's also possible to use fastboot to flash the whole ``mmc0boot0`` HW
|
||||||
|
partition, but it's not so straightforward, as one have to prepare the image for
|
||||||
|
the whole ``boot0`` partition containing all firmware binaries first.
|
||||||
|
|
||||||
References
|
References
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
|||||||
@ -259,6 +259,15 @@ config MT76X8_USB_PHY
|
|||||||
|
|
||||||
This PHY is found on MT76x8 devices supporting USB.
|
This PHY is found on MT76x8 devices supporting USB.
|
||||||
|
|
||||||
|
config PHY_EXYNOS_USBDRD
|
||||||
|
bool "Exynos SoC series USB DRD PHY driver"
|
||||||
|
depends on PHY && CLK
|
||||||
|
depends on ARCH_EXYNOS
|
||||||
|
select REGMAP
|
||||||
|
select SYSCON
|
||||||
|
help
|
||||||
|
Enable USB DRD PHY support for Exynos SoC series.
|
||||||
|
|
||||||
config PHY_MTK_TPHY
|
config PHY_MTK_TPHY
|
||||||
bool "MediaTek T-PHY Driver"
|
bool "MediaTek T-PHY Driver"
|
||||||
depends on PHY
|
depends on PHY
|
||||||
|
|||||||
@ -35,6 +35,7 @@ obj-$(CONFIG_KEYSTONE_USB_PHY) += keystone-usb-phy.o
|
|||||||
obj-$(CONFIG_MT7620_USB_PHY) += mt7620-usb-phy.o
|
obj-$(CONFIG_MT7620_USB_PHY) += mt7620-usb-phy.o
|
||||||
obj-$(CONFIG_MT76X8_USB_PHY) += mt76x8-usb-phy.o
|
obj-$(CONFIG_MT76X8_USB_PHY) += mt76x8-usb-phy.o
|
||||||
obj-$(CONFIG_PHY_DA8XX_USB) += phy-da8xx-usb.o
|
obj-$(CONFIG_PHY_DA8XX_USB) += phy-da8xx-usb.o
|
||||||
|
obj-$(CONFIG_PHY_EXYNOS_USBDRD) += phy-exynos-usbdrd.o
|
||||||
obj-$(CONFIG_PHY_MTK_TPHY) += phy-mtk-tphy.o
|
obj-$(CONFIG_PHY_MTK_TPHY) += phy-mtk-tphy.o
|
||||||
obj-$(CONFIG_PHY_NPCM_USB) += phy-npcm-usb.o
|
obj-$(CONFIG_PHY_NPCM_USB) += phy-npcm-usb.o
|
||||||
obj-$(CONFIG_PHY_IMX8MQ_USB) += phy-imx8mq-usb.o
|
obj-$(CONFIG_PHY_IMX8MQ_USB) += phy-imx8mq-usb.o
|
||||||
|
|||||||
386
drivers/phy/phy-exynos-usbdrd.c
Normal file
386
drivers/phy/phy-exynos-usbdrd.c
Normal file
@ -0,0 +1,386 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2025 Linaro Ltd.
|
||||||
|
* Sam Protsenko <semen.protsenko@linaro.org>
|
||||||
|
*
|
||||||
|
* Samsung Exynos SoC series USB DRD PHY driver.
|
||||||
|
* Based on Linux kernel PHY driver: drivers/phy/samsung/phy-exynos5-usbdrd.c
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <clk.h>
|
||||||
|
#include <dm.h>
|
||||||
|
#include <generic-phy.h>
|
||||||
|
#include <regmap.h>
|
||||||
|
#include <syscon.h>
|
||||||
|
#include <asm/io.h>
|
||||||
|
#include <dm/device_compat.h>
|
||||||
|
#include <linux/bitfield.h>
|
||||||
|
#include <linux/bitops.h>
|
||||||
|
#include <linux/delay.h>
|
||||||
|
|
||||||
|
/* Offset of PMU register controlling USB PHY output isolation */
|
||||||
|
#define EXYNOS_USBDRD_PHY_CONTROL 0x0704
|
||||||
|
#define EXYNOS_PHY_ENABLE BIT(0)
|
||||||
|
|
||||||
|
/* Exynos USB PHY registers */
|
||||||
|
#define EXYNOS5_FSEL_9MHZ6 0x0
|
||||||
|
#define EXYNOS5_FSEL_10MHZ 0x1
|
||||||
|
#define EXYNOS5_FSEL_12MHZ 0x2
|
||||||
|
#define EXYNOS5_FSEL_19MHZ2 0x3
|
||||||
|
#define EXYNOS5_FSEL_20MHZ 0x4
|
||||||
|
#define EXYNOS5_FSEL_24MHZ 0x5
|
||||||
|
#define EXYNOS5_FSEL_26MHZ 0x6
|
||||||
|
#define EXYNOS5_FSEL_50MHZ 0x7
|
||||||
|
|
||||||
|
/* Exynos850: USB DRD PHY registers */
|
||||||
|
#define EXYNOS850_DRD_LINKCTRL 0x04
|
||||||
|
#define LINKCTRL_FORCE_QACT BIT(8)
|
||||||
|
#define LINKCTRL_BUS_FILTER_BYPASS GENMASK(7, 4)
|
||||||
|
|
||||||
|
#define EXYNOS850_DRD_CLKRST 0x20
|
||||||
|
#define CLKRST_LINK_SW_RST BIT(0)
|
||||||
|
#define CLKRST_PORT_RST BIT(1)
|
||||||
|
#define CLKRST_PHY_SW_RST BIT(3)
|
||||||
|
|
||||||
|
#define EXYNOS850_DRD_SSPPLLCTL 0x30
|
||||||
|
#define SSPPLLCTL_FSEL GENMASK(2, 0)
|
||||||
|
|
||||||
|
#define EXYNOS850_DRD_UTMI 0x50
|
||||||
|
#define UTMI_FORCE_SLEEP BIT(0)
|
||||||
|
#define UTMI_FORCE_SUSPEND BIT(1)
|
||||||
|
#define UTMI_DM_PULLDOWN BIT(2)
|
||||||
|
#define UTMI_DP_PULLDOWN BIT(3)
|
||||||
|
#define UTMI_FORCE_BVALID BIT(4)
|
||||||
|
#define UTMI_FORCE_VBUSVALID BIT(5)
|
||||||
|
|
||||||
|
#define EXYNOS850_DRD_HSP 0x54
|
||||||
|
#define HSP_COMMONONN BIT(8)
|
||||||
|
#define HSP_EN_UTMISUSPEND BIT(9)
|
||||||
|
#define HSP_VBUSVLDEXT BIT(12)
|
||||||
|
#define HSP_VBUSVLDEXTSEL BIT(13)
|
||||||
|
#define HSP_FSV_OUT_EN BIT(24)
|
||||||
|
|
||||||
|
#define EXYNOS850_DRD_HSP_TEST 0x5c
|
||||||
|
#define HSP_TEST_SIDDQ BIT(24)
|
||||||
|
|
||||||
|
#define KHZ 1000
|
||||||
|
#define MHZ (KHZ * KHZ)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct exynos_usbdrd_phy - driver data for Exynos USB PHY
|
||||||
|
* @reg_phy: USB PHY controller register memory base
|
||||||
|
* @clk: clock for register access
|
||||||
|
* @core_clk: core clock for phy (ref clock)
|
||||||
|
* @reg_pmu: regmap for PMU block
|
||||||
|
* @extrefclk: frequency select settings when using 'separate reference clocks'
|
||||||
|
*/
|
||||||
|
struct exynos_usbdrd_phy {
|
||||||
|
void __iomem *reg_phy;
|
||||||
|
struct clk *clk;
|
||||||
|
struct clk *core_clk;
|
||||||
|
struct regmap *reg_pmu;
|
||||||
|
u32 extrefclk;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void exynos_usbdrd_phy_isol(struct regmap *reg_pmu, bool isolate)
|
||||||
|
{
|
||||||
|
unsigned int val;
|
||||||
|
|
||||||
|
if (!reg_pmu)
|
||||||
|
return;
|
||||||
|
|
||||||
|
val = isolate ? 0 : EXYNOS_PHY_ENABLE;
|
||||||
|
regmap_update_bits(reg_pmu, EXYNOS_USBDRD_PHY_CONTROL,
|
||||||
|
EXYNOS_PHY_ENABLE, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert the supplied clock rate to the value that can be written to the PHY
|
||||||
|
* register.
|
||||||
|
*/
|
||||||
|
static unsigned int exynos_rate_to_clk(unsigned long rate, u32 *reg)
|
||||||
|
{
|
||||||
|
switch (rate) {
|
||||||
|
case 9600 * KHZ:
|
||||||
|
*reg = EXYNOS5_FSEL_9MHZ6;
|
||||||
|
break;
|
||||||
|
case 10 * MHZ:
|
||||||
|
*reg = EXYNOS5_FSEL_10MHZ;
|
||||||
|
break;
|
||||||
|
case 12 * MHZ:
|
||||||
|
*reg = EXYNOS5_FSEL_12MHZ;
|
||||||
|
break;
|
||||||
|
case 19200 * KHZ:
|
||||||
|
*reg = EXYNOS5_FSEL_19MHZ2;
|
||||||
|
break;
|
||||||
|
case 20 * MHZ:
|
||||||
|
*reg = EXYNOS5_FSEL_20MHZ;
|
||||||
|
break;
|
||||||
|
case 24 * MHZ:
|
||||||
|
*reg = EXYNOS5_FSEL_24MHZ;
|
||||||
|
break;
|
||||||
|
case 26 * MHZ:
|
||||||
|
*reg = EXYNOS5_FSEL_26MHZ;
|
||||||
|
break;
|
||||||
|
case 50 * MHZ:
|
||||||
|
*reg = EXYNOS5_FSEL_50MHZ;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void exynos850_usbdrd_utmi_init(struct phy *phy)
|
||||||
|
{
|
||||||
|
struct exynos_usbdrd_phy *phy_drd = dev_get_priv(phy->dev);
|
||||||
|
void __iomem *regs_base = phy_drd->reg_phy;
|
||||||
|
u32 reg;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Disable HWACG (hardware auto clock gating control). This will force
|
||||||
|
* QACTIVE signal in Q-Channel interface to HIGH level, to make sure
|
||||||
|
* the PHY clock is not gated by the hardware.
|
||||||
|
*/
|
||||||
|
reg = readl(regs_base + EXYNOS850_DRD_LINKCTRL);
|
||||||
|
reg |= LINKCTRL_FORCE_QACT;
|
||||||
|
writel(reg, regs_base + EXYNOS850_DRD_LINKCTRL);
|
||||||
|
|
||||||
|
/* Start PHY Reset (POR=high) */
|
||||||
|
reg = readl(regs_base + EXYNOS850_DRD_CLKRST);
|
||||||
|
reg |= CLKRST_PHY_SW_RST;
|
||||||
|
writel(reg, regs_base + EXYNOS850_DRD_CLKRST);
|
||||||
|
|
||||||
|
/* Enable UTMI+ */
|
||||||
|
reg = readl(regs_base + EXYNOS850_DRD_UTMI);
|
||||||
|
reg &= ~(UTMI_FORCE_SUSPEND | UTMI_FORCE_SLEEP | UTMI_DP_PULLDOWN |
|
||||||
|
UTMI_DM_PULLDOWN);
|
||||||
|
writel(reg, regs_base + EXYNOS850_DRD_UTMI);
|
||||||
|
|
||||||
|
/* Set PHY clock and control HS PHY */
|
||||||
|
reg = readl(regs_base + EXYNOS850_DRD_HSP);
|
||||||
|
reg |= HSP_EN_UTMISUSPEND | HSP_COMMONONN;
|
||||||
|
writel(reg, regs_base + EXYNOS850_DRD_HSP);
|
||||||
|
|
||||||
|
/* Set VBUS Valid and D+ pull-up control by VBUS pad usage */
|
||||||
|
reg = readl(regs_base + EXYNOS850_DRD_LINKCTRL);
|
||||||
|
reg |= FIELD_PREP(LINKCTRL_BUS_FILTER_BYPASS, 0xf);
|
||||||
|
writel(reg, regs_base + EXYNOS850_DRD_LINKCTRL);
|
||||||
|
|
||||||
|
reg = readl(regs_base + EXYNOS850_DRD_UTMI);
|
||||||
|
reg |= UTMI_FORCE_BVALID | UTMI_FORCE_VBUSVALID;
|
||||||
|
writel(reg, regs_base + EXYNOS850_DRD_UTMI);
|
||||||
|
|
||||||
|
reg = readl(regs_base + EXYNOS850_DRD_HSP);
|
||||||
|
reg |= HSP_VBUSVLDEXT | HSP_VBUSVLDEXTSEL;
|
||||||
|
writel(reg, regs_base + EXYNOS850_DRD_HSP);
|
||||||
|
|
||||||
|
reg = readl(regs_base + EXYNOS850_DRD_SSPPLLCTL);
|
||||||
|
reg &= ~SSPPLLCTL_FSEL;
|
||||||
|
switch (phy_drd->extrefclk) {
|
||||||
|
case EXYNOS5_FSEL_50MHZ:
|
||||||
|
reg |= FIELD_PREP(SSPPLLCTL_FSEL, 7);
|
||||||
|
break;
|
||||||
|
case EXYNOS5_FSEL_26MHZ:
|
||||||
|
reg |= FIELD_PREP(SSPPLLCTL_FSEL, 6);
|
||||||
|
break;
|
||||||
|
case EXYNOS5_FSEL_24MHZ:
|
||||||
|
reg |= FIELD_PREP(SSPPLLCTL_FSEL, 2);
|
||||||
|
break;
|
||||||
|
case EXYNOS5_FSEL_20MHZ:
|
||||||
|
reg |= FIELD_PREP(SSPPLLCTL_FSEL, 1);
|
||||||
|
break;
|
||||||
|
case EXYNOS5_FSEL_19MHZ2:
|
||||||
|
reg |= FIELD_PREP(SSPPLLCTL_FSEL, 0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
dev_warn(phy->dev, "unsupported ref clk: %#.2x\n",
|
||||||
|
phy_drd->extrefclk);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
writel(reg, regs_base + EXYNOS850_DRD_SSPPLLCTL);
|
||||||
|
|
||||||
|
/* Power up PHY analog blocks */
|
||||||
|
reg = readl(regs_base + EXYNOS850_DRD_HSP_TEST);
|
||||||
|
reg &= ~HSP_TEST_SIDDQ;
|
||||||
|
writel(reg, regs_base + EXYNOS850_DRD_HSP_TEST);
|
||||||
|
|
||||||
|
/* Finish PHY reset (POR=low) */
|
||||||
|
udelay(10); /* required before doing POR=low */
|
||||||
|
reg = readl(regs_base + EXYNOS850_DRD_CLKRST);
|
||||||
|
reg &= ~(CLKRST_PHY_SW_RST | CLKRST_PORT_RST);
|
||||||
|
writel(reg, regs_base + EXYNOS850_DRD_CLKRST);
|
||||||
|
udelay(75); /* required after POR=low for guaranteed PHY clock */
|
||||||
|
|
||||||
|
/* Disable single ended signal out */
|
||||||
|
reg = readl(regs_base + EXYNOS850_DRD_HSP);
|
||||||
|
reg &= ~HSP_FSV_OUT_EN;
|
||||||
|
writel(reg, regs_base + EXYNOS850_DRD_HSP);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void exynos850_usbdrd_utmi_exit(struct phy *phy)
|
||||||
|
{
|
||||||
|
struct exynos_usbdrd_phy *phy_drd = dev_get_priv(phy->dev);
|
||||||
|
void __iomem *regs_base = phy_drd->reg_phy;
|
||||||
|
u32 reg;
|
||||||
|
|
||||||
|
/* Set PHY clock and control HS PHY */
|
||||||
|
reg = readl(regs_base + EXYNOS850_DRD_UTMI);
|
||||||
|
reg &= ~(UTMI_DP_PULLDOWN | UTMI_DM_PULLDOWN);
|
||||||
|
reg |= UTMI_FORCE_SUSPEND | UTMI_FORCE_SLEEP;
|
||||||
|
writel(reg, regs_base + EXYNOS850_DRD_UTMI);
|
||||||
|
|
||||||
|
/* Power down PHY analog blocks */
|
||||||
|
reg = readl(regs_base + EXYNOS850_DRD_HSP_TEST);
|
||||||
|
reg |= HSP_TEST_SIDDQ;
|
||||||
|
writel(reg, regs_base + EXYNOS850_DRD_HSP_TEST);
|
||||||
|
|
||||||
|
/* Link reset */
|
||||||
|
reg = readl(regs_base + EXYNOS850_DRD_CLKRST);
|
||||||
|
reg |= CLKRST_LINK_SW_RST;
|
||||||
|
writel(reg, regs_base + EXYNOS850_DRD_CLKRST);
|
||||||
|
udelay(10); /* required before doing POR=low */
|
||||||
|
reg &= ~CLKRST_LINK_SW_RST;
|
||||||
|
writel(reg, regs_base + EXYNOS850_DRD_CLKRST);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int exynos_usbdrd_phy_init(struct phy *phy)
|
||||||
|
{
|
||||||
|
struct exynos_usbdrd_phy *phy_drd = dev_get_priv(phy->dev);
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = clk_prepare_enable(phy_drd->clk);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
exynos850_usbdrd_utmi_init(phy);
|
||||||
|
|
||||||
|
clk_disable_unprepare(phy_drd->clk);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int exynos_usbdrd_phy_exit(struct phy *phy)
|
||||||
|
{
|
||||||
|
struct exynos_usbdrd_phy *phy_drd = dev_get_priv(phy->dev);
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = clk_prepare_enable(phy_drd->clk);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
exynos850_usbdrd_utmi_exit(phy);
|
||||||
|
|
||||||
|
clk_disable_unprepare(phy_drd->clk);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int exynos_usbdrd_phy_power_on(struct phy *phy)
|
||||||
|
{
|
||||||
|
struct exynos_usbdrd_phy *phy_drd = dev_get_priv(phy->dev);
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
dev_dbg(phy->dev, "Request to power_on usbdrd_phy phy\n");
|
||||||
|
|
||||||
|
ret = clk_prepare_enable(phy_drd->core_clk);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
/* Power-on PHY */
|
||||||
|
exynos_usbdrd_phy_isol(phy_drd->reg_pmu, false);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int exynos_usbdrd_phy_power_off(struct phy *phy)
|
||||||
|
{
|
||||||
|
struct exynos_usbdrd_phy *phy_drd = dev_get_priv(phy->dev);
|
||||||
|
|
||||||
|
dev_dbg(phy->dev, "Request to power_off usbdrd_phy phy\n");
|
||||||
|
|
||||||
|
/* Power-off the PHY */
|
||||||
|
exynos_usbdrd_phy_isol(phy_drd->reg_pmu, true);
|
||||||
|
|
||||||
|
clk_disable_unprepare(phy_drd->core_clk);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int exynos_usbdrd_phy_init_clk(struct udevice *dev)
|
||||||
|
{
|
||||||
|
struct exynos_usbdrd_phy *phy_drd = dev_get_priv(dev);
|
||||||
|
unsigned long ref_rate;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
phy_drd->clk = devm_clk_get(dev, "phy");
|
||||||
|
if (IS_ERR(phy_drd->clk)) {
|
||||||
|
err = PTR_ERR(phy_drd->clk);
|
||||||
|
dev_err(dev, "Failed to get phy clock (err=%d)\n", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
phy_drd->core_clk = devm_clk_get(dev, "ref");
|
||||||
|
if (IS_ERR(phy_drd->core_clk)) {
|
||||||
|
err = PTR_ERR(phy_drd->core_clk);
|
||||||
|
dev_err(dev, "Failed to get ref clock (err=%d)\n", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
ref_rate = clk_get_rate(phy_drd->core_clk);
|
||||||
|
err = exynos_rate_to_clk(ref_rate, &phy_drd->extrefclk);
|
||||||
|
if (err) {
|
||||||
|
dev_err(dev, "Clock rate %lu not supported\n", ref_rate);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int exynos_usbdrd_phy_probe(struct udevice *dev)
|
||||||
|
{
|
||||||
|
struct exynos_usbdrd_phy *phy_drd = dev_get_priv(dev);
|
||||||
|
int err;
|
||||||
|
|
||||||
|
phy_drd->reg_phy = dev_read_addr_ptr(dev);
|
||||||
|
if (!phy_drd->reg_phy)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
err = exynos_usbdrd_phy_init_clk(dev);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
phy_drd->reg_pmu = syscon_regmap_lookup_by_phandle(dev,
|
||||||
|
"samsung,pmu-syscon");
|
||||||
|
if (IS_ERR(phy_drd->reg_pmu)) {
|
||||||
|
err = PTR_ERR(phy_drd->reg_pmu);
|
||||||
|
dev_err(dev, "Failed to lookup PMU regmap\n");
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct phy_ops exynos_usbdrd_phy_ops = {
|
||||||
|
.init = exynos_usbdrd_phy_init,
|
||||||
|
.exit = exynos_usbdrd_phy_exit,
|
||||||
|
.power_on = exynos_usbdrd_phy_power_on,
|
||||||
|
.power_off = exynos_usbdrd_phy_power_off,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct udevice_id exynos_usbdrd_phy_of_match[] = {
|
||||||
|
{
|
||||||
|
.compatible = "samsung,exynos850-usbdrd-phy",
|
||||||
|
},
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
|
||||||
|
U_BOOT_DRIVER(exynos_usbdrd_phy) = {
|
||||||
|
.name = "exynos-usbdrd-phy",
|
||||||
|
.id = UCLASS_PHY,
|
||||||
|
.of_match = exynos_usbdrd_phy_of_match,
|
||||||
|
.probe = exynos_usbdrd_phy_probe,
|
||||||
|
.ops = &exynos_usbdrd_phy_ops,
|
||||||
|
.priv_auto = sizeof(struct exynos_usbdrd_phy),
|
||||||
|
};
|
||||||
@ -704,6 +704,7 @@ static const struct udevice_id dwc3_glue_ids[] = {
|
|||||||
{ .compatible = "fsl,imx8mp-dwc3", .data = (ulong)&imx8mp_ops },
|
{ .compatible = "fsl,imx8mp-dwc3", .data = (ulong)&imx8mp_ops },
|
||||||
{ .compatible = "fsl,imx8mq-dwc3" },
|
{ .compatible = "fsl,imx8mq-dwc3" },
|
||||||
{ .compatible = "intel,tangier-dwc3" },
|
{ .compatible = "intel,tangier-dwc3" },
|
||||||
|
{ .compatible = "samsung,exynos850-dwusb3" },
|
||||||
{ }
|
{ }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -9,4 +9,25 @@
|
|||||||
#ifndef __E850_96_H
|
#ifndef __E850_96_H
|
||||||
#define __E850_96_H
|
#define __E850_96_H
|
||||||
|
|
||||||
|
/* GUIDs for capsule updatable firmware images */
|
||||||
|
#define E850_96_FWBL1_IMAGE_GUID \
|
||||||
|
EFI_GUID(0x181cd3f2, 0xe375, 0x44d2, 0x80, 0x78, \
|
||||||
|
0x32, 0x21, 0xe1, 0xdf, 0xb9, 0x5e)
|
||||||
|
|
||||||
|
#define E850_96_EPBL_IMAGE_GUID \
|
||||||
|
EFI_GUID(0x66c1a54d, 0xd149, 0x415d, 0xaa, 0xda, \
|
||||||
|
0xb8, 0xae, 0xe4, 0x99, 0xb3, 0x70)
|
||||||
|
|
||||||
|
#define E850_96_BL2_IMAGE_GUID \
|
||||||
|
EFI_GUID(0x89471c2a, 0x6c8d, 0x4158, 0xac, 0xad, \
|
||||||
|
0x23, 0xd3, 0xb2, 0x87, 0x3d, 0x35)
|
||||||
|
|
||||||
|
#define E850_96_BOOTLOADER_IMAGE_GUID \
|
||||||
|
EFI_GUID(0x629578c3, 0xffb3, 0x4a89, 0xac, 0x0c, \
|
||||||
|
0x61, 0x18, 0x40, 0x72, 0x77, 0x79)
|
||||||
|
|
||||||
|
#define E850_96_EL3_MON_IMAGE_GUID \
|
||||||
|
EFI_GUID(0xdf5718a2, 0x930a, 0x4916, 0xbb, 0x19, \
|
||||||
|
0x32, 0x13, 0x21, 0x4d, 0x84, 0x86)
|
||||||
|
|
||||||
#endif /* __E850_96_H */
|
#endif /* __E850_96_H */
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user