Re: [PATCH v4 01/18] remoteproc: st_xp70_rproc: add a xp70 slimcore rproc driver

From: Bjorn Andersson
Date: Wed May 25 2016 - 13:10:54 EST


On Wed 25 May 09:06 PDT 2016, Peter Griffin wrote:

> XP70 slim core is used as a basis for many IPs in the STi
> chipsets such as fdma, display, and demux. To avoid
> duplicating the elf loading code in each device driver
> an xp70 rproc driver has been created.
>
I like this approach.

[..]

> diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
[..]
>
> +config ST_XP70_REMOTEPROC
> + tristate "XP70 slim remoteproc support"

As this "driver" in itself doesn't do anything I don't think it makes
sense to have it user selectable. Please make the "clients" select it
instead.

> + depends on ARCH_STI
> + select REMOTEPROC
> + help
> + Say y here to support xp70 slim core.
> + If unsure say N.
> +
> endmenu
> diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile
[..]
> +#include <linux/clk.h>
> +#include <linux/elf.h>

You should be able to drop inclusion of elf.h now.

[..]
> +static int xp70_clk_get(struct st_xp70_rproc *xp70_rproc, struct device *dev)
> +{
> + int clk, err = 0;

No need to initialize err.

> +
> + for (clk = 0; clk < XP70_MAX_CLK; clk++) {
> + xp70_rproc->clks[clk] = of_clk_get(dev->of_node, clk);
> + if (IS_ERR(xp70_rproc->clks[clk])) {
> + err = PTR_ERR(xp70_rproc->clks[clk]);
> + if (err == -EPROBE_DEFER)
> + goto err_put_clks;
> + xp70_rproc->clks[clk] = NULL;
> + break;
> + }
> + }
> +
> + return 0;
> +
> +err_put_clks:
> + while (--clk >= 0)
> + clk_put(xp70_rproc->clks[clk]);
> +
> + return err;
> +}
> +
[..]
> +/**
> + * Remoteproc xp70 specific device handlers
> + */
> +static int xp70_rproc_start(struct rproc *rproc)
> +{
> + struct device *dev = &rproc->dev;
> + struct st_xp70_rproc *xp70_rproc = rproc->priv;
> + unsigned long hw_id, hw_ver, fw_rev;
> + u32 val, ret = 0;

ret should be signed and there's no need to initialize it.

> +
> + ret = xp70_clk_enable(xp70_rproc);
> + if (ret) {
> + dev_err(dev, "Failed to enable clocks\n");
> + goto err_clk;
> + }
[..]
> +
> + dev_dbg(dev, "XP70 started\n");
> +
> +err_clk:
> + return ret;
> +}
> +
> +static int xp70_rproc_stop(struct rproc *rproc)
> +{
> + struct st_xp70_rproc *xp70_rproc = rproc->priv;
> + u32 val;
> +
> + /* mask all (cmd & int) channels */
> + writel_relaxed(0UL, xp70_rproc->peri + XP70_INT_MASK_OFST);
> + writel_relaxed(0UL, xp70_rproc->peri + XP70_CMD_MASK_OFST);
> +
> + /* disable cpu pipeline clock */
> + writel_relaxed(XP70_CLK_GATE_DIS
> + , xp70_rproc->slimcore + XP70_CLK_GATE_OFST);
> +
> + writel_relaxed(!XP70_EN_RUN, xp70_rproc->slimcore + XP70_EN_OFST);

Don't you want to ensure ordering of these writes? Perhaps making this
last one a writel()?

> +
> + val = readl_relaxed(xp70_rproc->slimcore + XP70_EN_OFST);
> + if (val & XP70_EN_RUN)
> + dev_warn(&rproc->dev, "Failed to disable XP70");
> +
> + xp70_clk_disable(xp70_rproc);
> +
> + dev_dbg(&rproc->dev, "xp70 stopped\n");
> +
> + return 0;
> +}
> +
> +static void *xp70_rproc_da_to_va(struct rproc *rproc, u64 da, int len)
> +{
> + struct st_xp70_rproc *xp70_rproc = rproc->priv;
> + void *va = NULL;
> + int i;
> +
> + for (i = 0; i < XP70_MEM_MAX; i++) {
> +
> + if (da != xp70_rproc->mem[i].bus_addr)
> + continue;
> +
> + va = xp70_rproc->mem[i].cpu_addr;
> + break;
> + }

Please clean up the indentation and drop the extra newline at the
beginning in this loop.

> +
> + dev_dbg(&rproc->dev, "%s: da = 0x%llx len = 0x%x va = 0x%p\n"
> + , __func__, da, len, va);
> +
> + return va;
> +}
> +
> +static struct rproc_ops xp70_rproc_ops = {
> + .start = xp70_rproc_start,
> + .stop = xp70_rproc_stop,
> + .da_to_va = xp70_rproc_da_to_va,
> +};
> +
> +/**
> + * Firmware handler operations: sanity, boot address, load ...
> + */
> +
> +static struct resource_table empty_rsc_tbl = {
> + .ver = 1,
> + .num = 0,
> +};


I'm looking at making the resource table optional, good to see another
vote for that. Looks good for now though.

[..]

> +
> +/**
> + * xp70_rproc_alloc - allocate and initialise xp70 rproc

Drop one of the two spaces indenting this block and add () after then
function name.

> + * @pdev: Pointer to the platform_device struct
> + * @fw_name: Name of firmware for rproc to use
> + *
> + * Function for allocating and initialising a xp70 rproc for use by
> + * device drivers whose IP is based around the xp70 slim core. It
> + * obtains and enables any clocks required by the xp70 core and also
> + * ioremaps the various IO.
> + *
> + * Returns rproc pointer or PTR_ERR() on error.
> + */
> +
> +struct rproc *xp70_rproc_alloc(struct platform_device *pdev, char *fw_name)
> +{
> + struct device *dev = &pdev->dev;
> + struct st_xp70_rproc *xp70_rproc;
> + struct device_node *np = dev->of_node;
> + struct rproc *rproc;
> + struct resource *res;
> + int err, i;
> + const struct rproc_fw_ops *elf_ops;
> +
> + if (!np || !fw_name)
> + return ERR_PTR(-EINVAL);

This should, I believe, only ever happen in development. So if you want
to keep it to aid developers feel free to throw in a WARN_ON() in the
condition.

> +
> + if (!of_device_is_compatible(np, "st,xp70-rproc"))
> + return ERR_PTR(-EINVAL);
> +
> + rproc = rproc_alloc(dev, np->name, &xp70_rproc_ops,
> + fw_name, sizeof(*xp70_rproc));
> + if (!rproc)
> + return ERR_PTR(-ENOMEM);
> +
> + rproc->has_iommu = false;
> +
> + xp70_rproc = rproc->priv;
> + xp70_rproc->rproc = rproc;
> +
> + /* Get standard ELF ops */
> + elf_ops = rproc_get_elf_ops();
> +
> + /* Use some generic elf ops */
> + xp70_rproc_fw_ops.load = elf_ops->load;
> + xp70_rproc_fw_ops.sanity_check = elf_ops->sanity_check;
> +
> + rproc->fw_ops = &xp70_rproc_fw_ops;
> +
> + /* get imem and dmem */
> + for (i = 0; i < ARRAY_SIZE(mem_names); i++) {
> + res = xp70_rproc->mem[i].io_res;
> +

io_res is NULL here, and res is directly assigned again. So drop this
and io_res from the struct.

> + res = platform_get_resource_byname
> + (pdev, IORESOURCE_MEM, mem_names[i]);
> +
> + xp70_rproc->mem[i].cpu_addr = devm_ioremap_resource(dev, res);
> + if (IS_ERR(xp70_rproc->mem[i].cpu_addr)) {
> + dev_err(&pdev->dev, "devm_ioremap_resource failed\n");
> + err = PTR_ERR(xp70_rproc->mem[i].cpu_addr);
> + goto err;
> + }
> + xp70_rproc->mem[i].bus_addr = res->start;
> + xp70_rproc->mem[i].size = resource_size(res);
> + }
> +
> + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "slimcore");
> +
> + xp70_rproc->slimcore = devm_ioremap_resource(dev, res);
> + if (IS_ERR(xp70_rproc->slimcore)) {
> + dev_err(&pdev->dev, "devm_ioremap_resource failed for slimcore\n");
> + err = PTR_ERR(xp70_rproc->slimcore);
> + goto err;
> + }
> +
> + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "peripherals");
> +
> + xp70_rproc->peri = devm_ioremap_resource(dev, res);
> + if (IS_ERR(xp70_rproc->peri)) {
> + dev_err(&pdev->dev, "devm_ioremap_resource failed for peri\n");
> + err = PTR_ERR(xp70_rproc->peri);
> + goto err;
> + }
> +
> + err = xp70_clk_get(xp70_rproc, dev);
> + if (err)
> + goto err;
> +
> + /* Register as a remoteproc device */
> + err = rproc_add(rproc);
> + if (err) {
> + dev_err(dev, "registration of xp70 remoteproc failed\n");
> + goto err;

In this case you should also put the clocks.

> + }
> +
> + dev_dbg(dev, "XP70 rproc init successful\n");
> + return rproc;
> +
> +err:
> + rproc_put(rproc);
> + return ERR_PTR(err);
> +}
> +EXPORT_SYMBOL(xp70_rproc_alloc);
> +
> +/**
> + * xp70_rproc_put - put xp70 rproc resources
> + * @xp70_rproc: Pointer to the st_xp70_rproc struct
> + *
> + * Function for calling respective _put() functions on
> + * xp70_rproc resources.
> + *
> + * Returns rproc pointer or PTR_ERR() on error.
> + */
> +void xp70_rproc_put(struct st_xp70_rproc *xp70_rproc)
> +{
> + int clk;
> +
> + if (!xp70_rproc)
> + return;
> +
> + rproc_put(xp70_rproc->rproc);
> +
> + for (clk = 0; clk < XP70_MAX_CLK && xp70_rproc->clks[clk]; clk++)
> + clk_put(xp70_rproc->clks[clk]);

rproc_put() will free your private data, i.e. xp70_rproc, so you must
put your clocks before that.

> +
> +}
> +EXPORT_SYMBOL(xp70_rproc_put);
> +
> +MODULE_AUTHOR("Peter Griffin");
> +MODULE_DESCRIPTION("STMicroelectronics XP70 rproc driver");
> +MODULE_LICENSE("GPL v2");
> diff --git a/include/linux/remoteproc/st_xp70_rproc.h b/include/linux/remoteproc/st_xp70_rproc.h
[..]
> +
> +#define XP70_MEM_MAX 2
> +#define XP70_MAX_CLK 4
> +#define NAME_SZ 10

NAME_SZ seems unused and would be too generic.

> +
> +enum {
> + DMEM,
> + IMEM,

These are too generic for a header file.

> +};
> +
> +/**
> + * struct xp70_mem - xp70 internal memory structure
> + * @cpu_addr: MPU virtual address of the memory region
> + * @bus_addr: Bus address used to access the memory region
> + * @dev_addr: Device address from Wakeup M3 view
> + * @size: Size of the memory region
> + */
> +struct xp70_mem {
> + void __iomem *cpu_addr;
> + phys_addr_t bus_addr;
> + u32 dev_addr;

dev_addr is unused.

> + size_t size;
> + struct resource *io_res;

io_res is unused.

> +};
> +
> +/**
> + * struct st_xp70_rproc - XP70 slim core
> + * @rproc: rproc handle
> + * @pdev: pointer to platform device
> + * @mem: xp70 memory information
> + * @slimcore: xp70 slimcore regs
> + * @peri: xp70 peripheral regs
> + * @clks: xp70 clocks
> + */
> +struct st_xp70_rproc {
> + struct rproc *rproc;
> + struct platform_device *pdev;

pdev is unused.

> + struct xp70_mem mem[XP70_MEM_MAX];
> + void __iomem *slimcore;
> + void __iomem *peri;
> + struct clk *clks[XP70_MAX_CLK];
> +};

I generally don't like sharing a struct like this between two
implementations, but I don't think it's worth working around it in this
case.

Perhaps you can group the members into a section of "public" and a
section of "private" members; with a /* st_xp70_rproc private */ heading
the latter?

> +
> +struct rproc *xp70_rproc_alloc(struct platform_device *pdev, char *fw_name);

For consistency I think xp70_rproc_alloc() should return a st_xp70_rproc
reference, rather than forcing the clients pull that pointer out of
rproc->priv.

> +void xp70_rproc_put(struct st_xp70_rproc *xp70_rproc);
> +
> +#endif

Regards,
Bjorn