Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 18 Oct 2023 14:31:36 GMT
From:      Emmanuel Vadot <manu@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-branches@FreeBSD.org
Subject:   git: ab8f34675ae7 - stable/14 - arm64: zynqmp: Add firmware driver
Message-ID:  <202310181431.39IEVaUH089014@gitrepo.freebsd.org>

next in thread | raw e-mail | index | archive | help
The branch stable/14 has been updated by manu:

URL: https://cgit.FreeBSD.org/src/commit/?id=ab8f34675ae705060a6160521b1e7a91c5e00a9a

commit ab8f34675ae705060a6160521b1e7a91c5e00a9a
Author:     Emmanuel Vadot <manu@FreeBSD.org>
AuthorDate: 2023-09-07 07:56:45 +0000
Commit:     Emmanuel Vadot <manu@FreeBSD.org>
CommitDate: 2023-10-18 14:31:01 +0000

    arm64: zynqmp: Add firmware driver
    
    The ZynqMP SoC have a MCU running a firmware to control clocks, resets,
    fpga loading etc ...
    Add a driver that can be use to communicate with it.
    For now only the clock and reset part are implemented.
    
    Differential Revision:  https://reviews.freebsd.org/D41811
    Sponsored by:   Beckhoff Automation GmbH & Co. KG
    
    (cherry picked from commit 9e88711f28dc9afa7d68ae8dd027d2399a2a290b)
---
 sys/conf/files.arm64                         |   2 +
 sys/dev/firmware/xilinx/pm_defs.h            | 380 ++++++++++++++++++++
 sys/dev/firmware/xilinx/zynqmp_firmware.c    | 511 +++++++++++++++++++++++++++
 sys/dev/firmware/xilinx/zynqmp_firmware_if.m | 109 ++++++
 4 files changed, 1002 insertions(+)

diff --git a/sys/conf/files.arm64 b/sys/conf/files.arm64
index 02dcadc0aa5c..987a1ea6cd09 100644
--- a/sys/conf/files.arm64
+++ b/sys/conf/files.arm64
@@ -698,3 +698,5 @@ arm64/rockchip/clk/rk3568_pmucru.c		optional fdt soc_rockchip_rk3568
 arm/xilinx/uart_dev_cdnc.c			optional uart soc_xilinx_zynq fdt
 arm/xilinx/zy7_gpio.c				optional gpio soc_xilinx_zynq fdt
 dev/usb/controller/xlnx_dwc3.c			optional xhci soc_xilinx_zynq fdt
+dev/firmware/xilinx/zynqmp_firmware.c		optional fdt soc_xilinx_zynq
+dev/firmware/xilinx/zynqmp_firmware_if.m	optional fdt soc_xilinx_zynq
diff --git a/sys/dev/firmware/xilinx/pm_defs.h b/sys/dev/firmware/xilinx/pm_defs.h
new file mode 100644
index 000000000000..3461dd7820ad
--- /dev/null
+++ b/sys/dev/firmware/xilinx/pm_defs.h
@@ -0,0 +1,380 @@
+/*
+ * Copyright (c) 2013-2022, ARM Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+/* ZynqMP power management enums and defines */
+
+#ifndef PM_DEFS_H
+#define PM_DEFS_H
+
+/*********************************************************************
+ * Macro definitions
+ ********************************************************************/
+
+/*
+ * Version number is a 32bit value, like:
+ * (PM_VERSION_MAJOR << 16) | PM_VERSION_MINOR
+ */
+#define PM_VERSION_MAJOR	1U
+#define PM_VERSION_MINOR	1U
+
+#define PM_VERSION	((PM_VERSION_MAJOR << 16U) | PM_VERSION_MINOR)
+
+/**
+ * PM API versions
+ */
+/* Expected version of firmware APIs */
+#define FW_API_BASE_VERSION		(1U)
+/* Expected version of firmware API for feature check */
+#define FW_API_VERSION_2		(2U)
+/* Version of APIs implemented in ATF */
+#define ATF_API_BASE_VERSION		(1U)
+
+/* Capabilities for RAM */
+#define PM_CAP_ACCESS	0x1U
+#define PM_CAP_CONTEXT	0x2U
+
+#define MAX_LATENCY	(~0U)
+#define MAX_QOS		100U
+
+/* State arguments of the self suspend */
+#define PM_STATE_CPU_IDLE		0x0U
+#define PM_STATE_SUSPEND_TO_RAM		0xFU
+
+/* APU processor states */
+#define PM_PROC_STATE_FORCEDOFF		0U
+#define PM_PROC_STATE_ACTIVE		1U
+#define PM_PROC_STATE_SLEEP		2U
+#define PM_PROC_STATE_SUSPENDING	3U
+
+#define EM_FUNID_NUM_MASK    0xF0000U
+
+#define PM_GET_CALLBACK_DATA		0xa01
+#define PM_SET_SUSPEND_MODE		0xa02
+#define PM_GET_TRUSTZONE_VERSION	0xa03
+
+/*********************************************************************
+ * Enum definitions
+ ********************************************************************/
+
+enum pm_api_id {
+	/* Miscellaneous API functions: */
+	PM_GET_API_VERSION = 1, /* Do not change or move */
+	PM_SET_CONFIGURATION,
+	PM_GET_NODE_STATUS,
+	PM_GET_OP_CHARACTERISTIC,
+	PM_REGISTER_NOTIFIER,
+	/* API for suspending of PUs: */
+	PM_REQ_SUSPEND,
+	PM_SELF_SUSPEND,
+	PM_FORCE_POWERDOWN,
+	PM_ABORT_SUSPEND,
+	PM_REQ_WAKEUP,
+	PM_SET_WAKEUP_SOURCE,
+	PM_SYSTEM_SHUTDOWN,
+	/* API for managing PM slaves: */
+	PM_REQ_NODE,
+	PM_RELEASE_NODE,
+	PM_SET_REQUIREMENT,
+	PM_SET_MAX_LATENCY,
+	/* Direct control API functions: */
+	PM_RESET_ASSERT,
+	PM_RESET_GET_STATUS,
+	PM_MMIO_WRITE,
+	PM_MMIO_READ,
+	PM_INIT_FINALIZE,
+	PM_FPGA_LOAD,
+	PM_FPGA_GET_STATUS,
+	PM_GET_CHIPID,
+	PM_SECURE_RSA_AES,
+	PM_SECURE_SHA,
+	PM_SECURE_RSA,
+	PM_PINCTRL_REQUEST,
+	PM_PINCTRL_RELEASE,
+	PM_PINCTRL_GET_FUNCTION,
+	PM_PINCTRL_SET_FUNCTION,
+	PM_PINCTRL_CONFIG_PARAM_GET,
+	PM_PINCTRL_CONFIG_PARAM_SET,
+	PM_IOCTL,
+	/* API to query information from firmware */
+	PM_QUERY_DATA,
+	/* Clock control API functions */
+	PM_CLOCK_ENABLE,
+	PM_CLOCK_DISABLE,
+	PM_CLOCK_GETSTATE,
+	PM_CLOCK_SETDIVIDER,
+	PM_CLOCK_GETDIVIDER,
+	PM_CLOCK_SETRATE,
+	PM_CLOCK_GETRATE,
+	PM_CLOCK_SETPARENT,
+	PM_CLOCK_GETPARENT,
+	PM_SECURE_IMAGE,
+	/* FPGA PL Readback */
+	PM_FPGA_READ,
+	PM_SECURE_AES,
+	/* PLL control API functions */
+	PM_PLL_SET_PARAMETER,
+	PM_PLL_GET_PARAMETER,
+	PM_PLL_SET_MODE,
+	PM_PLL_GET_MODE,
+	/* PM Register Access API */
+	PM_REGISTER_ACCESS,
+	PM_EFUSE_ACCESS,
+	PM_FPGA_GET_VERSION,
+	PM_FPGA_GET_FEATURE_LIST,
+	PM_FEATURE_CHECK = 63,
+	PM_API_MAX
+};
+
+enum pm_query_id {
+	PM_QID_INVALID = 0,
+	PM_QID_CLOCK_GET_NAME,
+	PM_QID_CLOCK_GET_TOPOLOGY,
+	PM_QID_CLOCK_GET_FIXEDFACTOR_PARAMS,
+	PM_QID_CLOCK_GET_PARENTS,
+	PM_QID_CLOCK_GET_ATTRIBUTES,
+	PM_QID_PINCTRL_GET_NUM_PINS,
+	PM_QID_PINCTRL_GET_NUM_FUNCTIONS,
+	PM_QID_PINCTRL_GET_NUM_FUNCTION_GROUPS,
+	PM_QID_PINCTRL_GET_FUNCTION_NAME,
+	PM_QID_PINCTRL_GET_FUNCTION_GROUPS,
+	PM_QID_PINCTRL_GET_PIN_GROUPS,
+	PM_QID_CLOCK_GET_NUM_CLOCKS,
+	PM_QID_CLOCK_GET_MAX_DIVISOR,
+};
+
+enum pm_node_id {
+	NODE_UNKNOWN = 0,
+	NODE_APU,
+	NODE_APU_0,
+	NODE_APU_1,
+	NODE_APU_2,
+	NODE_APU_3,
+	NODE_RPU,
+	NODE_RPU_0,
+	NODE_RPU_1,
+	NODE_PLD,
+	NODE_FPD,
+	NODE_OCM_BANK_0,
+	NODE_OCM_BANK_1,
+	NODE_OCM_BANK_2,
+	NODE_OCM_BANK_3,
+	NODE_TCM_0_A,
+	NODE_TCM_0_B,
+	NODE_TCM_1_A,
+	NODE_TCM_1_B,
+	NODE_L2,
+	NODE_GPU_PP_0,
+	NODE_GPU_PP_1,
+	NODE_USB_0,
+	NODE_USB_1,
+	NODE_TTC_0,
+	NODE_TTC_1,
+	NODE_TTC_2,
+	NODE_TTC_3,
+	NODE_SATA,
+	NODE_ETH_0,
+	NODE_ETH_1,
+	NODE_ETH_2,
+	NODE_ETH_3,
+	NODE_UART_0,
+	NODE_UART_1,
+	NODE_SPI_0,
+	NODE_SPI_1,
+	NODE_I2C_0,
+	NODE_I2C_1,
+	NODE_SD_0,
+	NODE_SD_1,
+	NODE_DP,
+	NODE_GDMA,
+	NODE_ADMA,
+	NODE_NAND,
+	NODE_QSPI,
+	NODE_GPIO,
+	NODE_CAN_0,
+	NODE_CAN_1,
+	NODE_EXTERN,
+	NODE_APLL,
+	NODE_VPLL,
+	NODE_DPLL,
+	NODE_RPLL,
+	NODE_IOPLL,
+	NODE_DDR,
+	NODE_IPI_APU,
+	NODE_IPI_RPU_0,
+	NODE_GPU,
+	NODE_PCIE,
+	NODE_PCAP,
+	NODE_RTC,
+	NODE_LPD,
+	NODE_VCU,
+	NODE_IPI_RPU_1,
+	NODE_IPI_PL_0,
+	NODE_IPI_PL_1,
+	NODE_IPI_PL_2,
+	NODE_IPI_PL_3,
+	NODE_PL,
+	NODE_GEM_TSU,
+	NODE_SWDT_0,
+	NODE_SWDT_1,
+	NODE_CSU,
+	NODE_PJTAG,
+	NODE_TRACE,
+	NODE_TESTSCAN,
+	NODE_PMU,
+	NODE_MAX,
+};
+
+enum pm_request_ack {
+	REQ_ACK_NO = 1,
+	REQ_ACK_BLOCKING,
+	REQ_ACK_NON_BLOCKING,
+};
+
+enum pm_abort_reason {
+	ABORT_REASON_WKUP_EVENT = 100,
+	ABORT_REASON_PU_BUSY,
+	ABORT_REASON_NO_PWRDN,
+	ABORT_REASON_UNKNOWN,
+};
+
+enum pm_suspend_reason {
+	SUSPEND_REASON_PU_REQ = 201,
+	SUSPEND_REASON_ALERT,
+	SUSPEND_REASON_SYS_SHUTDOWN,
+};
+
+enum pm_ram_state {
+	PM_RAM_STATE_OFF = 1,
+	PM_RAM_STATE_RETENTION,
+	PM_RAM_STATE_ON,
+};
+
+enum pm_opchar_type {
+	PM_OPCHAR_TYPE_POWER = 1,
+	PM_OPCHAR_TYPE_TEMP,
+	PM_OPCHAR_TYPE_LATENCY,
+};
+
+/**
+ * @PM_RET_SUCCESS:		success
+ * @PM_RET_ERROR_ARGS:		illegal arguments provided (deprecated)
+ * @PM_RET_ERROR_NOTSUPPORTED:	feature not supported  (deprecated)
+ * @PM_RET_ERROR_NOT_ENABLED:	feature is not enabled
+ * @PM_RET_ERROR_INTERNAL:	internal error
+ * @PM_RET_ERROR_CONFLICT:	conflict
+ * @PM_RET_ERROR_ACCESS:	access rights violation
+ * @PM_RET_ERROR_INVALID_NODE:	invalid node
+ * @PM_RET_ERROR_DOUBLE_REQ:	duplicate request for same node
+ * @PM_RET_ERROR_ABORT_SUSPEND:	suspend procedure has been aborted
+ * @PM_RET_ERROR_TIMEOUT:	timeout in communication with PMU
+ * @PM_RET_ERROR_NODE_USED:	node is already in use
+ */
+enum pm_ret_status {
+	PM_RET_SUCCESS = (0U),
+	PM_RET_ERROR_ARGS = (1U),
+	PM_RET_ERROR_NOTSUPPORTED = (4U),
+	PM_RET_ERROR_NOT_ENABLED = (29U),
+	PM_RET_ERROR_INTERNAL = (2000U),
+	PM_RET_ERROR_CONFLICT = (2001U),
+	PM_RET_ERROR_ACCESS = (2002U),
+	PM_RET_ERROR_INVALID_NODE = (2003U),
+	PM_RET_ERROR_DOUBLE_REQ = (2004U),
+	PM_RET_ERROR_ABORT_SUSPEND = (2005U),
+	PM_RET_ERROR_TIMEOUT = (2006U),
+	PM_RET_ERROR_NODE_USED = (2007U),
+	PM_RET_ERROR_NO_FEATURE = (2008U)
+};
+
+/**
+ * @PM_INITIAL_BOOT:	boot is a fresh system startup
+ * @PM_RESUME:		boot is a resume
+ * @PM_BOOT_ERROR:	error, boot cause cannot be identified
+ */
+enum pm_boot_status {
+	PM_INITIAL_BOOT,
+	PM_RESUME,
+	PM_BOOT_ERROR,
+};
+
+/**
+ * @PMF_SHUTDOWN_TYPE_SHUTDOWN:		shutdown
+ * @PMF_SHUTDOWN_TYPE_RESET:		reset/reboot
+ * @PMF_SHUTDOWN_TYPE_SETSCOPE_ONLY:	set the shutdown/reboot scope
+ */
+enum pm_shutdown_type {
+	PMF_SHUTDOWN_TYPE_SHUTDOWN,
+	PMF_SHUTDOWN_TYPE_RESET,
+	PMF_SHUTDOWN_TYPE_SETSCOPE_ONLY,
+};
+
+/**
+ * @PMF_SHUTDOWN_SUBTYPE_SUBSYSTEM:	shutdown/reboot APU subsystem only
+ * @PMF_SHUTDOWN_SUBTYPE_PS_ONLY:	shutdown/reboot entire PS (but not PL)
+ * @PMF_SHUTDOWN_SUBTYPE_SYSTEM:	shutdown/reboot entire system
+ */
+enum pm_shutdown_subtype {
+	PMF_SHUTDOWN_SUBTYPE_SUBSYSTEM,
+	PMF_SHUTDOWN_SUBTYPE_PS_ONLY,
+	PMF_SHUTDOWN_SUBTYPE_SYSTEM,
+};
+
+/**
+ * @PM_PLL_PARAM_DIV2:		Enable for divide by 2 function inside the PLL
+ * @PM_PLL_PARAM_FBDIV:		Feedback divisor integer portion for the PLL
+ * @PM_PLL_PARAM_DATA:		Feedback divisor fractional portion for the PLL
+ * @PM_PLL_PARAM_PRE_SRC:	Clock source for PLL input
+ * @PM_PLL_PARAM_POST_SRC:	Clock source for PLL Bypass mode
+ * @PM_PLL_PARAM_LOCK_DLY:	Lock circuit config settings for lock windowsize
+ * @PM_PLL_PARAM_LOCK_CNT:	Lock circuit counter setting
+ * @PM_PLL_PARAM_LFHF:		PLL loop filter high frequency capacitor control
+ * @PM_PLL_PARAM_CP:		PLL charge pump control
+ * @PM_PLL_PARAM_RES:		PLL loop filter resistor control
+ */
+enum pm_pll_param {
+	PM_PLL_PARAM_DIV2,
+	PM_PLL_PARAM_FBDIV,
+	PM_PLL_PARAM_DATA,
+	PM_PLL_PARAM_PRE_SRC,
+	PM_PLL_PARAM_POST_SRC,
+	PM_PLL_PARAM_LOCK_DLY,
+	PM_PLL_PARAM_LOCK_CNT,
+	PM_PLL_PARAM_LFHF,
+	PM_PLL_PARAM_CP,
+	PM_PLL_PARAM_RES,
+	PM_PLL_PARAM_MAX,
+};
+
+/**
+ * @PM_PLL_MODE_RESET:		PLL is in reset (not locked)
+ * @PM_PLL_MODE_INTEGER:	PLL is locked in integer mode
+ * @PM_PLL_MODE_FRACTIONAL:	PLL is locked in fractional mode
+ */
+enum pm_pll_mode {
+	PM_PLL_MODE_RESET,
+	PM_PLL_MODE_INTEGER,
+	PM_PLL_MODE_FRACTIONAL,
+	PM_PLL_MODE_MAX,
+};
+
+/**
+ * @PM_CLOCK_DIV0_ID:		Clock divider 0
+ * @PM_CLOCK_DIV1_ID:		Clock divider 1
+ */
+enum pm_clock_div_id {
+	PM_CLOCK_DIV0_ID,
+	PM_CLOCK_DIV1_ID,
+};
+
+/**
+ * EM API IDs
+ */
+enum em_api_id {
+	EM_SET_ACTION = 1,
+	EM_REMOVE_ACTION,
+	EM_SEND_ERRORS,
+};
+
+#endif /* PM_DEFS_H */
diff --git a/sys/dev/firmware/xilinx/zynqmp_firmware.c b/sys/dev/firmware/xilinx/zynqmp_firmware.c
new file mode 100644
index 000000000000..8ee6c9a21377
--- /dev/null
+++ b/sys/dev/firmware/xilinx/zynqmp_firmware.c
@@ -0,0 +1,511 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2023 Beckhoff Automation GmbH & Co. KG
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/malloc.h>
+#include <sys/bus.h>
+#include <sys/cpu.h>
+#include <machine/bus.h>
+
+#include <dev/fdt/simplebus.h>
+
+#include <dev/ofw/openfirm.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/psci/smccc.h>
+
+#include <dev/firmware/xilinx/pm_defs.h>
+
+#include "zynqmp_firmware_if.h"
+
+enum {
+	IOCTL_GET_RPU_OPER_MODE = 0,
+	IOCTL_SET_RPU_OPER_MODE = 1,
+	IOCTL_RPU_BOOT_ADDR_CONFIG = 2,
+	IOCTL_TCM_COMB_CONFIG = 3,
+	IOCTL_SET_TAPDELAY_BYPASS = 4,
+	IOCTL_SET_SGMII_MODE = 5,
+	IOCTL_SD_DLL_RESET = 6,
+	IOCTL_SET_SD_TAPDELAY = 7,
+	 /* Ioctl for clock driver */
+	IOCTL_SET_PLL_FRAC_MODE = 8,
+	IOCTL_GET_PLL_FRAC_MODE = 9,
+	IOCTL_SET_PLL_FRAC_DATA = 10,
+	IOCTL_GET_PLL_FRAC_DATA = 11,
+	IOCTL_WRITE_GGS = 12,
+	IOCTL_READ_GGS = 13,
+	IOCTL_WRITE_PGGS = 14,
+	IOCTL_READ_PGGS = 15,
+	/* IOCTL for ULPI reset */
+	IOCTL_ULPI_RESET = 16,
+	/* Set healthy bit value */
+	IOCTL_SET_BOOT_HEALTH_STATUS = 17,
+	IOCTL_AFI = 18,
+	/* Probe counter read/write */
+	IOCTL_PROBE_COUNTER_READ = 19,
+	IOCTL_PROBE_COUNTER_WRITE = 20,
+	IOCTL_OSPI_MUX_SELECT = 21,
+	/* IOCTL for USB power request */
+	IOCTL_USB_SET_STATE = 22,
+	/* IOCTL to get last reset reason */
+	IOCTL_GET_LAST_RESET_REASON = 23,
+	/* AI engine NPI ISR clear */
+	IOCTL_AIE_ISR_CLEAR = 24,
+	/* Register SGI to ATF */
+	IOCTL_REGISTER_SGI = 25,
+};
+
+typedef int (*zynqmp_callfn_t)(register_t, register_t, register_t, uint32_t *payload);
+
+struct zynqmp_firmware_softc {
+	struct simplebus_softc	sc;
+	device_t		dev;
+	zynqmp_callfn_t		callfn;
+};
+
+/* SMC calling methods */
+#define	PM_SIP_SVC	0xC2000000
+
+static int
+zynqmp_call_smc(uint32_t id, uint32_t a0, uint32_t a1, uint32_t a2, uint32_t a3, uint32_t *payload, bool ignore_error)
+{
+	struct arm_smccc_res res;
+	uint64_t args[3];
+
+	args[0] = id | PM_SIP_SVC;
+	args[1] = ((uint64_t)a1 << 32) | a0;
+	args[2] = ((uint64_t)a3 << 32) | a2;
+	arm_smccc_smc(args[0], args[1], args[2], 0, 0, 0, 0, 0, &res);
+	if (payload != NULL) {
+		payload[0] = res.a0 & 0xFFFFFFFF;
+		payload[1] = res.a0 >> 32;
+		payload[2] = res.a1 & 0xFFFFFFFF;
+		payload[3] = res.a1 >> 32;
+		if (!ignore_error && payload[0] != PM_RET_SUCCESS) {
+			printf("%s: fail %x\n", __func__, payload[0]);
+			return (EINVAL);
+		}
+	}
+	return (0);
+}
+
+/* Firmware methods */
+static int
+zynqmp_get_api_version(struct zynqmp_firmware_softc *sc)
+{
+	uint32_t payload[4];
+	int rv;
+
+	rv = zynqmp_call_smc(PM_GET_API_VERSION, 0, 0, 0, 0, payload, false);
+	if (rv != 0) {
+		device_printf(sc->dev, "SMC Call fail %d\n", rv);
+		goto out;
+	}
+	device_printf(sc->dev, "API version = %d.%d\n",
+	    payload[1] >> 16, payload[1] & 0xFFFF);
+out:
+	return (rv);
+}
+
+static int
+zynqmp_get_chipid(struct zynqmp_firmware_softc *sc)
+{
+	uint32_t payload[4];
+	int rv;
+
+	rv = zynqmp_call_smc(PM_GET_CHIPID, 0, 0, 0, 0, payload, false);
+	if (rv != 0) {
+		device_printf(sc->dev, "SMC Call fail %d\n", rv);
+		goto out;
+	}
+	device_printf(sc->dev, "ID Code = %x Version = %x\n",
+	    payload[1], payload[2]);
+out:
+	return (rv);
+}
+
+static int
+zynqmp_get_trustzone_version(struct zynqmp_firmware_softc *sc)
+{
+	uint32_t payload[4];
+	int rv;
+
+	rv = zynqmp_call_smc(PM_GET_TRUSTZONE_VERSION, 0, 0, 0, 0, payload, false);
+	if (rv != 0) {
+		device_printf(sc->dev, "SMC Call fail %d\n", rv);
+		goto out;
+	}
+	device_printf(sc->dev, "Trustzone Version = %x\n",
+	    payload[1]);
+out:
+	return (rv);
+}
+
+/* zynqmp_firmware methods */
+static int
+zynqmp_firmware_clock_enable(device_t dev, uint32_t clkid)
+{
+	struct zynqmp_firmware_softc *sc;
+	uint32_t payload[4];
+	int rv;
+
+	sc = device_get_softc(dev);
+	rv = zynqmp_call_smc(PM_CLOCK_ENABLE, clkid, 0, 0, 0, payload, false);
+	if (rv != 0)
+		device_printf(sc->dev, "SMC Call fail %d\n", rv);
+	return (rv);
+}
+
+static int
+zynqmp_firmware_clock_disable(device_t dev, uint32_t clkid)
+{
+	struct zynqmp_firmware_softc *sc;
+	uint32_t payload[4];
+	int rv;
+
+	sc = device_get_softc(dev);
+	rv = zynqmp_call_smc(PM_CLOCK_DISABLE, clkid, 0, 0, 0, payload, false);
+	if (rv != 0)
+		device_printf(sc->dev, "SMC Call fail %d\n", rv);
+	return (rv);
+}
+
+static int
+zynqmp_firmware_clock_getstate(device_t dev, uint32_t clkid, bool *enabled)
+{
+	struct zynqmp_firmware_softc *sc;
+	uint32_t payload[4];
+	int rv;
+
+	sc = device_get_softc(dev);
+	rv = zynqmp_call_smc(PM_CLOCK_GETSTATE, clkid, 0, 0, 0, payload, false);
+	if (rv != 0) {
+		device_printf(sc->dev, "SMC Call fail %d\n", rv);
+		goto out;
+	}
+	*enabled = payload[1] == 1 ? true : false;
+
+out:
+	return (rv);
+}
+
+static int
+zynqmp_firmware_clock_setdivider(device_t dev, uint32_t clkid, uint32_t div)
+{
+	struct zynqmp_firmware_softc *sc;
+	uint32_t payload[4];
+	int rv;
+
+	sc = device_get_softc(dev);
+	rv = zynqmp_call_smc(PM_CLOCK_SETDIVIDER, clkid, div, 0, 0, payload, false);
+	if (rv != 0)
+		device_printf(sc->dev, "SMC Call fail %d\n", rv);
+	return (rv);
+}
+
+static int
+zynqmp_firmware_clock_getdivider(device_t dev, uint32_t clkid, uint32_t *div)
+{
+	struct zynqmp_firmware_softc *sc;
+	uint32_t payload[4];
+	int rv;
+
+	sc = device_get_softc(dev);
+	rv = zynqmp_call_smc(PM_CLOCK_GETDIVIDER, clkid, 0, 0, 0, payload, false);
+	if (rv != 0) {
+		device_printf(sc->dev, "SMC Call fail %d\n", rv);
+		goto out;
+	}
+	*div = payload[1];
+
+out:
+	return (rv);
+}
+
+static int
+zynqmp_firmware_clock_setparent(device_t dev, uint32_t clkid, uint32_t parentid)
+{
+	struct zynqmp_firmware_softc *sc;
+	uint32_t payload[4];
+	int rv;
+
+	sc = device_get_softc(dev);
+	rv = zynqmp_call_smc(PM_CLOCK_SETPARENT, clkid, parentid, 0, 0, payload, false);
+	if (rv != 0)
+		device_printf(sc->dev, "SMC Call fail %d\n", rv);
+	return (rv);
+}
+
+static int
+zynqmp_firmware_clock_getparent(device_t dev, uint32_t clkid, uint32_t *parentid)
+{
+	struct zynqmp_firmware_softc *sc;
+	uint32_t payload[4];
+	int rv;
+
+	sc = device_get_softc(dev);
+	rv = zynqmp_call_smc(PM_CLOCK_GETPARENT, clkid, 0, 0, 0, payload, false);
+	if (rv != 0) {
+		device_printf(sc->dev, "SMC Call fail %d\n", rv);
+		goto out;
+	}
+	*parentid = payload[1];
+out:
+	return (rv);
+}
+
+static int
+zynqmp_firmware_pll_get_mode(device_t dev, uint32_t pllid, uint32_t *mode)
+{
+	struct zynqmp_firmware_softc *sc;
+	uint32_t payload[4];
+	int rv;
+
+	sc = device_get_softc(dev);
+	rv = zynqmp_call_smc(PM_IOCTL, 0, IOCTL_GET_PLL_FRAC_MODE, pllid, 0, payload, false);
+	if (rv != 0) {
+		device_printf(sc->dev, "SMC Call fail %d\n", rv);
+		goto out;
+	}
+	*mode = payload[1];
+out:
+	return (rv);
+}
+
+static int
+zynqmp_firmware_pll_get_frac_data(device_t dev, uint32_t pllid, uint32_t *data)
+{
+	struct zynqmp_firmware_softc *sc;
+	uint32_t payload[4];
+	int rv;
+
+	sc = device_get_softc(dev);
+	rv = zynqmp_call_smc(PM_IOCTL, 0, IOCTL_GET_PLL_FRAC_DATA, pllid, 0, payload, false);
+	if (rv != 0) {
+		device_printf(sc->dev, "SMC Call fail %d\n", rv);
+		goto out;
+	}
+	*data = payload[1];
+out:
+	return (rv);
+}
+
+static int
+zynqmp_firmware_clock_get_fixedfactor(device_t dev, uint32_t clkid, uint32_t *mult, uint32_t *div)
+{
+	struct zynqmp_firmware_softc *sc;
+	uint32_t payload[4];
+	int rv;
+
+	sc = device_get_softc(dev);
+	rv = zynqmp_call_smc(PM_QUERY_DATA, PM_QID_CLOCK_GET_FIXEDFACTOR_PARAMS, clkid, 0, 0, payload, true);
+	if (rv != 0) {
+		device_printf(sc->dev, "SMC Call fail %d\n", rv);
+		goto out;
+	}
+	*mult = payload[1];
+	*div = payload[2];
+out:
+	return (rv);
+}
+
+static int
+zynqmp_firmware_query_data(device_t dev, uint32_t qid, uint32_t arg1, uint32_t arg2, uint32_t arg3, uint32_t *data)
+{
+	struct zynqmp_firmware_softc *sc;
+	int rv;
+
+	sc = device_get_softc(dev);
+	rv = zynqmp_call_smc(PM_QUERY_DATA, qid, arg1, arg2, arg3, data, true);
+	/*
+	 * PM_QID_CLOCK_GET_NAME always success and if the clock name couldn't
+	 * be found the clock name will be all null byte
+	 */
+	if (qid == 1)
+		rv = 0;
+	if (rv != 0)
+		device_printf(sc->dev, "SMC Call fail %d\n", rv);
+	return (rv);
+}
+
+static int
+zynqmp_firmware_reset_assert(device_t dev, uint32_t resetid, bool enable)
+{
+	struct zynqmp_firmware_softc *sc;
+	int rv;
+
+	sc = device_get_softc(dev);
+	rv = zynqmp_call_smc(PM_RESET_ASSERT, resetid, enable, 0, 0, NULL, true);
+	if (rv != 0)
+		device_printf(sc->dev, "SMC Call fail %d\n", rv);
+
+	return (rv);
+}
+
+static int
+zynqmp_firmware_reset_get_status(device_t dev, uint32_t resetid, bool *status)
+{
+	struct zynqmp_firmware_softc *sc;
+	uint32_t payload[4];
+	int rv;
+
+	sc = device_get_softc(dev);
+	rv = zynqmp_call_smc(PM_RESET_GET_STATUS, resetid, 0, 0, 0, payload, true);
+	if (rv != 0) {
+		device_printf(sc->dev, "SMC Call fail %d\n", rv);
+		return (rv);
+	}
+	*status = payload[1];
+
+	return (rv);
+}
+
+/* Simplebus methods */
+static struct simplebus_devinfo *
+zynqmp_firmware_setup_dinfo(device_t dev, phandle_t node,
+    struct simplebus_devinfo *di)
+{
+	struct simplebus_softc *sc;
+	struct simplebus_devinfo *ndi;
+
+	sc = device_get_softc(dev);
+	if (di == NULL)
+		ndi = malloc(sizeof(*ndi), M_DEVBUF, M_WAITOK | M_ZERO);
+	else
+		ndi = di;
+	if (ofw_bus_gen_setup_devinfo(&ndi->obdinfo, node) != 0) {
+		if (di == NULL)
+			free(ndi, M_DEVBUF);
+		return (NULL);
+	}
+
+	/* reg resources is from the parent but interrupts is on the node itself */
+	resource_list_init(&ndi->rl);
+	ofw_bus_reg_to_rl(dev, OF_parent(node), sc->acells, sc->scells, &ndi->rl);
+	ofw_bus_intr_to_rl(dev, node, &ndi->rl, NULL);
+
+	return (ndi);
+}
+
+static device_t
+zynqmp_firmware_add_device(device_t dev, phandle_t node, u_int order,
+    const char *name, int unit, struct simplebus_devinfo *di)
+{
+	struct simplebus_devinfo *ndi;
+	device_t cdev;
+
+	if ((ndi = zynqmp_firmware_setup_dinfo(dev, node, di)) == NULL)
+		return (NULL);
+	cdev = device_add_child_ordered(dev, order, name, unit);
+	if (cdev == NULL) {
+		device_printf(dev, "<%s>: device_add_child failed\n",
+		    ndi->obdinfo.obd_name);
+		resource_list_free(&ndi->rl);
+		ofw_bus_gen_destroy_devinfo(&ndi->obdinfo);
+		if (di == NULL)
+			free(ndi, M_DEVBUF);
+		return (NULL);
+	}
+	device_set_ivars(cdev, ndi);
+
+	return(cdev);
+}
+
+static int
+zynqmp_firmware_probe(device_t dev)
+{
+
+	if (!ofw_bus_status_okay(dev))
+		return (ENXIO);
+	if (!ofw_bus_is_compatible(dev, "xlnx,zynqmp-firmware"))
+		return (ENXIO);
+	device_set_desc(dev, "ZynqMP Firmware");
+	return (0);
+}
+
+static int
+zynqmp_firmware_attach(device_t dev)
+{
+	struct zynqmp_firmware_softc *sc;
+	phandle_t node, child;
+	device_t cdev;
+
+	sc = device_get_softc(dev);
+	sc->dev = dev;
+
+	if (bootverbose) {
+		zynqmp_get_api_version(sc);
+		zynqmp_get_chipid(sc);
+		zynqmp_get_trustzone_version(sc);
+	}
+
+	/* Attach children */
+	node = ofw_bus_get_node(dev);
+	for (child = OF_child(node); child != 0; child = OF_peer(child)) {
+		cdev = zynqmp_firmware_add_device(dev, child, 0, NULL, -1, NULL);
+		if (cdev != NULL)
+			device_probe_and_attach(cdev);
+	}
+
+	return (bus_generic_attach(dev));
+}
+
+static device_method_t zynqmp_firmware_methods[] = {
+	/* device_if */
+	DEVMETHOD(device_probe, 	zynqmp_firmware_probe),
+	DEVMETHOD(device_attach, 	zynqmp_firmware_attach),
+
+	/* zynqmp_firmware_if */
+	DEVMETHOD(zynqmp_firmware_clock_enable, zynqmp_firmware_clock_enable),
+	DEVMETHOD(zynqmp_firmware_clock_disable, zynqmp_firmware_clock_disable),
+	DEVMETHOD(zynqmp_firmware_clock_getstate, zynqmp_firmware_clock_getstate),
+	DEVMETHOD(zynqmp_firmware_clock_setdivider, zynqmp_firmware_clock_setdivider),
+	DEVMETHOD(zynqmp_firmware_clock_getdivider, zynqmp_firmware_clock_getdivider),
+	DEVMETHOD(zynqmp_firmware_clock_setparent, zynqmp_firmware_clock_setparent),
+	DEVMETHOD(zynqmp_firmware_clock_getparent, zynqmp_firmware_clock_getparent),
+	DEVMETHOD(zynqmp_firmware_pll_get_mode, zynqmp_firmware_pll_get_mode),
+	DEVMETHOD(zynqmp_firmware_pll_get_frac_data, zynqmp_firmware_pll_get_frac_data),
+	DEVMETHOD(zynqmp_firmware_clock_get_fixedfactor, zynqmp_firmware_clock_get_fixedfactor),
+	DEVMETHOD(zynqmp_firmware_query_data, zynqmp_firmware_query_data),
+	DEVMETHOD(zynqmp_firmware_reset_assert, zynqmp_firmware_reset_assert),
+	DEVMETHOD(zynqmp_firmware_reset_get_status, zynqmp_firmware_reset_get_status),
+
+	DEVMETHOD_END
+};
+
+DEFINE_CLASS_1(zynqmp_firmware, zynqmp_firmware_driver, zynqmp_firmware_methods,
+  sizeof(struct zynqmp_firmware_softc), simplebus_driver);
+
+EARLY_DRIVER_MODULE(zynqmp_firmware, simplebus, zynqmp_firmware_driver, 0, 0,
+    BUS_PASS_BUS + BUS_PASS_ORDER_LATE);
+MODULE_VERSION(zynqmp_firmware, 1);
diff --git a/sys/dev/firmware/xilinx/zynqmp_firmware_if.m b/sys/dev/firmware/xilinx/zynqmp_firmware_if.m
new file mode 100644
index 000000000000..1007d721952d
--- /dev/null
+++ b/sys/dev/firmware/xilinx/zynqmp_firmware_if.m
@@ -0,0 +1,109 @@
+#-
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (c) 2023 Beckhoff Automation GmbH & Co. KG
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# $FreeBSD$
+
+INTERFACE zynqmp_firmware;
+
+METHOD int clock_enable {
+	device_t dev;
+	uint32_t clkid;
+};
+
+METHOD int clock_disable {
+	device_t dev;
+	uint32_t clkid;
+};
*** 70 LINES SKIPPED ***



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?202310181431.39IEVaUH089014>