Subject: Re: [PATCH 3/3] powerpc/mpc5121: shared DIU framebuffer support

From: Grant Likely <[email-masked]>
Date: Sat, 27 Feb 2010 23:50:09 -0700

On Sat, Feb 27, 2010 at 2:58 PM, Anatolij Gustschin <[email-masked]> wrote:
> MPC5121 DIU configuration/setup as initialized by the boot
> loader currently will get lost while booting Linux. As a
> result displaying the boot splash is not possible through
> the boot process.
>
> To prevent this we reserve configured DIU frame buffer
> address range while booting and preserve AOI descriptor
> and gamma table so that DIU continues displaying through
> the whole boot process. On first open from user space
> DIU frame buffer driver releases the reserved frame
> buffer area and continues to operate as usual.
>
> The patch also moves drivers/video/fsl-diu-fb.h file to
> include/linux as we use some DIU structures in platform
> code.
>
> 'diu_ops' callbacks in platform code borrowed from John's
> DIU code.
>
> Signed-off-by: John Rigby <[email-masked]>
> Signed-off-by: Anatolij Gustschin <[email-masked]>
> Cc: Grant Likely <[email-masked]>

On quick glance this patch seems mostly okay, but this patch should
probably be broken up a bit to simplify review and dissociate
unrelated changes.  For example, the move of fsl-diu-fb.h is a
discrete change that should be split off.  Some more comments
below....

> ---
>  arch/powerpc/platforms/512x/mpc5121_ads.c     |    7 +
>  arch/powerpc/platforms/512x/mpc5121_generic.c |   13 ++
>  arch/powerpc/platforms/512x/mpc512x.h         |    3 +
>  arch/powerpc/platforms/512x/mpc512x_shared.c  |  282 +++++++++++++++++++++++++
>  arch/powerpc/sysdev/fsl_soc.h                 |    1 +
>  drivers/video/fsl-diu-fb.c                    |   25 ++-
>  drivers/video/fsl-diu-fb.h                    |  223 -------------------
>  include/linux/fsl-diu-fb.h                    |  223 +++++++++++++++++++
>  8 files changed, 549 insertions(+), 228 deletions(-)
>  delete mode 100644 drivers/video/fsl-diu-fb.h
>  create mode 100644 include/linux/fsl-diu-fb.h
>
> diff --git a/arch/powerpc/platforms/512x/mpc5121_ads.c b/arch/powerpc/platforms/512x/mpc5121_ads.c
> index ee6ae12..aa4d5a8 100644
> --- a/arch/powerpc/platforms/512x/mpc5121_ads.c
> +++ b/arch/powerpc/platforms/512x/mpc5121_ads.c
> @@ -42,6 +42,7 @@ static void __init mpc5121_ads_setup_arch(void)
>        for_each_compatible_node(np, "pci", "fsl,mpc5121-pci")
>                mpc83xx_add_bridge(np);
>  #endif
> +       mpc512x_setup_diu();
>  }
>
>  static void __init mpc5121_ads_init_IRQ(void)
> @@ -60,11 +61,17 @@ static int __init mpc5121_ads_probe(void)
>        return of_flat_dt_is_compatible(root, "fsl,mpc5121ads");
>  }
>
> +void __init mpc5121_ads_init_early(void)
> +{
> +       mpc512x_init_diu();
> +}
> +
>  define_machine(mpc5121_ads) {
>        .name                   = "MPC5121 ADS",
>        .probe                  = mpc5121_ads_probe,
>        .setup_arch             = mpc5121_ads_setup_arch,
>        .init                   = mpc512x_init,
> +       .init_early             = mpc5121_ads_init_early,
>        .init_IRQ               = mpc5121_ads_init_IRQ,
>        .get_irq                = ipic_get_irq,
>        .calibrate_decr         = generic_calibrate_decr,
> diff --git a/arch/powerpc/platforms/512x/mpc5121_generic.c b/arch/powerpc/platforms/512x/mpc5121_generic.c
> index a6c0e3a..3aaa281 100644
> --- a/arch/powerpc/platforms/512x/mpc5121_generic.c
> +++ b/arch/powerpc/platforms/512x/mpc5121_generic.c
> @@ -28,6 +28,7 @@
>  */
>  static char *board[] __initdata = {
>        "prt,prtlvt",
> +       "ifm,pdm360ng",

You're adding a new board here.  This is completely unrelated.

>        NULL
>  };
>
> @@ -48,10 +49,22 @@ static int __init mpc5121_generic_probe(void)
>        return board[i] != NULL;
>  }
>
> +void __init mpc512x_generic_init_early(void)
> +{
> +       mpc512x_init_diu();
> +}
> +
> +void __init mpc512x_generic_setup_arch(void)
> +{
> +       mpc512x_setup_diu();
> +}
> +
>  define_machine(mpc5121_generic) {
>        .name                   = "MPC5121 generic",
>        .probe                  = mpc5121_generic_probe,
>        .init                   = mpc512x_init,
> +       .init_early             = mpc512x_generic_init_early,
> +       .setup_arch             = mpc512x_generic_setup_arch,
>        .init_IRQ               = mpc512x_init_IRQ,
>        .get_irq                = ipic_get_irq,
>        .calibrate_decr         = generic_calibrate_decr,
> diff --git a/arch/powerpc/platforms/512x/mpc512x.h b/arch/powerpc/platforms/512x/mpc512x.h
> index d72b2c7..1cfe9d5 100644
> --- a/arch/powerpc/platforms/512x/mpc512x.h
> +++ b/arch/powerpc/platforms/512x/mpc512x.h
> @@ -17,4 +17,7 @@ extern int __init mpc5121_clk_init(void);
>  void __init mpc512x_declare_of_platform_devices(void);
>  extern void mpc512x_restart(char *cmd);
>  extern void __init mpc5121_usb_init(void);
> +extern void __init mpc512x_init_diu(void);
> +extern void __init mpc512x_setup_diu(void);

__init annotations do not belong in header files.

> +extern struct fsl_diu_shared_fb diu_shared_fb;

Hmmmm.  I'm not fond of the global data structure.  Especially
considering that the struct fsl_diu_shared_fb is defined in
mpc512x_shared.c, so nothing outside of that .c file can do anything
with the structure.

>  #endif                         /* __MPC512X_H__ */
> diff --git a/arch/powerpc/platforms/512x/mpc512x_shared.c b/arch/powerpc/platforms/512x/mpc512x_shared.c
> index fbdf65f..a8c50a6 100644
> --- a/arch/powerpc/platforms/512x/mpc512x_shared.c
> +++ b/arch/powerpc/platforms/512x/mpc512x_shared.c
> @@ -16,7 +16,11 @@
>  #include <linux/io.h>
>  #include <linux/irq.h>
>  #include <linux/of_platform.h>
> +#include <linux/fsl-diu-fb.h>
> +#include <linux/bootmem.h>
> +#include <sysdev/fsl_soc.h>
>
> +#include <asm/cacheflush.h>
>  #include <asm/machdep.h>
>  #include <asm/ipic.h>
>  #include <asm/prom.h>
> @@ -53,6 +57,284 @@ void mpc512x_restart(char *cmd)
>                ;
>  }
>
> +struct fsl_diu_shared_fb {
> +       char            gamma[0x300];   /* 32-bit aligned! */
> +       struct diu_ad   ad0;            /* 32-bit aligned! */
> +       phys_addr_t     fb_phys;
> +       size_t          fb_len;
> +       bool            in_use;
> +};
> +
> +unsigned int mpc512x_get_pixel_format(unsigned int bits_per_pixel,
> +                                     int monitor_port)
> +{
> +       unsigned int pix_fmt;
> +
> +       switch (bits_per_pixel) {
> +       case 32:
> +               pix_fmt = 0x88883316;
> +               break;
> +       case 24:
> +               pix_fmt = 0x88082219;
> +               break;
> +       case 16:
> +               pix_fmt = 0x65053118;
> +               break;
> +       default:
> +               pix_fmt = 0x00000400;
> +       }
> +       return pix_fmt;
> +}
> +
> +void mpc512x_set_gamma_table(int monitor_port, char *gamma_table_base)
> +{
> +}
> +
> +void mpc512x_set_monitor_port(int monitor_port)
> +{
> +}
> +
> +#define CCM_SCFR1      0x0000000c
> +#define DIU_DIV_MASK   0x000000ff
> +void mpc512x_set_pixel_clock(unsigned int pixclock)
> +{
> +       unsigned long bestval, bestfreq, speed_ccb, busfreq;
> +       unsigned long minpixclock, maxpixclock, pixval;
> +       struct device_node *np;
> +       void __iomem *ccm;
> +       u32 temp;
> +       long err;
> +       int i;
> +
> +       np = of_find_compatible_node(NULL, NULL, "fsl,mpc5121-clock");
> +       if (!np) {
> +               pr_err("Can't find clock control module.\n");
> +               return;
> +       }
> +
> +       ccm = of_iomap(np, 0);
> +       if (!ccm) {
> +               pr_err("Can't map clock control module reg.\n");
> +               of_node_put(np);
> +               return;
> +       }
> +       of_node_put(np);
> +
> +       busfreq = 200000000;

Instead of some hard coding some bogus defalt busfreq, you should
error out if the real frequency cannot be determined.  Force users to
supply a valid tree.

> +       np = of_find_node_by_type(NULL, "cpu");
> +       if (np) {
> +               unsigned int size;
> +               const unsigned int *prop =
> +                       of_get_property(np, "bus-frequency", &size);
> +               if (prop)
> +                       busfreq = *prop;
> +               of_node_put(np);
> +       }
> +
> +       /* Pixel Clock configuration */
> +       pr_debug("DIU: Bus Frequency = %lu\n", busfreq);
> +       speed_ccb = busfreq * 4;
> +
> +       /* Calculate the pixel clock with the smallest error */
> +       /* calculate the following in steps to avoid overflow */
> +       pr_debug("DIU pixclock in ps - %d\n", pixclock);
> +       temp = 1;
> +       temp *= 1000000000;
> +       temp /= pixclock;
> +       temp *= 1000;
> +       pixclock = temp;

Really?  I think you can simplify this.

> +       pr_debug("DIU pixclock freq - %u\n", pixclock);
> +
> +       temp *= 5;
> +       temp /= 100;  /* pixclock * 0.05 */
> +       pr_debug("deviation = %d\n", temp);
> +       minpixclock = pixclock - temp;
> +       maxpixclock = pixclock + temp;
> +       pr_debug("DIU minpixclock - %lu\n", minpixclock);
> +       pr_debug("DIU maxpixclock - %lu\n", maxpixclock);
> +       pixval = speed_ccb/pixclock;
> +       pr_debug("DIU pixval = %lu\n", pixval);
> +
> +       err = 100000000;
> +       bestval = pixval;
> +       pr_debug("DIU bestval = %lu\n", bestval);
> +
> +       bestfreq = 0;
> +       for (i = -1; i <= 1; i++) {
> +               temp = speed_ccb / (pixval+i);
> +               pr_debug("DIU test pixval i=%d, pixval=%lu, temp freq. = %u\n",
> +                       i, pixval, temp);
> +               if ((temp < minpixclock) || (temp > maxpixclock))
> +                       pr_debug("DIU exceeds monitor range (%lu to %lu)\n",
> +                               minpixclock, maxpixclock);
> +               else if (abs(temp - pixclock) < err) {
> +                       pr_debug("Entered the else if block %d\n", i);
> +                       err = abs(temp - pixclock);
> +                       bestval = pixval + i;
> +                       bestfreq = temp;
> +               }
> +       }
> +
> +       pr_debug("DIU chose = %lx\n", bestval);
> +       pr_debug("DIU error = %ld\n NomPixClk ", err);
> +       pr_debug("DIU: Best Freq = %lx\n", bestfreq);
> +       /* Modify DIU_DIV in CCM SCFR1 */
> +       temp = in_be32(ccm + CCM_SCFR1);
> +       pr_debug("DIU: Current value of SCFR1: 0x%08x\n", temp);
> +       temp &= ~DIU_DIV_MASK;
> +       temp |= (bestval & DIU_DIV_MASK);
> +       out_be32(ccm + CCM_SCFR1, temp);
> +       pr_debug("DIU: Modified value of SCFR1: 0x%08x\n", temp);
> +       iounmap(ccm);
> +}
> +
> +ssize_t mpc512x_show_monitor_port(int monitor_port, char *buf)
> +{
> +       return snprintf(buf, PAGE_SIZE, "0 - 5121 LCD\n");
> +}
> +
> +int mpc512x_set_sysfs_monitor_port(int val)
> +{
> +       return 0;
> +}
> +
> +struct fsl_diu_shared_fb __attribute__ ((__aligned__(8))) diu_shared_fb;
> +EXPORT_SYMBOL_GPL(diu_shared_fb);
> +
> +#if defined(CONFIG_FB_FSL_DIU) || \
> +    defined(CONFIG_FB_FSL_DIU_MODULE)
> +static inline void mpc512x_free_bootmem(struct page *page)
> +{
> +       __ClearPageReserved(page);
> +       BUG_ON(PageTail(page));
> +       BUG_ON(atomic_read(&page->_count) > 1);
> +       atomic_set(&page->_count, 1);
> +       __free_page(page);
> +       totalram_pages++;
> +}
> +
> +void mpc512x_release_bootmem(void)
> +{
> +       unsigned long addr = diu_shared_fb.fb_phys & PAGE_MASK;
> +       unsigned long size = diu_shared_fb.fb_len;
> +       unsigned long start, end;
> +
> +       if (diu_shared_fb.in_use) {
> +               start = PFN_UP(addr);
> +               end = PFN_DOWN(addr + size);
> +
> +               for (; start < end; start++)
> +                       mpc512x_free_bootmem(pfn_to_page(start));
> +
> +               diu_shared_fb.in_use = false;
> +       }
> +       diu_ops.release_bootmem = NULL;
> +}
> +#endif
> +
> +/*
> + * Check if DIU was pre-initialized. If so, perform steps
> + * needed to continue displaying through the whole boot process.
> + * Move area descriptor and gamma table elsewhere, they are
> + * destroyed by bootmem allocator otherwise. The frame buffer
> + * address range will be reserved in setup_arch() after bootmem
> + * allocator is up.
> + */
> +void __init mpc512x_init_diu(void)
> +{
> +       struct device_node *np;
> +       void __iomem *diu_reg;
> +       phys_addr_t desc;
> +       void __iomem *vaddr;
> +       unsigned long mode, pix_fmt, res, bpp;
> +       unsigned long dst;
> +
> +       np = of_find_compatible_node(NULL, NULL, "fsl,mpc5121-diu");
> +       if (!np) {
> +               pr_err("No DIU node\n");
> +               return;
> +       }
> +
> +       diu_reg = of_iomap(np, 0);
> +       of_node_put(np);
> +       if (!diu_reg) {
> +               pr_err("Can't map DIU\n");
> +               return;
> +       }
> +
> +       mode = in_be32(diu_reg + 0x1c);
> +       if (mode != 1) {
> +               pr_info("%s: DIU OFF\n", __func__);
> +               goto out;
> +       }
> +
> +       desc = in_be32(diu_reg);
> +       vaddr = ioremap(desc, sizeof(struct diu_ad));
> +       if (!vaddr) {
> +               pr_err("Can't map DIU area desc.\n");
> +               goto out;
> +       }
> +       memcpy(&diu_shared_fb.ad0, vaddr, sizeof(struct diu_ad));
> +       /* flush fb area descriptor */
> +       dst = (unsigned long)&diu_shared_fb.ad0;
> +       flush_dcache_range(dst, dst + sizeof(struct diu_ad) - 1);
> +
> +       res = in_be32(diu_reg + 0x28);
> +       pix_fmt = in_le32(vaddr);
> +       bpp = ((pix_fmt >> 16) & 0x3) + 1;
> +       diu_shared_fb.fb_phys = in_le32(vaddr + 4);
> +       diu_shared_fb.fb_len = ((res & 0xfff0000) >> 16) * (res & 0xfff) * bpp;
> +       diu_shared_fb.in_use = true;
> +       iounmap(vaddr);
> +
> +       desc = in_be32(diu_reg + 0xc);
> +       vaddr = ioremap(desc, sizeof(diu_shared_fb.gamma));
> +       if (!vaddr) {
> +               pr_err("Can't map DIU area desc.\n");
> +               diu_shared_fb.in_use = false;
> +               goto out;
> +       }
> +       memcpy(&diu_shared_fb.gamma, vaddr, sizeof(diu_shared_fb.gamma));
> +       /* flush gamma table */
> +       dst = (unsigned long)&diu_shared_fb.gamma;
> +       flush_dcache_range(dst, dst + sizeof(diu_shared_fb.gamma) - 1);
> +
> +       iounmap(vaddr);
> +       out_be32(diu_reg + 0xc, virt_to_phys(&diu_shared_fb.gamma));
> +       out_be32(diu_reg + 4, 0);
> +       out_be32(diu_reg + 8, 0);
> +       out_be32(diu_reg, virt_to_phys(&diu_shared_fb.ad0));
> +
> +out:
> +       iounmap(diu_reg);
> +}
> +
> +void __init mpc512x_setup_diu(void)
> +{
> +       int ret;
> +
> +       if (diu_shared_fb.in_use) {
> +               ret = reserve_bootmem(diu_shared_fb.fb_phys,
> +                                     diu_shared_fb.fb_len,
> +                                     BOOTMEM_EXCLUSIVE);
> +               if (ret) {
> +                       pr_err("%s: reserve bootmem failed\n", __func__);
> +                       diu_shared_fb.in_use = false;
> +               }
> +       }
> +
> +#if defined(CONFIG_FB_FSL_DIU) || \
> +    defined(CONFIG_FB_FSL_DIU_MODULE)
> +       diu_ops.get_pixel_format        = mpc512x_get_pixel_format;
> +       diu_ops.set_gamma_table         = mpc512x_set_gamma_table;
> +       diu_ops.set_monitor_port        = mpc512x_set_monitor_port;
> +       diu_ops.set_pixel_clock         = mpc512x_set_pixel_clock;
> +       diu_ops.show_monitor_port       = mpc512x_show_monitor_port;
> +       diu_ops.set_sysfs_monitor_port  = mpc512x_set_sysfs_monitor_port;
> +       diu_ops.release_bootmem         = mpc512x_release_bootmem;
> +#endif
> +}
> +
>  void __init mpc512x_init_IRQ(void)
>  {
>        struct device_node *np;
> diff --git a/arch/powerpc/sysdev/fsl_soc.h b/arch/powerpc/sysdev/fsl_soc.h
> index 19754be..346057d 100644
> --- a/arch/powerpc/sysdev/fsl_soc.h
> +++ b/arch/powerpc/sysdev/fsl_soc.h
> @@ -30,6 +30,7 @@ struct platform_diu_data_ops {
>        void (*set_pixel_clock) (unsigned int pixclock);
>        ssize_t (*show_monitor_port) (int monitor_port, char *buf);
>        int (*set_sysfs_monitor_port) (int val);
> +       void (*release_bootmem) (void);
>  };
>
>  extern struct platform_diu_data_ops diu_ops;
> diff --git a/drivers/video/fsl-diu-fb.c b/drivers/video/fsl-diu-fb.c
> index 19ca1da..263f7da 100644
> --- a/drivers/video/fsl-diu-fb.c
> +++ b/drivers/video/fsl-diu-fb.c
> @@ -34,7 +34,7 @@
>  #include <linux/of_platform.h>
>
>  #include <sysdev/fsl_soc.h>
> -#include "fsl-diu-fb.h"
> +#include <linux/fsl-diu-fb.h>
>
>  #include "ofmode.h"
>
> @@ -331,8 +331,11 @@ static int fsl_diu_enable_panel(struct fb_info *info)
>        if (mfbi->type != MFB_TYPE_OFF) {
>                switch (mfbi->index) {
>                case 0:                         /* plane 0 */
> -                       if (hw->desc[0] != ad->paddr)
> +                       if (in_be32(&hw->desc[0]) != ad->paddr) {

Unrelated bugfix?  If so, please split into separate patch.

> +                               out_be32(&dr.diu_reg->diu_mode, 0);
>                                out_be32(&hw->desc[0], ad->paddr);

This line also looks like it needs fixing.

> +                               out_be32(&dr.diu_reg->diu_mode, 1);
> +                       }
>                        break;
>                case 1:                         /* plane 1 AOI 0 */
>                        cmfbi = machine_data->fsl_diu_info[2]->par;
> @@ -391,7 +394,7 @@ static int fsl_diu_disable_panel(struct fb_info *info)
>
>        switch (mfbi->index) {
>        case 0:                                 /* plane 0 */
> -               if (hw->desc[0] != machine_data->dummy_ad->paddr)
> +               if (in_be32(&hw->desc[0]) != machine_data->dummy_ad->paddr)

Same bugfix?

>                        out_be32(&hw->desc[0],
>                                machine_data->dummy_ad->paddr);
>                break;
> @@ -1102,6 +1105,10 @@ static int fsl_diu_open(struct fb_info *info, int user)
>        struct mfb_info *mfbi = info->par;
>        int res = 0;
>
> +       /* free boot splash memory on first /dev/fb0 open */
> +       if (!mfbi->index && diu_ops.release_bootmem)
> +               diu_ops.release_bootmem();
> +
>        spin_lock(&diu_lock);
>        mfbi->count++;
>        if (mfbi->count == 1) {
> @@ -1436,6 +1443,7 @@ static int __devinit fsl_diu_probe(struct of_device *ofdev,
>        int ret, i, error = 0;
>        struct resource res;
>        struct fsl_diu_data *machine_data;
> +       int diu_mode;
>
>        machine_data = kzalloc(sizeof(struct fsl_diu_data), GFP_KERNEL);
>        if (!machine_data)
> @@ -1472,7 +1480,9 @@ static int __devinit fsl_diu_probe(struct of_device *ofdev,
>                goto error2;
>        }
>
> -       out_be32(&dr.diu_reg->diu_mode, 0);             /* disable DIU anyway*/
> +       diu_mode = in_be32(&dr.diu_reg->diu_mode);
> +       if (diu_mode != MFB_MODE1)
> +               out_be32(&dr.diu_reg->diu_mode, 0);     /* disable DIU */

Is this the best approach?  Would it be better to make this decision
based on a property in the device tree?

>
>        /* Get the IRQ of the DIU */
>        machine_data->irq = irq_of_parse_and_map(np, 0);
> @@ -1520,7 +1530,12 @@ static int __devinit fsl_diu_probe(struct of_device *ofdev,
>        machine_data->dummy_ad->offset_xyd = 0;
>        machine_data->dummy_ad->next_ad = 0;
>
> -       out_be32(&dr.diu_reg->desc[0], machine_data->dummy_ad->paddr);
> +       /* Let DIU display splash screen if it was pre-initialized
> +        * by the bootloader, set dummy area descriptor otherwise.
> +        */
> +       if (diu_mode != MFB_MODE1)
> +               out_be32(&dr.diu_reg->desc[0], machine_data->dummy_ad->paddr);
> +

Same as above.

>        out_be32(&dr.diu_reg->desc[1], machine_data->dummy_ad->paddr);
>        out_be32(&dr.diu_reg->desc[2], machine_data->dummy_ad->paddr);
>

-- 
Grant Likely, B.Sc., P.Eng.
Secret Lab Technologies Ltd.