summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/mailbox/zynqmp-ipi-mailbox.c159
1 files changed, 152 insertions, 7 deletions
diff --git a/drivers/mailbox/zynqmp-ipi-mailbox.c b/drivers/mailbox/zynqmp-ipi-mailbox.c
index 1cb553cced90..7c90bac3de21 100644
--- a/drivers/mailbox/zynqmp-ipi-mailbox.c
+++ b/drivers/mailbox/zynqmp-ipi-mailbox.c
@@ -6,9 +6,11 @@
*/
#include <linux/arm-smccc.h>
+#include <linux/cpuhotplug.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/interrupt.h>
+#include <linux/irqdomain.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/mailbox_controller.h>
@@ -16,6 +18,7 @@
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
+#include <linux/of_irq.h>
#include <linux/platform_device.h>
/* IPI agent ID any */
@@ -59,6 +62,8 @@
#define DST_BIT_POS 9U
#define SRC_BITMASK GENMASK(11, 8)
+#define MAX_SGI 16
+
/**
* struct zynqmp_ipi_mchan - Description of a Xilinx ZynqMP IPI mailbox channel
* @is_opened: indicate if the IPI channel is opened
@@ -111,6 +116,7 @@ struct zynqmp_ipi_mbox {
* @irq: IPI agent interrupt ID
* @method: IPI SMC or HVC is going to be used
* @local_id: local IPI agent ID
+ * @virq_sgi: IRQ number mapped to SGI
* @num_mboxes: number of mailboxes of this IPI agent
* @ipi_mboxes: IPI mailboxes of this IPI agent
*/
@@ -119,10 +125,13 @@ struct zynqmp_ipi_pdata {
int irq;
unsigned int method;
u32 local_id;
+ int virq_sgi;
int num_mboxes;
struct zynqmp_ipi_mbox ipi_mboxes[] __counted_by(num_mboxes);
};
+static DEFINE_PER_CPU(struct zynqmp_ipi_pdata *, per_cpu_pdata);
+
static struct device_driver zynqmp_ipi_mbox_driver = {
.owner = THIS_MODULE,
.name = "zynqmp-ipi-mbox",
@@ -189,6 +198,14 @@ static irqreturn_t zynqmp_ipi_interrupt(int irq, void *data)
return status;
}
+static irqreturn_t zynqmp_sgi_interrupt(int irq, void *data)
+{
+ struct zynqmp_ipi_pdata **pdata_ptr = data;
+ struct zynqmp_ipi_pdata *pdata = *pdata_ptr;
+
+ return zynqmp_ipi_interrupt(irq, pdata);
+}
+
/**
* zynqmp_ipi_peek_data - Peek to see if there are any rx messages.
*
@@ -748,6 +765,112 @@ static int versal_ipi_setup(struct zynqmp_ipi_mbox *ipi_mbox,
return 0;
}
+static int xlnx_mbox_cpuhp_start(unsigned int cpu)
+{
+ struct zynqmp_ipi_pdata *pdata;
+
+ pdata = get_cpu_var(per_cpu_pdata);
+ put_cpu_var(per_cpu_pdata);
+ enable_percpu_irq(pdata->virq_sgi, IRQ_TYPE_NONE);
+
+ return 0;
+}
+
+static int xlnx_mbox_cpuhp_down(unsigned int cpu)
+{
+ struct zynqmp_ipi_pdata *pdata;
+
+ pdata = get_cpu_var(per_cpu_pdata);
+ put_cpu_var(per_cpu_pdata);
+ disable_percpu_irq(pdata->virq_sgi);
+
+ return 0;
+}
+
+static void xlnx_disable_percpu_irq(void *data)
+{
+ struct zynqmp_ipi_pdata *pdata;
+
+ pdata = *this_cpu_ptr(&per_cpu_pdata);
+
+ disable_percpu_irq(pdata->virq_sgi);
+}
+
+static int xlnx_mbox_init_sgi(struct platform_device *pdev,
+ int sgi_num,
+ struct zynqmp_ipi_pdata *pdata)
+{
+ int ret = 0;
+ int cpu;
+
+ /*
+ * IRQ related structures are used for the following:
+ * for each SGI interrupt ensure its mapped by GIC IRQ domain
+ * and that each corresponding linux IRQ for the HW IRQ has
+ * a handler for when receiving an interrupt from the remote
+ * processor.
+ */
+ struct irq_domain *domain;
+ struct irq_fwspec sgi_fwspec;
+ struct device_node *interrupt_parent = NULL;
+ struct device *dev = &pdev->dev;
+
+ /* Find GIC controller to map SGIs. */
+ interrupt_parent = of_irq_find_parent(dev->of_node);
+ if (!interrupt_parent) {
+ dev_err(&pdev->dev, "Failed to find property for Interrupt parent\n");
+ return -EINVAL;
+ }
+
+ /* Each SGI needs to be associated with GIC's IRQ domain. */
+ domain = irq_find_host(interrupt_parent);
+ of_node_put(interrupt_parent);
+
+ /* Each mapping needs GIC domain when finding IRQ mapping. */
+ sgi_fwspec.fwnode = domain->fwnode;
+
+ /*
+ * When irq domain looks at mapping each arg is as follows:
+ * 3 args for: interrupt type (SGI), interrupt # (set later), type
+ */
+ sgi_fwspec.param_count = 1;
+
+ /* Set SGI's hwirq */
+ sgi_fwspec.param[0] = sgi_num;
+ pdata->virq_sgi = irq_create_fwspec_mapping(&sgi_fwspec);
+
+ for_each_possible_cpu(cpu)
+ per_cpu(per_cpu_pdata, cpu) = pdata;
+
+ ret = request_percpu_irq(pdata->virq_sgi, zynqmp_sgi_interrupt, pdev->name,
+ &per_cpu_pdata);
+ WARN_ON(ret);
+ if (ret) {
+ irq_dispose_mapping(pdata->virq_sgi);
+ return ret;
+ }
+
+ irq_to_desc(pdata->virq_sgi);
+ irq_set_status_flags(pdata->virq_sgi, IRQ_PER_CPU);
+
+ /* Setup function for the CPU hot-plug cases */
+ cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "mailbox/sgi:starting",
+ xlnx_mbox_cpuhp_start, xlnx_mbox_cpuhp_down);
+
+ return ret;
+}
+
+static void xlnx_mbox_cleanup_sgi(struct zynqmp_ipi_pdata *pdata)
+{
+ cpuhp_remove_state(CPUHP_AP_ONLINE_DYN);
+
+ on_each_cpu(xlnx_disable_percpu_irq, NULL, 1);
+
+ irq_clear_status_flags(pdata->virq_sgi, IRQ_PER_CPU);
+ free_percpu_irq(pdata->virq_sgi, &per_cpu_pdata);
+ irq_dispose_mapping(pdata->virq_sgi);
+}
+
/**
* zynqmp_ipi_free_mboxes - Free IPI mailboxes devices
*
@@ -758,6 +881,9 @@ static void zynqmp_ipi_free_mboxes(struct zynqmp_ipi_pdata *pdata)
struct zynqmp_ipi_mbox *ipi_mbox;
int i;
+ if (pdata->irq < MAX_SGI)
+ xlnx_mbox_cleanup_sgi(pdata);
+
i = pdata->num_mboxes;
for (; i >= 0; i--) {
ipi_mbox = &pdata->ipi_mboxes[i];
@@ -773,7 +899,8 @@ static int zynqmp_ipi_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *nc, *np = pdev->dev.of_node;
- struct zynqmp_ipi_pdata *pdata;
+ struct zynqmp_ipi_pdata __percpu *pdata;
+ struct of_phandle_args out_irq;
struct zynqmp_ipi_mbox *mbox;
int num_mboxes, ret = -EINVAL;
setup_ipi_fn ipi_fn;
@@ -821,14 +948,32 @@ static int zynqmp_ipi_probe(struct platform_device *pdev)
mbox++;
}
- /* IPI IRQ */
- ret = platform_get_irq(pdev, 0);
- if (ret < 0)
+ ret = of_irq_parse_one(dev_of_node(dev), 0, &out_irq);
+ if (ret < 0) {
+ dev_err(dev, "failed to parse interrupts\n");
goto free_mbox_dev;
+ }
+ ret = out_irq.args[1];
+
+ /*
+ * If Interrupt number is in SGI range, then request SGI else request
+ * IPI system IRQ.
+ */
+ if (ret < MAX_SGI) {
+ pdata->irq = ret;
+ ret = xlnx_mbox_init_sgi(pdev, pdata->irq, pdata);
+ if (ret)
+ goto free_mbox_dev;
+ } else {
+ ret = platform_get_irq(pdev, 0);
+ if (ret < 0)
+ goto free_mbox_dev;
+
+ pdata->irq = ret;
+ ret = devm_request_irq(dev, pdata->irq, zynqmp_ipi_interrupt,
+ IRQF_SHARED, dev_name(dev), pdata);
+ }
- pdata->irq = ret;
- ret = devm_request_irq(dev, pdata->irq, zynqmp_ipi_interrupt,
- IRQF_SHARED, dev_name(dev), pdata);
if (ret) {
dev_err(dev, "IRQ %d is not requested successfully.\n",
pdata->irq);