diff options
75 files changed, 5215 insertions, 444 deletions
@@ -1388,9 +1388,12 @@ cmd_binman = $(srctree)/tools/binman/binman $(if $(BINMAN_DEBUG),-D) \ -a rockchip-tpl-path=$(ROCKCHIP_TPL) \ -a spl-bss-pad=$(if $(CONFIG_SPL_SEPARATE_BSS),,1) \ -a tpl-bss-pad=$(if $(CONFIG_TPL_SEPARATE_BSS),,1) \ + -a vpl-bss-pad=$(if $(CONFIG_VPL_SEPARATE_BSS),,1) \ -a spl-dtb=$(CONFIG_SPL_OF_REAL) \ -a tpl-dtb=$(CONFIG_TPL_OF_REAL) \ + -a vpl-dtb=$(CONFIG_VPL_OF_REAL) \ -a pre-load-key-path=${PRE_LOAD_KEY_PATH} \ + -a of-spl-remove-props=$(CONFIG_OF_SPL_REMOVE_PROPS) \ $(BINMAN_$(@F)) OBJCOPYFLAGS_u-boot.ldr.hex := -I binary -O ihex diff --git a/arch/arm/dts/exynos850-e850-96-u-boot.dtsi b/arch/arm/dts/exynos850-e850-96-u-boot.dtsi index 6d7148f7264..3aa5d8bb10d 100644 --- a/arch/arm/dts/exynos850-e850-96-u-boot.dtsi +++ b/arch/arm/dts/exynos850-e850-96-u-boot.dtsi @@ -3,6 +3,17 @@ * Copyright (c) 2023 Linaro Ltd. */ +&soc { + /* TODO: Remove this node once it appears in upstream dts */ + trng: rng@12081400 { + compatible = "samsung,exynos850-trng"; + reg = <0x12081400 0x100>; + clocks = <&cmu_core CLK_GOUT_SSS_ACLK>, + <&cmu_core CLK_GOUT_SSS_PCLK>; + clock-names = "secss", "pclk"; + }; +}; + &pmu_system_controller { bootph-all; samsung,uart-debug-1; diff --git a/arch/arm/dts/qcom-ipq4019-u-boot.dtsi b/arch/arm/dts/qcom-ipq4019-u-boot.dtsi new file mode 100644 index 00000000000..07aaaa9f417 --- /dev/null +++ b/arch/arm/dts/qcom-ipq4019-u-boot.dtsi @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0-or-later OR MIT +/ { + soc { + switch: switch@c000000 { + compatible = "qcom,ipq4019-ess"; + reg = <0xc000000 0x80000>, <0x98000 0x800>, <0xc080000 0x8000>; + reg-names = "base", "psgmii_phy", "edma"; + resets = <&gcc ESS_PSGMII_ARES>, <&gcc ESS_RESET>; + reset-names = "psgmii", "ess"; + clocks = <&gcc GCC_ESS_CLK>; + clock-names = "ess"; + mdio = <&mdio>; + interrupts = <GIC_SPI 65 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 66 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 67 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 68 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 69 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 70 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 71 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 72 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 73 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 74 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 75 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 76 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 77 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 78 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 79 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 80 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 240 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 241 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 242 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 243 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 244 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 245 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 246 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 247 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 248 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 249 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 250 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 251 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 252 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 253 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 254 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 255 IRQ_TYPE_EDGE_RISING>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + swport1: port@1 { /* MAC1 */ + reg = <1>; + label = "lan1"; + phy-handle = <ðphy0>; + phy-mode = "psgmii"; + + status = "disabled"; + }; + + swport2: port@2 { /* MAC2 */ + reg = <2>; + label = "lan2"; + phy-handle = <ðphy1>; + phy-mode = "psgmii"; + + status = "disabled"; + }; + + swport3: port@3 { /* MAC3 */ + reg = <3>; + label = "lan3"; + phy-handle = <ðphy2>; + phy-mode = "psgmii"; + + status = "disabled"; + }; + + swport4: port@4 { /* MAC4 */ + reg = <4>; + label = "lan4"; + phy-handle = <ðphy3>; + phy-mode = "psgmii"; + + status = "disabled"; + }; + + swport5: port@5 { /* MAC5 */ + reg = <5>; + label = "wan"; + phy-handle = <ðphy4>; + phy-mode = "psgmii"; + + status = "disabled"; + }; + }; + }; + }; +}; + +&mdio { + psgmiiphy: psgmii-phy@5 { + reg = <5>; + }; +}; diff --git a/arch/arm/mach-exynos/Kconfig b/arch/arm/mach-exynos/Kconfig index cad8bb044cf..3fee5a4299b 100644 --- a/arch/arm/mach-exynos/Kconfig +++ b/arch/arm/mach-exynos/Kconfig @@ -250,6 +250,8 @@ config TARGET_E850_96 select PINCTRL select PINCTRL_EXYNOS850 imply OF_UPSTREAM + imply DM_RNG + imply RNG_EXYNOS endchoice endif diff --git a/board/samsung/e850-96/Makefile b/board/samsung/e850-96/Makefile index 301c2233711..71d46ea3d2b 100644 --- a/board/samsung/e850-96/Makefile +++ b/board/samsung/e850-96/Makefile @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0+ # -# Copyright (C) 2020, Linaro Limited +# Copyright (C) 2024, Linaro Limited # Sam Protsenko <semen.protsenko@linaro.org> -obj-y := e850-96.o +obj-y := e850-96.o fw.o diff --git a/board/samsung/e850-96/e850-96.c b/board/samsung/e850-96/e850-96.c index a00d81b5d4c..c5cef6f19d2 100644 --- a/board/samsung/e850-96/e850-96.c +++ b/board/samsung/e850-96/e850-96.c @@ -1,10 +1,11 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * Copyright (C) 2020, Linaro Limited - * Sam Protsenko <semen.protsenko@linaro.org> + * Copyright (c) 2024, Linaro Ltd. + * Author: Sam Protsenko <semen.protsenko@linaro.org> */ #include <init.h> +#include "fw.h" int dram_init(void) { @@ -18,5 +19,6 @@ int dram_init_banksize(void) int board_init(void) { + load_ldfw(); return 0; } diff --git a/board/samsung/e850-96/e850-96.env b/board/samsung/e850-96/e850-96.env new file mode 100644 index 00000000000..f36f90be950 --- /dev/null +++ b/board/samsung/e850-96/e850-96.env @@ -0,0 +1,26 @@ +partitions= + uuid_disk=${uuid_gpt_disk}; + name=efs,start=512K,size=20M,uuid=${uuid_gpt_efs}; + name=env,size=16K,uuid=${uuid_gpt_env}; + name=kernel,size=30M,uuid=${uuid_gpt_kernel}; + name=ramdisk,size=26M,uuid=${uuid_gpt_ramdisk}; + name=dtbo,size=1M,uuid=${uuid_gpt_dtbo}; + name=ldfw,size=4016K,uuid=${uuid_gpt_ldfw}; + name=keystorage,size=8K,uuid=${uuid_gpt_keystorage}; + name=tzsw,size=1M,uuid=${uuid_gpt_tzsw}; + name=harx,size=2M,uuid=${uuid_gpt_harx}; + name=harx_rkp,size=2M,uuid=${uuid_gpt_harx_rkp}; + name=logo,size=40M,uuid=${uuid_gpt_logo}; + name=super,size=3600M,uuid=${uuid_gpt_super}; + name=cache,size=300M,uuid=${uuid_gpt_cache}; + name=modem,size=100M,uuid=${uuid_gpt_modem}; + name=boot,size=100M,uuid=${uuid_gpt_boot}; + name=persist,size=30M,uuid=${uuid_gpt_persist}; + name=recovery,size=40M,uuid=${uuid_gpt_recovery}; + name=misc,size=40M,uuid=${uuid_gpt_misc}; + name=mnv,size=20M,uuid=${uuid_gpt_mnv}; + name=frp,size=512K,uuid=${uuid_gpt_frp}; + name=vbmeta,size=64K,uuid=${uuid_gpt_vbmeta}; + name=metadata,size=16M,uuid=${uuid_gpt_metadata}; + name=dtb,size=1M,uuid=${uuid_gpt_dtb}; + name=userdata,size=-,uuid=${uuid_gpt_userdata} diff --git a/board/samsung/e850-96/fw.c b/board/samsung/e850-96/fw.c new file mode 100644 index 00000000000..82a0b224c67 --- /dev/null +++ b/board/samsung/e850-96/fw.c @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2024 Linaro Ltd. + * Author: Sam Protsenko <semen.protsenko@linaro.org> + * + * Firmware loading code. + */ + +#include <part.h> +#include <linux/arm-smccc.h> +#include "fw.h" + +#define EMMC_IFACE "mmc" +#define EMMC_DEV_NUM 0 + +/* LDFW constants */ +#define LDFW_PART_NAME "ldfw" +#define LDFW_NWD_ADDR 0x88000000 +#define LDFW_MAGIC 0x10adab1e +#define SMC_CMD_LOAD_LDFW -0x500 +#define SDM_HW_RESET_STATUS 0x1230 +#define SDM_SW_RESET_STATUS 0x1231 +#define SB_ERROR_PREFIX 0xfdaa0000 + +struct ldfw_header { + u32 magic; + u32 size; + u32 init_entry; + u32 entry_point; + u32 suspend_entry; + u32 resume_entry; + u32 start_smc_id; + u32 version; + u32 set_runtime_entry; + u32 reserved[3]; + char fw_name[16]; +}; + +static int read_fw(const char *part_name, void *buf) +{ + struct blk_desc *blk_desc; + struct disk_partition part; + unsigned long cnt; + int part_num; + + blk_desc = blk_get_dev(EMMC_IFACE, EMMC_DEV_NUM); + if (!blk_desc) { + debug("%s: Can't get eMMC device\n", __func__); + return -ENODEV; + } + + part_num = part_get_info_by_name(blk_desc, part_name, &part); + if (part_num < 0) { + debug("%s: Can't get LDWF partition\n", __func__); + return -ENOENT; + } + + cnt = blk_dread(blk_desc, part.start, part.size, buf); + if (cnt != part.size) { + debug("%s: Can't read LDFW partition\n", __func__); + return -EIO; + } + + return 0; +} + +int load_ldfw(void) +{ + const phys_addr_t addr = (phys_addr_t)LDFW_NWD_ADDR; + struct ldfw_header *hdr; + struct arm_smccc_res res; + void *buf = (void *)addr; + u64 size = 0; + int err, i; + + /* Load LDFW from the block device partition into RAM buffer */ + err = read_fw(LDFW_PART_NAME, buf); + if (err) + return err; + + /* Validate LDFW by magic number in its header */ + hdr = buf; + if (hdr->magic != LDFW_MAGIC) { + debug("%s: Wrong LDFW magic; is LDFW flashed?\n", __func__); + return -EINVAL; + } + + /* Calculate actual total size of all LDFW blobs */ + for (i = 0; hdr->magic == LDFW_MAGIC; ++i) { +#ifdef DEBUG + char name[17] = { 0 }; + + strncpy(name, hdr->fw_name, 16); + debug("%s: ldfw #%d: version = 0x%x, name = %s\n", __func__, i, + hdr->version, name); +#endif + + size += (u64)hdr->size; + hdr = (struct ldfw_header *)((u64)hdr + (u64)hdr->size); + } + debug("%s: The whole size of all LDFWs: 0x%llx\n", __func__, size); + + /* Load LDFW firmware to SWD (Secure World) memory via EL3 monitor */ + arm_smccc_smc(SMC_CMD_LOAD_LDFW, addr, size, 0, 0, 0, 0, 0, &res); + err = (int)res.a0; + if (err == -1 || err == SDM_HW_RESET_STATUS) { + debug("%s: Can't load LDFW in dump_gpr state\n", __func__); + return -EIO; + } else if (err == SDM_SW_RESET_STATUS) { + debug("%s: Can't load LDFW in kernel panic (SW RESET) state\n", + __func__); + return -EIO; + } else if (err < 0 && (err & 0xffff0000) == SB_ERROR_PREFIX) { + debug("%s: LDFW signature is corrupted! ret=0x%x\n", __func__, + (u32)err); + return -EIO; + } else if (err == 0) { + debug("%s: No LDFW is inited\n", __func__); + return -EIO; + } + +#ifdef DEBUG + u32 tried = res.a0 & 0xffff; + u32 failed = (res.a0 >> 16) & 0xffff; + + debug("%s: %d/%d LDFWs have been loaded successfully\n", __func__, + tried - failed, tried); +#endif + + return 0; +} diff --git a/board/samsung/e850-96/fw.h b/board/samsung/e850-96/fw.h new file mode 100644 index 00000000000..472664e4ed2 --- /dev/null +++ b/board/samsung/e850-96/fw.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2024 Linaro Ltd. + * Sam Protsenko <semen.protsenko@linaro.org> + */ + +#ifndef __E850_96_FW_H +#define __E850_96_FW_H + +int load_ldfw(void); + +#endif /* __E850_96_FW_H */ diff --git a/board/samsung/odroid/Makefile b/board/samsung/odroid/Makefile index 5bf48313de3..615c99f5019 100644 --- a/board/samsung/odroid/Makefile +++ b/board/samsung/odroid/Makefile @@ -3,4 +3,4 @@ # Copyright (c) 2014 Samsung Electronics Co., Ltd. All rights reserved. # Przemyslaw Marczak <p.marczak@samsung.com> -obj-y := odroid.o +obj-$(CONFIG_TARGET_ODROID) := odroid.o diff --git a/board/samsung/smdk5420/Kconfig b/board/samsung/smdk5420/Kconfig index a9d62fffa55..37d6bbbed1b 100644 --- a/board/samsung/smdk5420/Kconfig +++ b/board/samsung/smdk5420/Kconfig @@ -1,7 +1,7 @@ if TARGET_ODROID_XU3 config SYS_BOARD - default "smdk5420" + default "odroid" config SYS_VENDOR default "samsung" diff --git a/configs/e850-96_defconfig b/configs/e850-96_defconfig index 38b9968c167..2949da24267 100644 --- a/configs/e850-96_defconfig +++ b/configs/e850-96_defconfig @@ -11,6 +11,7 @@ CONFIG_DEFAULT_DEVICE_TREE="exynos/exynos850-e850-96" CONFIG_SYS_LOAD_ADDR=0x80000000 # CONFIG_AUTOBOOT is not set # CONFIG_DISPLAY_CPUINFO is not set +CONFIG_CMD_RNG=y # CONFIG_NET is not set CONFIG_CLK_EXYNOS850=y # CONFIG_MMC is not set diff --git a/configs/qcom_defconfig b/configs/qcom_defconfig index 49422374026..419e969e5d1 100644 --- a/configs/qcom_defconfig +++ b/configs/qcom_defconfig @@ -33,6 +33,7 @@ CONFIG_CMD_UFS=y CONFIG_CMD_USB=y CONFIG_CMD_CAT=y CONFIG_CMD_BMP=y +CONFIG_CMD_REGULATOR=y CONFIG_CMD_LOG=y CONFIG_OF_LIVE=y CONFIG_BUTTON_QCOM_PMIC=y @@ -90,9 +91,13 @@ CONFIG_DM_PMIC=y CONFIG_PMIC_QCOM=y CONFIG_DM_REGULATOR=y CONFIG_DM_REGULATOR_FIXED=y +CONFIG_DM_REGULATOR_QCOM_RPMH=y CONFIG_SCSI=y CONFIG_MSM_SERIAL=y CONFIG_MSM_GENI_SERIAL=y +CONFIG_SOC_QCOM=y +CONFIG_QCOM_COMMAND_DB=y +CONFIG_QCOM_RPMH=y CONFIG_SPMI_MSM=y CONFIG_SYSINFO=y CONFIG_SYSINFO_SMBIOS=y diff --git a/doc/develop/qconfig.rst b/doc/develop/qconfig.rst index 8efb1eb2685..123779eab17 100644 --- a/doc/develop/qconfig.rst +++ b/doc/develop/qconfig.rst @@ -85,6 +85,20 @@ example, to find boards which enabled CONFIG_SCSI but not CONFIG_BLK:: 3 matches pg_wcom_seli8_defconfig highbank_defconfig pg_wcom_expu1_defconfig +It is also possible to search for particular values. For example, this finds all +boards with an empty string for `CONFIG_DEFAULT_FDT_FILE`:: + + ./tools/qconfig.py -f DEFAULT_FDT_FILE=\"\" + 1092 matches + ... + +This finds boards which have a value for SYS_MAXARGS other than 64:: + + ./tools/qconfig.py -f ~SYS_MAXARGS=64 + cfg CONFIG_SYS_MAXARGS + 281 matches + ... + Finding implied CONFIGs ----------------------- diff --git a/doc/develop/spl.rst b/doc/develop/spl.rst index 0a3e572310a..4bb48e6b7b3 100644 --- a/doc/develop/spl.rst +++ b/doc/develop/spl.rst @@ -121,6 +121,8 @@ Use `spl_phase()` to find the current U-Boot phase, e.g. `PHASE_SPL`. You can also find the previous and next phase and get the phase name. +.. _fdtgrep_filter: + Device tree ----------- The U-Boot device tree is filtered by the fdtgrep tools during the build diff --git a/drivers/clk/exynos/clk-exynos850.c b/drivers/clk/exynos/clk-exynos850.c index 0c09ba02de4..8cbc626f31e 100644 --- a/drivers/clk/exynos/clk-exynos850.c +++ b/drivers/clk/exynos/clk-exynos850.c @@ -323,14 +323,18 @@ U_BOOT_DRIVER(exynos850_cmu_peri) = { /* Register Offset definitions for CMU_CORE (0x12000000) */ #define PLL_CON0_MUX_CLKCMU_CORE_BUS_USER 0x0600 #define PLL_CON0_MUX_CLKCMU_CORE_MMC_EMBD_USER 0x0620 +#define PLL_CON0_MUX_CLKCMU_CORE_SSS_USER 0x0630 #define CLK_CON_DIV_DIV_CLK_CORE_BUSP 0x1800 #define CLK_CON_GAT_GOUT_CORE_MMC_EMBD_I_ACLK 0x20e8 #define CLK_CON_GAT_GOUT_CORE_MMC_EMBD_SDCLKIN 0x20ec +#define CLK_CON_GAT_GOUT_CORE_SSS_I_ACLK 0x2128 +#define CLK_CON_GAT_GOUT_CORE_SSS_I_PCLK 0x212c /* List of parent clocks for Muxes in CMU_CORE */ PNAME(mout_core_bus_user_p) = { "clock-oscclk", "dout_core_bus" }; PNAME(mout_core_mmc_embd_user_p) = { "clock-oscclk", "dout_core_mmc_embd" }; +PNAME(mout_core_sss_user_p) = { "clock-oscclk", "dout_core_sss" }; static const struct samsung_mux_clock core_mux_clks[] = { MUX(CLK_MOUT_CORE_BUS_USER, "mout_core_bus_user", mout_core_bus_user_p, @@ -338,6 +342,8 @@ static const struct samsung_mux_clock core_mux_clks[] = { MUX_F(CLK_MOUT_CORE_MMC_EMBD_USER, "mout_core_mmc_embd_user", mout_core_mmc_embd_user_p, PLL_CON0_MUX_CLKCMU_CORE_MMC_EMBD_USER, 4, 1, CLK_SET_RATE_PARENT, 0), + MUX(CLK_MOUT_CORE_SSS_USER, "mout_core_sss_user", mout_core_sss_user_p, + PLL_CON0_MUX_CLKCMU_CORE_SSS_USER, 4, 1), }; static const struct samsung_div_clock core_div_clks[] = { @@ -351,6 +357,10 @@ static const struct samsung_gate_clock core_gate_clks[] = { GATE(CLK_GOUT_MMC_EMBD_SDCLKIN, "gout_mmc_embd_sdclkin", "mout_core_mmc_embd_user", CLK_CON_GAT_GOUT_CORE_MMC_EMBD_SDCLKIN, 21, CLK_SET_RATE_PARENT, 0), + GATE(CLK_GOUT_SSS_ACLK, "gout_sss_aclk", "mout_core_sss_user", + CLK_CON_GAT_GOUT_CORE_SSS_I_ACLK, 21, 0, 0), + GATE(CLK_GOUT_SSS_PCLK, "gout_sss_pclk", "dout_core_busp", + CLK_CON_GAT_GOUT_CORE_SSS_I_PCLK, 21, 0, 0), }; static const struct samsung_clk_group core_cmu_clks[] = { diff --git a/drivers/clk/qcom/clock-ipq4019.c b/drivers/clk/qcom/clock-ipq4019.c index 9352ff46818..f6760c6fb3d 100644 --- a/drivers/clk/qcom/clock-ipq4019.c +++ b/drivers/clk/qcom/clock-ipq4019.c @@ -64,6 +64,9 @@ static int ipq4019_clk_enable(struct clk *clk) case GCC_USB2_MOCK_UTMI_CLK: /* These clocks is already initialized by SBL1 */ return 0; + case GCC_ESS_CLK: + /* This clock is already initialized by SBL1 */ + return 0; default: return -EINVAL; } @@ -141,6 +144,12 @@ static const struct qcom_reset_map gcc_ipq4019_resets[] = { [GCC_TCSR_BCR] = {0x22000, 0}, [GCC_MPM_BCR] = {0x24000, 0}, [GCC_SPDM_BCR] = {0x25000, 0}, + [ESS_MAC1_ARES] = {0x1200C, 0}, + [ESS_MAC2_ARES] = {0x1200C, 1}, + [ESS_MAC3_ARES] = {0x1200C, 2}, + [ESS_MAC4_ARES] = {0x1200C, 3}, + [ESS_MAC5_ARES] = {0x1200C, 4}, + [ESS_PSGMII_ARES] = {0x1200C, 5}, }; static struct msm_clk_data ipq4019_clk_data = { diff --git a/drivers/core/root.c b/drivers/core/root.c index 7cf6607a9b7..7a714f5478a 100644 --- a/drivers/core/root.c +++ b/drivers/core/root.c @@ -243,7 +243,8 @@ int dm_extended_scan(bool pre_reloc_only) const char * const nodes[] = { "/chosen", "/clocks", - "/firmware" + "/firmware", + "/reserved-memory", }; ret = dm_scan_fdt(pre_reloc_only); diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index b4ff033afa9..69ae7c07508 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -325,6 +325,14 @@ config EEPRO100 This driver supports Intel(R) PRO/100 82557/82559/82559ER fast ethernet family of adapters. +config ESSEDMA + bool "Qualcomm ESS Edma support" + depends on DM_ETH && ARCH_IPQ40XX + select PHYLIB + help + This driver supports ethernet DMA adapter found in + Qualcomm IPQ40xx series SoC-s. + config ETH_SANDBOX depends on SANDBOX default y diff --git a/drivers/net/Makefile b/drivers/net/Makefile index dce71685c3d..425dd721f9d 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_DWC_ETH_QOS_STM32) += dwc_eth_qos_stm32.o obj-$(CONFIG_E1000) += e1000.o obj-$(CONFIG_E1000_SPI) += e1000_spi.o obj-$(CONFIG_EEPRO100) += eepro100.o +obj-$(CONFIG_ESSEDMA) += essedma.o obj-$(CONFIG_ETHOC) += ethoc.o obj-$(CONFIG_ETH_DESIGNWARE) += designware.o obj-$(CONFIG_ETH_DESIGNWARE_MESON8B) += dwmac_meson8b.o diff --git a/drivers/net/essedma.c b/drivers/net/essedma.c new file mode 100644 index 00000000000..fccc5f5a440 --- /dev/null +++ b/drivers/net/essedma.c @@ -0,0 +1,1192 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2020 Sartura Ltd. + * + * Author: Robert Marko <robert.marko@sartura.hr> + * + * Copyright (c) 2021 Toco Technologies FZE <contact@toco.ae> + * Copyright (c) 2021 Gabor Juhos <j4g8y7@gmail.com> + * + * Qualcomm ESS EDMA ethernet driver + */ + +#include <asm/io.h> +#include <clk.h> +#include <cpu_func.h> +#include <dm.h> +#include <dm/device_compat.h> +#include <errno.h> +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <log.h> +#include <miiphy.h> +#include <net.h> +#include <reset.h> + +#include "essedma.h" + +#define EDMA_MAX_PKT_SIZE (PKTSIZE_ALIGN + PKTALIGN) + +#define EDMA_RXQ_ID 0 +#define EDMA_TXQ_ID 0 + +/* descriptor ring */ +struct edma_ring { + u16 count; /* number of descriptors in the ring */ + void *hw_desc; /* descriptor ring virtual address */ + unsigned int hw_size; /* hw descriptor ring length in bytes */ + dma_addr_t dma; /* descriptor ring physical address */ + u16 head; /* next Tx descriptor to fill */ + u16 tail; /* next Tx descriptor to clean */ +}; + +struct ess_switch { + phys_addr_t base; + struct phy_device *phydev[ESS_PORTS_NUM]; + u32 phy_mask; + ofnode ports_node; + phy_interface_t port_wrapper_mode; + int num_phy; +}; + +struct essedma_priv { + phys_addr_t base; + struct udevice *dev; + struct clk ess_clk; + struct reset_ctl ess_rst; + struct udevice *mdio_dev; + struct ess_switch esw; + phys_addr_t psgmii_base; + struct edma_ring tpd_ring; + struct edma_ring rfd_ring; +}; + +static void esw_port_loopback_set(struct ess_switch *esw, int port, + bool enable) +{ + u32 t; + + t = readl(esw->base + ESS_PORT_LOOKUP_CTRL(port)); + if (enable) + t |= ESS_PORT_LOOP_BACK_EN; + else + t &= ~ESS_PORT_LOOP_BACK_EN; + writel(t, esw->base + ESS_PORT_LOOKUP_CTRL(port)); +} + +static void esw_port_loopback_set_all(struct ess_switch *esw, bool enable) +{ + int i; + + for (i = 1; i < ESS_PORTS_NUM; i++) + esw_port_loopback_set(esw, i, enable); +} + +static void ess_reset(struct udevice *dev) +{ + struct essedma_priv *priv = dev_get_priv(dev); + + reset_assert(&priv->ess_rst); + mdelay(10); + + reset_deassert(&priv->ess_rst); + mdelay(10); +} + +void qca8075_ess_reset(struct udevice *dev) +{ + struct essedma_priv *priv = dev_get_priv(dev); + struct phy_device *psgmii_phy; + int i, val; + + /* Find the PSGMII PHY */ + psgmii_phy = priv->esw.phydev[priv->esw.num_phy - 1]; + + /* Fix phy psgmii RX 20bit */ + phy_write(psgmii_phy, MDIO_DEVAD_NONE, MII_BMCR, 0x005b); + + /* Reset phy psgmii */ + phy_write(psgmii_phy, MDIO_DEVAD_NONE, MII_BMCR, 0x001b); + + /* Release reset phy psgmii */ + phy_write(psgmii_phy, MDIO_DEVAD_NONE, MII_BMCR, 0x005b); + for (i = 0; i < 100; i++) { + val = phy_read_mmd(psgmii_phy, MDIO_MMD_PMAPMD, 0x28); + if (val & 0x1) + break; + mdelay(1); + } + if (i >= 100) + printf("QCA807x PSGMII PLL_VCO_CALIB Not Ready\n"); + + /* + * Check qca8075 psgmii calibration done end. + * Freeze phy psgmii RX CDR + */ + phy_write(psgmii_phy, MDIO_DEVAD_NONE, 0x1a, 0x2230); + + ess_reset(dev); + + /* Check ipq psgmii calibration done start */ + for (i = 0; i < 100; i++) { + val = readl(priv->psgmii_base + PSGMIIPHY_VCO_CALIBRATION_CTRL_REGISTER_2); + if (val & 0x1) + break; + mdelay(1); + } + if (i >= 100) + printf("PSGMII PLL_VCO_CALIB Not Ready\n"); + + /* + * Check ipq psgmii calibration done end. + * Relesae phy psgmii RX CDR + */ + phy_write(psgmii_phy, MDIO_DEVAD_NONE, 0x1a, 0x3230); + + /* Release phy psgmii RX 20bit */ + phy_write(psgmii_phy, MDIO_DEVAD_NONE, MII_BMCR, 0x005f); +} + +#define PSGMII_ST_NUM_RETRIES 20 +#define PSGMII_ST_PKT_COUNT (4 * 1024) +#define PSGMII_ST_PKT_SIZE 1504 + +/* + * Transmitting one byte over a 1000Mbps link requires 8 ns. + * Additionally, use + 1 ns for safety to compensate latencies + * and such. + */ +#define PSGMII_ST_TRAFFIC_TIMEOUT_NS \ + (PSGMII_ST_PKT_COUNT * PSGMII_ST_PKT_SIZE * (8 + 1)) + +#define PSGMII_ST_TRAFFIC_TIMEOUT \ + DIV_ROUND_UP(PSGMII_ST_TRAFFIC_TIMEOUT_NS, 1000000) + +static bool psgmii_self_test_repeat; + +static void psgmii_st_phy_power_down(struct phy_device *phydev) +{ + int val; + + val = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMCR); + val |= QCA807X_POWER_DOWN; + phy_write(phydev, MDIO_DEVAD_NONE, MII_BMCR, val); +} + +static void psgmii_st_phy_prepare(struct phy_device *phydev) +{ + int val; + + /* check phydev combo port */ + val = phy_read(phydev, MDIO_DEVAD_NONE, + QCA807X_CHIP_CONFIGURATION); + if (val) { + /* Select copper page */ + val |= QCA807X_MEDIA_PAGE_SELECT; + phy_write(phydev, MDIO_DEVAD_NONE, + QCA807X_CHIP_CONFIGURATION, val); + } + + /* Force no link by power down */ + psgmii_st_phy_power_down(phydev); + + /* Packet number (Non documented) */ + phy_write_mmd(phydev, MDIO_MMD_AN, 0x8021, PSGMII_ST_PKT_COUNT); + phy_write_mmd(phydev, MDIO_MMD_AN, 0x8062, PSGMII_ST_PKT_SIZE); + + /* Fix MDI status */ + val = phy_read(phydev, MDIO_DEVAD_NONE, QCA807X_FUNCTION_CONTROL); + val &= ~QCA807X_MDI_CROSSOVER_MODE_MASK; + val |= FIELD_PREP(QCA807X_MDI_CROSSOVER_MODE_MASK, + QCA807X_MDI_CROSSOVER_MODE_MANUAL_MDI); + val &= ~QCA807X_POLARITY_REVERSAL; + phy_write(phydev, MDIO_DEVAD_NONE, QCA807X_FUNCTION_CONTROL, val); +} + +static void psgmii_st_phy_recover(struct phy_device *phydev) +{ + int val; + + /* Packet number (Non documented) */ + phy_write_mmd(phydev, MDIO_MMD_AN, 0x8021, 0x0); + + /* Disable CRC checker and packet counter */ + val = phy_read_mmd(phydev, MDIO_MMD_AN, QCA807X_MMD7_CRC_PACKET_COUNTER); + val &= ~QCA807X_MMD7_PACKET_COUNTER_SELFCLR; + val &= ~QCA807X_MMD7_CRC_PACKET_COUNTER_EN; + phy_write_mmd(phydev, MDIO_MMD_AN, QCA807X_MMD7_CRC_PACKET_COUNTER, val); + + /* Disable traffic (Undocumented) */ + phy_write_mmd(phydev, MDIO_MMD_AN, 0x8020, 0x0); +} + +static void psgmii_st_phy_start_traffic(struct phy_device *phydev) +{ + int val; + + /* Enable CRC checker and packet counter */ + val = phy_read_mmd(phydev, MDIO_MMD_AN, QCA807X_MMD7_CRC_PACKET_COUNTER); + val |= QCA807X_MMD7_CRC_PACKET_COUNTER_EN; + phy_write_mmd(phydev, MDIO_MMD_AN, QCA807X_MMD7_CRC_PACKET_COUNTER, val); + + /* Start traffic (Undocumented) */ + phy_write_mmd(phydev, MDIO_MMD_AN, 0x8020, 0xa000); +} + +static bool psgmii_st_phy_check_counters(struct phy_device *phydev) +{ + u32 tx_ok; + + /* + * The number of test packets is limited to 65535 so + * only read the lower 16 bits of the counter. + */ + tx_ok = phy_read_mmd(phydev, MDIO_MMD_AN, + QCA807X_MMD7_VALID_EGRESS_COUNTER_2); + + return (tx_ok == PSGMII_ST_PKT_COUNT); +} + +static void psgmii_st_phy_reset_loopback(struct phy_device *phydev) +{ + /* reset the PHY */ + phy_write(phydev, MDIO_DEVAD_NONE, MII_BMCR, 0x9000); + + /* enable loopback mode */ + phy_write(phydev, MDIO_DEVAD_NONE, MII_BMCR, 0x4140); +} + +static inline bool psgmii_st_phy_link_is_up(struct phy_device *phydev) +{ + int val; + + val = phy_read(phydev, MDIO_DEVAD_NONE, QCA807X_PHY_SPECIFIC); + return !!(val & QCA807X_PHY_SPECIFIC_LINK); +} + +static bool psgmii_st_phy_wait(struct ess_switch *esw, u32 mask, + int retries, int delay, + bool (*check)(struct phy_device *)) +{ + int i; + + for (i = 0; i < retries; i++) { + int phy; + + for (phy = 0; phy < esw->num_phy - 1; phy++) { + u32 phybit = BIT(phy); + + if (!(mask & phybit)) + continue; + + if (check(esw->phydev[phy])) + mask &= ~phybit; + } + + if (!mask) + break; + + mdelay(delay); + } + + return (!mask); +} + +static bool psgmii_st_phy_wait_link(struct ess_switch *esw, u32 mask) +{ + return psgmii_st_phy_wait(esw, mask, 100, 10, + psgmii_st_phy_link_is_up); +} + +static bool psgmii_st_phy_wait_tx_complete(struct ess_switch *esw, u32 mask) +{ + return psgmii_st_phy_wait(esw, mask, PSGMII_ST_TRAFFIC_TIMEOUT, 1, + psgmii_st_phy_check_counters); +} + +static bool psgmii_st_run_test_serial(struct ess_switch *esw) +{ + bool result = true; + int i; + + for (i = 0; i < esw->num_phy - 1; i++) { + struct phy_device *phydev = esw->phydev[i]; + + psgmii_st_phy_reset_loopback(phydev); + + psgmii_st_phy_wait_link(esw, BIT(i)); + + psgmii_st_phy_start_traffic(phydev); + + /* wait for the traffic to complete */ + result &= psgmii_st_phy_wait_tx_complete(esw, BIT(i)); + + /* Power down */ + psgmii_st_phy_power_down(phydev); + + if (!result) + break; + } + + return result; +} + +static bool psgmii_st_run_test_parallel(struct ess_switch *esw) +{ + bool result; + int i; + + /* enable loopback mode on all PHYs */ + for (i = 0; i < esw->num_phy - 1; i++) + psgmii_st_phy_reset_loopback(esw->phydev[i]); + + psgmii_st_phy_wait_link(esw, esw->phy_mask); + + /* start traffic on all PHYs parallely */ + for (i = 0; i < esw->num_phy - 1; i++) + psgmii_st_phy_start_traffic(esw->phydev[i]); + + /* wait for the traffic to complete on all PHYs */ + result = psgmii_st_phy_wait_tx_complete(esw, esw->phy_mask); + + /* Power down all PHYs */ + for (i = 0; i < esw->num_phy - 1; i++) + psgmii_st_phy_power_down(esw->phydev[i]); + + return result; +} + +struct psgmii_st_stats { + int succeed; + int failed; + int failed_max; + int failed_cont; +}; + +static void psgmii_st_update_stats(struct psgmii_st_stats *stats, + bool success) +{ + if (success) { + stats->succeed++; + stats->failed_cont = 0; + return; + } + + stats->failed++; + stats->failed_cont++; + if (stats->failed_max < stats->failed_cont) + stats->failed_max = stats->failed_cont; +} + +static void psgmii_self_test(struct udevice *dev) +{ + struct essedma_priv *priv = dev_get_priv(dev); + struct ess_switch *esw = &priv->esw; + struct psgmii_st_stats stats; + bool result = false; + unsigned long tm; + int i; + + memset(&stats, 0, sizeof(stats)); + + tm = get_timer(0); + + for (i = 0; i < esw->num_phy - 1; i++) + psgmii_st_phy_prepare(esw->phydev[i]); + + for (i = 0; i < PSGMII_ST_NUM_RETRIES; i++) { + qca8075_ess_reset(dev); + + /* enable loopback mode on the switch's ports */ + esw_port_loopback_set_all(esw, true); + + /* run test on each PHYs individually after each other */ + result = psgmii_st_run_test_serial(esw); + + if (result) { + /* run test on each PHYs parallely */ + result = psgmii_st_run_test_parallel(esw); + } + + psgmii_st_update_stats(&stats, result); + + if (psgmii_self_test_repeat) + continue; + + if (result) + break; + } + + for (i = 0; i < esw->num_phy - 1; i++) { + /* Configuration recover */ + psgmii_st_phy_recover(esw->phydev[i]); + + /* Disable loopback */ + phy_write(esw->phydev[i], MDIO_DEVAD_NONE, + QCA807X_FUNCTION_CONTROL, 0x6860); + phy_write(esw->phydev[i], MDIO_DEVAD_NONE, MII_BMCR, 0x9040); + } + + /* disable loopback mode on the switch's ports */ + esw_port_loopback_set_all(esw, false); + + tm = get_timer(tm); + dev_dbg(priv->dev, "\nPSGMII self-test: succeed %d, failed %d (max %d), duration %lu.%03lu secs\n", + stats.succeed, stats.failed, stats.failed_max, + tm / 1000, tm % 1000); +} + +static int ess_switch_disable_lookup(struct ess_switch *esw) +{ + int val; + int i; + + /* Disable port lookup for all ports*/ + for (i = 0; i < ESS_PORTS_NUM; i++) { + int ess_port_vid; + + val = readl(esw->base + ESS_PORT_LOOKUP_CTRL(i)); + val &= ~ESS_PORT_VID_MEM_MASK; + + switch (i) { + case 0: + fallthrough; + case 5: + /* CPU,WAN port -> nothing */ + ess_port_vid = 0; + break; + case 1 ... 4: + /* LAN ports -> all other LAN ports */ + ess_port_vid = GENMASK(4, 1); + ess_port_vid &= ~BIT(i); + break; + default: + return -EINVAL; + } + + val |= FIELD_PREP(ESS_PORT_VID_MEM_MASK, ess_port_vid); + + writel(val, esw->base + ESS_PORT_LOOKUP_CTRL(i)); + } + + /* Set magic value for the global forwarding register 1 */ + writel(0x3e3e3e, esw->base + ESS_GLOBAL_FW_CTRL1); + + return 0; +} + +static int ess_switch_enable_lookup(struct ess_switch *esw) +{ + int val; + int i; + + /* Enable port lookup for all ports*/ + for (i = 0; i < ESS_PORTS_NUM; i++) { + int ess_port_vid; + + val = readl(esw->base + ESS_PORT_LOOKUP_CTRL(i)); + val &= ~ESS_PORT_VID_MEM_MASK; + + switch (i) { + case 0: + /* CPU port -> all other ports */ + ess_port_vid = GENMASK(5, 1); + break; + case 1 ... 4: + /* LAN ports -> CPU and all other LAN ports */ + ess_port_vid = GENMASK(4, 0); + ess_port_vid &= ~BIT(i); + break; + case 5: + /* WAN port -> CPU port only */ + ess_port_vid = BIT(0); + break; + default: + return -EINVAL; + } + + val |= FIELD_PREP(ESS_PORT_VID_MEM_MASK, ess_port_vid); + + writel(val, esw->base + ESS_PORT_LOOKUP_CTRL(i)); + } + + /* Set magic value for the global forwarding register 1 */ + writel(0x3f3f3f, esw->base + ESS_GLOBAL_FW_CTRL1); + + return 0; +} + +static void ess_switch_init(struct ess_switch *esw) +{ + int val = 0; + int i; + + /* Set magic value for the global forwarding register 1 */ + writel(0x3e3e3e, esw->base + ESS_GLOBAL_FW_CTRL1); + + /* Set 1000M speed, full duplex and RX/TX flow control for the CPU port*/ + val &= ~ESS_PORT_SPEED_MASK; + val |= FIELD_PREP(ESS_PORT_SPEED_MASK, ESS_PORT_SPEED_1000); + val |= ESS_PORT_DUPLEX_MODE; + val |= ESS_PORT_TX_FLOW_EN; + val |= ESS_PORT_RX_FLOW_EN; + + writel(val, esw->base + ESS_PORT0_STATUS); + + /* Disable port lookup for all ports*/ + for (i = 0; i < ESS_PORTS_NUM; i++) { + val = readl(esw->base + ESS_PORT_LOOKUP_CTRL(i)); + val &= ~ESS_PORT_VID_MEM_MASK; + + writel(val, esw->base + ESS_PORT_LOOKUP_CTRL(i)); + } + + /* Set HOL settings for all ports*/ + for (i = 0; i < ESS_PORTS_NUM; i++) { + val = 0; + + val |= FIELD_PREP(EG_PORT_QUEUE_NUM_MASK, 30); + if (i == 0 || i == 5) { + val |= FIELD_PREP(EG_PRI5_QUEUE_NUM_MASK, 4); + val |= FIELD_PREP(EG_PRI4_QUEUE_NUM_MASK, 4); + } + val |= FIELD_PREP(EG_PRI3_QUEUE_NUM_MASK, 4); + val |= FIELD_PREP(EG_PRI2_QUEUE_NUM_MASK, 4); + val |= FIELD_PREP(EG_PRI1_QUEUE_NUM_MASK, 4); + val |= FIELD_PREP(EG_PRI0_QUEUE_NUM_MASK, 4); + + writel(val, esw->base + ESS_PORT_HOL_CTRL0(i)); + + val = readl(esw->base + ESS_PORT_HOL_CTRL1(i)); + val &= ~ESS_ING_BUF_NUM_0_MASK; + val |= FIELD_PREP(ESS_ING_BUF_NUM_0_MASK, 6); + + writel(val, esw->base + ESS_PORT_HOL_CTRL1(i)); + } + + /* Give switch some time */ + mdelay(1); + + /* Enable RX and TX MAC-s */ + val = readl(esw->base + ESS_PORT0_STATUS); + val |= ESS_PORT_TXMAC_EN; + val |= ESS_PORT_RXMAC_EN; + + writel(val, esw->base + ESS_PORT0_STATUS); + + /* Set magic value for the global forwarding register 1 */ + writel(0x7f7f7f, esw->base + ESS_GLOBAL_FW_CTRL1); +} + +static int essedma_of_phy(struct udevice *dev) +{ + struct essedma_priv *priv = dev_get_priv(dev); + struct ess_switch *esw = &priv->esw; + int num_phy = 0, ret = 0; + ofnode node; + int i; + + ofnode_for_each_subnode(node, esw->ports_node) { + struct ofnode_phandle_args phandle_args; + struct phy_device *phydev; + u32 phy_addr; + + if (ofnode_is_enabled(node)) { + if (ofnode_parse_phandle_with_args(node, "phy-handle", NULL, 0, 0, + &phandle_args)) { + dev_dbg(priv->dev, "Failed to find phy-handle\n"); + return -ENODEV; + } + + ret = ofnode_read_u32(phandle_args.node, "reg", &phy_addr); + if (ret) { + dev_dbg(priv->dev, "Missing reg property in PHY node %s\n", + ofnode_get_name(phandle_args.node)); + return ret; + } + + phydev = dm_mdio_phy_connect(priv->mdio_dev, phy_addr, + dev, priv->esw.port_wrapper_mode); + if (!phydev) { + dev_dbg(priv->dev, "Failed to find phy on addr %d\n", phy_addr); + return -ENODEV; + } + + phydev->node = phandle_args.node; + ret = phy_config(phydev); + + esw->phydev[num_phy] = phydev; + + num_phy++; + } + } + + esw->num_phy = num_phy; + + for (i = 0; i < esw->num_phy - 1; i++) + esw->phy_mask |= BIT(i); + + return ret; +} + +static int essedma_of_switch(struct udevice *dev) +{ + struct essedma_priv *priv = dev_get_priv(dev); + int port_wrapper_mode = -1; + + priv->esw.ports_node = ofnode_find_subnode(dev_ofnode(dev), "ports"); + if (!ofnode_valid(priv->esw.ports_node)) { + printf("Failed to find ports node\n"); + return -EINVAL; + } + + port_wrapper_mode = ofnode_read_phy_mode(priv->esw.ports_node); + if (port_wrapper_mode == -1) + return -EINVAL; + + priv->esw.port_wrapper_mode = port_wrapper_mode; + + return essedma_of_phy(dev); +} + +static void ipq40xx_edma_start_rx_tx(struct essedma_priv *priv) +{ + volatile u32 data; + + /* enable RX queues */ + data = readl(priv->base + EDMA_REG_RXQ_CTRL); + data |= EDMA_RXQ_CTRL_EN; + writel(data, priv->base + EDMA_REG_RXQ_CTRL); + + /* enable TX queues */ + data = readl(priv->base + EDMA_REG_TXQ_CTRL); + data |= EDMA_TXQ_CTRL_TXQ_EN; + writel(data, priv->base + EDMA_REG_TXQ_CTRL); +} + +/* + * ipq40xx_edma_init_desc() + * Update descriptor ring size, + * Update buffer and producer/consumer index + */ +static void ipq40xx_edma_init_desc(struct essedma_priv *priv) +{ + struct edma_ring *rfd_ring; + struct edma_ring *etdr; + volatile u32 data = 0; + u16 hw_cons_idx = 0; + + /* Set the base address of every TPD ring. */ + etdr = &priv->tpd_ring; + + /* Update TX descriptor ring base address. */ + writel((u32)(etdr->dma & 0xffffffff), + priv->base + EDMA_REG_TPD_BASE_ADDR_Q(EDMA_TXQ_ID)); + data = readl(priv->base + EDMA_REG_TPD_IDX_Q(EDMA_TXQ_ID)); + + /* Calculate hardware consumer index for Tx. */ + hw_cons_idx = FIELD_GET(EDMA_TPD_CONS_IDX_MASK, data); + etdr->head = hw_cons_idx; + etdr->tail = hw_cons_idx; + data &= ~EDMA_TPD_PROD_IDX_MASK; + data |= hw_cons_idx; + + /* Update producer index for Tx. */ + writel(data, priv->base + EDMA_REG_TPD_IDX_Q(EDMA_TXQ_ID)); + + /* Update SW consumer index register for Tx. */ + writel(hw_cons_idx, + priv->base + EDMA_REG_TX_SW_CONS_IDX_Q(EDMA_TXQ_ID)); + + /* Set TPD ring size. */ + writel((u32)(etdr->count & EDMA_TPD_RING_SIZE_MASK), + priv->base + EDMA_REG_TPD_RING_SIZE); + + /* Configure Rx ring. */ + rfd_ring = &priv->rfd_ring; + + /* Update Receive Free descriptor ring base address. */ + writel((u32)(rfd_ring->dma & 0xffffffff), + priv->base + EDMA_REG_RFD_BASE_ADDR_Q(EDMA_RXQ_ID)); + data = readl(priv->base + EDMA_REG_RFD_BASE_ADDR_Q(EDMA_RXQ_ID)); + + /* Update RFD ring size and RX buffer size. */ + data = (rfd_ring->count & EDMA_RFD_RING_SIZE_MASK) + << EDMA_RFD_RING_SIZE_SHIFT; + data |= (EDMA_MAX_PKT_SIZE & EDMA_RX_BUF_SIZE_MASK) + << EDMA_RX_BUF_SIZE_SHIFT; + writel(data, priv->base + EDMA_REG_RX_DESC0); + + /* Disable TX FIFO low watermark and high watermark */ + writel(0, priv->base + EDMA_REG_TXF_WATER_MARK); + + /* Load all of base address above */ + data = readl(priv->base + EDMA_REG_TX_SRAM_PART); + data |= 1 << EDMA_LOAD_PTR_SHIFT; + writel(data, priv->base + EDMA_REG_TX_SRAM_PART); +} + +static void ipq40xx_edma_init_rfd_ring(struct essedma_priv *priv) +{ + struct edma_ring *erdr = &priv->rfd_ring; + struct edma_rfd *rfds = erdr->hw_desc; + int i; + + for (i = 0; i < erdr->count; i++) + rfds[i].buffer_addr = virt_to_phys(net_rx_packets[i]); + + flush_dcache_range(erdr->dma, erdr->dma + erdr->hw_size); + + /* setup producer index */ + erdr->head = erdr->count - 1; + writel(erdr->head, priv->base + EDMA_REG_RFD_IDX_Q(EDMA_RXQ_ID)); +} + +static void ipq40xx_edma_configure(struct essedma_priv *priv) +{ + u32 tmp; + int i; + + /* Set RSS type */ + writel(IPQ40XX_EDMA_RSS_TYPE_NONE, priv->base + EDMA_REG_RSS_TYPE); + + /* Configure RSS indirection table. + * 128 hash will be configured in the following + * pattern: hash{0,1,2,3} = {Q0,Q2,Q4,Q6} respectively + * and so on + */ + for (i = 0; i < EDMA_NUM_IDT; i++) + writel(EDMA_RSS_IDT_VALUE, priv->base + EDMA_REG_RSS_IDT(i)); + + /* Set RFD burst number */ + tmp = (EDMA_RFD_BURST << EDMA_RXQ_RFD_BURST_NUM_SHIFT); + + /* Set RFD prefetch threshold */ + tmp |= (EDMA_RFD_THR << EDMA_RXQ_RFD_PF_THRESH_SHIFT); + + /* Set RFD in host ring low threshold to generte interrupt */ + tmp |= (EDMA_RFD_LTHR << EDMA_RXQ_RFD_LOW_THRESH_SHIFT); + writel(tmp, priv->base + EDMA_REG_RX_DESC1); + + /* configure reception control data. */ + + /* Set Rx FIFO threshold to start to DMA data to host */ + tmp = EDMA_FIFO_THRESH_128_BYTE; + + /* Set RX remove vlan bit */ + tmp |= EDMA_RXQ_CTRL_RMV_VLAN; + writel(tmp, priv->base + EDMA_REG_RXQ_CTRL); + + /* Configure transmission control data */ + tmp = (EDMA_TPD_BURST << EDMA_TXQ_NUM_TPD_BURST_SHIFT); + tmp |= EDMA_TXQ_CTRL_TPD_BURST_EN; + tmp |= (EDMA_TXF_BURST << EDMA_TXQ_TXF_BURST_NUM_SHIFT); + writel(tmp, priv->base + EDMA_REG_TXQ_CTRL); +} + +static void ipq40xx_edma_stop_rx_tx(struct essedma_priv *priv) +{ + volatile u32 data; + + data = readl(priv->base + EDMA_REG_RXQ_CTRL); + data &= ~EDMA_RXQ_CTRL_EN; + writel(data, priv->base + EDMA_REG_RXQ_CTRL); + data = readl(priv->base + EDMA_REG_TXQ_CTRL); + data &= ~EDMA_TXQ_CTRL_TXQ_EN; + writel(data, priv->base + EDMA_REG_TXQ_CTRL); +} + +static int ipq40xx_eth_recv(struct udevice *dev, int flags, uchar **packetp) +{ + struct essedma_priv *priv = dev_get_priv(dev); + struct edma_ring *erdr = &priv->rfd_ring; + struct edma_rrd *rrd; + u32 hw_tail; + u8 *rx_pkt; + + hw_tail = readl(priv->base + EDMA_REG_RFD_IDX_Q(EDMA_RXQ_ID)); + hw_tail = FIELD_GET(EDMA_RFD_CONS_IDX_MASK, hw_tail); + + if (hw_tail == erdr->tail) + return -EAGAIN; + + rx_pkt = net_rx_packets[erdr->tail]; + invalidate_dcache_range((unsigned long)rx_pkt, + (unsigned long)(rx_pkt + EDMA_MAX_PKT_SIZE)); + + rrd = (struct edma_rrd *)rx_pkt; + + /* Check if RRD is valid */ + if (!(rrd->rrd7 & EDMA_RRD7_DESC_VALID)) + return 0; + + *packetp = rx_pkt + EDMA_RRD_SIZE; + + /* get the packet size */ + return rrd->rrd6; +} + +static int ipq40xx_eth_free_pkt(struct udevice *dev, uchar *packet, + int length) +{ + struct essedma_priv *priv = dev_get_priv(dev); + struct edma_ring *erdr; + + erdr = &priv->rfd_ring; + + /* Update the producer index */ + writel(erdr->head, priv->base + EDMA_REG_RFD_IDX_Q(EDMA_RXQ_ID)); + + erdr->head++; + if (erdr->head == erdr->count) + erdr->head = 0; + + /* Update the consumer index */ + erdr->tail++; + if (erdr->tail == erdr->count) + erdr->tail = 0; + + writel(erdr->tail, + priv->base + EDMA_REG_RX_SW_CONS_IDX_Q(EDMA_RXQ_ID)); + + return 0; +} + +static int ipq40xx_eth_start(struct udevice *dev) +{ + struct essedma_priv *priv = dev_get_priv(dev); + + ipq40xx_edma_init_rfd_ring(priv); + + ipq40xx_edma_start_rx_tx(priv); + ess_switch_enable_lookup(&priv->esw); + + return 0; +} + +/* + * One TPD would be enough for sending a packet, however because the + * minimal cache line size is larger than the size of a TPD it is not + * possible to flush only one at once. To overcome this limitation + * multiple TPDs are used for sending a single packet. + */ +#define EDMA_TPDS_PER_PACKET 4 +#define EDMA_TPD_MIN_BYTES 4 +#define EDMA_MIN_PKT_SIZE (EDMA_TPDS_PER_PACKET * EDMA_TPD_MIN_BYTES) + +#define EDMA_TX_COMPLETE_TIMEOUT 1000000 + +static int ipq40xx_eth_send(struct udevice *dev, void *packet, int length) +{ + struct essedma_priv *priv = dev_get_priv(dev); + struct edma_tpd *first_tpd; + struct edma_tpd *tpds; + int i; + + if (length < EDMA_MIN_PKT_SIZE) + return 0; + + flush_dcache_range((unsigned long)(packet), + (unsigned long)(packet) + + roundup(length, ARCH_DMA_MINALIGN)); + + tpds = priv->tpd_ring.hw_desc; + for (i = 0; i < EDMA_TPDS_PER_PACKET; i++) { + struct edma_tpd *tpd; + void *frag; + + frag = packet + (i * EDMA_TPD_MIN_BYTES); + + /* get the next TPD */ + tpd = &tpds[priv->tpd_ring.head]; + if (i == 0) + first_tpd = tpd; + + /* update the software index */ + priv->tpd_ring.head++; + if (priv->tpd_ring.head == priv->tpd_ring.count) + priv->tpd_ring.head = 0; + + tpd->svlan_tag = 0; + tpd->addr = virt_to_phys(frag); + tpd->word3 = EDMA_PORT_ENABLE_ALL << EDMA_TPD_PORT_BITMAP_SHIFT; + + if (i < (EDMA_TPDS_PER_PACKET - 1)) { + tpd->len = EDMA_TPD_MIN_BYTES; + tpd->word1 = 0; + } else { + tpd->len = length; + tpd->word1 = 1 << EDMA_TPD_EOP_SHIFT; + } + + length -= EDMA_TPD_MIN_BYTES; + } + + /* make sure that memory writing completes */ + wmb(); + + flush_dcache_range((unsigned long)first_tpd, + (unsigned long)first_tpd + + EDMA_TPDS_PER_PACKET * sizeof(struct edma_tpd)); + + /* update the TX producer index */ + writel(priv->tpd_ring.head, + priv->base + EDMA_REG_TPD_IDX_Q(EDMA_TXQ_ID)); + + /* Wait for TX DMA completion */ + for (i = 0; i < EDMA_TX_COMPLETE_TIMEOUT; i++) { + u32 r, prod, cons; + + r = readl(priv->base + EDMA_REG_TPD_IDX_Q(EDMA_TXQ_ID)); + prod = FIELD_GET(EDMA_TPD_PROD_IDX_MASK, r); + cons = FIELD_GET(EDMA_TPD_CONS_IDX_MASK, r); + + if (cons == prod) + break; + + udelay(1); + } + + if (i == EDMA_TX_COMPLETE_TIMEOUT) + printf("TX timeout: packet not sent!\n"); + + /* update the software TX consumer index register */ + writel(priv->tpd_ring.head, + priv->base + EDMA_REG_TX_SW_CONS_IDX_Q(EDMA_TXQ_ID)); + + return 0; +} + +static void ipq40xx_eth_stop(struct udevice *dev) +{ + struct essedma_priv *priv = dev_get_priv(dev); + + ess_switch_disable_lookup(&priv->esw); + ipq40xx_edma_stop_rx_tx(priv); +} + +static void ipq40xx_edma_free_ring(struct edma_ring *ring) +{ + free(ring->hw_desc); +} + +/* + * Free Tx and Rx rings + */ +static void ipq40xx_edma_free_rings(struct essedma_priv *priv) +{ + ipq40xx_edma_free_ring(&priv->tpd_ring); + ipq40xx_edma_free_ring(&priv->rfd_ring); +} + +/* + * ipq40xx_edma_alloc_ring() + * allocate edma ring descriptor. + */ +static int ipq40xx_edma_alloc_ring(struct edma_ring *erd, + unsigned int desc_size) +{ + erd->head = 0; + erd->tail = 0; + + /* Alloc HW descriptors */ + erd->hw_size = roundup(desc_size * erd->count, + ARCH_DMA_MINALIGN); + + erd->hw_desc = memalign(CONFIG_SYS_CACHELINE_SIZE, erd->hw_size); + if (!erd->hw_desc) + return -ENOMEM; + + memset(erd->hw_desc, 0, erd->hw_size); + erd->dma = virt_to_phys(erd->hw_desc); + + return 0; + +} + +/* + * ipq40xx_allocate_tx_rx_rings() + */ +static int ipq40xx_edma_alloc_tx_rx_rings(struct essedma_priv *priv) +{ + int ret; + + ret = ipq40xx_edma_alloc_ring(&priv->tpd_ring, + sizeof(struct edma_tpd)); + if (ret) + return ret; + + ret = ipq40xx_edma_alloc_ring(&priv->rfd_ring, + sizeof(struct edma_rfd)); + if (ret) + goto err_free_tpd; + + return 0; + +err_free_tpd: + ipq40xx_edma_free_ring(&priv->tpd_ring); + return ret; +} + +static int ipq40xx_eth_write_hwaddr(struct udevice *dev) +{ + struct eth_pdata *pdata = dev_get_plat(dev); + struct essedma_priv *priv = dev_get_priv(dev); + unsigned char *mac = pdata->enetaddr; + u32 mac_lo, mac_hi; + + mac_hi = ((u32)mac[0]) << 8 | (u32)mac[1]; + mac_lo = ((u32)mac[2]) << 24 | ((u32)mac[3]) << 16 | + ((u32)mac[4]) << 8 | (u32)mac[5]; + + writel(mac_lo, priv->base + REG_MAC_CTRL0); + writel(mac_hi, priv->base + REG_MAC_CTRL1); + + return 0; +} + +static int edma_init(struct udevice *dev) +{ + struct essedma_priv *priv = dev_get_priv(dev); + int ret; + + priv->tpd_ring.count = IPQ40XX_EDMA_TX_RING_SIZE; + priv->rfd_ring.count = PKTBUFSRX; + + ret = ipq40xx_edma_alloc_tx_rx_rings(priv); + if (ret) + return -ENOMEM; + + ipq40xx_edma_stop_rx_tx(priv); + + /* Configure EDMA. */ + ipq40xx_edma_configure(priv); + + /* Configure descriptor Ring */ + ipq40xx_edma_init_desc(priv); + + ess_switch_disable_lookup(&priv->esw); + + return 0; +} + +static int essedma_probe(struct udevice *dev) +{ + struct essedma_priv *priv = dev_get_priv(dev); + int ret; + + priv->dev = dev; + + priv->base = dev_read_addr_name(dev, "edma"); + if (priv->base == FDT_ADDR_T_NONE) + return -EINVAL; + + priv->psgmii_base = dev_read_addr_name(dev, "psgmii_phy"); + if (priv->psgmii_base == FDT_ADDR_T_NONE) + return -EINVAL; + + priv->esw.base = dev_read_addr_name(dev, "base"); + if (priv->esw.base == FDT_ADDR_T_NONE) + return -EINVAL; + + ret = clk_get_by_name(dev, "ess", &priv->ess_clk); + if (ret) + return ret; + + ret = reset_get_by_name(dev, "ess", &priv->ess_rst); + if (ret) + return ret; + + ret = clk_enable(&priv->ess_clk); + if (ret) + return ret; + + ess_reset(dev); + + ret = uclass_get_device_by_driver(UCLASS_MDIO, + DM_DRIVER_GET(ipq4019_mdio), + &priv->mdio_dev); + if (ret) { + dev_dbg(dev, "Cant find IPQ4019 MDIO: %d\n", ret); + goto err; + } + + /* OF switch and PHY parsing and configuration */ + ret = essedma_of_switch(dev); + if (ret) + goto err; + + switch (priv->esw.port_wrapper_mode) { + case PHY_INTERFACE_MODE_PSGMII: + writel(PSGMIIPHY_PLL_VCO_VAL, + priv->psgmii_base + PSGMIIPHY_PLL_VCO_RELATED_CTRL); + writel(PSGMIIPHY_VCO_VAL, priv->psgmii_base + + PSGMIIPHY_VCO_CALIBRATION_CTRL_REGISTER_1); + /* wait for 10ms */ + mdelay(10); + writel(PSGMIIPHY_VCO_RST_VAL, priv->psgmii_base + + PSGMIIPHY_VCO_CALIBRATION_CTRL_REGISTER_1); + break; + case PHY_INTERFACE_MODE_RGMII: + writel(0x1, RGMII_TCSR_ESS_CFG); + writel(0x400, priv->esw.base + ESS_RGMII_CTRL); + break; + default: + printf("Unknown MII interface\n"); + } + + if (priv->esw.port_wrapper_mode == PHY_INTERFACE_MODE_PSGMII) + psgmii_self_test(dev); + + ess_switch_init(&priv->esw); + + ret = edma_init(dev); + if (ret) + goto err; + + return 0; + +err: + reset_assert(&priv->ess_rst); + clk_disable(&priv->ess_clk); + return ret; +} + +static int essedma_remove(struct udevice *dev) +{ + struct essedma_priv *priv = dev_get_priv(dev); + + ipq40xx_edma_free_rings(priv); + + clk_disable(&priv->ess_clk); + reset_assert(&priv->ess_rst); + + return 0; +} + +static const struct eth_ops essedma_eth_ops = { + .start = ipq40xx_eth_start, + .send = ipq40xx_eth_send, + .recv = ipq40xx_eth_recv, + .free_pkt = ipq40xx_eth_free_pkt, + .stop = ipq40xx_eth_stop, + .write_hwaddr = ipq40xx_eth_write_hwaddr, +}; + +static const struct udevice_id essedma_ids[] = { + { .compatible = "qcom,ipq4019-ess", }, + { } +}; + +U_BOOT_DRIVER(essedma) = { + .name = "essedma", + .id = UCLASS_ETH, + .of_match = essedma_ids, + .probe = essedma_probe, + .remove = essedma_remove, + .priv_auto = sizeof(struct essedma_priv), + .plat_auto = sizeof(struct eth_pdata), + .ops = &essedma_eth_ops, + .flags = DM_FLAG_ALLOC_PRIV_DMA, +}; diff --git a/drivers/net/essedma.h b/drivers/net/essedma.h new file mode 100644 index 00000000000..067cb442fb4 --- /dev/null +++ b/drivers/net/essedma.h @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2020 Sartura Ltd. + * + * Author: Robert Marko <robert.marko@sartura.hr> + * + * Copyright (c) 2021 Toco Technologies FZE <contact@toco.ae> + * Copyright (c) 2021 Gabor Juhos <j4g8y7@gmail.com> + * + * Qualcomm ESS EDMA ethernet driver + */ + +#ifndef _ESSEDMA_ETH_H +#define _ESSEDMA_ETH_H + +#define ESS_PORTS_NUM 6 + +#define ESS_RGMII_CTRL 0x4 + +#define ESS_GLOBAL_FW_CTRL1 0x624 + +#define ESS_PORT0_STATUS 0x7c +#define ESS_PORT_SPEED_MASK GENMASK(1, 0) +#define ESS_PORT_SPEED_1000 3 +#define ESS_PORT_SPEED_100 2 +#define ESS_PORT_SPEED_10 1 +#define ESS_PORT_TXMAC_EN BIT(2) +#define ESS_PORT_RXMAC_EN BIT(3) +#define ESS_PORT_TX_FLOW_EN BIT(4) +#define ESS_PORT_RX_FLOW_EN BIT(5) +#define ESS_PORT_DUPLEX_MODE BIT(6) + +#define ESS_PORT_LOOKUP_CTRL(_p) (0x660 + (_p) * 12) +#define ESS_PORT_LOOP_BACK_EN BIT(21) +#define ESS_PORT_VID_MEM_MASK GENMASK(6, 0) + +#define ESS_PORT_HOL_CTRL0(_p) (0x970 + (_p) * 8) +#define EG_PORT_QUEUE_NUM_MASK GENMASK(29, 24) + +/* Ports 0 and 5 have queues 0-5 + * Ports 1 to 4 have queues 0-3 + */ +#define EG_PRI5_QUEUE_NUM_MASK GENMASK(23, 20) +#define EG_PRI4_QUEUE_NUM_MASK GENMASK(19, 16) +#define EG_PRI3_QUEUE_NUM_MASK GENMASK(15, 12) +#define EG_PRI2_QUEUE_NUM_MASK GENMASK(11, 8) +#define EG_PRI1_QUEUE_NUM_MASK GENMASK(7, 4) +#define EG_PRI0_QUEUE_NUM_MASK GENMASK(3, 0) + +#define ESS_PORT_HOL_CTRL1(_p) (0x974 + (_p) * 8) +#define ESS_ING_BUF_NUM_0_MASK GENMASK(3, 0) + +/* QCA807x PHY registers */ +#define QCA807X_CHIP_CONFIGURATION 0x1f +#define QCA807X_MEDIA_PAGE_SELECT BIT(15) + +#define QCA807X_POWER_DOWN BIT(11) + +#define QCA807X_FUNCTION_CONTROL 0x10 +#define QCA807X_MDI_CROSSOVER_MODE_MASK GENMASK(6, 5) +#define QCA807X_MDI_CROSSOVER_MODE_MANUAL_MDI 0 +#define QCA807X_POLARITY_REVERSAL BIT(1) + +#define QCA807X_PHY_SPECIFIC 0x11 +#define QCA807X_PHY_SPECIFIC_LINK BIT(10) + +#define QCA807X_MMD7_CRC_PACKET_COUNTER 0x8029 +#define QCA807X_MMD7_PACKET_COUNTER_SELFCLR BIT(1) +#define QCA807X_MMD7_CRC_PACKET_COUNTER_EN BIT(0) +#define QCA807X_MMD7_VALID_EGRESS_COUNTER_2 0x802e + +/* PSGMII specific registers */ +#define PSGMIIPHY_VCO_CALIBRATION_CTRL_REGISTER_1 0x9c +#define PSGMIIPHY_VCO_VAL 0x4ada +#define PSGMIIPHY_VCO_RST_VAL 0xada +#define PSGMIIPHY_VCO_CALIBRATION_CTRL_REGISTER_2 0xa0 + +#define PSGMIIPHY_PLL_VCO_RELATED_CTRL 0x78c +#define PSGMIIPHY_PLL_VCO_VAL 0x2803 + +#define RGMII_TCSR_ESS_CFG 0x01953000 + +/* EDMA registers */ +#define IPQ40XX_EDMA_TX_RING_SIZE 8 +#define IPQ40XX_EDMA_RSS_TYPE_NONE 0x1 + +#define EDMA_RSS_TYPE 0 +#define EDMA_TPD_EOP_SHIFT 31 + +/* tpd word 3 bit 18-28 */ +#define EDMA_TPD_PORT_BITMAP_SHIFT 18 + +/* Enable Tx for all ports */ +#define EDMA_PORT_ENABLE_ALL 0x3E + +/* Edma receive consumer index */ +/* x = queue id */ +#define EDMA_REG_RX_SW_CONS_IDX_Q(x) (0x220 + ((x) << 2)) +/* Edma transmit consumer index */ +#define EDMA_REG_TX_SW_CONS_IDX_Q(x) (0x240 + ((x) << 2)) +/* TPD Index Register */ +#define EDMA_REG_TPD_IDX_Q(x) (0x460 + ((x) << 2)) +/* Tx Descriptor Control Register */ +#define EDMA_REG_TPD_RING_SIZE 0x41C +#define EDMA_TPD_RING_SIZE_MASK 0xFFFF + +/* Transmit descriptor base address */ + /* x = queue id */ +#define EDMA_REG_TPD_BASE_ADDR_Q(x) (0x420 + ((x) << 2)) +#define EDMA_TPD_PROD_IDX_MASK GENMASK(15, 0) +#define EDMA_TPD_CONS_IDX_MASK GENMASK(31, 16) + +#define EDMA_REG_TX_SRAM_PART 0x400 +#define EDMA_LOAD_PTR_SHIFT 16 + +/* TXQ Control Register */ +#define EDMA_REG_TXQ_CTRL 0x404 +#define EDMA_TXQ_CTRL_TXQ_EN 0x20 +#define EDMA_TXQ_CTRL_TPD_BURST_EN 0x100 +#define EDMA_TXQ_NUM_TPD_BURST_SHIFT 0 +#define EDMA_TXQ_TXF_BURST_NUM_SHIFT 16 +#define EDMA_TXF_BURST 0x100 +#define EDMA_TPD_BURST 5 + +#define EDMA_REG_TXF_WATER_MARK 0x408 + +/* RSS Indirection Register */ +/* x = No. of indirection table */ +#define EDMA_REG_RSS_IDT(x) (0x840 + ((x) << 2)) +#define EDMA_NUM_IDT 16 +#define EDMA_RSS_IDT_VALUE 0x64206420 + +/* RSS Hash Function Type Register */ +#define EDMA_REG_RSS_TYPE 0x894 + +/* x = queue id */ +#define EDMA_REG_RFD_BASE_ADDR_Q(x) (0x950 + ((x) << 2)) +/* RFD Index Register */ +#define EDMA_RFD_BURST 8 +#define EDMA_RFD_THR 16 +#define EDMA_RFD_LTHR 0 +#define EDMA_REG_RFD_IDX_Q(x) (0x9B0 + ((x) << 2)) + +#define EDMA_RFD_CONS_IDX_MASK GENMASK(27, 16) + +/* Rx Descriptor Control Register */ +#define EDMA_REG_RX_DESC0 0xA10 +#define EDMA_RFD_RING_SIZE_MASK 0xFFF +#define EDMA_RX_BUF_SIZE_MASK 0xFFFF +#define EDMA_RFD_RING_SIZE_SHIFT 0 +#define EDMA_RX_BUF_SIZE_SHIFT 16 + +#define EDMA_REG_RX_DESC1 0xA14 +#define EDMA_RXQ_RFD_BURST_NUM_SHIFT 0 +#define EDMA_RXQ_RFD_PF_THRESH_SHIFT 8 +#define EDMA_RXQ_RFD_LOW_THRESH_SHIFT 16 + +/* RXQ Control Register */ +#define EDMA_REG_RXQ_CTRL 0xA18 +#define EDMA_FIFO_THRESH_128_BYTE 0x0 +#define EDMA_RXQ_CTRL_RMV_VLAN 0x00000002 +#define EDMA_RXQ_CTRL_EN 0x0000FF00 + +/* MAC Control Register */ +#define REG_MAC_CTRL0 0xC20 +#define REG_MAC_CTRL1 0xC24 + +/* Transmit Packet Descriptor */ +struct edma_tpd { + u16 len; /* full packet including CRC */ + u16 svlan_tag; /* vlan tag */ + u32 word1; /* byte 4-7 */ + u32 addr; /* address of buffer */ + u32 word3; /* byte 12 */ +}; + +/* Receive Return Descriptor */ +struct edma_rrd { + u16 rrd0; + u16 rrd1; + u16 rrd2; + u16 rrd3; + u16 rrd4; + u16 rrd5; + u16 rrd6; + u16 rrd7; +} __packed; + +#define EDMA_RRD_SIZE sizeof(struct edma_rrd) + +#define EDMA_RRD7_DESC_VALID BIT(15) + +/* Receive Free Descriptor */ +struct edma_rfd { + u32 buffer_addr; /* buffer address */ +}; + +#endif /* _ESSEDMA_ETH_H */ diff --git a/drivers/power/regulator/Kconfig b/drivers/power/regulator/Kconfig index 102ec7bc5f8..bc061c20d75 100644 --- a/drivers/power/regulator/Kconfig +++ b/drivers/power/regulator/Kconfig @@ -216,6 +216,14 @@ config DM_REGULATOR_GPIO features for gpio regulators. The driver implements get/set for voltage value. +config DM_REGULATOR_QCOM_RPMH + bool "Enable driver model for Qualcomm RPMh regulator" + depends on DM_REGULATOR && QCOM_RPMH + ---help--- + Enable support for the Qualcomm RPMh regulator. The driver + implements get/set api for a limited set of regulators used + by u-boot. + config SPL_DM_REGULATOR_GPIO bool "Enable Driver Model for GPIO REGULATOR in SPL" depends on DM_REGULATOR_GPIO && SPL_GPIO diff --git a/drivers/power/regulator/Makefile b/drivers/power/regulator/Makefile index f79932d8330..56a527612b7 100644 --- a/drivers/power/regulator/Makefile +++ b/drivers/power/regulator/Makefile @@ -21,6 +21,7 @@ obj-$(CONFIG_$(SPL_)DM_REGULATOR_FAN53555) += fan53555.o obj-$(CONFIG_$(SPL_)DM_REGULATOR_COMMON) += regulator_common.o obj-$(CONFIG_$(SPL_)DM_REGULATOR_FIXED) += fixed.o obj-$(CONFIG_$(SPL_)DM_REGULATOR_GPIO) += gpio-regulator.o +obj-$(CONFIG_DM_REGULATOR_QCOM_RPMH) += qcom-rpmh-regulator.o obj-$(CONFIG_$(SPL_TPL_)REGULATOR_RK8XX) += rk8xx.o obj-$(CONFIG_DM_REGULATOR_S2MPS11) += s2mps11_regulator.o obj-$(CONFIG_REGULATOR_S5M8767) += s5m8767.o diff --git a/drivers/power/regulator/qcom-rpmh-regulator.c b/drivers/power/regulator/qcom-rpmh-regulator.c new file mode 100644 index 00000000000..06fd3f31956 --- /dev/null +++ b/drivers/power/regulator/qcom-rpmh-regulator.c @@ -0,0 +1,544 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2018-2021, The Linux Foundation. All rights reserved. +// Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved. + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include <linux/err.h> +#include <dm/device_compat.h> +#include <dm/device.h> +#include <dm/devres.h> +#include <dm/lists.h> +#include <power/regulator.h> +#include <log.h> + +#include <soc/qcom/cmd-db.h> +#include <soc/qcom/rpmh.h> + +#include <dt-bindings/regulator/qcom,rpmh-regulator.h> + +/** + * enum rpmh_regulator_type - supported RPMh accelerator types + * @VRM: RPMh VRM accelerator which supports voting on enable, voltage, + * and mode of LDO, SMPS, and BOB type PMIC regulators. + * @XOB: RPMh XOB accelerator which supports voting on the enable state + * of PMIC regulators. + */ +enum rpmh_regulator_type { + VRM, + XOB, +}; + +enum rpmh_regulator_mode { + REGULATOR_MODE_RETENTION, + REGULATOR_MODE_LPM, + REGULATOR_MODE_AUTO, + REGULATOR_MODE_HPM, +}; + +#define RPMH_REGULATOR_REG_VRM_VOLTAGE 0x0 +#define RPMH_REGULATOR_REG_ENABLE 0x4 +#define RPMH_REGULATOR_REG_VRM_MODE 0x8 + +#define PMIC4_LDO_MODE_RETENTION 4 +#define PMIC4_LDO_MODE_LPM 5 +#define PMIC4_LDO_MODE_HPM 7 + +#define PMIC4_SMPS_MODE_RETENTION 4 +#define PMIC4_SMPS_MODE_PFM 5 +#define PMIC4_SMPS_MODE_AUTO 6 +#define PMIC4_SMPS_MODE_PWM 7 + +#define PMIC4_BOB_MODE_PASS 0 +#define PMIC4_BOB_MODE_PFM 1 +#define PMIC4_BOB_MODE_AUTO 2 +#define PMIC4_BOB_MODE_PWM 3 + +#define PMIC5_LDO_MODE_RETENTION 3 +#define PMIC5_LDO_MODE_LPM 4 +#define PMIC5_LDO_MODE_HPM 7 + +#define PMIC5_SMPS_MODE_RETENTION 3 +#define PMIC5_SMPS_MODE_PFM 4 +#define PMIC5_SMPS_MODE_AUTO 6 +#define PMIC5_SMPS_MODE_PWM 7 + +#define PMIC5_BOB_MODE_PASS 2 +#define PMIC5_BOB_MODE_PFM 4 +#define PMIC5_BOB_MODE_AUTO 6 +#define PMIC5_BOB_MODE_PWM 7 + + +/** + * struct linear_range - table of selector - value pairs + * + * Define a lookup-table for range of values. Intended to help when looking + * for a register value matching certaing physical measure (like voltage). + * Usable when increment of one in register always results a constant increment + * of the physical measure (like voltage). + * + * @min: Lowest value in range + * @min_sel: Lowest selector for range + * @max_sel: Highest selector for range + * @step: Value step size + */ +struct linear_range { + unsigned int min; + unsigned int min_sel; + unsigned int max_sel; + unsigned int step; +}; + +/* Initialize struct linear_range for regulators */ +#define REGULATOR_LINEAR_RANGE(_min_uV, _min_sel, _max_sel, _step_uV) \ +{ \ + .min = _min_uV, \ + .min_sel = _min_sel, \ + .max_sel = _max_sel, \ + .step = _step_uV, \ +} + +/** + * struct rpmh_vreg_hw_data - RPMh regulator hardware configurations + * @regulator_type: RPMh accelerator type used to manage this + * regulator + * @ops: Pointer to regulator ops callback structure + * @voltage_range: The single range of voltages supported by this + * PMIC regulator type + * @n_voltages: The number of unique voltage set points defined + * by voltage_range + * @hpm_min_load_uA: Minimum load current in microamps that requires + * high power mode (HPM) operation. This is used + * for LDO hardware type regulators only. + * @pmic_mode_map: Array indexed by regulator framework mode + * containing PMIC hardware modes. Must be large + * enough to index all framework modes supported + * by this regulator hardware type. + * @of_map_mode: Maps an RPMH_REGULATOR_MODE_* mode value defined + * in device tree to a regulator framework mode + */ +struct rpmh_vreg_hw_data { + enum rpmh_regulator_type regulator_type; + const struct dm_regulator_ops *ops; + struct linear_range voltage_range; + int n_voltages; + int hpm_min_load_uA; + struct dm_regulator_mode *pmic_mode_map; + int n_modes; + unsigned int (*of_map_mode)(unsigned int mode); +}; + +/** + * struct rpmh_vreg - individual RPMh regulator data structure encapsulating a + * single regulator device + * @dev: Device pointer for the top-level PMIC RPMh + * regulator parent device. This is used as a + * handle in RPMh write requests. + * @addr: Base address of the regulator resource within + * an RPMh accelerator + * @rdesc: Regulator descriptor + * @hw_data: PMIC regulator configuration data for this RPMh + * regulator + * @always_wait_for_ack: Boolean flag indicating if a request must always + * wait for an ACK from RPMh before continuing even + * if it corresponds to a strictly lower power + * state (e.g. enabled --> disabled). + * @enabled: Flag indicating if the regulator is enabled or + * not + * @bypassed: Boolean indicating if the regulator is in + * bypass (pass-through) mode or not. This is + * only used by BOB rpmh-regulator resources. + * @uv: Selector used for get_voltage_sel() and + * set_value() callbacks + * @mode: RPMh VRM regulator current framework mode + */ +struct rpmh_vreg { + struct udevice *dev; + u32 addr; + const struct rpmh_vreg_hw_data *hw_data; + bool always_wait_for_ack; + + int enabled; + bool bypassed; + int uv; + int mode; +}; + +/** + * struct rpmh_vreg_init_data - initialization data for an RPMh regulator + * @name: Name for the regulator which also corresponds + * to the device tree subnode name of the regulator + * @resource_name: RPMh regulator resource name format string. + * This must include exactly one field: '%s' which + * is filled at run-time with the PMIC ID provided + * by device tree property qcom,pmic-id. Example: + * "ldo%s1" for RPMh resource "ldoa1". + * @supply_name: Parent supply regulator name + * @hw_data: Configuration data for this PMIC regulator type + */ +struct rpmh_vreg_init_data { + const char *name; + const char *resource_name; + const char *supply_name; + const struct rpmh_vreg_hw_data *hw_data; +}; + +/** + * rpmh_regulator_send_request() - send the request to RPMh + * @vreg: Pointer to the RPMh regulator + * @cmd: Pointer to the RPMh command to send + * @wait_for_ack: Boolean indicating if execution must wait until the + * request has been acknowledged as complete + * + * Return: 0 on success, errno on failure + */ +static int rpmh_regulator_send_request(struct rpmh_vreg *vreg, + const struct tcs_cmd *cmd, bool wait_for_ack) +{ + int ret; + + if (wait_for_ack || vreg->always_wait_for_ack) + ret = rpmh_write(vreg->dev->parent, RPMH_ACTIVE_ONLY_STATE, cmd, 1); + else + ret = rpmh_write_async(vreg->dev->parent, RPMH_ACTIVE_ONLY_STATE, cmd, 1); + + return ret; +} + +static int _rpmh_regulator_vrm_set_value(struct udevice *rdev, + int uv, bool wait_for_ack) +{ + struct rpmh_vreg *vreg = dev_get_priv(rdev); + struct tcs_cmd cmd = { + .addr = vreg->addr + RPMH_REGULATOR_REG_VRM_VOLTAGE, + }; + int ret; + unsigned int selector; + + selector = (uv - vreg->hw_data->voltage_range.min) / vreg->hw_data->voltage_range.step; + cmd.data = DIV_ROUND_UP(vreg->hw_data->voltage_range.min + + selector * vreg->hw_data->voltage_range.step, 1000); + + ret = rpmh_regulator_send_request(vreg, &cmd, wait_for_ack); + if (!ret) + vreg->uv = cmd.data * 1000; + + return ret; +} + +static int rpmh_regulator_vrm_set_value(struct udevice *rdev, + int uv) +{ + struct rpmh_vreg *vreg = dev_get_priv(rdev); + + debug("%s: set_value %d (current %d)\n", rdev->name, uv, vreg->uv); + + if (vreg->enabled == -EINVAL) { + /* + * Cache the voltage and send it later when the regulator is + * enabled or disabled. + */ + vreg->uv = uv; + return 0; + } + + return _rpmh_regulator_vrm_set_value(rdev, uv, + uv > vreg->uv); +} + +static int rpmh_regulator_vrm_get_value(struct udevice *rdev) +{ + struct rpmh_vreg *vreg = dev_get_priv(rdev); + + debug("%s: get_value %d\n", rdev->name, vreg->uv); + + return vreg->uv; +} + +static int rpmh_regulator_is_enabled(struct udevice *rdev) +{ + struct rpmh_vreg *vreg = dev_get_priv(rdev); + + debug("%s: is_enabled %d\n", rdev->name, vreg->enabled); + + return vreg->enabled > 0; +} + +static int rpmh_regulator_set_enable_state(struct udevice *rdev, + bool enable) +{ + struct rpmh_vreg *vreg = dev_get_priv(rdev); + struct tcs_cmd cmd = { + .addr = vreg->addr + RPMH_REGULATOR_REG_ENABLE, + .data = enable, + }; + int ret; + + debug("%s: set_enable %d (current %d)\n", rdev->name, enable, + vreg->enabled); + + if (vreg->enabled == -EINVAL && + vreg->uv != -ENOTRECOVERABLE) { + ret = _rpmh_regulator_vrm_set_value(rdev, + vreg->uv, true); + if (ret < 0) + return ret; + } + + ret = rpmh_regulator_send_request(vreg, &cmd, enable); + if (!ret) + vreg->enabled = enable; + + return ret; +} + +static int rpmh_regulator_vrm_set_mode_bypass(struct rpmh_vreg *vreg, + unsigned int mode, bool bypassed) +{ + struct tcs_cmd cmd = { + .addr = vreg->addr + RPMH_REGULATOR_REG_VRM_MODE, + }; + struct dm_regulator_mode *pmic_mode; + int i; + + if (mode > REGULATOR_MODE_HPM) + return -EINVAL; + + for (i = 0; i < vreg->hw_data->n_modes; i++) { + pmic_mode = &vreg->hw_data->pmic_mode_map[i]; + if (pmic_mode->id == mode) + break; + } + if (pmic_mode->id != mode) { + printf("Invalid mode %d\n", mode); + return -EINVAL; + } + + if (bypassed) + cmd.data = PMIC4_BOB_MODE_PASS; + else + cmd.data = pmic_mode->id; + + return rpmh_regulator_send_request(vreg, &cmd, true); +} + +static int rpmh_regulator_vrm_set_mode(struct udevice *rdev, + int mode) +{ + struct rpmh_vreg *vreg = dev_get_priv(rdev); + int ret; + + debug("%s: set_mode %d (current %d)\n", rdev->name, mode, vreg->mode); + + if (mode == vreg->mode) + return 0; + + ret = rpmh_regulator_vrm_set_mode_bypass(vreg, mode, vreg->bypassed); + if (!ret) + vreg->mode = mode; + + return ret; +} + +static int rpmh_regulator_vrm_get_mode(struct udevice *rdev) +{ + struct rpmh_vreg *vreg = dev_get_priv(rdev); + + debug("%s: get_mode %d\n", rdev->name, vreg->mode); + + return vreg->mode; +} +static const struct dm_regulator_ops rpmh_regulator_vrm_drms_ops = { + .get_value = rpmh_regulator_vrm_get_value, + .set_value = rpmh_regulator_vrm_set_value, + .set_enable = rpmh_regulator_set_enable_state, + .get_enable = rpmh_regulator_is_enabled, + .set_mode = rpmh_regulator_vrm_set_mode, + .get_mode = rpmh_regulator_vrm_get_mode, +}; + +static struct dm_regulator_mode pmic_mode_map_pmic5_ldo[] = { + { + .id = REGULATOR_MODE_RETENTION, + .register_value = PMIC5_LDO_MODE_RETENTION, + .name = "PMIC5_LDO_MODE_RETENTION" + }, { + .id = REGULATOR_MODE_LPM, + .register_value = PMIC5_LDO_MODE_LPM, + .name = "PMIC5_LDO_MODE_LPM" + }, { + .id = REGULATOR_MODE_HPM, + .register_value = PMIC5_LDO_MODE_HPM, + .name = "PMIC5_LDO_MODE_HPM" + }, +}; + +static const struct rpmh_vreg_hw_data pmic5_pldo = { + .regulator_type = VRM, + .ops = &rpmh_regulator_vrm_drms_ops, + .voltage_range = REGULATOR_LINEAR_RANGE(1504000, 0, 255, 8000), + .n_voltages = 256, + .hpm_min_load_uA = 10000, + .pmic_mode_map = pmic_mode_map_pmic5_ldo, + .n_modes = ARRAY_SIZE(pmic_mode_map_pmic5_ldo), +}; + +static const struct rpmh_vreg_hw_data pmic5_pldo_lv = { + .regulator_type = VRM, + .ops = &rpmh_regulator_vrm_drms_ops, + .voltage_range = REGULATOR_LINEAR_RANGE(1504000, 0, 62, 8000), + .n_voltages = 63, + .hpm_min_load_uA = 10000, + .pmic_mode_map = pmic_mode_map_pmic5_ldo, + .n_modes = ARRAY_SIZE(pmic_mode_map_pmic5_ldo), +}; + +#define RPMH_VREG(_name, _resource_name, _hw_data, _supply_name) \ +{ \ + .name = _name, \ + .resource_name = _resource_name, \ + .hw_data = _hw_data, \ + .supply_name = _supply_name, \ +} + +static const struct rpmh_vreg_init_data pm8150_vreg_data[] = { + RPMH_VREG("ldo13", "ldo%s13", &pmic5_pldo, "vdd-l13-l16-l17"), + {} +}; + +static const struct rpmh_vreg_init_data pm8150l_vreg_data[] = { + RPMH_VREG("ldo1", "ldo%s1", &pmic5_pldo_lv, "vdd-l1-l8"), + RPMH_VREG("ldo11", "ldo%s11", &pmic5_pldo, "vdd-l7-l11"), + {} +}; + +/* probe an individual regulator */ +static int rpmh_regulator_probe(struct udevice *dev) +{ + const struct rpmh_vreg_init_data *init_data; + struct rpmh_vreg *priv; + struct dm_regulator_uclass_plat *plat_data; + + init_data = (const struct rpmh_vreg_init_data *)dev_get_driver_data(dev); + priv = dev_get_priv(dev); + plat_data = dev_get_uclass_plat(dev); + + priv->dev = dev; + priv->addr = cmd_db_read_addr(dev->name); + if (!priv->addr) { + dev_err(dev, "Failed to read RPMh address for %s\n", dev->name); + return -ENODEV; + } + + priv->hw_data = init_data->hw_data; + priv->enabled = -EINVAL; + priv->uv = -ENOTRECOVERABLE; + if (ofnode_read_u32(dev_ofnode(dev), "regulator-initial-mode", &priv->mode)) + priv->mode = -EINVAL; + + plat_data->mode = priv->hw_data->pmic_mode_map; + plat_data->mode_count = priv->hw_data->n_modes; + + return 0; +} + +/* for non-drm, xob, or bypass regulators add additional driver definitions */ +U_BOOT_DRIVER(rpmh_regulator_drm) = { + .name = "rpmh_regulator_drm", + .id = UCLASS_REGULATOR, + .probe = rpmh_regulator_probe, + .priv_auto = sizeof(struct rpmh_vreg), + .ops = &rpmh_regulator_vrm_drms_ops, +}; + +/* This driver intentionally only supports a subset of the available regulators. + * This function checks to see if a given regulator node in DT matches a regulator + * defined in the driver. + */ +static const struct rpmh_vreg_init_data * +vreg_get_init_data(const struct rpmh_vreg_init_data *init_data, ofnode node) +{ + const struct rpmh_vreg_init_data *data; + + for (data = init_data; data->name; data++) { + if (!strcmp(data->name, ofnode_get_name(node))) + return data; + } + + return NULL; +} + +static int rpmh_regulators_bind(struct udevice *dev) +{ + const struct rpmh_vreg_init_data *init_data, *data; + const char *pmic_id; + char *name; + struct driver *drv; + ofnode node; + int ret; + size_t namelen; + + init_data = (const struct rpmh_vreg_init_data *)dev_get_driver_data(dev); + if (!init_data) { + dev_err(dev, "No RPMh regulator init data\n"); + return -ENODEV; + } + + pmic_id = ofnode_read_string(dev_ofnode(dev), "qcom,pmic-id"); + if (!pmic_id) { + dev_err(dev, "No PMIC ID\n"); + return -ENODEV; + } + + drv = lists_driver_lookup_name("rpmh_regulator_drm"); + + ofnode_for_each_subnode(node, dev_ofnode(dev)) { + data = vreg_get_init_data(init_data, node); + if (!data) + continue; + + /* %s is replaced with pmic_id, so subtract 2, then add 1 for the null terminator */ + namelen = strlen(data->resource_name) + strlen(pmic_id) - 1; + name = devm_kzalloc(dev, namelen, GFP_KERNEL); + ret = snprintf(name, namelen, data->resource_name, pmic_id); + if (ret < 0 || ret >= namelen) { + dev_err(dev, "Failed to create RPMh regulator name\n"); + return -ENOMEM; + } + + ret = device_bind_with_driver_data(dev, drv, name, (ulong)data, + node, NULL); + if (ret < 0) { + dev_err(dev, "Failed to bind RPMh regulator %s: %d\n", name, ret); + return ret; + } + } + + return 0; +} + +static const struct udevice_id rpmh_regulator_ids[] = { + { + .compatible = "qcom,pm8150-rpmh-regulators", + .data = (ulong)pm8150_vreg_data, + }, + { + .compatible = "qcom,pm8150l-rpmh-regulators", + .data = (ulong)pm8150l_vreg_data, + }, + { /* sentinal */ }, +}; + +/* Driver for a 'bank' of regulators. This creates devices for each + * individual regulator + */ +U_BOOT_DRIVER(rpmh_regulators) = { + .name = "rpmh_regulators", + .id = UCLASS_MISC, + .bind = rpmh_regulators_bind, + .of_match = rpmh_regulator_ids, + .ops = &rpmh_regulator_vrm_drms_ops, +}; + +MODULE_DESCRIPTION("Qualcomm RPMh regulator driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/rng/Kconfig b/drivers/rng/Kconfig index 5758ae192a6..b35d8c66b9c 100644 --- a/drivers/rng/Kconfig +++ b/drivers/rng/Kconfig @@ -120,4 +120,17 @@ config RNG_TURRIS_RWTM on other Armada-3700 devices (like EspressoBin) if Secure Firmware from CZ.NIC is used. +config RNG_EXYNOS + bool "Samsung Exynos True Random Number Generator support" + depends on DM_RNG + help + Enable support for True Random Number Generator (TRNG) available on + Exynos SoCs. + + On some chips (like Exynos850) TRNG registers are protected with TZPC + (TrustZone Protection Control). For such chips the driver provides an + implementation based on SMC calls to EL3 monitor program. In that + case the LDFW (Loadable Firmware) has to be loaded first, as it + actually implements TRNG SMC calls. + endif diff --git a/drivers/rng/Makefile b/drivers/rng/Makefile index c1f1c616e00..30553c9d6e9 100644 --- a/drivers/rng/Makefile +++ b/drivers/rng/Makefile @@ -18,3 +18,4 @@ obj-$(CONFIG_RNG_ARM_RNDR) += arm_rndr.o obj-$(CONFIG_TPM_RNG) += tpm_rng.o obj-$(CONFIG_RNG_JH7110) += jh7110_rng.o obj-$(CONFIG_RNG_TURRIS_RWTM) += turris_rwtm_rng.o +obj-$(CONFIG_RNG_EXYNOS) += exynos-trng.o diff --git a/drivers/rng/exynos-trng.c b/drivers/rng/exynos-trng.c new file mode 100644 index 00000000000..d2479d244ed --- /dev/null +++ b/drivers/rng/exynos-trng.c @@ -0,0 +1,291 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2024 Linaro Ltd. + * Author: Sam Protsenko <semen.protsenko@linaro.org> + * + * Samsung Exynos TRNG driver (True Random Number Generator). + */ + +#include <clk.h> +#include <dm.h> +#include <rng.h> +#include <dm/device.h> +#include <dm/device_compat.h> +#include <asm/io.h> +#include <linux/arm-smccc.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/iopoll.h> +#include <linux/time.h> + +#define EXYNOS_TRNG_CLKDIV 0x0 +#define EXYNOS_TRNG_CLKDIV_MASK GENMASK(15, 0) +#define EXYNOS_TRNG_CLOCK_RATE 500000 + +#define EXYNOS_TRNG_CTRL 0x20 +#define EXYNOS_TRNG_CTRL_RNGEN BIT(31) + +#define EXYNOS_TRNG_POST_CTRL 0x30 +#define EXYNOS_TRNG_ONLINE_CTRL 0x40 +#define EXYNOS_TRNG_ONLINE_STAT 0x44 +#define EXYNOS_TRNG_ONLINE_MAXCHI2 0x48 +#define EXYNOS_TRNG_FIFO_CTRL 0x50 +#define EXYNOS_TRNG_FIFO_0 0x80 +#define EXYNOS_TRNG_FIFO_1 0x84 +#define EXYNOS_TRNG_FIFO_2 0x88 +#define EXYNOS_TRNG_FIFO_3 0x8c +#define EXYNOS_TRNG_FIFO_4 0x90 +#define EXYNOS_TRNG_FIFO_5 0x94 +#define EXYNOS_TRNG_FIFO_6 0x98 +#define EXYNOS_TRNG_FIFO_7 0x9c +#define EXYNOS_TRNG_FIFO_LEN 8 +#define EXYNOS_TRNG_FIFO_TIMEOUT (1 * USEC_PER_SEC) + +#define EXYNOS_SMC_CALL_VAL(func_num) \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \ + ARM_SMCCC_SMC_32, \ + ARM_SMCCC_OWNER_SIP, \ + func_num) + +/* SMC command for DTRNG access */ +#define SMC_CMD_RANDOM EXYNOS_SMC_CALL_VAL(0x1012) + +/* SMC_CMD_RANDOM: arguments */ +#define HWRNG_INIT 0x0 +#define HWRNG_EXIT 0x1 +#define HWRNG_GET_DATA 0x2 + +/* SMC_CMD_RANDOM: return values */ +#define HWRNG_RET_OK 0x0 +#define HWRNG_RET_RETRY_ERROR 0x2 + +#define HWRNG_MAX_TRIES 100 + +/** + * struct exynos_trng_variant - Chip specific data + * + * @smc: Set "true" if TRNG block has to be accessed via SMC calls + * @init: (Optional) TRNG initialization function to call on probe + * @exit: (Optional) TRNG deinitialization function to call on remove + * @read: Function to read the random data from TRNG block + */ +struct exynos_trng_variant { + bool smc; + int (*init)(struct udevice *dev); + void (*exit)(struct udevice *dev); + int (*read)(struct udevice *dev, void *data, size_t len); +}; + +/** + * struct exynos_trng_priv - Driver's private data + * + * @base: Base address of MMIO registers of TRNG block + * @clk: Operating clock (needed for TRNG block functioning) + * @pclk: Bus clock (needed for interfacing the TRNG block registers) + * @data: Chip specific data + */ +struct exynos_trng_priv { + void __iomem *base; + struct clk *clk; + struct clk *pclk; + const struct exynos_trng_variant *data; +}; + +static int exynos_trng_read_reg(struct udevice *dev, void *data, size_t len) +{ + struct exynos_trng_priv *trng = dev_get_priv(dev); + int val; + + len = min_t(size_t, len, EXYNOS_TRNG_FIFO_LEN * 4); + writel_relaxed(len * 8, trng->base + EXYNOS_TRNG_FIFO_CTRL); + val = readl_poll_timeout(trng->base + EXYNOS_TRNG_FIFO_CTRL, val, + val == 0, EXYNOS_TRNG_FIFO_TIMEOUT); + if (val < 0) + return val; + + memcpy_fromio(data, trng->base + EXYNOS_TRNG_FIFO_0, len); + + return 0; +} + +static int exynos_trng_read_smc(struct udevice *dev, void *data, size_t len) +{ + struct arm_smccc_res res; + unsigned int copied = 0; + u32 *buf = data; + int tries = 0; + + while (copied < len) { + arm_smccc_smc(SMC_CMD_RANDOM, HWRNG_GET_DATA, 0, 0, 0, 0, 0, 0, + &res); + switch (res.a0) { + case HWRNG_RET_OK: + *buf++ = res.a2; + *buf++ = res.a3; + copied += 8; + tries = 0; + break; + case HWRNG_RET_RETRY_ERROR: + if (++tries >= HWRNG_MAX_TRIES) + return -EIO; + udelay(10); + break; + default: + return -EIO; + } + } + + return 0; +} + +static int exynos_trng_init_reg(struct udevice *dev) +{ + const u32 max_div = EXYNOS_TRNG_CLKDIV_MASK; + struct exynos_trng_priv *trng = dev_get_priv(dev); + unsigned long sss_rate; + u32 div; + + sss_rate = clk_get_rate(trng->clk); + + /* + * For most TRNG circuits the clock frequency of under 500 kHz is safe. + * The clock divider should be an even number. + */ + div = sss_rate / EXYNOS_TRNG_CLOCK_RATE; + div -= div % 2; /* make sure it's even */ + if (div > max_div) { + dev_err(dev, "Clock divider too large: %u", div); + return -ERANGE; + } + writel_relaxed(div, trng->base + EXYNOS_TRNG_CLKDIV); + + /* Enable the generator */ + writel_relaxed(EXYNOS_TRNG_CTRL_RNGEN, trng->base + EXYNOS_TRNG_CTRL); + + /* Disable post-processing */ + writel_relaxed(0, trng->base + EXYNOS_TRNG_POST_CTRL); + + return 0; +} + +static int exynos_trng_init_smc(struct udevice *dev) +{ + struct arm_smccc_res res; + int ret = 0; + + arm_smccc_smc(SMC_CMD_RANDOM, HWRNG_INIT, 0, 0, 0, 0, 0, 0, &res); + if (res.a0 != HWRNG_RET_OK) { + dev_err(dev, "SMC command for TRNG init failed (%d)\n", + (int)res.a0); + ret = -EIO; + } + if ((int)res.a0 == -1) + dev_info(dev, "Make sure LDFW is loaded\n"); + + return ret; +} + +static void exynos_trng_exit_smc(struct udevice *dev) +{ + struct arm_smccc_res res; + + arm_smccc_smc(SMC_CMD_RANDOM, HWRNG_EXIT, 0, 0, 0, 0, 0, 0, &res); +} + +static int exynos_trng_read(struct udevice *dev, void *data, size_t len) +{ + struct exynos_trng_priv *trng = dev_get_priv(dev); + + return trng->data->read(dev, data, len); +} + +static int exynos_trng_of_to_plat(struct udevice *dev) +{ + struct exynos_trng_priv *trng = dev_get_priv(dev); + + trng->data = (struct exynos_trng_variant *)dev_get_driver_data(dev); + if (!trng->data->smc) { + trng->base = dev_read_addr_ptr(dev); + if (!trng->base) + return -EINVAL; + } + + trng->clk = devm_clk_get(dev, "secss"); + if (IS_ERR(trng->clk)) + return PTR_ERR(trng->clk); + + trng->pclk = devm_clk_get_optional(dev, "pclk"); + if (IS_ERR(trng->pclk)) + return PTR_ERR(trng->pclk); + + return 0; +} + +static int exynos_trng_probe(struct udevice *dev) +{ + struct exynos_trng_priv *trng = dev_get_priv(dev); + int ret; + + ret = clk_enable(trng->pclk); + if (ret) + return ret; + + ret = clk_enable(trng->clk); + if (ret) + return ret; + + if (trng->data->init) + ret = trng->data->init(dev); + + return ret; +} + +static int exynos_trng_remove(struct udevice *dev) +{ + struct exynos_trng_priv *trng = dev_get_priv(dev); + + if (trng->data->exit) + trng->data->exit(dev); + + /* Keep SSS clocks enabled, they are needed for EL3_MON and kernel */ + + return 0; +} + +static const struct dm_rng_ops exynos_trng_ops = { + .read = exynos_trng_read, +}; + +static const struct exynos_trng_variant exynos5250_trng_data = { + .init = exynos_trng_init_reg, + .read = exynos_trng_read_reg, +}; + +static const struct exynos_trng_variant exynos850_trng_data = { + .smc = true, + .init = exynos_trng_init_smc, + .exit = exynos_trng_exit_smc, + .read = exynos_trng_read_smc, +}; + +static const struct udevice_id exynos_trng_match[] = { + { + .compatible = "samsung,exynos5250-trng", + .data = (ulong)&exynos5250_trng_data, + }, { + .compatible = "samsung,exynos850-trng", + .data = (ulong)&exynos850_trng_data, + }, + { }, +}; + +U_BOOT_DRIVER(exynos_trng) = { + .name = "exynos-trng", + .id = UCLASS_RNG, + .of_match = exynos_trng_match, + .of_to_plat = exynos_trng_of_to_plat, + .probe = exynos_trng_probe, + .remove = exynos_trng_remove, + .ops = &exynos_trng_ops, + .priv_auto = sizeof(struct exynos_trng_priv), +}; diff --git a/drivers/soc/Kconfig b/drivers/soc/Kconfig index cee506fe474..8ef408d9ba1 100644 --- a/drivers/soc/Kconfig +++ b/drivers/soc/Kconfig @@ -48,6 +48,7 @@ config SOC_XILINX_VERSAL_NET This allows other drivers to verify the SoC familiy & revision using matching SoC attributes. +source "drivers/soc/qcom/Kconfig" source "drivers/soc/samsung/Kconfig" source "drivers/soc/ti/Kconfig" diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile index 5ec89a05316..00e6a5ac8e2 100644 --- a/drivers/soc/Makefile +++ b/drivers/soc/Makefile @@ -3,6 +3,7 @@ # Makefile for the U-Boot SOC specific device drivers. obj-$(CONFIG_SOC_AMD_VERSAL2) += soc_amd_versal2.o +obj-$(CONFIG_SOC_QCOM) += qcom/ obj-$(CONFIG_SOC_SAMSUNG) += samsung/ obj-$(CONFIG_SOC_TI) += ti/ obj-$(CONFIG_SOC_DEVICE) += soc-uclass.o diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig new file mode 100644 index 00000000000..4aa7833930c --- /dev/null +++ b/drivers/soc/qcom/Kconfig @@ -0,0 +1,27 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# QCOM Soc drivers +# +menuconfig SOC_QCOM + bool "Qualcomm SOC drivers support" + help + Say Y here if you want to enable Qualcomm SOC drivers support. + +if SOC_QCOM + +config QCOM_COMMAND_DB + bool "Qualcomm Command DB" + help + Command DB queries shared memory by key string for shared system + resources. Platform drivers that require to set state of a shared + resource on a RPM-hardened platform must use this database to get + SoC specific identifier and information for the shared resources. + +config QCOM_RPMH + bool "Qualcomm RPMh support" + depends on QCOM_COMMAND_DB + help + Say y here to support the Qualcomm RPMh (resource peripheral manager) + if you need to control regulators on Qualcomm platforms, say y here. + +endif # SOC_QCOM diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile new file mode 100644 index 00000000000..78fae8bbfa1 --- /dev/null +++ b/drivers/soc/qcom/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0+ + +obj-$(CONFIG_QCOM_COMMAND_DB) += cmd-db.o +obj-$(CONFIG_QCOM_RPMH) += rpmh-rsc.o rpmh.o diff --git a/drivers/soc/qcom/cmd-db.c b/drivers/soc/qcom/cmd-db.c new file mode 100644 index 00000000000..08736ea936a --- /dev/null +++ b/drivers/soc/qcom/cmd-db.c @@ -0,0 +1,225 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2016-2018, 2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2024, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#define pr_fmt(fmt) "cmd-db: " fmt + +#include <dm.h> +#include <dm/ofnode.h> +#include <dm/device_compat.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/ioport.h> +#include <linux/byteorder/generic.h> + +#include <soc/qcom/cmd-db.h> + +#define NUM_PRIORITY 2 +#define MAX_SLV_ID 8 +#define SLAVE_ID_MASK 0x7 +#define SLAVE_ID_SHIFT 16 +#define SLAVE_ID(addr) FIELD_GET(GENMASK(19, 16), addr) +#define VRM_ADDR(addr) FIELD_GET(GENMASK(19, 4), addr) + +/** + * struct entry_header: header for each entry in cmddb + * + * @id: resource's identifier + * @priority: unused + * @addr: the address of the resource + * @len: length of the data + * @offset: offset from :@data_offset, start of the data + */ +struct entry_header { + u8 id[8]; + __le32 priority[NUM_PRIORITY]; + __le32 addr; + __le16 len; + __le16 offset; +}; + +/** + * struct rsc_hdr: resource header information + * + * @slv_id: id for the resource + * @header_offset: entry's header at offset from the end of the cmd_db_header + * @data_offset: entry's data at offset from the end of the cmd_db_header + * @cnt: number of entries for HW type + * @version: MSB is major, LSB is minor + * @reserved: reserved for future use. + */ +struct rsc_hdr { + __le16 slv_id; + __le16 header_offset; + __le16 data_offset; + __le16 cnt; + __le16 version; + __le16 reserved[3]; +}; + +/** + * struct cmd_db_header: The DB header information + * + * @version: The cmd db version + * @magic: constant expected in the database + * @header: array of resources + * @checksum: checksum for the header. Unused. + * @reserved: reserved memory + * @data: driver specific data + */ +struct cmd_db_header { + __le32 version; + u8 magic[4]; + struct rsc_hdr header[MAX_SLV_ID]; + __le32 checksum; + __le32 reserved; + u8 data[]; +}; + +/** + * DOC: Description of the Command DB database. + * + * At the start of the command DB memory is the cmd_db_header structure. + * The cmd_db_header holds the version, checksum, magic key as well as an + * array for header for each slave (depicted by the rsc_header). Each h/w + * based accelerator is a 'slave' (shared resource) and has slave id indicating + * the type of accelerator. The rsc_header is the header for such individual + * slaves of a given type. The entries for each of these slaves begin at the + * rsc_hdr.header_offset. In addition each slave could have auxiliary data + * that may be needed by the driver. The data for the slave starts at the + * entry_header.offset to the location pointed to by the rsc_hdr.data_offset. + * + * Drivers have a stringified key to a slave/resource. They can query the slave + * information and get the slave id and the auxiliary data and the length of the + * data. Using this information, they can format the request to be sent to the + * h/w accelerator and request a resource state. + */ + +static const u8 CMD_DB_MAGIC[] = { 0xdb, 0x30, 0x03, 0x0c }; + +static bool cmd_db_magic_matches(const struct cmd_db_header *header) +{ + const u8 *magic = header->magic; + + return memcmp(magic, CMD_DB_MAGIC, ARRAY_SIZE(CMD_DB_MAGIC)) == 0; +} + +static struct cmd_db_header *cmd_db_header __section(".data") = NULL; + +static inline const void *rsc_to_entry_header(const struct rsc_hdr *hdr) +{ + u16 offset = le16_to_cpu(hdr->header_offset); + + return cmd_db_header->data + offset; +} + +static inline void * +rsc_offset(const struct rsc_hdr *hdr, const struct entry_header *ent) +{ + u16 offset = le16_to_cpu(hdr->data_offset); + u16 loffset = le16_to_cpu(ent->offset); + + return cmd_db_header->data + offset + loffset; +} + +static int cmd_db_get_header(const char *id, const struct entry_header **eh, + const struct rsc_hdr **rh) +{ + const struct rsc_hdr *rsc_hdr; + const struct entry_header *ent; + int i, j; + u8 query[sizeof(ent->id)] __nonstring; + + strncpy(query, id, sizeof(query)); + + for (i = 0; i < MAX_SLV_ID; i++) { + rsc_hdr = &cmd_db_header->header[i]; + if (!rsc_hdr->slv_id) + break; + + ent = rsc_to_entry_header(rsc_hdr); + for (j = 0; j < le16_to_cpu(rsc_hdr->cnt); j++, ent++) { + if (memcmp(ent->id, query, sizeof(ent->id)) == 0) { + if (eh) + *eh = ent; + if (rh) + *rh = rsc_hdr; + return 0; + } + } + } + + return -ENODEV; +} + +/** + * cmd_db_read_addr() - Query command db for resource id address. + * + * @id: resource id to query for address + * + * Return: resource address on success, 0 on error + * + * This is used to retrieve resource address based on resource + * id. + */ +u32 cmd_db_read_addr(const char *id) +{ + int ret; + const struct entry_header *ent; + + debug("%s(%s)\n", __func__, id); + + if (!cmd_db_header) { + log_err("%s: Command DB not initialized\n", __func__); + return 0; + } + + ret = cmd_db_get_header(id, &ent, NULL); + + return ret < 0 ? 0 : le32_to_cpu(ent->addr); +} +EXPORT_SYMBOL_GPL(cmd_db_read_addr); + +int cmd_db_bind(struct udevice *dev) +{ + void __iomem *base; + ofnode node; + + if (cmd_db_header) + return 0; + + node = dev_ofnode(dev); + + debug("%s(%s)\n", __func__, ofnode_get_name(node)); + + base = (void __iomem *)ofnode_get_addr(node); + if ((fdt_addr_t)base == FDT_ADDR_T_NONE) { + log_err("%s: Failed to read base address\n", __func__); + return -ENOENT; + } + + cmd_db_header = base; + if (!cmd_db_magic_matches(cmd_db_header)) { + log_err("%s: Invalid Command DB Magic\n", __func__); + return -EINVAL; + } + + return 0; +} + +static const struct udevice_id cmd_db_ids[] = { + { .compatible = "qcom,cmd-db" }, + { } +}; + +U_BOOT_DRIVER(qcom_cmd_db) = { + .name = "qcom_cmd_db", + .id = UCLASS_MISC, + .bind = cmd_db_bind, + .of_match = cmd_db_ids, +}; + +MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Command DB Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/soc/qcom/rpmh-internal.h b/drivers/soc/qcom/rpmh-internal.h new file mode 100644 index 00000000000..ac8f6c35a7a --- /dev/null +++ b/drivers/soc/qcom/rpmh-internal.h @@ -0,0 +1,138 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + */ + + +#ifndef __RPM_INTERNAL_H__ +#define __RPM_INTERNAL_H__ + +#include <linux/bitmap.h> +#include <soc/qcom/tcs.h> + +#define TCS_TYPE_NR 4 +#define MAX_CMDS_PER_TCS 16 +#define MAX_TCS_PER_TYPE 3 +#define MAX_TCS_NR (MAX_TCS_PER_TYPE * TCS_TYPE_NR) +#define MAX_TCS_SLOTS (MAX_CMDS_PER_TCS * MAX_TCS_PER_TYPE) + +#define USEC_PER_SEC 1000000UL + +struct rsc_drv; + +/** + * struct tcs_group: group of Trigger Command Sets (TCS) to send state requests + * to the controller + * + * @drv: The controller. + * @type: Type of the TCS in this group - active, sleep, wake. + * @mask: Mask of the TCSes relative to all the TCSes in the RSC. + * @offset: Start of the TCS group relative to the TCSes in the RSC. + * @num_tcs: Number of TCSes in this type. + * @ncpt: Number of commands in each TCS. + * @req: Requests that are sent from the TCS; only used for ACTIVE_ONLY + * transfers (could be on a wake/sleep TCS if we are borrowing for + * an ACTIVE_ONLY transfer). + * Start: grab drv->lock, set req, set tcs_in_use, drop drv->lock, + * trigger + * End: get irq, access req, + * grab drv->lock, clear tcs_in_use, drop drv->lock + * @slots: Indicates which of @cmd_addr are occupied; only used for + * SLEEP / WAKE TCSs. Things are tightly packed in the + * case that (ncpt < MAX_CMDS_PER_TCS). That is if ncpt = 2 and + * MAX_CMDS_PER_TCS = 16 then bit[2] = the first bit in 2nd TCS. + */ +struct tcs_group { + struct rsc_drv *drv; + int type; + u32 mask; + u32 offset; + int num_tcs; + int ncpt; + const struct tcs_request *req[MAX_TCS_PER_TYPE]; + DECLARE_BITMAP(slots, MAX_TCS_SLOTS); +}; + +/** + * struct rpmh_request: the message to be sent to rpmh-rsc + * + * @msg: the request + * @cmd: the payload that will be part of the @msg + * @completion: triggered when request is done + * @dev: the device making the request + * @needs_free: check to free dynamically allocated request object + */ +struct rpmh_request { + struct tcs_request msg; + struct tcs_cmd cmd[MAX_RPMH_PAYLOAD]; + const struct udevice *dev; + bool needs_free; +}; + +/** + * struct rpmh_ctrlr: our representation of the controller + * + * @cache: the list of cached requests + * @cache_lock: synchronize access to the cache data + * @dirty: was the cache updated since flush + * @batch_cache: Cache sleep and wake requests sent as batch + */ +struct rpmh_ctrlr { + struct list_head cache; + bool dirty; + struct list_head batch_cache; +}; + +struct rsc_ver { + u32 major; + u32 minor; +}; + +/** + * struct rsc_drv: the Direct Resource Voter (DRV) of the + * Resource State Coordinator controller (RSC) + * + * @name: Controller identifier. + * @base: Start address of the DRV registers in this controller. + * @tcs_base: Start address of the TCS registers in this controller. + * @id: Instance id in the controller (Direct Resource Voter). + * @num_tcs: Number of TCSes in this DRV. + * @rsc_pm: CPU PM notifier for controller. + * Used when solver mode is not present. + * @cpus_in_pm: Number of CPUs not in idle power collapse. + * Used when solver mode and "power-domains" is not present. + * @genpd_nb: PM Domain notifier for cluster genpd notifications. + * @tcs: TCS groups. + * @tcs_in_use: S/W state of the TCS; only set for ACTIVE_ONLY + * transfers, but might show a sleep/wake TCS in use if + * it was borrowed for an active_only transfer. You + * must hold the lock in this struct (AKA drv->lock) in + * order to update this. + * @lock: Synchronize state of the controller. If RPMH's cache + * lock will also be held, the order is: drv->lock then + * cache_lock. + * @tcs_wait: Wait queue used to wait for @tcs_in_use to free up a + * slot + * @client: Handle to the DRV's client. + * @dev: RSC device. + */ +struct rsc_drv { + const char *name; + void __iomem *base; + void __iomem *tcs_base; + int id; + int num_tcs; + struct tcs_group tcs[TCS_TYPE_NR]; + DECLARE_BITMAP(tcs_in_use, MAX_TCS_NR); + struct rpmh_ctrlr client; + struct udevice *dev; + struct rsc_ver ver; + u32 *regs; +}; + +int rpmh_rsc_send_data(struct rsc_drv *drv, const struct tcs_request *msg); +int rpmh_rsc_write_ctrl_data(struct rsc_drv *drv, + const struct tcs_request *msg); +void rpmh_rsc_invalidate(struct rsc_drv *drv); + +#endif /* __RPM_INTERNAL_H__ */ diff --git a/drivers/soc/qcom/rpmh-rsc.c b/drivers/soc/qcom/rpmh-rsc.c new file mode 100644 index 00000000000..61fb2e69558 --- /dev/null +++ b/drivers/soc/qcom/rpmh-rsc.c @@ -0,0 +1,505 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + * Copyright (c) 2023-2024, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#define pr_fmt(fmt) "%s " fmt, KBUILD_MODNAME + +#include <dm.h> +#include <dm/device_compat.h> +#include <dm/devres.h> +#include <dm/lists.h> +#include <dm/ofnode.h> +#include <linux/bitmap.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/types.h> +#include <asm/bitops.h> +#include <asm/io.h> + +#include <log.h> + +#include <soc/qcom/tcs.h> +#include <dt-bindings/soc/qcom,rpmh-rsc.h> + +#include "rpmh-internal.h" + + +#define RSC_DRV_ID 0 + +#define MAJOR_VER_MASK 0xFF +#define MAJOR_VER_SHIFT 16 +#define MINOR_VER_MASK 0xFF +#define MINOR_VER_SHIFT 8 + +enum { + RSC_DRV_TCS_OFFSET, + RSC_DRV_CMD_OFFSET, + DRV_SOLVER_CONFIG, + DRV_PRNT_CHLD_CONFIG, + RSC_DRV_IRQ_ENABLE, + RSC_DRV_IRQ_STATUS, + RSC_DRV_IRQ_CLEAR, + RSC_DRV_CMD_WAIT_FOR_CMPL, + RSC_DRV_CONTROL, + RSC_DRV_STATUS, + RSC_DRV_CMD_ENABLE, + RSC_DRV_CMD_MSGID, + RSC_DRV_CMD_ADDR, + RSC_DRV_CMD_DATA, + RSC_DRV_CMD_STATUS, + RSC_DRV_CMD_RESP_DATA, +}; + +/* DRV HW Solver Configuration Information Register */ +#define DRV_HW_SOLVER_MASK 1 +#define DRV_HW_SOLVER_SHIFT 24 + +/* DRV TCS Configuration Information Register */ +#define DRV_NUM_TCS_MASK 0x3F +#define DRV_NUM_TCS_SHIFT 6 +#define DRV_NCPT_MASK 0x1F +#define DRV_NCPT_SHIFT 27 + +/* Offsets for CONTROL TCS Registers */ +#define RSC_DRV_CTL_TCS_DATA_HI 0x38 +#define RSC_DRV_CTL_TCS_DATA_HI_MASK 0xFFFFFF +#define RSC_DRV_CTL_TCS_DATA_HI_VALID BIT(31) +#define RSC_DRV_CTL_TCS_DATA_LO 0x40 +#define RSC_DRV_CTL_TCS_DATA_LO_MASK 0xFFFFFFFF +#define RSC_DRV_CTL_TCS_DATA_SIZE 32 + +#define TCS_AMC_MODE_ENABLE BIT(16) +#define TCS_AMC_MODE_TRIGGER BIT(24) + +/* TCS CMD register bit mask */ +#define CMD_MSGID_LEN 8 +#define CMD_MSGID_RESP_REQ BIT(8) +#define CMD_MSGID_WRITE BIT(16) +#define CMD_STATUS_ISSUED BIT(8) +#define CMD_STATUS_COMPL BIT(16) + +/* + * Here's a high level overview of how all the registers in RPMH work + * together: + * + * - The main rpmh-rsc address is the base of a register space that can + * be used to find overall configuration of the hardware + * (DRV_PRNT_CHLD_CONFIG). Also found within the rpmh-rsc register + * space are all the TCS blocks. The offset of the TCS blocks is + * specified in the device tree by "qcom,tcs-offset" and used to + * compute tcs_base. + * - TCS blocks come one after another. Type, count, and order are + * specified by the device tree as "qcom,tcs-config". + * - Each TCS block has some registers, then space for up to 16 commands. + * Note that though address space is reserved for 16 commands, fewer + * might be present. See ncpt (num cmds per TCS). + * + * Here's a picture: + * + * +---------------------------------------------------+ + * |RSC | + * | ctrl | + * | | + * | Drvs: | + * | +-----------------------------------------------+ | + * | |DRV0 | | + * | | ctrl/config | | + * | | IRQ | | + * | | | | + * | | TCSes: | | + * | | +------------------------------------------+ | | + * | | |TCS0 | | | | | | | | | | | | | | | + * | | | ctrl | 0| 1| 2| 3| 4| 5| .| .| .| .|14|15| | | + * | | | | | | | | | | | | | | | | | | + * | | +------------------------------------------+ | | + * | | +------------------------------------------+ | | + * | | |TCS1 | | | | | | | | | | | | | | | + * | | | ctrl | 0| 1| 2| 3| 4| 5| .| .| .| .|14|15| | | + * | | | | | | | | | | | | | | | | | | + * | | +------------------------------------------+ | | + * | | +------------------------------------------+ | | + * | | |TCS2 | | | | | | | | | | | | | | | + * | | | ctrl | 0| 1| 2| 3| 4| 5| .| .| .| .|14|15| | | + * | | | | | | | | | | | | | | | | | | + * | | +------------------------------------------+ | | + * | | ...... | | + * | +-----------------------------------------------+ | + * | +-----------------------------------------------+ | + * | |DRV1 | | + * | | (same as DRV0) | | + * | +-----------------------------------------------+ | + * | ...... | + * +---------------------------------------------------+ + */ + +static u32 rpmh_rsc_reg_offset_ver_2_7[] = { + [RSC_DRV_TCS_OFFSET] = 672, + [RSC_DRV_CMD_OFFSET] = 20, + [DRV_SOLVER_CONFIG] = 0x04, + [DRV_PRNT_CHLD_CONFIG] = 0x0C, + [RSC_DRV_IRQ_ENABLE] = 0x00, + [RSC_DRV_IRQ_STATUS] = 0x04, + [RSC_DRV_IRQ_CLEAR] = 0x08, + [RSC_DRV_CMD_WAIT_FOR_CMPL] = 0x10, + [RSC_DRV_CONTROL] = 0x14, + [RSC_DRV_STATUS] = 0x18, + [RSC_DRV_CMD_ENABLE] = 0x1C, + [RSC_DRV_CMD_MSGID] = 0x30, + [RSC_DRV_CMD_ADDR] = 0x34, + [RSC_DRV_CMD_DATA] = 0x38, + [RSC_DRV_CMD_STATUS] = 0x3C, + [RSC_DRV_CMD_RESP_DATA] = 0x40, +}; + +static u32 rpmh_rsc_reg_offset_ver_3_0[] = { + [RSC_DRV_TCS_OFFSET] = 672, + [RSC_DRV_CMD_OFFSET] = 24, + [DRV_SOLVER_CONFIG] = 0x04, + [DRV_PRNT_CHLD_CONFIG] = 0x0C, + [RSC_DRV_IRQ_ENABLE] = 0x00, + [RSC_DRV_IRQ_STATUS] = 0x04, + [RSC_DRV_IRQ_CLEAR] = 0x08, + [RSC_DRV_CMD_WAIT_FOR_CMPL] = 0x20, + [RSC_DRV_CONTROL] = 0x24, + [RSC_DRV_STATUS] = 0x28, + [RSC_DRV_CMD_ENABLE] = 0x2C, + [RSC_DRV_CMD_MSGID] = 0x34, + [RSC_DRV_CMD_ADDR] = 0x38, + [RSC_DRV_CMD_DATA] = 0x3C, + [RSC_DRV_CMD_STATUS] = 0x40, + [RSC_DRV_CMD_RESP_DATA] = 0x44, +}; + +static inline void __iomem * +tcs_reg_addr(const struct rsc_drv *drv, int reg, int tcs_id) +{ + return drv->tcs_base + drv->regs[RSC_DRV_TCS_OFFSET] * tcs_id + reg; +} + +static inline void __iomem * +tcs_cmd_addr(const struct rsc_drv *drv, int reg, int tcs_id, int cmd_id) +{ + return tcs_reg_addr(drv, reg, tcs_id) + drv->regs[RSC_DRV_CMD_OFFSET] * cmd_id; +} + +static u32 read_tcs_cmd(const struct rsc_drv *drv, int reg, int tcs_id, + int cmd_id) +{ + return readl_relaxed(tcs_cmd_addr(drv, reg, tcs_id, cmd_id)); +} + +static u32 read_tcs_reg(const struct rsc_drv *drv, int reg, int tcs_id) +{ + return readl_relaxed(tcs_reg_addr(drv, reg, tcs_id)); +} + +static void write_tcs_cmd(const struct rsc_drv *drv, int reg, int tcs_id, + int cmd_id, u32 data) +{ + writel_relaxed(data, tcs_cmd_addr(drv, reg, tcs_id, cmd_id)); +} + +static void write_tcs_reg(const struct rsc_drv *drv, int reg, int tcs_id, + u32 data) +{ + writel_relaxed(data, tcs_reg_addr(drv, reg, tcs_id)); +} + +static void write_tcs_reg_sync(const struct rsc_drv *drv, int reg, int tcs_id, + u32 data) +{ + int i; + + writel(data, tcs_reg_addr(drv, reg, tcs_id)); + + /* + * Wait until we read back the same value. Use a counter rather than + * ktime for timeout since this may be called after timekeeping stops. + */ + for (i = 0; i < USEC_PER_SEC; i++) { + if (readl(tcs_reg_addr(drv, reg, tcs_id)) == data) + return; + udelay(1); + } + pr_err("%s: error writing %#x to %d:%#x\n", drv->name, + data, tcs_id, reg); +} + +/** + * get_tcs_for_msg() - Get the tcs_group used to send the given message. + * @drv: The RSC controller. + * @msg: The message we want to send. + * + * This is normally pretty straightforward except if we are trying to send + * an ACTIVE_ONLY message but don't have any active_only TCSes. + * + * Return: A pointer to a tcs_group or an ERR_PTR. + */ +static struct tcs_group *get_tcs_for_msg(struct rsc_drv *drv, + const struct tcs_request *msg) +{ + /* + * U-Boot: since we're single threaded and running synchronously we can + * just always used the first active TCS. + */ + if (msg->state != RPMH_ACTIVE_ONLY_STATE) { + log_err("WARN: only ACTIVE_ONLY state supported\n"); + return ERR_PTR(-EINVAL); + } + + return &drv->tcs[ACTIVE_TCS]; +} + +/** + * __tcs_buffer_write() - Write to TCS hardware from a request; don't trigger. + * @drv: The controller. + * @tcs_id: The global ID of this TCS. + * @cmd_id: The index within the TCS to start writing. + * @msg: The message we want to send, which will contain several addr/data + * pairs to program (but few enough that they all fit in one TCS). + * + * This is used for all types of transfers (active, sleep, and wake). + */ +static void __tcs_buffer_write(struct rsc_drv *drv, int tcs_id, int cmd_id, + const struct tcs_request *msg) +{ + u32 msgid; + u32 cmd_msgid = CMD_MSGID_LEN | CMD_MSGID_WRITE; + u32 cmd_enable = 0; + struct tcs_cmd *cmd; + int i, j; + + for (i = 0, j = cmd_id; i < msg->num_cmds; i++, j++) { + cmd = &msg->cmds[i]; + cmd_enable |= BIT(j); + msgid = cmd_msgid; + /* + * Additionally, if the cmd->wait is set, make the command + * response reqd even if the overall request was fire-n-forget. + */ + msgid |= cmd->wait ? CMD_MSGID_RESP_REQ : 0; + + write_tcs_cmd(drv, drv->regs[RSC_DRV_CMD_MSGID], tcs_id, j, msgid); + write_tcs_cmd(drv, drv->regs[RSC_DRV_CMD_ADDR], tcs_id, j, cmd->addr); + write_tcs_cmd(drv, drv->regs[RSC_DRV_CMD_DATA], tcs_id, j, cmd->data); + debug("tcs(m): %d [%s] cmd(n): %d msgid: %#x addr: %#x data: %#x complete: %d\n", + tcs_id, msg->state == RPMH_ACTIVE_ONLY_STATE ? "active" : "?", j, msgid, + cmd->addr, cmd->data, cmd->wait); + } + + cmd_enable |= read_tcs_reg(drv, drv->regs[RSC_DRV_CMD_ENABLE], tcs_id); + write_tcs_reg(drv, drv->regs[RSC_DRV_CMD_ENABLE], tcs_id, cmd_enable); +} + +/** + * rpmh_rsc_send_data() - Write / trigger active-only message. + * @drv: The controller. + * @msg: The data to be sent. + * + * NOTES: + * - This is only used for "ACTIVE_ONLY" since the limitations of this + * function don't make sense for sleep/wake cases. + * - To do the transfer, we will grab a whole TCS for ourselves--we don't + * try to share. If there are none available we'll wait indefinitely + * for a free one. + * - This function will not wait for the commands to be finished, only for + * data to be programmed into the RPMh. See rpmh_tx_done() which will + * be called when the transfer is fully complete. + * - This function must be called with interrupts enabled. If the hardware + * is busy doing someone else's transfer we need that transfer to fully + * finish so that we can have the hardware, and to fully finish it needs + * the interrupt handler to run. If the interrupts is set to run on the + * active CPU this can never happen if interrupts are disabled. + * + * Return: 0 on success, -EINVAL on error. + */ +int rpmh_rsc_send_data(struct rsc_drv *drv, const struct tcs_request *msg) +{ + struct tcs_group *tcs; + int tcs_id, i; + u32 addr; + + tcs = get_tcs_for_msg(drv, msg); + if (IS_ERR(tcs)) + return PTR_ERR(tcs); + + /* u-boot is single-threaded, always use the first TCS as we'll never conflict */ + tcs_id = tcs->offset; + + tcs->req[tcs_id - tcs->offset] = msg; + generic_set_bit(tcs_id, drv->tcs_in_use); + if (msg->state == RPMH_ACTIVE_ONLY_STATE && tcs->type != ACTIVE_TCS) { + /* + * Clear previously programmed WAKE commands in selected + * repurposed TCS to avoid triggering them. tcs->slots will be + * cleaned from rpmh_flush() by invoking rpmh_rsc_invalidate() + */ + write_tcs_reg_sync(drv, drv->regs[RSC_DRV_CMD_ENABLE], tcs_id, 0); + } + + /* + * These two can be done after the lock is released because: + * - We marked "tcs_in_use" under lock. + * - Once "tcs_in_use" has been marked nobody else could be writing + * to these registers until the interrupt goes off. + * - The interrupt can't go off until we trigger w/ the last line + * of __tcs_set_trigger() below. + */ + __tcs_buffer_write(drv, tcs_id, 0, msg); + + /* U-Boot: Now wait for the TCS to be cleared, indicating that we're done */ + for (i = 0; i < USEC_PER_SEC; i++) { + addr = read_tcs_cmd(drv, drv->regs[RSC_DRV_CMD_ADDR], i, 0); + if (addr != msg->cmds[0].addr) + break; + udelay(1); + } + + if (i == USEC_PER_SEC) { + log_err("%s: error writing %#x to %d:%#x\n", drv->name, + msg->cmds[0].addr, tcs_id, drv->regs[RSC_DRV_CMD_ADDR]); + return -EINVAL; + } + + return 0; +} + +static int rpmh_probe_tcs_config(struct udevice *dev, struct rsc_drv *drv) +{ + struct tcs_type_config { + u32 type; + u32 n; + } tcs_cfg[TCS_TYPE_NR] = { { 0 } }; + ofnode dn = dev_ofnode(dev); + u32 config, max_tcs, ncpt, offset; + int i, ret, n, st = 0; + struct tcs_group *tcs; + + ret = ofnode_read_u32(dn, "qcom,tcs-offset", &offset); + if (ret) + return ret; + drv->tcs_base = drv->base + offset; + + config = readl_relaxed(drv->base + drv->regs[DRV_PRNT_CHLD_CONFIG]); + + max_tcs = config; + max_tcs &= DRV_NUM_TCS_MASK << (DRV_NUM_TCS_SHIFT * drv->id); + max_tcs = max_tcs >> (DRV_NUM_TCS_SHIFT * drv->id); + + ncpt = config & (DRV_NCPT_MASK << DRV_NCPT_SHIFT); + ncpt = ncpt >> DRV_NCPT_SHIFT; + + n = ofnode_read_u32_array(dn, "qcom,tcs-config", (u32 *)tcs_cfg, 2 * TCS_TYPE_NR); + if (n < 0) { + log_err("RPMh: %s: error reading qcom,tcs-config %d\n", dev->name, n); + return n; + } + + for (i = 0; i < TCS_TYPE_NR; i++) { + if (tcs_cfg[i].n > MAX_TCS_PER_TYPE) + return -EINVAL; + } + + for (i = 0; i < TCS_TYPE_NR; i++) { + tcs = &drv->tcs[tcs_cfg[i].type]; + if (tcs->drv) + return -EINVAL; + tcs->drv = drv; + tcs->type = tcs_cfg[i].type; + tcs->num_tcs = tcs_cfg[i].n; + tcs->ncpt = ncpt; + + if (!tcs->num_tcs || tcs->type == CONTROL_TCS) + continue; + + if (st + tcs->num_tcs > max_tcs || + st + tcs->num_tcs >= BITS_PER_BYTE * sizeof(tcs->mask)) + return -EINVAL; + + tcs->mask = ((1 << tcs->num_tcs) - 1) << st; + tcs->offset = st; + st += tcs->num_tcs; + } + + drv->num_tcs = st; + + return 0; +} + +static int rpmh_rsc_probe(struct udevice *dev) +{ + ofnode dn = dev_ofnode(dev); + struct rsc_drv *drv; + char drv_id[10] = {0}; + int ret; + u32 rsc_id; + + drv = dev_get_priv(dev); + + ret = ofnode_read_u32(dn, "qcom,drv-id", &drv->id); + if (ret) + return ret; + + drv->name = ofnode_get_property(dn, "label", NULL); + if (!drv->name) + drv->name = dev->name; + + snprintf(drv_id, ARRAY_SIZE(drv_id), "drv-%d", drv->id); + drv->base = (void __iomem *)dev_read_addr_name(dev, drv_id); + if (IS_ERR(drv->base)) + return PTR_ERR(drv->base); + + rsc_id = readl_relaxed(drv->base + RSC_DRV_ID); + drv->ver.major = rsc_id & (MAJOR_VER_MASK << MAJOR_VER_SHIFT); + drv->ver.major >>= MAJOR_VER_SHIFT; + drv->ver.minor = rsc_id & (MINOR_VER_MASK << MINOR_VER_SHIFT); + drv->ver.minor >>= MINOR_VER_SHIFT; + + if (drv->ver.major == 3) + drv->regs = rpmh_rsc_reg_offset_ver_3_0; + else + drv->regs = rpmh_rsc_reg_offset_ver_2_7; + + ret = rpmh_probe_tcs_config(dev, drv); + if (ret) + return ret; + + spin_lock_init(&drv->lock); + init_waitqueue_head(&drv->tcs_wait); + bitmap_zero(drv->tcs_in_use, MAX_TCS_NR); + + /* Enable the active TCS to send requests immediately */ + writel_relaxed(drv->tcs[ACTIVE_TCS].mask, + drv->tcs_base + drv->regs[RSC_DRV_IRQ_ENABLE]); + + spin_lock_init(&drv->client.cache_lock); + INIT_LIST_HEAD(&drv->client.cache); + INIT_LIST_HEAD(&drv->client.batch_cache); + + dev_set_drvdata(dev, drv); + drv->dev = dev; + + log_debug("RPMh: %s: v%d.%d\n", dev->name, drv->ver.major, drv->ver.minor); + + return ret; +} + +static const struct udevice_id qcom_rpmh_ids[] = { + { .compatible = "qcom,rpmh-rsc" }, + { } +}; + +U_BOOT_DRIVER(qcom_rpmh_rsc) = { + .name = "qcom_rpmh_rsc", + .id = UCLASS_MISC, + .priv_auto = sizeof(struct rsc_drv), + .probe = rpmh_rsc_probe, + .of_match = qcom_rpmh_ids, + /* rpmh is under CLUSTER_PD which we don't support, so skip trying to enable PDs */ + .flags = DM_FLAG_DEFAULT_PD_CTRL_OFF, +}; + +MODULE_DESCRIPTION("Qualcomm Technologies, Inc. RPMh Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/soc/qcom/rpmh.c b/drivers/soc/qcom/rpmh.c new file mode 100644 index 00000000000..96f14a9afdf --- /dev/null +++ b/drivers/soc/qcom/rpmh.c @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + */ + +#include <dm/device.h> +#include <dm/device_compat.h> +#include <linux/bug.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/types.h> + +#include <soc/qcom/rpmh.h> + +#include "rpmh-internal.h" + +#define RPMH_TIMEOUT_MS msecs_to_jiffies(10000) + +#define DEFINE_RPMH_MSG_ONSTACK(device, s, name) \ + struct rpmh_request name = { \ + .msg = { \ + .state = s, \ + .cmds = name.cmd, \ + .num_cmds = 0, \ + }, \ + .cmd = { { 0 } }, \ + .dev = device, \ + .needs_free = false, \ + } + +#define ctrlr_to_drv(ctrlr) container_of(ctrlr, struct rsc_drv, client) + +static struct rpmh_ctrlr *get_rpmh_ctrlr(const struct udevice *dev) +{ + struct rsc_drv *drv = (struct rsc_drv *)dev_get_priv(dev->parent); + + if (!drv) { + log_err("BUG: no RPMh driver for %s (parent %s)\n", dev->name, dev->parent->name); + BUG(); + } + + return &drv->client; +} + +/** + * __rpmh_write: Cache and send the RPMH request + * + * @dev: The device making the request + * @state: Active/Sleep request type + * @rpm_msg: The data that needs to be sent (cmds). + * + * Cache the RPMH request and send if the state is ACTIVE_ONLY. + * SLEEP/WAKE_ONLY requests are not sent to the controller at + * this time. Use rpmh_flush() to send them to the controller. + */ +static int __rpmh_write(const struct udevice *dev, enum rpmh_state state, + struct rpmh_request *rpm_msg) +{ + struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr(dev); + + if (state != RPMH_ACTIVE_ONLY_STATE) { + log_err("only ACTIVE_ONLY state supported\n"); + return -EINVAL; + } + + return rpmh_rsc_send_data(ctrlr_to_drv(ctrlr), &rpm_msg->msg); +} + +static int __fill_rpmh_msg(struct rpmh_request *req, enum rpmh_state state, + const struct tcs_cmd *cmd, u32 n) +{ + if (!cmd || !n || n > MAX_RPMH_PAYLOAD) + return -EINVAL; + + memcpy(req->cmd, cmd, n * sizeof(*cmd)); + + req->msg.state = state; + req->msg.cmds = req->cmd; + req->msg.num_cmds = n; + + debug("rpmh_msg: %d, %d cmds [first %#x/%#x]\n", state, n, cmd->addr, cmd->data); + + return 0; +} + +/** + * rpmh_write: Write a set of RPMH commands and block until response + * + * @dev: The device making the request + * @state: Active/sleep set + * @cmd: The payload data + * @n: The number of elements in @cmd + * + * May sleep. Do not call from atomic contexts. + */ +int rpmh_write(const struct udevice *dev, enum rpmh_state state, + const struct tcs_cmd *cmd, u32 n) +{ + DEFINE_RPMH_MSG_ONSTACK(dev, state, rpm_msg); + int ret; + + ret = __fill_rpmh_msg(&rpm_msg, state, cmd, n); + if (ret) + return ret; + + ret = __rpmh_write(dev, state, &rpm_msg); + + return ret; +} +EXPORT_SYMBOL_GPL(rpmh_write); diff --git a/include/dt-bindings/clock/qcom,gcc-ipq4019.h b/include/dt-bindings/clock/qcom,gcc-ipq4019.h deleted file mode 100644 index 7e8a7be6dcd..00000000000 --- a/include/dt-bindings/clock/qcom,gcc-ipq4019.h +++ /dev/null @@ -1,169 +0,0 @@ -/* Copyright (c) 2015 The Linux Foundation. All rights reserved. - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ -#ifndef __QCOM_CLK_IPQ4019_H__ -#define __QCOM_CLK_IPQ4019_H__ - -#define GCC_DUMMY_CLK 0 -#define AUDIO_CLK_SRC 1 -#define BLSP1_QUP1_I2C_APPS_CLK_SRC 2 -#define BLSP1_QUP1_SPI_APPS_CLK_SRC 3 -#define BLSP1_QUP2_I2C_APPS_CLK_SRC 4 -#define BLSP1_QUP2_SPI_APPS_CLK_SRC 5 -#define BLSP1_UART1_APPS_CLK_SRC 6 -#define BLSP1_UART2_APPS_CLK_SRC 7 -#define GCC_USB3_MOCK_UTMI_CLK_SRC 8 -#define GCC_APPS_CLK_SRC 9 -#define GCC_APPS_AHB_CLK_SRC 10 -#define GP1_CLK_SRC 11 -#define GP2_CLK_SRC 12 -#define GP3_CLK_SRC 13 -#define SDCC1_APPS_CLK_SRC 14 -#define FEPHY_125M_DLY_CLK_SRC 15 -#define WCSS2G_CLK_SRC 16 -#define WCSS5G_CLK_SRC 17 -#define GCC_APSS_AHB_CLK 18 -#define GCC_AUDIO_AHB_CLK 19 -#define GCC_AUDIO_PWM_CLK 20 -#define GCC_BLSP1_AHB_CLK 21 -#define GCC_BLSP1_QUP1_I2C_APPS_CLK 22 -#define GCC_BLSP1_QUP1_SPI_APPS_CLK 23 -#define GCC_BLSP1_QUP2_I2C_APPS_CLK 24 -#define GCC_BLSP1_QUP2_SPI_APPS_CLK 25 -#define GCC_BLSP1_UART1_APPS_CLK 26 -#define GCC_BLSP1_UART2_APPS_CLK 27 -#define GCC_DCD_XO_CLK 28 -#define GCC_GP1_CLK 29 -#define GCC_GP2_CLK 30 -#define GCC_GP3_CLK 31 -#define GCC_BOOT_ROM_AHB_CLK 32 -#define GCC_CRYPTO_AHB_CLK 33 -#define GCC_CRYPTO_AXI_CLK 34 -#define GCC_CRYPTO_CLK 35 -#define GCC_ESS_CLK 36 -#define GCC_IMEM_AXI_CLK 37 -#define GCC_IMEM_CFG_AHB_CLK 38 -#define GCC_PCIE_AHB_CLK 39 -#define GCC_PCIE_AXI_M_CLK 40 -#define GCC_PCIE_AXI_S_CLK 41 -#define GCC_PCNOC_AHB_CLK 42 -#define GCC_PRNG_AHB_CLK 43 -#define GCC_QPIC_AHB_CLK 44 -#define GCC_QPIC_CLK 45 -#define GCC_SDCC1_AHB_CLK 46 -#define GCC_SDCC1_APPS_CLK 47 -#define GCC_SNOC_PCNOC_AHB_CLK 48 -#define GCC_SYS_NOC_125M_CLK 49 -#define GCC_SYS_NOC_AXI_CLK 50 -#define GCC_TCSR_AHB_CLK 51 -#define GCC_TLMM_AHB_CLK 52 -#define GCC_USB2_MASTER_CLK 53 -#define GCC_USB2_SLEEP_CLK 54 -#define GCC_USB2_MOCK_UTMI_CLK 55 -#define GCC_USB3_MASTER_CLK 56 -#define GCC_USB3_SLEEP_CLK 57 -#define GCC_USB3_MOCK_UTMI_CLK 58 -#define GCC_WCSS2G_CLK 59 -#define GCC_WCSS2G_REF_CLK 60 -#define GCC_WCSS2G_RTC_CLK 61 -#define GCC_WCSS5G_CLK 62 -#define GCC_WCSS5G_REF_CLK 63 -#define GCC_WCSS5G_RTC_CLK 64 -#define GCC_APSS_DDRPLL_VCO 65 -#define GCC_SDCC_PLLDIV_CLK 66 -#define GCC_FEPLL_VCO 67 -#define GCC_FEPLL125_CLK 68 -#define GCC_FEPLL125DLY_CLK 69 -#define GCC_FEPLL200_CLK 70 -#define GCC_FEPLL500_CLK 71 -#define GCC_FEPLL_WCSS2G_CLK 72 -#define GCC_FEPLL_WCSS5G_CLK 73 -#define GCC_APSS_CPU_PLLDIV_CLK 74 -#define GCC_PCNOC_AHB_CLK_SRC 75 - -#define WIFI0_CPU_INIT_RESET 0 -#define WIFI0_RADIO_SRIF_RESET 1 -#define WIFI0_RADIO_WARM_RESET 2 -#define WIFI0_RADIO_COLD_RESET 3 -#define WIFI0_CORE_WARM_RESET 4 -#define WIFI0_CORE_COLD_RESET 5 -#define WIFI1_CPU_INIT_RESET 6 -#define WIFI1_RADIO_SRIF_RESET 7 -#define WIFI1_RADIO_WARM_RESET 8 -#define WIFI1_RADIO_COLD_RESET 9 -#define WIFI1_CORE_WARM_RESET 10 -#define WIFI1_CORE_COLD_RESET 11 -#define USB3_UNIPHY_PHY_ARES 12 -#define USB3_HSPHY_POR_ARES 13 -#define USB3_HSPHY_S_ARES 14 -#define USB2_HSPHY_POR_ARES 15 -#define USB2_HSPHY_S_ARES 16 -#define PCIE_PHY_AHB_ARES 17 -#define PCIE_AHB_ARES 18 -#define PCIE_PWR_ARES 19 -#define PCIE_PIPE_STICKY_ARES 20 -#define PCIE_AXI_M_STICKY_ARES 21 -#define PCIE_PHY_ARES 22 -#define PCIE_PARF_XPU_ARES 23 -#define PCIE_AXI_S_XPU_ARES 24 -#define PCIE_AXI_M_VMIDMT_ARES 25 -#define PCIE_PIPE_ARES 26 -#define PCIE_AXI_S_ARES 27 -#define PCIE_AXI_M_ARES 28 -#define ESS_RESET 29 -#define GCC_BLSP1_BCR 30 -#define GCC_BLSP1_QUP1_BCR 31 -#define GCC_BLSP1_UART1_BCR 32 -#define GCC_BLSP1_QUP2_BCR 33 -#define GCC_BLSP1_UART2_BCR 34 -#define GCC_BIMC_BCR 35 -#define GCC_TLMM_BCR 36 -#define GCC_IMEM_BCR 37 -#define GCC_ESS_BCR 38 -#define GCC_PRNG_BCR 39 -#define GCC_BOOT_ROM_BCR 40 -#define GCC_CRYPTO_BCR 41 -#define GCC_SDCC1_BCR 42 -#define GCC_SEC_CTRL_BCR 43 -#define GCC_AUDIO_BCR 44 -#define GCC_QPIC_BCR 45 -#define GCC_PCIE_BCR 46 -#define GCC_USB2_BCR 47 -#define GCC_USB2_PHY_BCR 48 -#define GCC_USB3_BCR 49 -#define GCC_USB3_PHY_BCR 50 -#define GCC_SYSTEM_NOC_BCR 51 -#define GCC_PCNOC_BCR 52 -#define GCC_DCD_BCR 53 -#define GCC_SNOC_BUS_TIMEOUT0_BCR 54 -#define GCC_SNOC_BUS_TIMEOUT1_BCR 55 -#define GCC_SNOC_BUS_TIMEOUT2_BCR 56 -#define GCC_SNOC_BUS_TIMEOUT3_BCR 57 -#define GCC_PCNOC_BUS_TIMEOUT0_BCR 58 -#define GCC_PCNOC_BUS_TIMEOUT1_BCR 59 -#define GCC_PCNOC_BUS_TIMEOUT2_BCR 60 -#define GCC_PCNOC_BUS_TIMEOUT3_BCR 61 -#define GCC_PCNOC_BUS_TIMEOUT4_BCR 62 -#define GCC_PCNOC_BUS_TIMEOUT5_BCR 63 -#define GCC_PCNOC_BUS_TIMEOUT6_BCR 64 -#define GCC_PCNOC_BUS_TIMEOUT7_BCR 65 -#define GCC_PCNOC_BUS_TIMEOUT8_BCR 66 -#define GCC_PCNOC_BUS_TIMEOUT9_BCR 67 -#define GCC_TCSR_BCR 68 -#define GCC_QDSS_BCR 69 -#define GCC_MPM_BCR 70 -#define GCC_SPDM_BCR 71 - -#endif diff --git a/include/linux/bitmap.h b/include/linux/bitmap.h index 0a8503af9f1..40ca2212cb4 100644 --- a/include/linux/bitmap.h +++ b/include/linux/bitmap.h @@ -196,6 +196,14 @@ static inline void bitmap_fill(unsigned long *dst, unsigned int nbits) } } +static inline bool bitmap_empty(const unsigned long *src, unsigned int nbits) +{ + if (small_const_nbits(nbits)) + return !(*src & BITMAP_LAST_WORD_MASK(nbits)); + + return find_first_bit(src, nbits) == nbits; +} + static inline void bitmap_or(unsigned long *dst, const unsigned long *src1, const unsigned long *src2, unsigned int nbits) { diff --git a/include/soc/qcom/cmd-db.h b/include/soc/qcom/cmd-db.h new file mode 100644 index 00000000000..1190f2c22ca --- /dev/null +++ b/include/soc/qcom/cmd-db.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + * Copyright (c) 2024, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef __QCOM_COMMAND_DB_H__ +#define __QCOM_COMMAND_DB_H__ + +#include <linux/err.h> + +enum cmd_db_hw_type { + CMD_DB_HW_INVALID = 0, + CMD_DB_HW_MIN = 3, + CMD_DB_HW_ARC = CMD_DB_HW_MIN, + CMD_DB_HW_VRM = 4, + CMD_DB_HW_BCM = 5, + CMD_DB_HW_MAX = CMD_DB_HW_BCM, + CMD_DB_HW_ALL = 0xff, +}; + +#if IS_ENABLED(CONFIG_QCOM_COMMAND_DB) +u32 cmd_db_read_addr(const char *resource_id); + +#else +static inline u32 cmd_db_read_addr(const char *resource_id) +{ return 0; } + +#endif /* CONFIG_QCOM_COMMAND_DB */ +#endif /* __QCOM_COMMAND_DB_H__ */ diff --git a/include/soc/qcom/rpmh.h b/include/soc/qcom/rpmh.h new file mode 100644 index 00000000000..3421fbf1ee3 --- /dev/null +++ b/include/soc/qcom/rpmh.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + */ + +#ifndef __SOC_QCOM_RPMH_H__ +#define __SOC_QCOM_RPMH_H__ + +#include <dm/device-internal.h> +#include <soc/qcom/tcs.h> + + +#if IS_ENABLED(CONFIG_QCOM_RPMH) +int rpmh_write(const struct udevice *dev, enum rpmh_state state, + const struct tcs_cmd *cmd, u32 n); + +#else + +static inline int rpmh_write(const struct device *dev, enum rpmh_state state, + const struct tcs_cmd *cmd, u32 n) +{ return -ENODEV; } + +#endif /* CONFIG_QCOM_RPMH */ + +/* u-boot: no multithreading */ +#define rpmh_write_async(dev, state, cmd, n) rpmh_write(dev, state, cmd, n) + +#endif /* __SOC_QCOM_RPMH_H__ */ diff --git a/include/soc/qcom/tcs.h b/include/soc/qcom/tcs.h new file mode 100644 index 00000000000..3acca067c72 --- /dev/null +++ b/include/soc/qcom/tcs.h @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2016-2019, The Linux Foundation. All rights reserved. + */ + +#ifndef __SOC_QCOM_TCS_H__ +#define __SOC_QCOM_TCS_H__ + +#define MAX_RPMH_PAYLOAD 16 + +/** + * rpmh_state: state for the request + * + * RPMH_SLEEP_STATE: State of the resource when the processor subsystem + * is powered down. There is no client using the + * resource actively. + * RPMH_WAKE_ONLY_STATE: Resume resource state to the value previously + * requested before the processor was powered down. + * RPMH_ACTIVE_ONLY_STATE: Active or AMC mode requests. Resource state + * is aggregated immediately. + */ +enum rpmh_state { + RPMH_SLEEP_STATE, + RPMH_WAKE_ONLY_STATE, + RPMH_ACTIVE_ONLY_STATE, +}; + +/** + * struct tcs_cmd: an individual request to RPMH. + * + * @addr: the address of the resource slv_id:18:16 | offset:0:15 + * @data: the resource state request + * @wait: ensure that this command is complete before returning. + * Setting "wait" here only makes sense during rpmh_write_batch() for + * active-only transfers, this is because: + * rpmh_write() - Always waits. + * (DEFINE_RPMH_MSG_ONSTACK will set .wait_for_compl) + * rpmh_write_async() - Never waits. + * (There's no request completion callback) + */ +struct tcs_cmd { + u32 addr; + u32 data; + u32 wait; +}; + +/** + * struct tcs_request: A set of tcs_cmds sent together in a TCS + * + * @state: state for the request. + * @wait_for_compl: wait until we get a response from the h/w accelerator + * (same as setting cmd->wait for all commands in the request) + * @num_cmds: the number of @cmds in this request + * @cmds: an array of tcs_cmds + */ +struct tcs_request { + enum rpmh_state state; + u32 wait_for_compl; + u32 num_cmds; + struct tcs_cmd *cmds; +}; + +#define BCM_TCS_CMD_COMMIT_SHFT 30 +#define BCM_TCS_CMD_COMMIT_MASK 0x40000000 +#define BCM_TCS_CMD_VALID_SHFT 29 +#define BCM_TCS_CMD_VALID_MASK 0x20000000 +#define BCM_TCS_CMD_VOTE_X_SHFT 14 +#define BCM_TCS_CMD_VOTE_MASK 0x3fff +#define BCM_TCS_CMD_VOTE_Y_SHFT 0 +#define BCM_TCS_CMD_VOTE_Y_MASK 0xfffc000 + +/* Construct a Bus Clock Manager (BCM) specific TCS command */ +#define BCM_TCS_CMD(commit, valid, vote_x, vote_y) \ + (((commit) << BCM_TCS_CMD_COMMIT_SHFT) | \ + ((valid) << BCM_TCS_CMD_VALID_SHFT) | \ + ((cpu_to_le32(vote_x) & \ + BCM_TCS_CMD_VOTE_MASK) << BCM_TCS_CMD_VOTE_X_SHFT) | \ + ((cpu_to_le32(vote_y) & \ + BCM_TCS_CMD_VOTE_MASK) << BCM_TCS_CMD_VOTE_Y_SHFT)) + +#endif /* __SOC_QCOM_TCS_H__ */ diff --git a/scripts/basic/fixdep.c b/scripts/basic/fixdep.c index 5ced0f6b069..3d40bd7ee25 100644 --- a/scripts/basic/fixdep.c +++ b/scripts/basic/fixdep.c @@ -421,6 +421,8 @@ int main(int argc, char *argv[]) strcpy(tmp_buf, "SPL_"); else if (!strncmp(target, "tpl/", 4)) strcpy(tmp_buf, "TPL_"); + else if (!strncmp(target, "vpl/", 4)) + strcpy(tmp_buf, "VPL_"); /* end U-Boot hack */ xprintf("cmd_%s := %s\n\n", target, cmdline); diff --git a/scripts/gen_ll_addressable_symbols.sh b/scripts/gen_ll_addressable_symbols.sh index 13f670ae0ef..fc5ee0e9c0b 100755 --- a/scripts/gen_ll_addressable_symbols.sh +++ b/scripts/gen_ll_addressable_symbols.sh @@ -11,5 +11,6 @@ set -e echo '#include <linux/compiler.h>' -$@ 2>/dev/null | grep -oe '_u_boot_list_2_[a-zA-Z0-9_]*_2_[a-zA-Z0-9_]*' | \ - sort -u | sed -e 's/^\(.*\)/extern char \1[];\n__ADDRESSABLE(\1);/' +$@ 2>/dev/null | grep -oe '_u_boot_list_2_[a-zA-Z0-9_]*_2_[a-zA-Z0-9_]*' \ + -e '__stack_chk_guard' | sort -u | \ + sed -e 's/^\(.*\)/extern char \1[];\n__ADDRESSABLE(\1);/' diff --git a/tools/binman/binman.rst b/tools/binman/binman.rst index 872e9746c8c..0cafc36bdcb 100644 --- a/tools/binman/binman.rst +++ b/tools/binman/binman.rst @@ -1211,7 +1211,7 @@ Templates provide a simple way to handle this:: spi-image { filename = "image-spi.bin"; - insert-template = <&fit>; + insert-template = <&common_part>; /* things specific to SPI follow */ footer { @@ -1224,7 +1224,7 @@ Templates provide a simple way to handle this:: mmc-image { filename = "image-mmc.bin"; - insert-template = <&fit>; + insert-template = <&common_part>; /* things specific to MMC follow */ footer { diff --git a/tools/binman/bintools.rst b/tools/binman/bintools.rst index 1336f4d0115..cd05ad8cb26 100644 --- a/tools/binman/bintools.rst +++ b/tools/binman/bintools.rst @@ -10,6 +10,15 @@ binaries. It is fairly easy to create new bintools. Just add a new file to the +Bintool: bootgen: Generate bootable fsbl image for zynq/zynqmp +-------------------------------------------------------------- + +This bintools supports running Xilinx "bootgen" in order +to generate a bootable, authenticated image form an SPL. + + + + Bintool: bzip2: Compression/decompression using the bzip2 algorithm ------------------------------------------------------------------- @@ -37,6 +46,33 @@ Documentation about CBFS is at https://www.coreboot.org/CBFS +Bintool: cst: Image generation for U-Boot +----------------------------------------- + +This bintool supports running `cst` with some basic parameters as +needed by binman. + + + +Bintool: fdt_add_pubkey: Add public key to control dtb (spl or u-boot proper) +----------------------------------------------------------------------------- + +This bintool supports running `fdt_add_pubkey`. + +Normally mkimage adds signature information to the control dtb. However +binman images are built independent from each other. Thus it is required +to add the public key separately from mkimage. + + + +Bintool: fdtgrep: Handles the 'fdtgrep' tool +-------------------------------------------- + +This bintool supports running `fdtgrep` with parameters suitable for +producing SPL devicetrees from the main one. + + + Bintool: fiptool: Image generation for ARM Trusted Firmware ----------------------------------------------------------- @@ -143,11 +179,20 @@ Documentation is available via:: +Bintool: mkeficapsule: Handles the 'mkeficapsule' tool +------------------------------------------------------ + +This bintool is used for generating the EFI capsules. The +capsule generation parameters can either be specified through +commandline, or through a config file. + + + Bintool: mkimage: Image generation for U-Boot --------------------------------------------- This bintool supports running `mkimage` with some basic parameters as -neeed by binman. +needed by binman. Normally binman uses the mkimage built by U-Boot. But when run outside the U-Boot build system, binman can use the version installed in your system. @@ -194,25 +239,3 @@ Documentation is available via:: -Bintool: fdt_add_pubkey: Add public key to device tree ------------------------------------------------------- - -This bintool supports running `fdt_add_pubkey` in order to add a public -key coming from a certificate to a device-tree. - -Normally signing is done using `mkimage` in context of `binman sign`. However, -in this process the public key is not added to the stage before u-boot proper. -Using `fdt_add_pubkey` the key can be injected to the SPL independent of -`mkimage` - - - -Bintool: bootgen: Sign ZynqMP FSBL image ----------------------------------------- - -This bintool supports running `bootgen` in order to sign a SPL for ZynqMP -devices. - -The bintool automatically creates an appropriate input image file (.bif) for -bootgen based on the passed arguments. The output is a bootable, -authenticated `boot.bin` file. diff --git a/tools/binman/btool/cbfstool.py b/tools/binman/btool/cbfstool.py index 29be2d8a2b5..2d8559abb2b 100644 --- a/tools/binman/btool/cbfstool.py +++ b/tools/binman/btool/cbfstool.py @@ -214,6 +214,7 @@ class Bintoolcbfstool(bintool.Bintool): """ if method != bintool.FETCH_BIN: return None + # Version 4.22.01 fname, tmpdir = self.fetch_from_drive( - '1IOnE0Qvy97d-0WOCwF64xBGpKSY2sMtJ') + '1gxNxRuJgD0Iiy9LAPCSB_0959eJCp98g') return fname, tmpdir diff --git a/tools/binman/btool/fdt_add_pubkey.py b/tools/binman/btool/fdt_add_pubkey.py index a50774200c9..75a9716ad72 100644 --- a/tools/binman/btool/fdt_add_pubkey.py +++ b/tools/binman/btool/fdt_add_pubkey.py @@ -32,6 +32,10 @@ class Bintoolfdt_add_pubkey(bintool.Bintool): verified for the image / configuration to be considered valid. algo (str): Cryptographic algorithm. Optional parameter, default value: sha1,rsa2048 + + Returns: + CommandResult: Resulting output from the bintool, or None if the + tool is not present """ args = [] if algo: diff --git a/tools/binman/btool/fdtgrep.py b/tools/binman/btool/fdtgrep.py new file mode 100644 index 00000000000..da1f8c7bf4e --- /dev/null +++ b/tools/binman/btool/fdtgrep.py @@ -0,0 +1,137 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright 2022 Google LLC +# +"""Bintool implementation for fdtgrep + +fdtgrepprovides a way to grep devicetree-binary files to extract or remove +certain elements. + +Usage: fdtgrep - extract portions from device tree + +Usage: + fdtgrep <options> <dt file>|- + +Output formats are: + dts - device tree soure text + dtb - device tree blob (sets -Hmt automatically) + bin - device tree fragment (may not be a valid .dtb) + +Options: -[haAc:b:C:defg:G:HIlLmn:N:o:O:p:P:rRsStTvhV] + -a, --show-address Display address + -A, --colour Show all nodes/tags, colour those that match + -b, --include-node-with-prop <arg> Include contains containing property + -c, --include-compat <arg> Compatible nodes to include in grep + -C, --exclude-compat <arg> Compatible nodes to exclude in grep + -d, --diff Diff: Mark matching nodes with +, others with - + -e, --enter-node Enter direct subnode names of matching nodes + -f, --show-offset Display offset + -g, --include-match <arg> Node/property/compatible string to include in grep + -G, --exclude-match <arg> Node/property/compatible string to exclude in grep + -H, --show-header Output a header + -I, --show-version Put "/dts-v1/;" on first line of dts output + -l, --list-regions Output a region list + -L, --list-strings List strings in string table + -m, --include-mem Include mem_rsvmap section in binary output + -n, --include-node <arg> Node to include in grep + -N, --exclude-node <arg> Node to exclude in grep + -p, --include-prop <arg> Property to include in grep + -P, --exclude-prop <arg> Property to exclude in grep + -r, --remove-strings Remove unused strings from string table + -R, --include-root Include root node and all properties + -s, --show-subnodes Show all subnodes matching nodes + -S, --skip-supernodes Don't include supernodes of matching nodes + -t, --show-stringtab Include string table in binary output + -T, --show-aliases Include matching aliases in output + -o, --out <arg> -o <output file> + -O, --out-format <arg> -O <output format> + -v, --invert-match Invert the sense of matching (select non-matching lines) + -h, --help Print this help and exit + -V, --version Print version and exit +""" + +import tempfile + +from u_boot_pylib import tools +from binman import bintool + +class Bintoolfdtgrep(bintool.Bintool): + """Handles the 'fdtgrep' tool + + This bintool supports running `fdtgrep` with parameters suitable for + producing SPL devicetrees from the main one. + """ + def __init__(self, name): + super().__init__(name, 'Grep devicetree files') + + def create_for_phase(self, infile, phase, outfile, remove_props): + """Create the FDT for a particular phase + + Args: + infile (str): Input filename containing the full FDT contents (with + all nodes and properties) + phase (str): Phase to generate for ('tpl', 'vpl', 'spl') + outfile (str): Output filename to write the grepped FDT contents to + (with only neceesary nodes and properties) + + Returns: + CommandResult: Resulting output from the bintool, or None if the + tool is not present + """ + if phase == 'tpl': + tag = 'bootph-pre-sram' + elif phase == 'vpl': + tag = 'bootph-verify' + elif phase == 'spl': + tag = 'bootph-pre-ram' + else: + raise ValueError(f"Invalid U-Boot phase '{phase}': Use tpl/vpl/spl") + + # These args mirror those in cmd_fdtgrep in scripts/Makefile.lib + # First do the first stage + with tempfile.NamedTemporaryFile(prefix='fdtgrep.tmp', + dir=tools.get_output_dir()) as tmp: + args = [ + infile, + '-o', tmp.name, + '-b', 'bootph-all', + '-b', tag, + '-u', + '-RT', + '-n', '/chosen', + '-n', '/config', + '-O', 'dtb', + ] + self.run_cmd(*args) + args = [ + tmp.name, + '-o', outfile, + '-r', + '-O', 'dtb', + '-P', 'bootph-all', + '-P', 'bootph-pre-ram', + '-P', 'bootph-pre-sram', + '-P', 'bootph-verify', + ] + for prop_name in remove_props: + args += ['-P', prop_name] + return self.run_cmd(*args) + + def fetch(self, method): + """Fetch handler for fdtgrep + + This installs fdtgrep using the apt utility, which assumes that it is + packaged in u-boot tools, which it is not. + + Args: + method (FETCH_...): Method to use + + Returns: + True if the file was fetched and now installed, None if a method + other than FETCH_BIN was requested + + Raises: + Valuerror: Fetching could not be completed + """ + if method != bintool.FETCH_BIN: + return None + return self.apt_install('u-boot-tools') diff --git a/tools/binman/control.py b/tools/binman/control.py index 2f00279232b..542c2b45644 100644 --- a/tools/binman/control.py +++ b/tools/binman/control.py @@ -617,6 +617,50 @@ def PrepareImagesAndDtbs(dtb_fname, select_images, update_fdt, use_expanded): dtb_item.Flush() return images +def CheckForProblems(image): + """Check for problems with image generation + + Shows warning about missing, faked or optional external blobs, as well as + missing bintools. + + Args: + image (Image): Image to process + + Returns: + bool: True if there are any problems which result in a non-functional + image + """ + missing_list = [] + image.CheckMissing(missing_list) + if missing_list: + tout.error("Image '%s' is missing external blobs and is non-functional: %s\n" % + (image.name, ' '.join([e.name for e in missing_list]))) + _ShowHelpForMissingBlobs(tout.ERROR, missing_list) + + faked_list = [] + image.CheckFakedBlobs(faked_list) + if faked_list: + tout.warning( + "Image '%s' has faked external blobs and is non-functional: %s\n" % + (image.name, ' '.join([os.path.basename(e.GetDefaultFilename()) + for e in faked_list]))) + + optional_list = [] + image.CheckOptional(optional_list) + if optional_list: + tout.warning( + "Image '%s' is missing optional external blobs but is still functional: %s\n" % + (image.name, ' '.join([e.name for e in optional_list]))) + _ShowHelpForMissingBlobs(tout.WARNING, optional_list) + + missing_bintool_list = [] + image.check_missing_bintools(missing_bintool_list) + if missing_bintool_list: + tout.warning( + "Image '%s' has missing bintools and is non-functional: %s\n" % + (image.name, ' '.join([os.path.basename(bintool.name) + for bintool in missing_bintool_list]))) + return any([missing_list, faked_list, missing_bintool_list]) def ProcessImage(image, update_fdt, write_map, get_contents=True, allow_resize=True, allow_missing=False, @@ -689,38 +733,11 @@ def ProcessImage(image, update_fdt, write_map, get_contents=True, if write_map: image.WriteMap() - missing_list = [] - image.CheckMissing(missing_list) - if missing_list: - tout.error("Image '%s' is missing external blobs and is non-functional: %s\n" % - (image.name, ' '.join([e.name for e in missing_list]))) - _ShowHelpForMissingBlobs(tout.ERROR, missing_list) - - faked_list = [] - image.CheckFakedBlobs(faked_list) - if faked_list: - tout.warning( - "Image '%s' has faked external blobs and is non-functional: %s\n" % - (image.name, ' '.join([os.path.basename(e.GetDefaultFilename()) - for e in faked_list]))) + has_problems = CheckForProblems(image) - optional_list = [] - image.CheckOptional(optional_list) - if optional_list: - tout.warning( - "Image '%s' is missing optional external blobs but is still functional: %s\n" % - (image.name, ' '.join([e.name for e in optional_list]))) - _ShowHelpForMissingBlobs(tout.WARNING, optional_list) - - missing_bintool_list = [] - image.check_missing_bintools(missing_bintool_list) - if missing_bintool_list: - tout.warning( - "Image '%s' has missing bintools and is non-functional: %s\n" % - (image.name, ' '.join([os.path.basename(bintool.name) - for bintool in missing_bintool_list]))) - return any([missing_list, faked_list, missing_bintool_list]) + image.WriteAlternates() + return has_problems def Binman(args): """The main control code for binman diff --git a/tools/binman/elf.py b/tools/binman/elf.py index 2ecc95f7eb8..a4694056391 100644 --- a/tools/binman/elf.py +++ b/tools/binman/elf.py @@ -275,7 +275,7 @@ def LookupAndWriteSymbols(elf_fname, entry, section, is_elf=False, return 0 base = syms.get(base_sym) if not base and not is_elf: - tout.debug('LookupAndWriteSymbols: no base') + tout.debug(f'LookupAndWriteSymbols: no base: elf_fname={elf_fname}, base_sym={base_sym}, is_elf={is_elf}') return 0 base_addr = 0 if is_elf else base.address count = 0 diff --git a/tools/binman/entries.rst b/tools/binman/entries.rst index bdda1ef2855..12482703782 100644 --- a/tools/binman/entries.rst +++ b/tools/binman/entries.rst @@ -11,6 +11,48 @@ features to produce new behaviours. +.. _etype_alternates_fdt: + +Entry: alternates-fdt: Entry that generates alternative sections for each devicetree provided +--------------------------------------------------------------------------------------------- + +When creating an image designed to boot on multiple models, each model +requires its own devicetree. This entry deals with selecting the correct +devicetree from a directory containing them. Each one is read in turn, then +used to produce section contents which are written to a file. This results +in a number of images, one for each model. + +For example this produces images for each .dtb file in the 'dtb' directory:: + + alternates-fdt { + fdt-list-dir = "dtb"; + filename-pattern = "NAME.bin"; + fdt-phase = "tpl"; + + section { + u-boot-tpl { + }; + }; + }; + +Each output file is named based on its input file, so an input file of +`model1.dtb` results in an output file of `model1.bin` (i.e. the `NAME` in +the `filename-pattern` property is replaced with the .dtb basename). + +Note that this entry type still produces contents for the 'main' image, in +that case using the normal dtb provided to Binman, e.g. `u-boot-tpl.dtb`. +But that image is unlikely to be useful, since it relates to whatever dtb +happened to be the default when U-Boot builds +(i.e. `CONFIG_DEFAULT_DEVICE_TREE`). However, Binman ensures that the size +of each of the alternates is the same as the 'default' one, so they can in +principle be 'slotted in' to the appropriate place in the main image. + +The optional `fdt-phase` property indicates the phase to build. In this +case, it etype runs fdtgrep to obtain the devicetree subset for that phase, +respecting the `bootph-xxx` tags in the devicetree. + + + .. _etype_atf_bl31: Entry: atf-bl31: ARM Trusted Firmware (ATF) BL31 blob @@ -815,6 +857,13 @@ The top-level 'fit' node supports the following special properties: fit,fdt-list-val = "dtb1", "dtb2"; + fit,fdt-list-dir + As an alternative to fit,fdt-list the list of device tree files + can be provided as a directory. Each .dtb file in the directory is + processed, , e.g.:: + + fit,fdt-list-dir = "arch/arm/dts + Substitutions ~~~~~~~~~~~~~ @@ -890,6 +939,7 @@ You can create config nodes in a similar way:: firmware = "atf"; loadables = "uboot"; fdt = "fdt-SEQ"; + fit,compatible; // optional }; }; @@ -899,6 +949,39 @@ for each of your two files. Note that if no devicetree files are provided (with '-a of-list' as above) then no nodes will be generated. +The 'fit,compatible' property (if present) is replaced with the compatible +string from the root node of the devicetree, so that things work correctly +with FIT's configuration-matching algortihm. + +Dealing with phases +~~~~~~~~~~~~~~~~~~~ + +FIT can be used to load firmware. In this case it may be necessary to run +the devicetree for each model through fdtgrep to remove unwanted properties. +The 'fit,fdt-phase' property can be provided to indicate the phase for which +the devicetree is intended. + +For example this indicates that the FDT should be processed for VPL:: + + images { + @fdt-SEQ { + description = "fdt-NAME"; + type = "flat_dt"; + compression = "none"; + fit,fdt-phase = "vpl"; + }; + }; + +Using this mechanism, it is possible to generate a FIT which can provide VPL +images for multiple models, with TPL selecting the correct model to use. The +same approach can of course be used for SPL images. + +Note that the `of-spl-remove-props` entryarg can be used to indicate +additional properties to remove. It is often used to remove properties like +`clock-names` and `pinctrl-names` which are not needed in SPL builds. + +See :ref:`fdtgrep_filter` for more information. + Generating nodes from an ELF file (split-elf) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -2290,8 +2373,6 @@ u-boot-spl-dtb SPL can access binman symbols at runtime. See :ref:`binman_fdt`. -in the binman README for more information. - The ELF file 'spl/u-boot-spl' must also be available for this to work, since binman uses that to look up symbols to write into the SPL binary. @@ -2480,8 +2561,6 @@ u-boot-tpl-dtb TPL can access binman symbols at runtime. See :ref:`binman_fdt`. -in the binman README for more information. - The ELF file 'tpl/u-boot-tpl' must also be available for this to work, since binman uses that to look up symbols to write into the TPL binary. @@ -2571,6 +2650,9 @@ in the binman README for more information. The ELF file 'vpl/u-boot-vpl' must also be available for this to work, since binman uses that to look up symbols to write into the VPL binary. +Note that this entry is automatically replaced with u-boot-vpl-expanded +unless --no-expanded is used or the node has a 'no-expanded' property. + .. _etype_u_boot_vpl_bss_pad: @@ -2659,8 +2741,8 @@ Properties / Entry arguments: This is the U-Boot VPL binary, It does not include a device tree blob at the end of it so may not be able to work without it, assuming VPL needs -a device tree to operate on your platform. You can add a u_boot_vpl_dtb -entry after this one, or use a u_boot_vpl entry instead, which normally +a device tree to operate on your platform. You can add a u-boot-vpl-dtb +entry after this one, or use a u-boot-vpl entry instead, which normally expands to a section containing u-boot-vpl-dtb, u-boot-vpl-bss-pad and u-boot-vpl-dtb diff --git a/tools/binman/entry.py b/tools/binman/entry.py index 219d5dcecab..6d2f3789940 100644 --- a/tools/binman/entry.py +++ b/tools/binman/entry.py @@ -1199,6 +1199,9 @@ features to produce new behaviours. self.uncomp_size = len(indata) if self.comp_bintool.is_present(): data = self.comp_bintool.compress(indata) + uniq = self.GetUniqueName() + fname = tools.get_output_filename(f'comp.{uniq}') + tools.write_file(fname, data) else: self.record_missing_bintool(self.comp_bintool) data = tools.get_bytes(0, 1024) @@ -1383,3 +1386,17 @@ features to produce new behaviours. def UpdateSignatures(self, privatekey_fname, algo, input_fname): self.Raise('Updating signatures is not supported with this entry type') + + def FdtContents(self, fdt_etype): + """Get the contents of an FDT for a particular phase + + Args: + fdt_etype (str): Filename of the phase of the FDT to return, e.g. + 'u-boot-tpl-dtb' + + Returns: + tuple: + fname (str): Filename of .dtb + bytes: Contents of FDT (possibly run through fdtgrep) + """ + return self.section.FdtContents(fdt_etype) diff --git a/tools/binman/etype/alternates_fdt.py b/tools/binman/etype/alternates_fdt.py new file mode 100644 index 00000000000..808f535aa1b --- /dev/null +++ b/tools/binman/etype/alternates_fdt.py @@ -0,0 +1,132 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright 2024 Google LLC +# Written by Simon Glass <sjg@chromium.org> + +"""Entry-type module for producing multiple alternate sections""" + +import glob +import os + +from binman.entry import EntryArg +from binman.etype.section import Entry_section +from dtoc import fdt_util +from u_boot_pylib import tools + +class Entry_alternates_fdt(Entry_section): + """Entry that generates alternative sections for each devicetree provided + + When creating an image designed to boot on multiple models, each model + requires its own devicetree. This entry deals with selecting the correct + devicetree from a directory containing them. Each one is read in turn, then + used to produce section contents which are written to a file. This results + in a number of images, one for each model. + + For example this produces images for each .dtb file in the 'dtb' directory:: + + alternates-fdt { + fdt-list-dir = "dtb"; + filename-pattern = "NAME.bin"; + fdt-phase = "tpl"; + + section { + u-boot-tpl { + }; + }; + }; + + Each output file is named based on its input file, so an input file of + `model1.dtb` results in an output file of `model1.bin` (i.e. the `NAME` in + the `filename-pattern` property is replaced with the .dtb basename). + + Note that this entry type still produces contents for the 'main' image, in + that case using the normal dtb provided to Binman, e.g. `u-boot-tpl.dtb`. + But that image is unlikely to be useful, since it relates to whatever dtb + happened to be the default when U-Boot builds + (i.e. `CONFIG_DEFAULT_DEVICE_TREE`). However, Binman ensures that the size + of each of the alternates is the same as the 'default' one, so they can in + principle be 'slotted in' to the appropriate place in the main image. + + The optional `fdt-phase` property indicates the phase to build. In this + case, it etype runs fdtgrep to obtain the devicetree subset for that phase, + respecting the `bootph-xxx` tags in the devicetree. + """ + def __init__(self, section, etype, node): + super().__init__(section, etype, node) + self.fdt_list_dir = None + self.filename_pattern = None + self.required_props = ['fdt-list-dir'] + self._cur_fdt = None + self._fdt_phase = None + self.fdtgrep = None + self._fdt_dir = None + self._fdts = None + self._fname_pattern = None + self._remove_props = None + self.alternates = None + + def ReadNode(self): + """Read properties from the node""" + super().ReadNode() + self._fdt_dir = fdt_util.GetString(self._node, 'fdt-list-dir') + fname = tools.get_input_filename(self._fdt_dir) + fdts = glob.glob('*.dtb', root_dir=fname) + self._fdts = [os.path.splitext(f)[0] for f in fdts] + + self._fdt_phase = fdt_util.GetString(self._node, 'fdt-phase') + + # This is used by Image.WriteAlternates() + self.alternates = self._fdts + + self._fname_pattern = fdt_util.GetString(self._node, 'filename-pattern') + + self._remove_props = [] + props, = self.GetEntryArgsOrProps( + [EntryArg('of-spl-remove-props', str)], required=False) + if props: + self._remove_props = props.split() + + def FdtContents(self, fdt_etype): + # If there is no current FDT, just use the normal one + if not self._cur_fdt: + return self.section.FdtContents(fdt_etype) + + # Find the file to use + fname = os.path.join(self._fdt_dir, f'{self._cur_fdt}.dtb') + infile = tools.get_input_filename(fname) + + # Run fdtgrep if needed, to remove unwanted nodes and properties + if self._fdt_phase: + uniq = self.GetUniqueName() + outfile = tools.get_output_filename( + f'{uniq}.{self._cur_fdt}-{self._fdt_phase}.dtb') + self.fdtgrep.create_for_phase(infile, self._fdt_phase, outfile, + self._remove_props) + return outfile, tools.read_file(outfile) + return fname, tools.read_file(infile) + + def ProcessWithFdt(self, alt): + """Produce the contents of this entry, using a particular FDT blob + + Args: + alt (str): Name of the alternate + + Returns: + tuple: + str: Filename to use for the alternate's .bin file + bytes: Contents of this entry's section, using the selected FDT + """ + pattern = self._fname_pattern or 'NAME.bin' + fname = pattern.replace('NAME', alt) + + data = b'' + try: + self._cur_fdt = alt + self.ProcessContents() + data = self.GetPaddedData() + finally: + self._cur_fdt = None + return fname, data + + def AddBintools(self, btools): + super().AddBintools(btools) + self.fdtgrep = self.AddBintool(btools, 'fdtgrep') diff --git a/tools/binman/etype/blob_dtb.py b/tools/binman/etype/blob_dtb.py index d543de9f759..b234323d7cf 100644 --- a/tools/binman/etype/blob_dtb.py +++ b/tools/binman/etype/blob_dtb.py @@ -41,12 +41,12 @@ class Entry_blob_dtb(Entry_blob): def ObtainContents(self, fake_size=0): """Get the device-tree from the list held by the 'state' module""" self._filename = self.GetDefaultFilename() - self._pathname, _ = state.GetFdtContents(self.GetFdtEtype()) + self._pathname, _ = self.FdtContents(self.GetFdtEtype()) return super().ReadBlobContents() def ProcessContents(self): """Re-read the DTB contents so that we get any calculated properties""" - _, indata = state.GetFdtContents(self.GetFdtEtype()) + _, indata = self.FdtContents(self.GetFdtEtype()) if self.compress == 'zstd' and self.prepend != 'length': self.Raise('The zstd compression requires a length header') @@ -57,7 +57,9 @@ class Entry_blob_dtb(Entry_blob): def GetFdtEtype(self): """Get the entry type of this device tree - This can be 'u-boot-dtb', 'u-boot-spl-dtb' or 'u-boot-tpl-dtb' + This can be 'u-boot-dtb', 'u-boot-spl-dtb', 'u-boot-tpl-dtb' or + 'u-boot-vpl-dtb' + Returns: Entry type if any, e.g. 'u-boot-dtb' """ diff --git a/tools/binman/etype/fit.py b/tools/binman/etype/fit.py index 2c14b15b03c..ee44e5a1cd6 100644 --- a/tools/binman/etype/fit.py +++ b/tools/binman/etype/fit.py @@ -5,7 +5,9 @@ """Entry-type module for producing a FIT""" +import glob import libfdt +import os from binman.entry import Entry, EntryArg from binman.etype.section import Entry_section @@ -87,6 +89,13 @@ class Entry_fit(Entry_section): fit,fdt-list-val = "dtb1", "dtb2"; + fit,fdt-list-dir + As an alternative to fit,fdt-list the list of device tree files + can be provided as a directory. Each .dtb file in the directory is + processed, , e.g.:: + + fit,fdt-list-dir = "arch/arm/dts + Substitutions ~~~~~~~~~~~~~ @@ -162,6 +171,7 @@ class Entry_fit(Entry_section): firmware = "atf"; loadables = "uboot"; fdt = "fdt-SEQ"; + fit,compatible; // optional }; }; @@ -171,6 +181,40 @@ class Entry_fit(Entry_section): Note that if no devicetree files are provided (with '-a of-list' as above) then no nodes will be generated. + The 'fit,compatible' property (if present) is replaced with the compatible + string from the root node of the devicetree, so that things work correctly + with FIT's configuration-matching algortihm. + + Dealing with phases + ~~~~~~~~~~~~~~~~~~~ + + FIT can be used to load firmware. In this case it may be necessary to run + the devicetree for each model through fdtgrep to remove unwanted properties. + The 'fit,fdt-phase' property can be provided to indicate the phase for which + the devicetree is intended. + + For example this indicates that the FDT should be processed for VPL:: + + images { + @fdt-SEQ { + description = "fdt-NAME"; + type = "flat_dt"; + compression = "none"; + fit,fdt-phase = "vpl"; + }; + }; + + Using this mechanism, it is possible to generate a FIT which can provide VPL + images for multiple models, with TPL selecting the correct model to use. The + same approach can of course be used for SPL images. + + Note that the `of-spl-remove-props` entryarg can be used to indicate + additional properties to remove. It is often used to remove properties like + `clock-names` and `pinctrl-names` which are not needed in SPL builds. This + value is automatically passed to binman by the U-Boot build. + + See :ref:`fdtgrep_filter` for more information. + Generating nodes from an ELF file (split-elf) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -352,9 +396,16 @@ class Entry_fit(Entry_section): self._fit = None self._fit_props = {} self._fdts = None + self._fdt_dir = None self.mkimage = None + self.fdtgrep = None self._priv_entries = {} self._loadables = [] + self._remove_props = [] + props, = self.GetEntryArgsOrProps( + [EntryArg('of-spl-remove-props', str)], required=False) + if props: + self._remove_props = props.split() def ReadNode(self): super().ReadNode() @@ -368,7 +419,14 @@ class Entry_fit(Entry_section): if fdts is not None: self._fdts = fdts.split() else: - self._fdts = fdt_util.GetStringList(self._node, 'fit,fdt-list-val') + self._fdt_dir = fdt_util.GetString(self._node, 'fit,fdt-list-dir') + if self._fdt_dir: + indir = tools.get_input_filename(self._fdt_dir) + fdts = glob.glob('*.dtb', root_dir=indir) + self._fdts = [os.path.splitext(f)[0] for f in sorted(fdts)] + else: + self._fdts = fdt_util.GetStringList(self._node, + 'fit,fdt-list-val') self._fit_default_dt = self.GetEntryArgsOrProps([EntryArg('default-dt', str)])[0] @@ -483,6 +541,19 @@ class Entry_fit(Entry_section): rel_path = node.path[len(self._node.path) + 1:] self.Raise(f"subnode '{rel_path}': {msg}") + def _run_fdtgrep(self, infile, phase, outfile): + """Run fdtgrep to create the dtb for a phase + + Args: + infile (str): Input filename containing the full FDT contents (with + all nodes and properties) + phase (str): Phase to generate for ('tpl', 'vpl', 'spl') + outfile (str): Output filename to write the grepped FDT contents to + (with only neceesary nodes and properties) + """ + return self.fdtgrep.create_for_phase(infile, phase, outfile, + self._remove_props) + def _build_input(self): """Finish the FIT by adding the 'data' properties to it @@ -584,6 +655,7 @@ class Entry_fit(Entry_section): for seq, fdt_fname in enumerate(self._fdts): node_name = node.name[1:].replace('SEQ', str(seq + 1)) fname = tools.get_input_filename(fdt_fname + '.dtb') + fdt_phase = None with fsw.add_node(node_name): for pname, prop in node.props.items(): if pname == 'fit,firmware': @@ -594,6 +666,15 @@ class Entry_fit(Entry_section): fsw.property('loadables', val.encode('utf-8')) elif pname == 'fit,operation': pass + elif pname == 'fit,compatible': + fdt_phase = fdt_util.GetString(node, pname) + data = tools.read_file(fname) + fdt = Fdt.FromData(data) + fdt.Scan() + prop = fdt.GetRoot().props['compatible'] + fsw.property('compatible', prop.bytes) + elif pname == 'fit,fdt-phase': + fdt_phase = fdt_util.GetString(node, pname) elif pname.startswith('fit,'): self._raise_subnode( node, f"Unknown directive '{pname}'") @@ -606,7 +687,14 @@ class Entry_fit(Entry_section): # Add data for 'images' nodes (but not 'config') if depth == 1 and in_images: - fsw.property('data', tools.read_file(fname)) + if fdt_phase: + phase_fname = tools.get_output_filename( + f'{fdt_fname}-{fdt_phase}.dtb') + self._run_fdtgrep(fname, fdt_phase, phase_fname) + data = tools.read_file(phase_fname) + else: + data = tools.read_file(fname) + fsw.property('data', data) for subnode in node.subnodes: with fsw.add_node(subnode.name): @@ -834,6 +922,7 @@ class Entry_fit(Entry_section): def AddBintools(self, btools): super().AddBintools(btools) self.mkimage = self.AddBintool(btools, 'mkimage') + self.fdtgrep = self.AddBintool(btools, 'fdtgrep') def CheckMissing(self, missing_list): # We must use our private entry list for this since generator nodes diff --git a/tools/binman/etype/u_boot_spl_nodtb.py b/tools/binman/etype/u_boot_spl_nodtb.py index e7ec329c902..0e172aec1b0 100644 --- a/tools/binman/etype/u_boot_spl_nodtb.py +++ b/tools/binman/etype/u_boot_spl_nodtb.py @@ -23,8 +23,6 @@ class Entry_u_boot_spl_nodtb(Entry_blob): SPL can access binman symbols at runtime. See :ref:`binman_fdt`. - in the binman README for more information. - The ELF file 'spl/u-boot-spl' must also be available for this to work, since binman uses that to look up symbols to write into the SPL binary. """ diff --git a/tools/binman/etype/u_boot_tpl_nodtb.py b/tools/binman/etype/u_boot_tpl_nodtb.py index 9bb2b5dda30..e0c8a557d07 100644 --- a/tools/binman/etype/u_boot_tpl_nodtb.py +++ b/tools/binman/etype/u_boot_tpl_nodtb.py @@ -23,8 +23,6 @@ class Entry_u_boot_tpl_nodtb(Entry_blob): TPL can access binman symbols at runtime. See :ref:`binman_fdt`. - in the binman README for more information. - The ELF file 'tpl/u-boot-tpl' must also be available for this to work, since binman uses that to look up symbols to write into the TPL binary. """ diff --git a/tools/binman/etype/u_boot_vpl.py b/tools/binman/etype/u_boot_vpl.py index 31d7e8374e2..0797831688f 100644 --- a/tools/binman/etype/u_boot_vpl.py +++ b/tools/binman/etype/u_boot_vpl.py @@ -27,6 +27,9 @@ class Entry_u_boot_vpl(Entry_blob): The ELF file 'vpl/u-boot-vpl' must also be available for this to work, since binman uses that to look up symbols to write into the VPL binary. + + Note that this entry is automatically replaced with u-boot-vpl-expanded + unless --no-expanded is used or the node has a 'no-expanded' property. """ def __init__(self, section, etype, node): super().__init__(section, etype, node, auto_write_symbols=True) diff --git a/tools/binman/etype/u_boot_vpl_nodtb.py b/tools/binman/etype/u_boot_vpl_nodtb.py index 64c2767488d..765cf53d164 100644 --- a/tools/binman/etype/u_boot_vpl_nodtb.py +++ b/tools/binman/etype/u_boot_vpl_nodtb.py @@ -16,8 +16,8 @@ class Entry_u_boot_vpl_nodtb(Entry_blob): This is the U-Boot VPL binary, It does not include a device tree blob at the end of it so may not be able to work without it, assuming VPL needs - a device tree to operate on your platform. You can add a u_boot_vpl_dtb - entry after this one, or use a u_boot_vpl entry instead, which normally + a device tree to operate on your platform. You can add a u-boot-vpl-dtb + entry after this one, or use a u-boot-vpl entry instead, which normally expands to a section containing u-boot-vpl-dtb, u-boot-vpl-bss-pad and u-boot-vpl-dtb diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py index e4da04030a5..93f3d22cf57 100644 --- a/tools/binman/ftest.py +++ b/tools/binman/ftest.py @@ -7,6 +7,7 @@ # python -m unittest func_test.TestFunctional.testHelp import collections +import glob import gzip import hashlib from optparse import OptionParser @@ -546,7 +547,7 @@ class TestFunctional(unittest.TestCase): dtb_data = self._SetupDtb(fname) # For testing purposes, make a copy of the DT for SPL and TPL. Add - # a node indicating which it is, so aid verification. + # a node indicating which it is, to aid verification. for name in ['spl', 'tpl', 'vpl']: dtb_fname = '%s/u-boot-%s.dtb' % (name, name) outfile = os.path.join(self._indir, dtb_fname) @@ -4180,8 +4181,8 @@ class TestFunctional(unittest.TestCase): data = self._DoReadFile('172_scp.dts') self.assertEqual(SCP_DATA, data[:len(SCP_DATA)]) - def testFitFdt(self): - """Test an image with an FIT with multiple FDT images""" + def CheckFitFdt(self, dts='170_fit_fdt.dts', use_fdt_list=True): + """Check an image with an FIT with multiple FDT images""" def _CheckFdt(seq, expected_data): """Check the FDT nodes @@ -4220,11 +4221,12 @@ class TestFunctional(unittest.TestCase): self.assertEqual('fdt-%d' % seq, fnode.props['fdt'].value) entry_args = { - 'of-list': 'test-fdt1 test-fdt2', 'default-dt': 'test-fdt2', } + if use_fdt_list: + entry_args['of-list'] = 'test-fdt1 test-fdt2' data = self._DoReadFileDtb( - '170_fit_fdt.dts', + dts, entry_args=entry_args, extra_indirs=[os.path.join(self._indir, TEST_FDT_SUBDIR)])[0] self.assertEqual(U_BOOT_NODTB_DATA, data[-len(U_BOOT_NODTB_DATA):]) @@ -4243,6 +4245,10 @@ class TestFunctional(unittest.TestCase): _CheckConfig(1, TEST_FDT1_DATA) _CheckConfig(2, TEST_FDT2_DATA) + def testFitFdt(self): + """Test an image with an FIT with multiple FDT images""" + self.CheckFitFdt() + def testFitFdtMissingList(self): """Test handling of a missing 'of-list' entry arg""" with self.assertRaises(ValueError) as e: @@ -7175,27 +7181,24 @@ fdt fdtmap Extract the devicetree blob from the fdtmap def testSplPubkeyDtb(self): - """Test u_boot_spl_pubkey_dtb etype""" - data = tools.read_file(self.TestFile("key.pem")) - self._MakeInputFile("key.crt", data) - self._DoReadFileRealDtb('306_spl_pubkey_dtb.dts') - image = control.images['image'] - entries = image.GetEntries() - dtb_entry = entries['u-boot-spl-pubkey-dtb'] - dtb_data = dtb_entry.GetData() - dtb = fdt.Fdt.FromData(dtb_data) - dtb.Scan() - - signature_node = dtb.GetNode('/signature') - self.assertIsNotNone(signature_node) - key_node = signature_node.FindNode("key-key") - self.assertIsNotNone(key_node) - self.assertEqual(fdt_util.GetString(key_node, "required"), - "conf") - self.assertEqual(fdt_util.GetString(key_node, "algo"), - "sha384,rsa4096") - self.assertEqual(fdt_util.GetString(key_node, "key-name-hint"), - "key") + """Test u_boot_spl_pubkey_dtb etype""" + data = tools.read_file(self.TestFile("key.pem")) + self._MakeInputFile("key.crt", data) + self._DoReadFileRealDtb('306_spl_pubkey_dtb.dts') + image = control.images['image'] + entries = image.GetEntries() + dtb_entry = entries['u-boot-spl-pubkey-dtb'] + dtb_data = dtb_entry.GetData() + dtb = fdt.Fdt.FromData(dtb_data) + dtb.Scan() + + signature_node = dtb.GetNode('/signature') + self.assertIsNotNone(signature_node) + key_node = signature_node.FindNode("key-key") + self.assertIsNotNone(key_node) + self.assertEqual(fdt_util.GetString(key_node, "required"), "conf") + self.assertEqual(fdt_util.GetString(key_node, "algo"), "sha384,rsa4096") + self.assertEqual(fdt_util.GetString(key_node, "key-name-hint"), "key") def testXilinxBootgenSigning(self): """Test xilinx-bootgen etype""" @@ -7487,6 +7490,206 @@ fdt fdtmap Extract the devicetree blob from the fdtmap err, "Image '.*' is missing external blobs and is non-functional: .*") + def SetupAlternateDts(self): + """Compile the .dts test files for alternative-fdt + + Returns: + tuple: + str: Test directory created + list of str: '.bin' files which we expect Binman to create + """ + testdir = TestFunctional._MakeInputDir('dtb') + dtb_list = [] + for fname in glob.glob(f'{self.TestFile("alt_dts")}/*.dts'): + tmp_fname = fdt_util.EnsureCompiled(fname, testdir) + base = os.path.splitext(os.path.basename(fname))[0] + dtb_list.append(base + '.bin') + shutil.move(tmp_fname, os.path.join(testdir, base + '.dtb')) + + return testdir, dtb_list + + def CheckAlternates(self, dts, phase, xpl_data): + """Run the test for the alterative-fdt etype + + Args: + dts (str): Devicetree file to process + phase (str): Phase to process ('spl', 'tpl' or 'vpl') + xpl_data (bytes): Expected data for the phase's binary + + Returns: + dict of .dtb files produced + key: str filename + value: Fdt object + """ + dtb_list = self.SetupAlternateDts()[1] + + entry_args = { + f'{phase}-dtb': '1', + f'{phase}-bss-pad': 'y', + 'of-spl-remove-props': 'prop-to-remove another-prop-to-get-rid-of', + } + data = self._DoReadFileDtb(dts, use_real_dtb=True, update_dtb=True, + use_expanded=True, entry_args=entry_args)[0] + self.assertEqual(xpl_data, data[:len(xpl_data)]) + rest = data[len(xpl_data):] + pad_len = 10 + self.assertEqual(tools.get_bytes(0, pad_len), rest[:pad_len]) + + # Check the dtb is using the test file + dtb_data = rest[pad_len:] + dtb = fdt.Fdt.FromData(dtb_data) + dtb.Scan() + fdt_size = dtb.GetFdtObj().totalsize() + self.assertEqual('model-not-set', + fdt_util.GetString(dtb.GetRoot(), 'compatible')) + + pad_len = 10 + + # Check the other output files + dtbs = {} + for fname in dtb_list: + pathname = tools.get_output_filename(fname) + self.assertTrue(os.path.exists(pathname)) + + data = tools.read_file(pathname) + self.assertEqual(xpl_data, data[:len(xpl_data)]) + rest = data[len(xpl_data):] + + self.assertEqual(tools.get_bytes(0, pad_len), rest[:pad_len]) + rest = rest[pad_len:] + + dtb = fdt.Fdt.FromData(rest) + dtb.Scan() + dtbs[fname] = dtb + + expected = 'one' if '1' in fname else 'two' + self.assertEqual(f'u-boot,model-{expected}', + fdt_util.GetString(dtb.GetRoot(), 'compatible')) + + # Make sure the FDT is the same size as the 'main' one + rest = rest[fdt_size:] + + self.assertEqual(b'', rest) + return dtbs + + def testAlternatesFdt(self): + """Test handling of alternates-fdt etype""" + self._SetupTplElf() + dtbs = self.CheckAlternates('328_alternates_fdt.dts', 'tpl', + U_BOOT_TPL_NODTB_DATA) + for dtb in dtbs.values(): + # Check for the node with the tag + node = dtb.GetNode('/node') + self.assertIsNotNone(node) + self.assertEqual(5, len(node.props.keys())) + + # Make sure the other node is still there + self.assertIsNotNone(dtb.GetNode('/node/other-node')) + + def testAlternatesFdtgrep(self): + """Test handling of alternates-fdt etype using fdtgrep""" + self._SetupTplElf() + dtbs = self.CheckAlternates('329_alternates_fdtgrep.dts', 'tpl', + U_BOOT_TPL_NODTB_DATA) + for dtb in dtbs.values(): + # Check for the node with the tag + node = dtb.GetNode('/node') + self.assertIsNotNone(node) + self.assertEqual({'some-prop', 'not-a-prop-to-remove'}, + node.props.keys()) + + # Make sure the other node is gone + self.assertIsNone(dtb.GetNode('/node/other-node')) + + def testAlternatesFdtgrepVpl(self): + """Test handling of alternates-fdt etype using fdtgrep with vpl""" + self._SetupVplElf() + dtbs = self.CheckAlternates('330_alternates_vpl.dts', 'vpl', + U_BOOT_VPL_NODTB_DATA) + + def testAlternatesFdtgrepSpl(self): + """Test handling of alternates-fdt etype using fdtgrep with spl""" + self._SetupSplElf() + dtbs = self.CheckAlternates('331_alternates_spl.dts', 'spl', + U_BOOT_SPL_NODTB_DATA) + + def testAlternatesFdtgrepInval(self): + """Test alternates-fdt etype using fdtgrep with invalid phase""" + self._SetupSplElf() + with self.assertRaises(ValueError) as e: + dtbs = self.CheckAlternates('332_alternates_inval.dts', 'spl', + U_BOOT_SPL_NODTB_DATA) + self.assertIn("Invalid U-Boot phase 'bad-phase': Use tpl/vpl/spl", + str(e.exception)) + + def testFitFdtListDir(self): + """Test an image with an FIT with FDT images using fit,fdt-list-dir""" + self.CheckFitFdt('333_fit_fdt_dir.dts', False) + + def testFitFdtCompat(self): + """Test an image with an FIT with compatible in the config nodes""" + entry_args = { + 'of-list': 'model1 model2', + 'default-dt': 'model2', + } + testdir, dtb_list = self.SetupAlternateDts() + data = self._DoReadFileDtb( + '334_fit_fdt_compat.dts', use_real_dtb=True, update_dtb=True, + entry_args=entry_args, extra_indirs=[testdir])[0] + + fit_data = data[len(U_BOOT_DATA):-len(U_BOOT_NODTB_DATA)] + + fit = fdt.Fdt.FromData(fit_data) + fit.Scan() + + cnode = fit.GetNode('/configurations') + self.assertIn('default', cnode.props) + self.assertEqual('config-2', cnode.props['default'].value) + + for seq in range(1, 2): + name = f'config-{seq}' + fnode = fit.GetNode('/configurations/%s' % name) + self.assertIsNotNone(fnode) + self.assertIn('compatible', fnode.props.keys()) + expected = 'one' if seq == 1 else 'two' + self.assertEqual(f'u-boot,model-{expected}', + fnode.props['compatible'].value) + + def testFitFdtPhase(self): + """Test an image with an FIT with fdt-phase in the fdt nodes""" + phase = 'tpl' + entry_args = { + f'{phase}-dtb': '1', + f'{phase}-bss-pad': 'y', + 'of-spl-remove-props': 'prop-to-remove another-prop-to-get-rid-of', + 'of-list': 'model1 model2', + 'default-dt': 'model2', + } + testdir, dtb_list = self.SetupAlternateDts() + data = self._DoReadFileDtb( + '335_fit_fdt_phase.dts', use_real_dtb=True, update_dtb=True, + entry_args=entry_args, extra_indirs=[testdir])[0] + fit_data = data[len(U_BOOT_DATA):-len(U_BOOT_NODTB_DATA)] + fit = fdt.Fdt.FromData(fit_data) + fit.Scan() + + # Check that each FDT has only the expected properties for the phase + for seq in range(1, 2): + fnode = fit.GetNode(f'/images/fdt-{seq}') + self.assertIsNotNone(fnode) + dtb = fdt.Fdt.FromData(fnode.props['data'].bytes) + dtb.Scan() + + # Make sure that the 'bootph-pre-sram' tag in /node protects it from + # removal + node = dtb.GetNode('/node') + self.assertIsNotNone(node) + self.assertEqual({'some-prop', 'not-a-prop-to-remove'}, + node.props.keys()) + + # Make sure the other node is gone + self.assertIsNone(dtb.GetNode('/node/other-node')) + if __name__ == "__main__": unittest.main() diff --git a/tools/binman/image.py b/tools/binman/image.py index e77b5d0d97c..702c9055585 100644 --- a/tools/binman/image.py +++ b/tools/binman/image.py @@ -21,6 +21,9 @@ from dtoc import fdt_util from u_boot_pylib import tools from u_boot_pylib import tout +# This is imported if needed +state = None + class Image(section.Entry_section): """A Image, representing an output from binman @@ -75,6 +78,10 @@ class Image(section.Entry_section): def __init__(self, name, node, copy_to_orig=True, test=False, ignore_missing=False, use_expanded=False, missing_etype=False, generate=True): + # Put this here to allow entry-docs and help to work without libfdt + global state + from binman import state + super().__init__(None, 'section', node, test=test) self.copy_to_orig = copy_to_orig self.name = name @@ -186,6 +193,19 @@ class Image(section.Entry_section): os.remove(sname) os.symlink(fname, sname) + def WriteAlternates(self): + """Write out alternative devicetree blobs, each in its own file""" + alt_entry = self.FindEntryType('alternates-fdt') + if not alt_entry: + return + + for alt in alt_entry.alternates: + fname, data = alt_entry.ProcessWithFdt(alt) + pathname = tools.get_output_filename(fname) + tout.info(f"Writing alternate '{alt}' to '{pathname}'") + tools.write_file(pathname, data) + tout.info("Wrote %#x bytes" % len(data)) + def WriteMap(self): """Write a map of the image to a .map file @@ -418,3 +438,7 @@ class Image(section.Entry_section): super().AddBintools(bintools) self.bintools = bintools return bintools + + def FdtContents(self, fdt_etype): + """This base-class implementation simply calls the state function""" + return state.GetFdtContents(fdt_etype) diff --git a/tools/binman/main.py b/tools/binman/main.py index 92d2431aea7..dc817ddcd42 100755 --- a/tools/binman/main.py +++ b/tools/binman/main.py @@ -122,6 +122,8 @@ def RunBinman(args): ret_code = RunTests(args.debug, args.verbosity, args.processes, args.test_preserve_dirs, args.tests, args.toolpath) + if args.debug and not test_util.use_concurrent: + print('Tests can run in parallel: pip install concurrencytest') elif args.cmd == 'bintool-docs': control.write_bintool_docs(bintool.Bintool.get_tool_list()) diff --git a/tools/binman/test/328_alternates_fdt.dts b/tools/binman/test/328_alternates_fdt.dts new file mode 100644 index 00000000000..c913c8e4745 --- /dev/null +++ b/tools/binman/test/328_alternates_fdt.dts @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2024 Google LLC +// Written by Simon Glass <sjg@chromium.org> + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + compatible = "model-not-set"; + + binman { + alternates-fdt { + fdt-list-dir = "dtb"; + filename-pattern = "NAME.bin"; + + section { + u-boot-tpl { + }; + }; + }; + + blob { + filename = "blobfile"; + }; + }; +}; diff --git a/tools/binman/test/329_alternates_fdtgrep.dts b/tools/binman/test/329_alternates_fdtgrep.dts new file mode 100644 index 00000000000..41695281456 --- /dev/null +++ b/tools/binman/test/329_alternates_fdtgrep.dts @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2024 Google LLC +// Written by Simon Glass <sjg@chromium.org> + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + compatible = "model-not-set"; + + binman { + alternates-fdt { + fdt-list-dir = "dtb"; + filename-pattern = "NAME.bin"; + fdt-phase = "tpl"; + + section { + u-boot-tpl { + }; + }; + }; + + blob { + filename = "blobfile"; + }; + }; +}; diff --git a/tools/binman/test/330_alternates_vpl.dts b/tools/binman/test/330_alternates_vpl.dts new file mode 100644 index 00000000000..5b57069e2ab --- /dev/null +++ b/tools/binman/test/330_alternates_vpl.dts @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2024 Google LLC +// Written by Simon Glass <sjg@chromium.org> + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + compatible = "model-not-set"; + + binman { + alternates-fdt { + fdt-list-dir = "dtb"; + filename-pattern = "NAME.bin"; + fdt-phase = "vpl"; + + section { + u-boot-vpl { + }; + }; + }; + + blob { + filename = "blobfile"; + }; + }; +}; diff --git a/tools/binman/test/331_alternates_spl.dts b/tools/binman/test/331_alternates_spl.dts new file mode 100644 index 00000000000..882fefce34a --- /dev/null +++ b/tools/binman/test/331_alternates_spl.dts @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2024 Google LLC +// Written by Simon Glass <sjg@chromium.org> + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + compatible = "model-not-set"; + + binman { + alternates-fdt { + fdt-list-dir = "dtb"; + filename-pattern = "NAME.bin"; + fdt-phase = "spl"; + + section { + u-boot-spl { + }; + }; + }; + + blob { + filename = "blobfile"; + }; + }; +}; diff --git a/tools/binman/test/332_alternates_inval.dts b/tools/binman/test/332_alternates_inval.dts new file mode 100644 index 00000000000..8c145dd2449 --- /dev/null +++ b/tools/binman/test/332_alternates_inval.dts @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2024 Google LLC +// Written by Simon Glass <sjg@chromium.org> + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + compatible = "model-not-set"; + + binman { + alternates-fdt { + fdt-list-dir = "dtb"; + filename-pattern = "NAME.bin"; + fdt-phase = "bad-phase"; + + section { + u-boot-spl { + }; + }; + }; + + blob { + filename = "blobfile"; + }; + }; +}; diff --git a/tools/binman/test/333_fit_fdt_dir.dts b/tools/binman/test/333_fit_fdt_dir.dts new file mode 100644 index 00000000000..aa778451a4b --- /dev/null +++ b/tools/binman/test/333_fit_fdt_dir.dts @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + u-boot { + }; + fit { + description = "test-desc"; + #address-cells = <1>; + fit,fdt-list-dir = "fdts"; + + images { + kernel { + description = "Vanilla Linux kernel"; + type = "kernel"; + arch = "ppc"; + os = "linux"; + compression = "gzip"; + load = <00000000>; + entry = <00000000>; + hash-1 { + algo = "crc32"; + }; + hash-2 { + algo = "sha1"; + }; + u-boot { + }; + }; + @fdt-SEQ { + description = "fdt-NAME.dtb"; + type = "flat_dt"; + compression = "none"; + hash { + algo = "sha256"; + }; + }; + }; + + configurations { + default = "@config-DEFAULT-SEQ"; + @config-SEQ { + description = "conf-NAME.dtb"; + firmware = "uboot"; + loadables = "atf"; + fdt = "fdt-SEQ"; + }; + }; + }; + u-boot-nodtb { + }; + }; +}; diff --git a/tools/binman/test/334_fit_fdt_compat.dts b/tools/binman/test/334_fit_fdt_compat.dts new file mode 100644 index 00000000000..3bf45c710db --- /dev/null +++ b/tools/binman/test/334_fit_fdt_compat.dts @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + u-boot { + }; + fit { + description = "test-desc"; + #address-cells = <1>; + fit,fdt-list = "of-list"; + + images { + kernel { + description = "Vanilla Linux kernel"; + type = "kernel"; + arch = "ppc"; + os = "linux"; + compression = "gzip"; + load = <00000000>; + entry = <00000000>; + hash-1 { + algo = "crc32"; + }; + hash-2 { + algo = "sha1"; + }; + u-boot { + }; + }; + @fdt-SEQ { + description = "fdt-NAME.dtb"; + type = "flat_dt"; + compression = "none"; + hash { + algo = "sha256"; + }; + }; + }; + + configurations { + default = "@config-DEFAULT-SEQ"; + @config-SEQ { + description = "conf-NAME.dtb"; + firmware = "uboot"; + loadables = "atf"; + fdt = "fdt-SEQ"; + fit,firmware = "vpl"; + fit,compatible; + }; + }; + }; + u-boot-nodtb { + }; + }; +}; diff --git a/tools/binman/test/335_fit_fdt_phase.dts b/tools/binman/test/335_fit_fdt_phase.dts new file mode 100644 index 00000000000..f8d0740a394 --- /dev/null +++ b/tools/binman/test/335_fit_fdt_phase.dts @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + u-boot { + }; + fit { + description = "test-desc"; + #address-cells = <1>; + fit,fdt-list = "of-list"; + + images { + kernel { + description = "Vanilla Linux kernel"; + type = "kernel"; + arch = "ppc"; + os = "linux"; + compression = "gzip"; + load = <00000000>; + entry = <00000000>; + hash-1 { + algo = "crc32"; + }; + hash-2 { + algo = "sha1"; + }; + u-boot { + }; + }; + @fdt-SEQ { + description = "fdt-NAME.dtb"; + type = "flat_dt"; + compression = "none"; + fit,fdt-phase = "tpl"; + hash { + algo = "sha256"; + }; + }; + }; + + configurations { + default = "@config-DEFAULT-SEQ"; + @config-SEQ { + description = "conf-NAME.dtb"; + firmware = "uboot"; + loadables = "atf"; + fdt = "fdt-SEQ"; + fit,firmware = "tpl"; + fit,compatible; + }; + }; + }; + u-boot-nodtb { + }; + }; +}; diff --git a/tools/binman/test/alt_dts/model1.dts b/tools/binman/test/alt_dts/model1.dts new file mode 100644 index 00000000000..01e95e8fabe --- /dev/null +++ b/tools/binman/test/alt_dts/model1.dts @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2024 Google LLC +// Written by Simon Glass <sjg@chromium.org> + +/dts-v1/; + +/ { + model = "Model One"; + compatible = "u-boot,model-one"; + + /* this node remains due to bootph-pre-sram tag */ + node { + some-prop; + prop-to-remove; + another-prop-to-get-rid-of; + not-a-prop-to-remove; + bootph-pre-sram; + + /* this node get removed by fdtgrep */ + other-node { + another-prop; + }; + }; +}; diff --git a/tools/binman/test/alt_dts/model2.dts b/tools/binman/test/alt_dts/model2.dts new file mode 100644 index 00000000000..7829c519772 --- /dev/null +++ b/tools/binman/test/alt_dts/model2.dts @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2024 Google LLC +// Written by Simon Glass <sjg@chromium.org> + +/dts-v1/; + +/ { + model = "Model Two"; + compatible = "u-boot,model-two"; + + /* this node remains due to bootph-pre-sram tag */ + node { + some-prop; + prop-to-remove; + another-prop-to-get-rid-of; + not-a-prop-to-remove; + bootph-pre-sram; + + /* this node get removed by fdtgrep */ + other-node { + another-prop; + }; + }; +}; diff --git a/tools/buildman/control.py b/tools/buildman/control.py index 464835c5be5..d3d027f02ab 100644 --- a/tools/buildman/control.py +++ b/tools/buildman/control.py @@ -124,7 +124,8 @@ def show_actions(series, why_selected, boards_selected, output_dir, print(commit.subject) print() for arg in why_selected: - if arg != 'all': + # When -x is used, only the 'all' member exists + if arg != 'all' or len(why_selected) == 1: print(arg, f': {len(why_selected[arg])} boards') if verbose: print(f" {' '.join(why_selected[arg])}") diff --git a/tools/patman/setup.py b/tools/patman/setup.py index 2ff791da0f7..bcaad69a1c2 100644 --- a/tools/patman/setup.py +++ b/tools/patman/setup.py @@ -3,7 +3,6 @@ from setuptools import setup setup(name='patman', version='1.0', - license='GPL-2.0+', scripts=['patman'], packages=['patman'], package_dir={'patman': ''}, diff --git a/tools/qconfig.py b/tools/qconfig.py index 04118d942da..7b868c7d72c 100755 --- a/tools/qconfig.py +++ b/tools/qconfig.py @@ -1,13 +1,12 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0+ -# -# Author: Masahiro Yamada <yamada.masahiro@socionext.com> -# -""" -Build and query a Kconfig database for boards. +"""Build and query a Kconfig database for boards. + +See doc/develop/qconfig.rst for documentation. -See doc/develop/moveconfig.rst for documentation. +Author: Masahiro Yamada <yamada.masahiro@socionext.com> +Author: Simon Glass <sjg@chromium.org> """ from argparse import ArgumentParser @@ -29,7 +28,6 @@ import threading import time import unittest -import asteval from buildman import bsettings from buildman import kconfiglib from buildman import toolchain @@ -216,43 +214,29 @@ def read_file(fname, as_lines=True, skip_unicode=False): print(f"Failed on file '{fname}: {exc}") return None -def try_expand(line): - """If value looks like an expression, try expanding it - Otherwise just return the existing value - """ - if line.find('=') == -1: - return line - - try: - aeval = asteval.Interpreter( usersyms=SIZES, minimal=True ) - cfg, val = re.split("=", line) - val= val.strip('\"') - if re.search(r'[*+-/]|<<|SZ_+|\(([^\)]+)\)', val): - newval = hex(aeval(val)) - print(f'\tExpanded expression {val} to {newval}') - return cfg+'='+newval - except: - print(f'\tFailed to expand expression in {line}') - - return line - ### classes ### class Progress: - """Progress Indicator""" def __init__(self, col, total): """Create a new progress indicator. Args: - color_enabled (bool): True for colour output + col (terminal.Color): Colour-output class total (int): A number of defconfig files to process. + + current (int): Number of boards processed so far + failed (int): Number of failed boards + failure_msg (str): Message indicating number of failures, '' if none """ self.col = col + self.total = total + self.current = 0 self.good = 0 - self.total = total + self.failed = None + self.failure_msg = None def inc(self, success): """Increment the number of processed defconfig files. @@ -274,22 +258,28 @@ class Progress: print(f'{line} \r', end='') sys.stdout.flush() + def completed(self): + """Set up extra properties when completed""" + self.failed = self.total - self.good + self.failure_msg = f'{self.failed} failed, ' if self.failed else '' -class KconfigScanner: - """Kconfig scanner.""" - def __init__(self): - """Scan all the Kconfig files and create a Config object.""" - # Define environment variables referenced from Kconfig - os.environ['srctree'] = os.getcwd() - os.environ['UBOOTVERSION'] = 'dummy' - os.environ['KCONFIG_OBJDIR'] = '' - os.environ['CC'] = 'gcc' - self.conf = kconfiglib.Kconfig() +def scan_kconfig(): + """Scan all the Kconfig files and create a Config object + Returns: + Kconfig object + """ + # Define environment variables referenced from Kconfig + os.environ['srctree'] = os.getcwd() + os.environ['UBOOTVERSION'] = 'dummy' + os.environ['KCONFIG_OBJDIR'] = '' + os.environ['CC'] = 'gcc' + return kconfiglib.Kconfig() -class KconfigParser: +# pylint: disable=R0903 +class KconfigParser: """A parser of .config and include/autoconf.mk.""" re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"') @@ -374,7 +364,7 @@ class Slot: """ def __init__(self, toolchains, args, progress, devnull, make_cmd, - reference_src_dir, db_queue, col): + reference_src_dir, db_queue): """Create a new process slot. Args: @@ -396,7 +386,7 @@ class Slot: self.make_cmd = (make_cmd, 'O=' + self.build_dir) self.reference_src_dir = reference_src_dir self.db_queue = db_queue - self.col = col + self.col = progress.col self.parser = KconfigParser(args, self.build_dir) self.state = STATE_IDLE self.failed_boards = set() @@ -501,6 +491,7 @@ class Slot: cmd = list(self.make_cmd) cmd.append(self.defconfig) + # pylint: disable=R1732 self.proc = subprocess.Popen(cmd, stdout=self.devnull, stderr=subprocess.PIPE, cwd=self.current_src_dir) @@ -523,6 +514,7 @@ class Slot: cmd = list(self.make_cmd) cmd.append('KCONFIG_IGNORE_DUPLICATES=1') cmd.append(AUTO_CONF_PATH) + # pylint: disable=R1732 self.proc = subprocess.Popen(cmd, stdout=self.devnull, env=env, stderr=subprocess.PIPE, cwd=self.current_src_dir) @@ -546,6 +538,7 @@ class Slot: cmd = list(self.make_cmd) cmd.append('savedefconfig') + # pylint: disable=R1732 self.proc = subprocess.Popen(cmd, stdout=self.devnull, stderr=subprocess.PIPE) self.state = STATE_SAVEDEFCONFIG @@ -601,11 +594,9 @@ class Slot: return self.failed_boards class Slots: - """Controller of the array of subprocess slots.""" - def __init__(self, toolchains, args, progress, reference_src_dir, db_queue, - col): + def __init__(self, toolchains, args, progress, reference_src_dir, db_queue): """Create a new slots controller. Args: @@ -615,17 +606,16 @@ class Slots: reference_src_dir (str): Determine the true starting config state from this source tree (None for none) db_queue (Queue): output queue to write config info for the database - col (terminal.Color): Colour object """ self.args = args self.slots = [] self.progress = progress - self.col = col + self.col = progress.col devnull = subprocess.DEVNULL make_cmd = get_make_cmd() for _ in range(args.jobs): self.slots.append(Slot(toolchains, args, progress, devnull, - make_cmd, reference_src_dir, db_queue, col)) + make_cmd, reference_src_dir, db_queue)) def add(self, defconfig): """Add a new subprocess if a vacant slot is found. @@ -711,18 +701,35 @@ class ReferenceSource: return self.src_dir -def move_config(toolchains, args, db_queue, col): +def move_config(args): """Build database or sync config options to defconfig files. Args: - toolchains (Toolchains): Toolchains to use args (Namespace): Program arguments - db_queue (Queue): Queue for database updates - col (terminal.Color): Colour object Returns: - Progress: Progress indicator + tuple: + config_db (dict of configs for each defconfig): + key: defconfig name, e.g. "MPC8548CDS_legacy_defconfig" + value: dict: + key: CONFIG option + value: Value of option + Progress: Progress indicator """ + config_db = {} + db_queue = queue.Queue() + dbt = DatabaseThread(config_db, db_queue) + dbt.daemon = True + dbt.start() + + check_clean_directory() + bsettings.setup('') + + # Get toolchains to use + toolchains = toolchain.Toolchains() + toolchains.GetSettings() + toolchains.Scan(verbose=False) + if args.git_ref: reference_src = ReferenceSource(args.git_ref) reference_src_dir = reference_src.get_dir() @@ -734,8 +741,10 @@ def move_config(toolchains, args, db_queue, col): else: defconfigs = get_all_defconfigs() + col = terminal.Color(terminal.COLOR_NEVER if args.nocolour + else terminal.COLOR_IF_TERMINAL) progress = Progress(col, len(defconfigs)) - slots = Slots(toolchains, args, progress, reference_src_dir, db_queue, col) + slots = Slots(toolchains, args, progress, reference_src_dir, db_queue) # Main loop to process defconfig files: # Add a new subprocess into a vacant slot. @@ -751,7 +760,9 @@ def move_config(toolchains, args, db_queue, col): time.sleep(SLEEP_TIME) slots.write_failed_boards() - return progress + db_queue.join() + progress.completed() + return config_db, progress def find_kconfig_rules(kconf, config, imply_config): """Check whether a config has a 'select' or 'imply' keyword @@ -772,7 +783,7 @@ def find_kconfig_rules(kconf, config, imply_config): return sym return None -def check_imply_rule(kconf, config, imply_config): +def check_imply_rule(kconf, imply_config): """Check if we can add an 'imply' option This finds imply_config in the Kconfig and looks to see if it is possible @@ -780,7 +791,6 @@ def check_imply_rule(kconf, config, imply_config): Args: kconf (Kconfiglib.Kconfig): Kconfig object - config (str): Name of config to check (without CONFIG_ prefix) imply_config (str): Implying config (without CONFIG_ prefix) which may or may not have an 'imply' for 'config') @@ -873,6 +883,7 @@ def read_database(): all_defconfigs = set() defconfig_db = collections.defaultdict(set) + defconfig = None for line in read_file(CONFIG_DATABASE): line = line.rstrip() if not line: # Separator between defconfigs @@ -914,15 +925,15 @@ def do_imply_config(config_list, add_imply, imply_flags, skip_added, - If imply_defconfigs contains anything not in defconfigs then this config does not imply the target config - Params: - config_list: List of CONFIG options to check (each a string) - add_imply: Automatically add an 'imply' for each config. - imply_flags: Flags which control which implying configs are allowed + Args: + config_list (list of str): List of CONFIG options to check + add_imply (bool): Automatically add an 'imply' for each config. + imply_flags (int): Flags which control which implying configs are allowed (IMPLY_...) - skip_added: Don't show options which already have an imply added. - check_kconfig: Check if implied symbols already have an 'imply' or + skip_added (bool): Don't show options which already have an imply added. + check_kconfig (bool): Check if implied symbols already have an 'imply' or 'select' for the target config, and show this information if so. - find_superset: True to look for configs which are a superset of those + find_superset (bool): True to look for configs which are a superset of those already found. So for example if CONFIG_EXYNOS5 implies an option, but CONFIG_EXYNOS covers a larger set of defconfigs and also implies that option, this will drop the former in favour of the @@ -932,7 +943,7 @@ def do_imply_config(config_list, add_imply, imply_flags, skip_added, config - a CONFIG_XXX options (a string, e.g. 'CONFIG_CMD_EEPROM') defconfig - a defconfig file (a string, e.g. 'configs/snow_defconfig') """ - kconf = KconfigScanner().conf if check_kconfig else None + kconf = scan_kconfig() if check_kconfig else None if add_imply and add_imply != 'all': add_imply = add_imply.split(',') @@ -1051,13 +1062,13 @@ def do_imply_config(config_list, add_imply, imply_flags, skip_added, if add_imply and (add_imply == 'all' or iconfig in add_imply): fname, linenum, kconfig_info = (check_imply_rule(kconf, - config[CONFIG_LEN:], iconfig[CONFIG_LEN:])) + iconfig[CONFIG_LEN:])) if fname: add_list[fname].append(linenum) if show and kconfig_info != 'skip': - print(f'{num_common:5d} : ' - f'{iconfig.ljust(30):-30s}{kconfig_info:-25s} {missing_str}') + print(f'{num_common:5} : ' + f'{iconfig.ljust(30)}{kconfig_info.ljust(25)} {missing_str}') # Having collected a list of things to add, now we add them. We process # each file from the largest line number to the smallest so that @@ -1068,7 +1079,7 @@ def do_imply_config(config_list, add_imply, imply_flags, skip_added, for linenum in sorted(linenums, reverse=True): add_imply_rule(config[CONFIG_LEN:], fname, linenum) -def defconfig_matches(configs, re_match): +def defconfig_matches(configs, re_match, re_val): """Check if any CONFIG option matches a regex The match must be complete, i.e. from the start to end of the CONFIG option. @@ -1078,23 +1089,30 @@ def defconfig_matches(configs, re_match): key: CONFIG option value: Value of option re_match (re.Pattern): Match to check + re_val (re.Pattern): Regular expression to check against value (or None) Returns: bool: True if any CONFIG matches the regex """ - for cfg in configs: + for cfg, val in configs.items(): if re_match.fullmatch(cfg): - return True + if not re_val or re_val.fullmatch(val): + return True return False -def do_find_config(config_list): +def do_find_config(config_list, list_format): """Find boards with a given combination of CONFIGs - Params: - config_list: List of CONFIG options to check (each a regex consisting - of a config option, with or without a CONFIG_ prefix. If an option - is preceded by a tilde (~) then it must be false, otherwise it must - be true) + Args: + config_list (list of str): List of CONFIG options to check (each a regex + consisting of a config option, with or without a CONFIG_ prefix. If + an option is preceded by a tilde (~) then it must be false, + otherwise it must be true) + list_format (bool): True to write in 'list' format, one board name per + line + + Returns: + int: exit code (0 for success) """ _, all_defconfigs, config_db, _ = read_database() @@ -1109,6 +1127,11 @@ def do_find_config(config_list): if cfg[0] == '~': want = False cfg = cfg[1:] + val = None + re_val = None + if '=' in cfg: + cfg, val = cfg.split('=', maxsplit=1) + re_val = re.compile(val) # Search everything that is still in the running. If it has a config # that we want, or doesn't have one that we don't, add it into the @@ -1117,11 +1140,14 @@ def do_find_config(config_list): out = set() re_match = re.compile(cfg) for defc in in_list: - has_cfg = defconfig_matches(config_db[defc], re_match) + has_cfg = defconfig_matches(config_db[defc], re_match, re_val) if has_cfg == want: out.add(defc) - print(f'{len(out)} matches') - print(' '.join(item.split('_defconfig')[0] for item in out)) + if not list_format: + print(f'{len(out)} matches') + sep = '\n' if list_format else ' ' + print(sep.join(item.split('_defconfig')[0] for item in sorted(list(out)))) + return 0 def prefix_config(cfg): @@ -1155,7 +1181,16 @@ RE_C_CONFIGS = re.compile(r'CONFIG_([A-Za-z0-9_]*)') RE_CONFIG_IS = re.compile(r'CONFIG_IS_ENABLED\(([A-Za-z0-9_]*)\)') class ConfigUse: + """Tracks whether a config relates to SPL or not""" def __init__(self, cfg, is_spl, fname, rest): + """Set up a new ConfigUse + + Args: + cfg (str): CONFIG option, without any CONFIG_ or SPL_ prefix + is_spl (bool): True if this option relates to SPL + fname (str): Makefile filename where the CONFIG option was found + rest (str): Line of the Makefile + """ self.cfg = cfg self.is_spl = is_spl self.fname = fname @@ -1362,7 +1397,7 @@ def do_scan_source(path, do_update): print('Scanning Kconfig') - kconf = KconfigScanner().conf + kconf = scan_kconfig() print(f'Scanning source in {path}') args = ['git', 'grep', '-E', r'IS_ENABLED|\bCONFIG'] with subprocess.Popen(args, stdout=subprocess.PIPE) as proc: @@ -1460,9 +1495,17 @@ def do_scan_source(path, do_update): file=out) for item in sorted(proper_not_found): print(item, file=out) + return 0 -def main(): +def parse_args(): + """Parse the program arguments + + Returns: + tuple: + argparse.ArgumentParser: parser + argparse.Namespace: Parsed arguments + """ try: cpu_count = multiprocessing.cpu_count() except NotImplementedError: @@ -1496,6 +1539,8 @@ doc/develop/moveconfig.rst for documentation.''' help='Find boards with a given config combination') parser.add_argument('-i', '--imply', action='store_true', default=False, help='find options which imply others') + parser.add_argument('-l', '--list', action='store_true', default=False, + help='Show a sorted list of board names, one per line') parser.add_argument('-I', '--imply-flags', type=str, default='', help="control the -i option ('help' for help") parser.add_argument('-j', '--jobs', type=int, default=cpu_count, @@ -1521,103 +1566,144 @@ doc/develop/moveconfig.rst for documentation.''' parser.add_argument('configs', nargs='*') args = parser.parse_args() + if not any((args.force_sync, args.build_db, args.imply, args.find, + args.scan_source, args.test)): + parser.print_usage() + sys.exit(1) - if args.test: - sys.argv = [sys.argv[0]] - fail, _ = doctest.testmod() - if fail: - return 1 - unittest.main() + return parser, args - col = terminal.Color(terminal.COLOR_NEVER if args.nocolour - else terminal.COLOR_IF_TERMINAL) - if args.scan_source: - do_scan_source(os.getcwd(), args.update) - return 0 +def imply(args): + """Handle checking for flags which imply others - if not any((args.force_sync, args.build_db, args.imply, args.find)): - parser.print_usage() - sys.exit(1) + Args: + args (argparse.Namespace): Program arguments + + Returns: + int: exit code (0 for success) + """ + imply_flags = 0 + if args.imply_flags == 'all': + imply_flags = -1 + + elif args.imply_flags: + for flag in args.imply_flags.split(','): + bad = flag not in IMPLY_FLAGS + if bad: + print(f"Invalid flag '{flag}'") + if flag == 'help' or bad: + print("Imply flags: (separate with ',')") + for name, info in IMPLY_FLAGS.items(): + print(f' {name:-15s}: {info[1]}') + return 1 + imply_flags |= IMPLY_FLAGS[flag][0] + + do_imply_config(args.configs, args.add_imply, imply_flags, args.skip_added) + return 0 + + +def add_commit(configs): + """Add a commit indicating which CONFIG options were converted + + Args: + configs (list of str) List of CONFIG_... options to process + """ + subprocess.call(['git', 'add', '-u']) + if configs: + part = 'et al ' if len(configs) > 1 else '' + msg = f'Convert {configs[0]} {part}to Kconfig' + msg += ('\n\nThis converts the following to Kconfig:\n %s\n' % + '\n '.join(configs)) + else: + msg = 'configs: Resync with savedefconfig' + msg += '\n\nRsync all defconfig files using moveconfig.py' + subprocess.call(['git', 'commit', '-s', '-m', msg]) + + +def write_db(config_db, progress): + """Write the database to a file + + Args: + config_db (dict of dict): configs for each defconfig + key: defconfig name, e.g. "MPC8548CDS_legacy_defconfig" + value: dict: + key: CONFIG option + value: Value of option + progress (Progress): Progress indicator. + + Returns: + int: exit code (0 for success) + """ + col = progress.col + with open(CONFIG_DATABASE, 'w', encoding='utf-8') as outf: + for defconfig, configs in config_db.items(): + outf.write(f'{defconfig}\n') + for config in sorted(configs.keys()): + outf.write(f' {config}={configs[config]}\n') + outf.write('\n') + print(col.build( + col.RED if progress.failed else col.GREEN, + f'{progress.failure_msg}{len(config_db)} boards written to {CONFIG_DATABASE}')) + return 0 + + +def move_done(progress): + """Write a message indicating that the move is done + + Args: + progress (Progress): Progress indicator. + + Returns: + int: exit code (0 for success) + """ + col = progress.col + if progress.failed: + print(col.build(col.RED, f'{progress.failure_msg}see {FAILED_LIST}', True)) + else: + # Add enough spaces to overwrite the progress indicator + print(col.build( + col.GREEN, f'{progress.total} processed ', bright=True)) + return 0 + +def do_tests(): + """Run doctests and unit tests (so far there are no unit tests)""" + sys.argv = [sys.argv[0]] + fail, _ = doctest.testmod() + if fail: + return 1 + unittest.main() + return 0 - # prefix the option name with CONFIG_ if missing - configs = [prefix_config(cfg) for cfg in args.configs] +def main(): + """Main program""" + parser, args = parse_args() check_top_directory() + # prefix the option name with CONFIG_ if missing + args.configs = [prefix_config(cfg) for cfg in args.configs] + + if args.test: + return do_tests() + if args.scan_source: + return do_scan_source(os.getcwd(), args.update) if args.imply: - imply_flags = 0 - if args.imply_flags == 'all': - imply_flags = -1 - - elif args.imply_flags: - for flag in args.imply_flags.split(','): - bad = flag not in IMPLY_FLAGS - if bad: - print(f"Invalid flag '{flag}'") - if flag == 'help' or bad: - print("Imply flags: (separate with ',')") - for name, info in IMPLY_FLAGS.items(): - print(f' {name:-15s}: {info[1]}') - parser.print_usage() - sys.exit(1) - imply_flags |= IMPLY_FLAGS[flag][0] - - do_imply_config(configs, args.add_imply, imply_flags, args.skip_added) + if imply(args): + parser.print_usage() + sys.exit(1) return 0 - if args.find: - do_find_config(configs) - return 0 + return do_find_config(args.configs, args.list) - # We are either building the database or forcing a sync of defconfigs - config_db = {} - db_queue = queue.Queue() - dbt = DatabaseThread(config_db, db_queue) - dbt.daemon = True - dbt.start() - - check_clean_directory() - bsettings.setup('') - toolchains = toolchain.Toolchains() - toolchains.GetSettings() - toolchains.Scan(verbose=False) - progress = move_config(toolchains, args, db_queue, col) - db_queue.join() + config_db, progress = move_config(args) if args.commit: - subprocess.call(['git', 'add', '-u']) - if configs: - msg = 'Convert %s %sto Kconfig' % (configs[0], - 'et al ' if len(configs) > 1 else '') - msg += ('\n\nThis converts the following to Kconfig:\n %s\n' % - '\n '.join(configs)) - else: - msg = 'configs: Resync with savedefconfig' - msg += '\n\nRsync all defconfig files using moveconfig.py' - subprocess.call(['git', 'commit', '-s', '-m', msg]) + add_commit(args.configs) - failed = progress.total - progress.good - failure = f'{failed} failed, ' if failed else '' if args.build_db: - with open(CONFIG_DATABASE, 'w', encoding='utf-8') as outf: - for defconfig, configs in config_db.items(): - outf.write(f'{defconfig}\n') - for config in sorted(configs.keys()): - outf.write(f' {config}={configs[config]}\n') - outf.write('\n') - print(col.build( - col.RED if failed else col.GREEN, - f'{failure}{len(config_db)} boards written to {CONFIG_DATABASE}')) - else: - if failed: - print(col.build(col.RED, f'{failure}see {FAILED_LIST}', True)) - else: - # Add enough spaces to overwrite the progress indicator - print(col.build( - col.GREEN, f'{progress.total} processed ', bright=True)) - - return 0 + return write_db(config_db, progress) + return move_done(progress) if __name__ == '__main__': |