[PATCH 0/4] mailbox: zynqmp: support multiple child nodes

Current zynqmp_ipi driver is hardcoded to work with only PMU mailbox node.
This patch series adds new driver "zynqmp_ipi_dest" to support multiple child node of current "zynqmp-ipi-mailbox" node. It also adds support of SMC calls to TF-A to configure IPI register address space.
New driver uses compatible property recently introduced for each child mailbox node. However it is important to maintain backward compatibility for old dts. Previous driver now binds each child node to "zynqmp_ipi_dest" driver which is needed to maintain backward compatibility.
Tanmay Shah (4): mailbox: zynqmp: support smc calls to TF-A mailbox: add el3 support only for zynqmp platform mailbox: zynqmp: support mulitple mboxes via device-tree dts: zynqmp: add missing destination mailbox compatible
arch/arm/dts/zynqmp.dtsi | 1 + drivers/firmware/firmware-zynqmp.c | 22 ++++ drivers/mailbox/zynqmp-ipi.c | 159 ++++++++++++++++++++++++++--- 3 files changed, 169 insertions(+), 13 deletions(-)
base-commit: 5c4e9d0c74a8c2f8d0f4e0ab9cf44959298c2bad

Use SMC calls to TF-A to operate IPI for execution level below 3. For EL3 use hardcode IPI registers as TF-A isn't available in EL3. Hence, in EL3 remote and local IPI ids retrieved using xlnx,ipi-id property are unused.
Signed-off-by: Tanmay Shah tanmay.shah@amd.com --- drivers/mailbox/zynqmp-ipi.c | 88 +++++++++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 2 deletions(-)
diff --git a/drivers/mailbox/zynqmp-ipi.c b/drivers/mailbox/zynqmp-ipi.c index 3e4ec47389..acd0b287db 100644 --- a/drivers/mailbox/zynqmp-ipi.c +++ b/drivers/mailbox/zynqmp-ipi.c @@ -8,9 +8,12 @@ #include <common.h> #include <log.h> #include <asm/io.h> +#include <asm/system.h> #include <dm.h> #include <mailbox-uclass.h> #include <dm/device_compat.h> +#include <dm/of_access.h> +#include <linux/arm-smccc.h> #include <linux/ioport.h> #include <linux/io.h> #include <wait_bit.h> @@ -21,6 +24,43 @@ #define IPI_BIT_MASK_PMU0 0x10000 #define IPI_INT_REG_BASE_APU 0xFF300000
+/* IPI agent ID any */ +#define IPI_ID_ANY 0xFFUL + +/* indicate if ZynqMP IPI mailbox driver uses SMC calls or HVC calls */ +#define USE_SMC 0 + +/* Default IPI SMC function IDs */ +#define SMC_IPI_MAILBOX_OPEN 0x82001000U +#define SMC_IPI_MAILBOX_RELEASE 0x82001001U +#define SMC_IPI_MAILBOX_STATUS_ENQUIRY 0x82001002U +#define SMC_IPI_MAILBOX_NOTIFY 0x82001003U +#define SMC_IPI_MAILBOX_ACK 0x82001004U +#define SMC_IPI_MAILBOX_ENABLE_IRQ 0x82001005U +#define SMC_IPI_MAILBOX_DISABLE_IRQ 0x82001006U + +/* IPI SMC Macros */ + +/* + * Flag to indicate if notification interrupt + * to be disabled. + */ +#define IPI_SMC_ENQUIRY_DIRQ_MASK BIT(0) + +/* + * Flag to indicate if notification interrupt + * to be enabled. + */ +#define IPI_SMC_ACK_EIRQ_MASK BIT(0) + +/* IPI mailbox status */ +#define IPI_MB_STATUS_IDLE 0 +#define IPI_MB_STATUS_SEND_PENDING 1 +#define IPI_MB_STATUS_RECV_PENDING 2 + +#define IPI_MB_CHNL_TX 0 /* IPI mailbox TX channel */ +#define IPI_MB_CHNL_RX 1 /* IPI mailbox RX channel */ + struct ipi_int_regs { u32 trig; /* 0x0 */ u32 obs; /* 0x4 */ @@ -39,8 +79,23 @@ struct zynqmp_ipi { void __iomem *local_res_regs; void __iomem *remote_req_regs; void __iomem *remote_res_regs; + u32 remote_id; + u32 local_id; };
+static int zynqmp_ipi_fw_call(struct zynqmp_ipi *ipi_mbox, + unsigned long a0, unsigned long a3) +{ + struct arm_smccc_res res = {0}; + unsigned long a1, a2; + + a1 = ipi_mbox->local_id; + a2 = ipi_mbox->remote_id; + arm_smccc_smc(a0, a1, a2, a3, 0, 0, 0, 0, &res); + + return (int)res.a0; +} + static int zynqmp_ipi_send(struct mbox_chan *chan, const void *data) { const struct zynqmp_ipi_msg *msg = (struct zynqmp_ipi_msg *)data; @@ -51,6 +106,15 @@ static int zynqmp_ipi_send(struct mbox_chan *chan, const void *data) for (size_t i = 0; i < msg->len; i++) writel(msg->buf[i], &mbx[i]);
+ /* Use SMC calls for Exception Level less than 3 where TF-A is available */ + if (!IS_ENABLED(CONFIG_SPL_BUILD) && current_el() < 3) { + ret = zynqmp_ipi_fw_call(zynqmp, SMC_IPI_MAILBOX_NOTIFY, 0); + + debug("%s, send %ld bytes\n", __func__, msg->len); + + return ret; + } + /* Write trigger interrupt */ writel(IPI_BIT_MASK_PMU0, &ipi_int_apu->trig);
@@ -67,16 +131,23 @@ static int zynqmp_ipi_recv(struct mbox_chan *chan, void *data) struct zynqmp_ipi_msg *msg = (struct zynqmp_ipi_msg *)data; struct zynqmp_ipi *zynqmp = dev_get_priv(chan->dev); u32 *mbx = (u32 *)zynqmp->local_res_regs; + int ret = 0;
/* * PMU Firmware does not trigger IPI interrupt for API call responses so - * there is no need to check ISR flags + * there is no need to check ISR flags for EL3. */ for (size_t i = 0; i < msg->len; i++) msg->buf[i] = readl(&mbx[i]);
+ /* Ack to remote if EL is not 3 */ + if (!IS_ENABLED(CONFIG_SPL_BUILD) && current_el() < 3) { + ret = zynqmp_ipi_fw_call(zynqmp, SMC_IPI_MAILBOX_ACK, + IPI_SMC_ACK_EIRQ_MASK); + } + debug("%s, recv %ld bytes\n", __func__, msg->len); - return 0; + return ret; };
static int zynqmp_ipi_probe(struct udevice *dev) @@ -84,6 +155,7 @@ static int zynqmp_ipi_probe(struct udevice *dev) struct zynqmp_ipi *zynqmp = dev_get_priv(dev); struct resource res; ofnode node; + int ret;
debug("%s(dev=%p)\n", __func__, dev);
@@ -91,6 +163,18 @@ static int zynqmp_ipi_probe(struct udevice *dev) /* Note IPI mailbox node needs to be the first one in DT */ node = ofnode_first_subnode(dev_ofnode(dev));
+ ret = dev_read_u32(dev, "xlnx,ipi-id", &zynqmp->local_id); + if (ret) { + dev_err(dev, "can't get local ipi id\n"); + return ret; + } + + ret = ofnode_read_u32(node, "xlnx,ipi-id", &zynqmp->remote_id); + if (ret) { + dev_err(dev, "can't get remote ipi id\n"); + return ret; + } + if (ofnode_read_resource_byname(node, "local_request_region", &res)) { dev_err(dev, "No reg property for local_request_region\n"); return -EINVAL;

If U-Boot is running in Exception Level 3 then use hardcode register values for mailbox message passing with PMU. This is only supported for zynqmp platform.
Signed-off-by: Tanmay Shah tanmay.shah@amd.com --- drivers/mailbox/zynqmp-ipi.c | 10 ++++++++++ 1 file changed, 10 insertions(+)
diff --git a/drivers/mailbox/zynqmp-ipi.c b/drivers/mailbox/zynqmp-ipi.c index acd0b287db..e76561f310 100644 --- a/drivers/mailbox/zynqmp-ipi.c +++ b/drivers/mailbox/zynqmp-ipi.c @@ -81,6 +81,7 @@ struct zynqmp_ipi { void __iomem *remote_res_regs; u32 remote_id; u32 local_id; + bool el3_supported; };
static int zynqmp_ipi_fw_call(struct zynqmp_ipi *ipi_mbox, @@ -115,6 +116,12 @@ static int zynqmp_ipi_send(struct mbox_chan *chan, const void *data) return ret; }
+ /* Return if EL3 is not supported */ + if (!zynqmp->el3_supported) { + dev_err(chan->dev, "mailbox in EL3 only supported for zynqmp"); + return -EOPNOTSUPP; + } + /* Write trigger interrupt */ writel(IPI_BIT_MASK_PMU0, &ipi_int_apu->trig);
@@ -159,6 +166,9 @@ static int zynqmp_ipi_probe(struct udevice *dev)
debug("%s(dev=%p)\n", __func__, dev);
+ if (IS_ENABLED(CONFIG_SPL_BUILD) || of_machine_is_compatible("xlnx,zynqmp")) + zynqmp->el3_supported = true; + /* Get subnode where the regs are defined */ /* Note IPI mailbox node needs to be the first one in DT */ node = ofnode_first_subnode(dev_ofnode(dev));

As of now only one mailbox agent is supported by mailbox driver. On zynqmp platform there are about 7 mailbox agents which can communicate over same IPI channel to U-Boot. This patch series introduces new "zynqmp_ipi_dest" driver which adds one to multi-channel mailbox support.
Following format in device-tree is expected as per latest bindings: zynqmp-ipi { compatible = "xlnx,zynqmp-ipi-mailbox";
mbox_1: mailbox@1 { /* New compatible for child node */ compatible = "xlnx,zynqmp-ipi-dest-mailbox"; ... };
...
mbox_n: mailbox@n { compatible = "xlnx,zynqmp-ipi-dest-mailbox"; ... } };
Then mailbox client uses child mailbox node as following:
ipi-dest-1 { ... mboxes = <mbox_1 0>, <mbox_1 1>; mbox-names = "tx", "rx"; ... };
New "zynqmp_ipi_dest" driver is for devices with "xlnx,zynqmp-ipi-dest-mailbox" compatible string. This driver will take care of mailbox send recv ops and it replaces previous "zynqmp_ipi" driver. Now "zynqmp_ipi" driver simply binds each child device with "zynqmp_ipi_dest" driver.
However, its important to maintain backward comaptibility with previous bindings where child node does not have compatible string. In such case, new driver isn't probed by U-Boot during boot and system fails to boot. To resolve this issue firmware-zynqmp.c driver probes all the IPI parent node driver which binds each child node device with "zynqmp_ipi_dest" driver.
This makes sure corresponding child driver will be probed when requested using mbox_get_by_name or mbox_get_by_idx framework calls.
This way multiple mailbox agents are supported in device-tree without breaking previous binding support.
Signed-off-by: Tanmay Shah tanmay.shah@amd.com --- drivers/firmware/firmware-zynqmp.c | 22 ++++++++++ drivers/mailbox/zynqmp-ipi.c | 65 ++++++++++++++++++++++++------ 2 files changed, 74 insertions(+), 13 deletions(-)
diff --git a/drivers/firmware/firmware-zynqmp.c b/drivers/firmware/firmware-zynqmp.c index 8ea15c7ed3..dfad798a2e 100644 --- a/drivers/firmware/firmware-zynqmp.c +++ b/drivers/firmware/firmware-zynqmp.c @@ -8,6 +8,7 @@ #include <common.h> #include <cpu_func.h> #include <dm.h> +#include <dm/device_compat.h> #include <dm/lists.h> #include <log.h> #include <zynqmp_firmware.h> @@ -290,10 +291,31 @@ int zynqmp_pmufw_load_config_object(const void *cfg_obj, size_t size)
static int zynqmp_power_probe(struct udevice *dev) { + struct udevice *ipi_dev; + ofnode ipi_node; int ret;
debug("%s, (dev=%p)\n", __func__, dev);
+ /* + * Probe all IPI parent node driver. It is important to have IPI + * devices available when requested by mbox_get_by* API. + * If IPI device isn't available, then mailbox request fails and + * that causes system boot failure. + * To avoid this make sure all IPI parent drivers are probed here, + * and IPI parent driver binds each child node to mailbox driver. + * This way mbox_get_by_* API will have correct mailbox device + * driver probed. + */ + ofnode_for_each_compatible_node(ipi_node, "xlnx,zynqmp-ipi-mailbox") { + ret = uclass_get_device_by_ofnode(UCLASS_NOP, ipi_node, &ipi_dev); + if (ret) { + dev_err(dev, "failed to get IPI device from node %s\n", + ofnode_get_name(ipi_node)); + return ret; + } + } + ret = mbox_get_by_name(dev, "tx", &zynqmp_power.tx_chan); if (ret) { debug("%s: Cannot find tx mailbox\n", __func__); diff --git a/drivers/mailbox/zynqmp-ipi.c b/drivers/mailbox/zynqmp-ipi.c index e76561f310..eb86847bbe 100644 --- a/drivers/mailbox/zynqmp-ipi.c +++ b/drivers/mailbox/zynqmp-ipi.c @@ -12,6 +12,7 @@ #include <dm.h> #include <mailbox-uclass.h> #include <dm/device_compat.h> +#include <dm/lists.h> #include <dm/of_access.h> #include <linux/arm-smccc.h> #include <linux/ioport.h> @@ -157,7 +158,7 @@ static int zynqmp_ipi_recv(struct mbox_chan *chan, void *data) return ret; };
-static int zynqmp_ipi_probe(struct udevice *dev) +static int zynqmp_ipi_dest_probe(struct udevice *dev) { struct zynqmp_ipi *zynqmp = dev_get_priv(dev); struct resource res; @@ -166,14 +167,12 @@ static int zynqmp_ipi_probe(struct udevice *dev)
debug("%s(dev=%p)\n", __func__, dev);
+ node = dev_ofnode(dev); + if (IS_ENABLED(CONFIG_SPL_BUILD) || of_machine_is_compatible("xlnx,zynqmp")) zynqmp->el3_supported = true;
- /* Get subnode where the regs are defined */ - /* Note IPI mailbox node needs to be the first one in DT */ - node = ofnode_first_subnode(dev_ofnode(dev)); - - ret = dev_read_u32(dev, "xlnx,ipi-id", &zynqmp->local_id); + ret = dev_read_u32(dev->parent, "xlnx,ipi-id", &zynqmp->local_id); if (ret) { dev_err(dev, "can't get local ipi id\n"); return ret; @@ -191,6 +190,8 @@ static int zynqmp_ipi_probe(struct udevice *dev) }; zynqmp->local_req_regs = devm_ioremap(dev, res.start, (res.start - res.end)); + if (!zynqmp->local_req_regs) + return -EINVAL;
if (ofnode_read_resource_byname(node, "local_response_region", &res)) { dev_err(dev, "No reg property for local_response_region\n"); @@ -198,6 +199,8 @@ static int zynqmp_ipi_probe(struct udevice *dev) }; zynqmp->local_res_regs = devm_ioremap(dev, res.start, (res.start - res.end)); + if (!zynqmp->local_res_regs) + return -EINVAL;
if (ofnode_read_resource_byname(node, "remote_request_region", &res)) { dev_err(dev, "No reg property for remote_request_region\n"); @@ -205,6 +208,8 @@ static int zynqmp_ipi_probe(struct udevice *dev) }; zynqmp->remote_req_regs = devm_ioremap(dev, res.start, (res.start - res.end)); + if (!zynqmp->remote_req_regs) + return -EINVAL;
if (ofnode_read_resource_byname(node, "remote_response_region", &res)) { dev_err(dev, "No reg property for remote_response_region\n"); @@ -212,25 +217,59 @@ static int zynqmp_ipi_probe(struct udevice *dev) }; zynqmp->remote_res_regs = devm_ioremap(dev, res.start, (res.start - res.end)); + if (!zynqmp->remote_res_regs) + return -EINVAL;
return 0; };
-static const struct udevice_id zynqmp_ipi_ids[] = { - { .compatible = "xlnx,zynqmp-ipi-mailbox" }, - { } +static int zynqmp_ipi_probe(struct udevice *dev) +{ + struct udevice *cdev; + ofnode cnode; + int ret; + + debug("%s(dev=%p)\n", __func__, dev); + + dev_for_each_subnode(cnode, dev) { + ret = device_bind_driver_to_node(dev, "zynqmp_ipi_dest", + ofnode_get_name(cnode), + cnode, &cdev); + if (ret) + return ret; + } + + return 0; };
-struct mbox_ops zynqmp_ipi_mbox_ops = { +struct mbox_ops zynqmp_ipi_dest_mbox_ops = { .send = zynqmp_ipi_send, .recv = zynqmp_ipi_recv, };
+static const struct udevice_id zynqmp_ipi_dest_ids[] = { + { .compatible = "xlnx,zynqmp-ipi-dest-mailbox" }, + { } +}; + +U_BOOT_DRIVER(zynqmp_ipi_dest) = { + .name = "zynqmp_ipi_dest", + .id = UCLASS_MAILBOX, + .of_match = zynqmp_ipi_dest_ids, + .probe = zynqmp_ipi_dest_probe, + .priv_auto = sizeof(struct zynqmp_ipi), + .ops = &zynqmp_ipi_dest_mbox_ops, +}; + +static const struct udevice_id zynqmp_ipi_ids[] = { + { .compatible = "xlnx,zynqmp-ipi-mailbox" }, + { } +}; + U_BOOT_DRIVER(zynqmp_ipi) = { .name = "zynqmp_ipi", - .id = UCLASS_MAILBOX, + .id = UCLASS_NOP, .of_match = zynqmp_ipi_ids, .probe = zynqmp_ipi_probe, - .priv_auto = sizeof(struct zynqmp_ipi), - .ops = &zynqmp_ipi_mbox_ops, + .flags = DM_FLAG_PROBE_AFTER_BIND, };

The commit in linux kernel 81186dc16101 ("dt-bindings: zynqmp: add destination mailbox compatible") make compatible string for child nodes mandatory that's why add it.
Signed-off-by: Tanmay Shah tanmay.shah@amd.com --- arch/arm/dts/zynqmp.dtsi | 1 + 1 file changed, 1 insertion(+)
diff --git a/arch/arm/dts/zynqmp.dtsi b/arch/arm/dts/zynqmp.dtsi index 58a56bc1bd..21be909b1a 100644 --- a/arch/arm/dts/zynqmp.dtsi +++ b/arch/arm/dts/zynqmp.dtsi @@ -148,6 +148,7 @@
ipi_mailbox_pmu1: mailbox@ff9905c0 { bootph-all; + compatible = "xlnx,zynqmp-ipi-dest-mailbox"; reg = <0x0 0xff9905c0 0x0 0x20>, <0x0 0xff9905e0 0x0 0x20>, <0x0 0xff990e80 0x0 0x20>,

On 12/4/23 22:56, Tanmay Shah wrote:
Current zynqmp_ipi driver is hardcoded to work with only PMU mailbox node.
This patch series adds new driver "zynqmp_ipi_dest" to support multiple child node of current "zynqmp-ipi-mailbox" node. It also adds support of SMC calls to TF-A to configure IPI register address space.
New driver uses compatible property recently introduced for each child mailbox node. However it is important to maintain backward compatibility for old dts. Previous driver now binds each child node to "zynqmp_ipi_dest" driver which is needed to maintain backward compatibility.
Tanmay Shah (4): mailbox: zynqmp: support smc calls to TF-A mailbox: add el3 support only for zynqmp platform mailbox: zynqmp: support mulitple mboxes via device-tree dts: zynqmp: add missing destination mailbox compatible
arch/arm/dts/zynqmp.dtsi | 1 + drivers/firmware/firmware-zynqmp.c | 22 ++++ drivers/mailbox/zynqmp-ipi.c | 159 ++++++++++++++++++++++++++--- 3 files changed, 169 insertions(+), 13 deletions(-)
base-commit: 5c4e9d0c74a8c2f8d0f4e0ab9cf44959298c2bad
I have reviewed it internally multiple times and also tested on kv260 and I can't see any issue. That's why applied.
Thanks, Michal
participants (2)
-
Michal Simek
-
Tanmay Shah