Hey Michal-

I've got a few comments on this.

On Mon, Feb 23, 2015 at 12:37:26PM +0100, Michal Simek wrote:
> Xilinx Microblaze is placed in programmable logic on Xilinx
> Zynq architecture. Driver requires specific HW setting
> described in DT binding.
>
> Signed-off-by: Michal Simek <michal.si...@xilinx.com>
[..]
> +++ b/drivers/remoteproc/Kconfig
> @@ -64,4 +64,15 @@ config DA8XX_REMOTEPROC
>         It's safe to say n here if you're not interested in multimedia
>         offloading.
>  
> +config MB_REMOTEPROC
> +     tristate "Support Microblaze remoteproc"
> +     depends on ARCH_ZYNQ
> +     select GPIO_XILINX

Why?  The driver doesn't (and shouldn't) know which GPIO controller is
being used.

> +     select REMOTEPROC
> +     select RPMSG
> +     default m
> +     help
> +       Say y here to support Xilinx Microblaze remote processors
> +       on the Xilinx Zynq.
> +
>  endmenu
> diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile
> index ac2ff75686d2..40e247ffac34 100644
> --- a/drivers/remoteproc/Makefile
> +++ b/drivers/remoteproc/Makefile
> @@ -10,3 +10,4 @@ remoteproc-y                                += 
> remoteproc_elf_loader.o
>  obj-$(CONFIG_OMAP_REMOTEPROC)                += omap_remoteproc.o
>  obj-$(CONFIG_STE_MODEM_RPROC)                += ste_modem_rproc.o
>  obj-$(CONFIG_DA8XX_REMOTEPROC)               += da8xx_remoteproc.o
> +obj-$(CONFIG_MB_REMOTEPROC)          += mb_remoteproc.o
> diff --git a/drivers/remoteproc/mb_remoteproc.c 
> b/drivers/remoteproc/mb_remoteproc.c
> new file mode 100644
> index 000000000000..7febf32abafc
> --- /dev/null
> +++ b/drivers/remoteproc/mb_remoteproc.c
[..]
> +/* Private data */
> +struct mb_rproc_pdata {
> +     struct rproc *rproc;
> +     u32 mem_start;
> +     u32 mem_end;
> +     int reset_gpio;
> +     int mb_debug_gpio;
> +     int ipi;
> +     int vring0;
> +     int vring1;
> +     void __iomem *vbase;
> +     const unsigned char *bootloader;
> +};
> +
> +/* Store rproc for IPI handler */
> +static struct platform_device *remoteprocdev;
> +static struct work_struct workqueue;

Roll the workqueue into mb_rproc_pdata?  I see no reason that you need
this global state.

> +static void handle_event(struct work_struct *work)
> +{
> +     struct mb_rproc_pdata *local = platform_get_drvdata(remoteprocdev);
> +
> +     flush_cache_all();
> +     outer_flush_range(local->mem_start, local->mem_end);
> +
> +     if (rproc_vq_interrupt(local->rproc, 0) == IRQ_NONE)
> +             dev_info(&remoteprocdev->dev, "no message found in vqid 0\n");
> +}
> +
> +static irqreturn_t ipi_kick(int irq, void *dev_id)
> +{
> +     dev_dbg(&remoteprocdev->dev, "KICK Linux because of pending message\n");
> +     schedule_work(&workqueue);

Perhaps it's easier to use request_threaded_irq() as your mechanism of
deferring instead of a workqueue.

> +     dev_dbg(&remoteprocdev->dev, "KICK Linux handled\n");
> +
> +     return IRQ_HANDLED;
> +}
> +
> +static int mb_rproc_start(struct rproc *rproc)
> +{
> +     struct device *dev = rproc->dev.parent;
> +     struct platform_device *pdev = to_platform_device(dev);
> +     struct mb_rproc_pdata *local = platform_get_drvdata(pdev);
> +     const struct firmware *fw;
> +     int ret;
> +
> +     dev_info(dev, "%s\n", __func__);
> +     INIT_WORK(&workqueue, handle_event);
> +
> +     flush_cache_all();
> +     outer_flush_range(local->mem_start, local->mem_end);
> +
> +     remoteprocdev = pdev;
> +
> +     ret = request_firmware(&fw, local->bootloader, &pdev->dev);
> +     if (ret < 0) {
> +             dev_err(&pdev->dev, "request_firmware failed\n");
> +             return ret;
> +     }
> +     /* Copy bootloader to memory */
> +     memcpy(local->vbase, fw->data, fw->size);

sparse is not going to like you.  How are you guaranteeing coherence?

> +     release_firmware(fw);
> +
> +     /* Just for sure synchronize memories */
> +     dsb();
> +
> +     /* Release Microblaze from reset */
> +     gpio_set_value(local->reset_gpio, 0);
> +
> +     return 0;
> +}
> +
> +/* kick a firmware */
> +static void mb_rproc_kick(struct rproc *rproc, int vqid)
> +{
> +     struct device *dev = rproc->dev.parent;
> +     struct platform_device *pdev = to_platform_device(dev);
> +     struct mb_rproc_pdata *local = platform_get_drvdata(pdev);
> +
> +     dev_dbg(dev, "KICK Firmware to start send messages vqid %d\n", vqid);
> +
> +     flush_cache_all();
> +     outer_flush_all();
> +
> +     /* Send swirq to firmware */
> +     gpio_set_value(local->vring0, 0);
> +     gpio_set_value(local->vring1, 0);
> +     dsb();
> +
> +     if (!vqid) {
> +             udelay(500);
> +             gpio_set_value(local->vring0, 1);
> +             dsb();
> +     } else {
> +             udelay(100);
> +             gpio_set_value(local->vring1, 1);
> +             dsb();
> +     }
> +}
> +
> +/* power off the remote processor */
> +static int mb_rproc_stop(struct rproc *rproc)
> +{
> +     struct device *dev = rproc->dev.parent;
> +     struct platform_device *pdev = to_platform_device(dev);
> +     struct mb_rproc_pdata *local = platform_get_drvdata(pdev);
> +
> +     /* Setup MB to the state where all memory transactions are done */
> +     gpio_set_value(local->mb_debug_gpio, 1);
> +     dsb(); /* Be sure that this write has been done */
> +     /*
> +      * This should be enough to ensure one CLK as
> +      * it is written in MB ref guide
> +      */
> +     gpio_set_value(local->mb_debug_gpio, 0);
> +
> +     udelay(1000); /* Wait some time to finish all mem transactions */
> +
> +     /* Add Microblaze to reset state */
> +     gpio_set_value(local->reset_gpio, 1);
> +
> +     /* No reason to wait that operations where done */
> +     return 0;
> +}
> +
> +static struct rproc_ops mb_rproc_ops = {

const?

> +     .start          = mb_rproc_start,
> +     .stop           = mb_rproc_stop,
> +     .kick           = mb_rproc_kick,
> +};
> +
> +/* Just to detect bug if interrupt forwarding is broken */
> +static irqreturn_t mb_remoteproc_interrupt(int irq, void *dev_id)
> +{
> +     struct device *dev = dev_id;
> +
> +     dev_err(dev, "GIC IRQ %d is not forwarded correctly\n", irq);
> +
> +     return IRQ_HANDLED;
> +}
> +
> +static int mb_remoteproc_probe(struct platform_device *pdev)
> +{
> +     const unsigned char *prop;
> +     struct platform_device *bram_pdev;
> +     struct device_node *bram_dev;
> +     struct resource *res; /* IO mem resources */
> +     int ret = 0;

Doesn't need to be initialized.

> +     int count = 0;
> +     struct mb_rproc_pdata *local;
> +
> +     local = devm_kzalloc(&pdev->dev, sizeof(*local), GFP_KERNEL);
> +     if (!local)
> +             return -ENOMEM;
> +
> +     platform_set_drvdata(pdev, local);
> +
> +     /* Declare memory for firmware */
> +     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +     if (!res) {
> +             dev_err(&pdev->dev, "invalid address\n");
> +             return -ENODEV;
> +     }
> +
> +     local->mem_start = res->start;
> +     local->mem_end = res->end;
> +
> +     /* Alloc phys addr from 0 to max_addr for firmware */
> +     ret = dma_declare_coherent_memory(&pdev->dev, local->mem_start,
> +             local->mem_start, local->mem_end - local->mem_start + 1,
> +             DMA_MEMORY_IO);

This appears to be useless, as you never use dma_alloc_coherent()?

> +     if (!ret) {
> +             dev_err(&pdev->dev, "dma_declare_coherent_memory failed\n");
> +             return -ENOMEM;
> +     }
> +
> +     ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32));
> +     if (ret) {
> +             dev_err(&pdev->dev, "dma_set_coherent_mask: %d\n", ret);
> +             goto dma_mask_fault;
> +     }
> +
> +     /* Alloc IRQ based on DTS to be sure that no other driver will use it */
> +     while (1) {
> +             int irq;
> +             /* Allocating shared IRQs will ensure that any module will
> +              * use these IRQs */
> +             irq = platform_get_irq(pdev, count++);
> +             if (irq == -ENXIO || irq == -EINVAL)
> +                     break;
> +             ret = devm_request_irq(&pdev->dev, irq, mb_remoteproc_interrupt,
> +                                    0, dev_name(&pdev->dev), &pdev->dev);
> +             if (ret) {
> +                     dev_err(&pdev->dev, "IRQ %d already allocated\n", irq);
> +                     goto dma_mask_fault;
> +             }
> +
> +             dev_info(&pdev->dev, "%d: Alloc irq: %d\n", count, irq);
> +     }

So, you're registering a NOP handler for any of the specified IRQs.
This seems really bizarre.

> +
> +     /* Find out reset gpio and keep microblaze in reset */
> +     local->reset_gpio = of_get_named_gpio(pdev->dev.of_node,
> +                                           "reset-gpio", 0);
> +     if (local->reset_gpio < 0) {
> +             dev_err(&pdev->dev, "reset-gpio property not found\n");
> +             ret = local->reset_gpio;
> +             goto dma_mask_fault;
> +     }
> +     ret = devm_gpio_request_one(&pdev->dev, local->reset_gpio,
> +                                 GPIOF_OUT_INIT_HIGH, "mb_reset");
> +     if (ret) {
> +             dev_err(&pdev->dev, "Please specify gpio reset addr\n");
> +             goto dma_mask_fault;
> +     }
> +
> +     /* Find out reset gpio and keep microblaze in reset */
> +     local->mb_debug_gpio = of_get_named_gpio(pdev->dev.of_node,
> +                                              "debug-gpio", 0);
> +     if (local->mb_debug_gpio < 0) {
> +             dev_err(&pdev->dev, "mb-debug-gpio property not found\n");
> +             ret = local->mb_debug_gpio;
> +             goto dma_mask_fault;
> +     }
> +     ret = devm_gpio_request_one(&pdev->dev, local->mb_debug_gpio,
> +                                 GPIOF_OUT_INIT_LOW, "mb_debug");

devm_gpiod_get() might make your life a little easier.

> +     if (ret) {
> +             dev_err(&pdev->dev, "Please specify gpio debug pin\n");
> +             goto dma_mask_fault;
> +     }
> +
> +     /* IPI number for getting irq from firmware */
> +     local->ipi = of_get_named_gpio(pdev->dev.of_node, "ipino-gpio", 0);
> +     if (local->ipi < 0) {
> +             dev_err(&pdev->dev, "ipi-gpio property not found\n");
> +             ret = local->ipi;
> +             goto dma_mask_fault;
> +     }
> +     ret = devm_gpio_request_one(&pdev->dev, local->ipi, GPIOF_IN, "mb_ipi");
> +     if (ret) {
> +             dev_err(&pdev->dev, "Please specify gpio reset addr\n");
> +             goto dma_mask_fault;
> +     }
> +     ret = devm_request_irq(&pdev->dev, gpio_to_irq(local->ipi),
> +                            ipi_kick, IRQF_SHARED|IRQF_TRIGGER_RISING,
> +                            dev_name(&pdev->dev), local);
> +     if (ret) {
> +             dev_err(&pdev->dev, "IRQ %d already allocated\n", local->ipi);
> +             goto dma_mask_fault;
> +     }
> +
[..]
> +     res = platform_get_resource(bram_pdev, IORESOURCE_MEM, 0);
> +     local->vbase = devm_ioremap_resource(&pdev->dev, res);
> +     if (!local->vbase) {
> +             ret = -ENODEV;
> +             goto dma_mask_fault;
> +     }
> +
> +     /* Load simple bootloader to bram */
> +     local->bootloader = of_get_property(pdev->dev.of_node,
> +                                         "bram-firmware", NULL);
> +     if (!local->bootloader) {
> +             dev_err(&pdev->dev, "Please specify BRAM firmware\n");
> +             ret = -ENODEV;
> +             goto dma_mask_fault;
> +     }

Is there precedent for putting firmware file names in a device tree
binary?  This makes me nervous.  Have you considered embedding the
firmware image directly in a binary "bram-firmware" property instead?

> +     dev_info(&pdev->dev, "Using microblaze BRAM bootloader: %s\n",
> +              local->bootloader);
> +
> +     /* Module param firmware first */
> +     if (firmware)
> +             prop = firmware;
> +     else
> +             prop = of_get_property(pdev->dev.of_node, "firmware", NULL);
> +
> +     if (prop) {

Nit: You're inconsistent style-wise here.  It makes it hard to follow
the "happy path".  Consider:

        if (!prop) {
                ret = -ENODEV;
                goto dma_mask_fault;
        }

> +             dev_info(&pdev->dev, "Using firmware: %s\n", prop);
> +             local->rproc = rproc_alloc(&pdev->dev, dev_name(&pdev->dev),
> +                             &mb_rproc_ops, prop, sizeof(struct rproc));
> +             if (!local->rproc) {
> +                     dev_err(&pdev->dev, "rproc allocation failed\n");
> +                     ret = -ENODEV;
> +                     goto dma_mask_fault;
> +             }
> +
> +             ret = rproc_add(local->rproc);
> +             if (ret) {
> +                     dev_err(&pdev->dev, "rproc registration failed\n");
> +                     rproc_put(local->rproc);
> +                     goto dma_mask_fault;
> +             }
> +             return 0;
> +     }
> +
> +     ret = -ENODEV;
> +
> +dma_mask_fault:
> +     dma_release_declared_memory(&pdev->dev);

Does this not get done automatically?

> +
> +     return ret;
> +}
> +
> +static int mb_remoteproc_remove(struct platform_device *pdev)
> +{
> +     struct mb_rproc_pdata *local = platform_get_drvdata(pdev);
> +
> +     dev_info(&pdev->dev, "%s\n", __func__);

This is just noise.

> +
> +     dma_release_declared_memory(&pdev->dev);
> +
> +     rproc_del(local->rproc);
> +     rproc_put(local->rproc);
> +
> +     return 0;
> +}
> +
> +/* Match table for OF platform binding */
> +static const struct of_device_id mb_remoteproc_match[] = {
> +     { .compatible = "xlnx,mb-remoteproc", },
> +     { /* end of list */ },
> +};
> +MODULE_DEVICE_TABLE(of, mb_remoteproc_match);
> +
> +static struct platform_driver mb_remoteproc_driver = {
> +     .probe = mb_remoteproc_probe,
> +     .remove = mb_remoteproc_remove,
> +     .driver = {
> +             .name = "mb-remoteproc",
> +             .of_match_table = mb_remoteproc_match,
> +     },
> +};
> +module_platform_driver(mb_remoteproc_driver);
> +
> +module_param(firmware, charp, 0);
> +MODULE_PARM_DESC(firmware, "Override the firmware image name. Default value 
> in DTS.");

Two higher level things:

Conceptually, it'd be possible (given sufficient resources) to
instantiate two instances of this MB <-> GPIO <-> BRAM stack.  The way
the driver currently written, this is not possible.  This seems fixable.

The use of BRAM + GPIO really smells like it would fit into the mailbox
framework.  Have you considered this?

  Josh
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to