@@ -200,6 +200,8 @@ config ST_SLIM_REMOTEPROC
config PRUSS_REMOTEPROC
tristate "TI PRUSS remoteproc support"
depends on TI_PRUSS
+ select MAILBOX
+ select OMAP2PLUS_MBOX if ARCH_OMAP2PLUS
default n
help
Support for TI PRU-ICSS remote processors via the remote processor
@@ -11,6 +11,7 @@
#include <linux/debugfs.h>
#include <linux/interrupt.h>
#include <linux/irqchip/irq-pruss-intc.h>
+#include <linux/mailbox_client.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of_device.h>
@@ -64,6 +65,10 @@
* @debug_regmap: regmap to PRU DEBUG IOMEM
* @client_np: client device node
* @intc_config: PRU INTC configuration data
+ * @mbox: mailbox channel handle used for vring signalling with MPU
+ * @client: mailbox client to request the mailbox channel
+ * @irq_ring: IRQ number to use for processing vring buffers
+ * @irq_kick: IRQ number to use to perform virtio kick
* @dram0: PRUSS DRAM0 region
* @dram1: PRUSS DRAM1 region
* @shrdram: PRUSS SHARED RAM region
@@ -90,6 +95,10 @@ struct pru_rproc {
struct regmap *debug_regmap;
struct device_node *client_np;
struct pruss_intc_config intc_config;
+ struct mbox_chan *mbox;
+ struct mbox_client client;
+ int irq_vring;
+ int irq_kick;
struct pruss_mem_region dram0;
struct pruss_mem_region dram1;
struct pruss_mem_region shrdram;
@@ -615,6 +624,82 @@ static int pru_rproc_handle_vendor_rsc(struct rproc *rproc,
return ret;
}
+/**
+ * pru_rproc_mbox_callback() - inbound mailbox message handler
+ * @client: mailbox client pointer used for requesting the mailbox channel
+ * @data: mailbox payload
+ *
+ * This handler is invoked by omap's mailbox driver whenever a mailbox
+ * message is received. Usually, the mailbox payload simply contains
+ * the index of the virtqueue that is kicked by the PRU remote processor,
+ * and we let remoteproc core handle it.
+ *
+ * In addition to virtqueue indices, we might also have some out-of-band
+ * values that indicates different events. Those values are deliberately
+ * very big so they don't coincide with virtqueue indices.
+ */
+static void pru_rproc_mbox_callback(struct mbox_client *client, void *data)
+{
+ struct pru_rproc *pru = container_of(client, struct pru_rproc, client);
+ struct device *dev = &pru->rproc->dev;
+ u32 msg = (u32)data;
+
+ dev_dbg(dev, "mbox msg: 0x%x\n", msg);
+
+ /* msg contains the index of the triggered vring */
+ if (rproc_vq_interrupt(pru->rproc, msg) == IRQ_NONE)
+ dev_dbg(dev, "no message was found in vqid %d\n", msg);
+}
+
+/**
+ * pru_rproc_vring_interrupt() - interrupt handler for processing vrings
+ * @irq: irq number associated with the PRU event MPU is listening on
+ * @data: interrupt handler data, will be a PRU rproc structure
+ *
+ * This handler is used by the PRU remoteproc driver when using PRU system
+ * events for processing the virtqueues. Unlike the mailbox IP, there is
+ * no payload associated with an interrupt, so either a unique event is
+ * used for each virtqueue kick, or a both virtqueues are processed on
+ * a single event. The latter is chosen to conserve the usable PRU system
+ * events.
+ */
+static irqreturn_t pru_rproc_vring_interrupt(int irq, void *data)
+{
+ struct pru_rproc *pru = data;
+
+ dev_dbg(&pru->rproc->dev, "got vring irq\n");
+
+ /* process incoming buffers on both the Rx and Tx vrings */
+ rproc_vq_interrupt(pru->rproc, 0);
+ rproc_vq_interrupt(pru->rproc, 1);
+
+ return IRQ_HANDLED;
+}
+
+/* kick a virtqueue */
+static void pru_rproc_kick(struct rproc *rproc, int vq_id)
+{
+ struct device *dev = &rproc->dev;
+ struct pru_rproc *pru = rproc->priv;
+ int ret;
+
+ dev_dbg(dev, "kicking vqid %d on PRU%d\n", vq_id, pru->id);
+
+ if (pru->irq_kick > 0) {
+ ret = pruss_intc_trigger(pru->irq_kick);
+ if (ret < 0)
+ dev_err(dev, "pruss_intc_trigger failed: %d\n", ret);
+ } else if (pru->mbox) {
+ /*
+ * send the index of the triggered virtqueue in the mailbox
+ * payload
+ */
+ ret = mbox_send_message(pru->mbox, (void *)vq_id);
+ if (ret < 0)
+ dev_err(dev, "mbox_send_message failed: %d\n", ret);
+ }
+}
+
/* start a PRU core */
static int pru_rproc_start(struct rproc *rproc)
{
@@ -635,10 +720,36 @@ static int pru_rproc_start(struct rproc *rproc)
}
}
+ if (!list_empty(&pru->rproc->rvdevs)) {
+ if (!pru->mbox && (pru->irq_vring <= 0 || pru->irq_kick <= 0)) {
+ dev_err(dev, "virtio vring interrupt mechanisms are not provided\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ if (!pru->mbox && pru->irq_vring > 0) {
+ ret = request_threaded_irq(pru->irq_vring, NULL,
+ pru_rproc_vring_interrupt,
+ IRQF_ONESHOT, dev_name(dev),
+ pru);
+ if (ret) {
+ dev_err(dev, "failed to enable vring interrupt, ret = %d\n",
+ ret);
+ goto fail;
+ }
+ }
+ }
+
val = CTRL_CTRL_EN | ((rproc->bootaddr >> 2) << 16);
regmap_write(pru->ctrl_regmap, PRU_CTRL_CTRL, val);
return 0;
+
+fail:
+ if (pru->dt_irqs || pru->fw_irqs)
+ pruss_intc_unconfigure(rproc->dev.parent, &pru->intc_config);
+
+ return ret;
}
/* stop/disable a PRU core */
@@ -650,6 +761,10 @@ static int pru_rproc_stop(struct rproc *rproc)
dev_dbg(dev, "stopping PRU%d\n", pru->id);
regmap_update_bits(pru->ctrl_regmap, PRU_CTRL_CTRL, CTRL_CTRL_EN, 0);
+ if (!list_empty(&pru->rproc->rvdevs) &&
+ !pru->mbox && pru->irq_vring > 0)
+ free_irq(pru->irq_vring, pru);
+
/* undo INTC config */
if (pru->dt_irqs || pru->fw_irqs)
pruss_intc_unconfigure(rproc->dev.parent, &pru->intc_config);
@@ -746,6 +861,7 @@ static void *pru_da_to_va(struct rproc *rproc, u64 da, int len, int map)
static struct rproc_ops pru_rproc_ops = {
.start = pru_rproc_start,
.stop = pru_rproc_stop,
+ .kick = pru_rproc_kick,
.da_to_va = pru_da_to_va,
.handle_vendor_rsc = pru_rproc_handle_vendor_rsc,
};
@@ -789,6 +905,7 @@ static int pru_rproc_probe(struct platform_device *pdev)
struct pru_rproc *pru;
const char *fw_name;
struct rproc *rproc = NULL;
+ struct mbox_client *client;
struct resource *res;
int ret;
void __iomem *va;
@@ -924,10 +1041,51 @@ static int pru_rproc_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, rproc);
+ /* get optional vring and kick interrupts for supporting virtio rpmsg */
+ pru->irq_vring = platform_get_irq_byname(pdev, "vring");
+ if (pru->irq_vring <= 0) {
+ ret = pru->irq_vring;
+ if (ret == -EPROBE_DEFER)
+ goto free_rproc;
+ dev_dbg(dev, "unable to get vring interrupt, status = %d\n",
+ ret);
+ }
+
+ pru->irq_kick = platform_get_irq_byname(pdev, "kick");
+ if (pru->irq_kick <= 0) {
+ ret = pru->irq_kick;
+ if (ret == -EPROBE_DEFER)
+ goto free_rproc;
+ dev_dbg(dev, "unable to get kick interrupt, status = %d\n",
+ ret);
+ }
+
+ /*
+ * get optional mailbox for virtio rpmsg signalling if vring and kick
+ * interrupts are not specified for OMAP architecture based SoCs
+ */
+ if (pru->irq_vring <= 0 && pru->irq_kick <= 0 &&
+ !of_device_is_compatible(np, "ti,k2g-pru") &&
+ !of_device_is_compatible(np, "ti,da850-pru")) {
+ client = &pru->client;
+ client->dev = dev;
+ client->tx_done = NULL;
+ client->rx_callback = pru_rproc_mbox_callback;
+ client->tx_block = false;
+ client->knows_txdone = false;
+ pru->mbox = mbox_request_channel(client, 0);
+ if (IS_ERR(pru->mbox)) {
+ ret = PTR_ERR(pru->mbox);
+ pru->mbox = NULL;
+ dev_dbg(dev, "unable to get mailbox channel, status = %d\n",
+ ret);
+ }
+ }
+
ret = rproc_add(pru->rproc);
if (ret) {
dev_err(dev, "rproc_add failed: %d\n", ret);
- goto free_rproc;
+ goto put_mbox;
}
pru_rproc_create_debug_entries(rproc);
@@ -936,6 +1094,8 @@ static int pru_rproc_probe(struct platform_device *pdev)
return 0;
+put_mbox:
+ mbox_free_channel(pru->mbox);
free_rproc:
rproc_free(rproc);
return ret;
@@ -945,9 +1105,12 @@ static int pru_rproc_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct rproc *rproc = platform_get_drvdata(pdev);
+ struct pru_rproc *pru = rproc->priv;
dev_info(dev, "%s: removing rproc %s\n", __func__, rproc->name);
+ mbox_free_channel(pru->mbox);
+
rproc_del(rproc);
rproc_free(rproc);