From 4c8f76ccf3d15d153893962196d1ff548accddc2 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Sun, 20 Feb 2022 13:47:13 +0100 Subject: PBL: fdt: factor reg property parsing into helper Instead of duplicating the loop for each of base and size, move it into a helper function. This may come in handy later when extending the function, e.g. to have the generic-dt-2nd image take /reserved-memory entries into account and not rely on CONFIG_OPTEE_SIZE. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20220220124736.3052502-2-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- pbl/fdt.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/pbl/fdt.c b/pbl/fdt.c index 7a913c546a..51719698f2 100644 --- a/pbl/fdt.c +++ b/pbl/fdt.c @@ -3,12 +3,24 @@ #include #include +static const __be32 *fdt_parse_reg(const __be32 *reg, uint32_t n, + uint64_t *val) +{ + int i; + + *val = 0; + for (i = 0; i < n; i++) + *val = (*val << 32) | fdt32_to_cpu(*reg++); + + return reg; +} + void fdt_find_mem(const void *fdt, unsigned long *membase, unsigned long *memsize) { const __be32 *nap, *nsp, *reg; uint32_t na, ns; uint64_t memsize64, membase64; - int node, size, i; + int node, size; /* Make sure FDT blob is sane */ if (fdt_check_header(fdt) != 0) { @@ -51,14 +63,9 @@ void fdt_find_mem(const void *fdt, unsigned long *membase, unsigned long *memsiz goto err; } - membase64 = 0; - for (i = 0; i < na; i++) - membase64 = (membase64 << 32) | fdt32_to_cpu(*reg++); - /* get the memsize and truncate it to under 4G on 32 bit machines */ - memsize64 = 0; - for (i = 0; i < ns; i++) - memsize64 = (memsize64 << 32) | fdt32_to_cpu(*reg++); + reg = fdt_parse_reg(reg, na, &membase64); + reg = fdt_parse_reg(reg, ns, &memsize64); *membase = membase64; *memsize = memsize64; -- cgit v1.2.3 From 817e02dc6fcbd6b3a27fd0b76fc920192c11eea7 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Sun, 20 Feb 2022 13:47:14 +0100 Subject: pinctrl: stm32: use gpio-ranges instead of alias Upstream device tree doesn't feature aliases and we don't really need it, as the gpio-ranges property has a GPIO controller offset cell. Use it instead. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20220220124736.3052502-3-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- arch/arm/dts/stm32mp151.dtsi | 12 ------------ drivers/pinctrl/pinctrl-stm32.c | 14 ++------------ 2 files changed, 2 insertions(+), 24 deletions(-) diff --git a/arch/arm/dts/stm32mp151.dtsi b/arch/arm/dts/stm32mp151.dtsi index f1fd888fa1..aac2907bc6 100644 --- a/arch/arm/dts/stm32mp151.dtsi +++ b/arch/arm/dts/stm32mp151.dtsi @@ -1,18 +1,6 @@ / { aliases { - gpio0 = &gpioa; - gpio1 = &gpiob; - gpio2 = &gpioc; - gpio3 = &gpiod; - gpio4 = &gpioe; - gpio5 = &gpiof; - gpio6 = &gpiog; - gpio7 = &gpioh; - gpio8 = &gpioi; - gpio9 = &gpioj; - gpio10 = &gpiok; - gpio25 = &gpioz; mmc0 = &sdmmc1; mmc1 = &sdmmc2; mmc2 = &sdmmc3; diff --git a/drivers/pinctrl/pinctrl-stm32.c b/drivers/pinctrl/pinctrl-stm32.c index ceaa4254c4..cee10636ce 100644 --- a/drivers/pinctrl/pinctrl-stm32.c +++ b/drivers/pinctrl/pinctrl-stm32.c @@ -303,7 +303,7 @@ static int stm32_gpiochip_add(struct stm32_gpio_bank *bank, enum { PINCTRL_PHANDLE, GPIOCTRL_OFFSET, PINCTRL_OFFSET, PINCOUNT, GPIO_RANGE_NCELLS }; const __be32 *gpio_ranges; u32 ngpios; - int id, ret, size; + int ret, size; dev = of_platform_device_create(np, parent); if (!dev) @@ -347,17 +347,7 @@ static int stm32_gpiochip_add(struct stm32_gpio_bank *bank, bank->base = IOMEM(iores->start); - if (dev->id >= 0) { - id = dev->id; - } else { - id = of_alias_get_id(np, "gpio"); - if (id < 0) { - dev_err(dev, "Failed to get GPIO alias\n"); - return id; - } - } - - bank->chip.base = id * STM32_GPIO_PINS_PER_BANK; + bank->chip.base = be32_to_cpu(gpio_ranges[PINCTRL_OFFSET]); bank->chip.ops = &stm32_gpio_ops; bank->chip.dev = dev; bank->clk = clk_get(dev, NULL); -- cgit v1.2.3 From f4d7d6589dc68df4f851d3b652c0ad75bd253799 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Sun, 20 Feb 2022 13:47:15 +0100 Subject: ARM: stm32mp: simplify with build_stm32mp_image macro Reduce duplication by adding a common macro to get the four lines of boilerplate down to a single line. No functional change. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20220220124736.3052502-4-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- images/Makefile.stm32mp | 49 ++++++++++++++++++++----------------------------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/images/Makefile.stm32mp b/images/Makefile.stm32mp index 17f03908b0..558f8c5859 100644 --- a/images/Makefile.stm32mp +++ b/images/Makefile.stm32mp @@ -9,35 +9,26 @@ $(obj)/%.stm32: $(obj)/% FORCE $(call if_changed,stm32_image) -STM32MP1_OPTS = -a 0xc0100000 -e 0xc0100000 -v1 +define build_stm32mp_image = +$(eval +ifeq ($($(strip $(1))), y) + pblb-y += $(strip $(2)) + FILE_barebox-$(strip $(3)).img = $(strip $(2)).pblb.stm32 + OPTS_$(strip $(2)).pblb.stm32 = -a 0xc0100000 -e 0xc0100000 -v1 + image-y += barebox-$(strip $(3)).img +endif +) +endef # -------------------------------------- -pblb-$(CONFIG_MACH_STM32MP15XX_DKX) += start_stm32mp15xx_dkx -FILE_barebox-stm32mp15xx-dkx.img = start_stm32mp15xx_dkx.pblb.stm32 -OPTS_start_stm32mp15xx_dkx.pblb.stm32 = $(STM32MP1_OPTS) -image-$(CONFIG_MACH_STM32MP15XX_DKX) += barebox-stm32mp15xx-dkx.img - -pblb-$(CONFIG_MACH_LXA_MC1) += start_stm32mp157c_lxa_mc1 -FILE_barebox-stm32mp157c-lxa-mc1.img = start_stm32mp157c_lxa_mc1.pblb.stm32 -OPTS_start_stm32mp157c_lxa_mc1.pblb.stm32 = $(STM32MP1_OPTS) -image-$(CONFIG_MACH_LXA_MC1) += barebox-stm32mp157c-lxa-mc1.img - -pblb-$(CONFIG_MACH_PROTONIC_STM32MP1) += start_prtt1a start_prtt1s start_prtt1c -FILE_barebox-prtt1a.img = start_prtt1a.pblb.stm32 -FILE_barebox-prtt1c.img = start_prtt1c.pblb.stm32 -FILE_barebox-prtt1s.img = start_prtt1s.pblb.stm32 -OPTS_start_prtt1a.pblb.stm32 = $(STM32MP1_OPTS) -OPTS_start_prtt1c.pblb.stm32 = $(STM32MP1_OPTS) -OPTS_start_prtt1s.pblb.stm32 = $(STM32MP1_OPTS) -image-$(CONFIG_MACH_PROTONIC_STM32MP1) += barebox-prtt1a.img barebox-prtt1s.img barebox-prtt1c.img - -pblb-$(CONFIG_MACH_SEEED_ODYSSEY) += start_stm32mp157c_seeed_odyssey -FILE_barebox-stm32mp157c-seeed-odyssey.img = start_stm32mp157c_seeed_odyssey.pblb.stm32 -OPTS_start_stm32mp157c_seeed_odyssey.pblb.stm32 = $(STM32MP1_OPTS) -image-$(CONFIG_MACH_SEEED_ODYSSEY) += barebox-stm32mp157c-seeed-odyssey.img - -pblb-$(CONFIG_MACH_STM32MP15X_EV1) += start_stm32mp15x_ev1 -FILE_barebox-stm32mp15x-ev1.img = start_stm32mp15x_ev1.pblb.stm32 -OPTS_start_stm32mp15x_ev1.pblb.stm32 = $(STM32MP1_OPTS) -image-$(CONFIG_MACH_STM32MP15X_EV1) += barebox-stm32mp15x-ev1.img +$(call build_stm32mp_image, CONFIG_MACH_STM32MP15XX_DKX, start_stm32mp15xx_dkx, stm32mp15xx-dkx) +$(call build_stm32mp_image, CONFIG_MACH_STM32MP15X_EV1, start_stm32mp15x_ev1, stm32mp15x-ev1) + +$(call build_stm32mp_image, CONFIG_MACH_LXA_MC1, start_stm32mp157c_lxa_mc1, stm32mp157c-lxa-mc1) + +$(call build_stm32mp_image, CONFIG_MACH_PROTONIC_STM32MP1, start_prtt1a, prtt1a) +$(call build_stm32mp_image, CONFIG_MACH_PROTONIC_STM32MP1, start_prtt1s, prtt1s) +$(call build_stm32mp_image, CONFIG_MACH_PROTONIC_STM32MP1, start_prtt1c, prtt1c) + +$(call build_stm32mp_image, CONFIG_MACH_SEEED_ODYSSEY, start_stm32mp157c_seeed_odyssey, stm32mp157c-seeed-odyssey) -- cgit v1.2.3 From 3536e3904152210d70ae5ef6cf24c6d572cbf04f Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Sun, 20 Feb 2022 13:47:16 +0100 Subject: ARM: stm32mp: change stm32image extension to .stm32 The .img extension for stm32mp1 images is unfortunate. The format is deprecated and its header makes it not directly executable and thus not suitable as-is for use in a FIP image where the BL33 is run from offset 0. To make existence of the STM32 header evident, rename the extension from .img to .stm32. As it's still supported by TF-A, have symlinks, so user build script can use the old names for now. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20220220124736.3052502-5-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- Documentation/boards/stm32mp.rst | 29 +++++++++++++++++------------ arch/arm/mach-stm32mp/Kconfig | 6 +++--- images/Makefile.stm32mp | 11 +++++++---- 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/Documentation/boards/stm32mp.rst b/Documentation/boards/stm32mp.rst index 24b7a19ceb..2b9dc01b55 100644 --- a/Documentation/boards/stm32mp.rst +++ b/Documentation/boards/stm32mp.rst @@ -10,7 +10,7 @@ The first stage boot loader (FSBL) is loaded by the ROM code into the built-in SYSRAM and executed. The FSBL sets up the SDRAM, install a secure monitor and then the second stage boot loader (SSBL) is loaded into DRAM. -When building barebox, the resulting ``barebox-${board}.img`` file has the STM32 +When building barebox, the resulting ``barebox-${board}.stm32`` file has the STM32 header preprended, so it can be loaded directly as SSBL by the ARM TF-A (https://github.com/ARM-software/arm-trusted-firmware). Each entry point has a header-less image ending in ``*.pblb`` as well. @@ -25,18 +25,23 @@ as sole defconfig for all STM32MP boards:: make ARCH=arm stm32mp_defconfig -The resulting images will be placed under ``images/``: +The resulting images will be placed under ``images/``:: -:: + barebox-stm32mp15xx-dkx.stm32 + barebox-stm32mp15x-ev1.stm32 + barebox-stm32mp157c-lxa-mc1.stm32 + barebox-prtt1a.stm32 + barebox-prtt1s.stm32 + barebox-prtt1c.stm32 + barebox-stm32mp157c-seeed-odyssey.stm32 + barebox-dt-2nd.img - barebox-stm32mp15xx-dkx.img # both DK1 and DK2 - barebox-stm32mp157c-lxa-mc1.img - barebox-stm32mp157c-seeed-odyssey.img - barebox-stm32mp15x-ev1.img # stm32mp157c-ev1 and friends +In the above output, images with a ``.stm32`` extension feature the (legacy) +stm32image header. ``barebox-dt-2nd.img`` is a generic barebox image that +received an external device tree. - -Flashing barebox ----------------- +Flashing barebox (legacy stm32image) +------------------------------------ An appropriate image for a SD-Card can be generated with following ``genimage(1)`` config:: @@ -55,7 +60,7 @@ An appropriate image for a SD-Card can be generated with following size = 256K } partition ssbl { - image = "barebox-@STM32MP_BOARD@.img" + image = "barebox-@STM32MP_BOARD@.stm32" size = 1M } partition barebox-environment { @@ -69,7 +74,7 @@ partitions may look like this:: image @STM32MP_BOARD@.img { partition ssbl { - image = "barebox-@STM32MP_BOARD@.img" + image = "barebox-@STM32MP_BOARD@.stm32" size = 1M } partition barebox-environment { diff --git a/arch/arm/mach-stm32mp/Kconfig b/arch/arm/mach-stm32mp/Kconfig index d059dbda56..38c1a44770 100644 --- a/arch/arm/mach-stm32mp/Kconfig +++ b/arch/arm/mach-stm32mp/Kconfig @@ -14,7 +14,7 @@ config MACH_STM32MP15XX_DKX select ARCH_STM32MP157 bool "STM32MP157 DK1 and DK2 boards" help - builds a single barebox-stm32mp15xx-dkx.img that can be deployed + builds a single barebox-stm32mp15xx-dkx.stm32 that can be deployed as SSBL on both the stm32mp157a-dk1 and stm32mp157c-dk2 config MACH_LXA_MC1 @@ -29,7 +29,7 @@ config MACH_STM32MP15X_EV1 select ARCH_STM32MP157 bool "STM32MP15X-EV1 board" help - builds a single barebox-stm32mp15x-ev1.img that can be deployed + builds a single barebox-stm32mp15x-ev1.stm32 that can be deployed as SSBL on any STM32MP15X-EVAL platform, like the STM32MP157C-EV1 @@ -37,7 +37,7 @@ config MACH_PROTONIC_STM32MP1 select ARCH_STM32MP157 bool "Protonic PRTT1L family of boards" help - Builds all barebox-prtt1*.img that can be deployed as SSBL + Builds all barebox-prtt1*.stm32 that can be deployed as SSBL on the respective PRTT1L family board endif diff --git a/images/Makefile.stm32mp b/images/Makefile.stm32mp index 558f8c5859..eeb5d9ecf6 100644 --- a/images/Makefile.stm32mp +++ b/images/Makefile.stm32mp @@ -6,16 +6,19 @@ # %.stm32 - convert into STM32MP image # -------------------------------------- -$(obj)/%.stm32: $(obj)/% FORCE +.SECONDEXPANSION: +$(obj)/%.stm32: $(obj)/$$(FILE_$$(@F)) FORCE + $(Q)if [ -z $(FILE_$(@F)) ]; then echo "FILE_$(@F) empty!"; false; fi + @(cd $(obj) && ln -fs $(notdir $@) $(basename $(notdir $@)).img) $(call if_changed,stm32_image) define build_stm32mp_image = $(eval ifeq ($($(strip $(1))), y) pblb-y += $(strip $(2)) - FILE_barebox-$(strip $(3)).img = $(strip $(2)).pblb.stm32 - OPTS_$(strip $(2)).pblb.stm32 = -a 0xc0100000 -e 0xc0100000 -v1 - image-y += barebox-$(strip $(3)).img + FILE_barebox-$(strip $(3)).stm32 = $(strip $(2)).pblb + OPTS_barebox-$(strip $(3)).stm32 = -a 0xc0100000 -e 0xc0100000 -v1 + image-y += barebox-$(strip $(3)).stm32 endif ) endef -- cgit v1.2.3 From eee339bf0d50d27875cb6987885cc08ff44f436e Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Sun, 20 Feb 2022 13:47:17 +0100 Subject: filetype: detect TF-A Firmware Image Packages (FIP) FIP is the new format for firmware loaded by ARM Trusted Firmware. It can contain non-secure firmware, hardware config (device tree), firmware config (configuration DT) and optionally OP-TEE. In future, we may want to mount FIP to chain boot barebox out of it, but for now, just let barebox filetype detect it correctly. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20220220124736.3052502-6-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- common/filetype.c | 4 ++++ include/filetype.h | 1 + 2 files changed, 5 insertions(+) diff --git a/common/filetype.c b/common/filetype.c index 53517da70d..0ded64b83c 100644 --- a/common/filetype.c +++ b/common/filetype.c @@ -75,6 +75,7 @@ static const struct filetype_str filetype_str[] = { [filetype_zynq_image] = { "Zynq image", "zynq-image" }, [filetype_mxs_sd_image] = { "i.MX23/28 SD card image", "mxs-sd-image" }, [filetype_rockchip_rkns_image] = { "Rockchip boot image", "rk-image" }, + [filetype_fip] = { "TF-A Firmware Image Package", "fip" }, }; const char *file_type_to_string(enum filetype f) @@ -315,6 +316,9 @@ enum filetype file_detect_type(const void *_buf, size_t bufsize) return filetype_riscv_barebox_image; if (strncmp(buf8, "RKNS", 4) == 0) return filetype_rockchip_rkns_image; + if (le32_to_cpu(buf[0]) == le32_to_cpu(0xaa640001)) + return filetype_fip; + if ((buf8[0] == 0x5a || buf8[0] == 0x69 || buf8[0] == 0x78 || buf8[0] == 0x8b || buf8[0] == 0x9c) && buf8[0x1] == 0 && buf8[0x2] == 0 && buf8[0x3] == 0 && diff --git a/include/filetype.h b/include/filetype.h index 8bc179ac08..9b7499fdf3 100644 --- a/include/filetype.h +++ b/include/filetype.h @@ -56,6 +56,7 @@ enum filetype { filetype_zynq_image, filetype_mxs_sd_image, filetype_rockchip_rkns_image, + filetype_fip, filetype_max, }; -- cgit v1.2.3 From eff5c04efa94e581f45f9d5811f3184ddab6ba9c Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Sun, 20 Feb 2022 13:47:18 +0100 Subject: scripts: add tool to adjust bl33 load address in existing FIP Instead of rebuilding TF-A with a (newer) barebox, an existing image can be repacked: fiptool update fip.bin --nt-fw images/barebox-dt-2nd.img \ --hw-config build/arch/arm/dts/stm32mp157c-dk2.dtb This may fail at runtime though, because the STM32MP default is to place BL33 at the very start of RAM. The script introduced here offers an alternative to rebuilding TF-A with adjust FW_CONFIG: ./scripts/fiptool_fwconfig -l 0xc0001000 mmcblk0p3 This will have barebox loaded 4096 bytes into the STM32MP SDRAM. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20220220124736.3052502-7-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- scripts/fiptool_fwconfig | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100755 scripts/fiptool_fwconfig diff --git a/scripts/fiptool_fwconfig b/scripts/fiptool_fwconfig new file mode 100755 index 0000000000..f76f450b04 --- /dev/null +++ b/scripts/fiptool_fwconfig @@ -0,0 +1,39 @@ +#!/bin/sh + +set -e + +# adjust bl33 load address in existing FIP + +FDTGET=${FDTGET:-fdtget} +FDTPUT=${FDTPUT:-fdtput} + +if [ "$1" != "-l" ] || [ "$#" -ne 3 ]; then + echo "USAGE: $0 -l NT_LOAD_ADDR FIP" 1>&2 + exit 1 +fi + +NEW_LOAD_ADDR=$(($2)) +FIP=$3 +FWCONFIG=".$FIP.fw-config.bin" + +fiptool unpack --fw-config "$FWCONFIG" --force "$FIP" + +MAX_SIZE="$($FDTGET -t u $FWCONFIG /dtb-registry/nt_fw max-size)" +set $($FDTGET -t u $FWCONFIG /dtb-registry/nt_fw load-address) +ENTRY=$1 +OLD_LOAD_ADDR=$2 + +if [ $NEW_LOAD_ADDR -lt $OLD_LOAD_ADDR ] || + [ $NEW_LOAD_ADDR -ge $((OLD_LOAD_ADDR + MAX_SIZE)) ]; then + printf "New load address 0x%08x out of bounds [0x%08x-0x%08x)\n" \ + $NEW_LOAD_ADDR $OLD_LOAD_ADDR $((OLD_LOAD_ADDR + MAX_SIZE)) 1>&2 + exit 1 +fi + +$FDTPUT -t u $FWCONFIG /dtb-registry/nt_fw load-address $ENTRY $NEW_LOAD_ADDR +$FDTPUT -t u $FWCONFIG /dtb-registry/nt_fw max-size \ + $((MAX_SIZE + OLD_LOAD_ADDR - NEW_LOAD_ADDR)) + +fiptool update $FIP --fw-config $FWCONFIG + +rm $FWCONFIG -- cgit v1.2.3 From b07c1c88edd3753b8c10ef13b3c6daf78ab88b3f Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Sun, 20 Feb 2022 13:47:19 +0100 Subject: ARM: stm32mp: build extra barebox-stm32mp-generic-bl33.img barebox-dt-2nd.img expects being loaded at an offset, so the stack can grow down from entry point. The STM32MP TF-A default is to not have an offset. Avoid this issue by having a stm32mp specific entry point that sets up a 64 byte stack after end of barebox. As it's stm32mp-specific anyway, we can skip the early FDT parsing and ask the SDRAM controller about RAM size. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20220220124736.3052502-8-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- Documentation/boards/stm32mp.rst | 51 +++++++++++++++++++++++++++++++----- arch/arm/mach-stm32mp/Makefile | 1 + arch/arm/mach-stm32mp/bl33-generic.c | 24 +++++++++++++++++ images/Makefile.stm32mp | 5 ++++ 4 files changed, 74 insertions(+), 7 deletions(-) create mode 100644 arch/arm/mach-stm32mp/bl33-generic.c diff --git a/Documentation/boards/stm32mp.rst b/Documentation/boards/stm32mp.rst index 2b9dc01b55..55bdafe785 100644 --- a/Documentation/boards/stm32mp.rst +++ b/Documentation/boards/stm32mp.rst @@ -13,20 +13,26 @@ then the second stage boot loader (SSBL) is loaded into DRAM. When building barebox, the resulting ``barebox-${board}.stm32`` file has the STM32 header preprended, so it can be loaded directly as SSBL by the ARM TF-A (https://github.com/ARM-software/arm-trusted-firmware). Each entry point has a -header-less image ending in ``*.pblb`` as well. +header-less image ending in ``*.pblb`` as well. Additionally, there is +a ``barebox-stm32mp-generic.img``, which is a header-less image for +use as part of a Firmware Image Package (FIP). -Use of barebox as FSBL is not supported. +barebox images are meant to be loaded by the ARM TF-A +(https://github.com/ARM-software/arm-trusted-firmware). FIP images are +mandatory for STM32MP1 since TF-A v2.7. + +Use of barebox as FSBL is not implemented. Building barebox ---------------- -With multi-image and device trees, it's expected to have ``stm32mp_defconfig`` -as sole defconfig for all STM32MP boards:: +There's a single ``stm32mp_defconfig`` for all STM32MP boards:: make ARCH=arm stm32mp_defconfig The resulting images will be placed under ``images/``:: + barebox-stm32mp-generic-bl33.img barebox-stm32mp15xx-dkx.stm32 barebox-stm32mp15x-ev1.stm32 barebox-stm32mp157c-lxa-mc1.stm32 @@ -37,13 +43,44 @@ The resulting images will be placed under ``images/``:: barebox-dt-2nd.img In the above output, images with a ``.stm32`` extension feature the (legacy) -stm32image header. ``barebox-dt-2nd.img`` is a generic barebox image that -received an external device tree. +stm32image header. ``barebox-dt-2nd.img`` and ``barebox-stm32mp-generic-bl33.img`` +are board-generic barebox images that receive an external device tree. + +Flashing barebox (FIP) +---------------------- + +After building barebox in ``$BAREBOX_BUILDDIR``, change directory to ARM +Trusted Firmware to build a FIP image. Example building STM32MP157C-DK2 +with SP_min (no OP-TEE): + +.. code:: bash + + make CROSS_COMPILE=arm-none-eabi- PLAT=stm32mp1 ARCH=aarch32 ARM_ARCH_MAJOR=7 \ + STM32MP_EMMC=1 STM32MP_EMMC_BOOT=1 STM32MP_SDMMC=1 STM32MP_SPI_NOR=1 \ + AARCH32_SP=sp_min \ + DTB_FILE_NAME=stm32mp157c-dk2.dtb \ + BL33=$BAREBOX_BUILDDIR/images/barebox-stm32mp-generic-bl33.img \ + BL33_CFG=$BAREBOX_BUILDDIR/arch/arm/dts/stm32mp157c-dk2.dtb \ + fip + +For different boards, adjust ``DTB_FILENAME`` and ``BL33_CFG`` as appropriate. + +If OP-TEE is used, ensure ``CONFIG_OPTEE_SIZE`` is set appropriately, so +early barebox code does not attempt accessing secure memory. + +barebox can also be patched into an existing FIP image with ``fiptool``: + +.. code:: bash + + fiptool update mmcblk0p3 \ + --nt-fw $BAREBOX_BUILDDIR/images/barebox-stm32mp-generic-bl33.img \ + --hw-config $BAREBOX_BUILDDIR/arch/arm/dts/stm32mp135f-dk.dtb Flashing barebox (legacy stm32image) ------------------------------------ -An appropriate image for a SD-Card can be generated with following +After building ARM Trusted Firmware with ``STM32MP_USE_STM32IMAGE=1``, +an appropriate image for a SD-Card can be generated with following ``genimage(1)`` config:: image @STM32MP_BOARD@.img { diff --git a/arch/arm/mach-stm32mp/Makefile b/arch/arm/mach-stm32mp/Makefile index 4163bd176b..86c13b8fca 100644 --- a/arch/arm/mach-stm32mp/Makefile +++ b/arch/arm/mach-stm32mp/Makefile @@ -2,4 +2,5 @@ obj-y := init.o obj-pbl-y := ddrctrl.o +pbl-y := bl33-generic.o obj-$(CONFIG_BOOTM) += stm32image.o diff --git a/arch/arm/mach-stm32mp/bl33-generic.c b/arch/arm/mach-stm32mp/bl33-generic.c new file mode 100644 index 0000000000..6f779b19cf --- /dev/null +++ b/arch/arm/mach-stm32mp/bl33-generic.c @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include + +/* + * barebox-dt-2nd.img expects being loaded at an offset, so the + * stack can grow down from entry point. The STM32MP TF-A default + * is to not have an offset. This stm32mp specific entry points + * avoids this issue by setting up a 64 byte stack after end of + * barebox and by asking the memory controller about RAM size + * instead of parsing it out of the DT. + * + * When using OP-TEE, ensure CONFIG_OPTEE_SIZE is appopriately set. + */ + +ENTRY_FUNCTION(start_stm32mp_bl33, r0, r1, r2) +{ + stm32mp_cpu_lowlevel_init(); + + putc_ll('>'); + + stm32mp1_barebox_entry((void *)r2); +} diff --git a/images/Makefile.stm32mp b/images/Makefile.stm32mp index eeb5d9ecf6..fa79e09f95 100644 --- a/images/Makefile.stm32mp +++ b/images/Makefile.stm32mp @@ -25,6 +25,11 @@ endef # -------------------------------------- +# For use as --nt-fw (BL33) in FIP images +pblb-$(CONFIG_ARCH_STM32MP) += start_stm32mp_bl33 +FILE_barebox-stm32mp-generic-bl33.img = start_stm32mp_bl33.pblb +image-$(CONFIG_ARCH_STM32MP) += barebox-stm32mp-generic-bl33.img + $(call build_stm32mp_image, CONFIG_MACH_STM32MP15XX_DKX, start_stm32mp15xx_dkx, stm32mp15xx-dkx) $(call build_stm32mp_image, CONFIG_MACH_STM32MP15X_EV1, start_stm32mp15x_ev1, stm32mp15x-ev1) -- cgit v1.2.3 From 8b7058834d18e796cf8bf5dd3a6111a50986fc39 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Sun, 20 Feb 2022 13:47:20 +0100 Subject: ARM: stm32mp: ddrctrl: fix wrong register field widths Consulting the reference manual shows that column fields are 4 bits each, but some of them were treated as 5-bit wide. Fix it. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20220220124736.3052502-9-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- arch/arm/mach-stm32mp/ddrctrl.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/arch/arm/mach-stm32mp/ddrctrl.c b/arch/arm/mach-stm32mp/ddrctrl.c index 7f2013c22d..93996d0afc 100644 --- a/arch/arm/mach-stm32mp/ddrctrl.c +++ b/arch/arm/mach-stm32mp/ddrctrl.c @@ -24,12 +24,12 @@ #define ADDRMAP2_COL_B5 GENMASK(27, 24) #define ADDRMAP3_COL_B6 GENMASK( 3, 0) -#define ADDRMAP3_COL_B7 GENMASK(12, 8) -#define ADDRMAP3_COL_B8 GENMASK(20, 16) -#define ADDRMAP3_COL_B9 GENMASK(28, 24) +#define ADDRMAP3_COL_B7 GENMASK(11, 8) +#define ADDRMAP3_COL_B8 GENMASK(19, 16) +#define ADDRMAP3_COL_B9 GENMASK(27, 24) -#define ADDRMAP4_COL_B10 GENMASK( 4, 0) -#define ADDRMAP4_COL_B11 GENMASK(12, 8) +#define ADDRMAP4_COL_B10 GENMASK( 3, 0) +#define ADDRMAP4_COL_B11 GENMASK(11, 8) #define ADDRMAP5_ROW_B0 GENMASK( 3, 0) #define ADDRMAP5_ROW_B1 GENMASK(11, 8) -- cgit v1.2.3 From 41a1b37ec18f321e5d1b9e431c0301f9b654af4d Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Sun, 20 Feb 2022 13:47:21 +0100 Subject: reset: stm32: drop stm32mp1_reset_ops indirection The driver used to support both STM32 MCUs and the STM32MP1. STM32 MCU support is now handled by the reset-simple driver, so the indirection to support both is no longer necessary. Remove it and simplify the code. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20220220124736.3052502-10-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- drivers/reset/reset-stm32.c | 97 +++++++++++++++++---------------------------- 1 file changed, 37 insertions(+), 60 deletions(-) diff --git a/drivers/reset/reset-stm32.c b/drivers/reset/reset-stm32.c index 186b2a8bc6..e625ba27ff 100644 --- a/drivers/reset/reset-stm32.c +++ b/drivers/reset/reset-stm32.c @@ -44,13 +44,6 @@ struct stm32_reset { void __iomem *base; struct reset_controller_dev rcdev; struct restart_handler restart; - const struct stm32_reset_ops *ops; -}; - -struct stm32_reset_ops { - void (*reset)(void __iomem *reg, unsigned offset, bool assert); - void __noreturn (*sys_reset)(struct restart_handler *rst); - const struct stm32_reset_reason *reset_reasons; }; static struct stm32_reset *to_stm32_reset(struct reset_controller_dev *rcdev) @@ -58,14 +51,6 @@ static struct stm32_reset *to_stm32_reset(struct reset_controller_dev *rcdev) return container_of(rcdev, struct stm32_reset, rcdev); } -static void stm32mp_reset(void __iomem *reg, unsigned offset, bool assert) -{ - if (!assert) - reg += RCC_CL; - - writel(BIT(offset), reg); -} - static u32 stm32_reset_status(struct stm32_reset *priv, unsigned long bank) { return readl(priv->base + bank); @@ -75,10 +60,38 @@ static void stm32_reset(struct stm32_reset *priv, unsigned long id, bool assert) { int bank = (id / 32) * 4; int offset = id % 32; + void __iomem *reg = priv->base + bank; - priv->ops->reset(priv->base + bank, offset, assert); + if (!assert) + reg += RCC_CL; + + writel(BIT(offset), reg); } +static void __noreturn stm32mp_rcc_restart_handler(struct restart_handler *rst) +{ + struct stm32_reset *priv = container_of(rst, struct stm32_reset, restart); + + stm32_reset(priv, RCC_MP_GRSTCSETR * BITS_PER_BYTE, true); + + mdelay(1000); + hang(); +} + +static const struct stm32_reset_reason stm32mp_reset_reasons[] = { + { STM32MP_RCC_RSTF_POR, RESET_POR, 0 }, + { STM32MP_RCC_RSTF_BOR, RESET_BROWNOUT, 0 }, + { STM32MP_RCC_RSTF_STDBY, RESET_WKE, 0 }, + { STM32MP_RCC_RSTF_CSTDBY, RESET_WKE, 1 }, + { STM32MP_RCC_RSTF_MPSYS, RESET_RST, 2 }, + { STM32MP_RCC_RSTF_MPUP0, RESET_RST, 0 }, + { STM32MP_RCC_RSTF_MPUP1, RESET_RST, 1 }, + { STM32MP_RCC_RSTF_IWDG1, RESET_WDG, 0 }, + { STM32MP_RCC_RSTF_IWDG2, RESET_WDG, 1 }, + { STM32MP_RCC_RSTF_PAD, RESET_EXT, 1 }, + { /* sentinel */ } +}; + static void stm32_set_reset_reason(struct stm32_reset *priv, const struct stm32_reset_reason *reasons) { @@ -128,9 +141,6 @@ static int stm32_reset_probe(struct device_d *dev) int ret; priv = xzalloc(sizeof(*priv)); - ret = dev_get_drvdata(dev, (const void **)&priv->ops); - if (ret) - return ret; iores = dev_request_mem_resource(dev, 0); if (IS_ERR(iores)) @@ -141,54 +151,21 @@ static int stm32_reset_probe(struct device_d *dev) priv->rcdev.ops = &stm32_reset_ops; priv->rcdev.of_node = dev->device_node; - if (priv->ops->sys_reset) { - priv->restart.name = "stm32-rcc"; - priv->restart.restart = priv->ops->sys_reset; - priv->restart.priority = 200; + priv->restart.name = "stm32-rcc"; + priv->restart.restart = stm32mp_rcc_restart_handler; + priv->restart.priority = 200; - ret = restart_handler_register(&priv->restart); - if (ret) - dev_warn(dev, "Cannot register restart handler\n"); - } + ret = restart_handler_register(&priv->restart); + if (ret) + dev_warn(dev, "Cannot register restart handler\n"); - if (priv->ops->reset_reasons) - stm32_set_reset_reason(priv, priv->ops->reset_reasons); + stm32_set_reset_reason(priv, stm32mp_reset_reasons); return reset_controller_register(&priv->rcdev); } -static void __noreturn stm32mp_rcc_restart_handler(struct restart_handler *rst) -{ - struct stm32_reset *priv = container_of(rst, struct stm32_reset, restart); - - stm32_reset(priv, RCC_MP_GRSTCSETR * BITS_PER_BYTE, true); - - mdelay(1000); - hang(); -} - -static const struct stm32_reset_reason stm32mp_reset_reasons[] = { - { STM32MP_RCC_RSTF_POR, RESET_POR, 0 }, - { STM32MP_RCC_RSTF_BOR, RESET_BROWNOUT, 0 }, - { STM32MP_RCC_RSTF_STDBY, RESET_WKE, 0 }, - { STM32MP_RCC_RSTF_CSTDBY, RESET_WKE, 1 }, - { STM32MP_RCC_RSTF_MPSYS, RESET_RST, 2 }, - { STM32MP_RCC_RSTF_MPUP0, RESET_RST, 0 }, - { STM32MP_RCC_RSTF_MPUP1, RESET_RST, 1 }, - { STM32MP_RCC_RSTF_IWDG1, RESET_WDG, 0 }, - { STM32MP_RCC_RSTF_IWDG2, RESET_WDG, 1 }, - { STM32MP_RCC_RSTF_PAD, RESET_EXT, 1 }, - { /* sentinel */ } -}; - -static const struct stm32_reset_ops stm32mp1_reset_ops = { - .reset = stm32mp_reset, - .sys_reset = stm32mp_rcc_restart_handler, - .reset_reasons = stm32mp_reset_reasons, -}; - static const struct of_device_id stm32_rcc_reset_dt_ids[] = { - { .compatible = "st,stm32mp1-rcc", .data = &stm32mp1_reset_ops }, + { .compatible = "st,stm32mp1-rcc" }, { /* sentinel */ }, }; -- cgit v1.2.3 From 2261d05cc0412695f6816303b9be7de390d6e551 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Sun, 20 Feb 2022 13:47:22 +0100 Subject: reset: move stm32 reset code to drivers/power/reset We will gut the STM32 reset controller parts in a follow-up commit, because that will be handled together with clocking in a single RCC driver. This will leave only restart and reset reason support in the driver, so move it to drivers/power/reset, so it's not out-of-place. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20220220124736.3052502-11-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- drivers/power/reset/Kconfig | 6 ++ drivers/power/reset/Makefile | 1 + drivers/power/reset/stm32-reboot.c | 178 +++++++++++++++++++++++++++++++++++++ drivers/reset/Kconfig | 6 -- drivers/reset/Makefile | 1 - drivers/reset/reset-stm32.c | 178 ------------------------------------- 6 files changed, 185 insertions(+), 185 deletions(-) create mode 100644 drivers/power/reset/stm32-reboot.c delete mode 100644 drivers/reset/reset-stm32.c diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig index 564b77ecba..40cbc4a3df 100644 --- a/drivers/power/reset/Kconfig +++ b/drivers/power/reset/Kconfig @@ -62,3 +62,9 @@ config POWER_RESET_HTIF_POWEROFF help Adds poweroff support via the syscall device on systems supporting the UC Berkely Host/Target Interface (HTIF). + +config RESET_STM32 + bool "STM32 Reset Driver" + depends on ARCH_STM32MP || COMPILE_TEST + help + This enables the reset controller driver for STM32MP1. diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile index c581382be8..347da50d76 100644 --- a/drivers/power/reset/Makefile +++ b/drivers/power/reset/Makefile @@ -7,3 +7,4 @@ obj-$(CONFIG_POWER_RESET_SYSCON_POWEROFF) += syscon-poweroff.o obj-$(CONFIG_POWER_RESET_GPIO) += gpio-poweroff.o obj-$(CONFIG_POWER_RESET_GPIO_RESTART) += gpio-restart.o obj-$(CONFIG_POWER_RESET_HTIF_POWEROFF) += htif-poweroff.o +obj-$(CONFIG_RESET_STM32) += stm32-reboot.o diff --git a/drivers/power/reset/stm32-reboot.c b/drivers/power/reset/stm32-reboot.c new file mode 100644 index 0000000000..e625ba27ff --- /dev/null +++ b/drivers/power/reset/stm32-reboot.c @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved + * Copyright (C) 2019, Ahmad Fatoum, Pengutronix + * Author(s): Patrice Chotard, for STMicroelectronics. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define RCC_CL 0x4 + +#define RCC_MP_GRSTCSETR 0x404 +#define RCC_MP_RSTSCLRR 0x408 + +#define STM32MP_RCC_RSTF_POR BIT(0) +#define STM32MP_RCC_RSTF_BOR BIT(1) +#define STM32MP_RCC_RSTF_PAD BIT(2) +#define STM32MP_RCC_RSTF_HCSS BIT(3) +#define STM32MP_RCC_RSTF_VCORE BIT(4) + +#define STM32MP_RCC_RSTF_MPSYS BIT(6) +#define STM32MP_RCC_RSTF_MCSYS BIT(7) +#define STM32MP_RCC_RSTF_IWDG1 BIT(8) +#define STM32MP_RCC_RSTF_IWDG2 BIT(9) + +#define STM32MP_RCC_RSTF_STDBY BIT(11) +#define STM32MP_RCC_RSTF_CSTDBY BIT(12) +#define STM32MP_RCC_RSTF_MPUP0 BIT(13) +#define STM32MP_RCC_RSTF_MPUP1 BIT(14) + +struct stm32_reset_reason { + uint32_t mask; + enum reset_src_type type; + int instance; +}; + +struct stm32_reset { + void __iomem *base; + struct reset_controller_dev rcdev; + struct restart_handler restart; +}; + +static struct stm32_reset *to_stm32_reset(struct reset_controller_dev *rcdev) +{ + return container_of(rcdev, struct stm32_reset, rcdev); +} + +static u32 stm32_reset_status(struct stm32_reset *priv, unsigned long bank) +{ + return readl(priv->base + bank); +} + +static void stm32_reset(struct stm32_reset *priv, unsigned long id, bool assert) +{ + int bank = (id / 32) * 4; + int offset = id % 32; + void __iomem *reg = priv->base + bank; + + if (!assert) + reg += RCC_CL; + + writel(BIT(offset), reg); +} + +static void __noreturn stm32mp_rcc_restart_handler(struct restart_handler *rst) +{ + struct stm32_reset *priv = container_of(rst, struct stm32_reset, restart); + + stm32_reset(priv, RCC_MP_GRSTCSETR * BITS_PER_BYTE, true); + + mdelay(1000); + hang(); +} + +static const struct stm32_reset_reason stm32mp_reset_reasons[] = { + { STM32MP_RCC_RSTF_POR, RESET_POR, 0 }, + { STM32MP_RCC_RSTF_BOR, RESET_BROWNOUT, 0 }, + { STM32MP_RCC_RSTF_STDBY, RESET_WKE, 0 }, + { STM32MP_RCC_RSTF_CSTDBY, RESET_WKE, 1 }, + { STM32MP_RCC_RSTF_MPSYS, RESET_RST, 2 }, + { STM32MP_RCC_RSTF_MPUP0, RESET_RST, 0 }, + { STM32MP_RCC_RSTF_MPUP1, RESET_RST, 1 }, + { STM32MP_RCC_RSTF_IWDG1, RESET_WDG, 0 }, + { STM32MP_RCC_RSTF_IWDG2, RESET_WDG, 1 }, + { STM32MP_RCC_RSTF_PAD, RESET_EXT, 1 }, + { /* sentinel */ } +}; + +static void stm32_set_reset_reason(struct stm32_reset *priv, + const struct stm32_reset_reason *reasons) +{ + enum reset_src_type type = RESET_UKWN; + u32 reg; + int i, instance = 0; + + reg = stm32_reset_status(priv, RCC_MP_RSTSCLRR); + + for (i = 0; reasons[i].mask; i++) { + if (reg & reasons[i].mask) { + type = reasons[i].type; + instance = reasons[i].instance; + break; + } + } + + reset_source_set_prinst(type, RESET_SOURCE_DEFAULT_PRIORITY, instance); + + pr_info("STM32 RCC reset reason %s (MP_RSTSR: 0x%08x)\n", + reset_source_to_string(type), reg); +} + +static int stm32_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + stm32_reset(to_stm32_reset(rcdev), id, true); + return 0; +} + +static int stm32_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + stm32_reset(to_stm32_reset(rcdev), id, false); + return 0; +} + +static const struct reset_control_ops stm32_reset_ops = { + .assert = stm32_reset_assert, + .deassert = stm32_reset_deassert, +}; + +static int stm32_reset_probe(struct device_d *dev) +{ + struct stm32_reset *priv; + struct resource *iores; + int ret; + + priv = xzalloc(sizeof(*priv)); + + iores = dev_request_mem_resource(dev, 0); + if (IS_ERR(iores)) + return PTR_ERR(iores); + + priv->base = IOMEM(iores->start); + priv->rcdev.nr_resets = (iores->end - iores->start) * BITS_PER_BYTE; + priv->rcdev.ops = &stm32_reset_ops; + priv->rcdev.of_node = dev->device_node; + + priv->restart.name = "stm32-rcc"; + priv->restart.restart = stm32mp_rcc_restart_handler; + priv->restart.priority = 200; + + ret = restart_handler_register(&priv->restart); + if (ret) + dev_warn(dev, "Cannot register restart handler\n"); + + stm32_set_reset_reason(priv, stm32mp_reset_reasons); + + return reset_controller_register(&priv->rcdev); +} + +static const struct of_device_id stm32_rcc_reset_dt_ids[] = { + { .compatible = "st,stm32mp1-rcc" }, + { /* sentinel */ }, +}; + +static struct driver_d stm32_rcc_reset_driver = { + .name = "stm32mp_rcc_reset", + .probe = stm32_reset_probe, + .of_compatible = DRV_OF_COMPAT(stm32_rcc_reset_dt_ids), +}; + +postcore_platform_driver(stm32_rcc_reset_driver); diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig index b12159094d..21a6e1a50d 100644 --- a/drivers/reset/Kconfig +++ b/drivers/reset/Kconfig @@ -39,12 +39,6 @@ config RESET_IMX7 help This enables the reset controller driver for i.MX7 SoCs. -config RESET_STM32 - bool "STM32 Reset Driver" - depends on ARCH_STM32MP || COMPILE_TEST - help - This enables the reset controller driver for STM32MP1. - config RESET_STARFIVE bool "StarFive Controller Driver" if COMPILE_TEST default SOC_STARFIVE diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile index b4270411fd..6d0cd51f86 100644 --- a/drivers/reset/Makefile +++ b/drivers/reset/Makefile @@ -3,5 +3,4 @@ obj-$(CONFIG_RESET_CONTROLLER) += core.o obj-$(CONFIG_RESET_SIMPLE) += reset-simple.o obj-$(CONFIG_ARCH_SOCFPGA) += reset-socfpga.o obj-$(CONFIG_RESET_IMX7) += reset-imx7.o -obj-$(CONFIG_RESET_STM32) += reset-stm32.o obj-$(CONFIG_RESET_STARFIVE) += reset-starfive-vic.o diff --git a/drivers/reset/reset-stm32.c b/drivers/reset/reset-stm32.c deleted file mode 100644 index e625ba27ff..0000000000 --- a/drivers/reset/reset-stm32.c +++ /dev/null @@ -1,178 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2017, STMicroelectronics - All Rights Reserved - * Copyright (C) 2019, Ahmad Fatoum, Pengutronix - * Author(s): Patrice Chotard, for STMicroelectronics. - */ - -#include -#include -#include -#include -#include -#include -#include - -#define RCC_CL 0x4 - -#define RCC_MP_GRSTCSETR 0x404 -#define RCC_MP_RSTSCLRR 0x408 - -#define STM32MP_RCC_RSTF_POR BIT(0) -#define STM32MP_RCC_RSTF_BOR BIT(1) -#define STM32MP_RCC_RSTF_PAD BIT(2) -#define STM32MP_RCC_RSTF_HCSS BIT(3) -#define STM32MP_RCC_RSTF_VCORE BIT(4) - -#define STM32MP_RCC_RSTF_MPSYS BIT(6) -#define STM32MP_RCC_RSTF_MCSYS BIT(7) -#define STM32MP_RCC_RSTF_IWDG1 BIT(8) -#define STM32MP_RCC_RSTF_IWDG2 BIT(9) - -#define STM32MP_RCC_RSTF_STDBY BIT(11) -#define STM32MP_RCC_RSTF_CSTDBY BIT(12) -#define STM32MP_RCC_RSTF_MPUP0 BIT(13) -#define STM32MP_RCC_RSTF_MPUP1 BIT(14) - -struct stm32_reset_reason { - uint32_t mask; - enum reset_src_type type; - int instance; -}; - -struct stm32_reset { - void __iomem *base; - struct reset_controller_dev rcdev; - struct restart_handler restart; -}; - -static struct stm32_reset *to_stm32_reset(struct reset_controller_dev *rcdev) -{ - return container_of(rcdev, struct stm32_reset, rcdev); -} - -static u32 stm32_reset_status(struct stm32_reset *priv, unsigned long bank) -{ - return readl(priv->base + bank); -} - -static void stm32_reset(struct stm32_reset *priv, unsigned long id, bool assert) -{ - int bank = (id / 32) * 4; - int offset = id % 32; - void __iomem *reg = priv->base + bank; - - if (!assert) - reg += RCC_CL; - - writel(BIT(offset), reg); -} - -static void __noreturn stm32mp_rcc_restart_handler(struct restart_handler *rst) -{ - struct stm32_reset *priv = container_of(rst, struct stm32_reset, restart); - - stm32_reset(priv, RCC_MP_GRSTCSETR * BITS_PER_BYTE, true); - - mdelay(1000); - hang(); -} - -static const struct stm32_reset_reason stm32mp_reset_reasons[] = { - { STM32MP_RCC_RSTF_POR, RESET_POR, 0 }, - { STM32MP_RCC_RSTF_BOR, RESET_BROWNOUT, 0 }, - { STM32MP_RCC_RSTF_STDBY, RESET_WKE, 0 }, - { STM32MP_RCC_RSTF_CSTDBY, RESET_WKE, 1 }, - { STM32MP_RCC_RSTF_MPSYS, RESET_RST, 2 }, - { STM32MP_RCC_RSTF_MPUP0, RESET_RST, 0 }, - { STM32MP_RCC_RSTF_MPUP1, RESET_RST, 1 }, - { STM32MP_RCC_RSTF_IWDG1, RESET_WDG, 0 }, - { STM32MP_RCC_RSTF_IWDG2, RESET_WDG, 1 }, - { STM32MP_RCC_RSTF_PAD, RESET_EXT, 1 }, - { /* sentinel */ } -}; - -static void stm32_set_reset_reason(struct stm32_reset *priv, - const struct stm32_reset_reason *reasons) -{ - enum reset_src_type type = RESET_UKWN; - u32 reg; - int i, instance = 0; - - reg = stm32_reset_status(priv, RCC_MP_RSTSCLRR); - - for (i = 0; reasons[i].mask; i++) { - if (reg & reasons[i].mask) { - type = reasons[i].type; - instance = reasons[i].instance; - break; - } - } - - reset_source_set_prinst(type, RESET_SOURCE_DEFAULT_PRIORITY, instance); - - pr_info("STM32 RCC reset reason %s (MP_RSTSR: 0x%08x)\n", - reset_source_to_string(type), reg); -} - -static int stm32_reset_assert(struct reset_controller_dev *rcdev, - unsigned long id) -{ - stm32_reset(to_stm32_reset(rcdev), id, true); - return 0; -} - -static int stm32_reset_deassert(struct reset_controller_dev *rcdev, - unsigned long id) -{ - stm32_reset(to_stm32_reset(rcdev), id, false); - return 0; -} - -static const struct reset_control_ops stm32_reset_ops = { - .assert = stm32_reset_assert, - .deassert = stm32_reset_deassert, -}; - -static int stm32_reset_probe(struct device_d *dev) -{ - struct stm32_reset *priv; - struct resource *iores; - int ret; - - priv = xzalloc(sizeof(*priv)); - - iores = dev_request_mem_resource(dev, 0); - if (IS_ERR(iores)) - return PTR_ERR(iores); - - priv->base = IOMEM(iores->start); - priv->rcdev.nr_resets = (iores->end - iores->start) * BITS_PER_BYTE; - priv->rcdev.ops = &stm32_reset_ops; - priv->rcdev.of_node = dev->device_node; - - priv->restart.name = "stm32-rcc"; - priv->restart.restart = stm32mp_rcc_restart_handler; - priv->restart.priority = 200; - - ret = restart_handler_register(&priv->restart); - if (ret) - dev_warn(dev, "Cannot register restart handler\n"); - - stm32_set_reset_reason(priv, stm32mp_reset_reasons); - - return reset_controller_register(&priv->rcdev); -} - -static const struct of_device_id stm32_rcc_reset_dt_ids[] = { - { .compatible = "st,stm32mp1-rcc" }, - { /* sentinel */ }, -}; - -static struct driver_d stm32_rcc_reset_driver = { - .name = "stm32mp_rcc_reset", - .probe = stm32_reset_probe, - .of_compatible = DRV_OF_COMPAT(stm32_rcc_reset_dt_ids), -}; - -postcore_platform_driver(stm32_rcc_reset_driver); -- cgit v1.2.3 From 4a2a67a98755594358e578c6ed4378a73e0a306e Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Sun, 20 Feb 2022 13:47:23 +0100 Subject: ARM: smccc: sync header with upstream For upcoming SCMI over SMC support, we'll need a newer version of the header. Import it from v5.13-rc1. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20220220124736.3052502-12-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- arch/arm/include/asm/opcodes-sec.h | 17 ++ include/linux/arm-smccc.h | 363 +++++++++++++++++++++++++++++++++++-- 2 files changed, 366 insertions(+), 14 deletions(-) create mode 100644 arch/arm/include/asm/opcodes-sec.h diff --git a/arch/arm/include/asm/opcodes-sec.h b/arch/arm/include/asm/opcodes-sec.h new file mode 100644 index 0000000000..b6f4b35024 --- /dev/null +++ b/arch/arm/include/asm/opcodes-sec.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * + * Copyright (C) 2012 ARM Limited + */ + +#ifndef __ASM_ARM_OPCODES_SEC_H +#define __ASM_ARM_OPCODES_SEC_H + +#include + +#define __SMC(imm4) __inst_arm_thumb32( \ + 0xE1600070 | (((imm4) & 0xF) << 0), \ + 0xF7F08000 | (((imm4) & 0xF) << 16) \ +) + +#endif /* __ASM_ARM_OPCODES_SEC_H */ diff --git a/include/linux/arm-smccc.h b/include/linux/arm-smccc.h index 1b38b7b372..64ee3863a6 100644 --- a/include/linux/arm-smccc.h +++ b/include/linux/arm-smccc.h @@ -1,28 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * Copyright (c) 2015, Linaro Limited - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * */ #ifndef __LINUX_ARM_SMCCC_H #define __LINUX_ARM_SMCCC_H +#include + /* * This file provides common defines for ARM SMC Calling Convention as * specified in - * http://infocenter.arm.com/help/topic/com.arm.doc.den0028a/index.html + * https://developer.arm.com/docs/den0028/latest + * + * This code is up-to-date with version DEN 0028 C */ -/* This constant is shifted by 31, make sure it's of an unsigned type */ -#define ARM_SMCCC_STD_CALL 0UL -#define ARM_SMCCC_FAST_CALL 1UL +#define ARM_SMCCC_STD_CALL _AC(0,U) +#define ARM_SMCCC_FAST_CALL _AC(1,U) #define ARM_SMCCC_TYPE_SHIFT 31 #define ARM_SMCCC_SMC_32 0 @@ -53,18 +47,178 @@ #define ARM_SMCCC_OWNER_SIP 2 #define ARM_SMCCC_OWNER_OEM 3 #define ARM_SMCCC_OWNER_STANDARD 4 +#define ARM_SMCCC_OWNER_STANDARD_HYP 5 +#define ARM_SMCCC_OWNER_VENDOR_HYP 6 #define ARM_SMCCC_OWNER_TRUSTED_APP 48 #define ARM_SMCCC_OWNER_TRUSTED_APP_END 49 #define ARM_SMCCC_OWNER_TRUSTED_OS 50 #define ARM_SMCCC_OWNER_TRUSTED_OS_END 63 +#define ARM_SMCCC_FUNC_QUERY_CALL_UID 0xff01 + #define ARM_SMCCC_QUIRK_NONE 0 #define ARM_SMCCC_QUIRK_QCOM_A6 1 /* Save/restore register a6 */ +#define ARM_SMCCC_VERSION_1_0 0x10000 +#define ARM_SMCCC_VERSION_1_1 0x10001 +#define ARM_SMCCC_VERSION_1_2 0x10002 + +#define ARM_SMCCC_VERSION_FUNC_ID \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \ + ARM_SMCCC_SMC_32, \ + 0, 0) + +#define ARM_SMCCC_ARCH_FEATURES_FUNC_ID \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \ + ARM_SMCCC_SMC_32, \ + 0, 1) + +#define ARM_SMCCC_ARCH_SOC_ID \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \ + ARM_SMCCC_SMC_32, \ + 0, 2) + +#define ARM_SMCCC_ARCH_WORKAROUND_1 \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \ + ARM_SMCCC_SMC_32, \ + 0, 0x8000) + +#define ARM_SMCCC_ARCH_WORKAROUND_2 \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \ + ARM_SMCCC_SMC_32, \ + 0, 0x7fff) + +#define ARM_SMCCC_VENDOR_HYP_CALL_UID_FUNC_ID \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \ + ARM_SMCCC_SMC_32, \ + ARM_SMCCC_OWNER_VENDOR_HYP, \ + ARM_SMCCC_FUNC_QUERY_CALL_UID) + +/* KVM UID value: 28b46fb6-2ec5-11e9-a9ca-4b564d003a74 */ +#define ARM_SMCCC_VENDOR_HYP_UID_KVM_REG_0 0xb66fb428U +#define ARM_SMCCC_VENDOR_HYP_UID_KVM_REG_1 0xe911c52eU +#define ARM_SMCCC_VENDOR_HYP_UID_KVM_REG_2 0x564bcaa9U +#define ARM_SMCCC_VENDOR_HYP_UID_KVM_REG_3 0x743a004dU + +/* KVM "vendor specific" services */ +#define ARM_SMCCC_KVM_FUNC_FEATURES 0 +#define ARM_SMCCC_KVM_FUNC_PTP 1 +#define ARM_SMCCC_KVM_FUNC_FEATURES_2 127 +#define ARM_SMCCC_KVM_NUM_FUNCS 128 + +#define ARM_SMCCC_VENDOR_HYP_KVM_FEATURES_FUNC_ID \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \ + ARM_SMCCC_SMC_32, \ + ARM_SMCCC_OWNER_VENDOR_HYP, \ + ARM_SMCCC_KVM_FUNC_FEATURES) + +#define SMCCC_ARCH_WORKAROUND_RET_UNAFFECTED 1 + +/* + * ptp_kvm is a feature used for time sync between vm and host. + * ptp_kvm module in guest kernel will get service from host using + * this hypercall ID. + */ +#define ARM_SMCCC_VENDOR_HYP_KVM_PTP_FUNC_ID \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \ + ARM_SMCCC_SMC_32, \ + ARM_SMCCC_OWNER_VENDOR_HYP, \ + ARM_SMCCC_KVM_FUNC_PTP) + +/* ptp_kvm counter type ID */ +#define KVM_PTP_VIRT_COUNTER 0 +#define KVM_PTP_PHYS_COUNTER 1 + +/* Paravirtualised time calls (defined by ARM DEN0057A) */ +#define ARM_SMCCC_HV_PV_TIME_FEATURES \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \ + ARM_SMCCC_SMC_64, \ + ARM_SMCCC_OWNER_STANDARD_HYP, \ + 0x20) + +#define ARM_SMCCC_HV_PV_TIME_ST \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \ + ARM_SMCCC_SMC_64, \ + ARM_SMCCC_OWNER_STANDARD_HYP, \ + 0x21) + +/* TRNG entropy source calls (defined by ARM DEN0098) */ +#define ARM_SMCCC_TRNG_VERSION \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \ + ARM_SMCCC_SMC_32, \ + ARM_SMCCC_OWNER_STANDARD, \ + 0x50) + +#define ARM_SMCCC_TRNG_FEATURES \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \ + ARM_SMCCC_SMC_32, \ + ARM_SMCCC_OWNER_STANDARD, \ + 0x51) + +#define ARM_SMCCC_TRNG_GET_UUID \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \ + ARM_SMCCC_SMC_32, \ + ARM_SMCCC_OWNER_STANDARD, \ + 0x52) + +#define ARM_SMCCC_TRNG_RND32 \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \ + ARM_SMCCC_SMC_32, \ + ARM_SMCCC_OWNER_STANDARD, \ + 0x53) + +#define ARM_SMCCC_TRNG_RND64 \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \ + ARM_SMCCC_SMC_64, \ + ARM_SMCCC_OWNER_STANDARD, \ + 0x53) + +/* + * Return codes defined in ARM DEN 0070A + * ARM DEN 0070A is now merged/consolidated into ARM DEN 0028 C + */ +#define SMCCC_RET_SUCCESS 0 +#define SMCCC_RET_NOT_SUPPORTED -1 +#define SMCCC_RET_NOT_REQUIRED -2 +#define SMCCC_RET_INVALID_PARAMETER -3 + #ifndef __ASSEMBLY__ #include #include + +enum arm_smccc_conduit { + SMCCC_CONDUIT_NONE, + SMCCC_CONDUIT_SMC, + SMCCC_CONDUIT_HVC, +}; + +/** + * arm_smccc_1_1_get_conduit() + * + * Returns the conduit to be used for SMCCCv1.1 or later. + * + * When SMCCCv1.1 is not present, returns SMCCC_CONDUIT_NONE. + */ +static inline enum arm_smccc_conduit arm_smccc_1_1_get_conduit(void) +{ + /* No HVC support yet */ + return SMCCC_CONDUIT_SMC; +} + +/** + * arm_smccc_get_version() + * + * Returns the version to be used for SMCCCv1.1 or later. + * + * When SMCCCv1.1 or above is not present, returns SMCCCv1.0, but this + * does not imply the presence of firmware or a valid conduit. Caller + * handling SMCCCv1.0 must determine the conduit by other means. + */ +u32 arm_smccc_get_version(void); + +void arm_smccc_version_init(u32 version, enum arm_smccc_conduit conduit); + /** * struct arm_smccc_res - Result from SMC/HVC call * @a0-a3 result values from registers 0 to 3 @@ -131,5 +285,186 @@ asmlinkage void __arm_smccc_hvc(unsigned long a0, unsigned long a1, #define arm_smccc_hvc_quirk(...) __arm_smccc_hvc(__VA_ARGS__) +/* SMCCC v1.1 implementation madness follows */ +#ifdef CONFIG_ARM64 + +#define SMCCC_SMC_INST "smc #0" +#define SMCCC_HVC_INST "hvc #0" + +#elif defined(CONFIG_ARM) +#include +#include + +#define SMCCC_SMC_INST __SMC(0) +#define SMCCC_HVC_INST __HVC(0) + +#endif + +#define ___count_args(_0, _1, _2, _3, _4, _5, _6, _7, _8, x, ...) x + +#define __count_args(...) \ + ___count_args(__VA_ARGS__, 7, 6, 5, 4, 3, 2, 1, 0) + +#define __constraint_read_0 "r" (arg0) +#define __constraint_read_1 __constraint_read_0, "r" (arg1) +#define __constraint_read_2 __constraint_read_1, "r" (arg2) +#define __constraint_read_3 __constraint_read_2, "r" (arg3) +#define __constraint_read_4 __constraint_read_3, "r" (arg4) +#define __constraint_read_5 __constraint_read_4, "r" (arg5) +#define __constraint_read_6 __constraint_read_5, "r" (arg6) +#define __constraint_read_7 __constraint_read_6, "r" (arg7) + +#define __declare_arg_0(a0, res) \ + struct arm_smccc_res *___res = res; \ + register unsigned long arg0 asm("r0") = (u32)a0 + +#define __declare_arg_1(a0, a1, res) \ + typeof(a1) __a1 = a1; \ + struct arm_smccc_res *___res = res; \ + register unsigned long arg0 asm("r0") = (u32)a0; \ + register typeof(a1) arg1 asm("r1") = __a1 + +#define __declare_arg_2(a0, a1, a2, res) \ + typeof(a1) __a1 = a1; \ + typeof(a2) __a2 = a2; \ + struct arm_smccc_res *___res = res; \ + register unsigned long arg0 asm("r0") = (u32)a0; \ + register typeof(a1) arg1 asm("r1") = __a1; \ + register typeof(a2) arg2 asm("r2") = __a2 + +#define __declare_arg_3(a0, a1, a2, a3, res) \ + typeof(a1) __a1 = a1; \ + typeof(a2) __a2 = a2; \ + typeof(a3) __a3 = a3; \ + struct arm_smccc_res *___res = res; \ + register unsigned long arg0 asm("r0") = (u32)a0; \ + register typeof(a1) arg1 asm("r1") = __a1; \ + register typeof(a2) arg2 asm("r2") = __a2; \ + register typeof(a3) arg3 asm("r3") = __a3 + +#define __declare_arg_4(a0, a1, a2, a3, a4, res) \ + typeof(a4) __a4 = a4; \ + __declare_arg_3(a0, a1, a2, a3, res); \ + register typeof(a4) arg4 asm("r4") = __a4 + +#define __declare_arg_5(a0, a1, a2, a3, a4, a5, res) \ + typeof(a5) __a5 = a5; \ + __declare_arg_4(a0, a1, a2, a3, a4, res); \ + register typeof(a5) arg5 asm("r5") = __a5 + +#define __declare_arg_6(a0, a1, a2, a3, a4, a5, a6, res) \ + typeof(a6) __a6 = a6; \ + __declare_arg_5(a0, a1, a2, a3, a4, a5, res); \ + register typeof(a6) arg6 asm("r6") = __a6 + +#define __declare_arg_7(a0, a1, a2, a3, a4, a5, a6, a7, res) \ + typeof(a7) __a7 = a7; \ + __declare_arg_6(a0, a1, a2, a3, a4, a5, a6, res); \ + register typeof(a7) arg7 asm("r7") = __a7 + +#define ___declare_args(count, ...) __declare_arg_ ## count(__VA_ARGS__) +#define __declare_args(count, ...) ___declare_args(count, __VA_ARGS__) + +#define ___constraints(count) \ + : __constraint_read_ ## count \ + : "memory" +#define __constraints(count) ___constraints(count) + +/* + * We have an output list that is not necessarily used, and GCC feels + * entitled to optimise the whole sequence away. "volatile" is what + * makes it stick. + */ +#define __arm_smccc_1_1(inst, ...) \ + do { \ + register unsigned long r0 asm("r0"); \ + register unsigned long r1 asm("r1"); \ + register unsigned long r2 asm("r2"); \ + register unsigned long r3 asm("r3"); \ + __declare_args(__count_args(__VA_ARGS__), __VA_ARGS__); \ + asm volatile(inst "\n" : \ + "=r" (r0), "=r" (r1), "=r" (r2), "=r" (r3) \ + __constraints(__count_args(__VA_ARGS__))); \ + if (___res) \ + *___res = (typeof(*___res)){r0, r1, r2, r3}; \ + } while (0) + +/* + * arm_smccc_1_1_smc() - make an SMCCC v1.1 compliant SMC call + * + * This is a variadic macro taking one to eight source arguments, and + * an optional return structure. + * + * @a0-a7: arguments passed in registers 0 to 7 + * @res: result values from registers 0 to 3 + * + * This macro is used to make SMC calls following SMC Calling Convention v1.1. + * The content of the supplied param are copied to registers 0 to 7 prior + * to the SMC instruction. The return values are updated with the content + * from register 0 to 3 on return from the SMC instruction if not NULL. + */ +#define arm_smccc_1_1_smc(...) __arm_smccc_1_1(SMCCC_SMC_INST, __VA_ARGS__) + +/* + * arm_smccc_1_1_hvc() - make an SMCCC v1.1 compliant HVC call + * + * This is a variadic macro taking one to eight source arguments, and + * an optional return structure. + * + * @a0-a7: arguments passed in registers 0 to 7 + * @res: result values from registers 0 to 3 + * + * This macro is used to make HVC calls following SMC Calling Convention v1.1. + * The content of the supplied param are copied to registers 0 to 7 prior + * to the HVC instruction. The return values are updated with the content + * from register 0 to 3 on return from the HVC instruction if not NULL. + */ +#define arm_smccc_1_1_hvc(...) __arm_smccc_1_1(SMCCC_HVC_INST, __VA_ARGS__) + +/* + * Like arm_smccc_1_1* but always returns SMCCC_RET_NOT_SUPPORTED. + * Used when the SMCCC conduit is not defined. The empty asm statement + * avoids compiler warnings about unused variables. + */ +#define __fail_smccc_1_1(...) \ + do { \ + __declare_args(__count_args(__VA_ARGS__), __VA_ARGS__); \ + asm ("" : __constraints(__count_args(__VA_ARGS__))); \ + if (___res) \ + ___res->a0 = SMCCC_RET_NOT_SUPPORTED; \ + } while (0) + +/* + * arm_smccc_1_1_invoke() - make an SMCCC v1.1 compliant call + * + * This is a variadic macro taking one to eight source arguments, and + * an optional return structure. + * + * @a0-a7: arguments passed in registers 0 to 7 + * @res: result values from registers 0 to 3 + * + * This macro will make either an HVC call or an SMC call depending on the + * current SMCCC conduit. If no valid conduit is available then -1 + * (SMCCC_RET_NOT_SUPPORTED) is returned in @res.a0 (if supplied). + * + * The return value also provides the conduit that was used. + */ +#define arm_smccc_1_1_invoke(...) ({ \ + int method = arm_smccc_1_1_get_conduit(); \ + switch (method) { \ + case SMCCC_CONDUIT_HVC: \ + arm_smccc_1_1_hvc(__VA_ARGS__); \ + break; \ + case SMCCC_CONDUIT_SMC: \ + arm_smccc_1_1_smc(__VA_ARGS__); \ + break; \ + default: \ + __fail_smccc_1_1(__VA_ARGS__); \ + method = SMCCC_CONDUIT_NONE; \ + break; \ + } \ + method; \ + }) + #endif /*__ASSEMBLY__*/ #endif /*__LINUX_ARM_SMCCC_H*/ -- cgit v1.2.3 From 8993005bda72568429ad57fabe9ef955bbc83927 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Sun, 20 Feb 2022 13:47:24 +0100 Subject: firmware: import Linux v5.13 SCMI support The ARM System Control and Management Interface (SCMI) is a standard way to offload handling of system resources like clocks, resets and power domain handling to the firmware. This should replace Silicon Provider specific conduits for new SoCs. Systems where this is already relevant: - STM32MP1: For trusted boot, the clock and reset controller are managed from secure world. This is not yet supported by barebox and must be disabled in TF-A - RK3568: At least the CPU clock is only controllable via SCMI - i.MX8X: Communication with the SCU occurs via SCMI Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20220220124736.3052502-13-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- drivers/base/driver.c | 28 + drivers/firmware/Kconfig | 10 + drivers/firmware/Makefile | 1 + drivers/firmware/arm_scmi/Makefile | 10 + drivers/firmware/arm_scmi/base.c | 281 ++++++++ drivers/firmware/arm_scmi/bus.c | 226 +++++++ drivers/firmware/arm_scmi/common.h | 330 ++++++++++ drivers/firmware/arm_scmi/driver.c | 1275 ++++++++++++++++++++++++++++++++++++ drivers/firmware/arm_scmi/shmem.c | 89 +++ drivers/firmware/arm_scmi/smc.c | 137 ++++ include/driver.h | 3 + include/linux/idr.h | 79 +++ include/linux/processor.h | 29 + include/linux/scmi_protocol.h | 654 ++++++++++++++++++ include/linux/slab.h | 3 + 15 files changed, 3155 insertions(+) create mode 100644 drivers/firmware/arm_scmi/Makefile create mode 100644 drivers/firmware/arm_scmi/base.c create mode 100644 drivers/firmware/arm_scmi/bus.c create mode 100644 drivers/firmware/arm_scmi/common.h create mode 100644 drivers/firmware/arm_scmi/driver.c create mode 100644 drivers/firmware/arm_scmi/shmem.c create mode 100644 drivers/firmware/arm_scmi/smc.c create mode 100644 include/linux/idr.h create mode 100644 include/linux/processor.h create mode 100644 include/linux/scmi_protocol.h diff --git a/drivers/base/driver.c b/drivers/base/driver.c index 2347b5c71f..303ca061ce 100644 --- a/drivers/base/driver.c +++ b/drivers/base/driver.c @@ -619,3 +619,31 @@ int dev_err_probe(const struct device_d *dev, int err, const char *fmt, ...) return err; } EXPORT_SYMBOL_GPL(dev_err_probe); + +/* + * device_find_child - device iterator for locating a particular device. + * @parent: parent struct device_d + * @match: Callback function to check device + * @data: Data to pass to match function + * + * The callback should return 0 if the device doesn't match and non-zero + * if it does. If the callback returns non-zero and a reference to the + * current device can be obtained, this function will return to the caller + * and not iterate over any more devices. + */ +struct device_d *device_find_child(struct device_d *parent, void *data, + int (*match)(struct device_d *dev, void *data)) +{ + struct device_d *child; + + if (!parent) + return NULL; + + list_for_each_entry(child, &parent->children, sibling) { + if (match(child, data)) + return child; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(device_find_child); diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig index ffba7146f7..d3cca41a7e 100644 --- a/drivers/firmware/Kconfig +++ b/drivers/firmware/Kconfig @@ -22,4 +22,14 @@ config FIRMWARE_ZYNQMP_FPGA select FIRMWARE help Load a bitstream to the PL of Zynq Ultrascale+ + +config ARM_SCMI_PROTOCOL + tristate "ARM System Control and Management Interface (SCMI) Message Protocol" + depends on ARM || COMPILE_TEST + depends on ARM_SMCCC + help + ARM System Control and Management Interface (SCMI) protocol is a + set of operating system-independent software interfaces that are + used in system management. + endmenu diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile index d16469cf72..26d6f3275a 100644 --- a/drivers/firmware/Makefile +++ b/drivers/firmware/Makefile @@ -2,3 +2,4 @@ obj-$(CONFIG_FIRMWARE_ALTERA_SERIAL) += altera_serial.o obj-$(CONFIG_FIRMWARE_ALTERA_SOCFPGA) += socfpga.o socfpga_sdr.o obj-$(CONFIG_FIRMWARE_ZYNQMP_FPGA) += zynqmp-fpga.o +obj-y += arm_scmi/ diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile new file mode 100644 index 0000000000..8bb25484b9 --- /dev/null +++ b/drivers/firmware/arm_scmi/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-only +scmi-bus-y = bus.o +scmi-driver-y = driver.o +scmi-transport-y = shmem.o +scmi-transport-$(CONFIG_ARM_SMCCC) += smc.o +scmi-protocols-y = base.o + +scmi-module-objs := $(scmi-bus-y) $(scmi-driver-y) $(scmi-protocols-y) \ + $(scmi-transport-y) +obj-$(CONFIG_ARM_SCMI_PROTOCOL) += scmi-module.o diff --git a/drivers/firmware/arm_scmi/base.c b/drivers/firmware/arm_scmi/base.c new file mode 100644 index 0000000000..d4af40c40c --- /dev/null +++ b/drivers/firmware/arm_scmi/base.c @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * System Control and Management Interface (SCMI) Base Protocol + * + * Copyright (C) 2018-2021 ARM Ltd. + */ + +#define pr_fmt(fmt) "SCMI BASE - " fmt + +#include +#include + +#include "common.h" + +#define SCMI_BASE_NUM_SOURCES 1 +#define SCMI_BASE_MAX_CMD_ERR_COUNT 1024 + +struct scmi_msg_resp_base_attributes { + u8 num_protocols; + u8 num_agents; + __le16 reserved; +}; + +enum scmi_base_protocol_cmd { + BASE_DISCOVER_VENDOR = 0x3, + BASE_DISCOVER_SUB_VENDOR = 0x4, + BASE_DISCOVER_IMPLEMENT_VERSION = 0x5, + BASE_DISCOVER_LIST_PROTOCOLS = 0x6, + BASE_DISCOVER_AGENT = 0x7, + BASE_NOTIFY_ERRORS = 0x8, + BASE_SET_DEVICE_PERMISSIONS = 0x9, + BASE_SET_PROTOCOL_PERMISSIONS = 0xa, + BASE_RESET_AGENT_CONFIGURATION = 0xb, +}; + +/** + * scmi_base_attributes_get() - gets the implementation details + * that are associated with the base protocol. + * + * @ph: SCMI protocol handle + * + * Return: 0 on success, else appropriate SCMI error. + */ +static int scmi_base_attributes_get(const struct scmi_protocol_handle *ph) +{ + int ret; + struct scmi_xfer *t; + struct scmi_msg_resp_base_attributes *attr_info; + struct scmi_revision_info *rev = ph->get_priv(ph); + + ret = ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES, + 0, sizeof(*attr_info), &t); + if (ret) + return ret; + + ret = ph->xops->do_xfer(ph, t); + if (!ret) { + attr_info = t->rx.buf; + rev->num_protocols = attr_info->num_protocols; + rev->num_agents = attr_info->num_agents; + } + + ph->xops->xfer_put(ph, t); + + return ret; +} + +/** + * scmi_base_vendor_id_get() - gets vendor/subvendor identifier ASCII string. + * + * @ph: SCMI protocol handle + * @sub_vendor: specify true if sub-vendor ID is needed + * + * Return: 0 on success, else appropriate SCMI error. + */ +static int +scmi_base_vendor_id_get(const struct scmi_protocol_handle *ph, bool sub_vendor) +{ + u8 cmd; + int ret, size; + char *vendor_id; + struct scmi_xfer *t; + struct scmi_revision_info *rev = ph->get_priv(ph); + + + if (sub_vendor) { + cmd = BASE_DISCOVER_SUB_VENDOR; + vendor_id = rev->sub_vendor_id; + size = ARRAY_SIZE(rev->sub_vendor_id); + } else { + cmd = BASE_DISCOVER_VENDOR; + vendor_id = rev->vendor_id; + size = ARRAY_SIZE(rev->vendor_id); + } + + ret = ph->xops->xfer_get_init(ph, cmd, 0, size, &t); + if (ret) + return ret; + + ret = ph->xops->do_xfer(ph, t); + if (!ret) + memcpy(vendor_id, t->rx.buf, size); + + ph->xops->xfer_put(ph, t); + + return ret; +} + +/** + * scmi_base_implementation_version_get() - gets a vendor-specific + * implementation 32-bit version. The format of the version number is + * vendor-specific + * + * @ph: SCMI protocol handle + * + * Return: 0 on success, else appropriate SCMI error. + */ +static int +scmi_base_implementation_version_get(const struct scmi_protocol_handle *ph) +{ + int ret; + __le32 *impl_ver; + struct scmi_xfer *t; + struct scmi_revision_info *rev = ph->get_priv(ph); + + ret = ph->xops->xfer_get_init(ph, BASE_DISCOVER_IMPLEMENT_VERSION, + 0, sizeof(*impl_ver), &t); + if (ret) + return ret; + + ret = ph->xops->do_xfer(ph, t); + if (!ret) { + impl_ver = t->rx.buf; + rev->impl_ver = le32_to_cpu(*impl_ver); + } + + ph->xops->xfer_put(ph, t); + + return ret; +} + +/** + * scmi_base_implementation_list_get() - gets the list of protocols it is + * OSPM is allowed to access + * + * @ph: SCMI protocol handle + * @protocols_imp: pointer to hold the list of protocol identifiers + * + * Return: 0 on success, else appropriate SCMI error. + */ +static int +scmi_base_implementation_list_get(const struct scmi_protocol_handle *ph, + u8 *protocols_imp) +{ + u8 *list; + int ret, loop; + struct scmi_xfer *t; + __le32 *num_skip, *num_ret; + u32 tot_num_ret = 0, loop_num_ret; + struct device_d *dev = ph->dev; + + ret = ph->xops->xfer_get_init(ph, BASE_DISCOVER_LIST_PROTOCOLS, + sizeof(*num_skip), 0, &t); + if (ret) + return ret; + + num_skip = t->tx.buf; + num_ret = t->rx.buf; + list = t->rx.buf + sizeof(*num_ret); + + do { + /* Set the number of protocols to be skipped/already read */ + *num_skip = cpu_to_le32(tot_num_ret); + + ret = ph->xops->do_xfer(ph, t); + if (ret) + break; + + loop_num_ret = le32_to_cpu(*num_ret); + if (tot_num_ret + loop_num_ret > MAX_PROTOCOLS_IMP) { + dev_err(dev, "No. of Protocol > MAX_PROTOCOLS_IMP"); + break; + } + + for (loop = 0; loop < loop_num_ret; loop++) + protocols_imp[tot_num_ret + loop] = *(list + loop); + + tot_num_ret += loop_num_ret; + + ph->xops->reset_rx_to_maxsz(ph, t); + } while (loop_num_ret); + + ph->xops->xfer_put(ph, t); + + return ret; +} + +/** + * scmi_base_discover_agent_get() - discover the name of an agent + * + * @ph: SCMI protocol handle + * @id: Agent identifier + * @name: Agent identifier ASCII string + * + * An agent id of 0 is reserved to identify the platform itself. + * Generally operating system is represented as "OSPM" + * + * Return: 0 on success, else appropriate SCMI error. + */ +static int scmi_base_discover_agent_get(const struct scmi_protocol_handle *ph, + int id, char *name) +{ + int ret; + struct scmi_xfer *t; + + ret = ph->xops->xfer_get_init(ph, BASE_DISCOVER_AGENT, + sizeof(__le32), SCMI_MAX_STR_SIZE, &t); + if (ret) + return ret; + + put_unaligned_le32(id, t->tx.buf); + + ret = ph->xops->do_xfer(ph, t); + if (!ret) + strlcpy(name, t->rx.buf, SCMI_MAX_STR_SIZE); + + ph->xops->xfer_put(ph, t); + + return ret; +} + +static int scmi_base_protocol_init(const struct scmi_protocol_handle *ph) +{ + int id, ret; + u8 *prot_imp; + u32 version; + char name[SCMI_MAX_STR_SIZE]; + struct device_d *dev = ph->dev; + struct scmi_revision_info *rev = scmi_revision_area_get(ph); + + ret = ph->xops->version_get(ph, &version); + if (ret) + return ret; + + prot_imp = kcalloc(MAX_PROTOCOLS_IMP, sizeof(u8), GFP_KERNEL); + if (!prot_imp) + return -ENOMEM; + + rev->major_ver = PROTOCOL_REV_MAJOR(version), + rev->minor_ver = PROTOCOL_REV_MINOR(version); + ph->set_priv(ph, rev); + + scmi_base_attributes_get(ph); + scmi_base_vendor_id_get(ph, false); + scmi_base_vendor_id_get(ph, true); + scmi_base_implementation_version_get(ph); + scmi_base_implementation_list_get(ph, prot_imp); + + scmi_setup_protocol_implemented(ph, prot_imp); + + dev_info(dev, "SCMI Protocol v%d.%d '%s:%s' Firmware version 0x%x\n", + rev->major_ver, rev->minor_ver, rev->vendor_id, + rev->sub_vendor_id, rev->impl_ver); + dev_dbg(dev, "Found %d protocol(s) %d agent(s)\n", rev->num_protocols, + rev->num_agents); + + for (id = 0; id < rev->num_agents; id++) { + scmi_base_discover_agent_get(ph, id, name); + dev_dbg(dev, "Agent %d: %s\n", id, name); + } + + return 0; +} + +static const struct scmi_protocol scmi_base = { + .id = SCMI_PROTOCOL_BASE, + .instance_init = &scmi_base_protocol_init, + .ops = NULL, +}; + +DEFINE_SCMI_PROTOCOL_REGISTER(base, scmi_base) diff --git a/drivers/firmware/arm_scmi/bus.c b/drivers/firmware/arm_scmi/bus.c new file mode 100644 index 0000000000..e8297be4d2 --- /dev/null +++ b/drivers/firmware/arm_scmi/bus.c @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * System Control and Management Interface (SCMI) Message Protocol bus layer + * + * Copyright (C) 2018-2021 ARM Ltd. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include + +#include "common.h" + +static DEFINE_IDR(scmi_protocols); + +static const struct scmi_device_id * +scmi_dev_match_id(struct scmi_device *scmi_dev, struct scmi_driver *scmi_drv) +{ + const struct scmi_device_id *id = scmi_drv->id_table; + + if (!id) + return NULL; + + for (; id->protocol_id; id++) + if (id->protocol_id == scmi_dev->protocol_id) { + if (!id->name) + return id; + else if (!strcmp(id->name, scmi_dev->name)) + return id; + } + + return NULL; +} + +static int scmi_dev_match(struct device_d *dev, struct driver_d *drv) +{ + struct scmi_driver *scmi_drv = to_scmi_driver(drv); + struct scmi_device *scmi_dev = to_scmi_dev(dev); + const struct scmi_device_id *id; + + id = scmi_dev_match_id(scmi_dev, scmi_drv); + if (id) + return 0; + + return -1; +} + +static int scmi_match_by_id_table(struct device_d *dev, void *data) +{ + struct scmi_device *sdev = to_scmi_dev(dev); + struct scmi_device_id *id_table = data; + + return sdev->protocol_id == id_table->protocol_id && + !strcmp(sdev->name, id_table->name); +} + +struct scmi_device *scmi_child_dev_find(struct device_d *parent, + int prot_id, const char *name) +{ + struct scmi_device_id id_table; + struct device_d *dev; + + id_table.protocol_id = prot_id; + id_table.name = name; + + dev = device_find_child(parent, &id_table, scmi_match_by_id_table); + if (!dev) + return NULL; + + return to_scmi_dev(dev); +} + +const struct scmi_protocol *scmi_protocol_get(int protocol_id) +{ + const struct scmi_protocol *proto; + + proto = idr_find(&scmi_protocols, protocol_id); + if (!proto) { + pr_warn("SCMI Protocol 0x%x not found!\n", protocol_id); + return NULL; + } + + pr_debug("Found SCMI Protocol 0x%x\n", protocol_id); + + return proto; +} + +static int scmi_dev_probe(struct device_d *dev) +{ + struct scmi_driver *scmi_drv = to_scmi_driver(dev->driver); + struct scmi_device *scmi_dev = to_scmi_dev(dev); + const struct scmi_device_id *id; + + id = scmi_dev_match_id(scmi_dev, scmi_drv); + if (!id) + return -ENODEV; + + if (!scmi_dev->handle) + return -EPROBE_DEFER; + + return scmi_drv->probe(scmi_dev); +} + +static void scmi_dev_remove(struct device_d *dev) +{ + struct scmi_driver *scmi_drv = to_scmi_driver(dev->driver); + struct scmi_device *scmi_dev = to_scmi_dev(dev); + + if (scmi_drv->remove) + scmi_drv->remove(scmi_dev); +} + +static struct bus_type scmi_bus_type = { + .name = "scmi_protocol", + .match = scmi_dev_match, + .probe = scmi_dev_probe, + .remove = scmi_dev_remove, +}; + +int scmi_driver_register(struct scmi_driver *driver) +{ + int retval; + + retval = scmi_protocol_device_request(driver->id_table); + if (retval) + return retval; + + driver->driver.bus = &scmi_bus_type; + driver->driver.name = driver->name; + + retval = register_driver(&driver->driver); + if (!retval) + pr_debug("registered new scmi driver %s\n", driver->name); + + return retval; +} +EXPORT_SYMBOL_GPL(scmi_driver_register); + +struct scmi_device * +scmi_device_alloc(struct device_node *np, struct device_d *parent, int protocol, + const char *name) +{ + struct scmi_device *scmi_dev; + + scmi_dev = kzalloc(sizeof(*scmi_dev), GFP_KERNEL); + if (!scmi_dev) + return NULL; + + scmi_dev->name = kstrdup_const(name ?: "unknown", GFP_KERNEL); + if (!scmi_dev->name) { + kfree(scmi_dev); + return NULL; + } + + scmi_dev->dev.id = DEVICE_ID_DYNAMIC; + scmi_dev->protocol_id = protocol; + scmi_dev->dev.parent = parent; + scmi_dev->dev.device_node = np; + scmi_dev->dev.bus = &scmi_bus_type; + dev_set_name(&scmi_dev->dev, "scmi_dev"); + + return scmi_dev; +} + +void scmi_device_destroy(struct scmi_device *scmi_dev) +{ + kfree_const(scmi_dev->name); + unregister_device(&scmi_dev->dev); +} + +void scmi_set_handle(struct scmi_device *scmi_dev) +{ + scmi_dev->handle = scmi_handle_get(&scmi_dev->dev); +} + +int scmi_protocol_register(const struct scmi_protocol *proto) +{ + int ret; + + if (!proto) { + pr_err("invalid protocol\n"); + return -EINVAL; + } + + if (!proto->instance_init) { + pr_err("missing init for protocol 0x%x\n", proto->id); + return -EINVAL; + } + + ret = idr_alloc_one(&scmi_protocols, (void *)proto, proto->id); + if (ret != proto->id) { + pr_err("unable to allocate SCMI idr slot for 0x%x - err %d\n", + proto->id, ret); + return ret; + } + + pr_debug("Registered SCMI Protocol 0x%x\n", proto->id); + + return 0; +} +EXPORT_SYMBOL_GPL(scmi_protocol_register); + +void scmi_protocol_unregister(const struct scmi_protocol *proto) +{ + idr_remove(&scmi_protocols, proto->id); + + pr_debug("Unregistered SCMI Protocol 0x%x\n", proto->id); + + return; +} +EXPORT_SYMBOL_GPL(scmi_protocol_unregister); + +int __init scmi_bus_init(void) +{ + int retval; + + retval = bus_register(&scmi_bus_type); + if (retval) + pr_err("scmi protocol bus register failed (%d)\n", retval); + + return retval; +} diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h new file mode 100644 index 0000000000..9e48c0e774 --- /dev/null +++ b/drivers/firmware/arm_scmi/common.h @@ -0,0 +1,330 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * System Control and Management Interface (SCMI) Message Protocol + * driver common header file containing some definitions, structures + * and function prototypes used in all the different SCMI protocols. + * + * Copyright (C) 2018-2021 ARM Ltd. + */ +#ifndef _SCMI_COMMON_H +#define _SCMI_COMMON_H + +#include +#include +#include +#include +#include +#include +#include + +#include + +#define PROTOCOL_REV_MINOR_MASK GENMASK(15, 0) +#define PROTOCOL_REV_MAJOR_MASK GENMASK(31, 16) +#define PROTOCOL_REV_MAJOR(x) (u16)(FIELD_GET(PROTOCOL_REV_MAJOR_MASK, (x))) +#define PROTOCOL_REV_MINOR(x) (u16)(FIELD_GET(PROTOCOL_REV_MINOR_MASK, (x))) +#define MAX_PROTOCOLS_IMP 16 +#define MAX_OPPS 16 + +enum scmi_common_cmd { + PROTOCOL_VERSION = 0x0, + PROTOCOL_ATTRIBUTES = 0x1, + PROTOCOL_MESSAGE_ATTRIBUTES = 0x2, +}; + +/** + * struct scmi_msg_resp_prot_version - Response for a message + * + * @minor_version: Minor version of the ABI that firmware supports + * @major_version: Major version of the ABI that firmware supports + * + * In general, ABI version changes follow the rule that minor version increments + * are backward compatible. Major revision changes in ABI may not be + * backward compatible. + * + * Response to a generic message with message type SCMI_MSG_VERSION + */ +struct scmi_msg_resp_prot_version { + __le16 minor_version; + __le16 major_version; +}; + +#define MSG_ID_MASK GENMASK(7, 0) +#define MSG_XTRACT_ID(hdr) FIELD_GET(MSG_ID_MASK, (hdr)) +#define MSG_TYPE_MASK GENMASK(9, 8) +#define MSG_XTRACT_TYPE(hdr) FIELD_GET(MSG_TYPE_MASK, (hdr)) +#define MSG_TYPE_COMMAND 0 +#define MSG_TYPE_DELAYED_RESP 2 +#define MSG_TYPE_NOTIFICATION 3 +#define MSG_PROTOCOL_ID_MASK GENMASK(17, 10) +#define MSG_XTRACT_PROT_ID(hdr) FIELD_GET(MSG_PROTOCOL_ID_MASK, (hdr)) +#define MSG_TOKEN_ID_MASK GENMASK(27, 18) +#define MSG_XTRACT_TOKEN(hdr) FIELD_GET(MSG_TOKEN_ID_MASK, (hdr)) +#define MSG_TOKEN_MAX (MSG_XTRACT_TOKEN(MSG_TOKEN_ID_MASK) + 1) + +/** + * struct scmi_msg_hdr - Message(Tx/Rx) header + * + * @id: The identifier of the message being sent + * @protocol_id: The identifier of the protocol used to send @id message + * @seq: The token to identify the message. When a message returns, the + * platform returns the whole message header unmodified including the + * token + * @status: Status of the transfer once it's complete + * @poll_completion: Indicate if the transfer needs to be polled for + * completion or interrupt mode is used + */ +struct scmi_msg_hdr { + u8 id; + u8 protocol_id; + u16 seq; + u32 status; + bool poll_completion; +}; + +/** + * pack_scmi_header() - packs and returns 32-bit header + * + * @hdr: pointer to header containing all the information on message id, + * protocol id and sequence id. + * + * Return: 32-bit packed message header to be sent to the platform. + */ +static inline u32 pack_scmi_header(struct scmi_msg_hdr *hdr) +{ + return FIELD_PREP(MSG_ID_MASK, hdr->id) | + FIELD_PREP(MSG_TOKEN_ID_MASK, hdr->seq) | + FIELD_PREP(MSG_PROTOCOL_ID_MASK, hdr->protocol_id); +} + +/** + * unpack_scmi_header() - unpacks and records message and protocol id + * + * @msg_hdr: 32-bit packed message header sent from the platform + * @hdr: pointer to header to fetch message and protocol id. + */ +static inline void unpack_scmi_header(u32 msg_hdr, struct scmi_msg_hdr *hdr) +{ + hdr->id = MSG_XTRACT_ID(msg_hdr); + hdr->protocol_id = MSG_XTRACT_PROT_ID(msg_hdr); +} + +/** + * struct scmi_msg - Message(Tx/Rx) structure + * + * @buf: Buffer pointer + * @len: Length of data in the Buffer + */ +struct scmi_msg { + void *buf; + size_t len; +}; + +/** + * struct scmi_xfer - Structure representing a message flow + * + * @transfer_id: Unique ID for debug & profiling purpose + * @hdr: Transmit message header + * @tx: Transmit message + * @rx: Receive message, the buffer should be pre-allocated to store + * message. If request-ACK protocol is used, we can reuse the same + * buffer for the rx path as we use for the tx path. + * @done: command message transmit completion event + * @async_done: pointer to delayed response message received event completion + */ +struct scmi_xfer { + int transfer_id; + struct scmi_msg_hdr hdr; + struct scmi_msg tx; + struct scmi_msg rx; + bool done; + bool *async_done; +}; + +struct scmi_xfer_ops; + +/** + * struct scmi_protocol_handle - Reference to an initialized protocol instance + * + * @dev: A reference to the associated SCMI instance device (handle->dev). + * @xops: A reference to a struct holding refs to the core xfer operations that + * can be used by the protocol implementation to generate SCMI messages. + * @set_priv: A method to set protocol private data for this instance. + * @get_priv: A method to get protocol private data previously set. + * + * This structure represents a protocol initialized against specific SCMI + * instance and it will be used as follows: + * - as a parameter fed from the core to the protocol initialization code so + * that it can access the core xfer operations to build and generate SCMI + * messages exclusively for the specific underlying protocol instance. + * - as an opaque handle fed by an SCMI driver user when it tries to access + * this protocol through its own protocol operations. + * In this case this handle will be returned as an opaque object together + * with the related protocol operations when the SCMI driver tries to access + * the protocol. + */ +struct scmi_protocol_handle { + struct device_d *dev; + const struct scmi_xfer_ops *xops; + int (*set_priv)(const struct scmi_protocol_handle *ph, void *priv); + void *(*get_priv)(const struct scmi_protocol_handle *ph); +}; + +/** + * struct scmi_xfer_ops - References to the core SCMI xfer operations. + * @version_get: Get this version protocol. + * @xfer_get_init: Initialize one struct xfer if any xfer slot is free. + * @reset_rx_to_maxsz: Reset rx size to max transport size. + * @do_xfer: Do the SCMI transfer. + * @do_xfer_with_response: Do the SCMI transfer waiting for a response. + * @xfer_put: Free the xfer slot. + * + * Note that all this operations expect a protocol handle as first parameter; + * they then internally use it to infer the underlying protocol number: this + * way is not possible for a protocol implementation to forge messages for + * another protocol. + */ +struct scmi_xfer_ops { + int (*version_get)(const struct scmi_protocol_handle *ph, u32 *version); + int (*xfer_get_init)(const struct scmi_protocol_handle *ph, u8 msg_id, + size_t tx_size, size_t rx_size, + struct scmi_xfer **p); + void (*reset_rx_to_maxsz)(const struct scmi_protocol_handle *ph, + struct scmi_xfer *xfer); + int (*do_xfer)(const struct scmi_protocol_handle *ph, + struct scmi_xfer *xfer); + int (*do_xfer_with_response)(const struct scmi_protocol_handle *ph, + struct scmi_xfer *xfer); + void (*xfer_put)(const struct scmi_protocol_handle *ph, + struct scmi_xfer *xfer); +}; + +struct scmi_revision_info * +scmi_revision_area_get(const struct scmi_protocol_handle *ph); +int scmi_handle_put(const struct scmi_handle *handle); +struct scmi_handle *scmi_handle_get(struct device_d *dev); +void scmi_set_handle(struct scmi_device *scmi_dev); +void scmi_setup_protocol_implemented(const struct scmi_protocol_handle *ph, + u8 *prot_imp); + +typedef int (*scmi_prot_init_ph_fn_t)(const struct scmi_protocol_handle *); + +/** + * struct scmi_protocol - Protocol descriptor + * @id: Protocol ID. + * @instance_init: Mandatory protocol initialization function. + * @instance_deinit: Optional protocol de-initialization function. + * @ops: Optional reference to the operations provided by the protocol and + * exposed in scmi_protocol.h. + * @events: An optional reference to the events supported by this protocol. + */ +struct scmi_protocol { + const u8 id; + const scmi_prot_init_ph_fn_t instance_init; + const scmi_prot_init_ph_fn_t instance_deinit; + const void *ops; + const struct scmi_protocol_events *events; +}; + +int __init scmi_bus_init(void); +void __exit scmi_bus_exit(void); + +#define DECLARE_SCMI_REGISTER(func) \ + int __init scmi_##func##_register(void); +DECLARE_SCMI_REGISTER(base); + +#define DEFINE_SCMI_PROTOCOL_REGISTER(name, proto) \ +static const struct scmi_protocol *__this_proto = &(proto); \ + \ +int __init scmi_##name##_register(void) \ +{ \ + return scmi_protocol_register(__this_proto); \ +} + +const struct scmi_protocol *scmi_protocol_get(int protocol_id); + +int scmi_protocol_acquire(const struct scmi_handle *handle, u8 protocol_id); +void scmi_protocol_release(const struct scmi_handle *handle, u8 protocol_id); + +/* SCMI Transport */ +/** + * struct scmi_chan_info - Structure representing a SCMI channel information + * + * @dev: Reference to device in the SCMI hierarchy corresponding to this + * channel + * @handle: Pointer to SCMI entity handle + * @transport_info: Transport layer related information + */ +struct scmi_chan_info { + struct device_d *dev; + struct scmi_handle *handle; + void *transport_info; +}; + +/** + * struct scmi_transport_ops - Structure representing a SCMI transport ops + * + * @chan_available: Callback to check if channel is available or not + * @chan_setup: Callback to allocate and setup a channel + * @chan_free: Callback to free a channel + * @send_message: Callback to send a message + * @mark_txdone: Callback to mark tx as done + * @fetch_response: Callback to fetch response + * @clear_channel: Callback to clear a channel + * @poll_done: Callback to poll transfer status + */ +struct scmi_transport_ops { + bool (*chan_available)(struct device_d *dev, int idx); + int (*chan_setup)(struct scmi_chan_info *cinfo, struct device_d *dev, + bool tx); + int (*chan_free)(int id, void *p, void *data); + int (*send_message)(struct scmi_chan_info *cinfo, + struct scmi_xfer *xfer); + void (*mark_txdone)(struct scmi_chan_info *cinfo, int ret); + void (*fetch_response)(struct scmi_chan_info *cinfo, + struct scmi_xfer *xfer); + void (*clear_channel)(struct scmi_chan_info *cinfo); + bool (*poll_done)(struct scmi_chan_info *cinfo, struct scmi_xfer *xfer); +}; + +int scmi_protocol_device_request(const struct scmi_device_id *id_table); +void scmi_protocol_device_unrequest(const struct scmi_device_id *id_table); +struct scmi_device *scmi_child_dev_find(struct device_d *parent, + int prot_id, const char *name); + +/** + * struct scmi_desc - Description of SoC integration + * + * @ops: Pointer to the transport specific ops structure + * @max_rx_timeout_ms: Timeout for communication with SoC (in Milliseconds) + * @max_msg: Maximum number of messages that can be pending + * simultaneously in the system + * @max_msg_size: Maximum size of data per message that can be handled. + */ +struct scmi_desc { + const struct scmi_transport_ops *ops; + int max_rx_timeout_ms; + int max_msg; + int max_msg_size; +}; + +#ifdef CONFIG_ARM_SMCCC +extern const struct scmi_desc scmi_smc_desc; +#endif + +void scmi_rx_callback(struct scmi_chan_info *cinfo, u32 msg_hdr); +void scmi_free_channel(struct scmi_chan_info *cinfo, struct idr *idr, int id); + +/* shmem related declarations */ +struct scmi_shared_mem; + +void shmem_tx_prepare(struct scmi_shared_mem __iomem *shmem, + struct scmi_xfer *xfer); +u32 shmem_read_header(struct scmi_shared_mem __iomem *shmem); +void shmem_fetch_response(struct scmi_shared_mem __iomem *shmem, + struct scmi_xfer *xfer); +void shmem_clear_channel(struct scmi_shared_mem __iomem *shmem); +bool shmem_poll_done(struct scmi_shared_mem __iomem *shmem, + struct scmi_xfer *xfer); + +#endif /* _SCMI_COMMON_H */ diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c new file mode 100644 index 0000000000..c9819e7c2a --- /dev/null +++ b/drivers/firmware/arm_scmi/driver.c @@ -0,0 +1,1275 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * System Control and Management Interface (SCMI) Message Protocol driver + * + * SCMI Message Protocol is used between the System Control Processor(SCP) + * and the Application Processors(AP). The Message Handling Unit(MHU) + * provides a mechanism for inter-processor communication between SCP's + * Cortex M3 and AP. + * + * SCP offers control and management of the core/cluster power states, + * various power domain DVFS including the core/cluster, certain system + * clocks configuration, thermal sensors and many others. + * + * Copyright (C) 2018-2021 ARM Ltd. + */ + +#define pr_fmt(fmt) "SCMI DRIVER - " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" + +enum scmi_error_codes { + SCMI_SUCCESS = 0, /* Success */ + SCMI_ERR_SUPPORT = -1, /* Not supported */ + SCMI_ERR_PARAMS = -2, /* Invalid Parameters */ + SCMI_ERR_ACCESS = -3, /* Invalid access/permission denied */ + SCMI_ERR_ENTRY = -4, /* Not found */ + SCMI_ERR_RANGE = -5, /* Value out of range */ + SCMI_ERR_BUSY = -6, /* Device busy */ + SCMI_ERR_COMMS = -7, /* Communication Error */ + SCMI_ERR_GENERIC = -8, /* Generic Error */ + SCMI_ERR_HARDWARE = -9, /* Hardware Error */ + SCMI_ERR_PROTOCOL = -10,/* Protocol Error */ + SCMI_ERR_MAX +}; + +/* List of all SCMI devices active in system */ +static LIST_HEAD(scmi_list); +/* Protection for the entire list */ +/* Track the unique id for the transfers for debug & profiling purpose */ +static unsigned transfer_last_id; + +static DEFINE_IDR(scmi_requested_devices); + +struct scmi_requested_dev { + const struct scmi_device_id *id_table; + struct list_head node; +}; + +/** + * struct scmi_xfers_info - Structure to manage transfer information + * + * @xfer_block: Preallocated Message array + * @xfer_alloc_table: Bitmap table for allocated messages. + * Index of this bitmap table is also used for message + * sequence identifier. + */ +struct scmi_xfers_info { + struct scmi_xfer *xfer_block; + unsigned long *xfer_alloc_table; +}; + +/** + * struct scmi_protocol_instance - Describe an initialized protocol instance. + * @handle: Reference to the SCMI handle associated to this protocol instance. + * @proto: A reference to the protocol descriptor. + * @users: A refcount to track effective users of this protocol. + * @priv: Reference for optional protocol private data. + * @ph: An embedded protocol handle that will be passed down to protocol + * initialization code to identify this instance. + * + * Each protocol is initialized independently once for each SCMI platform in + * which is defined by DT and implemented by the SCMI server fw. + */ +struct scmi_protocol_instance { + const struct scmi_handle *handle; + const struct scmi_protocol *proto; + int users; + void *priv; + struct scmi_protocol_handle ph; +}; + +#define ph_to_pi(h) container_of(h, struct scmi_protocol_instance, ph) + +/** + * struct scmi_info - Structure representing a SCMI instance + * + * @dev: Device pointer + * @desc: SoC description for this instance + * @version: SCMI revision information containing protocol version, + * implementation version and (sub-)vendor identification. + * @handle: Instance of SCMI handle to send to clients + * @tx_minfo: Universal Transmit Message management info + * @rx_minfo: Universal Receive Message management info + * @tx_idr: IDR object to map protocol id to Tx channel info pointer + * @rx_idr: IDR object to map protocol id to Rx channel info pointer + * @protocols: IDR for protocols' instance descriptors initialized for + * this SCMI instance: populated on protocol's first attempted + * usage. + * @protocols_imp: List of protocols implemented, currently maximum of + * MAX_PROTOCOLS_IMP elements allocated by the base protocol + * @active_protocols: IDR storing device_nodes for protocols actually defined + * in the DT and confirmed as implemented by fw. + * @node: List head + * @users: Number of users of this instance + */ +struct scmi_info { + struct device_d *dev; + const struct scmi_desc *desc; + struct scmi_revision_info version; + struct scmi_handle handle; + struct scmi_xfers_info tx_minfo; + struct scmi_xfers_info rx_minfo; + struct idr tx_idr; + struct idr rx_idr; + struct idr protocols; + /* Ensure mutual exclusive access to protocols instance array */ + u8 *protocols_imp; + struct idr active_protocols; + struct list_head node; + int users; +}; + +#define handle_to_scmi_info(h) container_of(h, struct scmi_info, handle) + +static const int scmi_linux_errmap[] = { + /* better than switch case as long as return value is continuous */ + 0, /* SCMI_SUCCESS */ + -EOPNOTSUPP, /* SCMI_ERR_SUPPORT */ + -EINVAL, /* SCMI_ERR_PARAM */ + -EACCES, /* SCMI_ERR_ACCESS */ + -ENOENT, /* SCMI_ERR_ENTRY */ + -ERANGE, /* SCMI_ERR_RANGE */ + -EBUSY, /* SCMI_ERR_BUSY */ + -ECOMM, /* SCMI_ERR_COMMS */ + -EIO, /* SCMI_ERR_GENERIC */ + -EREMOTEIO, /* SCMI_ERR_HARDWARE */ + -EPROTO, /* SCMI_ERR_PROTOCOL */ +}; + +static inline int scmi_to_linux_errno(int errno) +{ + if (errno < SCMI_SUCCESS && errno > SCMI_ERR_MAX) + return scmi_linux_errmap[-errno]; + return -EIO; +} + +/** + * scmi_dump_header_dbg() - Helper to dump a message header. + * + * @dev: Device pointer corresponding to the SCMI entity + * @hdr: pointer to header. + */ +static inline void scmi_dump_header_dbg(struct device_d *dev, + struct scmi_msg_hdr *hdr) +{ + dev_dbg(dev, "Message ID: %x Sequence ID: %x Protocol: %x\n", + hdr->id, hdr->seq, hdr->protocol_id); +} + +/** + * scmi_xfer_get() - Allocate one message + * + * @handle: Pointer to SCMI entity handle + * @minfo: Pointer to Tx/Rx Message management info based on channel type + * + * Helper function which is used by various message functions that are + * exposed to clients of this driver for allocating a message traffic event. + * + * This function can sleep depending on pending requests already in the system + * for the SCMI entity. + * + * Return: 0 if all went fine, else corresponding error. + */ +static struct scmi_xfer *scmi_xfer_get(const struct scmi_handle *handle, + struct scmi_xfers_info *minfo) +{ + u16 xfer_id; + struct scmi_xfer *xfer; + unsigned long bit_pos; + struct scmi_info *info = handle_to_scmi_info(handle); + + bit_pos = find_first_zero_bit(minfo->xfer_alloc_table, + info->desc->max_msg); + if (bit_pos == info->desc->max_msg) + return ERR_PTR(-ENOMEM); + set_bit(bit_pos, minfo->xfer_alloc_table); + + xfer_id = bit_pos; + + xfer = &minfo->xfer_block[xfer_id]; + xfer->hdr.seq = xfer_id; + xfer->done = false; + xfer->transfer_id = ++transfer_last_id; + + return xfer; +} + +/** + * __scmi_xfer_put() - Release a message + * + * @minfo: Pointer to Tx/Rx Message management info based on channel type + * @xfer: message that was reserved by scmi_xfer_get + */ +static void +__scmi_xfer_put(struct scmi_xfers_info *minfo, struct scmi_xfer *xfer) +{ + clear_bit(xfer->hdr.seq, minfo->xfer_alloc_table); +} + +static void scmi_handle_response(struct scmi_chan_info *cinfo, + u16 xfer_id, u8 msg_type) +{ + struct scmi_xfer *xfer; + struct device_d *dev = cinfo->dev; + struct scmi_info *info = handle_to_scmi_info(cinfo->handle); + struct scmi_xfers_info *minfo = &info->tx_minfo; + + /* Are we even expecting this? */ + if (!test_bit(xfer_id, minfo->xfer_alloc_table)) { + dev_err(dev, "message for %d is not expected!\n", xfer_id); + info->desc->ops->clear_channel(cinfo); + return; + } + + xfer = &minfo->xfer_block[xfer_id]; + /* + * Even if a response was indeed expected on this slot at this point, + * a buggy platform could wrongly reply feeding us an unexpected + * delayed response we're not prepared to handle: bail-out safely + * blaming firmware. + */ + if (unlikely(msg_type == MSG_TYPE_DELAYED_RESP && !xfer->async_done)) { + dev_err(dev, + "Delayed Response for %d not expected! Buggy F/W ?\n", + xfer_id); + info->desc->ops->clear_channel(cinfo); + /* It was unexpected, so nobody will clear the xfer if not us */ + __scmi_xfer_put(minfo, xfer); + return; + } + + scmi_dump_header_dbg(dev, &xfer->hdr); + + info->desc->ops->fetch_response(cinfo, xfer); + + if (msg_type == MSG_TYPE_DELAYED_RESP) { + info->desc->ops->clear_channel(cinfo); + *xfer->async_done = true; + } else { + xfer->done = true; + } +} + +/** + * scmi_rx_callback() - callback for receiving messages + * + * @cinfo: SCMI channel info + * @msg_hdr: Message header + * + * Processes one received message to appropriate transfer information and + * signals completion of the transfer. + * + * NOTE: This function will be invoked in IRQ context, hence should be + * as optimal as possible. + */ +void scmi_rx_callback(struct scmi_chan_info *cinfo, u32 msg_hdr) +{ + u16 xfer_id = MSG_XTRACT_TOKEN(msg_hdr); + u8 msg_type = MSG_XTRACT_TYPE(msg_hdr); + + switch (msg_type) { + case MSG_TYPE_COMMAND: + case MSG_TYPE_DELAYED_RESP: + scmi_handle_response(cinfo, xfer_id, msg_type); + break; + default: + WARN_ONCE(1, "received unknown msg_type:%d\n", msg_type); + break; + } +} + +/** + * xfer_put() - Release a transmit message + * + * @ph: Pointer to SCMI protocol handle + * @xfer: message that was reserved by scmi_xfer_get + */ +static void xfer_put(const struct scmi_protocol_handle *ph, + struct scmi_xfer *xfer) +{ + const struct scmi_protocol_instance *pi = ph_to_pi(ph); + struct scmi_info *info = handle_to_scmi_info(pi->handle); + + __scmi_xfer_put(&info->tx_minfo, xfer); +} + +/** + * do_xfer() - Do one transfer + * + * @ph: Pointer to SCMI protocol handle + * @xfer: Transfer to initiate and wait for response + * + * Return: -ETIMEDOUT in case of no response, if transmit error, + * return corresponding error, else if all goes well, + * return 0. + */ +static int do_xfer(const struct scmi_protocol_handle *ph, + struct scmi_xfer *xfer) +{ + int ret; + const struct scmi_protocol_instance *pi = ph_to_pi(ph); + struct scmi_info *info = handle_to_scmi_info(pi->handle); + struct device_d *dev = info->dev; + struct scmi_chan_info *cinfo; + u64 start; + + /* + * Re-instate protocol id here from protocol handle so that cannot be + * overridden by mistake (or malice) by the protocol code mangling with + * the scmi_xfer structure. + */ + xfer->hdr.protocol_id = pi->proto->id; + + cinfo = idr_find(&info->tx_idr, xfer->hdr.protocol_id); + if (unlikely(!cinfo)) + return -EINVAL; + + ret = info->desc->ops->send_message(cinfo, xfer); + if (ret < 0) { + dev_dbg(dev, "Failed to send message %d\n", ret); + return ret; + } + + /* And we wait for the response. */ + start = get_time_ns(); + while (!xfer->done) { + if (is_timeout(start, info->desc->max_rx_timeout_ms * (u64)NSEC_PER_MSEC)) { + dev_err(dev, "timed out in resp(caller: %pS)\n", (void *)_RET_IP_); + ret = -ETIMEDOUT; + break; + } + } + + if (!ret && xfer->hdr.status) + ret = scmi_to_linux_errno(xfer->hdr.status); + + if (info->desc->ops->mark_txdone) + info->desc->ops->mark_txdone(cinfo, ret); + + return ret; +} + +static void reset_rx_to_maxsz(const struct scmi_protocol_handle *ph, + struct scmi_xfer *xfer) +{ + const struct scmi_protocol_instance *pi = ph_to_pi(ph); + struct scmi_info *info = handle_to_scmi_info(pi->handle); + + xfer->rx.len = info->desc->max_msg_size; +} + +#define SCMI_MAX_RESPONSE_TIMEOUT_NS (2 * NSEC_PER_SEC) + +/** + * do_xfer_with_response() - Do one transfer and wait until the delayed + * response is received + * + * @ph: Pointer to SCMI protocol handle + * @xfer: Transfer to initiate and wait for response + * + * Return: -ETIMEDOUT in case of no delayed response, if transmit error, + * return corresponding error, else if all goes well, return 0. + */ +static int do_xfer_with_response(const struct scmi_protocol_handle *ph, + struct scmi_xfer *xfer) +{ + int ret; + const struct scmi_protocol_instance *pi = ph_to_pi(ph); + bool async_response = false; + u64 start; + + xfer->hdr.protocol_id = pi->proto->id; + + xfer->async_done = &async_response; + + ret = do_xfer(ph, xfer); + if (ret) + goto out; + + start = get_time_ns(); + while (!*xfer->async_done) { + if (is_timeout(start, SCMI_MAX_RESPONSE_TIMEOUT_NS)) { + ret = -ETIMEDOUT; + break; + } + } + +out: + xfer->async_done = NULL; + return ret; +} + +/** + * xfer_get_init() - Allocate and initialise one message for transmit + * + * @ph: Pointer to SCMI protocol handle + * @msg_id: Message identifier + * @tx_size: transmit message size + * @rx_size: receive message size + * @p: pointer to the allocated and initialised message + * + * This function allocates the message using @scmi_xfer_get and + * initialise the header. + * + * Return: 0 if all went fine with @p pointing to message, else + * corresponding error. + */ +static int xfer_get_init(const struct scmi_protocol_handle *ph, + u8 msg_id, size_t tx_size, size_t rx_size, + struct scmi_xfer **p) +{ + int ret; + struct scmi_xfer *xfer; + const struct scmi_protocol_instance *pi = ph_to_pi(ph); + struct scmi_info *info = handle_to_scmi_info(pi->handle); + struct scmi_xfers_info *minfo = &info->tx_minfo; + struct device_d *dev = info->dev; + + /* Ensure we have sane transfer sizes */ + if (rx_size > info->desc->max_msg_size || + tx_size > info->desc->max_msg_size) + return -ERANGE; + + xfer = scmi_xfer_get(pi->handle, minfo); + if (IS_ERR(xfer)) { + ret = PTR_ERR(xfer); + dev_err(dev, "failed to get free message slot(%d)\n", ret); + return ret; + } + + xfer->tx.len = tx_size; + xfer->rx.len = rx_size ? : info->desc->max_msg_size; + xfer->hdr.id = msg_id; + xfer->hdr.protocol_id = pi->proto->id; + xfer->hdr.poll_completion = false; + + *p = xfer; + + return 0; +} + +/** + * version_get() - command to get the revision of the SCMI entity + * + * @ph: Pointer to SCMI protocol handle + * @version: Holds returned version of protocol. + * + * Updates the SCMI information in the internal data structure. + * + * Return: 0 if all went fine, else return appropriate error. + */ +static int version_get(const struct scmi_protocol_handle *ph, u32 *version) +{ + int ret; + __le32 *rev_info; + struct scmi_xfer *t; + + ret = xfer_get_init(ph, PROTOCOL_VERSION, 0, sizeof(*version), &t); + if (ret) + return ret; + + ret = do_xfer(ph, t); + if (!ret) { + rev_info = t->rx.buf; + *version = le32_to_cpu(*rev_info); + } + + xfer_put(ph, t); + return ret; +} + +/** + * scmi_set_protocol_priv - Set protocol specific data at init time + * + * @ph: A reference to the protocol handle. + * @priv: The private data to set. + * + * Return: 0 on Success + */ +static int scmi_set_protocol_priv(const struct scmi_protocol_handle *ph, + void *priv) +{ + struct scmi_protocol_instance *pi = ph_to_pi(ph); + + pi->priv = priv; + + return 0; +} + +/** + * scmi_get_protocol_priv - Set protocol specific data at init time + * + * @ph: A reference to the protocol handle. + * + * Return: Protocol private data if any was set. + */ +static void *scmi_get_protocol_priv(const struct scmi_protocol_handle *ph) +{ + const struct scmi_protocol_instance *pi = ph_to_pi(ph); + + return pi->priv; +} + +static const struct scmi_xfer_ops xfer_ops = { + .version_get = version_get, + .xfer_get_init = xfer_get_init, + .reset_rx_to_maxsz = reset_rx_to_maxsz, + .do_xfer = do_xfer, + .do_xfer_with_response = do_xfer_with_response, + .xfer_put = xfer_put, +}; + +/** + * scmi_revision_area_get - Retrieve version memory area. + * + * @ph: A reference to the protocol handle. + * + * A helper to grab the version memory area reference during SCMI Base protocol + * initialization. + * + * Return: A reference to the version memory area associated to the SCMI + * instance underlying this protocol handle. + */ +struct scmi_revision_info * +scmi_revision_area_get(const struct scmi_protocol_handle *ph) +{ + const struct scmi_protocol_instance *pi = ph_to_pi(ph); + + return pi->handle->version; +} + +/** + * scmi_alloc_init_protocol_instance - Allocate and initialize a protocol + * instance descriptor. + * @info: The reference to the related SCMI instance. + * @proto: The protocol descriptor. + * + * Allocate a new protocol instance descriptor, using the provided @proto + * description, against the specified SCMI instance @info, and initialize it; + * + * Return: A reference to a freshly allocated and initialized protocol instance + * or ERR_PTR on failure. On failure the @proto reference is at first + */ +static struct scmi_protocol_instance * +scmi_alloc_init_protocol_instance(struct scmi_info *info, + const struct scmi_protocol *proto) +{ + int ret = -ENOMEM; + struct scmi_protocol_instance *pi; + const struct scmi_handle *handle = &info->handle; + + pi = kzalloc(sizeof(*pi), GFP_KERNEL); + if (!pi) + goto clean; + + pi->proto = proto; + pi->handle = handle; + pi->ph.dev = handle->dev; + pi->ph.xops = &xfer_ops; + pi->ph.set_priv = scmi_set_protocol_priv; + pi->ph.get_priv = scmi_get_protocol_priv; + pi->users++; + /* proto->init is assured NON NULL by scmi_protocol_register */ + ret = pi->proto->instance_init(&pi->ph); + if (ret) + goto clean; + + ret = idr_alloc_one(&info->protocols, pi, proto->id); + if (ret != proto->id) + goto clean; + + dev_dbg(handle->dev, "Initialized protocol: 0x%X\n", pi->proto->id); + + return pi; + +clean: + return ERR_PTR(ret); +} + +/** + * scmi_get_protocol_instance - Protocol initialization helper. + * @handle: A reference to the SCMI platform instance. + * @protocol_id: The protocol being requested. + * + * In case the required protocol has never been requested before for this + * instance, allocate and initialize all the needed structures. + * + * Return: A reference to an initialized protocol instance or error on failure: + * in particular returns -EPROBE_DEFER when the desired protocol could + * NOT be found. + */ +static struct scmi_protocol_instance * __must_check +scmi_get_protocol_instance(const struct scmi_handle *handle, u8 protocol_id) +{ + struct scmi_protocol_instance *pi; + struct scmi_info *info = handle_to_scmi_info(handle); + + pi = idr_find(&info->protocols, protocol_id); + + if (pi) { + pi->users++; + } else { + const struct scmi_protocol *proto; + + /* Fails if protocol not registered on bus */ + proto = scmi_protocol_get(protocol_id); + if (proto) + pi = scmi_alloc_init_protocol_instance(info, proto); + else + pi = ERR_PTR(-EPROBE_DEFER); + } + + return pi; +} + +/** + * scmi_protocol_acquire - Protocol acquire + * @handle: A reference to the SCMI platform instance. + * @protocol_id: The protocol being requested. + * + * Register a new user for the requested protocol on the specified SCMI + * platform instance, possibly triggering its initialization on first user. + * + * Return: 0 if protocol was acquired successfully. + */ +int scmi_protocol_acquire(const struct scmi_handle *handle, u8 protocol_id) +{ + return PTR_ERR_OR_ZERO(scmi_get_protocol_instance(handle, protocol_id)); +} + +void scmi_setup_protocol_implemented(const struct scmi_protocol_handle *ph, + u8 *prot_imp) +{ + const struct scmi_protocol_instance *pi = ph_to_pi(ph); + struct scmi_info *info = handle_to_scmi_info(pi->handle); + + info->protocols_imp = prot_imp; +} + +static bool +scmi_is_protocol_implemented(const struct scmi_handle *handle, u8 prot_id) +{ + int i; + struct scmi_info *info = handle_to_scmi_info(handle); + + if (!info->protocols_imp) + return false; + + for (i = 0; i < MAX_PROTOCOLS_IMP; i++) + if (info->protocols_imp[i] == prot_id) + return true; + return false; +} + +/** + * scmi_dev_protocol_get - get protocol operations and handle + * @protocol_id: The protocol being requested. + * @ph: A pointer reference used to pass back the associated protocol handle. + * + * Get hold of a protocol accounting for its usage, eventually triggering its + * initialization, and returning the protocol specific operations and related + * protocol handle which will be used as first argument in most of the + * protocols operations methods. + * Being a devres based managed method, protocol hold will be automatically + * released, and possibly de-initialized on last user, once the SCMI driver + * owning the scmi_device is unbound from it. + * + * Return: A reference to the requested protocol operations or error. + * Must be checked for errors by caller. + */ +static const void __must_check * +scmi_dev_protocol_get(struct scmi_device *sdev, u8 protocol_id, + struct scmi_protocol_handle **ph) +{ + struct scmi_protocol_instance *pi; + struct scmi_handle *handle = sdev->handle; + + if (!ph) + return ERR_PTR(-EINVAL); + + pi = scmi_get_protocol_instance(handle, protocol_id); + if (IS_ERR(pi)) + return pi; + + *ph = &pi->ph; + + return pi->proto->ops; +} + +static inline +struct scmi_handle *scmi_handle_get_from_info_unlocked(struct scmi_info *info) +{ + info->users++; + return &info->handle; +} + +/** + * scmi_handle_get() - Get the SCMI handle for a device + * + * @dev: pointer to device for which we want SCMI handle + * + * NOTE: The function does not track individual clients of the framework + * and is expected to be maintained by caller of SCMI protocol library. + * scmi_handle_put must be balanced with successful scmi_handle_get + * + * Return: pointer to handle if successful, NULL on error + */ +struct scmi_handle *scmi_handle_get(struct device_d *dev) +{ + struct list_head *p; + struct scmi_info *info; + struct scmi_handle *handle = NULL; + + list_for_each(p, &scmi_list) { + info = list_entry(p, struct scmi_info, node); + if (dev->parent == info->dev) { + handle = scmi_handle_get_from_info_unlocked(info); + break; + } + } + + return handle; +} + +/** + * scmi_handle_put() - Release the handle acquired by scmi_handle_get + * + * @handle: handle acquired by scmi_handle_get + * + * NOTE: The function does not track individual clients of the framework + * and is expected to be maintained by caller of SCMI protocol library. + * scmi_handle_put must be balanced with successful scmi_handle_get + * + * Return: 0 is successfully released + * if null was passed, it returns -EINVAL; + */ +int scmi_handle_put(const struct scmi_handle *handle) +{ + struct scmi_info *info; + + if (!handle) + return -EINVAL; + + info = handle_to_scmi_info(handle); + if (!WARN_ON(!info->users)) + info->users--; + + return 0; +} + +static int __scmi_xfer_info_init(struct scmi_info *sinfo, + struct scmi_xfers_info *info) +{ + int i; + struct scmi_xfer *xfer; + struct device_d *dev = sinfo->dev; + const struct scmi_desc *desc = sinfo->desc; + + /* Pre-allocated messages, no more than what hdr.seq can support */ + if (WARN_ON(desc->max_msg >= MSG_TOKEN_MAX)) { + dev_err(dev, "Maximum message of %d exceeds supported %ld\n", + desc->max_msg, MSG_TOKEN_MAX); + return -EINVAL; + } + + info->xfer_block = kcalloc(desc->max_msg, sizeof(*info->xfer_block), GFP_KERNEL); + if (!info->xfer_block) + return -ENOMEM; + + info->xfer_alloc_table = kcalloc(BITS_TO_LONGS(desc->max_msg), + sizeof(long), GFP_KERNEL); + if (!info->xfer_alloc_table) + return -ENOMEM; + + /* Pre-initialize the buffer pointer to pre-allocated buffers */ + for (i = 0, xfer = info->xfer_block; i < desc->max_msg; i++, xfer++) { + xfer->rx.buf = kcalloc(sizeof(u8), desc->max_msg_size, + GFP_KERNEL); + if (!xfer->rx.buf) + return -ENOMEM; + + xfer->tx.buf = xfer->rx.buf; + xfer->done = false; + } + + return 0; +} + +static int scmi_xfer_info_init(struct scmi_info *sinfo) +{ + int ret = __scmi_xfer_info_init(sinfo, &sinfo->tx_minfo); + + if (!ret && idr_find(&sinfo->rx_idr, SCMI_PROTOCOL_BASE)) + ret = __scmi_xfer_info_init(sinfo, &sinfo->rx_minfo); + + return ret; +} + +static int scmi_chan_setup(struct scmi_info *info, struct device_d *dev, + int prot_id, bool tx) +{ + int ret, idx; + struct scmi_chan_info *cinfo; + struct idr *idr; + + /* Transmit channel is first entry i.e. index 0 */ + idx = tx ? 0 : 1; + idr = tx ? &info->tx_idr : &info->rx_idr; + + /* check if already allocated, used for multiple device per protocol */ + cinfo = idr_find(idr, prot_id); + if (cinfo) + return 0; + + if (!info->desc->ops->chan_available(dev, idx)) { + cinfo = idr_find(idr, SCMI_PROTOCOL_BASE); + if (unlikely(!cinfo)) /* Possible only if platform has no Rx */ + return -EINVAL; + goto idr_alloc; + } + + cinfo = kzalloc(sizeof(*cinfo), GFP_KERNEL); + if (!cinfo) + return -ENOMEM; + + cinfo->dev = dev; + + ret = info->desc->ops->chan_setup(cinfo, info->dev, tx); + if (ret) + return ret; + +idr_alloc: + ret = idr_alloc_one(idr, cinfo, prot_id); + if (ret != prot_id) { + dev_err(dev, "unable to allocate SCMI idr slot err %d\n", ret); + return ret; + } + + cinfo->handle = &info->handle; + return 0; +} + +static inline int +scmi_txrx_setup(struct scmi_info *info, struct device_d *dev, int prot_id) +{ + int ret = scmi_chan_setup(info, dev, prot_id, true); + + if (!ret) /* Rx is optional, hence no error check */ + scmi_chan_setup(info, dev, prot_id, false); + + return ret; +} + +/** + * scmi_get_protocol_device - Helper to get/create an SCMI device. + * + * @np: A device node representing a valid active protocols for the referred + * SCMI instance. + * @info: The referred SCMI instance for which we are getting/creating this + * device. + * @prot_id: The protocol ID. + * @name: The device name. + * + * Referring to the specific SCMI instance identified by @info, this helper + * takes care to return a properly initialized device matching the requested + * @proto_id and @name: if device was still not existent it is created as a + * child of the specified SCMI instance @info and its transport properly + * initialized as usual. + */ +static inline struct scmi_device * +scmi_get_protocol_device(struct device_node *np, struct scmi_info *info, + int prot_id, const char *name) +{ + struct scmi_device *sdev; + + /* Already created for this parent SCMI instance ? */ + sdev = scmi_child_dev_find(info->dev, prot_id, name); + if (sdev) + return sdev; + + pr_debug("Creating SCMI device (%s) for protocol %x\n", name, prot_id); + + sdev = scmi_device_alloc(np, info->dev, prot_id, name); + if (!sdev) { + dev_err(info->dev, "failed to create %d protocol device\n", + prot_id); + return NULL; + } + + if (scmi_txrx_setup(info, &sdev->dev, prot_id)) { + dev_err(&sdev->dev, "failed to setup transport\n"); + scmi_device_destroy(sdev); + return NULL; + } + + return sdev; +} + +static inline void +scmi_create_protocol_device(struct device_node *np, struct scmi_info *info, + int prot_id, const char *name) +{ + struct scmi_device *sdev; + + sdev = scmi_get_protocol_device(np, info, prot_id, name); + if (!sdev) + return; + + /* setup handle now as the transport is ready */ + scmi_set_handle(sdev); + + /* Register if not done yet */ + if (sdev->dev.id == DEVICE_ID_DYNAMIC) + register_device(&sdev->dev); +} + +/** + * scmi_create_protocol_devices - Create devices for all pending requests for + * this SCMI instance. + * + * @np: The device node describing the protocol + * @info: The SCMI instance descriptor + * @prot_id: The protocol ID + * + * All devices previously requested for this instance (if any) are found and + * created by scanning the proper @&scmi_requested_devices entry. + */ +static void scmi_create_protocol_devices(struct device_node *np, + struct scmi_info *info, int prot_id) +{ + struct list_head *phead; + + phead = idr_find(&scmi_requested_devices, prot_id); + if (phead) { + struct scmi_requested_dev *rdev; + + list_for_each_entry(rdev, phead, node) + scmi_create_protocol_device(np, info, prot_id, + rdev->id_table->name); + } +} + +/** + * scmi_protocol_device_request - Helper to request a device + * + * @id_table: A protocol/name pair descriptor for the device to be created. + * + * This helper let an SCMI driver request specific devices identified by the + * @id_table to be created for each active SCMI instance. + * + * The requested device name MUST NOT be already existent for any protocol; + * at first the freshly requested @id_table is annotated in the IDR table + * @scmi_requested_devices, then a matching device is created for each already + * active SCMI instance. (if any) + * + * This way the requested device is created straight-away for all the already + * initialized(probed) SCMI instances (handles) and it remains also annotated + * as pending creation if the requesting SCMI driver was loaded before some + * SCMI instance and related transports were available: when such late instance + * is probed, its probe will take care to scan the list of pending requested + * devices and create those on its own (see @scmi_create_protocol_devices and + * its enclosing loop) + * + * Return: 0 on Success + */ +int scmi_protocol_device_request(const struct scmi_device_id *id_table) +{ + int ret = 0; + struct list_head *phead = NULL; + struct scmi_requested_dev *rdev; + struct scmi_info *info; + struct idr *idr; + + pr_debug("Requesting SCMI device (%s) for protocol 0x%x\n", + id_table->name, id_table->protocol_id); + + /* + * Search for the matching protocol rdev list and then search + * of any existent equally named device...fails if any duplicate found. + */ + __idr_for_each_entry(&scmi_requested_devices, idr) { + struct list_head *head = idr->ptr; + if (!phead) { + /* A list found registered in the IDR is never empty */ + rdev = list_first_entry(head, struct scmi_requested_dev, + node); + if (rdev->id_table->protocol_id == + id_table->protocol_id) + phead = head; + } + list_for_each_entry(rdev, head, node) { + if (!strcmp(rdev->id_table->name, id_table->name)) { + pr_err("Ignoring duplicate request [%d] %s\n", + rdev->id_table->protocol_id, + rdev->id_table->name); + ret = -EINVAL; + goto out; + } + } + } + + /* + * No duplicate found for requested id_table, so let's create a new + * requested device entry for this new valid request. + */ + rdev = kzalloc(sizeof(*rdev), GFP_KERNEL); + if (!rdev) { + ret = -ENOMEM; + goto out; + } + rdev->id_table = id_table; + + /* + * Append the new requested device table descriptor to the head of the + * related protocol list, eventually creating such head if not already + * there. + */ + if (!phead) { + phead = kzalloc(sizeof(*phead), GFP_KERNEL); + if (!phead) { + kfree(rdev); + ret = -ENOMEM; + goto out; + } + INIT_LIST_HEAD(phead); + + ret = idr_alloc_one(&scmi_requested_devices, (void *)phead, + id_table->protocol_id); + if (ret != id_table->protocol_id) { + pr_err("Failed to save SCMI device - ret:%d\n", ret); + kfree(rdev); + kfree(phead); + ret = -EINVAL; + goto out; + } + ret = 0; + } + list_add(&rdev->node, phead); + + /* + * Now effectively create and initialize the requested device for every + * already initialized SCMI instance which has registered the requested + * protocol as a valid active one: i.e. defined in DT and supported by + * current platform FW. + */ + list_for_each_entry(info, &scmi_list, node) { + struct device_node *child; + + child = idr_find(&info->active_protocols, + id_table->protocol_id); + if (child) { + struct scmi_device *sdev; + + sdev = scmi_get_protocol_device(child, info, + id_table->protocol_id, + id_table->name); + /* Set handle if not already set: device existed */ + if (sdev && !sdev->handle) + sdev->handle = + scmi_handle_get_from_info_unlocked(info); + } else { + dev_err(info->dev, + "Failed. SCMI protocol %d not active.\n", + id_table->protocol_id); + } + } + +out: + return ret; +} + +/** + * scmi_protocol_device_unrequest - Helper to unrequest a device + * + * @id_table: A protocol/name pair descriptor for the device to be unrequested. + * + * An helper to let an SCMI driver release its request about devices; note that + * devices are created and initialized once the first SCMI driver request them + * but they destroyed only on SCMI core unloading/unbinding. + * + * The current SCMI transport layer uses such devices as internal references and + * as such they could be shared as same transport between multiple drivers so + * that cannot be safely destroyed till the whole SCMI stack is removed. + * (unless adding further burden of refcounting.) + */ +void scmi_protocol_device_unrequest(const struct scmi_device_id *id_table) +{ + struct list_head *phead; + + pr_debug("Unrequesting SCMI device (%s) for protocol %x\n", + id_table->name, id_table->protocol_id); + + phead = idr_find(&scmi_requested_devices, id_table->protocol_id); + if (phead) { + struct scmi_requested_dev *victim, *tmp; + + list_for_each_entry_safe(victim, tmp, phead, node) { + if (!strcmp(victim->id_table->name, id_table->name)) { + list_del(&victim->node); + kfree(victim); + break; + } + } + + if (list_empty(phead)) { + idr_remove(&scmi_requested_devices, + id_table->protocol_id); + kfree(phead); + } + } +} + +static void version_info(struct device_d *dev) +{ + struct scmi_info *info = dev->priv; + + printf("SCMI information:\n" + " version: %u.%u\n" + " firmware version: 0x%x\n" + " vendor: %s (sub: %s)\n", + info->version.minor_ver, + info->version.major_ver, + info->version.impl_ver, + info->version.vendor_id, + info->version.sub_vendor_id); +} + +static int scmi_probe(struct device_d *dev) +{ + int ret; + struct scmi_handle *handle; + const struct scmi_desc *desc; + struct scmi_info *info; + struct device_node *child, *np = dev->device_node; + + desc = of_device_get_match_data(dev); + if (!desc) + return -EINVAL; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->dev = dev; + info->desc = desc; + INIT_LIST_HEAD(&info->node); + idr_init(&info->protocols); + idr_init(&info->active_protocols); + + dev->priv = info; + idr_init(&info->tx_idr); + idr_init(&info->rx_idr); + + handle = &info->handle; + handle->dev = info->dev; + handle->version = &info->version; + handle->protocol_get = scmi_dev_protocol_get; + + ret = scmi_txrx_setup(info, dev, SCMI_PROTOCOL_BASE); + if (ret) + return ret; + + ret = scmi_xfer_info_init(info); + if (ret) + return ret; + + /* + * Trigger SCMI Base protocol initialization. + * It's mandatory and won't be ever released/deinit until the + * SCMI stack is shutdown/unloaded as a whole. + */ + ret = scmi_protocol_acquire(handle, SCMI_PROTOCOL_BASE); + if (ret) { + dev_err(dev, "unable to communicate with SCMI\n"); + return ret; + } + + list_add_tail(&info->node, &scmi_list); + + for_each_available_child_of_node(np, child) { + u32 prot_id; + + if (of_property_read_u32(child, "reg", &prot_id)) + continue; + + if (!FIELD_FIT(MSG_PROTOCOL_ID_MASK, prot_id)) + dev_err(dev, "Out of range protocol %d\n", prot_id); + + if (!scmi_is_protocol_implemented(handle, prot_id)) { + dev_err(dev, "SCMI protocol %d not implemented\n", + prot_id); + continue; + } + + /* + * Save this valid DT protocol descriptor amongst + * @active_protocols for this SCMI instance/ + */ + ret = idr_alloc_one(&info->active_protocols, child, prot_id); + if (ret != prot_id) { + dev_err(dev, "SCMI protocol %d already activated. Skip\n", + prot_id); + continue; + } + + scmi_create_protocol_devices(child, info, prot_id); + } + + dev->info = version_info; + + return 0; +} + +void scmi_free_channel(struct scmi_chan_info *cinfo, struct idr *idr, int id) +{ + idr_remove(idr, id); +} + +/* Each compatible listed below must have descriptor associated with it */ +static const struct of_device_id scmi_of_match[] = { +#ifdef CONFIG_ARM_SMCCC + { .compatible = "arm,scmi-smc", .data = &scmi_smc_desc}, +#endif + { /* Sentinel */ }, +}; + +static struct driver_d scmi_driver = { + .name = "arm-scmi", + .of_compatible = scmi_of_match, + .probe = scmi_probe, +}; + +static int __init scmi_bus_driver_init(void) +{ + scmi_bus_init(); + + scmi_base_register(); + + return 0; +} +pure_initcall(scmi_bus_driver_init); + +static int __init scmi_platform_driver_init(void) +{ + return platform_driver_register(&scmi_driver); +} +core_initcall(scmi_platform_driver_init); + +MODULE_ALIAS("platform: arm-scmi"); +MODULE_AUTHOR("Sudeep Holla "); +MODULE_DESCRIPTION("ARM SCMI protocol driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/firmware/arm_scmi/shmem.c b/drivers/firmware/arm_scmi/shmem.c new file mode 100644 index 0000000000..2dde2b6e09 --- /dev/null +++ b/drivers/firmware/arm_scmi/shmem.c @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * For transport using shared mem structure. + * + * Copyright (C) 2019 ARM Ltd. + */ + +#include +#include +#include +#include + +#include "common.h" + +/* + * SCMI specification requires all parameters, message headers, return + * arguments or any protocol data to be expressed in little endian + * format only. + */ +struct scmi_shared_mem { + __le32 reserved; + __le32 channel_status; +#define SCMI_SHMEM_CHAN_STAT_CHANNEL_ERROR BIT(1) +#define SCMI_SHMEM_CHAN_STAT_CHANNEL_FREE BIT(0) + __le32 reserved1[2]; + __le32 flags; +#define SCMI_SHMEM_FLAG_INTR_ENABLED BIT(0) + __le32 length; + __le32 msg_header; + u8 msg_payload[]; +}; + +void shmem_tx_prepare(struct scmi_shared_mem __iomem *shmem, + struct scmi_xfer *xfer) +{ + /* + * Ideally channel must be free by now unless OS timeout last + * request and platform continued to process the same, wait + * until it releases the shared memory, otherwise we may endup + * overwriting its response with new message payload or vice-versa + */ + spin_until_cond(ioread32(&shmem->channel_status) & + SCMI_SHMEM_CHAN_STAT_CHANNEL_FREE); + /* Mark channel busy + clear error */ + iowrite32(0x0, &shmem->channel_status); + iowrite32(xfer->hdr.poll_completion ? 0 : SCMI_SHMEM_FLAG_INTR_ENABLED, + &shmem->flags); + iowrite32(sizeof(shmem->msg_header) + xfer->tx.len, &shmem->length); + iowrite32(pack_scmi_header(&xfer->hdr), &shmem->msg_header); + if (xfer->tx.buf) + memcpy_toio(shmem->msg_payload, xfer->tx.buf, xfer->tx.len); +} + +u32 shmem_read_header(struct scmi_shared_mem __iomem *shmem) +{ + return ioread32(&shmem->msg_header); +} + +void shmem_fetch_response(struct scmi_shared_mem __iomem *shmem, + struct scmi_xfer *xfer) +{ + xfer->hdr.status = ioread32(shmem->msg_payload); + /* Skip the length of header and status in shmem area i.e 8 bytes */ + xfer->rx.len = min_t(size_t, xfer->rx.len, + ioread32(&shmem->length) - 8); + + /* Take a copy to the rx buffer.. */ + memcpy_fromio(xfer->rx.buf, shmem->msg_payload + 4, xfer->rx.len); +} + +void shmem_clear_channel(struct scmi_shared_mem __iomem *shmem) +{ + iowrite32(SCMI_SHMEM_CHAN_STAT_CHANNEL_FREE, &shmem->channel_status); +} + +bool shmem_poll_done(struct scmi_shared_mem __iomem *shmem, + struct scmi_xfer *xfer) +{ + u16 xfer_id; + + xfer_id = MSG_XTRACT_TOKEN(ioread32(&shmem->msg_header)); + + if (xfer->hdr.seq != xfer_id) + return false; + + return ioread32(&shmem->channel_status) & + (SCMI_SHMEM_CHAN_STAT_CHANNEL_ERROR | + SCMI_SHMEM_CHAN_STAT_CHANNEL_FREE); +} diff --git a/drivers/firmware/arm_scmi/smc.c b/drivers/firmware/arm_scmi/smc.c new file mode 100644 index 0000000000..67f19a7b43 --- /dev/null +++ b/drivers/firmware/arm_scmi/smc.c @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * System Control and Management Interface (SCMI) Message SMC/HVC + * Transport driver + * + * Copyright 2020 NXP + */ + +#include +#include +#include +#include +#include +#include + +#include "common.h" + +/** + * struct scmi_smc - Structure representing a SCMI smc transport + * + * @cinfo: SCMI channel info + * @shmem: Transmit/Receive shared memory area + * @func_id: smc/hvc call function id + */ + +struct scmi_smc { + struct scmi_chan_info *cinfo; + struct scmi_shared_mem __iomem *shmem; + u32 func_id; +}; + +static bool smc_chan_available(struct device_d *dev, int idx) +{ + return of_parse_phandle(dev->device_node, "shmem", 0) != NULL; +} + +static int smc_chan_setup(struct scmi_chan_info *cinfo, struct device_d *dev, + bool tx) +{ + struct device_d *cdev = cinfo->dev; + struct scmi_smc *scmi_info; + resource_size_t size; + struct resource res; + struct device_node *np; + u32 func_id; + int ret; + + if (!tx) + return -ENODEV; + + scmi_info = kzalloc(sizeof(*scmi_info), GFP_KERNEL); + if (!scmi_info) + return -ENOMEM; + + np = of_parse_phandle(cdev->device_node, "shmem", 0); + ret = of_address_to_resource(np, 0, &res); + if (ret) { + dev_err(cdev, "failed to get SCMI Tx shared memory\n"); + return ret; + } + + size = resource_size(&res); + scmi_info->shmem = IOMEM(res.start); + + ret = of_property_read_u32(dev->device_node, "arm,smc-id", &func_id); + if (ret < 0) + return ret; + + scmi_info->func_id = func_id; + scmi_info->cinfo = cinfo; + cinfo->transport_info = scmi_info; + + return 0; +} + +static int smc_chan_free(int id, void *p, void *data) +{ + struct scmi_chan_info *cinfo = p; + struct scmi_smc *scmi_info = cinfo->transport_info; + + cinfo->transport_info = NULL; + scmi_info->cinfo = NULL; + + scmi_free_channel(cinfo, data, id); + + return 0; +} + +static int smc_send_message(struct scmi_chan_info *cinfo, + struct scmi_xfer *xfer) +{ + struct scmi_smc *scmi_info = cinfo->transport_info; + struct arm_smccc_res res; + + shmem_tx_prepare(scmi_info->shmem, xfer); + + arm_smccc_1_1_invoke(scmi_info->func_id, 0, 0, 0, 0, 0, 0, 0, &res); + + scmi_rx_callback(scmi_info->cinfo, shmem_read_header(scmi_info->shmem)); + + /* Only SMCCC_RET_NOT_SUPPORTED is valid error code */ + if (res.a0) + return -EOPNOTSUPP; + return 0; +} + +static void smc_fetch_response(struct scmi_chan_info *cinfo, + struct scmi_xfer *xfer) +{ + struct scmi_smc *scmi_info = cinfo->transport_info; + + shmem_fetch_response(scmi_info->shmem, xfer); +} + +static bool +smc_poll_done(struct scmi_chan_info *cinfo, struct scmi_xfer *xfer) +{ + struct scmi_smc *scmi_info = cinfo->transport_info; + + return shmem_poll_done(scmi_info->shmem, xfer); +} + +static const struct scmi_transport_ops scmi_smc_ops = { + .chan_available = smc_chan_available, + .chan_setup = smc_chan_setup, + .chan_free = smc_chan_free, + .send_message = smc_send_message, + .fetch_response = smc_fetch_response, + .poll_done = smc_poll_done, +}; + +const struct scmi_desc scmi_smc_desc = { + .ops = &scmi_smc_ops, + .max_rx_timeout_ms = 30, + .max_msg = 20, + .max_msg_size = 128, +}; diff --git a/include/driver.h b/include/driver.h index a787f985a0..e59f0a164d 100644 --- a/include/driver.h +++ b/include/driver.h @@ -595,4 +595,7 @@ const void *device_get_match_data(struct device_d *dev); int device_match_of_modalias(struct device_d *dev, struct driver_d *drv); +struct device_d *device_find_child(struct device_d *parent, void *data, + int (*match)(struct device_d *dev, void *data)); + #endif /* DRIVER_H */ diff --git a/include/linux/idr.h b/include/linux/idr.h new file mode 100644 index 0000000000..8a0f452d76 --- /dev/null +++ b/include/linux/idr.h @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * include/linux/idr.h + * + * 2002-10-18 written by Jim Houston jim.houston@ccur.com + * Copyright (C) 2002 by Concurrent Computer Corporation + * + * Small id to pointer translation service avoiding fixed sized + * tables. + */ + +#ifndef __IDR_H__ +#define __IDR_H__ + +#include +#include + +struct idr { + int id; + void *ptr; + struct list_head list; +}; + +#define DEFINE_IDR(name) \ + struct idr name = { .list = LIST_HEAD_INIT((name).list) } + +#define __idr_for_each_entry(head, idr) \ + list_for_each_entry((idr), &(head)->list, list) + +static inline struct idr *__idr_find(struct idr *head, int id) +{ + struct idr *idr; + + __idr_for_each_entry(head, idr) { + if (idr->id == id) + return idr; + } + + return NULL; +} + +static inline void *idr_find(struct idr *head, int id) +{ + struct idr *idr = __idr_find(head, id); + + return idr ? idr->ptr : NULL; +} + +static inline int idr_alloc_one(struct idr *head, void *ptr, int start) +{ + struct idr *idr; + + if (__idr_find(head, start)) + return -EBUSY; + + idr = malloc(sizeof(*idr)); + + idr->id = start; + idr->ptr = ptr; + + list_add(&idr->list, &head->list); + + return start; +} + +static inline void idr_init(struct idr *idr) +{ + INIT_LIST_HEAD(&idr->list); +} + +static inline void idr_remove(struct idr *head, int id) +{ + struct idr *idr = __idr_find(head, id); + + list_del(&idr->list); + free(idr); +} + +#endif /* __IDR_H__ */ diff --git a/include/linux/processor.h b/include/linux/processor.h new file mode 100644 index 0000000000..94a458c3d1 --- /dev/null +++ b/include/linux/processor.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Misc low level processor primitives */ +#ifndef _LINUX_PROCESSOR_H +#define _LINUX_PROCESSOR_H + +#include + +/* + * spin_until_cond can be used to wait for a condition to become true. It + * may be expected that the first iteration will true in the common case + * (no spinning), so that callers should not require a first "likely" test + * for the uncontended case before using this primitive. + * + * Usage and implementation guidelines are the same as for the spin_begin + * primitives, above. + */ +#ifndef spin_until_cond +#define spin_until_cond(cond) \ +do { \ + if (unlikely(!(cond))) { \ + do { \ + cpu_relax(); \ + } while (!(cond)); \ + } \ +} while (0) + +#endif + +#endif /* _LINUX_PROCESSOR_H */ diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h new file mode 100644 index 0000000000..5b6de7bb87 --- /dev/null +++ b/include/linux/scmi_protocol.h @@ -0,0 +1,654 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * SCMI Message Protocol driver header + * + * Copyright (C) 2018-2021 ARM Ltd. + */ + +#ifndef _LINUX_SCMI_PROTOCOL_H +#define _LINUX_SCMI_PROTOCOL_H + +#include +#include +#include + +#define SCMI_MAX_STR_SIZE 16 +#define SCMI_MAX_NUM_RATES 16 + +/** + * struct scmi_revision_info - version information structure + * + * @major_ver: Major ABI version. Change here implies risk of backward + * compatibility break. + * @minor_ver: Minor ABI version. Change here implies new feature addition, + * or compatible change in ABI. + * @num_protocols: Number of protocols that are implemented, excluding the + * base protocol. + * @num_agents: Number of agents in the system. + * @impl_ver: A vendor-specific implementation version. + * @vendor_id: A vendor identifier(Null terminated ASCII string) + * @sub_vendor_id: A sub-vendor identifier(Null terminated ASCII string) + */ +struct scmi_revision_info { + u16 major_ver; + u16 minor_ver; + u8 num_protocols; + u8 num_agents; + u32 impl_ver; + char vendor_id[SCMI_MAX_STR_SIZE]; + char sub_vendor_id[SCMI_MAX_STR_SIZE]; +}; + +struct scmi_clock_info { + char name[SCMI_MAX_STR_SIZE]; + bool rate_discrete; + union { + struct { + int num_rates; + u64 rates[SCMI_MAX_NUM_RATES]; + } list; + struct { + u64 min_rate; + u64 max_rate; + u64 step_size; + } range; + }; +}; + +struct scmi_handle; +struct scmi_device; +struct scmi_protocol_handle; + +/** + * struct scmi_clk_proto_ops - represents the various operations provided + * by SCMI Clock Protocol + * + * @count_get: get the count of clocks provided by SCMI + * @info_get: get the information of the specified clock + * @rate_get: request the current clock rate of a clock + * @rate_set: set the clock rate of a clock + * @enable: enables the specified clock + * @disable: disables the specified clock + */ +struct scmi_clk_proto_ops { + int (*count_get)(const struct scmi_protocol_handle *ph); + + const struct scmi_clock_info *(*info_get) + (const struct scmi_protocol_handle *ph, u32 clk_id); + int (*rate_get)(const struct scmi_protocol_handle *ph, u32 clk_id, + u64 *rate); + int (*rate_set)(const struct scmi_protocol_handle *ph, u32 clk_id, + u64 rate); + int (*enable)(const struct scmi_protocol_handle *ph, u32 clk_id); + int (*disable)(const struct scmi_protocol_handle *ph, u32 clk_id); +}; + +/** + * struct scmi_perf_proto_ops - represents the various operations provided + * by SCMI Performance Protocol + * + * @limits_set: sets limits on the performance level of a domain + * @limits_get: gets limits on the performance level of a domain + * @level_set: sets the performance level of a domain + * @level_get: gets the performance level of a domain + * @device_domain_id: gets the scmi domain id for a given device_d + * @transition_latency_get: gets the DVFS transition latency for a given device_d + * @device_opps_add: adds all the OPPs for a given device_d + * @freq_set: sets the frequency for a given device_d using sustained frequency + * to sustained performance level mapping + * @freq_get: gets the frequency for a given device_d using sustained frequency + * to sustained performance level mapping + * @est_power_get: gets the estimated power cost for a given performance domain + * at a given frequency + */ +struct scmi_perf_proto_ops { + int (*limits_set)(const struct scmi_protocol_handle *ph, u32 domain, + u32 max_perf, u32 min_perf); + int (*limits_get)(const struct scmi_protocol_handle *ph, u32 domain, + u32 *max_perf, u32 *min_perf); + int (*level_set)(const struct scmi_protocol_handle *ph, u32 domain, + u32 level, bool poll); + int (*level_get)(const struct scmi_protocol_handle *ph, u32 domain, + u32 *level, bool poll); + int (*device_domain_id)(struct device_d *dev); + int (*transition_latency_get)(const struct scmi_protocol_handle *ph, + struct device_d *dev); + int (*device_opps_add)(const struct scmi_protocol_handle *ph, + struct device_d *dev); + int (*freq_set)(const struct scmi_protocol_handle *ph, u32 domain, + unsigned long rate, bool poll); + int (*freq_get)(const struct scmi_protocol_handle *ph, u32 domain, + unsigned long *rate, bool poll); + int (*est_power_get)(const struct scmi_protocol_handle *ph, u32 domain, + unsigned long *rate, unsigned long *power); + bool (*fast_switch_possible)(const struct scmi_protocol_handle *ph, + struct device_d *dev); + bool (*power_scale_mw_get)(const struct scmi_protocol_handle *ph); +}; + +/** + * struct scmi_power_proto_ops - represents the various operations provided + * by SCMI Power Protocol + * + * @num_domains_get: get the count of power domains provided by SCMI + * @name_get: gets the name of a power domain + * @state_set: sets the power state of a power domain + * @state_get: gets the power state of a power domain + */ +struct scmi_power_proto_ops { + int (*num_domains_get)(const struct scmi_protocol_handle *ph); + char *(*name_get)(const struct scmi_protocol_handle *ph, u32 domain); +#define SCMI_POWER_STATE_TYPE_SHIFT 30 +#define SCMI_POWER_STATE_ID_MASK (BIT(28) - 1) +#define SCMI_POWER_STATE_PARAM(type, id) \ + ((((type) & BIT(0)) << SCMI_POWER_STATE_TYPE_SHIFT) | \ + ((id) & SCMI_POWER_STATE_ID_MASK)) +#define SCMI_POWER_STATE_GENERIC_ON SCMI_POWER_STATE_PARAM(0, 0) +#define SCMI_POWER_STATE_GENERIC_OFF SCMI_POWER_STATE_PARAM(1, 0) + int (*state_set)(const struct scmi_protocol_handle *ph, u32 domain, + u32 state); + int (*state_get)(const struct scmi_protocol_handle *ph, u32 domain, + u32 *state); +}; + +/** + * scmi_sensor_reading - represent a timestamped read + * + * Used by @reading_get_timestamped method. + * + * @value: The signed value sensor read. + * @timestamp: An unsigned timestamp for the sensor read, as provided by + * SCMI platform. Set to zero when not available. + */ +struct scmi_sensor_reading { + long long value; + unsigned long long timestamp; +}; + +/** + * scmi_range_attrs - specifies a sensor or axis values' range + * @min_range: The minimum value which can be represented by the sensor/axis. + * @max_range: The maximum value which can be represented by the sensor/axis. + */ +struct scmi_range_attrs { + long long min_range; + long long max_range; +}; + +/** + * scmi_sensor_axis_info - describes one sensor axes + * @id: The axes ID. + * @type: Axes type. Chosen amongst one of @enum scmi_sensor_class. + * @scale: Power-of-10 multiplier applied to the axis unit. + * @name: NULL-terminated string representing axes name as advertised by + * SCMI platform. + * @extended_attrs: Flag to indicate the presence of additional extended + * attributes for this axes. + * @resolution: Extended attribute representing the resolution of the axes. + * Set to 0 if not reported by this axes. + * @exponent: Extended attribute representing the power-of-10 multiplier that + * is applied to the resolution field. Set to 0 if not reported by + * this axes. + * @attrs: Extended attributes representing minimum and maximum values + * measurable by this axes. Set to 0 if not reported by this sensor. + */ +struct scmi_sensor_axis_info { + unsigned int id; + unsigned int type; + int scale; + char name[SCMI_MAX_STR_SIZE]; + bool extended_attrs; + unsigned int resolution; + int exponent; + struct scmi_range_attrs attrs; +}; + +/** + * scmi_sensor_intervals_info - describes number and type of available update + * intervals + * @segmented: Flag for segmented intervals' representation. When True there + * will be exactly 3 intervals in @desc, with each entry + * representing a member of a segment in this order: + * {lowest update interval, highest update interval, step size} + * @count: Number of intervals described in @desc. + * @desc: Array of @count interval descriptor bitmask represented as detailed in + * the SCMI specification: it can be accessed using the accompanying + * macros. + * @prealloc_pool: A minimal preallocated pool of desc entries used to avoid + * lesser-than-64-bytes dynamic allocation for small @count + * values. + */ +struct scmi_sensor_intervals_info { + bool segmented; + unsigned int count; +#define SCMI_SENS_INTVL_SEGMENT_LOW 0 +#define SCMI_SENS_INTVL_SEGMENT_HIGH 1 +#define SCMI_SENS_INTVL_SEGMENT_STEP 2 + unsigned int *desc; +#define SCMI_SENS_INTVL_GET_SECS(x) FIELD_GET(GENMASK(20, 5), (x)) +#define SCMI_SENS_INTVL_GET_EXP(x) \ + ({ \ + int __signed_exp = FIELD_GET(GENMASK(4, 0), (x)); \ + \ + if (__signed_exp & BIT(4)) \ + __signed_exp |= GENMASK(31, 5); \ + __signed_exp; \ + }) +#define SCMI_MAX_PREALLOC_POOL 16 + unsigned int prealloc_pool[SCMI_MAX_PREALLOC_POOL]; +}; + +/** + * struct scmi_sensor_info - represents information related to one of the + * available sensors. + * @id: Sensor ID. + * @type: Sensor type. Chosen amongst one of @enum scmi_sensor_class. + * @scale: Power-of-10 multiplier applied to the sensor unit. + * @num_trip_points: Number of maximum configurable trip points. + * @async: Flag for asynchronous read support. + * @update: Flag for continuouos update notification support. + * @timestamped: Flag for timestamped read support. + * @tstamp_scale: Power-of-10 multiplier applied to the sensor timestamps to + * represent it in seconds. + * @num_axis: Number of supported axis if any. Reported as 0 for scalar sensors. + * @axis: Pointer to an array of @num_axis descriptors. + * @intervals: Descriptor of available update intervals. + * @sensor_config: A bitmask reporting the current sensor configuration as + * detailed in the SCMI specification: it can accessed and + * modified through the accompanying macros. + * @name: NULL-terminated string representing sensor name as advertised by + * SCMI platform. + * @extended_scalar_attrs: Flag to indicate the presence of additional extended + * attributes for this sensor. + * @sensor_power: Extended attribute representing the average power + * consumed by the sensor in microwatts (uW) when it is active. + * Reported here only for scalar sensors. + * Set to 0 if not reported by this sensor. + * @resolution: Extended attribute representing the resolution of the sensor. + * Reported here only for scalar sensors. + * Set to 0 if not reported by this sensor. + * @exponent: Extended attribute representing the power-of-10 multiplier that is + * applied to the resolution field. + * Reported here only for scalar sensors. + * Set to 0 if not reported by this sensor. + * @scalar_attrs: Extended attributes representing minimum and maximum + * measurable values by this sensor. + * Reported here only for scalar sensors. + * Set to 0 if not reported by this sensor. + */ +struct scmi_sensor_info { + unsigned int id; + unsigned int type; + int scale; + unsigned int num_trip_points; + bool async; + bool update; + bool timestamped; + int tstamp_scale; + unsigned int num_axis; + struct scmi_sensor_axis_info *axis; + struct scmi_sensor_intervals_info intervals; + unsigned int sensor_config; +#define SCMI_SENS_CFG_UPDATE_SECS_MASK GENMASK(31, 16) +#define SCMI_SENS_CFG_GET_UPDATE_SECS(x) \ + FIELD_GET(SCMI_SENS_CFG_UPDATE_SECS_MASK, (x)) + +#define SCMI_SENS_CFG_UPDATE_EXP_MASK GENMASK(15, 11) +#define SCMI_SENS_CFG_GET_UPDATE_EXP(x) \ + ({ \ + int __signed_exp = \ + FIELD_GET(SCMI_SENS_CFG_UPDATE_EXP_MASK, (x)); \ + \ + if (__signed_exp & BIT(4)) \ + __signed_exp |= GENMASK(31, 5); \ + __signed_exp; \ + }) + +#define SCMI_SENS_CFG_ROUND_MASK GENMASK(10, 9) +#define SCMI_SENS_CFG_ROUND_AUTO 2 +#define SCMI_SENS_CFG_ROUND_UP 1 +#define SCMI_SENS_CFG_ROUND_DOWN 0 + +#define SCMI_SENS_CFG_TSTAMP_ENABLED_MASK BIT(1) +#define SCMI_SENS_CFG_TSTAMP_ENABLE 1 +#define SCMI_SENS_CFG_TSTAMP_DISABLE 0 +#define SCMI_SENS_CFG_IS_TSTAMP_ENABLED(x) \ + FIELD_GET(SCMI_SENS_CFG_TSTAMP_ENABLED_MASK, (x)) + +#define SCMI_SENS_CFG_SENSOR_ENABLED_MASK BIT(0) +#define SCMI_SENS_CFG_SENSOR_ENABLE 1 +#define SCMI_SENS_CFG_SENSOR_DISABLE 0 + char name[SCMI_MAX_STR_SIZE]; +#define SCMI_SENS_CFG_IS_ENABLED(x) FIELD_GET(BIT(0), (x)) + bool extended_scalar_attrs; + unsigned int sensor_power; + unsigned int resolution; + int exponent; + struct scmi_range_attrs scalar_attrs; +}; + +/* + * Partial list from Distributed Management Task Force (DMTF) specification: + * DSP0249 (Platform Level Data Model specification) + */ +enum scmi_sensor_class { + NONE = 0x0, + UNSPEC = 0x1, + TEMPERATURE_C = 0x2, + TEMPERATURE_F = 0x3, + TEMPERATURE_K = 0x4, + VOLTAGE = 0x5, + CURRENT = 0x6, + POWER = 0x7, + ENERGY = 0x8, + CHARGE = 0x9, + VOLTAMPERE = 0xA, + NITS = 0xB, + LUMENS = 0xC, + LUX = 0xD, + CANDELAS = 0xE, + KPA = 0xF, + PSI = 0x10, + NEWTON = 0x11, + CFM = 0x12, + RPM = 0x13, + HERTZ = 0x14, + SECS = 0x15, + MINS = 0x16, + HOURS = 0x17, + DAYS = 0x18, + WEEKS = 0x19, + MILS = 0x1A, + INCHES = 0x1B, + FEET = 0x1C, + CUBIC_INCHES = 0x1D, + CUBIC_FEET = 0x1E, + METERS = 0x1F, + CUBIC_CM = 0x20, + CUBIC_METERS = 0x21, + LITERS = 0x22, + FLUID_OUNCES = 0x23, + RADIANS = 0x24, + STERADIANS = 0x25, + REVOLUTIONS = 0x26, + CYCLES = 0x27, + GRAVITIES = 0x28, + OUNCES = 0x29, + POUNDS = 0x2A, + FOOT_POUNDS = 0x2B, + OUNCE_INCHES = 0x2C, + GAUSS = 0x2D, + GILBERTS = 0x2E, + HENRIES = 0x2F, + FARADS = 0x30, + OHMS = 0x31, + SIEMENS = 0x32, + MOLES = 0x33, + BECQUERELS = 0x34, + PPM = 0x35, + DECIBELS = 0x36, + DBA = 0x37, + DBC = 0x38, + GRAYS = 0x39, + SIEVERTS = 0x3A, + COLOR_TEMP_K = 0x3B, + BITS = 0x3C, + BYTES = 0x3D, + WORDS = 0x3E, + DWORDS = 0x3F, + QWORDS = 0x40, + PERCENTAGE = 0x41, + PASCALS = 0x42, + COUNTS = 0x43, + GRAMS = 0x44, + NEWTON_METERS = 0x45, + HITS = 0x46, + MISSES = 0x47, + RETRIES = 0x48, + OVERRUNS = 0x49, + UNDERRUNS = 0x4A, + COLLISIONS = 0x4B, + PACKETS = 0x4C, + MESSAGES = 0x4D, + CHARS = 0x4E, + ERRORS = 0x4F, + CORRECTED_ERRS = 0x50, + UNCORRECTABLE_ERRS = 0x51, + SQ_MILS = 0x52, + SQ_INCHES = 0x53, + SQ_FEET = 0x54, + SQ_CM = 0x55, + SQ_METERS = 0x56, + RADIANS_SEC = 0x57, + BPM = 0x58, + METERS_SEC_SQUARED = 0x59, + METERS_SEC = 0x5A, + CUBIC_METERS_SEC = 0x5B, + MM_MERCURY = 0x5C, + RADIANS_SEC_SQUARED = 0x5D, + OEM_UNIT = 0xFF +}; + +/** + * struct scmi_sensor_proto_ops - represents the various operations provided + * by SCMI Sensor Protocol + * + * @count_get: get the count of sensors provided by SCMI + * @info_get: get the information of the specified sensor + * @trip_point_config: selects and configures a trip-point of interest + * @reading_get: gets the current value of the sensor + * @reading_get_timestamped: gets the current value and timestamp, when + * available, of the sensor. (as of v3.0 spec) + * Supports multi-axis sensors for sensors which + * supports it and if the @reading array size of + * @count entry equals the sensor num_axis + * @config_get: Get sensor current configuration + * @config_set: Set sensor current configuration + */ +struct scmi_sensor_proto_ops { + int (*count_get)(const struct scmi_protocol_handle *ph); + const struct scmi_sensor_info *(*info_get) + (const struct scmi_protocol_handle *ph, u32 sensor_id); + int (*trip_point_config)(const struct scmi_protocol_handle *ph, + u32 sensor_id, u8 trip_id, u64 trip_value); + int (*reading_get)(const struct scmi_protocol_handle *ph, u32 sensor_id, + u64 *value); + int (*reading_get_timestamped)(const struct scmi_protocol_handle *ph, + u32 sensor_id, u8 count, + struct scmi_sensor_reading *readings); + int (*config_get)(const struct scmi_protocol_handle *ph, + u32 sensor_id, u32 *sensor_config); + int (*config_set)(const struct scmi_protocol_handle *ph, + u32 sensor_id, u32 sensor_config); +}; + +/** + * struct scmi_reset_proto_ops - represents the various operations provided + * by SCMI Reset Protocol + * + * @num_domains_get: get the count of reset domains provided by SCMI + * @name_get: gets the name of a reset domain + * @latency_get: gets the reset latency for the specified reset domain + * @reset: resets the specified reset domain + * @assert: explicitly assert reset signal of the specified reset domain + * @deassert: explicitly deassert reset signal of the specified reset domain + */ +struct scmi_reset_proto_ops { + int (*num_domains_get)(const struct scmi_protocol_handle *ph); + char *(*name_get)(const struct scmi_protocol_handle *ph, u32 domain); + int (*latency_get)(const struct scmi_protocol_handle *ph, u32 domain); + int (*reset)(const struct scmi_protocol_handle *ph, u32 domain); + int (*assert)(const struct scmi_protocol_handle *ph, u32 domain); + int (*deassert)(const struct scmi_protocol_handle *ph, u32 domain); +}; + +/** + * struct scmi_voltage_info - describe one available SCMI Voltage Domain + * + * @id: the domain ID as advertised by the platform + * @segmented: defines the layout of the entries of array @levels_uv. + * - when True the entries are to be interpreted as triplets, + * each defining a segment representing a range of equally + * space voltages: , , + * - when False the entries simply represent a single discrete + * supported voltage level + * @negative_volts_allowed: True if any of the entries of @levels_uv represent + * a negative voltage. + * @attributes: represents Voltage Domain advertised attributes + * @name: name assigned to the Voltage Domain by platform + * @num_levels: number of total entries in @levels_uv. + * @levels_uv: array of entries describing the available voltage levels for + * this domain. + */ +struct scmi_voltage_info { + unsigned int id; + bool segmented; + bool negative_volts_allowed; + unsigned int attributes; + char name[SCMI_MAX_STR_SIZE]; + unsigned int num_levels; +#define SCMI_VOLTAGE_SEGMENT_LOW 0 +#define SCMI_VOLTAGE_SEGMENT_HIGH 1 +#define SCMI_VOLTAGE_SEGMENT_STEP 2 + int *levels_uv; +}; + +/** + * struct scmi_voltage_proto_ops - represents the various operations provided + * by SCMI Voltage Protocol + * + * @num_domains_get: get the count of voltage domains provided by SCMI + * @info_get: get the information of the specified domain + * @config_set: set the config for the specified domain + * @config_get: get the config of the specified domain + * @level_set: set the voltage level for the specified domain + * @level_get: get the voltage level of the specified domain + */ +struct scmi_voltage_proto_ops { + int (*num_domains_get)(const struct scmi_protocol_handle *ph); + const struct scmi_voltage_info __must_check *(*info_get) + (const struct scmi_protocol_handle *ph, u32 domain_id); + int (*config_set)(const struct scmi_protocol_handle *ph, u32 domain_id, + u32 config); +#define SCMI_VOLTAGE_ARCH_STATE_OFF 0x0 +#define SCMI_VOLTAGE_ARCH_STATE_ON 0x7 + int (*config_get)(const struct scmi_protocol_handle *ph, u32 domain_id, + u32 *config); + int (*level_set)(const struct scmi_protocol_handle *ph, u32 domain_id, + u32 flags, s32 volt_uV); + int (*level_get)(const struct scmi_protocol_handle *ph, u32 domain_id, + s32 *volt_uV); +}; + +/** + * struct scmi_handle - Handle returned to ARM SCMI clients for usage. + * + * @dev: pointer to the SCMI device_d + * @version: pointer to the structure containing SCMI version information + * @protocol_get: method to acquire a protocol and get specific + * operations and a dedicated protocol handler + */ +struct scmi_handle { + struct device_d *dev; + struct scmi_revision_info *version; + + const void __must_check * + (*protocol_get)(struct scmi_device *sdev, u8 proto, + struct scmi_protocol_handle **ph); +}; + +enum scmi_std_protocol { + SCMI_PROTOCOL_BASE = 0x10, + SCMI_PROTOCOL_POWER = 0x11, + SCMI_PROTOCOL_SYSTEM = 0x12, + SCMI_PROTOCOL_PERF = 0x13, + SCMI_PROTOCOL_CLOCK = 0x14, + SCMI_PROTOCOL_SENSOR = 0x15, + SCMI_PROTOCOL_RESET = 0x16, + SCMI_PROTOCOL_VOLTAGE = 0x17, +}; + +enum scmi_system_events { + SCMI_SYSTEM_SHUTDOWN, + SCMI_SYSTEM_COLDRESET, + SCMI_SYSTEM_WARMRESET, + SCMI_SYSTEM_POWERUP, + SCMI_SYSTEM_SUSPEND, + SCMI_SYSTEM_MAX +}; + +struct scmi_device { + u8 protocol_id; + const char *name; + struct device_d dev; + struct scmi_handle *handle; +}; + +#define to_scmi_dev(d) container_of(d, struct scmi_device, dev) + +struct scmi_device * +scmi_device_alloc(struct device_node *np, struct device_d *parent, int protocol, + const char *name); +void scmi_device_destroy(struct scmi_device *scmi_dev); + +struct scmi_device_id { + u8 protocol_id; + const char *name; +}; + +struct scmi_driver { + const char *name; + int (*probe)(struct scmi_device *sdev); + void (*remove)(struct scmi_device *sdev); + const struct scmi_device_id *id_table; + + struct driver_d driver; +}; + +#define to_scmi_driver(d) container_of(d, struct scmi_driver, driver) + +#if IS_REACHABLE(CONFIG_ARM_SCMI_PROTOCOL) +int scmi_driver_register(struct scmi_driver *driver); +#else +static inline int +scmi_driver_register(struct scmi_driver *driver) +{ + return -EINVAL; +} + +#endif /* CONFIG_ARM_SCMI_PROTOCOL */ + +#define scmi_register(driver) \ + scmi_driver_register(driver) + +/** + * coredevice_scmi_driver() - Helper macro for registering a scmi driver + * @__scmi_driver: scmi_driver structure + * + * Helper macro for scmi drivers to set up proper module init / exit + * functions. Replaces module_init() and module_exit() and keeps people from + * printing pointless things to the kernel log when their driver is loaded. + */ +#define coredevice_scmi_driver(__scmi_driver) \ + register_driver_macro(coredevice,scmi,__scmi_driver) + +#define core_scmi_driver(__scmi_driver) \ + register_driver_macro(core,scmi,__scmi_driver) + +/** + * module_scmi_protocol() - Helper macro for registering a scmi protocol + * @__scmi_protocol: scmi_protocol structure + * + * Helper macro for scmi drivers to set up proper module init / exit + * functions. Replaces module_init() and module_exit() and keeps people from + * printing pointless things to the kernel log when their driver is loaded. + */ +#define module_scmi_protocol(__scmi_protocol) \ + module_driver(__scmi_protocol, \ + scmi_protocol_register, scmi_protocol_unregister) + +struct scmi_protocol; +int scmi_protocol_register(const struct scmi_protocol *proto); +void scmi_protocol_unregister(const struct scmi_protocol *proto); + +#endif /* _LINUX_SCMI_PROTOCOL_H */ diff --git a/include/linux/slab.h b/include/linux/slab.h index 348770a3a2..eb14c58e34 100644 --- a/include/linux/slab.h +++ b/include/linux/slab.h @@ -103,4 +103,7 @@ static inline void *kcalloc(size_t n, size_t size, gfp_t flags) return calloc(n, size); } +#define kstrdup_const(str, flags) strdup(str) +#define kfree_const(ptr) kfree((void *)ptr) + #endif /* _LINUX_SLAB_H */ -- cgit v1.2.3 From 3e3ddeb7cbd65924f6d1bd0973c7827fbcf8450e Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Sun, 20 Feb 2022 13:47:25 +0100 Subject: reset: add SCMI support Import the Linux v5.13 state of the SCMI reset protocol driver. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20220220124736.3052502-14-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- drivers/firmware/arm_scmi/Makefile | 2 +- drivers/firmware/arm_scmi/common.h | 1 + drivers/firmware/arm_scmi/driver.c | 2 + drivers/firmware/arm_scmi/reset.c | 229 +++++++++++++++++++++++++++++++++++++ drivers/reset/Kconfig | 11 ++ drivers/reset/Makefile | 1 + drivers/reset/reset-scmi.c | 130 +++++++++++++++++++++ 7 files changed, 375 insertions(+), 1 deletion(-) create mode 100644 drivers/firmware/arm_scmi/reset.c create mode 100644 drivers/reset/reset-scmi.c diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile index 8bb25484b9..510c0372a0 100644 --- a/drivers/firmware/arm_scmi/Makefile +++ b/drivers/firmware/arm_scmi/Makefile @@ -3,7 +3,7 @@ scmi-bus-y = bus.o scmi-driver-y = driver.o scmi-transport-y = shmem.o scmi-transport-$(CONFIG_ARM_SMCCC) += smc.o -scmi-protocols-y = base.o +scmi-protocols-y = base.o reset.o scmi-module-objs := $(scmi-bus-y) $(scmi-driver-y) $(scmi-protocols-y) \ $(scmi-transport-y) diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h index 9e48c0e774..f1f6cdae5a 100644 --- a/drivers/firmware/arm_scmi/common.h +++ b/drivers/firmware/arm_scmi/common.h @@ -232,6 +232,7 @@ void __exit scmi_bus_exit(void); #define DECLARE_SCMI_REGISTER(func) \ int __init scmi_##func##_register(void); DECLARE_SCMI_REGISTER(base); +DECLARE_SCMI_REGISTER(reset); #define DEFINE_SCMI_PROTOCOL_REGISTER(name, proto) \ static const struct scmi_protocol *__this_proto = &(proto); \ diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c index c9819e7c2a..d862e4de8a 100644 --- a/drivers/firmware/arm_scmi/driver.c +++ b/drivers/firmware/arm_scmi/driver.c @@ -1259,6 +1259,8 @@ static int __init scmi_bus_driver_init(void) scmi_base_register(); + scmi_reset_register(); + return 0; } pure_initcall(scmi_bus_driver_init); diff --git a/drivers/firmware/arm_scmi/reset.c b/drivers/firmware/arm_scmi/reset.c new file mode 100644 index 0000000000..94baab99e1 --- /dev/null +++ b/drivers/firmware/arm_scmi/reset.c @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * System Control and Management Interface (SCMI) Reset Protocol + * + * Copyright (C) 2019-2021 ARM Ltd. + */ + +#define pr_fmt(fmt) "SCMI RESET - " fmt + +#include +#include + +#include "common.h" + +enum scmi_reset_protocol_cmd { + RESET_DOMAIN_ATTRIBUTES = 0x3, + RESET = 0x4, + RESET_NOTIFY = 0x5, +}; + +#define NUM_RESET_DOMAIN_MASK 0xffff + +struct scmi_msg_resp_reset_domain_attributes { + __le32 attributes; +#define SUPPORTS_ASYNC_RESET(x) ((x) & BIT(31)) +#define SUPPORTS_NOTIFY_RESET(x) ((x) & BIT(30)) + __le32 latency; + u8 name[SCMI_MAX_STR_SIZE]; +}; + +struct scmi_msg_reset_domain_reset { + __le32 domain_id; + __le32 flags; +#define AUTONOMOUS_RESET BIT(0) +#define EXPLICIT_RESET_ASSERT BIT(1) +#define ASYNCHRONOUS_RESET BIT(2) + __le32 reset_state; +#define ARCH_COLD_RESET 0 +}; + +struct reset_dom_info { + bool async_reset; + u32 latency_us; + char name[SCMI_MAX_STR_SIZE]; +}; + +struct scmi_reset_info { + u32 version; + int num_domains; + struct reset_dom_info *dom_info; +}; + +static int scmi_reset_attributes_get(const struct scmi_protocol_handle *ph, + struct scmi_reset_info *pi) +{ + int ret; + struct scmi_xfer *t; + u32 attr; + + ret = ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES, + 0, sizeof(attr), &t); + if (ret) + return ret; + + ret = ph->xops->do_xfer(ph, t); + if (!ret) { + attr = get_unaligned_le32(t->rx.buf); + pi->num_domains = attr & NUM_RESET_DOMAIN_MASK; + } + + ph->xops->xfer_put(ph, t); + return ret; +} + +static int +scmi_reset_domain_attributes_get(const struct scmi_protocol_handle *ph, + u32 domain, struct reset_dom_info *dom_info) +{ + int ret; + struct scmi_xfer *t; + struct scmi_msg_resp_reset_domain_attributes *attr; + + ret = ph->xops->xfer_get_init(ph, RESET_DOMAIN_ATTRIBUTES, + sizeof(domain), sizeof(*attr), &t); + if (ret) + return ret; + + put_unaligned_le32(domain, t->tx.buf); + attr = t->rx.buf; + + ret = ph->xops->do_xfer(ph, t); + if (!ret) { + u32 attributes = le32_to_cpu(attr->attributes); + + dom_info->async_reset = SUPPORTS_ASYNC_RESET(attributes); + dom_info->latency_us = le32_to_cpu(attr->latency); + if (dom_info->latency_us == U32_MAX) + dom_info->latency_us = 0; + strlcpy(dom_info->name, attr->name, SCMI_MAX_STR_SIZE); + } + + ph->xops->xfer_put(ph, t); + return ret; +} + +static int scmi_reset_num_domains_get(const struct scmi_protocol_handle *ph) +{ + struct scmi_reset_info *pi = ph->get_priv(ph); + + return pi->num_domains; +} + +static char *scmi_reset_name_get(const struct scmi_protocol_handle *ph, + u32 domain) +{ + struct scmi_reset_info *pi = ph->get_priv(ph); + + struct reset_dom_info *dom = pi->dom_info + domain; + + return dom->name; +} + +static int scmi_reset_latency_get(const struct scmi_protocol_handle *ph, + u32 domain) +{ + struct scmi_reset_info *pi = ph->get_priv(ph); + struct reset_dom_info *dom = pi->dom_info + domain; + + return dom->latency_us; +} + +static int scmi_domain_reset(const struct scmi_protocol_handle *ph, u32 domain, + u32 flags, u32 state) +{ + int ret; + struct scmi_xfer *t; + struct scmi_msg_reset_domain_reset *dom; + struct scmi_reset_info *pi = ph->get_priv(ph); + struct reset_dom_info *rdom = pi->dom_info + domain; + + if (rdom->async_reset) + flags |= ASYNCHRONOUS_RESET; + + ret = ph->xops->xfer_get_init(ph, RESET, sizeof(*dom), 0, &t); + if (ret) + return ret; + + dom = t->tx.buf; + dom->domain_id = cpu_to_le32(domain); + dom->flags = cpu_to_le32(flags); + dom->reset_state = cpu_to_le32(state); + + if (rdom->async_reset) + ret = ph->xops->do_xfer_with_response(ph, t); + else + ret = ph->xops->do_xfer(ph, t); + + ph->xops->xfer_put(ph, t); + return ret; +} + +static int scmi_reset_domain_reset(const struct scmi_protocol_handle *ph, + u32 domain) +{ + return scmi_domain_reset(ph, domain, AUTONOMOUS_RESET, + ARCH_COLD_RESET); +} + +static int +scmi_reset_domain_assert(const struct scmi_protocol_handle *ph, u32 domain) +{ + return scmi_domain_reset(ph, domain, EXPLICIT_RESET_ASSERT, + ARCH_COLD_RESET); +} + +static int +scmi_reset_domain_deassert(const struct scmi_protocol_handle *ph, u32 domain) +{ + return scmi_domain_reset(ph, domain, 0, ARCH_COLD_RESET); +} + +static const struct scmi_reset_proto_ops reset_proto_ops = { + .num_domains_get = scmi_reset_num_domains_get, + .name_get = scmi_reset_name_get, + .latency_get = scmi_reset_latency_get, + .reset = scmi_reset_domain_reset, + .assert = scmi_reset_domain_assert, + .deassert = scmi_reset_domain_deassert, +}; + +static int scmi_reset_protocol_init(const struct scmi_protocol_handle *ph) +{ + int domain; + u32 version; + struct scmi_reset_info *pinfo; + + ph->xops->version_get(ph, &version); + + dev_dbg(ph->dev, "Reset Version %d.%d\n", + PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version)); + + pinfo = kzalloc(sizeof(*pinfo), GFP_KERNEL); + if (!pinfo) + return -ENOMEM; + + scmi_reset_attributes_get(ph, pinfo); + + pinfo->dom_info = kcalloc(pinfo->num_domains, + sizeof(*pinfo->dom_info), GFP_KERNEL); + if (!pinfo->dom_info) + return -ENOMEM; + + for (domain = 0; domain < pinfo->num_domains; domain++) { + struct reset_dom_info *dom = pinfo->dom_info + domain; + + scmi_reset_domain_attributes_get(ph, domain, dom); + } + + pinfo->version = version; + return ph->set_priv(ph, pinfo); +} + +static const struct scmi_protocol scmi_reset = { + .id = SCMI_PROTOCOL_RESET, + .instance_init = &scmi_reset_protocol_init, + .ops = &reset_proto_ops, +}; + +DEFINE_SCMI_PROTOCOL_REGISTER(reset, scmi_reset) diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig index 21a6e1a50d..913b309eac 100644 --- a/drivers/reset/Kconfig +++ b/drivers/reset/Kconfig @@ -45,4 +45,15 @@ config RESET_STARFIVE help This enables the reset controller driver for the StarFive JH7100. +config RESET_SCMI + tristate "Reset driver controlled via ARM SCMI interface" + depends on ARM_SCMI_PROTOCOL || COMPILE_TEST + default ARM_SCMI_PROTOCOL + help + This driver provides support for reset signal/domains that are + controlled by firmware that implements the SCMI interface. + + This driver uses SCMI Message Protocol to interact with the + firmware controlling all the reset signals. + endif diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile index 6d0cd51f86..b1668433d7 100644 --- a/drivers/reset/Makefile +++ b/drivers/reset/Makefile @@ -4,3 +4,4 @@ obj-$(CONFIG_RESET_SIMPLE) += reset-simple.o obj-$(CONFIG_ARCH_SOCFPGA) += reset-socfpga.o obj-$(CONFIG_RESET_IMX7) += reset-imx7.o obj-$(CONFIG_RESET_STARFIVE) += reset-starfive-vic.o +obj-$(CONFIG_RESET_SCMI) += reset-scmi.o diff --git a/drivers/reset/reset-scmi.c b/drivers/reset/reset-scmi.c new file mode 100644 index 0000000000..c33bbc5c8a --- /dev/null +++ b/drivers/reset/reset-scmi.c @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ARM System Control and Management Interface (ARM SCMI) reset driver + * + * Copyright (C) 2019-2021 ARM Ltd. + */ + +#include +#include +#include +#include +#include + +static const struct scmi_reset_proto_ops *reset_ops; + +/** + * struct scmi_reset_data - reset controller information structure + * @rcdev: reset controller entity + * @ph: ARM SCMI protocol handle used for communication with system controller + */ +struct scmi_reset_data { + struct reset_controller_dev rcdev; + const struct scmi_protocol_handle *ph; +}; + +#define to_scmi_reset_data(p) container_of((p), struct scmi_reset_data, rcdev) +#define to_scmi_handle(p) (to_scmi_reset_data(p)->ph) + +/** + * scmi_reset_assert() - assert device reset + * @rcdev: reset controller entity + * @id: ID of the reset to be asserted + * + * This function implements the reset driver op to assert a device's reset + * using the ARM SCMI protocol. + * + * Return: 0 for successful request, else a corresponding error value + */ +static int +scmi_reset_assert(struct reset_controller_dev *rcdev, unsigned long id) +{ + const struct scmi_protocol_handle *ph = to_scmi_handle(rcdev); + + return reset_ops->assert(ph, id); +} + +/** + * scmi_reset_deassert() - deassert device reset + * @rcdev: reset controller entity + * @id: ID of the reset to be deasserted + * + * This function implements the reset driver op to deassert a device's reset + * using the ARM SCMI protocol. + * + * Return: 0 for successful request, else a corresponding error value + */ +static int +scmi_reset_deassert(struct reset_controller_dev *rcdev, unsigned long id) +{ + const struct scmi_protocol_handle *ph = to_scmi_handle(rcdev); + + return reset_ops->deassert(ph, id); +} + +/** + * scmi_reset_reset() - reset the device + * @rcdev: reset controller entity + * @id: ID of the reset signal to be reset(assert + deassert) + * + * This function implements the reset driver op to trigger a device's + * reset signal using the ARM SCMI protocol. + * + * Return: 0 for successful request, else a corresponding error value + */ +static int +scmi_reset_reset(struct reset_controller_dev *rcdev, unsigned long id) +{ + const struct scmi_protocol_handle *ph = to_scmi_handle(rcdev); + + return reset_ops->reset(ph, id); +} + +static const struct reset_control_ops scmi_reset_ops = { + .assert = scmi_reset_assert, + .deassert = scmi_reset_deassert, + .reset = scmi_reset_reset, +}; + +static int scmi_reset_probe(struct scmi_device *sdev) +{ + struct scmi_reset_data *data; + struct device_d *dev = &sdev->dev; + struct device_node *np = dev->device_node; + const struct scmi_handle *handle = sdev->handle; + struct scmi_protocol_handle *ph; + + if (!handle) + return -ENODEV; + + reset_ops = handle->protocol_get(sdev, SCMI_PROTOCOL_RESET, &ph); + if (IS_ERR(reset_ops)) + return PTR_ERR(reset_ops); + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->rcdev.ops = &scmi_reset_ops; + data->rcdev.of_node = np; + data->rcdev.nr_resets = reset_ops->num_domains_get(ph); + data->ph = ph; + + return reset_controller_register(&data->rcdev); +} + +static const struct scmi_device_id scmi_id_table[] = { + { SCMI_PROTOCOL_RESET, "reset" }, + { }, +}; + +static struct scmi_driver scmi_reset_driver = { + .name = "scmi-reset", + .probe = scmi_reset_probe, + .id_table = scmi_id_table, +}; +core_scmi_driver(scmi_reset_driver); + +MODULE_AUTHOR("Sudeep Holla "); +MODULE_DESCRIPTION("ARM SCMI reset controller driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 07e5fe442aa023112f36c81514ccdc38eb38cda9 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Sun, 20 Feb 2022 13:47:26 +0100 Subject: clk: add SCMI clock driver Import the Linux v5.13 state of the SCMI clock protocol driver. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20220220124736.3052502-15-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- drivers/clk/Kconfig | 11 ++ drivers/clk/Makefile | 1 + drivers/clk/clk-scmi.c | 192 +++++++++++++++++++ drivers/firmware/arm_scmi/Makefile | 2 +- drivers/firmware/arm_scmi/clock.c | 374 +++++++++++++++++++++++++++++++++++++ drivers/firmware/arm_scmi/common.h | 1 + drivers/firmware/arm_scmi/driver.c | 1 + 7 files changed, 581 insertions(+), 1 deletion(-) create mode 100644 drivers/clk/clk-scmi.c create mode 100644 drivers/firmware/arm_scmi/clock.c diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index e01ffe10f2..5b5acf4e06 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -20,10 +20,21 @@ config CLK_SOCFPGA select COMMON_CLK_OF_PROVIDER default y if ARCH_SOCFPGA && OFDEVICE + config COMMON_CLK_STM32F bool "STM32F4 and STM32F7 clock driver" if COMPILE_TEST depends on COMMON_CLK && ARCH_STM32 help Support for stm32f4 and stm32f7 SoC families clocks +config COMMON_CLK_SCMI + tristate "Clock driver controlled via SCMI interface" + depends on ARM_SCMI_PROTOCOL || COMPILE_TEST + help + This driver provides support for clocks that are controlled + by firmware that implements the SCMI interface. + + This driver uses SCMI Message Protocol to interact with the + firmware providing all the clock controls. + source "drivers/clk/sifive/Kconfig" diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index c60b38ff60..ee503c1edb 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -26,3 +26,4 @@ obj-$(CONFIG_CLK_SIFIVE) += sifive/ obj-$(CONFIG_SOC_STARFIVE) += starfive/ obj-$(CONFIG_COMMON_CLK_STM32F) += clk-stm32f4.o obj-$(CONFIG_MACH_RPI_COMMON) += clk-rpi.o +obj-$(CONFIG_COMMON_CLK_SCMI) += clk-scmi.o diff --git a/drivers/clk/clk-scmi.c b/drivers/clk/clk-scmi.c new file mode 100644 index 0000000000..9170dba393 --- /dev/null +++ b/drivers/clk/clk-scmi.c @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * System Control and Power Interface (SCMI) Protocol based clock driver + * + * Copyright (C) 2018-2021 ARM Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static const struct scmi_clk_proto_ops *scmi_proto_clk_ops; + +struct scmi_clk { + u32 id; + struct clk_hw hw; + const struct scmi_clock_info *info; + const struct scmi_protocol_handle *ph; +}; + +#define to_scmi_clk(clk) container_of(clk, struct scmi_clk, hw) + +static unsigned long scmi_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + int ret; + u64 rate; + struct scmi_clk *clk = to_scmi_clk(hw); + + ret = scmi_proto_clk_ops->rate_get(clk->ph, clk->id, &rate); + if (ret) + return 0; + return rate; +} + +static long scmi_clk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + u64 fmin, fmax, ftmp; + struct scmi_clk *clk = to_scmi_clk(hw); + + /* + * We can't figure out what rate it will be, so just return the + * rate back to the caller. scmi_clk_recalc_rate() will be called + * after the rate is set and we'll know what rate the clock is + * running at then. + */ + if (clk->info->rate_discrete) + return rate; + + fmin = clk->info->range.min_rate; + fmax = clk->info->range.max_rate; + if (rate <= fmin) + return fmin; + else if (rate >= fmax) + return fmax; + + ftmp = rate - fmin; + ftmp += clk->info->range.step_size - 1; /* to round up */ + do_div(ftmp, clk->info->range.step_size); + + return ftmp * clk->info->range.step_size + fmin; +} + +static int scmi_clk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct scmi_clk *clk = to_scmi_clk(hw); + + return scmi_proto_clk_ops->rate_set(clk->ph, clk->id, rate); +} + +static int scmi_clk_enable(struct clk_hw *hw) +{ + struct scmi_clk *clk = to_scmi_clk(hw); + + return scmi_proto_clk_ops->enable(clk->ph, clk->id); +} + +static void scmi_clk_disable(struct clk_hw *hw) +{ + struct scmi_clk *clk = to_scmi_clk(hw); + + scmi_proto_clk_ops->disable(clk->ph, clk->id); +} + +static const struct clk_ops scmi_clk_ops = { + .recalc_rate = scmi_clk_recalc_rate, + .round_rate = scmi_clk_round_rate, + .set_rate = scmi_clk_set_rate, + /* + * Unlike Linux, we can provide enable/disable callback as everything + * runs in atomic context. + */ + .enable = scmi_clk_enable, + .disable = scmi_clk_disable, +}; + +static int scmi_clk_ops_init(struct device_d *dev, struct scmi_clk *sclk) +{ + struct clk_init_data init = { + .flags = CLK_GET_RATE_NOCACHE, + .num_parents = 0, + .ops = &scmi_clk_ops, + .name = sclk->info->name, + }; + + sclk->hw.init = &init; + return clk_hw_register(dev, &sclk->hw); +} + +static int scmi_clocks_probe(struct scmi_device *sdev) +{ + int idx, count, err; + struct clk **clks; + struct clk_onecell_data *clk_data; + struct device_d *dev = &sdev->dev; + struct device_node *np = dev->device_node; + const struct scmi_handle *handle = sdev->handle; + struct scmi_protocol_handle *ph; + + if (!handle) + return -ENODEV; + + scmi_proto_clk_ops = + handle->protocol_get(sdev, SCMI_PROTOCOL_CLOCK, &ph); + if (IS_ERR(scmi_proto_clk_ops)) + return PTR_ERR(scmi_proto_clk_ops); + + count = scmi_proto_clk_ops->count_get(ph); + if (count < 0) { + dev_err(dev, "%pOFn: invalid clock output count\n", np); + return -EINVAL; + } + + clk_data = kzalloc(sizeof (*clk_data), GFP_KERNEL); + if (!clk_data) + return -ENOMEM; + + clk_data->clk_num = count; + clks = clk_data->clks = calloc(clk_data->clk_num, sizeof(struct clk *)); + + for (idx = 0; idx < count; idx++) { + struct scmi_clk *sclk; + + sclk = kzalloc(sizeof(*sclk), GFP_KERNEL); + if (!sclk) + return -ENOMEM; + + sclk->info = scmi_proto_clk_ops->info_get(ph, idx); + if (!sclk->info) { + dev_dbg(dev, "invalid clock info for idx %d\n", idx); + continue; + } + + sclk->id = idx; + sclk->ph = ph; + + err = scmi_clk_ops_init(dev, sclk); + if (err) { + dev_err(dev, "failed to register clock %d\n", idx); + kfree(sclk); + clks[idx] = NULL; + } else { + dev_dbg(dev, "Registered clock:%s\n", sclk->info->name); + clks[idx] = &sclk->hw.clk; + } + } + + return of_clk_add_provider(dev->device_node, of_clk_src_onecell_get, clk_data); +} + +static const struct scmi_device_id scmi_id_table[] = { + { SCMI_PROTOCOL_CLOCK, "clocks" }, + { }, +}; + +static struct scmi_driver scmi_clocks_driver = { + .name = "scmi-clocks", + .probe = scmi_clocks_probe, + .id_table = scmi_id_table, +}; +core_scmi_driver(scmi_clocks_driver); + +MODULE_AUTHOR("Sudeep Holla "); +MODULE_DESCRIPTION("ARM SCMI clock driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile index 510c0372a0..968a7eb777 100644 --- a/drivers/firmware/arm_scmi/Makefile +++ b/drivers/firmware/arm_scmi/Makefile @@ -3,7 +3,7 @@ scmi-bus-y = bus.o scmi-driver-y = driver.o scmi-transport-y = shmem.o scmi-transport-$(CONFIG_ARM_SMCCC) += smc.o -scmi-protocols-y = base.o reset.o +scmi-protocols-y = base.o reset.o clock.o scmi-module-objs := $(scmi-bus-y) $(scmi-driver-y) $(scmi-protocols-y) \ $(scmi-transport-y) diff --git a/drivers/firmware/arm_scmi/clock.c b/drivers/firmware/arm_scmi/clock.c new file mode 100644 index 0000000000..8f9017206c --- /dev/null +++ b/drivers/firmware/arm_scmi/clock.c @@ -0,0 +1,374 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * System Control and Management Interface (SCMI) Clock Protocol + * + * Copyright (C) 2018-2021 ARM Ltd. + */ + +#include +#include + +#include "common.h" + +enum scmi_clock_protocol_cmd { + CLOCK_ATTRIBUTES = 0x3, + CLOCK_DESCRIBE_RATES = 0x4, + CLOCK_RATE_SET = 0x5, + CLOCK_RATE_GET = 0x6, + CLOCK_CONFIG_SET = 0x7, +}; + +struct scmi_msg_resp_clock_protocol_attributes { + __le16 num_clocks; + u8 max_async_req; + u8 reserved; +}; + +struct scmi_msg_resp_clock_attributes { + __le32 attributes; +#define CLOCK_ENABLE BIT(0) + u8 name[SCMI_MAX_STR_SIZE]; +}; + +struct scmi_clock_set_config { + __le32 id; + __le32 attributes; +}; + +struct scmi_msg_clock_describe_rates { + __le32 id; + __le32 rate_index; +}; + +struct scmi_msg_resp_clock_describe_rates { + __le32 num_rates_flags; +#define NUM_RETURNED(x) ((x) & 0xfff) +#define RATE_DISCRETE(x) !((x) & BIT(12)) +#define NUM_REMAINING(x) ((x) >> 16) + struct { + __le32 value_low; + __le32 value_high; + } rate[0]; +#define RATE_TO_U64(X) \ +({ \ + typeof(X) x = (X); \ + le32_to_cpu((x).value_low) | (u64)le32_to_cpu((x).value_high) << 32; \ +}) +}; + +struct scmi_clock_set_rate { + __le32 flags; +#define CLOCK_SET_ASYNC BIT(0) +#define CLOCK_SET_IGNORE_RESP BIT(1) +#define CLOCK_SET_ROUND_UP BIT(2) +#define CLOCK_SET_ROUND_AUTO BIT(3) + __le32 id; + __le32 value_low; + __le32 value_high; +}; + +struct clock_info { + u32 version; + int num_clocks; + int max_async_req; + unsigned cur_async_req; + struct scmi_clock_info *clk; +}; + +static int +scmi_clock_protocol_attributes_get(const struct scmi_protocol_handle *ph, + struct clock_info *ci) +{ + int ret; + struct scmi_xfer *t; + struct scmi_msg_resp_clock_protocol_attributes *attr; + + ret = ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES, + 0, sizeof(*attr), &t); + if (ret) + return ret; + + attr = t->rx.buf; + + ret = ph->xops->do_xfer(ph, t); + if (!ret) { + ci->num_clocks = le16_to_cpu(attr->num_clocks); + ci->max_async_req = attr->max_async_req; + } + + ph->xops->xfer_put(ph, t); + return ret; +} + +static int scmi_clock_attributes_get(const struct scmi_protocol_handle *ph, + u32 clk_id, struct scmi_clock_info *clk) +{ + int ret; + struct scmi_xfer *t; + struct scmi_msg_resp_clock_attributes *attr; + + ret = ph->xops->xfer_get_init(ph, CLOCK_ATTRIBUTES, + sizeof(clk_id), sizeof(*attr), &t); + if (ret) + return ret; + + put_unaligned_le32(clk_id, t->tx.buf); + attr = t->rx.buf; + + ret = ph->xops->do_xfer(ph, t); + if (!ret) + strlcpy(clk->name, attr->name, SCMI_MAX_STR_SIZE); + else + clk->name[0] = '\0'; + + ph->xops->xfer_put(ph, t); + return ret; +} + +static int rate_cmp_func(const void *_r1, const void *_r2) +{ + const u64 *r1 = _r1, *r2 = _r2; + + if (*r1 < *r2) + return -1; + else if (*r1 == *r2) + return 0; + else + return 1; +} + +static int +scmi_clock_describe_rates_get(const struct scmi_protocol_handle *ph, u32 clk_id, + struct scmi_clock_info *clk) +{ + u64 *rate = NULL; + int ret, cnt; + bool rate_discrete = false; + u32 tot_rate_cnt = 0, rates_flag; + u16 num_returned, num_remaining; + struct scmi_xfer *t; + struct scmi_msg_clock_describe_rates *clk_desc; + struct scmi_msg_resp_clock_describe_rates *rlist; + + ret = ph->xops->xfer_get_init(ph, CLOCK_DESCRIBE_RATES, + sizeof(*clk_desc), 0, &t); + if (ret) + return ret; + + clk_desc = t->tx.buf; + rlist = t->rx.buf; + + do { + clk_desc->id = cpu_to_le32(clk_id); + /* Set the number of rates to be skipped/already read */ + clk_desc->rate_index = cpu_to_le32(tot_rate_cnt); + + ret = ph->xops->do_xfer(ph, t); + if (ret) + goto err; + + rates_flag = le32_to_cpu(rlist->num_rates_flags); + num_remaining = NUM_REMAINING(rates_flag); + rate_discrete = RATE_DISCRETE(rates_flag); + num_returned = NUM_RETURNED(rates_flag); + + if (tot_rate_cnt + num_returned > SCMI_MAX_NUM_RATES) { + dev_err(ph->dev, "No. of rates > MAX_NUM_RATES"); + break; + } + + if (!rate_discrete) { + clk->range.min_rate = RATE_TO_U64(rlist->rate[0]); + clk->range.max_rate = RATE_TO_U64(rlist->rate[1]); + clk->range.step_size = RATE_TO_U64(rlist->rate[2]); + dev_dbg(ph->dev, "Min %llu Max %llu Step %llu Hz\n", + clk->range.min_rate, clk->range.max_rate, + clk->range.step_size); + break; + } + + rate = &clk->list.rates[tot_rate_cnt]; + for (cnt = 0; cnt < num_returned; cnt++, rate++) { + *rate = RATE_TO_U64(rlist->rate[cnt]); + dev_dbg(ph->dev, "Rate %llu Hz\n", *rate); + } + + tot_rate_cnt += num_returned; + + ph->xops->reset_rx_to_maxsz(ph, t); + /* + * check for both returned and remaining to avoid infinite + * loop due to buggy firmware + */ + } while (num_returned && num_remaining); + + if (rate_discrete && rate) { + clk->list.num_rates = tot_rate_cnt; + qsort(rate, tot_rate_cnt, sizeof(*rate), rate_cmp_func); + } + + clk->rate_discrete = rate_discrete; + +err: + ph->xops->xfer_put(ph, t); + return ret; +} + +static int +scmi_clock_rate_get(const struct scmi_protocol_handle *ph, + u32 clk_id, u64 *value) +{ + int ret; + struct scmi_xfer *t; + + ret = ph->xops->xfer_get_init(ph, CLOCK_RATE_GET, + sizeof(__le32), sizeof(u64), &t); + if (ret) + return ret; + + put_unaligned_le32(clk_id, t->tx.buf); + + ret = ph->xops->do_xfer(ph, t); + if (!ret) + *value = get_unaligned_le64(t->rx.buf); + + ph->xops->xfer_put(ph, t); + return ret; +} + +static int scmi_clock_rate_set(const struct scmi_protocol_handle *ph, + u32 clk_id, u64 rate) +{ + int ret; + u32 flags = 0; + struct scmi_xfer *t; + struct scmi_clock_set_rate *cfg; + struct clock_info *ci = ph->get_priv(ph); + + ret = ph->xops->xfer_get_init(ph, CLOCK_RATE_SET, sizeof(*cfg), 0, &t); + if (ret) + return ret; + + if (ci->max_async_req && + ci->cur_async_req++ < ci->max_async_req) + flags |= CLOCK_SET_ASYNC; + + cfg = t->tx.buf; + cfg->flags = cpu_to_le32(flags); + cfg->id = cpu_to_le32(clk_id); + cfg->value_low = cpu_to_le32(rate & 0xffffffff); + cfg->value_high = cpu_to_le32(rate >> 32); + + if (flags & CLOCK_SET_ASYNC) + ret = ph->xops->do_xfer_with_response(ph, t); + else + ret = ph->xops->do_xfer(ph, t); + + if (ci->max_async_req) + ci->cur_async_req--; + + ph->xops->xfer_put(ph, t); + return ret; +} + +static int +scmi_clock_config_set(const struct scmi_protocol_handle *ph, u32 clk_id, + u32 config) +{ + int ret; + struct scmi_xfer *t; + struct scmi_clock_set_config *cfg; + + ret = ph->xops->xfer_get_init(ph, CLOCK_CONFIG_SET, + sizeof(*cfg), 0, &t); + if (ret) + return ret; + + cfg = t->tx.buf; + cfg->id = cpu_to_le32(clk_id); + cfg->attributes = cpu_to_le32(config); + + ret = ph->xops->do_xfer(ph, t); + + ph->xops->xfer_put(ph, t); + return ret; +} + +static int scmi_clock_enable(const struct scmi_protocol_handle *ph, u32 clk_id) +{ + return scmi_clock_config_set(ph, clk_id, CLOCK_ENABLE); +} + +static int scmi_clock_disable(const struct scmi_protocol_handle *ph, u32 clk_id) +{ + return scmi_clock_config_set(ph, clk_id, 0); +} + +static int scmi_clock_count_get(const struct scmi_protocol_handle *ph) +{ + struct clock_info *ci = ph->get_priv(ph); + + return ci->num_clocks; +} + +static const struct scmi_clock_info * +scmi_clock_info_get(const struct scmi_protocol_handle *ph, u32 clk_id) +{ + struct clock_info *ci = ph->get_priv(ph); + struct scmi_clock_info *clk = ci->clk + clk_id; + + if (!clk->name[0]) + return NULL; + + return clk; +} + +static const struct scmi_clk_proto_ops clk_proto_ops = { + .count_get = scmi_clock_count_get, + .info_get = scmi_clock_info_get, + .rate_get = scmi_clock_rate_get, + .rate_set = scmi_clock_rate_set, + .enable = scmi_clock_enable, + .disable = scmi_clock_disable, +}; + +static int scmi_clock_protocol_init(const struct scmi_protocol_handle *ph) +{ + u32 version; + int clkid, ret; + struct clock_info *cinfo; + + ph->xops->version_get(ph, &version); + + dev_dbg(ph->dev, "Clock Version %d.%d\n", + PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version)); + + cinfo = kzalloc(sizeof(*cinfo), GFP_KERNEL); + if (!cinfo) + return -ENOMEM; + + scmi_clock_protocol_attributes_get(ph, cinfo); + + cinfo->clk = kcalloc(cinfo->num_clocks, sizeof(*cinfo->clk), GFP_KERNEL); + if (!cinfo->clk) + return -ENOMEM; + + for (clkid = 0; clkid < cinfo->num_clocks; clkid++) { + struct scmi_clock_info *clk = cinfo->clk + clkid; + + ret = scmi_clock_attributes_get(ph, clkid, clk); + if (!ret) + scmi_clock_describe_rates_get(ph, clkid, clk); + } + + cinfo->version = version; + return ph->set_priv(ph, cinfo); +} + +static const struct scmi_protocol scmi_clock = { + .id = SCMI_PROTOCOL_CLOCK, + .instance_init = &scmi_clock_protocol_init, + .ops = &clk_proto_ops, +}; + +DEFINE_SCMI_PROTOCOL_REGISTER(clock, scmi_clock) diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h index f1f6cdae5a..7ac9fd1bc9 100644 --- a/drivers/firmware/arm_scmi/common.h +++ b/drivers/firmware/arm_scmi/common.h @@ -233,6 +233,7 @@ void __exit scmi_bus_exit(void); int __init scmi_##func##_register(void); DECLARE_SCMI_REGISTER(base); DECLARE_SCMI_REGISTER(reset); +DECLARE_SCMI_REGISTER(clock); #define DEFINE_SCMI_PROTOCOL_REGISTER(name, proto) \ static const struct scmi_protocol *__this_proto = &(proto); \ diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c index d862e4de8a..ca71e1a279 100644 --- a/drivers/firmware/arm_scmi/driver.c +++ b/drivers/firmware/arm_scmi/driver.c @@ -1260,6 +1260,7 @@ static int __init scmi_bus_driver_init(void) scmi_base_register(); scmi_reset_register(); + scmi_clock_register(); return 0; } -- cgit v1.2.3 From 8e10c0db3e97b5f004130d35339fc1dffa82fd5d Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Sun, 20 Feb 2022 13:47:27 +0100 Subject: regulator: add SCMI regulator driver Import the Linux v5.13 state of the SCMI clock regulator driver. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20220220124736.3052502-16-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- drivers/firmware/arm_scmi/Makefile | 2 +- drivers/firmware/arm_scmi/common.h | 1 + drivers/firmware/arm_scmi/driver.c | 1 + drivers/firmware/arm_scmi/voltage.c | 379 ++++++++++++++++++++++++++++++++++ drivers/regulator/Kconfig | 9 + drivers/regulator/Makefile | 1 + drivers/regulator/scmi-regulator.c | 391 ++++++++++++++++++++++++++++++++++++ 7 files changed, 783 insertions(+), 1 deletion(-) create mode 100644 drivers/firmware/arm_scmi/voltage.c create mode 100644 drivers/regulator/scmi-regulator.c diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile index 968a7eb777..4b21e6609a 100644 --- a/drivers/firmware/arm_scmi/Makefile +++ b/drivers/firmware/arm_scmi/Makefile @@ -3,7 +3,7 @@ scmi-bus-y = bus.o scmi-driver-y = driver.o scmi-transport-y = shmem.o scmi-transport-$(CONFIG_ARM_SMCCC) += smc.o -scmi-protocols-y = base.o reset.o clock.o +scmi-protocols-y = base.o reset.o clock.o voltage.o scmi-module-objs := $(scmi-bus-y) $(scmi-driver-y) $(scmi-protocols-y) \ $(scmi-transport-y) diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h index 7ac9fd1bc9..5004a71dc9 100644 --- a/drivers/firmware/arm_scmi/common.h +++ b/drivers/firmware/arm_scmi/common.h @@ -234,6 +234,7 @@ void __exit scmi_bus_exit(void); DECLARE_SCMI_REGISTER(base); DECLARE_SCMI_REGISTER(reset); DECLARE_SCMI_REGISTER(clock); +DECLARE_SCMI_REGISTER(voltage); #define DEFINE_SCMI_PROTOCOL_REGISTER(name, proto) \ static const struct scmi_protocol *__this_proto = &(proto); \ diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c index ca71e1a279..ef3d76b3f4 100644 --- a/drivers/firmware/arm_scmi/driver.c +++ b/drivers/firmware/arm_scmi/driver.c @@ -1261,6 +1261,7 @@ static int __init scmi_bus_driver_init(void) scmi_reset_register(); scmi_clock_register(); + scmi_voltage_register(); return 0; } diff --git a/drivers/firmware/arm_scmi/voltage.c b/drivers/firmware/arm_scmi/voltage.c new file mode 100644 index 0000000000..a3d78db28f --- /dev/null +++ b/drivers/firmware/arm_scmi/voltage.c @@ -0,0 +1,379 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * System Control and Management Interface (SCMI) Voltage Protocol + * + * Copyright (C) 2020-2021 ARM Ltd. + */ + +#include +#include + +#include "common.h" + +#define VOLTAGE_DOMS_NUM_MASK GENMASK(15, 0) +#define REMAINING_LEVELS_MASK GENMASK(31, 16) +#define RETURNED_LEVELS_MASK GENMASK(11, 0) + +enum scmi_voltage_protocol_cmd { + VOLTAGE_DOMAIN_ATTRIBUTES = 0x3, + VOLTAGE_DESCRIBE_LEVELS = 0x4, + VOLTAGE_CONFIG_SET = 0x5, + VOLTAGE_CONFIG_GET = 0x6, + VOLTAGE_LEVEL_SET = 0x7, + VOLTAGE_LEVEL_GET = 0x8, +}; + +#define NUM_VOLTAGE_DOMAINS(x) ((u16)(FIELD_GET(VOLTAGE_DOMS_NUM_MASK, (x)))) + +struct scmi_msg_resp_domain_attributes { + __le32 attr; + u8 name[SCMI_MAX_STR_SIZE]; +}; + +struct scmi_msg_cmd_describe_levels { + __le32 domain_id; + __le32 level_index; +}; + +struct scmi_msg_resp_describe_levels { + __le32 flags; +#define NUM_REMAINING_LEVELS(f) ((u16)(FIELD_GET(REMAINING_LEVELS_MASK, (f)))) +#define NUM_RETURNED_LEVELS(f) ((u16)(FIELD_GET(RETURNED_LEVELS_MASK, (f)))) +#define SUPPORTS_SEGMENTED_LEVELS(f) ((f) & BIT(12)) + __le32 voltage[]; +}; + +struct scmi_msg_cmd_config_set { + __le32 domain_id; + __le32 config; +}; + +struct scmi_msg_cmd_level_set { + __le32 domain_id; + __le32 flags; + __le32 voltage_level; +}; + +struct voltage_info { + unsigned int version; + unsigned int num_domains; + struct scmi_voltage_info *domains; +}; + +static int scmi_protocol_attributes_get(const struct scmi_protocol_handle *ph, + struct voltage_info *vinfo) +{ + int ret; + struct scmi_xfer *t; + + ret = ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES, 0, + sizeof(__le32), &t); + if (ret) + return ret; + + ret = ph->xops->do_xfer(ph, t); + if (!ret) + vinfo->num_domains = + NUM_VOLTAGE_DOMAINS(get_unaligned_le32(t->rx.buf)); + + ph->xops->xfer_put(ph, t); + return ret; +} + +static int scmi_init_voltage_levels(struct device_d *dev, + struct scmi_voltage_info *v, + u32 num_returned, u32 num_remaining, + bool segmented) +{ + u32 num_levels; + + num_levels = num_returned + num_remaining; + /* + * segmented levels entries are represented by a single triplet + * returned all in one go. + */ + if (!num_levels || + (segmented && (num_remaining || num_returned != 3))) { + dev_err(dev, + "Invalid level descriptor(%d/%d/%d) for voltage dom %d\n", + num_levels, num_returned, num_remaining, v->id); + return -EINVAL; + } + + v->levels_uv = kcalloc(num_levels, sizeof(u32), GFP_KERNEL); + if (!v->levels_uv) + return -ENOMEM; + + v->num_levels = num_levels; + v->segmented = segmented; + + return 0; +} + +static int scmi_voltage_descriptors_get(const struct scmi_protocol_handle *ph, + struct voltage_info *vinfo) +{ + int ret, dom; + struct scmi_xfer *td, *tl; + struct device_d *dev = ph->dev; + struct scmi_msg_resp_domain_attributes *resp_dom; + struct scmi_msg_resp_describe_levels *resp_levels; + + ret = ph->xops->xfer_get_init(ph, VOLTAGE_DOMAIN_ATTRIBUTES, + sizeof(__le32), sizeof(*resp_dom), &td); + if (ret) + return ret; + resp_dom = td->rx.buf; + + ret = ph->xops->xfer_get_init(ph, VOLTAGE_DESCRIBE_LEVELS, + sizeof(__le64), 0, &tl); + if (ret) + goto outd; + resp_levels = tl->rx.buf; + + for (dom = 0; dom < vinfo->num_domains; dom++) { + u32 desc_index = 0; + u16 num_returned = 0, num_remaining = 0; + struct scmi_msg_cmd_describe_levels *cmd; + struct scmi_voltage_info *v; + + /* Retrieve domain attributes at first ... */ + put_unaligned_le32(dom, td->tx.buf); + ret = ph->xops->do_xfer(ph, td); + /* Skip domain on comms error */ + if (ret) + continue; + + v = vinfo->domains + dom; + v->id = dom; + v->attributes = le32_to_cpu(resp_dom->attr); + strlcpy(v->name, resp_dom->name, SCMI_MAX_STR_SIZE); + + cmd = tl->tx.buf; + /* ...then retrieve domain levels descriptions */ + do { + u32 flags; + int cnt; + + cmd->domain_id = cpu_to_le32(v->id); + cmd->level_index = desc_index; + ret = ph->xops->do_xfer(ph, tl); + if (ret) + break; + + flags = le32_to_cpu(resp_levels->flags); + num_returned = NUM_RETURNED_LEVELS(flags); + num_remaining = NUM_REMAINING_LEVELS(flags); + + /* Allocate space for num_levels if not already done */ + if (!v->num_levels) { + ret = scmi_init_voltage_levels(dev, v, + num_returned, + num_remaining, + SUPPORTS_SEGMENTED_LEVELS(flags)); + if (ret) + break; + } + + if (desc_index + num_returned > v->num_levels) { + dev_err(ph->dev, + "No. of voltage levels can't exceed %d\n", + v->num_levels); + ret = -EINVAL; + break; + } + + for (cnt = 0; cnt < num_returned; cnt++) { + s32 val; + + val = + (s32)le32_to_cpu(resp_levels->voltage[cnt]); + v->levels_uv[desc_index + cnt] = val; + if (val < 0) + v->negative_volts_allowed = true; + } + + desc_index += num_returned; + + ph->xops->reset_rx_to_maxsz(ph, tl); + /* check both to avoid infinite loop due to buggy fw */ + } while (num_returned && num_remaining); + + if (ret) { + v->num_levels = 0; + kfree(v->levels_uv); + } + + ph->xops->reset_rx_to_maxsz(ph, td); + } + + ph->xops->xfer_put(ph, tl); +outd: + ph->xops->xfer_put(ph, td); + + return ret; +} + +static int __scmi_voltage_get_u32(const struct scmi_protocol_handle *ph, + u8 cmd_id, u32 domain_id, u32 *value) +{ + int ret; + struct scmi_xfer *t; + struct voltage_info *vinfo = ph->get_priv(ph); + + if (domain_id >= vinfo->num_domains) + return -EINVAL; + + ret = ph->xops->xfer_get_init(ph, cmd_id, sizeof(__le32), 0, &t); + if (ret) + return ret; + + put_unaligned_le32(domain_id, t->tx.buf); + ret = ph->xops->do_xfer(ph, t); + if (!ret) + *value = get_unaligned_le32(t->rx.buf); + + ph->xops->xfer_put(ph, t); + return ret; +} + +static int scmi_voltage_config_set(const struct scmi_protocol_handle *ph, + u32 domain_id, u32 config) +{ + int ret; + struct scmi_xfer *t; + struct voltage_info *vinfo = ph->get_priv(ph); + struct scmi_msg_cmd_config_set *cmd; + + if (domain_id >= vinfo->num_domains) + return -EINVAL; + + ret = ph->xops->xfer_get_init(ph, VOLTAGE_CONFIG_SET, + sizeof(*cmd), 0, &t); + if (ret) + return ret; + + cmd = t->tx.buf; + cmd->domain_id = cpu_to_le32(domain_id); + cmd->config = cpu_to_le32(config & GENMASK(3, 0)); + + ret = ph->xops->do_xfer(ph, t); + + ph->xops->xfer_put(ph, t); + return ret; +} + +static int scmi_voltage_config_get(const struct scmi_protocol_handle *ph, + u32 domain_id, u32 *config) +{ + return __scmi_voltage_get_u32(ph, VOLTAGE_CONFIG_GET, + domain_id, config); +} + +static int scmi_voltage_level_set(const struct scmi_protocol_handle *ph, + u32 domain_id, u32 flags, s32 volt_uV) +{ + int ret; + struct scmi_xfer *t; + struct voltage_info *vinfo = ph->get_priv(ph); + struct scmi_msg_cmd_level_set *cmd; + + if (domain_id >= vinfo->num_domains) + return -EINVAL; + + ret = ph->xops->xfer_get_init(ph, VOLTAGE_LEVEL_SET, + sizeof(*cmd), 0, &t); + if (ret) + return ret; + + cmd = t->tx.buf; + cmd->domain_id = cpu_to_le32(domain_id); + cmd->flags = cpu_to_le32(flags); + cmd->voltage_level = cpu_to_le32(volt_uV); + + ret = ph->xops->do_xfer(ph, t); + + ph->xops->xfer_put(ph, t); + return ret; +} + +static int scmi_voltage_level_get(const struct scmi_protocol_handle *ph, + u32 domain_id, s32 *volt_uV) +{ + return __scmi_voltage_get_u32(ph, VOLTAGE_LEVEL_GET, + domain_id, (u32 *)volt_uV); +} + +static const struct scmi_voltage_info * __must_check +scmi_voltage_info_get(const struct scmi_protocol_handle *ph, u32 domain_id) +{ + struct voltage_info *vinfo = ph->get_priv(ph); + + if (domain_id >= vinfo->num_domains || + !vinfo->domains[domain_id].num_levels) + return NULL; + + return vinfo->domains + domain_id; +} + +static int scmi_voltage_domains_num_get(const struct scmi_protocol_handle *ph) +{ + struct voltage_info *vinfo = ph->get_priv(ph); + + return vinfo->num_domains; +} + +static struct scmi_voltage_proto_ops voltage_proto_ops = { + .num_domains_get = scmi_voltage_domains_num_get, + .info_get = scmi_voltage_info_get, + .config_set = scmi_voltage_config_set, + .config_get = scmi_voltage_config_get, + .level_set = scmi_voltage_level_set, + .level_get = scmi_voltage_level_get, +}; + +static int scmi_voltage_protocol_init(const struct scmi_protocol_handle *ph) +{ + int ret; + u32 version; + struct voltage_info *vinfo; + + ret = ph->xops->version_get(ph, &version); + if (ret) + return ret; + + dev_dbg(ph->dev, "Voltage Version %d.%d\n", + PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version)); + + vinfo = kzalloc(sizeof(*vinfo), GFP_KERNEL); + if (!vinfo) + return -ENOMEM; + vinfo->version = version; + + ret = scmi_protocol_attributes_get(ph, vinfo); + if (ret) + return ret; + + if (vinfo->num_domains) { + vinfo->domains = kcalloc(vinfo->num_domains, + sizeof(*vinfo->domains), + GFP_KERNEL); + if (!vinfo->domains) + return -ENOMEM; + ret = scmi_voltage_descriptors_get(ph, vinfo); + if (ret) + return ret; + } else { + dev_warn(ph->dev, "No Voltage domains found.\n"); + } + + return ph->set_priv(ph, vinfo); +} + +static const struct scmi_protocol scmi_voltage = { + .id = SCMI_PROTOCOL_VOLTAGE, + .instance_init = &scmi_voltage_protocol_init, + .ops = &voltage_proto_ops, +}; + +DEFINE_SCMI_PROTOCOL_REGISTER(voltage, scmi_voltage) diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index c468e45915..38a47b7bc2 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -55,4 +55,13 @@ config REGULATOR_ANATOP regulators. It is recommended that this option be enabled on i.MX6 platform. +config REGULATOR_ARM_SCMI + tristate "SCMI based regulator driver" + depends on ARM_SCMI_PROTOCOL && OFDEVICE + help + This adds the regulator driver support for ARM platforms using SCMI + protocol for device voltage management. + This driver uses SCMI Message Protocol driver to interact with the + firmware providing the device Voltage functionality. + endif diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 7b5d527cb1..5eaa657ad1 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_REGULATOR_STPMIC1) += stpmic1_regulator.o obj-$(CONFIG_REGULATOR_ANATOP) += anatop-regulator.o obj-$(CONFIG_REGULATOR_STM32_PWR) += stm32-pwr.o obj-$(CONFIG_REGULATOR_STM32_VREFBUF) += stm32-vrefbuf.o +obj-$(CONFIG_REGULATOR_ARM_SCMI) += scmi-regulator.o diff --git a/drivers/regulator/scmi-regulator.c b/drivers/regulator/scmi-regulator.c new file mode 100644 index 0000000000..3e6fd3ec60 --- /dev/null +++ b/drivers/regulator/scmi-regulator.c @@ -0,0 +1,391 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// System Control and Management Interface (SCMI) based regulator driver +// +// Copyright (C) 2020-2021 ARM Ltd. +// +// Implements a regulator driver on top of the SCMI Voltage Protocol. +// +// The ARM SCMI Protocol aims in general to hide as much as possible all the +// underlying operational details while providing an abstracted interface for +// its users to operate upon: as a consequence the resulting operational +// capabilities and configurability of this regulator device are much more +// limited than the ones usually available on a standard physical regulator. +// +// The supported SCMI regulator ops are restricted to the bare minimum: +// +// - 'status_ops': enable/disable/is_enabled +// - 'voltage_ops': get_voltage_sel/set_voltage_sel +// list_voltage/map_voltage +// +// Each SCMI regulator instance is associated, through the means of a proper DT +// entry description, to a specific SCMI Voltage Domain. + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include + +static const struct scmi_voltage_proto_ops *voltage_ops; + +struct scmi_reg_desc { + struct regulator_desc desc; + const char *name; + const char *supply_name; +}; + +struct scmi_regulator { + u32 id; + struct scmi_device *sdev; + struct scmi_protocol_handle *ph; + struct regulator_dev rdev; + struct device_node *device_node; + struct scmi_reg_desc sdesc; +}; + +struct scmi_regulator_info { + int num_doms; + struct scmi_regulator **sregv; +}; + +static inline struct scmi_regulator *to_scmi_regulator(struct regulator_dev *rdev) +{ + return container_of(rdev, struct scmi_regulator, rdev); +} + +static int scmi_reg_enable(struct regulator_dev *rdev) +{ + struct scmi_regulator *sreg = to_scmi_regulator(rdev); + + return voltage_ops->config_set(sreg->ph, sreg->id, + SCMI_VOLTAGE_ARCH_STATE_ON); +} + +static int scmi_reg_disable(struct regulator_dev *rdev) +{ + struct scmi_regulator *sreg = to_scmi_regulator(rdev); + + return voltage_ops->config_set(sreg->ph, sreg->id, + SCMI_VOLTAGE_ARCH_STATE_OFF); +} + +static int scmi_reg_is_enabled(struct regulator_dev *rdev) +{ + int ret; + u32 config; + struct scmi_regulator *sreg = to_scmi_regulator(rdev); + struct scmi_reg_desc *sdesc = &sreg->sdesc; + + ret = voltage_ops->config_get(sreg->ph, sreg->id, &config); + if (ret) { + dev_err(&sreg->sdev->dev, + "Error %d reading regulator %s status.\n", + ret, sdesc->name); + return ret; + } + + return config & SCMI_VOLTAGE_ARCH_STATE_ON; +} + +static int scmi_reg_get_voltage_sel(struct regulator_dev *rdev) +{ + int ret; + s32 volt_uV; + struct scmi_regulator *sreg = to_scmi_regulator(rdev); + + ret = voltage_ops->level_get(sreg->ph, sreg->id, &volt_uV); + if (ret) + return ret; + + return sreg->sdesc.desc.ops->map_voltage(rdev, volt_uV, volt_uV); +} + +static int scmi_reg_set_voltage_sel(struct regulator_dev *rdev, + unsigned int selector) +{ + s32 volt_uV; + struct scmi_regulator *sreg = to_scmi_regulator(rdev); + + volt_uV = sreg->sdesc.desc.ops->list_voltage(rdev, selector); + if (volt_uV <= 0) + return -EINVAL; + + return voltage_ops->level_set(sreg->ph, sreg->id, 0x0, volt_uV); +} + +static const struct regulator_ops scmi_reg_fixed_ops = { + .enable = scmi_reg_enable, + .disable = scmi_reg_disable, + .is_enabled = scmi_reg_is_enabled, +}; + +static const struct regulator_ops scmi_reg_linear_ops = { + .enable = scmi_reg_enable, + .disable = scmi_reg_disable, + .is_enabled = scmi_reg_is_enabled, + .get_voltage_sel = scmi_reg_get_voltage_sel, + .set_voltage_sel = scmi_reg_set_voltage_sel, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, +}; + +static const struct regulator_ops scmi_reg_discrete_ops = { + .enable = scmi_reg_enable, + .disable = scmi_reg_disable, + .is_enabled = scmi_reg_is_enabled, + .get_voltage_sel = scmi_reg_get_voltage_sel, + .set_voltage_sel = scmi_reg_set_voltage_sel, + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_iterate, +}; + +static int +scmi_config_linear_regulator_mappings(struct scmi_regulator *sreg, + const struct scmi_voltage_info *vinfo) +{ + struct regulator_desc *desc = &sreg->sdesc.desc; + s32 delta_uV; + + /* + * Note that SCMI voltage domains describable by linear ranges + * (segments) {low, high, step} are guaranteed to come in one single + * triplet by the SCMI Voltage Domain protocol support itself. + */ + + delta_uV = (vinfo->levels_uv[SCMI_VOLTAGE_SEGMENT_HIGH] - + vinfo->levels_uv[SCMI_VOLTAGE_SEGMENT_LOW]); + + /* Rule out buggy negative-intervals answers from fw */ + if (delta_uV < 0) { + dev_err(&sreg->sdev->dev, + "Invalid volt-range %d-%duV for domain %d\n", + vinfo->levels_uv[SCMI_VOLTAGE_SEGMENT_LOW], + vinfo->levels_uv[SCMI_VOLTAGE_SEGMENT_HIGH], + sreg->id); + return -EINVAL; + } + + if (!delta_uV) { + /* Just one fixed voltage exposed by SCMI */ + desc->fixed_uV = + vinfo->levels_uv[SCMI_VOLTAGE_SEGMENT_LOW]; + desc->n_voltages = 1; + desc->ops = &scmi_reg_fixed_ops; + } else { + /* One simple linear mapping. */ + desc->min_uV = + vinfo->levels_uv[SCMI_VOLTAGE_SEGMENT_LOW]; + desc->uV_step = + vinfo->levels_uv[SCMI_VOLTAGE_SEGMENT_STEP]; + desc->linear_min_sel = 0; + desc->n_voltages = (delta_uV / desc->uV_step) + 1; + desc->ops = &scmi_reg_linear_ops; + } + + return 0; +} + +static int +scmi_config_discrete_regulator_mappings(struct scmi_regulator *sreg, + const struct scmi_voltage_info *vinfo) +{ + struct regulator_desc *desc = &sreg->sdesc.desc; + + /* Discrete non linear levels are mapped to volt_table */ + desc->n_voltages = vinfo->num_levels; + + if (desc->n_voltages > 1) { + desc->volt_table = (const unsigned int *)vinfo->levels_uv; + desc->ops = &scmi_reg_discrete_ops; + } else { + desc->fixed_uV = vinfo->levels_uv[0]; + desc->ops = &scmi_reg_fixed_ops; + } + + return 0; +} + +static int scmi_regulator_common_init(struct scmi_regulator *sreg) +{ + int ret; + struct device_d *dev = &sreg->sdev->dev; + const struct scmi_voltage_info *vinfo; + struct scmi_reg_desc *sdesc = &sreg->sdesc; + + vinfo = voltage_ops->info_get(sreg->ph, sreg->id); + if (!vinfo) { + dev_warn(dev, "Failure to get voltage domain %d\n", + sreg->id); + return -ENODEV; + } + + /* + * Regulator framework does not fully support negative voltages + * so we discard any voltage domain reported as supporting negative + * voltages: as a consequence each levels_uv entry is guaranteed to + * be non-negative from here on. + */ + if (vinfo->negative_volts_allowed) { + dev_warn(dev, "Negative voltages NOT supported...skip %s\n", + sreg->device_node->full_name); + return -EOPNOTSUPP; + } + + sdesc->name = basprintf("%s", vinfo->name); + if (!sdesc->name) + return -ENOMEM; + + if (vinfo->segmented) + ret = scmi_config_linear_regulator_mappings(sreg, vinfo); + else + ret = scmi_config_discrete_regulator_mappings(sreg, vinfo); + + return ret; +} + +static int process_scmi_regulator_of_node(struct scmi_device *sdev, + struct scmi_protocol_handle *ph, + struct device_node *np, + struct scmi_regulator_info *rinfo) +{ + u32 dom, ret; + + ret = of_property_read_u32(np, "reg", &dom); + if (ret) + return ret; + + if (dom >= rinfo->num_doms) + return -ENODEV; + + if (rinfo->sregv[dom]) { + dev_err(&sdev->dev, + "SCMI Voltage Domain %d already in use. Skipping: %s\n", + dom, np->full_name); + return -EINVAL; + } + + rinfo->sregv[dom] = kzalloc(sizeof(struct scmi_regulator), GFP_KERNEL); + if (!rinfo->sregv[dom]) + return -ENOMEM; + + rinfo->sregv[dom]->id = dom; + rinfo->sregv[dom]->sdev = sdev; + rinfo->sregv[dom]->ph = ph; + + /* get hold of good nodes */ + rinfo->sregv[dom]->device_node = np; + + dev_dbg(&sdev->dev, + "Found SCMI Regulator entry -- OF node [%d] -> %s\n", + dom, np->full_name); + + return 0; +} + +static int scmi_regulator_probe(struct scmi_device *sdev) +{ + int d, ret, num_doms; + struct device_node *np, *child; + const struct scmi_handle *handle = sdev->handle; + struct scmi_regulator_info *rinfo; + struct scmi_protocol_handle *ph; + struct scmi_reg_desc *sdesc; + + if (!handle) + return -ENODEV; + + voltage_ops = handle->protocol_get(sdev, SCMI_PROTOCOL_VOLTAGE, &ph); + if (IS_ERR(voltage_ops)) + return PTR_ERR(voltage_ops); + + num_doms = voltage_ops->num_domains_get(ph); + if (num_doms <= 0) { + if (!num_doms) { + dev_err(&sdev->dev, + "number of voltage domains invalid\n"); + num_doms = -EINVAL; + } else { + dev_err(&sdev->dev, + "failed to get voltage domains - err:%d\n", + num_doms); + } + + return num_doms; + } + + rinfo = kzalloc(sizeof(*rinfo), GFP_KERNEL); + if (!rinfo) + return -ENOMEM; + + /* Allocate pointers array for all possible domains */ + rinfo->sregv = kcalloc(num_doms, sizeof(void *), GFP_KERNEL); + if (!rinfo->sregv) + return -ENOMEM; + + rinfo->num_doms = num_doms; + + /* + * Start collecting into rinfo->sregv possibly good SCMI Regulators as + * described by a well-formed DT entry and associated with an existing + * plausible SCMI Voltage Domain number, all belonging to this SCMI + * platform instance node (handle->dev->of_node). + */ + np = of_find_node_by_name(handle->dev->device_node, "regulators"); + for_each_child_of_node(np, child) { + ret = process_scmi_regulator_of_node(sdev, ph, child, rinfo); + /* abort on any mem issue */ + if (ret == -ENOMEM) + return ret; + } + + /* + * Register a regulator for each valid regulator-DT-entry that we + * can successfully reach via SCMI and has a valid associated voltage + * domain. + */ + for (d = 0; d < num_doms; d++) { + struct scmi_regulator *sreg = rinfo->sregv[d]; + + /* Skip empty slots */ + if (!sreg) + continue; + + sdesc = &sreg->sdesc; + + ret = scmi_regulator_common_init(sreg); + /* Skip invalid voltage domains */ + if (ret) + continue; + + ret = of_regulator_register(&sreg->rdev, np); + if (ret) + continue; + + dev_info(&sdev->dev, + "Regulator %s registered for domain [%d]\n", + sreg->sdesc.name, sreg->id); + } + + return 0; +} + +static const struct scmi_device_id scmi_regulator_id_table[] = { + { SCMI_PROTOCOL_VOLTAGE, "regulator" }, + { }, +}; + +static struct scmi_driver scmi_drv = { + .name = "scmi-regulator", + .probe = scmi_regulator_probe, + .id_table = scmi_regulator_id_table, +}; +core_scmi_driver(scmi_drv); + +MODULE_AUTHOR("Cristian Marussi "); +MODULE_DESCRIPTION("ARM SCMI regulator driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 203d97c81e9c9a2f5bcc268d9399899a306cb722 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Sun, 20 Feb 2022 13:47:28 +0100 Subject: clk: accept const arguments in clk_to_clk_hw/clk_hw_to_clk Driver code may want to pass const pointers into these functions. Change the implementation to support this. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20220220124736.3052502-17-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- include/linux/clk.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/linux/clk.h b/include/linux/clk.h index 9bee204652..9396e01003 100644 --- a/include/linux/clk.h +++ b/include/linux/clk.h @@ -431,14 +431,14 @@ struct clk_hw { const struct clk_init_data *init; }; -static inline struct clk *clk_hw_to_clk(struct clk_hw *hw) +static inline struct clk *clk_hw_to_clk(const struct clk_hw *hw) { - return IS_ERR(hw) ? ERR_CAST(hw) : &hw->clk; + return IS_ERR(hw) ? ERR_CAST(hw) : (struct clk *)&hw->clk; } -static inline struct clk_hw *clk_to_clk_hw(struct clk *clk) +static inline struct clk_hw *clk_to_clk_hw(const struct clk *clk) { - return IS_ERR(clk) ? ERR_CAST(clk) : container_of(clk, struct clk_hw, clk); + return IS_ERR(clk) ? ERR_CAST(clk) : (struct clk_hw *)container_of(clk, struct clk_hw, clk); } struct clk_div_table { -- cgit v1.2.3 From dd069035be60469d49b76ae48856db2dc357bc4f Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Sun, 20 Feb 2022 13:47:29 +0100 Subject: serial: stm32: bail if clock_get_rate returns zero While this shouldn't happen in a well-behaving system, with SCMI barebox may become dependent on the firmware to provide a non-zero rate here. Sanitize the value to avoid a division -by-zero. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20220220124736.3052502-18-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- drivers/serial/serial_stm32.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/serial/serial_stm32.c b/drivers/serial/serial_stm32.c index 271897f174..f1d4c6de90 100644 --- a/drivers/serial/serial_stm32.c +++ b/drivers/serial/serial_stm32.c @@ -41,6 +41,8 @@ static int stm32_serial_setbaudrate(struct console_device *cdev, int baudrate) unsigned long clock_rate; clock_rate = clk_get_rate(stm32->clk); + if (!clock_rate) + return -EINVAL; int_div = DIV_ROUND_CLOSEST(clock_rate, baudrate); -- cgit v1.2.3 From 054ccac22bd136b6cfd152024389bc8e4e604921 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Sun, 20 Feb 2022 13:47:30 +0100 Subject: clk: implement of_clk_hw_{onecell,simple}_get These functions are easily implemented and make porting code easier. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20220220124736.3052502-19-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- drivers/clk/clk.c | 22 ++++++++++++++++++++++ include/linux/clk.h | 17 +++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index 227ab75ed6..8e317b4b05 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -578,6 +578,12 @@ struct clk *of_clk_src_simple_get(struct of_phandle_args *clkspec, } EXPORT_SYMBOL_GPL(of_clk_src_simple_get); +struct clk_hw *of_clk_hw_simple_get(struct of_phandle_args *clkspec, void *data) +{ + return data; +} +EXPORT_SYMBOL_GPL(of_clk_hw_simple_get); + struct clk *of_clk_src_onecell_get(struct of_phandle_args *clkspec, void *data) { struct clk_onecell_data *clk_data = data; @@ -592,6 +598,22 @@ struct clk *of_clk_src_onecell_get(struct of_phandle_args *clkspec, void *data) } EXPORT_SYMBOL_GPL(of_clk_src_onecell_get); +struct clk_hw * +of_clk_hw_onecell_get(struct of_phandle_args *clkspec, void *data) +{ + struct clk_hw_onecell_data *hw_data = data; + unsigned int idx = clkspec->args[0]; + + if (idx >= hw_data->num) { + pr_err("%s: invalid index %u\n", __func__, idx); + return ERR_PTR(-EINVAL); + } + + return hw_data->hws[idx]; +} +EXPORT_SYMBOL_GPL(of_clk_hw_onecell_get); + + static int __of_clk_add_provider(struct device_node *np, struct clk *(*clk_src_get)(struct of_phandle_args *clkspec, void *data), diff --git a/include/linux/clk.h b/include/linux/clk.h index 9396e01003..c0e998e54a 100644 --- a/include/linux/clk.h +++ b/include/linux/clk.h @@ -778,6 +778,11 @@ struct clk_onecell_data { unsigned int clk_num; }; +struct clk_hw_onecell_data { + unsigned int num; + struct clk_hw *hws[]; +}; + #if defined(CONFIG_COMMON_CLK_OF_PROVIDER) #define CLK_OF_DECLARE(name, compat, fn) \ @@ -791,6 +796,8 @@ typedef int (*of_clk_init_cb_t)(struct device_node *); struct clk *of_clk_src_onecell_get(struct of_phandle_args *clkspec, void *data); struct clk *of_clk_src_simple_get(struct of_phandle_args *clkspec, void *data); +struct clk_hw *of_clk_hw_onecell_get(struct of_phandle_args *clkspec, void *data); +struct clk_hw *of_clk_hw_simple_get(struct of_phandle_args *clkspec, void *data); struct clk *of_clk_get(struct device_node *np, int index); struct clk *of_clk_get_by_name(struct device_node *np, const char *name); @@ -826,11 +833,21 @@ static inline struct clk *of_clk_src_onecell_get(struct of_phandle_args *clkspec { return ERR_PTR(-ENOENT); } +static inline struct clk_hw *of_clk_hw_onecell_get(struct of_phandle_args *clkspec, + void *data) +{ + return ERR_PTR(-ENOENT); +} static inline struct clk * of_clk_src_simple_get(struct of_phandle_args *clkspec, void *data) { return ERR_PTR(-ENOENT); } +static inline struct clk * +of_clk_hw_simple_get(struct of_phandle_args *clkspec, void *data) +{ + return ERR_PTR(-ENOENT); +} static inline struct clk *of_clk_get(struct device_node *np, int index) { return ERR_PTR(-ENOENT); -- cgit v1.2.3 From b63e5ff58fc4a8ff029592ac12d6e0c8d54ef658 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Sun, 20 Feb 2022 13:47:32 +0100 Subject: reset: add support for reset_control_status Driver code may want to query status of a reset line. Add an optional callback for drivers to provide this. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20220220124736.3052502-21-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- drivers/reset/core.c | 21 +++++++++++++++++++++ drivers/reset/reset-simple.c | 3 ++- include/linux/reset-controller.h | 2 ++ include/linux/reset.h | 6 ++++++ 4 files changed, 31 insertions(+), 1 deletion(-) diff --git a/drivers/reset/core.c b/drivers/reset/core.c index 17deb3fa8f..4355c3415e 100644 --- a/drivers/reset/core.c +++ b/drivers/reset/core.c @@ -81,6 +81,27 @@ void reset_controller_unregister(struct reset_controller_dev *rcdev) } EXPORT_SYMBOL_GPL(reset_controller_unregister); +/** + * reset_control_status - returns a negative errno if not supported, a + * positive value if the reset line is asserted, or zero if the reset + * line is not asserted or if the desc is NULL (optional reset). + * @rstc: reset controller + */ +int reset_control_status(struct reset_control *rstc) +{ + if (!rstc) + return 0; + + if (WARN_ON(IS_ERR(rstc))) + return -EINVAL; + + if (rstc->rcdev->ops->status) + return rstc->rcdev->ops->status(rstc->rcdev, rstc->id); + + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(reset_control_status); + /** * reset_control_reset - reset the controlled device * @rstc: reset controller diff --git a/drivers/reset/reset-simple.c b/drivers/reset/reset-simple.c index 082956d94d..9db00f64f4 100644 --- a/drivers/reset/reset-simple.c +++ b/drivers/reset/reset-simple.c @@ -75,7 +75,7 @@ static int reset_simple_reset(struct reset_controller_dev *rcdev, return reset_simple_deassert(rcdev, id); } -static int __maybe_unused reset_simple_status(struct reset_controller_dev *rcdev, +static int reset_simple_status(struct reset_controller_dev *rcdev, unsigned long id) { struct reset_simple_data *data = to_reset_simple_data(rcdev); @@ -93,6 +93,7 @@ const struct reset_control_ops reset_simple_ops = { .assert = reset_simple_assert, .deassert = reset_simple_deassert, .reset = reset_simple_reset, + .status = reset_simple_status, }; EXPORT_SYMBOL_GPL(reset_simple_ops); diff --git a/include/linux/reset-controller.h b/include/linux/reset-controller.h index 8ace8cd417..a9047e947e 100644 --- a/include/linux/reset-controller.h +++ b/include/linux/reset-controller.h @@ -14,11 +14,13 @@ struct reset_controller_dev; * things to reset the device * @assert: manually assert the reset line, if supported * @deassert: manually deassert the reset line, if supported + * @status: return the status of the reset line, if supported */ struct reset_control_ops { int (*reset)(struct reset_controller_dev *rcdev, unsigned long id); int (*assert)(struct reset_controller_dev *rcdev, unsigned long id); int (*deassert)(struct reset_controller_dev *rcdev, unsigned long id); + int (*status)(struct reset_controller_dev *rcdev, unsigned long id); }; struct device_node; diff --git a/include/linux/reset.h b/include/linux/reset.h index d25464d4bd..d0677b1d9f 100644 --- a/include/linux/reset.h +++ b/include/linux/reset.h @@ -8,6 +8,7 @@ struct reset_control; #ifdef CONFIG_RESET_CONTROLLER +int reset_control_status(struct reset_control *rstc); int reset_control_reset(struct reset_control *rstc); int reset_control_assert(struct reset_control *rstc); int reset_control_deassert(struct reset_control *rstc); @@ -25,6 +26,11 @@ int __must_check device_reset_all(struct device_d *dev); #else +static inline int reset_control_status(struct reset_control *rstc) +{ + return 0; +} + static inline int reset_control_reset(struct reset_control *rstc) { return 0; -- cgit v1.2.3 From 261c22952ee38a3764d395676558bad4ad3a6b5f Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Sun, 20 Feb 2022 13:47:33 +0100 Subject: clk: stm32mp1: sync with Linux v5.17-rc1 Linux has meanwhile extended the RCC driver to support both reset and clocks as well as peaceful co-existence with the SCMI driver. Import these changes into barebox and remove the reset controller handling from the old RCC reset driver. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20220220124736.3052502-22-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- drivers/clk/clk-stm32mp1.c | 899 +++++++++++++++++++++++++------------ drivers/power/reset/Kconfig | 5 +- drivers/power/reset/stm32-reboot.c | 60 +-- include/driver.h | 5 + include/soc/stm32/reboot.h | 16 + 5 files changed, 651 insertions(+), 334 deletions(-) create mode 100644 include/soc/stm32/reboot.h diff --git a/drivers/clk/clk-stm32mp1.c b/drivers/clk/clk-stm32mp1.c index 8f1ba96e63..c4b03e9f6d 100644 --- a/drivers/clk/clk-stm32mp1.c +++ b/drivers/clk/clk-stm32mp1.c @@ -6,14 +6,22 @@ */ #include +#include #include +#include #include #include #include -#include +#include +#include +#include +#include +#include #include +static DEFINE_SPINLOCK(rlock); + #define RCC_OCENSETR 0x0C #define RCC_HSICFGR 0x18 #define RCC_RDLSICR 0x144 @@ -116,7 +124,7 @@ static const char * const cpu_src[] = { }; static const char * const axi_src[] = { - "ck_hsi", "ck_hse", "pll2_p", "pll3_p" + "ck_hsi", "ck_hse", "pll2_p" }; static const char * const per_src[] = { @@ -220,19 +228,19 @@ static const char * const usart6_src[] = { }; static const char * const fdcan_src[] = { - "ck_hse", "pll3_q", "pll4_q" + "ck_hse", "pll3_q", "pll4_q", "pll4_r" }; static const char * const sai_src[] = { - "pll4_q", "pll3_q", "i2s_ckin", "ck_per" + "pll4_q", "pll3_q", "i2s_ckin", "ck_per", "pll3_r" }; static const char * const sai2_src[] = { - "pll4_q", "pll3_q", "i2s_ckin", "ck_per", "spdif_ck_symb" + "pll4_q", "pll3_q", "i2s_ckin", "ck_per", "spdif_ck_symb", "pll3_r" }; static const char * const adc12_src[] = { - "pll4_q", "ck_per" + "pll4_r", "ck_per", "pll3_q" }; static const char * const dsi_src[] = { @@ -240,7 +248,7 @@ static const char * const dsi_src[] = { }; static const char * const rtc_src[] = { - "off", "ck_lse", "ck_lsi", "ck_hse_rtc" + "off", "ck_lse", "ck_lsi", "ck_hse" }; static const char * const mco1_src[] = { @@ -264,7 +272,7 @@ static const struct clk_div_table axi_div_table[] = { static const struct clk_div_table mcu_div_table[] = { { 0, 1 }, { 1, 2 }, { 2, 4 }, { 3, 8 }, { 4, 16 }, { 5, 32 }, { 6, 64 }, { 7, 128 }, - { 8, 512 }, { 9, 512 }, { 10, 512}, { 11, 512 }, + { 8, 256 }, { 9, 512 }, { 10, 512}, { 11, 512 }, { 12, 512 }, { 13, 512 }, { 14, 512}, { 15, 512 }, { 0 }, }; @@ -312,7 +320,10 @@ struct clock_config { int num_parents; unsigned long flags; void *cfg; - struct clk * (*func)(void __iomem *base, const struct clock_config *cfg); + struct clk_hw * (*func)(struct device_d *dev, + struct clk_hw_onecell_data *clk_data, + void __iomem *base, spinlock_t *lock, + const struct clock_config *cfg); }; #define NO_ID ~0 @@ -368,53 +379,69 @@ struct stm32_composite_cfg { const struct stm32_mux_cfg *mux; }; -static struct clk * -_clk_hw_register_gate(void __iomem *base, +static struct clk_hw * +_clk_hw_register_gate(struct device_d *dev, + struct clk_hw_onecell_data *clk_data, + void __iomem *base, spinlock_t *lock, const struct clock_config *cfg) { struct gate_cfg *gate_cfg = cfg->cfg; - return clk_gate(cfg->name, cfg->parent_name, gate_cfg->reg_off + base, - gate_cfg->bit_idx, cfg->flags, gate_cfg->gate_flags); + return clk_hw_register_gate(dev, + cfg->name, + cfg->parent_name, + cfg->flags, + gate_cfg->reg_off + base, + gate_cfg->bit_idx, + gate_cfg->gate_flags, + lock); } -static struct clk * -_clk_hw_register_fixed_factor(void __iomem *base, +static struct clk_hw * +_clk_hw_register_fixed_factor(struct device_d *dev, + struct clk_hw_onecell_data *clk_data, + void __iomem *base, spinlock_t *lock, const struct clock_config *cfg) { struct fixed_factor_cfg *ff_cfg = cfg->cfg; - return clk_fixed_factor(cfg->name, cfg->parent_name, ff_cfg->mult, - ff_cfg->div, cfg->flags); + return clk_hw_register_fixed_factor(dev, cfg->name, cfg->parent_name, + cfg->flags, ff_cfg->mult, + ff_cfg->div); } -static struct clk * -_clk_hw_register_divider_table(void __iomem *base, +static struct clk_hw * +_clk_hw_register_divider_table(struct device_d *dev, + struct clk_hw_onecell_data *clk_data, + void __iomem *base, spinlock_t *lock, const struct clock_config *cfg) { - struct div_cfg *div_cfg = cfg->cfg; - if (div_cfg->table) - return clk_divider_table(cfg->name, cfg->parent_name, cfg->flags, - div_cfg->reg_off + base, div_cfg->shift, - div_cfg->width, div_cfg->table, - div_cfg->div_flags); - else - return clk_divider(cfg->name, cfg->parent_name, cfg->flags, - div_cfg->reg_off + base, div_cfg->shift, - div_cfg->width, div_cfg->div_flags); + return clk_hw_register_divider_table(dev, + cfg->name, + cfg->parent_name, + cfg->flags, + div_cfg->reg_off + base, + div_cfg->shift, + div_cfg->width, + div_cfg->div_flags, + div_cfg->table, + lock); } -static struct clk * -_clk_hw_register_mux(void __iomem *base, +static struct clk_hw * +_clk_hw_register_mux(struct device_d *dev, + struct clk_hw_onecell_data *clk_data, + void __iomem *base, spinlock_t *lock, const struct clock_config *cfg) { struct mux_cfg *mux_cfg = cfg->cfg; - return clk_mux(cfg->name,cfg->flags, mux_cfg->reg_off + base, mux_cfg->shift, - mux_cfg->width, cfg->parent_names, cfg->num_parents, - mux_cfg->mux_flags); + return clk_hw_register_mux(dev, cfg->name, cfg->parent_names, + cfg->num_parents, cfg->flags, + mux_cfg->reg_off + base, mux_cfg->shift, + mux_cfg->width, mux_cfg->mux_flags, lock); } /* MP1 Gate clock with set & clear registers */ @@ -430,9 +457,12 @@ static int mp1_gate_clk_enable(struct clk_hw *hw) static void mp1_gate_clk_disable(struct clk_hw *hw) { struct clk_gate *gate = to_clk_gate(hw); + unsigned long flags = 0; if (clk_gate_ops.is_enabled(hw)) { - writel(BIT(gate->shift), gate->reg + RCC_CLR); + spin_lock_irqsave(gate->lock, flags); + writel_relaxed(BIT(gate->shift), gate->reg + RCC_CLR); + spin_unlock_irqrestore(gate->lock, flags); } } @@ -442,8 +472,9 @@ static const struct clk_ops mp1_gate_clk_ops = { .is_enabled = clk_gate_is_enabled, }; -static struct clk_hw *_get_stm32_mux(void __iomem *base, - const struct stm32_mux_cfg *cfg) +static struct clk_hw *_get_stm32_mux(struct device_d *dev, void __iomem *base, + const struct stm32_mux_cfg *cfg, + spinlock_t *lock) { struct stm32_clk_mmux *mmux; struct clk_mux *mux; @@ -457,10 +488,13 @@ static struct clk_hw *_get_stm32_mux(void __iomem *base, mmux->mux.reg = cfg->mux->reg_off + base; mmux->mux.shift = cfg->mux->shift; mmux->mux.width = cfg->mux->width; + mmux->mux.flags = cfg->mux->mux_flags; + mmux->mux.table = cfg->mux->table; + mmux->mux.lock = lock; mmux->mmux = cfg->mmux; mux_hw = &mmux->mux.hw; cfg->mmux->hws[cfg->mmux->nbr_clk++] = mux_hw; - mux = &mmux->mux; + } else { mux = kzalloc(sizeof(*mux), GFP_KERNEL); if (!mux) @@ -469,23 +503,23 @@ static struct clk_hw *_get_stm32_mux(void __iomem *base, mux->reg = cfg->mux->reg_off + base; mux->shift = cfg->mux->shift; mux->width = cfg->mux->width; + mux->flags = cfg->mux->mux_flags; + mux->table = cfg->mux->table; + mux->lock = lock; mux_hw = &mux->hw; } - if (cfg->ops) - mux->hw.clk.ops = cfg->ops; - else - mux->hw.clk.ops = &clk_mux_ops; - return mux_hw; } -static struct clk_hw *_get_stm32_div(void __iomem *base, - const struct stm32_div_cfg *cfg) +static struct clk_hw *_get_stm32_div(struct device_d *dev, void __iomem *base, + const struct stm32_div_cfg *cfg, + spinlock_t *lock) { struct clk_divider *div; div = kzalloc(sizeof(*div), GFP_KERNEL); + if (!div) return ERR_PTR(-ENOMEM); @@ -494,21 +528,18 @@ static struct clk_hw *_get_stm32_div(void __iomem *base, div->width = cfg->div->width; div->flags = cfg->div->div_flags; div->table = cfg->div->table; - - if (cfg->ops) - div->hw.clk.ops = cfg->ops; - else - div->hw.clk.ops = &clk_divider_ops; + div->lock = lock; return &div->hw; } -static struct clk_gate * -_get_stm32_gate(void __iomem *base, - const struct stm32_gate_cfg *cfg) +static struct clk_hw *_get_stm32_gate(struct device_d *dev, void __iomem *base, + const struct stm32_gate_cfg *cfg, + spinlock_t *lock) { struct stm32_clk_mgate *mgate; struct clk_gate *gate; + struct clk_hw *gate_hw; if (cfg->mgate) { mgate = kzalloc(sizeof(*mgate), GFP_KERNEL); @@ -518,11 +549,12 @@ _get_stm32_gate(void __iomem *base, mgate->gate.reg = cfg->gate->reg_off + base; mgate->gate.shift = cfg->gate->bit_idx; mgate->gate.flags = cfg->gate->gate_flags; + mgate->gate.lock = lock; mgate->mask = BIT(cfg->mgate->nbr_clk++); mgate->mgate = cfg->mgate; - gate = &mgate->gate; + gate_hw = &mgate->gate.hw; } else { gate = kzalloc(sizeof(*gate), GFP_KERNEL); @@ -532,71 +564,103 @@ _get_stm32_gate(void __iomem *base, gate->reg = cfg->gate->reg_off + base; gate->shift = cfg->gate->bit_idx; gate->flags = cfg->gate->gate_flags; + gate->lock = lock; + + gate_hw = &gate->hw; } - - if (cfg->ops) - gate->hw.clk.ops = cfg->ops; - else - gate->hw.clk.ops = &clk_gate_ops; - return gate; + return gate_hw; } -static struct clk * -clk_stm32_register_gate_ops(const char *name, +static struct clk_hw * +clk_stm32_register_gate_ops(struct device_d *dev, + const char *name, const char *parent_name, unsigned long flags, void __iomem *base, - const struct stm32_gate_cfg *cfg) + const struct stm32_gate_cfg *cfg, + spinlock_t *lock) { - struct clk *clk; - struct clk_gate *gate; + struct clk_init_data init = { NULL }; + struct clk_hw *hw; int ret; - gate = _get_stm32_gate(base, cfg); - if (IS_ERR(gate)) + init.name = name; + init.parent_names = &parent_name; + init.num_parents = 1; + init.flags = flags; + + init.ops = &clk_gate_ops; + + if (cfg->ops) + init.ops = cfg->ops; + + hw = _get_stm32_gate(dev, base, cfg, lock); + if (IS_ERR(hw)) return ERR_PTR(-ENOMEM); - gate->parent = parent_name; - clk = &gate->hw.clk; - clk->name = name; - clk->parent_names = &gate->parent; - clk->num_parents = 1; - clk->flags = flags; + hw->init = &init; - ret = bclk_register(clk); + ret = clk_hw_register(dev, hw); if (ret) - clk = ERR_PTR(ret); + hw = ERR_PTR(ret); - return clk; + return hw; } -static struct clk * -clk_stm32_register_composite(const char *name, const char * const *parent_names, +static struct clk_hw * +clk_stm32_register_composite(struct device_d *dev, + const char *name, const char * const *parent_names, int num_parents, void __iomem *base, const struct stm32_composite_cfg *cfg, - unsigned long flags) + unsigned long flags, spinlock_t *lock) { + const struct clk_ops *mux_ops, *div_ops, *gate_ops; struct clk_hw *mux_hw, *div_hw, *gate_hw; - struct clk_gate *gate; mux_hw = NULL; div_hw = NULL; gate_hw = NULL; + mux_ops = NULL; + div_ops = NULL; + gate_ops = NULL; + + if (cfg->mux) { + mux_hw = _get_stm32_mux(dev, base, cfg->mux, lock); + + if (!IS_ERR(mux_hw)) { + mux_ops = &clk_mux_ops; + + if (cfg->mux->ops) + mux_ops = cfg->mux->ops; + } + } + + if (cfg->div) { + div_hw = _get_stm32_div(dev, base, cfg->div, lock); - if (cfg->mux) - mux_hw = _get_stm32_mux(base, cfg->mux); + if (!IS_ERR(div_hw)) { + div_ops = &clk_divider_ops; - if (cfg->div) - div_hw = _get_stm32_div(base, cfg->div); + if (cfg->div->ops) + div_ops = cfg->div->ops; + } + } if (cfg->gate) { - gate = _get_stm32_gate(base, cfg->gate); - gate_hw = &gate->hw; + gate_hw = _get_stm32_gate(dev, base, cfg->gate, lock); + + if (!IS_ERR(gate_hw)) { + gate_ops = &clk_gate_ops; + + if (cfg->gate->ops) + gate_ops = cfg->gate->ops; + } } - return clk_register_composite(name, parent_names, num_parents, - &mux_hw->clk, &div_hw->clk, &gate_hw->clk, flags); + return clk_hw_register_composite(dev, name, parent_names, num_parents, + mux_hw, mux_ops, div_hw, div_ops, + gate_hw, gate_ops, flags); } #define to_clk_mgate(_gate) container_of(_gate, struct stm32_clk_mgate, gate) @@ -640,36 +704,25 @@ static int clk_mmux_get_parent(struct clk_hw *hw) static int clk_mmux_set_parent(struct clk_hw *hw, u8 index) { - struct clk_mux *mux = to_clk_mux(hw); - struct stm32_clk_mmux *clk_mmux = to_clk_mmux(mux); - struct clk_hw *hwp; - int ret, n; - - ret = clk_mux_ops.set_parent(hw, index); - if (ret) - return ret; - - hwp = clk_hw_get_parent(hw); - - for (n = 0; n < clk_mmux->mmux->nbr_clk; n++) - clk_hw_set_parent(clk_mmux->mmux->hws[n], hw); - - return 0; + return clk_mux_ops.set_parent(hw, index); } static const struct clk_ops clk_mmux_ops = { .get_parent = clk_mmux_get_parent, .set_parent = clk_mmux_set_parent, + .round_rate = clk_mux_round_rate, }; /* STM32 PLL */ struct stm32_pll_obj { + /* lock pll enable/disable registers */ + spinlock_t *lock; void __iomem *reg; - const char *parent; - struct clk clk; + struct clk_hw hw; + struct clk_mux mux; }; -#define to_pll(clk) container_of(clk, struct stm32_pll_obj, clk) +#define to_pll(_hw) container_of(_hw, struct stm32_pll_obj, hw) #define PLL_ON BIT(0) #define PLL_RDY BIT(1) @@ -681,30 +734,34 @@ struct stm32_pll_obj { #define FRAC_MASK 0x1FFF #define FRAC_SHIFT 3 #define FRACLE BIT(16) +#define PLL_MUX_SHIFT 0 +#define PLL_MUX_WIDTH 2 -static int __pll_is_enabled(struct clk *clk) +static int __pll_is_enabled(struct clk_hw *hw) { - struct stm32_pll_obj *clk_elem = to_pll(clk); + struct stm32_pll_obj *clk_elem = to_pll(hw); - return readl(clk_elem->reg) & PLL_ON; + return readl_relaxed(clk_elem->reg) & PLL_ON; } #define TIMEOUT 5 static int pll_enable(struct clk_hw *hw) { - struct clk *clk = clk_hw_to_clk(hw); - struct stm32_pll_obj *clk_elem = to_pll(clk); + struct stm32_pll_obj *clk_elem = to_pll(hw); u32 reg; + unsigned long flags = 0; unsigned int timeout = TIMEOUT; int bit_status = 0; - if (__pll_is_enabled(clk)) + spin_lock_irqsave(clk_elem->lock, flags); + + if (__pll_is_enabled(hw)) goto unlock; - reg = readl(clk_elem->reg); + reg = readl_relaxed(clk_elem->reg); reg |= PLL_ON; - writel(reg, clk_elem->reg); + writel_relaxed(reg, clk_elem->reg); /* We can't use readl_poll_timeout() because we can be blocked if * someone enables this clock before clocksource changes. @@ -712,7 +769,7 @@ static int pll_enable(struct clk_hw *hw) * interruptions and enable op does not allow to be interrupted. */ do { - bit_status = !(readl(clk_elem->reg) & PLL_RDY); + bit_status = !(readl_relaxed(clk_elem->reg) & PLL_RDY); if (bit_status) udelay(120); @@ -720,26 +777,32 @@ static int pll_enable(struct clk_hw *hw) } while (bit_status && --timeout); unlock: + spin_unlock_irqrestore(clk_elem->lock, flags); + return bit_status; } static void pll_disable(struct clk_hw *hw) { - struct clk *clk = clk_hw_to_clk(hw); - struct stm32_pll_obj *clk_elem = to_pll(clk); + struct stm32_pll_obj *clk_elem = to_pll(hw); u32 reg; + unsigned long flags = 0; + + spin_lock_irqsave(clk_elem->lock, flags); - reg = readl(clk_elem->reg); + reg = readl_relaxed(clk_elem->reg); reg &= ~PLL_ON; - writel(reg, clk_elem->reg); + writel_relaxed(reg, clk_elem->reg); + + spin_unlock_irqrestore(clk_elem->lock, flags); } -static u32 pll_frac_val(struct clk *clk) +static u32 pll_frac_val(struct clk_hw *hw) { - struct stm32_pll_obj *clk_elem = to_pll(clk); + struct stm32_pll_obj *clk_elem = to_pll(hw); u32 reg, frac = 0; - reg = readl(clk_elem->reg + FRAC_OFFSET); + reg = readl_relaxed(clk_elem->reg + FRAC_OFFSET); if (reg & FRACLE) frac = (reg >> FRAC_SHIFT) & FRAC_MASK; @@ -749,13 +812,12 @@ static u32 pll_frac_val(struct clk *clk) static unsigned long pll_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) { - struct clk *clk = clk_hw_to_clk(hw); - struct stm32_pll_obj *clk_elem = to_pll(clk); + struct stm32_pll_obj *clk_elem = to_pll(hw); u32 reg; u32 frac, divm, divn; u64 rate, rate_frac = 0; - reg = readl(clk_elem->reg + 4); + reg = readl_relaxed(clk_elem->reg + 4); divm = ((reg >> DIVM_SHIFT) & DIVM_MASK) + 1; divn = ((reg >> DIVN_SHIFT) & DIVN_MASK) + 1; @@ -763,7 +825,7 @@ static unsigned long pll_recalc_rate(struct clk_hw *hw, do_div(rate, divm); - frac = pll_frac_val(clk); + frac = pll_frac_val(hw); if (frac) { rate_frac = (u64)parent_rate * (u64)frac; do_div(rate_frac, (divm * 8192)); @@ -772,79 +834,89 @@ static unsigned long pll_recalc_rate(struct clk_hw *hw, return rate + rate_frac; } -static int pll_is_enabled(struct clk_hw *hw) +static int pll_get_parent(struct clk_hw *hw) { - struct clk *clk = clk_hw_to_clk(hw); - int ret; + struct stm32_pll_obj *clk_elem = to_pll(hw); + struct clk_hw *mux_hw = &clk_elem->mux.hw; - ret = __pll_is_enabled(clk); + mux_hw->clk = hw->clk; - return ret; + return clk_mux_ops.get_parent(mux_hw); } static const struct clk_ops pll_ops = { .enable = pll_enable, .disable = pll_disable, .recalc_rate = pll_recalc_rate, - .is_enabled = pll_is_enabled, + .is_enabled = __pll_is_enabled, + .get_parent = pll_get_parent, }; -static struct clk *clk_register_pll(const char *name, - const char *parent_name, - void __iomem *reg, - unsigned long flags) +static struct clk_hw *clk_register_pll(struct device_d *dev, const char *name, + const char * const *parent_names, + int num_parents, + void __iomem *reg, + void __iomem *mux_reg, + unsigned long flags, + spinlock_t *lock) { struct stm32_pll_obj *element; - struct clk *clk; + struct clk_init_data init; + struct clk_hw *hw; int err; element = kzalloc(sizeof(*element), GFP_KERNEL); if (!element) return ERR_PTR(-ENOMEM); - element->parent = parent_name; - - clk = &element->clk; + init.name = name; + init.ops = &pll_ops; + init.flags = flags; + init.parent_names = parent_names; + init.num_parents = num_parents; - clk->name = name; - clk->ops = &pll_ops; - clk->flags = flags; - clk->parent_names = &element->parent; - clk->num_parents = 1; + element->mux.lock = lock; + element->mux.reg = mux_reg; + element->mux.shift = PLL_MUX_SHIFT; + element->mux.width = PLL_MUX_WIDTH; + element->mux.flags = CLK_MUX_READ_ONLY; + element->mux.reg = mux_reg; + element->hw.init = &init; element->reg = reg; + element->lock = lock; - err = bclk_register(clk); + hw = &element->hw; + err = clk_hw_register(dev, hw); - if (err) { - kfree(element); + if (err) return ERR_PTR(err); - } - return clk; + return hw; } /* Kernel Timer */ struct timer_cker { + /* lock the kernel output divider register */ + spinlock_t *lock; void __iomem *apbdiv; void __iomem *timpre; - const char *parent; - struct clk clk; + struct clk_hw hw; }; -#define to_timer_cker(_hw) container_of(_hw, struct timer_cker, clk) +#define to_timer_cker(_hw) container_of(_hw, struct timer_cker, hw) #define APB_DIV_MASK 0x07 #define TIM_PRE_MASK 0x01 -static unsigned long __bestmult(struct clk *clk, unsigned long rate, +static unsigned long __bestmult(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { - struct timer_cker *tim_ker = to_timer_cker(clk); + struct timer_cker *tim_ker = to_timer_cker(hw); u32 prescaler; unsigned int mult = 0; - prescaler = readl(tim_ker->apbdiv) & APB_DIV_MASK; + prescaler = readl_relaxed(tim_ker->apbdiv) & APB_DIV_MASK; if (prescaler < 2) return 1; @@ -859,8 +931,7 @@ static unsigned long __bestmult(struct clk *clk, unsigned long rate, static long timer_ker_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *parent_rate) { - struct clk *clk = clk_hw_to_clk(hw); - unsigned long factor = __bestmult(clk, rate, *parent_rate); + unsigned long factor = __bestmult(hw, rate, *parent_rate); return *parent_rate * factor; } @@ -868,23 +939,26 @@ static long timer_ker_round_rate(struct clk_hw *hw, unsigned long rate, static int timer_ker_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { - struct clk *clk = clk_hw_to_clk(hw); - struct timer_cker *tim_ker = to_timer_cker(clk); - unsigned long factor = __bestmult(clk, rate, parent_rate); + struct timer_cker *tim_ker = to_timer_cker(hw); + unsigned long flags = 0; + unsigned long factor = __bestmult(hw, rate, parent_rate); int ret = 0; + spin_lock_irqsave(tim_ker->lock, flags); + switch (factor) { case 1: break; case 2: - writel(0, tim_ker->timpre); + writel_relaxed(0, tim_ker->timpre); break; case 4: - writel(1, tim_ker->timpre); + writel_relaxed(1, tim_ker->timpre); break; default: ret = -EINVAL; } + spin_unlock_irqrestore(tim_ker->lock, flags); return ret; } @@ -892,14 +966,13 @@ static int timer_ker_set_rate(struct clk_hw *hw, unsigned long rate, static unsigned long timer_ker_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) { - struct clk *clk = clk_hw_to_clk(hw); - struct timer_cker *tim_ker = to_timer_cker(clk); + struct timer_cker *tim_ker = to_timer_cker(hw); u32 prescaler, timpre; u32 mul; - prescaler = readl(tim_ker->apbdiv) & APB_DIV_MASK; + prescaler = readl_relaxed(tim_ker->apbdiv) & APB_DIV_MASK; - timpre = readl(tim_ker->timpre) & TIM_PRE_MASK; + timpre = readl_relaxed(tim_ker->timpre) & TIM_PRE_MASK; if (!prescaler) return parent_rate; @@ -916,51 +989,59 @@ static const struct clk_ops timer_ker_ops = { }; -static struct clk *clk_register_cktim(const char *name, +static struct clk_hw *clk_register_cktim(struct device_d *dev, const char *name, const char *parent_name, unsigned long flags, void __iomem *apbdiv, - void __iomem *timpre) + void __iomem *timpre, + spinlock_t *lock) { struct timer_cker *tim_ker; - struct clk *clk; + struct clk_init_data init; + struct clk_hw *hw; int err; tim_ker = kzalloc(sizeof(*tim_ker), GFP_KERNEL); if (!tim_ker) return ERR_PTR(-ENOMEM); - clk = &tim_ker->clk; - tim_ker->parent = parent_name; - clk->name = name; - clk->parent_names = &tim_ker->parent; - clk->num_parents = 1; - clk->ops = &timer_ker_ops; - clk->flags = flags; + init.name = name; + init.ops = &timer_ker_ops; + init.flags = flags; + init.parent_names = &parent_name; + init.num_parents = 1; + tim_ker->hw.init = &init; + tim_ker->lock = lock; tim_ker->apbdiv = apbdiv; tim_ker->timpre = timpre; - err = bclk_register(clk); - if (err) { - kfree(tim_ker); + hw = &tim_ker->hw; + err = clk_hw_register(dev, hw); + + if (err) return ERR_PTR(err); - } - return clk; + return hw; } struct stm32_pll_cfg { u32 offset; + u32 muxoff; }; -static struct clk *_clk_register_pll(void __iomem *base, - const struct clock_config *cfg) +static struct clk_hw *_clk_register_pll(struct device_d *dev, + struct clk_hw_onecell_data *clk_data, + void __iomem *base, spinlock_t *lock, + const struct clock_config *cfg) { struct stm32_pll_cfg *stm_pll_cfg = cfg->cfg; - return clk_register_pll(cfg->name, cfg->parent_name, - base + stm_pll_cfg->offset, cfg->flags); + return clk_register_pll(dev, cfg->name, cfg->parent_names, + cfg->num_parents, + base + stm_pll_cfg->offset, + base + stm_pll_cfg->muxoff, + cfg->flags, lock); } struct stm32_cktim_cfg { @@ -968,31 +1049,42 @@ struct stm32_cktim_cfg { u32 offset_timpre; }; -static struct clk *_clk_register_cktim(void __iomem *base, - const struct clock_config *cfg) +static struct clk_hw *_clk_register_cktim(struct device_d *dev, + struct clk_hw_onecell_data *clk_data, + void __iomem *base, spinlock_t *lock, + const struct clock_config *cfg) { struct stm32_cktim_cfg *cktim_cfg = cfg->cfg; - return clk_register_cktim(cfg->name, cfg->parent_name, cfg->flags, + return clk_register_cktim(dev, cfg->name, cfg->parent_name, cfg->flags, cktim_cfg->offset_apbdiv + base, - cktim_cfg->offset_timpre + base); + cktim_cfg->offset_timpre + base, lock); } -static struct clk * -_clk_stm32_register_gate(void __iomem *base, +static struct clk_hw * +_clk_stm32_register_gate(struct device_d *dev, + struct clk_hw_onecell_data *clk_data, + void __iomem *base, spinlock_t *lock, const struct clock_config *cfg) { - return clk_stm32_register_gate_ops(cfg->name, cfg->parent_name, - cfg->flags, base, cfg->cfg); + return clk_stm32_register_gate_ops(dev, + cfg->name, + cfg->parent_name, + cfg->flags, + base, + cfg->cfg, + lock); } -static struct clk * -_clk_stm32_register_composite(void __iomem *base, +static struct clk_hw * +_clk_stm32_register_composite(struct device_d *dev, + struct clk_hw_onecell_data *clk_data, + void __iomem *base, spinlock_t *lock, const struct clock_config *cfg) { - return clk_stm32_register_composite(cfg->name, cfg->parent_names, + return clk_stm32_register_composite(dev, cfg->name, cfg->parent_names, cfg->num_parents, base, cfg->cfg, - cfg->flags); + cfg->flags, lock); } #define GATE(_id, _name, _parent, _flags, _offset, _bit_idx, _gate_flags)\ @@ -1059,14 +1151,16 @@ _clk_stm32_register_composite(void __iomem *base, .func = _clk_hw_register_mux,\ } -#define PLL(_id, _name, _parent, _flags, _offset)\ +#define PLL(_id, _name, _parents, _flags, _offset_p, _offset_mux)\ {\ .id = _id,\ .name = _name,\ - .parent_name = _parent,\ - .flags = _flags,\ + .parent_names = _parents,\ + .num_parents = ARRAY_SIZE(_parents),\ + .flags = CLK_IGNORE_UNUSED | (_flags),\ .cfg = &(struct stm32_pll_cfg) {\ - .offset = _offset,\ + .offset = _offset_p,\ + .muxoff = _offset_mux,\ },\ .func = _clk_register_pll,\ } @@ -1099,13 +1193,14 @@ _clk_stm32_register_composite(void __iomem *base, .func = _clk_stm32_register_gate,\ } -#define _STM32_GATE(_gate_offset, _gate_bit_idx, _gate_flags, _ops)\ +#define _STM32_GATE(_gate_offset, _gate_bit_idx, _gate_flags, _mgate, _ops)\ (&(struct stm32_gate_cfg) {\ &(struct gate_cfg) {\ .reg_off = _gate_offset,\ .bit_idx = _gate_bit_idx,\ .gate_flags = _gate_flags,\ },\ + .mgate = _mgate,\ .ops = _ops,\ }) @@ -1114,11 +1209,11 @@ _clk_stm32_register_composite(void __iomem *base, #define _GATE(_gate_offset, _gate_bit_idx, _gate_flags)\ _STM32_GATE(_gate_offset, _gate_bit_idx, _gate_flags,\ - NULL)\ + NULL, NULL)\ #define _GATE_MP1(_gate_offset, _gate_bit_idx, _gate_flags)\ _STM32_GATE(_gate_offset, _gate_bit_idx, _gate_flags,\ - &mp1_gate_clk_ops)\ + NULL, &mp1_gate_clk_ops)\ #define _MGATE_MP1(_mgate)\ .gate = &per_gate_cfg[_mgate] @@ -1191,10 +1286,11 @@ _clk_stm32_register_composite(void __iomem *base, MGATE_MP1(_id, _name, _parent, _flags, _mgate) #define KCLK(_id, _name, _parents, _flags, _mgate, _mmux)\ - COMPOSITE(_id, _name, _parents, CLK_OPS_PARENT_ENABLE | _flags,\ - _MGATE_MP1(_mgate),\ - _MMUX(_mmux),\ - _NO_DIV) + COMPOSITE(_id, _name, _parents, CLK_OPS_PARENT_ENABLE |\ + CLK_SET_RATE_NO_REPARENT | _flags,\ + _MGATE_MP1(_mgate),\ + _MMUX(_mmux),\ + _NO_DIV) enum { G_SAI1, @@ -1306,6 +1402,7 @@ enum { G_CRYP1, G_HASH1, G_BKPSRAM, + G_DDRPERFM, G_LAST }; @@ -1392,6 +1489,7 @@ static struct stm32_gate_cfg per_gate_cfg[G_LAST] = { K_GATE(G_STGENRO, RCC_APB4ENSETR, 20, 0), K_MGATE(G_USBPHY, RCC_APB4ENSETR, 16, 0), K_GATE(G_IWDG2, RCC_APB4ENSETR, 15, 0), + K_GATE(G_DDRPERFM, RCC_APB4ENSETR, 8, 0), K_MGATE(G_DSI, RCC_APB4ENSETR, 4, 0), K_MGATE(G_LTDC, RCC_APB4ENSETR, 0, 0), @@ -1533,7 +1631,7 @@ static const struct stm32_mux_cfg ker_mux_cfg[M_LAST] = { K_MMUX(M_ETHCK, RCC_ETHCKSELR, 0, 2, 0), K_MMUX(M_I2C46, RCC_I2C46CKSELR, 0, 3, 0), - /* Kernel simple mux */ + /* Kernel simple mux */ K_MUX(M_RNG2, RCC_RNG2CKSELR, 0, 2, 0), K_MUX(M_SDMMC3, RCC_SDMMC3CKSELR, 0, 3, 0), K_MUX(M_FMC, RCC_FMCCKSELR, 0, 2, 0), @@ -1559,34 +1657,26 @@ static const struct stm32_mux_cfg ker_mux_cfg[M_LAST] = { }; static const struct clock_config stm32mp1_clock_cfg[] = { - /* Oscillator divider */ - DIV(NO_ID, "clk-hsi-div", "clk-hsi", 0, RCC_HSICFGR, 0, 2, - CLK_DIVIDER_READ_ONLY), - /* External / Internal Oscillators */ GATE_MP1(CK_HSE, "ck_hse", "clk-hse", 0, RCC_OCENSETR, 8, 0), - GATE_MP1(CK_CSI, "ck_csi", "clk-csi", 0, RCC_OCENSETR, 4, 0), - GATE_MP1(CK_HSI, "ck_hsi", "clk-hsi-div", 0, RCC_OCENSETR, 0, 0), + /* ck_csi is used by IO compensation and should be critical */ + GATE_MP1(CK_CSI, "ck_csi", "clk-csi", CLK_IS_CRITICAL, + RCC_OCENSETR, 4, 0), + COMPOSITE(CK_HSI, "ck_hsi", PARENT("clk-hsi"), 0, + _GATE_MP1(RCC_OCENSETR, 0, 0), + _NO_MUX, + _DIV(RCC_HSICFGR, 0, 2, CLK_DIVIDER_POWER_OF_TWO | + CLK_DIVIDER_READ_ONLY, NULL)), GATE(CK_LSI, "ck_lsi", "clk-lsi", 0, RCC_RDLSICR, 0, 0), GATE(CK_LSE, "ck_lse", "clk-lse", 0, RCC_BDCR, 0, 0), FIXED_FACTOR(CK_HSE_DIV2, "clk-hse-div2", "ck_hse", 0, 1, 2), - /* ref clock pll */ - MUX(NO_ID, "ref1", ref12_parents, CLK_OPS_PARENT_ENABLE, RCC_RCK12SELR, - 0, 2, CLK_MUX_READ_ONLY), - - MUX(NO_ID, "ref3", ref3_parents, CLK_OPS_PARENT_ENABLE, RCC_RCK3SELR, - 0, 2, CLK_MUX_READ_ONLY), - - MUX(NO_ID, "ref4", ref4_parents, CLK_OPS_PARENT_ENABLE, RCC_RCK4SELR, - 0, 2, CLK_MUX_READ_ONLY), - /* PLLs */ - PLL(PLL1, "pll1", "ref1", CLK_IGNORE_UNUSED, RCC_PLL1CR), - PLL(PLL2, "pll2", "ref1", CLK_IGNORE_UNUSED, RCC_PLL2CR), - PLL(PLL3, "pll3", "ref3", CLK_IGNORE_UNUSED, RCC_PLL3CR), - PLL(PLL4, "pll4", "ref4", CLK_IGNORE_UNUSED, RCC_PLL4CR), + PLL(PLL1, "pll1", ref12_parents, 0, RCC_PLL1CR, RCC_RCK12SELR), + PLL(PLL2, "pll2", ref12_parents, 0, RCC_PLL2CR, RCC_RCK12SELR), + PLL(PLL3, "pll3", ref3_parents, 0, RCC_PLL3CR, RCC_RCK3SELR), + PLL(PLL4, "pll4", ref4_parents, 0, RCC_PLL4CR, RCC_RCK4SELR), /* ODF */ COMPOSITE(PLL1_P, "pll1_p", PARENT("pll1"), 0, @@ -1801,6 +1891,7 @@ static const struct clock_config stm32mp1_clock_cfg[] = { PCLK(CRC1, "crc1", "ck_axi", 0, G_CRC1), PCLK(USBH, "usbh", "ck_axi", 0, G_USBH), PCLK(ETHSTP, "ethstp", "ck_axi", 0, G_ETHSTP), + PCLK(DDRPERFM, "ddrperfm", "pclk4", 0, G_DDRPERFM), /* Kernel clocks */ KCLK(SDMMC1_K, "sdmmc1_k", sdmmc12_src, 0, G_SDMMC1, M_SDMMC12), @@ -1857,18 +1948,17 @@ static const struct clock_config stm32mp1_clock_cfg[] = { MGATE_MP1(GPU_K, "gpu_k", "pll2_q", 0, G_GPU), MGATE_MP1(DAC12_K, "dac12_k", "ck_lsi", 0, G_DAC12), - COMPOSITE(ETHPTP_K, "ethptp_k", eth_src, CLK_OPS_PARENT_ENABLE, + COMPOSITE(ETHPTP_K, "ethptp_k", eth_src, CLK_OPS_PARENT_ENABLE | + CLK_SET_RATE_NO_REPARENT, _NO_GATE, _MMUX(M_ETHCK), _DIV(RCC_ETHCKSELR, 4, 4, 0, NULL)), /* RTC clock */ - DIV(NO_ID, "ck_hse_rtc", "ck_hse", 0, RCC_RTCDIVR, 0, 7, 0), - COMPOSITE(RTC, "ck_rtc", rtc_src, CLK_OPS_PARENT_ENABLE | - CLK_SET_RATE_PARENT, - _GATE(RCC_BDCR, 20, 0), - _MUX(RCC_BDCR, 16, 2, 0), + CLK_SET_RATE_PARENT, + _NO_GATE, + _NO_MUX, _NO_DIV), /* MCO clocks */ @@ -1894,16 +1984,76 @@ static const struct clock_config stm32mp1_clock_cfg[] = { _DIV(RCC_DBGCFGR, 0, 3, 0, ck_trace_div_table)), }; -struct stm32_clock_match_data { +static const u32 stm32mp1_clock_secured[] = { + CK_HSE, + CK_HSI, + CK_CSI, + CK_LSI, + CK_LSE, + PLL1, + PLL2, + PLL1_P, + PLL2_P, + PLL2_Q, + PLL2_R, + CK_MPU, + CK_AXI, + SPI6, + I2C4, + I2C6, + USART1, + RTCAPB, + TZC1, + TZC2, + TZPC, + IWDG1, + BSEC, + STGEN, + GPIOZ, + CRYP1, + HASH1, + RNG1, + BKPSRAM, + RNG1_K, + STGEN_K, + SPI6_K, + I2C4_K, + I2C6_K, + USART1_K, + RTC, +}; + +static bool stm32_check_security(const struct clock_config *cfg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(stm32mp1_clock_secured); i++) + if (cfg->id == stm32mp1_clock_secured[i]) + return true; + return false; +} + +struct stm32_rcc_match_data { const struct clock_config *cfg; unsigned int num; unsigned int maxbinding; + u32 clear_offset; + bool (*check_security)(const struct clock_config *cfg); +}; + +static struct stm32_rcc_match_data stm32mp1_data = { + .cfg = stm32mp1_clock_cfg, + .num = ARRAY_SIZE(stm32mp1_clock_cfg), + .maxbinding = STM32MP1_LAST_CLK, + .clear_offset = RCC_CLR, }; -static struct stm32_clock_match_data stm32mp1_data = { +static struct stm32_rcc_match_data stm32mp1_data_secure = { .cfg = stm32mp1_clock_cfg, .num = ARRAY_SIZE(stm32mp1_clock_cfg), .maxbinding = STM32MP1_LAST_CLK, + .clear_offset = RCC_CLR, + .check_security = &stm32_check_security }; static const struct of_device_id stm32mp1_match_data[] = { @@ -1911,85 +2061,282 @@ static const struct of_device_id stm32mp1_match_data[] = { .compatible = "st,stm32mp1-rcc", .data = &stm32mp1_data, }, + { + .compatible = "st,stm32mp1-rcc-secure", + .data = &stm32mp1_data_secure, + }, { } }; -static int stm32_register_hw_clk(struct clk_onecell_data *clk_data, - void __iomem *base, +static int stm32_register_hw_clk(struct device_d *dev, + struct clk_hw_onecell_data *clk_data, + void __iomem *base, spinlock_t *lock, const struct clock_config *cfg) { - struct clk *clk = ERR_PTR(-ENOENT); + struct clk_hw **hws; + struct clk_hw *hw = ERR_PTR(-ENOENT); + + hws = clk_data->hws; if (cfg->func) - clk = (*cfg->func)(base, cfg); + hw = (*cfg->func)(dev, clk_data, base, lock, cfg); - if (IS_ERR(clk)) { + if (IS_ERR(hw)) { pr_err("Unable to register %s\n", cfg->name); - return PTR_ERR(clk); + return PTR_ERR(hw); } if (cfg->id != NO_ID) - clk_data->clks[cfg->id] = clk; + hws[cfg->id] = hw; return 0; } -static int stm32_rcc_init(struct device_node *np, - void __iomem *base, - const struct of_device_id *match_data) +#define STM32_RESET_ID_MASK GENMASK(15, 0) + +struct stm32_reset_data { + /* reset lock */ + spinlock_t lock; + struct reset_controller_dev rcdev; + void __iomem *membase; + u32 clear_offset; +}; + +static inline struct stm32_reset_data * +to_stm32_reset_data(struct reset_controller_dev *rcdev) { - struct clk_onecell_data *clk_data; - struct clk **clks; - const struct of_device_id *match; - const struct stm32_clock_match_data *data; - int err, n, max_binding; + return container_of(rcdev, struct stm32_reset_data, rcdev); +} - match = of_match_node(match_data, np); - if (!match) { - pr_err("%s: match data not found\n", __func__); - return -ENODEV; +static int stm32_reset_update(struct reset_controller_dev *rcdev, + unsigned long id, bool assert) +{ + struct stm32_reset_data *data = to_stm32_reset_data(rcdev); + int reg_width = sizeof(u32); + int bank = id / (reg_width * BITS_PER_BYTE); + int offset = id % (reg_width * BITS_PER_BYTE); + + if (data->clear_offset) { + void __iomem *addr; + + addr = data->membase + (bank * reg_width); + if (!assert) + addr += data->clear_offset; + + writel(BIT(offset), addr); + + } else { + unsigned long flags; + u32 reg; + + spin_lock_irqsave(&data->lock, flags); + + reg = readl(data->membase + (bank * reg_width)); + + if (assert) + reg |= BIT(offset); + else + reg &= ~BIT(offset); + + writel(reg, data->membase + (bank * reg_width)); + + spin_unlock_irqrestore(&data->lock, flags); } - data = match->data; + return 0; +} + +static int stm32_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return stm32_reset_update(rcdev, id, true); +} + +static int stm32_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return stm32_reset_update(rcdev, id, false); +} + +static int stm32_reset_status(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct stm32_reset_data *data = to_stm32_reset_data(rcdev); + int reg_width = sizeof(u32); + int bank = id / (reg_width * BITS_PER_BYTE); + int offset = id % (reg_width * BITS_PER_BYTE); + u32 reg; + + reg = readl(data->membase + (bank * reg_width)); - max_binding = data->maxbinding; + return !!(reg & BIT(offset)); +} - clk_data = xzalloc(sizeof(*clk_data)); - clk_data->clks = xzalloc(sizeof(void *) * max_binding); - clk_data->clk_num = max_binding; +static const struct reset_control_ops stm32_reset_ops = { + .assert = stm32_reset_assert, + .deassert = stm32_reset_deassert, + .status = stm32_reset_status, +}; - clks = clk_data->clks; +static int stm32_rcc_reset_init(struct device_d *dev, void __iomem *base, + const struct of_device_id *match) +{ + const struct stm32_rcc_match_data *data = match->data; + struct stm32_reset_data *reset_data = NULL; + + reset_data = kzalloc(sizeof(*reset_data), GFP_KERNEL); + if (!reset_data) + return -ENOMEM; + + reset_data->membase = base; + reset_data->rcdev.ops = &stm32_reset_ops; + reset_data->rcdev.of_node = dev_of_node(dev); + reset_data->rcdev.nr_resets = STM32_RESET_ID_MASK; + reset_data->clear_offset = data->clear_offset; + + return reset_controller_register(&reset_data->rcdev); +} + +static int stm32_rcc_clock_init(struct device_d *dev, void __iomem *base, + const struct of_device_id *match) +{ + const struct stm32_rcc_match_data *data = match->data; + struct clk_hw_onecell_data *clk_data; + struct clk_hw **hws; + int err, n, max_binding; + + max_binding = data->maxbinding; + + clk_data = kzalloc(struct_size(clk_data, hws, max_binding), + GFP_KERNEL); + if (!clk_data) + return -ENOMEM; + + clk_data->num = max_binding; + + hws = clk_data->hws; for (n = 0; n < max_binding; n++) - clks[n] = ERR_PTR(-ENOENT); + hws[n] = ERR_PTR(-ENOENT); for (n = 0; n < data->num; n++) { - err = stm32_register_hw_clk(clk_data, base, + if (data->check_security && data->check_security(&data->cfg[n])) + continue; + + err = stm32_register_hw_clk(dev, clk_data, base, &rlock, &data->cfg[n]); if (err) { - pr_err("%s: can't register %s\n", __func__, - data->cfg[n].name); - - kfree(clk_data); + dev_err(dev, "Can't register clk %s: %d\n", + data->cfg[n].name, err); return err; } } - return of_clk_add_provider(np, of_clk_src_onecell_get, clk_data); + return of_clk_add_hw_provider(dev_of_node(dev), of_clk_hw_onecell_get, clk_data); } -static void stm32mp1_rcc_init(struct device_node *np) +static int stm32_rcc_init(struct device_d *dev, void __iomem *base, + const struct of_device_id *match_data) +{ + const struct of_device_id *match; + int err; + + match = of_match_node(match_data, dev_of_node(dev)); + if (!match) { + dev_err(dev, "match data not found\n"); + return -ENODEV; + } + + /* RCC Reset Configuration */ + err = stm32_rcc_reset_init(dev, base, match); + if (err) { + pr_err("stm32mp1 reset failed to initialize\n"); + return err; + } + + /* RCC Clock Configuration */ + err = stm32_rcc_clock_init(dev, base, match); + if (err) { + pr_err("stm32mp1 clock failed to initialize\n"); + return err; + } + + return 0; +} + +static int stm32mp1_rcc_init(struct device_d *dev) { void __iomem *base; + int ret; - base = of_iomap(np, 0); + base = of_iomap(dev_of_node(dev), 0); if (!base) { - pr_err("%pOFn: unable to map resource", np); - return; + dev_err(dev, "unable to map resource\n"); + return -ENOMEM; } - stm32_rcc_init(np, base, stm32mp1_match_data); + ret = stm32_rcc_init(dev, base, stm32mp1_match_data); + if (ret) + return ret; + + stm32mp_system_restart_init(base); + return 0; +} + +static int get_clock_deps(struct device_d *dev) +{ + static const char * const clock_deps_name[] = { + "hsi", "hse", "csi", "lsi", "lse", + }; + size_t deps_size = sizeof(struct clk *) * ARRAY_SIZE(clock_deps_name); + struct clk **clk_deps; + int i; + + clk_deps = kzalloc(deps_size, GFP_KERNEL); + if (!clk_deps) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(clock_deps_name); i++) { + struct clk *clk = of_clk_get_by_name(dev_of_node(dev), + clock_deps_name[i]); + + if (IS_ERR(clk)) { + if (PTR_ERR(clk) != -EINVAL && PTR_ERR(clk) != -ENOENT) + return PTR_ERR(clk); + } else { + /* Device gets a reference count on the clock */ + clk_deps[i] = clk_get(dev, __clk_get_name(clk)); + clk_put(clk); + } + } + + return 0; +} + +static int stm32mp1_rcc_clocks_probe(struct device_d *dev) +{ + int ret = get_clock_deps(dev); + + if (!ret) + ret = stm32mp1_rcc_init(dev); + + return ret; +} + +static void stm32mp1_rcc_clocks_remove(struct device_d *dev) +{ + struct device_node *child, *np = dev_of_node(dev); + + for_each_available_child_of_node(np, child) + of_clk_del_provider(child); } -CLK_OF_DECLARE_DRIVER(stm32mp1_rcc, "st,stm32mp1-rcc", stm32mp1_rcc_init); +static struct driver_d stm32mp1_rcc_clocks_driver = { + .name = "stm32mp1_rcc", + .of_compatible = stm32mp1_match_data, + .probe = stm32mp1_rcc_clocks_probe, + .remove = stm32mp1_rcc_clocks_remove, +}; + +core_platform_driver(stm32mp1_rcc_clocks_driver); diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig index 40cbc4a3df..f931c117f4 100644 --- a/drivers/power/reset/Kconfig +++ b/drivers/power/reset/Kconfig @@ -64,7 +64,8 @@ config POWER_RESET_HTIF_POWEROFF supporting the UC Berkely Host/Target Interface (HTIF). config RESET_STM32 - bool "STM32 Reset Driver" + bool "STM32 restart Driver" depends on ARCH_STM32MP || COMPILE_TEST help - This enables the reset controller driver for STM32MP1. + This enables support for restarting and reset source + computation on the STM32MP1. diff --git a/drivers/power/reset/stm32-reboot.c b/drivers/power/reset/stm32-reboot.c index e625ba27ff..809531e713 100644 --- a/drivers/power/reset/stm32-reboot.c +++ b/drivers/power/reset/stm32-reboot.c @@ -6,12 +6,11 @@ */ #include -#include #include -#include #include #include #include +#include #define RCC_CL 0x4 @@ -42,15 +41,9 @@ struct stm32_reset_reason { struct stm32_reset { void __iomem *base; - struct reset_controller_dev rcdev; struct restart_handler restart; }; -static struct stm32_reset *to_stm32_reset(struct reset_controller_dev *rcdev) -{ - return container_of(rcdev, struct stm32_reset, rcdev); -} - static u32 stm32_reset_status(struct stm32_reset *priv, unsigned long bank) { return readl(priv->base + bank); @@ -115,64 +108,19 @@ static void stm32_set_reset_reason(struct stm32_reset *priv, reset_source_to_string(type), reg); } -static int stm32_reset_assert(struct reset_controller_dev *rcdev, - unsigned long id) -{ - stm32_reset(to_stm32_reset(rcdev), id, true); - return 0; -} - -static int stm32_reset_deassert(struct reset_controller_dev *rcdev, - unsigned long id) -{ - stm32_reset(to_stm32_reset(rcdev), id, false); - return 0; -} - -static const struct reset_control_ops stm32_reset_ops = { - .assert = stm32_reset_assert, - .deassert = stm32_reset_deassert, -}; - -static int stm32_reset_probe(struct device_d *dev) +void stm32mp_system_restart_init(void __iomem *base) { struct stm32_reset *priv; - struct resource *iores; - int ret; priv = xzalloc(sizeof(*priv)); - iores = dev_request_mem_resource(dev, 0); - if (IS_ERR(iores)) - return PTR_ERR(iores); - - priv->base = IOMEM(iores->start); - priv->rcdev.nr_resets = (iores->end - iores->start) * BITS_PER_BYTE; - priv->rcdev.ops = &stm32_reset_ops; - priv->rcdev.of_node = dev->device_node; + priv->base = base; priv->restart.name = "stm32-rcc"; priv->restart.restart = stm32mp_rcc_restart_handler; priv->restart.priority = 200; - ret = restart_handler_register(&priv->restart); - if (ret) - dev_warn(dev, "Cannot register restart handler\n"); + restart_handler_register(&priv->restart); stm32_set_reset_reason(priv, stm32mp_reset_reasons); - - return reset_controller_register(&priv->rcdev); } - -static const struct of_device_id stm32_rcc_reset_dt_ids[] = { - { .compatible = "st,stm32mp1-rcc" }, - { /* sentinel */ }, -}; - -static struct driver_d stm32_rcc_reset_driver = { - .name = "stm32mp_rcc_reset", - .probe = stm32_reset_probe, - .of_compatible = DRV_OF_COMPAT(stm32_rcc_reset_dt_ids), -}; - -postcore_platform_driver(stm32_rcc_reset_driver); diff --git a/include/driver.h b/include/driver.h index e59f0a164d..85cb30f8b0 100644 --- a/include/driver.h +++ b/include/driver.h @@ -598,4 +598,9 @@ int device_match_of_modalias(struct device_d *dev, struct driver_d *drv); struct device_d *device_find_child(struct device_d *parent, void *data, int (*match)(struct device_d *dev, void *data)); +static inline struct device_node *dev_of_node(struct device_d *dev) +{ + return IS_ENABLED(CONFIG_OFDEVICE) ? dev->device_node : NULL; +} + #endif /* DRIVER_H */ diff --git a/include/soc/stm32/reboot.h b/include/soc/stm32/reboot.h new file mode 100644 index 0000000000..d6c731f59f --- /dev/null +++ b/include/soc/stm32/reboot.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __SOC_STM32_REBOOT_H_ +#define __SOC_STM32_REBOOT_H_ + +#include + +#ifdef CONFIG_RESET_STM32 +void stm32mp_system_restart_init(void __iomem *rcc); +#else +static inline void stm32mp_system_restart_init(void __iomem *rcc) +{ +} +#endif + +#endif -- cgit v1.2.3 From 5e9120149025e75fc0f41f5f05be8a9ec1647192 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Sun, 20 Feb 2022 13:47:34 +0100 Subject: regulator: core: fall back to node name if no regulator-name property So far, the regulator was created anyway, but attempting to print the name by the regulator command just output a "" string. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20220220124736.3052502-23-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- drivers/regulator/core.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index b2e5f8caa2..997b986d5f 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -166,6 +166,8 @@ int of_regulator_register(struct regulator_dev *rd, struct device_node *node) rd->always_on = of_property_read_bool(node, "regulator-always-on"); name = of_get_property(node, "regulator-name", NULL); + if (!name) + name = node->name; ri = __regulator_register(rd, name); if (IS_ERR(ri)) -- cgit v1.2.3 From 4e4ad7d6c9e0318dc97d87d6e7f2ca57b7f6f871 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Sun, 20 Feb 2022 13:47:35 +0100 Subject: ARM: dts: stm32mp: remove regulator-name override in stm32mp151.dtsi barebox regulator core will now just take node name in absence of the property, so no need for this fixup any longer. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20220220124736.3052502-24-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- arch/arm/dts/stm32mp151.dtsi | 4 ---- 1 file changed, 4 deletions(-) diff --git a/arch/arm/dts/stm32mp151.dtsi b/arch/arm/dts/stm32mp151.dtsi index aac2907bc6..eac997dfce 100644 --- a/arch/arm/dts/stm32mp151.dtsi +++ b/arch/arm/dts/stm32mp151.dtsi @@ -51,10 +51,6 @@ barebox,provide-mac-address = <ðernet0 0x39>; }; -&vrefbuf { - regulator-name = "vref"; -}; - &usbphyc { vdda1v1-supply = <®11>; vdda1v8-supply = <®18>; -- cgit v1.2.3 From c1b883261f0e4916ef9e283d24bac1a579673239 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Sun, 20 Feb 2022 13:47:36 +0100 Subject: ARM: stm32mp: enable more config options We have recently gained SCMI and LTDC support as well as a new SPI display. Enable them in the defconfig. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20220220124736.3052502-25-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- arch/arm/configs/stm32mp_defconfig | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/arch/arm/configs/stm32mp_defconfig b/arch/arm/configs/stm32mp_defconfig index d682083d40..2fec3a2d3b 100644 --- a/arch/arm/configs/stm32mp_defconfig +++ b/arch/arm/configs/stm32mp_defconfig @@ -4,6 +4,7 @@ CONFIG_MACH_LXA_MC1=y CONFIG_MACH_SEEED_ODYSSEY=y CONFIG_MACH_STM32MP15X_EV1=y CONFIG_MACH_PROTONIC_STM32MP1=y +CONFIG_BOARD_ARM_GENERIC_DT=y CONFIG_THUMB2_BAREBOX=y CONFIG_ARM_BOARD_APPEND_ATAG=y CONFIG_ARM_OPTIMZED_STRING_FUNCTIONS=y @@ -118,12 +119,15 @@ CONFIG_USB_GADGET_DFU=y CONFIG_USB_GADGET_SERIAL=y CONFIG_USB_GADGET_FASTBOOT=y CONFIG_VIDEO=y +CONFIG_DRIVER_VIDEO_FB_SSD1307=y +CONFIG_DRIVER_VIDEO_STM32_LTDC=y CONFIG_DRIVER_VIDEO_BACKLIGHT=y CONFIG_DRIVER_VIDEO_SIMPLE_PANEL=y CONFIG_MCI=y CONFIG_MCI_STARTUP=y CONFIG_MCI_MMC_BOOT_PARTITIONS=y CONFIG_MCI_STM32_SDMMC2=y +CONFIG_COMMON_CLK_SCMI=y CONFIG_MFD_STPMIC1=y CONFIG_MFD_STM32_TIMERS=y CONFIG_LED=y @@ -149,12 +153,14 @@ CONFIG_REGULATOR_FIXED=y CONFIG_REGULATOR_STM32_PWR=y CONFIG_REGULATOR_STM32_VREFBUF=y CONFIG_REGULATOR_STPMIC1=y +CONFIG_REGULATOR_ARM_SCMI=y CONFIG_REMOTEPROC=y CONFIG_STM32_REMOTEPROC=y -CONFIG_RESET_STM32=y +CONFIG_ARM_SCMI_PROTOCOL=y CONFIG_GENERIC_PHY=y CONFIG_PHY_STM32_USBPHYC=y CONFIG_SYSCON_REBOOT_MODE=y +CONFIG_RESET_STM32=y CONFIG_FS_EXT4=y CONFIG_FS_TFTP=y CONFIG_FS_NFS=y -- cgit v1.2.3 From ba35a497d411468fe3b920ad42628fa8b9b479b4 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Mon, 21 Feb 2022 11:36:24 +0100 Subject: ARM: stm32mp: ddrctl: add STM32MP131 RAM size querying support Full buswidth for STM32MP131 means 2 byte wide, not 4 as the memory bus is restricted to 16-bit. Teach barebox the difference. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20220221103625.3728055-1-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- arch/arm/mach-stm32mp/ddrctrl.c | 26 +++++++++++++++++++------- arch/arm/mach-stm32mp/include/mach/revision.h | 8 ++++++++ 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/arch/arm/mach-stm32mp/ddrctrl.c b/arch/arm/mach-stm32mp/ddrctrl.c index 93996d0afc..7f0944d7e7 100644 --- a/arch/arm/mach-stm32mp/ddrctrl.c +++ b/arch/arm/mach-stm32mp/ddrctrl.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -62,7 +63,8 @@ enum ddrctrl_buswidth { }; static unsigned long ddrctrl_addrmap_ramsize(struct stm32mp1_ddrctl __iomem *d, - enum ddrctrl_buswidth buswidth) + enum ddrctrl_buswidth buswidth, + unsigned nb_bytes) { unsigned banks = 3, cols = 12, rows = 16; u32 reg; @@ -99,21 +101,26 @@ static unsigned long ddrctrl_addrmap_ramsize(struct stm32mp1_ddrctl __iomem *d, if (LINE_UNUSED(reg, ADDRMAP6_ROW_B13)) rows--; if (LINE_UNUSED(reg, ADDRMAP6_ROW_B12)) rows--; - return memory_sdram_size(cols, rows, BIT(banks), 4 / BIT(buswidth)); + return memory_sdram_size(cols, rows, BIT(banks), nb_bytes / BIT(buswidth)); } -static inline unsigned ddrctrl_ramsize(void __iomem *base) +static inline unsigned ddrctrl_ramsize(void __iomem *base, unsigned nb_bytes) { struct stm32mp1_ddrctl __iomem *ddrctl = base; unsigned buswidth = readl(&ddrctl->mstr) & DDRCTRL_MSTR_DATA_BUS_WIDTH_MASK; buswidth >>= DDRCTRL_MSTR_DATA_BUS_WIDTH_SHIFT; - return ddrctrl_addrmap_ramsize(ddrctl, buswidth); + return ddrctrl_addrmap_ramsize(ddrctl, buswidth, nb_bytes); } static inline unsigned stm32mp1_ddrctrl_ramsize(void) { - return ddrctrl_ramsize(IOMEM(STM32_DDRCTL_BASE)); + u32 nb_bytes = 4; + + if (cpu_stm32_is_stm32mp13()) + nb_bytes /= 2; + + return ddrctrl_ramsize(IOMEM(STM32_DDRCTL_BASE), nb_bytes); } void __noreturn stm32mp1_barebox_entry(void *boarddata) @@ -126,17 +133,22 @@ static int stm32mp1_ddr_probe(struct device_d *dev) { struct resource *iores; void __iomem *base; + unsigned long nb_bytes; iores = dev_request_mem_resource(dev, 0); if (IS_ERR(iores)) return PTR_ERR(iores); base = IOMEM(iores->start); - return arm_add_mem_device("ram0", STM32_DDR_BASE, ddrctrl_ramsize(base)); + nb_bytes = (unsigned long)device_get_match_data(dev); + + return arm_add_mem_device("ram0", STM32_DDR_BASE, + ddrctrl_ramsize(base, nb_bytes)); } static __maybe_unused struct of_device_id stm32mp1_ddr_dt_ids[] = { - { .compatible = "st,stm32mp1-ddr" }, + { .compatible = "st,stm32mp1-ddr", .data = (void *)4 }, + { .compatible = "st,stm32mp13-ddr", .data = (void *)2 }, { /* sentinel */ } }; diff --git a/arch/arm/mach-stm32mp/include/mach/revision.h b/arch/arm/mach-stm32mp/include/mach/revision.h index 2ef8ef30c3..c141b925a1 100644 --- a/arch/arm/mach-stm32mp/include/mach/revision.h +++ b/arch/arm/mach-stm32mp/include/mach/revision.h @@ -32,6 +32,14 @@ #define CPU_STM32MP151Fxx 0x050000AE #define CPU_STM32MP151Dxx 0x050000AF +#define cpu_stm32_is(mask, val) ({ \ + u32 type; \ + __stm32mp_get_cpu_type(&type) == 0 ? (type & mask) == val : 0; \ +}) + +#define cpu_stm32_is_stm32mp15() cpu_stm32_is(0xFFFF0000, 0x05000000) +#define cpu_stm32_is_stm32mp13() cpu_stm32_is(0xFFFF0000, 0x05010000) + /* silicon revisions */ #define CPU_REV_A 0x1000 #define CPU_REV_B 0x2000 -- cgit v1.2.3 From c7bb31362687174027660c2d393e7686523be856 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Mon, 21 Feb 2022 11:36:25 +0100 Subject: ARM: stm32mp: add board support for STM32MP135F-DK We already have the needed drivers in place to support the upcoming STM32MP131. Linux already has a basic DT for the DK board. Add a barebox board that leverages it. To try it out modify the existing FIP with: fiptool update --nt-fw build/images/barebox-stm32mp-generic-bl33.img \ --hw-config build/arch/arm/dts/stm32mp135f-dk.dtb \ mmcblk0p3 Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20220221103625.3728055-2-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- Documentation/boards/stm32mp.rst | 1 + arch/arm/boards/Makefile | 1 + arch/arm/boards/stm32mp13xx-dk/Makefile | 2 ++ arch/arm/boards/stm32mp13xx-dk/lowlevel.c | 19 +++++++++++++++++++ arch/arm/dts/Makefile | 1 + arch/arm/dts/stm32mp131.dtsi | 14 ++++++++++++++ arch/arm/dts/stm32mp135f-dk.dts | 12 ++++++++++++ arch/arm/mach-stm32mp/Kconfig | 8 ++++++++ images/Makefile.stm32mp | 1 + 9 files changed, 59 insertions(+) create mode 100644 arch/arm/boards/stm32mp13xx-dk/Makefile create mode 100644 arch/arm/boards/stm32mp13xx-dk/lowlevel.c create mode 100644 arch/arm/dts/stm32mp131.dtsi create mode 100644 arch/arm/dts/stm32mp135f-dk.dts diff --git a/Documentation/boards/stm32mp.rst b/Documentation/boards/stm32mp.rst index 55bdafe785..6f4b14049a 100644 --- a/Documentation/boards/stm32mp.rst +++ b/Documentation/boards/stm32mp.rst @@ -33,6 +33,7 @@ There's a single ``stm32mp_defconfig`` for all STM32MP boards:: The resulting images will be placed under ``images/``:: barebox-stm32mp-generic-bl33.img + barebox-stm32mp13xx-dk.stm32 barebox-stm32mp15xx-dkx.stm32 barebox-stm32mp15x-ev1.stm32 barebox-stm32mp157c-lxa-mc1.stm32 diff --git a/arch/arm/boards/Makefile b/arch/arm/boards/Makefile index a15963c775..8557e1dca8 100644 --- a/arch/arm/boards/Makefile +++ b/arch/arm/boards/Makefile @@ -139,6 +139,7 @@ obj-$(CONFIG_MACH_SOCFPGA_TERASIC_SOCKIT) += terasic-sockit/ obj-$(CONFIG_MACH_SOLIDRUN_CUBOX) += solidrun-cubox/ obj-$(CONFIG_MACH_SOLIDRUN_MICROSOM) += solidrun-microsom/ obj-$(CONFIG_MACH_STM32MP15XX_DKX) += stm32mp15xx-dkx/ +obj-$(CONFIG_MACH_STM32MP13XX_DK) += stm32mp13xx-dk/ obj-$(CONFIG_MACH_LXA_MC1) += lxa-mc1/ obj-$(CONFIG_MACH_STM32MP15X_EV1) += stm32mp15x-ev1/ obj-$(CONFIG_MACH_TECHNEXION_PICO_HOBBIT) += technexion-pico-hobbit/ diff --git a/arch/arm/boards/stm32mp13xx-dk/Makefile b/arch/arm/boards/stm32mp13xx-dk/Makefile new file mode 100644 index 0000000000..9961af02a3 --- /dev/null +++ b/arch/arm/boards/stm32mp13xx-dk/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +lwl-y += lowlevel.o diff --git a/arch/arm/boards/stm32mp13xx-dk/lowlevel.c b/arch/arm/boards/stm32mp13xx-dk/lowlevel.c new file mode 100644 index 0000000000..ac4fa40e19 --- /dev/null +++ b/arch/arm/boards/stm32mp13xx-dk/lowlevel.c @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include +#include + +extern char __dtb_z_stm32mp135f_dk_start[]; + +ENTRY_FUNCTION(start_stm32mp13xx_dk, r0, r1, r2) +{ + void *fdt; + + stm32mp_cpu_lowlevel_init(); + + putc_ll('>'); + + fdt = __dtb_z_stm32mp135f_dk_start + get_runtime_offset(); + + stm32mp1_barebox_entry(fdt); +} diff --git a/arch/arm/dts/Makefile b/arch/arm/dts/Makefile index d419e8394d..e0bb66580f 100644 --- a/arch/arm/dts/Makefile +++ b/arch/arm/dts/Makefile @@ -127,6 +127,7 @@ lwl-$(CONFIG_MACH_SKOV_IMX6) += imx6s-skov-imx6.dtb.o imx6dl-skov-imx6.dtb.o imx lwl-$(CONFIG_MACH_SKOV_ARM9CPU) += at91-skov-arm9cpu.dtb.o lwl-$(CONFIG_MACH_SEEED_ODYSSEY) += stm32mp157c-odyssey.dtb.o lwl-$(CONFIG_MACH_STM32MP15XX_DKX) += stm32mp157c-dk2.dtb.o stm32mp157a-dk1.dtb.o +lwl-$(CONFIG_MACH_STM32MP13XX_DK) += stm32mp135f-dk.dtb.o lwl-$(CONFIG_MACH_LXA_MC1) += stm32mp157c-lxa-mc1.dtb.o lwl-$(CONFIG_MACH_STM32MP15X_EV1) += stm32mp157c-ev1.dtb.o lwl-$(CONFIG_MACH_SCB9328) += imx1-scb9328.dtb.o diff --git a/arch/arm/dts/stm32mp131.dtsi b/arch/arm/dts/stm32mp131.dtsi new file mode 100644 index 0000000000..2ecad85f08 --- /dev/null +++ b/arch/arm/dts/stm32mp131.dtsi @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/ { + aliases { + mmc0 = &sdmmc1; + }; +}; + +&{/soc} { + memory-controller@5a003000 { + compatible = "st,stm32mp13-ddr"; + reg = <0x5a003000 0x1000>; + }; +}; diff --git a/arch/arm/dts/stm32mp135f-dk.dts b/arch/arm/dts/stm32mp135f-dk.dts new file mode 100644 index 0000000000..104886e8af --- /dev/null +++ b/arch/arm/dts/stm32mp135f-dk.dts @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) + +#include +#include "stm32mp131.dtsi" + +/ { + model = "STM32MP153F-DK"; + + chosen { + stdout-path = "serial0:115200n8"; + }; +}; diff --git a/arch/arm/mach-stm32mp/Kconfig b/arch/arm/mach-stm32mp/Kconfig index 38c1a44770..57c1691591 100644 --- a/arch/arm/mach-stm32mp/Kconfig +++ b/arch/arm/mach-stm32mp/Kconfig @@ -6,10 +6,18 @@ config ARCH_NR_GPIO int default 416 +config ARCH_STM32MP13 + select ARM_PSCI_CLIENT + bool + config ARCH_STM32MP157 select ARM_PSCI_CLIENT bool +config MACH_STM32MP13XX_DK + select ARCH_STM32MP13 + bool "STM32MP137F DK board" + config MACH_STM32MP15XX_DKX select ARCH_STM32MP157 bool "STM32MP157 DK1 and DK2 boards" diff --git a/images/Makefile.stm32mp b/images/Makefile.stm32mp index fa79e09f95..abe70a6a50 100644 --- a/images/Makefile.stm32mp +++ b/images/Makefile.stm32mp @@ -30,6 +30,7 @@ pblb-$(CONFIG_ARCH_STM32MP) += start_stm32mp_bl33 FILE_barebox-stm32mp-generic-bl33.img = start_stm32mp_bl33.pblb image-$(CONFIG_ARCH_STM32MP) += barebox-stm32mp-generic-bl33.img +$(call build_stm32mp_image, CONFIG_MACH_STM32MP13XX_DK, start_stm32mp13xx_dk, stm32mp13xx-dk) $(call build_stm32mp_image, CONFIG_MACH_STM32MP15XX_DKX, start_stm32mp15xx_dkx, stm32mp15xx-dkx) $(call build_stm32mp_image, CONFIG_MACH_STM32MP15X_EV1, start_stm32mp15x_ev1, stm32mp15x-ev1) -- cgit v1.2.3 From fb884f25a53aa25924bf9171ad6c8e3305a2ecf3 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Wed, 23 Feb 2022 12:38:47 +0100 Subject: ARM: stm32mp: ddrctl: add STM32MP131 RAM size querying support buswidth is read from HW. With nb_bytes == 2, there is a possibility that we get a zero size out here, if driver and device are mismatched, e.g. because barebox is booted in FIP with mismatched external device tree. As this runs very early before relocation, round up instead of crashing to be a bit more on the safe side. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20220223113846.3022227-1-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- arch/arm/mach-stm32mp/ddrctrl.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/arm/mach-stm32mp/ddrctrl.c b/arch/arm/mach-stm32mp/ddrctrl.c index 7f0944d7e7..ed211cf58e 100644 --- a/arch/arm/mach-stm32mp/ddrctrl.c +++ b/arch/arm/mach-stm32mp/ddrctrl.c @@ -101,7 +101,8 @@ static unsigned long ddrctrl_addrmap_ramsize(struct stm32mp1_ddrctl __iomem *d, if (LINE_UNUSED(reg, ADDRMAP6_ROW_B13)) rows--; if (LINE_UNUSED(reg, ADDRMAP6_ROW_B12)) rows--; - return memory_sdram_size(cols, rows, BIT(banks), nb_bytes / BIT(buswidth)); + return memory_sdram_size(cols, rows, BIT(banks), + DIV_ROUND_UP(nb_bytes, BIT(buswidth))); } static inline unsigned ddrctrl_ramsize(void __iomem *base, unsigned nb_bytes) -- cgit v1.2.3 From 79d2a2d4a4608fd0ca9f1eeb06e4aee0fb095f19 Mon Sep 17 00:00:00 2001 From: Steffen Trumtrar Date: Wed, 2 Mar 2022 17:02:42 +0100 Subject: ARM: stm32: add support for PHYTEC phyCORE stm32mp1 Import DT[1] and add the boilerplate to have barebox generate a SSBL for the board. [1]: git://git.phytec.de/tf-a-stm32mp Signed-off-by: Steffen Trumtrar Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20220302160242.2997946-1-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- arch/arm/boards/Makefile | 1 + arch/arm/boards/phytec-phycore-stm32mp1/Makefile | 3 + arch/arm/boards/phytec-phycore-stm32mp1/board.c | 28 +++ arch/arm/boards/phytec-phycore-stm32mp1/lowlevel.c | 19 ++ arch/arm/dts/Makefile | 1 + arch/arm/dts/stm32mp157c-phycore-stm32mp1-3.dts | 41 ++++ .../dts/stm32mp157c-phycore-stm32mp15-pinctrl.dtsi | 92 +++++++ .../arm/dts/stm32mp157c-phycore-stm32mp15-som.dtsi | 271 +++++++++++++++++++++ arch/arm/mach-stm32mp/Kconfig | 7 + images/Makefile.stm32mp | 2 + 10 files changed, 465 insertions(+) create mode 100644 arch/arm/boards/phytec-phycore-stm32mp1/Makefile create mode 100644 arch/arm/boards/phytec-phycore-stm32mp1/board.c create mode 100644 arch/arm/boards/phytec-phycore-stm32mp1/lowlevel.c create mode 100644 arch/arm/dts/stm32mp157c-phycore-stm32mp1-3.dts create mode 100644 arch/arm/dts/stm32mp157c-phycore-stm32mp15-pinctrl.dtsi create mode 100644 arch/arm/dts/stm32mp157c-phycore-stm32mp15-som.dtsi diff --git a/arch/arm/boards/Makefile b/arch/arm/boards/Makefile index 8557e1dca8..75e15cbda4 100644 --- a/arch/arm/boards/Makefile +++ b/arch/arm/boards/Makefile @@ -101,6 +101,7 @@ obj-$(CONFIG_MACH_PCM049) += phytec-phycore-omap4460/ obj-$(CONFIG_MACH_PHYTEC_SOM_AM335X) += phytec-som-am335x/ obj-$(CONFIG_MACH_PHYTEC_SOM_IMX6) += phytec-som-imx6/ obj-$(CONFIG_MACH_PHYTEC_PHYCORE_IMX7) += phytec-phycore-imx7/ +obj-$(CONFIG_MACH_PHYTEC_PHYCORE_STM32MP1) += phytec-phycore-stm32mp1/ obj-$(CONFIG_MACH_PHYTEC_SOM_IMX8MQ) += phytec-som-imx8mq/ obj-$(CONFIG_MACH_PLATHOME_OPENBLOCKS_AX3) += plathome-openblocks-ax3/ obj-$(CONFIG_MACH_PLATHOME_OPENBLOCKS_A6) += plathome-openblocks-a6/ diff --git a/arch/arm/boards/phytec-phycore-stm32mp1/Makefile b/arch/arm/boards/phytec-phycore-stm32mp1/Makefile new file mode 100644 index 0000000000..1d052d28c9 --- /dev/null +++ b/arch/arm/boards/phytec-phycore-stm32mp1/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only +lwl-y += lowlevel.o +obj-y += board.o diff --git a/arch/arm/boards/phytec-phycore-stm32mp1/board.c b/arch/arm/boards/phytec-phycore-stm32mp1/board.c new file mode 100644 index 0000000000..eb6147785f --- /dev/null +++ b/arch/arm/boards/phytec-phycore-stm32mp1/board.c @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include + +static int phycore_stm32mp1_probe(struct device_d *dev) +{ + if (bootsource_get_instance() == 0) + of_device_enable_path("/chosen/environment-sd"); + else + of_device_enable_path("/chosen/environment-emmc"); + + barebox_set_hostname("phyCORE-STM32MP1"); + + return 0; +} + +static const struct of_device_id phycore_stm32mp1_of_match[] = { + { .compatible = "phytec,phycore-stm32mp1-3" }, + { /* sentinel */ }, +}; + +static struct driver_d phycore_stm32mp1_board_driver = { + .name = "board-phycore-stm32mp1", + .probe = phycore_stm32mp1_probe, + .of_compatible = phycore_stm32mp1_of_match, +}; +device_platform_driver(phycore_stm32mp1_board_driver); diff --git a/arch/arm/boards/phytec-phycore-stm32mp1/lowlevel.c b/arch/arm/boards/phytec-phycore-stm32mp1/lowlevel.c new file mode 100644 index 0000000000..f76bad86a1 --- /dev/null +++ b/arch/arm/boards/phytec-phycore-stm32mp1/lowlevel.c @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0+ +#include +#include +#include + +extern char __dtb_z_stm32mp157c_phycore_stm32mp1_3_start[]; + +ENTRY_FUNCTION(start_phycore_stm32mp1_3, r0, r1, r2) +{ + void *fdt; + + stm32mp_cpu_lowlevel_init(); + + putc_ll('>'); + + fdt = __dtb_z_stm32mp157c_phycore_stm32mp1_3_start + get_runtime_offset(); + + stm32mp1_barebox_entry(fdt); +} diff --git a/arch/arm/dts/Makefile b/arch/arm/dts/Makefile index e0bb66580f..925ac12aa5 100644 --- a/arch/arm/dts/Makefile +++ b/arch/arm/dts/Makefile @@ -75,6 +75,7 @@ lwl-$(CONFIG_MACH_PHYTEC_SOM_IMX6) += imx6q-phytec-phycard.dtb.o \ imx6ull-phytec-phycore-som-nand.dtb.o \ imx6ull-phytec-phycore-som-emmc.dtb.o lwl-$(CONFIG_MACH_PHYTEC_PHYCORE_IMX7) += imx7d-phyboard-zeta.dtb.o +lwl-$(CONFIG_MACH_PHYTEC_PHYCORE_STM32MP1) += stm32mp157c-phycore-stm32mp1-3.dtb.o lwl-$(CONFIG_MACH_PHYTEC_SOM_IMX8MQ) += imx8mq-phytec-phycore-som.dtb.o lwl-$(CONFIG_MACH_PINE64_QUARTZ64) += rk3566-quartz64-a.dtb.o lwl-$(CONFIG_MACH_PLATHOME_OPENBLOCKS_AX3) += armada-xp-openblocks-ax3-4-bb.dtb.o diff --git a/arch/arm/dts/stm32mp157c-phycore-stm32mp1-3.dts b/arch/arm/dts/stm32mp157c-phycore-stm32mp1-3.dts new file mode 100644 index 0000000000..0818f8d9ad --- /dev/null +++ b/arch/arm/dts/stm32mp157c-phycore-stm32mp1-3.dts @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-3-Clause) +/* + * Copyright (C) Phytec GmbH 2019-2020 - All Rights Reserved + * Author: Dom VOVARD . + */ + +/dts-v1/; + +#include +#include +#include +#include +#include +#include "stm32mp157c-phycore-stm32mp15-som.dtsi" + +/ { + model = "PHYTEC phyCORE-STM32MP1-3 SoM"; + compatible = "phytec,phycore-stm32mp1-3", "st,stm32mp157"; + + chosen { + environment-sd { + compatible = "barebox,environment"; + device-path = &sdmmc1, "partname:barebox-environment"; + status = "disabled"; + }; + + environment-emmc { + compatible = "barebox,environment"; + device-path = &sdmmc2, "partname:barebox-environment"; + status = "disabled"; + }; + }; +}; + +&sdmmc1 { + status = "okay"; +}; + +&sdmmc2 { + status = "okay"; +}; diff --git a/arch/arm/dts/stm32mp157c-phycore-stm32mp15-pinctrl.dtsi b/arch/arm/dts/stm32mp157c-phycore-stm32mp15-pinctrl.dtsi new file mode 100644 index 0000000000..011d73ec3f --- /dev/null +++ b/arch/arm/dts/stm32mp157c-phycore-stm32mp15-pinctrl.dtsi @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-3-Clause) +/* + * Copyright (C) Phytec GmbH 2019-2020 - All Rights Reserved + * Author: Dom VOVARD . + */ +#include + +ðernet0_rgmii_pins_a { + pins1 { + pinmux = , /* ETH_RGMII_GTX_CLK */ + , /* ETH_RGMII_TXD0 */ + , /* ETH_RGMII_TXD1 */ + , /* ETH_RGMII_TXD2 */ + , /* ETH_RGMII_TXD3 */ + , /* ETH_RGMII_TX_CTL */ + , /* ETH_MDIO */ + ; /* ETH_MDC */ + bias-disable; + drive-push-pull; + slew-rate = <2>; + }; + pins2 { + pinmux = , /* ETH_RGMII_RXD0 */ + , /* ETH_RGMII_RXD1 */ + , /* ETH_RGMII_RXD2 */ + , /* ETH_RGMII_RXD3 */ + , /* ETH_RGMII_RX_CLK */ + ; /* ETH_RGMII_RX_CTL */ + bias-disable; + }; +}; + +&pinctrl { + sdmmc1_dir_pins_b: sdmmc1-dir-1 { + pins1 { + pinmux = , /* SDMMC1_D0DIR */ + , /* SDMMC1_D123DIR */ + ; /* SDMMC1_CDIR */ + slew-rate = <3>; + drive-push-pull; + bias-pull-up; + }; + pins2 { + pinmux = ; /* SDMMC1_CKIN */ + bias-pull-up; + }; + }; +}; + +&sdmmc1_b4_pins_a { + pins1 { + pinmux = , /* SDMMC1_D0 */ + , /* SDMMC1_D1 */ + , /* SDMMC1_D2 */ + , /* SDMMC1_D3 */ + ; /* SDMMC1_CMD */ + slew-rate = <1>; + drive-push-pull; + bias-disable; + }; + pins2 { + pinmux = ; /* SDMMC1_CK */ + slew-rate = <2>; + drive-push-pull; + bias-disable; + }; +}; + +&sdmmc2_d47_pins_a { + pins { + pinmux = , /* SDMMC2_D4 */ + , /* SDMMC2_D5 */ + , /* SDMMC2_D6 */ + ; /* SDMMC2_D7 */ + slew-rate = <1>; + drive-push-pull; + bias-pull-up; + }; +}; + +&uart4_pins_a { + pins1 { + pinmux = ; /* UART4_TX */ + bias-disable; + drive-push-pull; + slew-rate = <0>; + }; + pins2 { + pinmux = ; /* UART4_RX */ + bias-disable; + }; +}; diff --git a/arch/arm/dts/stm32mp157c-phycore-stm32mp15-som.dtsi b/arch/arm/dts/stm32mp157c-phycore-stm32mp15-som.dtsi new file mode 100644 index 0000000000..a40e59ae2e --- /dev/null +++ b/arch/arm/dts/stm32mp157c-phycore-stm32mp15-som.dtsi @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-3-Clause) +/* + * Copyright (C) Phytec GmbH 2019-2020 - All Rights Reserved + * Author: Dom VOVARD . + */ + +#include "stm32mp157c-phycore-stm32mp15-pinctrl.dtsi" +#include +#include "stm32mp151.dtsi" + +/ { + chosen { + stdout-path = "serial0:115200n8"; + }; + + aliases { + serial0 = &uart4; + serial1 = &usart3; + }; + + vin: vin { + compatible = "regulator-fixed"; + regulator-name = "vin"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + regulator-always-on; + }; + + pwr_irq: pwr@50001020 { + compatible = "st,stm32mp1-pwr"; + reg = <0x50001020 0x100>; + }; +}; + +&bsec { + board_id: board_id@ec { + reg = <0xec 0x4>; + st,non-secure-otp; + }; +}; + +&clk_hse { + st,digbypass; +}; + +&cpu0 { + cpu-supply = <&vddcore>; +}; + +&cpu1 { + cpu-supply = <&vddcore>; +}; + +ðernet0 { + pinctrl-names = "default", "sleep"; + pinctrl-0 = <ðernet0_rgmii_pins_a>; + pinctrl-1 = <ðernet0_rgmii_sleep_pins_a>; + phy-mode = "rgmii-id"; + phy-handle = <ðphy>; + max-speed = <1000>; + st,eth-clk-sel; + status = "okay"; + + mdio0 { + compatible = "snps,dwmac-mdio"; + #address-cells = <1>; + #size-cells = <0>; + + ethphy: ethernet-phy@1 { + reg = <1>; + }; + }; +}; + +&i2c4 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c4_pins_a>; + i2c-scl-rising-time-ns = <185>; + i2c-scl-falling-time-ns = <20>; + + pmic: stpmic@33 { + compatible = "st,stpmic1"; + reg = <0x33>; + + regulators { + compatible = "st,stpmic1-regulators"; + buck1-supply = <&vin>; + buck2-supply = <&vin>; + buck3-supply = <&vin>; + buck4-supply = <&vin>; + ldo1-supply = <&v3v3>; + ldo2-supply = <&v3v3>; + ldo3-supply = <&vdd_ddr>; + ldo4-supply = <&vin>; + ldo5-supply = <&v3v3>; + ldo6-supply = <&v3v3>; + vref_ddr-supply = <&vin>; + boost-supply = <&vin>; + pwr_sw1-supply = <&bst_out>; + pwr_sw2-supply = <&bst_out>; + + vddcore: buck1 { + regulator-name = "vddcore"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1350000>; + regulator-always-on; + regulator-initial-mode = <0>; + regulator-over-current-protection; + }; + + vdd_ddr: buck2 { + regulator-name = "vdd_ddr"; + regulator-min-microvolt = <1350000>; + regulator-max-microvolt = <1350000>; + regulator-always-on; + regulator-initial-mode = <0>; + regulator-over-current-protection; + }; + + vdd: buck3 { + regulator-name = "vdd"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + st,mask-reset; + regulator-initial-mode = <0>; + regulator-over-current-protection; + }; + + v3v3: buck4 { + regulator-name = "v3v3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + regulator-over-current-protection; + regulator-initial-mode = <0>; + }; + + v1v8_audio: ldo1 { + regulator-name = "v1v8_audio"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + }; + + vdd_eth_2v5: ldo2 { + regulator-name = "vdd_eth_2v5"; + regulator-min-microvolt = <2500000>; + regulator-max-microvolt = <2500000>; + regulator-always-on; + }; + + vtt_ddr: ldo3 { + regulator-name = "vtt_ddr"; + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <750000>; + regulator-always-on; + regulator-over-current-protection; + }; + + vdd_usb: ldo4 { + regulator-name = "vdd_usb"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + + vdda: ldo5 { + regulator-name = "vdda"; + regulator-min-microvolt = <2900000>; + regulator-max-microvolt = <2900000>; + regulator-boot-on; + }; + + vdd_eth_1v0: ldo6 { + regulator-name = "vdd_eth_1v0"; + regulator-min-microvolt = <1000000>; + regulator-max-microvolt = <1000000>; + regulator-always-on; + }; + + vref_ddr: vref_ddr { + regulator-name = "vref_ddr"; + regulator-always-on; + regulator-over-current-protection; + }; + + bst_out: boost { + regulator-name = "bst_out"; + }; + + vbus_otg: pwr_sw1 { + regulator-name = "vbus_otg"; + }; + + vbus_sw: pwr_sw2 { + regulator-name = "vbus_sw"; + regulator-active-discharge = <1>; + }; + }; + }; +}; + +&iwdg2 { + timeout-sec = <32>; + status = "okay"; +}; + +&pwr_regulators { + vdd-supply = <&vdd>; + vdd_3v3_usbfs-supply = <&vdd_usb>; +}; + +&rng1 { + status = "okay"; +}; + +&sdmmc1 { + pinctrl-names = "default"; + pinctrl-0 = <&sdmmc1_b4_pins_a>; + disable-wp; + st,neg-edge; + bus-width = <4>; + vmmc-supply = <&v3v3>; + status = "disabled"; +}; + +&sdmmc2 { + pinctrl-names = "default"; + pinctrl-0 = <&sdmmc2_b4_pins_a &sdmmc2_d47_pins_a>; + non-removable; + no-sd; + no-sdio; + st,neg-edge; + bus-width = <8>; + vmmc-supply = <&v3v3>; + vqmmc-supply = <&v3v3>; + mmc-ddr-3_3v; + status = "disabled"; +}; + +&uart4 { + pinctrl-names = "default"; + pinctrl-0 = <&uart4_pins_a>; + status = "okay"; +}; + +&usart3 { + pinctrl-names = "default"; + pinctrl-0 = <&usart3_pins_a>; + status = "disabled"; +}; + +&usbotg_hs { + phys = <&usbphyc_port1 0>; + phy-names = "usb2-phy"; + usb-role-switch; + status = "okay"; +}; + +&usbphyc { + status = "okay"; +}; + +&usbphyc_port0 { + phy-supply = <&vdd_usb>; +}; + +&usbphyc_port1 { + phy-supply = <&vdd_usb>; +}; diff --git a/arch/arm/mach-stm32mp/Kconfig b/arch/arm/mach-stm32mp/Kconfig index 57c1691591..bc0a48d64c 100644 --- a/arch/arm/mach-stm32mp/Kconfig +++ b/arch/arm/mach-stm32mp/Kconfig @@ -48,4 +48,11 @@ config MACH_PROTONIC_STM32MP1 Builds all barebox-prtt1*.stm32 that can be deployed as SSBL on the respective PRTT1L family board +config MACH_PHYTEC_PHYCORE_STM32MP1 + select ARCH_STM32MP157 + bool "phyCORE-STM32MP1" + help + builds an additional barebox-phytec-phycore.stm32 + that can be deployed as SSBL on the phyCORE-STM32MP1 + endif diff --git a/images/Makefile.stm32mp b/images/Makefile.stm32mp index abe70a6a50..59d6572207 100644 --- a/images/Makefile.stm32mp +++ b/images/Makefile.stm32mp @@ -41,3 +41,5 @@ $(call build_stm32mp_image, CONFIG_MACH_PROTONIC_STM32MP1, start_prtt1s, prtt1s) $(call build_stm32mp_image, CONFIG_MACH_PROTONIC_STM32MP1, start_prtt1c, prtt1c) $(call build_stm32mp_image, CONFIG_MACH_SEEED_ODYSSEY, start_stm32mp157c_seeed_odyssey, stm32mp157c-seeed-odyssey) + +$(call build_stm32mp_image, CONFIG_MACH_PHYTEC_PHYCORE_STM32MP1, start_phycore_stm32mp1_3, phycore-stm32mp1) -- cgit v1.2.3