This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

AM5706: IPU2 Early Boot - Late Attach in SDK 9

Part Number: AM5706

TL;DR: I tell you how I fixed early attach on SDK9 for my board. There is not an actual question appart from "why wasn't this supported?"

We're porting our product from SDK 6 (Linux 4.19) to SDK 9 (Linux 6.1) and we have found some troubles on late attaching both IPUs. The early boot part was not hard to port across U-Boot versions from previous implementation, but the Linux was.

First, IPU1 was allegedly attaching properly, but the firmware was not being parsed correctly and the traces (/sys/bus/platform/devices/58820000.ipu/remoteproc/remoteproc0/traces) were being mapped incorrectly. This was shown because the resource table (sys/kernel/debug/remoteproc/remoteproc0/resource_table) was different in Linux 4.19 (were everything worked) than in Linux 6.1...

However, this had a fix and was to not use the "no-map" and keep the "reusable" labels in ipu1_memory_region in dra7-ipu-common-early-boot.dtsi. With this change, the mapping was good, however, the traces were empty. That was because the memory allocation sets the carveouts to 0. Apparently, the old kernel had different paths for this and one would "malloc" (no set to zero) and another would "alloc" (set to 0) the memory.

In kernel 4.19 I see this

static int rproc_handle_carveout(struct rproc *rproc, struct fw_rsc_carveout *rsc, int offset, int avail, u16 ver)
{
    [...]
    if (rproc->late_attach) {
        va = dma_malloc_coherent(dev->parent, rsc->len, &dma, GFP_KERNEL);
    } else {
        va = dma_alloc_coherent(dev->parent, rsc->len, &dma, GFP_KERNEL);
    }
    [...]
}

But in kernel 6.1 I get this:

static int rproc_alloc_carveout(struct rproc *rproc, struct rproc_mem_entry *mem)
{
    struct rproc_mem_entry *mapping = NULL;
    struct device *dev = &rproc->dev;
    dma_addr_t dma;
    void *va;
    int ret;

    va = dma_alloc_coherent(dev->parent, mem->len, &dma, GFP_KERNEL);
    if (!va) {
        dev_err(dev->parent, "failed to allocate dma memory: len 0x%zx\n", mem->len);
        return -ENOMEM;
    }

However, in kernel 6.1 there is no dma_malloc_coherent(). So I found this commit https://git.ti.com/cgit/ti-linux-kernel/ti-linux-kernel/commit/kernel/dma/coherent.c?h=ti-rt-linux-6.1.y&id=b9325656e57244bad71836ba3c9d71250f81666f


Testing the path the code took when adding or removing the reusable/no-map I found out that when it maps properly memory is being allocated with __alloc_from_contiguous() from dma-mapping.c.
And there there is a call to __dma_clear_buffer(). In kernel 4.19, that call has an "if (zero)", but it can't be done here because that argument is gone. So I did the same "HACK" in this function:

--- a/arch/arm/mm/dma-mapping.c
+++ b/arch/arm/mm/dma-mapping.c
@@ -395,12 +395,16 @@ static void *__alloc_from_contiguous(struct device *dev, size_t size,
        size_t count = size >> PAGE_SHIFT;
        struct page *page;
        void *ptr = NULL;
+       struct device_node *np = of_find_node_by_path("/ocp/ipu@58820000");
+       bool ipu_early_booted = of_property_read_bool(np, "late_attach");

        page = dma_alloc_from_contiguous(dev, count, order, gfp & __GFP_NOWARN);
        if (!page)
                return NULL;

-       __dma_clear_buffer(page, size, coherent_flag);
+       if (dev && !(ipu_early_booted && (strstr(dev->kobj.name, "58820000.ipu") ||
+                                                                         strstr(dev->kobj.name, "55020000.ipu"))))
+               __dma_clear_buffer(page, size, coherent_flag);

        if (!want_vaddr)
                goto out;


Notice that that patch also takes care of IPU2

Well, that was all it took for IPU1 late-attach to work the same way it did in SDK 6.

Now for IPU2, I knew that these "HACK" commits from TI would only work for IPU1 (a little heads-up would have been nice...):
 - https://git.ti.com/cgit/ti-linux-kernel/ti-linux-kernel/commit?id=b9325656e57244bad71836ba3c9d71250f81666f
 - https://git.ti.com/cgit/ti-linux-kernel/ti-linux-kernel/commit?id=f84fad5a57dff62a62edce3172d526d75616cb17
 - https://git.ti.com/cgit/ti-linux-kernel/ti-linux-kernel/commit?id=b5d4d7d988d0dfb02133e9e4938a6a752d67f415

So I ported them as well as I could:

--- a/arch/arm/mach-omap2/clockdomain.c
+++ b/arch/arm/mach-omap2/clockdomain.c
@@ -41,8 +41,8 @@ static struct clkdm_ops *arch_clkdm;
 void clkdm_save_context(void);
 void clkdm_restore_context(void);

-struct device_node *np;
-bool ipu_early_booted;
+static struct device_node *np = NULL;
+static bool ipu_early_booted;

 /* Private functions */

@@ -175,7 +175,9 @@ static void _resolve_clkdm_deps(struct clockdomain *clkdm,
                        continue;
                cd->clkdm = _clkdm_lookup(cd->clkdm_name);

-               if (!((strcmp(cd->clkdm_name, "ipu_clkdm") == 0 || strcmp(cd->clkdm_name, "ipu1_clkdm") == 0) && ipu_early_booted))
+               if (!(ipu_early_booted && (strcmp(cd->clkdm_name, "ipu_clkdm") == 0 ||
+                                                                  strcmp(cd->clkdm_name, "ipu1_clkdm") == 0 ||
+                                                                  strcmp(cd->clkdm_name, "ipu2_clkdm") == 0)))
                        WARN(!cd->clkdm, "clockdomain: %s: could not find clkdm %s while resolving dependencies - should never happen",
                             clkdm->name, cd->clkdm_name);
        }
@@ -482,8 +484,11 @@ int clkdm_complete_init(void)
 {
        struct clockdomain *clkdm;
        static struct notifier_block nb;
-       np = of_find_node_by_path("/ocp/ipu@58820000");
-       ipu_early_booted = of_property_read_bool(np, "late_attach");
+
+       if (np == NULL) {
+               np = of_find_node_by_path("/ocp/ipu@58820000");
+               ipu_early_booted = of_property_read_bool(np, "late_attach");
+       }

        if (list_empty(&clkdm_list))
                return -EACCES;
@@ -493,7 +498,9 @@ int clkdm_complete_init(void)
         * Kernel from messing up with its clks.
         */
        list_for_each_entry(clkdm, &clkdm_list, node) {
-               if (!((strcmp(clkdm->name, "ipu_clkdm") == 0 || strcmp(clkdm->name, "ipu1_clkdm") == 0) && ipu_early_booted)) {
+               if (!(ipu_early_booted && (strcmp(clkdm->name, "ipu_clkdm") == 0 ||
+                                                                  strcmp(clkdm->name, "ipu1_clkdm") == 0 ||
+                                                                  strcmp(clkdm->name, "ipu2_clkdm") == 0))) {
                        clkdm_deny_idle(clkdm);

                        _resolve_clkdm_deps(clkdm, clkdm->wkdep_srcs);
--- a/drivers/clk/ti/clkctrl.c
+++ b/drivers/clk/ti/clkctrl.c
@@ -525,7 +525,6 @@ static void __init _ti_omap4_clkctrl_setup(struct device_node *node)
        struct device_node *np = of_find_node_by_path("/ocp/ipu@58820000");
        bool ipu_early_booted = of_property_read_bool(np, "late_attach");

-
        addrp = of_get_address(node, 0, NULL, NULL);
        addr = (u32)of_translate_address(node, addrp);

@@ -585,7 +584,7 @@ static void __init _ti_omap4_clkctrl_setup(struct device_node *node)
         * Do not initialize ipu_clkctrls if IPU is early booted to prevent
         * Kernel from messing up with its clks.
         */
-       if (data->addr == 0x4a005520 && ipu_early_booted)
+       if (ipu_early_booted && (data->addr == 0x4a005520 || data->addr == 0x4a008920))
                return;

        provider = kzalloc(sizeof(*provider), GFP_KERNEL);

--- a/kernel/dma/coherent.c
+++ b/kernel/dma/coherent.c
@@ -167,7 +167,8 @@ static void *__dma_alloc_from_coherent(struct device *dev,
                        ((dma_addr_t)pageno << PAGE_SHIFT);
        ret = mem->virt_base + ((dma_addr_t)pageno << PAGE_SHIFT);
        spin_unlock_irqrestore(&mem->spinlock, flags);
-       if (!(strstr(dev->kobj.name, "58820000.ipu") && ipu_early_booted))
+       if (!(ipu_early_booted && (strstr(dev->kobj.name, "58820000.ipu") ||
+                                                          strstr(dev->kobj.name, "55020000.ipu"))))
                memset(ret, 0, size);
        return ret;
 err:

Also, the same changes for dtsi:

/* IPU2 */
&target_module_timer3 {
    ti,no-idle-on-init;
    ti,no-reset-on-init;
};

&target_module_timer4 {
    ti,no-idle-on-init;
    ti,no-reset-on-init;
};

&target_module_timer9 {
    ti,no-idle-on-init;
    ti,no-reset-on-init;
};

&target_module_mmu_ipu2 {
    /* Delete the clocks and resets as they are not registered at init time */
    late_attach = <1>;

    /delete-property/ clocks;
    /delete-property/ clock-names;

    /delete-property/ resets;
    /delete-property/ reset-names;

    ti,no-idle-on-init;
    ti,no-reset-on-init;
};

&mmu_ipu2 {
    late_attach = <1>;
};

&ipu2_memory_region {
    late_attach = <1>;
    ti,no-idle-on-init;
    ti,no-reset-on-init;
//    /delete-property/ reusable;
//    no-map;
};

&ipu2 {
    /* Delete the clocks and resets as they are not registered at init time */
    /delete-property/ clocks;
    /delete-property/ resets;
    late_attach = <1>;
    ti,no-idle-on-init;
    ti,no-reset-on-init;
};

But this wasn't enough. The kernel was crashing very early. It boiled down to the "/delete-property/ clocks;" and "/delete-property/ clock-names;" lines for IPU2: when the clock was not present, it would crash, when left defined, kernel would boot but it wasn't able to bring up the IPU2 because the clock was never ready.

Long story short, the problem was that RM_IPU2_RSTCTRL was being set by the kernel, and then when ti-sysc.c was trying to acccess IPU2_MMU registers, it would crash because of it being held reset. So I mad yet another "HACK" to skip initialization of "core" clock:

--- a/drivers/soc/ti/omap_prm.c
+++ b/drivers/soc/ti/omap_prm.c
@@ -878,6 +878,8 @@ static int omap_prm_reset_init(struct platform_device *pdev,
        struct ti_prm_platform_data *pdata = dev_get_platdata(&pdev->dev);
        char buf[32];
        u32 v;
+       struct device_node const * const np = of_find_node_by_path("/ocp/ipu@55020000"); /* IPU2 */
+       bool const ipu_early_booted = of_property_read_bool(np, "late_attach");

        /*
         * Check if we have controllable resets. If either rstctrl is non-zero
@@ -925,8 +927,14 @@ static int omap_prm_reset_init(struct platform_device *pdev,
                map++;
        }

+       /* Skip initialization of reset for IPU2 if late-attach is enabled
+        * Not doing so causes kernel to crash on early stage because ti-sysc.c
+        * tries to access IPU2_MMU register at ddata->revision = sysc_read_revision(ddata);
+        * while reset is being held down. There is probably a better fix for this, because
+        * IPU1 doesn't show this behavior; but I could not find it.
+        */
        /* Quirk handling to assert rst_map_012 bits on reset and avoid errors */
-       if (prm->data->rstmap == rst_map_012) {
+       if (prm->data->rstmap == rst_map_012 && !(ipu_early_booted && strcmp(prm->data->name, "core") == 0)) {
                v = readl_relaxed(reset->prm->base + reset->prm->data->rstctrl);
                if ((v & reset->mask) != reset->mask) {
                        dev_dbg(&pdev->dev, "Asserting all resets: %08x\n", v);

All these changes got both IPUs to late-attach properly, still some work to do on the porting, but the hardest part (I hope so!) is done.

So, if you're stryggling with this same issue, I hope this post was somehow useful to you.