From 8c84a06f059632f3359c9852ae7e5aa0fd4e0cff Mon Sep 17 00:00:00 2001 From: Tero Kristo Date: Wed, 20 Feb 2019 15:14:16 -0600 Subject: [PATCH] remoteproc/pru: add support for parsing pru interrupt mapping from DT PRU interrupt mapping can now be parsed from devicetree also. The design uses a single property "ti,pru-interrupt-map" in the PRU consumer nodes to list all the interrupt mapping data associated with all the PRUs used by the PRU consumer/application. This is an alternative configuration method in addition to the legacy resource table config, and supercedes any firmware provided intc configuration structure. The design parses and configures the PRU INTC when a client driver acquires a PRU core through pru_rproc_get(), and unconfigures the INTC when it releases the PRU core through pru_rproc_put(). The design also allows any referenced PRU cores with zero corresponding entries in the "ti,pru-interrupt-map" property to fallback to use the interrupt mapping data through the firmware intc vendor resource type, that is processed during the loading/starting of the core through rproc_boot(). This provides the maximum flexibility for PRU client drivers/applications. If both are provided, the config in DT takes precedence. It is possible that a client neither has the DT property nor uses the vendor resource in its firmware, implying that particular PRU core doesn't use/need any interrupts. Signed-off-by: Tero Kristo [rogerq@ti.com: refactor DT-parse logic into a separate function] Signed-off-by: Roger Quadros [s-anna@ti.com: various fixes and cleanups, update patch description] Signed-off-by: Suman Anna --- drivers/remoteproc/pru_rproc.c | 124 +++++++++++++++++++++++++++++++-- 1 file changed, 120 insertions(+), 4 deletions(-) diff --git a/drivers/remoteproc/pru_rproc.c b/drivers/remoteproc/pru_rproc.c index 90a50973763b..1726c1266e84 100644 --- a/drivers/remoteproc/pru_rproc.c +++ b/drivers/remoteproc/pru_rproc.c @@ -75,9 +75,11 @@ enum pru_iomem { * @sdram_da: device address of secondary Data RAM for this PRU * @shrdram_da: device address of shared Data RAM * @fw_name: name of firmware image used during loading + * @dt_irqs: number of irqs configured from DT * @lock: mutex to protect client usage * @dbg_single_step: debug state variable to set PRU into single step mode * @dbg_continuous: debug state variable to restore PRU execution mode + * @fw_has_intc_rsc: boolean flag to indicate INTC config through firmware */ struct pru_rproc { int id; @@ -96,9 +98,11 @@ struct pru_rproc { u32 sdram_da; u32 shrdram_da; const char *fw_name; + int dt_irqs; struct mutex lock; /* client access lock */ u32 dbg_single_step; u32 dbg_continuous; + unsigned int fw_has_intc_rsc : 1; }; static void *pru_d_da_to_va(struct pru_rproc *pru, u32 da, int len); @@ -146,6 +150,104 @@ static int pru_rproc_set_firmware(struct rproc *rproc, const char *fw_name) return rproc_set_firmware(rproc, fw_name); } +static int pru_rproc_intc_dt_config(struct pru_rproc *pru, int index) +{ + struct device *dev = &pru->rproc->dev; + struct device_node *np = pru->client_np; + struct property *prop; + const char *prop_name = "ti,pru-interrupt-map"; + int ret = 0, i; + int dt_irqs; + u32 *arr; + bool has_irqs = false; + + prop = of_find_property(np, prop_name, NULL); + if (!prop) + return 0; + + dt_irqs = of_property_count_u32_elems(np, prop_name); + if (dt_irqs <= 0 || dt_irqs % 4) { + dev_err(dev, "bad interrupt map data %d, expected multiple of 4\n", + dt_irqs); + return -EINVAL; + } + + arr = kmalloc_array(dt_irqs, sizeof(u32), GFP_KERNEL); + if (!arr) + return -ENOMEM; + + ret = of_property_read_u32_array(np, prop_name, arr, dt_irqs); + if (ret) { + dev_err(dev, "failed to read pru irq map: %d\n", ret); + goto out; + } + + for (i = 0; i < ARRAY_SIZE(pru->intc_config.sysev_to_ch); i++) + pru->intc_config.sysev_to_ch[i] = -1; + + for (i = 0; i < ARRAY_SIZE(pru->intc_config.ch_to_host); i++) + pru->intc_config.ch_to_host[i] = -1; + + for (i = 0; i < dt_irqs; i += 4) { + if (arr[i] != index) + continue; + + if (arr[i + 1] < 0 || + arr[i + 1] >= MAX_PRU_SYS_EVENTS) { + dev_err(dev, "bad sys event %d\n", arr[i + 1]); + ret = -EINVAL; + goto out; + } + + if (arr[i + 2] < 0 || + arr[i + 2] >= MAX_PRU_CHANNELS) { + dev_err(dev, "bad channel %d\n", arr[i + 2]); + ret = -EINVAL; + goto out; + } + + if (arr[i + 3] < 0 || + arr[i + 3] >= MAX_PRU_HOST_INT) { + dev_err(dev, "bad irq %d\n", arr[i + 3]); + ret = -EINVAL; + goto out; + } + + pru->intc_config.sysev_to_ch[arr[i + 1]] = arr[i + 2]; + dev_dbg(dev, "sysevt-to-ch[%d] -> %d\n", arr[i + 1], + arr[i + 2]); + + pru->intc_config.ch_to_host[arr[i + 2]] = arr[i + 3]; + dev_dbg(dev, "chnl-to-host[%d] -> %d\n", arr[i + 2], + arr[i + 3]); + + has_irqs = true; + } + + /* + * The property "ti,pru-interrupt-map" is used in a consumer node, but + * need not necessarily have data for all referenced PRUs. Provide a + * fallback to get the interrupt data from firmware for PRUs ith no + * interrupt data. + */ + if (!has_irqs) { + dev_dbg(dev, "no DT irqs, falling back to firmware intc rsc mode\n"); + goto out; + } + + pru->dt_irqs = dt_irqs; + ret = pruss_intc_configure(pru->pruss, &pru->intc_config); + if (ret) { + dev_err(dev, "failed to configure intc %d\n", ret); + pru->dt_irqs = 0; + } + +out: + kfree(arr); + + return ret; +} + static struct rproc *__pru_rproc_get(struct device_node *np, int index) { struct device_node *rproc_np = NULL; @@ -234,6 +336,10 @@ struct rproc *pru_rproc_get(struct device_node *np, int index) } } + ret = pru_rproc_intc_dt_config(pru, index); + if (ret) + goto err; + return rproc; err: @@ -264,6 +370,9 @@ void pru_rproc_put(struct rproc *rproc) if (!pru->client_np) return; + if (pru->dt_irqs) + pruss_intc_unconfigure(pru->pruss, &pru->intc_config); + pru_rproc_set_firmware(rproc, NULL); mutex_lock(&pru->lock); @@ -544,7 +653,8 @@ static int pru_rproc_start(struct rproc *rproc) return 0; fail: - pruss_intc_unconfigure(pru->pruss, &pru->intc_config); + if (!pru->dt_irqs && pru->fw_has_intc_rsc) + pruss_intc_unconfigure(pru->pruss, &pru->intc_config); return ret; } @@ -566,7 +676,8 @@ static int pru_rproc_stop(struct rproc *rproc) free_irq(pru->irq_vring, pru); /* undo INTC config */ - pruss_intc_unconfigure(pru->pruss, &pru->intc_config); + if (!pru->dt_irqs && pru->fw_has_intc_rsc) + pruss_intc_unconfigure(pru->pruss, &pru->intc_config); return 0; } @@ -657,6 +768,8 @@ static int pru_handle_vendor_intrmap(struct rproc *rproc, dev_dbg(dev, "chnl-to-host[%d] -> %d\n", i, intr_no); } + pru->fw_has_intc_rsc = 1; + ret = pruss_intc_configure(pruss, &pru->intc_config); if (ret) dev_err(dev, "failed to configure pruss intc %d\n", ret); @@ -669,15 +782,18 @@ static int pru_rproc_handle_vendor_rsc(struct rproc *rproc, struct fw_rsc_vendor *rsc) { struct device *dev = rproc->dev.parent; - int ret = -EINVAL; + struct pru_rproc *pru = rproc->priv; + int ret = 0; switch (rsc->u.st.st_type) { case PRUSS_RSC_INTRS: - ret = pru_handle_vendor_intrmap(rproc, rsc); + if (!pru->dt_irqs) + ret = pru_handle_vendor_intrmap(rproc, rsc); break; default: dev_err(dev, "%s: cannot handle unknown type %d\n", __func__, rsc->u.st.st_type); + ret = -EINVAL; } return ret; -- 2.39.2