Files
u-boot/drivers/video/imx/mxc_ipuv3_fb.c
Brian Ruley f010993606 video: imx: ipuv3: refactor to use dm-managed state
Get rid of most globals that are spread around between TU's and place
them in their own structs managed by dm. Device state is now owned by
each driver instance. This design mirrors the Linux IPUv3 driver
architecture.

This work is done in preparation to migrate the driver to the clock
framework. While not the primary intent, this change also enables
multiple IPU instances to exist contemporarily.

Signed-off-by: Brian Ruley <brian.ruley@gehealthcare.com>
2025-12-29 10:17:01 -03:00

667 lines
16 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* Code fixes:
*
* (C) Copyright 2025
* Brian Ruley, GE HealthCare, brian.ruley@gehealthcare.com
*
* Porting to u-boot:
*
* (C) Copyright 2010
* Stefano Babic, DENX Software Engineering, sbabic@denx.de
*
* MX51 Linux framebuffer:
*
* (C) Copyright 2004-2010 Freescale Semiconductor, Inc.
*/
#include "../videomodes.h"
#include "display.h"
#include "ipu.h"
#include "ipu_regs.h"
#include "mxcfb.h"
#include <asm/cache.h>
#include <asm/global_data.h>
#include <asm/io.h>
#include <asm/mach-imx/video.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/fb.h>
#include <linux/list.h>
#include <linux/string.h>
#include <log.h>
#include <panel.h>
#include <part.h>
#include <dm.h>
#include <dm/devres.h>
#include <video.h>
DECLARE_GLOBAL_DATA_PTR;
static int mxcfb_map_video_memory(struct fb_info *fbi);
static int mxcfb_unmap_video_memory(struct fb_info *fbi);
static struct fb_videomode const *gmode;
static u8 gdisp;
static u32 gpixfmt;
static void fb_videomode_to_var(struct fb_var_screeninfo *var,
const struct fb_videomode *mode)
{
var->xres = mode->xres;
var->yres = mode->yres;
var->xres_virtual = mode->xres;
var->yres_virtual = mode->yres;
var->xoffset = 0;
var->yoffset = 0;
var->pixclock = mode->pixclock;
var->left_margin = mode->left_margin;
var->right_margin = mode->right_margin;
var->upper_margin = mode->upper_margin;
var->lower_margin = mode->lower_margin;
var->hsync_len = mode->hsync_len;
var->vsync_len = mode->vsync_len;
var->sync = mode->sync;
var->vmode = mode->vmode & FB_VMODE_MASK;
}
struct ipuv3_video_priv {
struct ipu_ctx *ctx;
ulong regs;
};
/*
* Structure containing the MXC specific framebuffer information.
*/
struct mxcfb_info {
struct udevice *udev;
int blank;
ipu_channel_t ipu_ch;
struct ipu_di_config *di;
u32 ipu_di_pix_fmt;
unsigned char overlay;
unsigned char alpha_chan_en;
dma_addr_t alpha_phy_addr0;
dma_addr_t alpha_phy_addr1;
void *alpha_virt_addr0;
void *alpha_virt_addr1;
u32 alpha_mem_len;
u32 cur_ipu_buf;
u32 cur_ipu_alpha_buf;
u32 pseudo_palette[16];
struct ipu_ctx *ctx;
};
enum { BOTH_ON, SRC_ON, TGT_ON, BOTH_OFF };
static unsigned long default_bpp = 16;
static unsigned char g_dp_in_use;
static struct fb_info *mxcfb_info[3];
static int ext_clk_used;
static u32 bpp_to_pixfmt(struct fb_info *fbi)
{
u32 pixfmt = 0;
debug("bpp_to_pixfmt: %d\n", fbi->var.bits_per_pixel);
if (fbi->var.nonstd)
return fbi->var.nonstd;
switch (fbi->var.bits_per_pixel) {
case 24:
pixfmt = IPU_PIX_FMT_BGR24;
break;
case 32:
pixfmt = IPU_PIX_FMT_BGR32;
break;
case 16:
pixfmt = IPU_PIX_FMT_RGB565;
break;
}
return pixfmt;
}
static int setup_disp_channel1(struct fb_info *fbi)
{
ipu_channel_params_t params;
struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par;
memset(&params, 0, sizeof(params));
params.mem_dp_bg_sync.di = mxc_fbi->di->disp;
debug("%s called\n", __func__);
/*
* Assuming interlaced means yuv output, below setting also
* valid for mem_dc_sync. FG should have the same vmode as BG.
*/
if (fbi->var.vmode & FB_VMODE_INTERLACED) {
params.mem_dp_bg_sync.interlaced = 1;
params.mem_dp_bg_sync.out_pixel_fmt = IPU_PIX_FMT_YUV444;
} else if (mxc_fbi->ipu_di_pix_fmt) {
params.mem_dp_bg_sync.out_pixel_fmt = mxc_fbi->ipu_di_pix_fmt;
} else {
params.mem_dp_bg_sync.out_pixel_fmt = IPU_PIX_FMT_RGB666;
}
params.mem_dp_bg_sync.in_pixel_fmt = bpp_to_pixfmt(fbi);
if (mxc_fbi->alpha_chan_en)
params.mem_dp_bg_sync.alpha_chan_en = 1;
return ipu_init_channel(mxc_fbi->ctx, mxc_fbi->ipu_ch, &params);
}
static int setup_disp_channel2(struct fb_info *fbi)
{
int retval = 0;
struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par;
mxc_fbi->cur_ipu_buf = 1;
if (mxc_fbi->alpha_chan_en)
mxc_fbi->cur_ipu_alpha_buf = 1;
fbi->var.xoffset = fbi->var.yoffset = 0;
debug("%s: %x %d %d %d %lx %lx\n", __func__, mxc_fbi->ipu_ch,
fbi->var.xres, fbi->var.yres, fbi->fix.line_length,
fbi->fix.smem_start,
fbi->fix.smem_start + (fbi->fix.line_length * fbi->var.yres));
retval = ipu_init_channel_buffer(
mxc_fbi->ipu_ch, IPU_INPUT_BUFFER, bpp_to_pixfmt(fbi),
fbi->var.xres, fbi->var.yres, fbi->fix.line_length,
fbi->fix.smem_start + (fbi->fix.line_length * fbi->var.yres),
fbi->fix.smem_start, 0, 0);
if (retval)
printf("ipu_init_channel_buffer error %d\n", retval);
return retval;
}
/*
* Set framebuffer parameters and change the operating mode.
*
* @param info framebuffer information pointer
*/
static int mxcfb_set_par(struct fb_info *fbi)
{
int retval = 0;
u32 mem_len;
ipu_di_signal_cfg_t sig_cfg;
struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par;
u32 out_pixel_fmt;
ipu_disable_channel(mxc_fbi->ctx, mxc_fbi->ipu_ch);
ipu_uninit_channel(mxc_fbi->ctx, mxc_fbi->ipu_ch);
mem_len = fbi->var.yres_virtual * fbi->fix.line_length;
if (!fbi->fix.smem_start || (mem_len > fbi->fix.smem_len)) {
if (fbi->fix.smem_start)
mxcfb_unmap_video_memory(fbi);
if (mxcfb_map_video_memory(fbi) < 0)
return -ENOMEM;
}
setup_disp_channel1(fbi);
memset(&sig_cfg, 0, sizeof(sig_cfg));
if (fbi->var.vmode & FB_VMODE_INTERLACED) {
sig_cfg.interlaced = 1;
out_pixel_fmt = IPU_PIX_FMT_YUV444;
} else {
if (mxc_fbi->ipu_di_pix_fmt)
out_pixel_fmt = mxc_fbi->ipu_di_pix_fmt;
else
out_pixel_fmt = IPU_PIX_FMT_RGB666;
}
if (fbi->var.vmode & FB_VMODE_ODD_FLD_FIRST) /* PAL */
sig_cfg.odd_field_first = 1;
if ((fbi->var.sync & FB_SYNC_EXT) || ext_clk_used)
sig_cfg.ext_clk = 1;
if (fbi->var.sync & FB_SYNC_HOR_HIGH_ACT)
sig_cfg.hsync_pol = 1;
if (fbi->var.sync & FB_SYNC_VERT_HIGH_ACT)
sig_cfg.vsync_pol = 1;
if (!(fbi->var.sync & FB_SYNC_CLK_LAT_FALL))
sig_cfg.clk_pol = 1;
if (fbi->var.sync & FB_SYNC_DATA_INVERT)
sig_cfg.data_pol = 1;
if (!(fbi->var.sync & FB_SYNC_OE_LOW_ACT))
sig_cfg.enable_pol = 1;
if (fbi->var.sync & FB_SYNC_CLK_IDLE_EN)
sig_cfg.clkidle_en = 1;
debug("pixclock = %lu Hz\n", PICOS2KHZ(fbi->var.pixclock) * 1000UL);
mxc_fbi->di->pixel_clk_rate = (PICOS2KHZ(fbi->var.pixclock)) * 1000UL;
mxc_fbi->di->pixel_fmt = out_pixel_fmt;
mxc_fbi->di->width = fbi->var.xres;
mxc_fbi->di->height = fbi->var.yres;
mxc_fbi->di->h_start_width = fbi->var.left_margin;
mxc_fbi->di->h_sync_width = fbi->var.hsync_len;
mxc_fbi->di->h_end_width = fbi->var.right_margin;
mxc_fbi->di->v_start_width = fbi->var.upper_margin;
mxc_fbi->di->v_sync_width = fbi->var.vsync_len;
mxc_fbi->di->v_end_width = fbi->var.lower_margin;
mxc_fbi->di->v_to_h_sync = 0;
if (ipu_init_sync_panel(mxc_fbi->di, sig_cfg) != 0) {
puts("mxcfb: Error initializing panel.\n");
return -EINVAL;
}
retval = setup_disp_channel2(fbi);
if (retval)
return retval;
if (mxc_fbi->blank == FB_BLANK_UNBLANK)
ipu_enable_channel(mxc_fbi->ctx, mxc_fbi->ipu_ch);
return retval;
}
/*
* Check framebuffer variable parameters and adjust to valid values.
*
* @param var framebuffer variable parameters
*
* @param info framebuffer information pointer
*/
static int mxcfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
{
u32 vtotal;
u32 htotal;
if (var->xres_virtual < var->xres)
var->xres_virtual = var->xres;
if (var->yres_virtual < var->yres)
var->yres_virtual = var->yres;
if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) &&
(var->bits_per_pixel != 16) && (var->bits_per_pixel != 8))
var->bits_per_pixel = default_bpp;
switch (var->bits_per_pixel) {
case 8:
var->red.length = 3;
var->red.offset = 5;
var->red.msb_right = 0;
var->green.length = 3;
var->green.offset = 2;
var->green.msb_right = 0;
var->blue.length = 2;
var->blue.offset = 0;
var->blue.msb_right = 0;
var->transp.length = 0;
var->transp.offset = 0;
var->transp.msb_right = 0;
break;
case 16:
var->red.length = 5;
var->red.offset = 11;
var->red.msb_right = 0;
var->green.length = 6;
var->green.offset = 5;
var->green.msb_right = 0;
var->blue.length = 5;
var->blue.offset = 0;
var->blue.msb_right = 0;
var->transp.length = 0;
var->transp.offset = 0;
var->transp.msb_right = 0;
break;
case 24:
var->red.length = 8;
var->red.offset = 16;
var->red.msb_right = 0;
var->green.length = 8;
var->green.offset = 8;
var->green.msb_right = 0;
var->blue.length = 8;
var->blue.offset = 0;
var->blue.msb_right = 0;
var->transp.length = 0;
var->transp.offset = 0;
var->transp.msb_right = 0;
break;
case 32:
var->red.length = 8;
var->red.offset = 16;
var->red.msb_right = 0;
var->green.length = 8;
var->green.offset = 8;
var->green.msb_right = 0;
var->blue.length = 8;
var->blue.offset = 0;
var->blue.msb_right = 0;
var->transp.length = 8;
var->transp.offset = 24;
var->transp.msb_right = 0;
break;
}
if (var->pixclock < 1000) {
htotal = var->xres + var->right_margin + var->hsync_len +
var->left_margin;
vtotal = var->yres + var->lower_margin + var->vsync_len +
var->upper_margin;
var->pixclock = (vtotal * htotal * 6UL) / 100UL;
var->pixclock = KHZ2PICOS(var->pixclock);
printf("pixclock set for 60Hz refresh = %u ps\n",
var->pixclock);
}
var->height = -1;
var->width = -1;
var->grayscale = 0;
return 0;
}
static int mxcfb_map_video_memory(struct fb_info *fbi)
{
struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par;
struct video_uc_plat *plat = dev_get_uclass_plat(mxc_fbi->udev);
if (fbi->fix.smem_len < fbi->var.yres_virtual * fbi->fix.line_length) {
fbi->fix.smem_len =
fbi->var.yres_virtual * fbi->fix.line_length;
}
fbi->fix.smem_len = roundup(fbi->fix.smem_len, ARCH_DMA_MINALIGN);
fbi->screen_base = (char *)plat->base;
fbi->fix.smem_start = (unsigned long)fbi->screen_base;
if (fbi->screen_base == 0) {
puts("Unable to allocate framebuffer memory\n");
fbi->fix.smem_len = 0;
fbi->fix.smem_start = 0;
return -EBUSY;
}
debug("allocated fb @ paddr=0x%08X, size=%d.\n",
(u32)fbi->fix.smem_start, fbi->fix.smem_len);
fbi->screen_size = fbi->fix.smem_len;
/* Clear the screen */
memset((char *)fbi->screen_base, 0, fbi->fix.smem_len);
return 0;
}
static int mxcfb_unmap_video_memory(struct fb_info *fbi)
{
fbi->screen_base = 0;
fbi->fix.smem_start = 0;
fbi->fix.smem_len = 0;
return 0;
}
/*
* Initializes the framebuffer information pointer. After allocating
* sufficient memory for the framebuffer structure, the fields are
* filled with custom information passed in from the configurable
* structures. This includes information such as bits per pixel,
* color maps, screen width/height and RGBA offsets.
*
* @param dev The device structure for the IPU passed in by the
* driver framework.
*
* Return: Framebuffer structure initialized with our information
*/
static struct fb_info *mxcfb_init_fbinfo(struct udevice *dev)
{
#define BYTES_PER_LONG 4
#define PADDING (BYTES_PER_LONG - (sizeof(struct fb_info) % BYTES_PER_LONG))
struct fb_info *fbi;
struct mxcfb_info *mxcfbi;
char *p;
int size = sizeof(struct mxcfb_info) + PADDING + sizeof(struct fb_info);
debug("%s: %d %d %d %d\n", __func__, PADDING, size,
sizeof(struct mxcfb_info), sizeof(struct fb_info));
/*
* Allocate sufficient memory for the fb structure
*/
p = devm_kzalloc(dev, size, GFP_KERNEL);
if (!p)
return NULL;
fbi = (struct fb_info *)p;
fbi->par = p + sizeof(struct fb_info) + PADDING;
mxcfbi = (struct mxcfb_info *)fbi->par;
debug("Framebuffer structures at: fbi=0x%x mxcfbi=0x%x\n",
(unsigned int)fbi, (unsigned int)mxcfbi);
fbi->var.activate = FB_ACTIVATE_NOW;
fbi->flags = FBINFO_FLAG_DEFAULT;
fbi->pseudo_palette = mxcfbi->pseudo_palette;
return fbi;
}
/*
* Probe routine for the framebuffer driver. It is called during the
* driver binding process. The following functions are performed in
* this routine: Framebuffer initialization, Memory allocation and
* mapping, Framebuffer registration, IPU initialization.
*
* Return: Appropriate error code to the kernel common code
*/
static int mxcfb_probe(struct udevice *dev, u32 interface_pix_fmt, u8 disp,
struct fb_videomode const *mode)
{
struct ipuv3_video_priv *ipu_priv = dev_get_priv(dev);
struct ipu_ctx *ctx = ipu_priv->ctx;
struct fb_info *fbi;
struct mxcfb_info *mxcfbi;
/*
* Initialize FB structures
*/
fbi = mxcfb_init_fbinfo(dev);
if (!fbi)
return -ENOMEM;
mxcfbi = (struct mxcfb_info *)fbi->par;
if (!g_dp_in_use) {
mxcfbi->ipu_ch = MEM_BG_SYNC;
mxcfbi->blank = FB_BLANK_UNBLANK;
} else {
mxcfbi->ipu_ch = MEM_DC_SYNC;
mxcfbi->blank = FB_BLANK_POWERDOWN;
}
mxcfbi->di = devm_kzalloc(ctx->dev, sizeof(*mxcfbi->di), GFP_KERNEL);
if (!mxcfbi->di)
return -ENOMEM;
mxcfbi->di->disp = disp;
mxcfbi->di->ctx = ctx;
mxcfbi->ctx = ctx;
mxcfbi->udev = dev;
if (!ipu_clk_enabled(ctx))
clk_enable(ctx->ipu_clk);
ipu_disp_set_global_alpha(mxcfbi->ipu_ch, 1, 0x80);
ipu_disp_set_color_key(mxcfbi->ipu_ch, 0, 0);
g_dp_in_use = 1;
mxcfb_info[mxcfbi->di->disp] = fbi;
/* Need dummy values until real panel is configured */
mxcfbi->ipu_di_pix_fmt = interface_pix_fmt;
fb_videomode_to_var(&fbi->var, mode);
fbi->var.bits_per_pixel = 16;
fbi->fix.line_length =
fbi->var.xres_virtual * (fbi->var.bits_per_pixel / 8);
fbi->fix.smem_len = fbi->var.yres_virtual * fbi->fix.line_length;
mxcfb_check_var(&fbi->var, fbi);
/* Default Y virtual size is 2x panel size */
fbi->var.yres_virtual = fbi->var.yres * 2;
/* allocate fb first */
if (mxcfb_map_video_memory(fbi) < 0)
return -ENOMEM;
mxcfb_set_par(fbi);
#ifdef DEBUG
ipu_dump_registers();
#endif
return 0;
}
void ipuv3_fb_shutdown(struct udevice *dev)
{
int i;
struct ipu_stat *stat = (struct ipu_stat *)IPU_STAT;
struct ipuv3_video_priv *ipu_priv = dev_get_priv(dev);
struct ipu_ctx *ctx = ipu_priv->ctx;
if (!ipu_clk_enabled(ctx))
return;
for (i = 0; i < ARRAY_SIZE(mxcfb_info); i++) {
struct fb_info *fbi = mxcfb_info[i];
if (fbi) {
struct mxcfb_info *mxc_fbi = fbi->par;
ipu_disable_channel(ctx, mxc_fbi->ipu_ch);
ipu_uninit_channel(ctx, mxc_fbi->ipu_ch);
}
}
for (i = 0; i < ARRAY_SIZE(stat->int_stat); i++) {
__raw_writel(__raw_readl(&stat->int_stat[i]),
&stat->int_stat[i]);
}
}
int ipuv3_fb_init(struct fb_videomode const *mode, u8 disp, u32 pixfmt)
{
gmode = mode;
gdisp = disp;
gpixfmt = pixfmt;
return 0;
}
enum {
/* Maximum display size we support */
LCD_MAX_WIDTH = 1920,
LCD_MAX_HEIGHT = 1080,
LCD_MAX_LOG2_BPP = VIDEO_BPP16,
};
static int ipuv3_video_probe(struct udevice *dev)
{
struct video_uc_plat *plat = dev_get_uclass_plat(dev);
struct video_priv *uc_priv = dev_get_uclass_priv(dev);
struct ipuv3_video_priv *ipu_priv = dev_get_priv(dev);
#if defined(CONFIG_DISPLAY)
struct udevice *disp_dev;
#endif
struct ipu_ctx *ctx;
u32 fb_start, fb_end;
int ret;
debug("%s() plat: base 0x%lx, size 0x%x\n", __func__, plat->base,
plat->size);
ctx = ipu_probe(dev);
if (IS_ERR(ctx))
return PTR_ERR(ctx);
ipu_priv->ctx = ctx;
ret = ipu_displays_init();
if (ret < 0)
return ret;
ret = mxcfb_probe(dev, gpixfmt, gdisp, gmode);
if (ret < 0)
return ret;
#if defined(CONFIG_DISPLAY)
ret = uclass_first_device_err(UCLASS_DISPLAY, &disp_dev);
if (!ret)
ret = display_enable(disp_dev, 16, NULL);
if (ret < 0)
return ret;
#endif
if (IS_ENABLED(CONFIG_PANEL)) {
struct udevice *panel_dev;
ret = uclass_get_device(UCLASS_PANEL, 0, &panel_dev);
if (panel_dev)
panel_enable_backlight(panel_dev);
}
uc_priv->xsize = gmode->xres;
uc_priv->ysize = gmode->yres;
uc_priv->bpix = LCD_MAX_LOG2_BPP;
/* Enable dcache for the frame buffer */
fb_start = plat->base & ~(MMU_SECTION_SIZE - 1);
fb_end = plat->base + plat->size;
fb_end = ALIGN(fb_end, 1 << MMU_SECTION_SHIFT);
mmu_set_region_dcache_behaviour(fb_start, fb_end - fb_start,
DCACHE_WRITEBACK);
video_set_flush_dcache(dev, true);
return 0;
}
static int ipuv3_video_bind(struct udevice *dev)
{
struct video_uc_plat *plat = dev_get_uclass_plat(dev);
plat->size = LCD_MAX_WIDTH * LCD_MAX_HEIGHT * (1 << VIDEO_BPP32) / 8;
return 0;
}
static const struct udevice_id ipuv3_video_ids[] = {
#ifdef CONFIG_ARCH_MX6
{ .compatible = "fsl,imx6q-ipu" },
#endif
#ifdef CONFIG_ARCH_MX5
{ .compatible = "fsl,imx53-ipu" },
#endif
{}
};
U_BOOT_DRIVER(fsl_imx6q_ipu) = {
.name = "fsl_imx6q_ipu",
.id = UCLASS_VIDEO,
.of_match = ipuv3_video_ids,
.bind = ipuv3_video_bind,
.probe = ipuv3_video_probe,
.priv_auto = sizeof(struct ipuv3_video_priv),
.flags = DM_FLAG_PRE_RELOC,
};