9
0
Fork 0
barebox/drivers/video/imx-ipu-v3/ipufb.c

354 lines
7.7 KiB
C

/*
* Freescale i.MX Frame Buffer device driver
*
* Copyright (C) 2004 Sascha Hauer, Pengutronix
* Based on acornfb.c Copyright (C) Russell King.
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of this archive for
* more details.
*
*/
#define pr_fmt(fmt) "IPU: " fmt
#include <common.h>
#include <fb.h>
#include <io.h>
#include <driver.h>
#include <malloc.h>
#include <errno.h>
#include <init.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <asm-generic/div64.h>
#include <asm/mmu.h>
#include "imx-ipu-v3.h"
#include "ipuv3-plane.h"
/*
* These are the bitfields for each
* display depth that we support.
*/
struct ipufb_rgb {
struct fb_bitfield red;
struct fb_bitfield green;
struct fb_bitfield blue;
struct fb_bitfield transp;
};
struct ipufb_info {
void __iomem *regs;
struct clk *ahb_clk;
struct clk *ipg_clk;
struct clk *per_clk;
struct fb_videomode *mode;
struct fb_info info;
struct device_d *dev;
/* plane[0] is the full plane, plane[1] is the partial plane */
struct ipu_plane *plane[2];
void (*enable)(int enable);
unsigned int di_clkflags;
u32 interface_pix_fmt;
struct ipu_dc *dc;
struct ipu_di *di;
struct list_head list;
char *name;
int id;
struct ipu_output *output;
};
static inline u_int chan_to_field(u_int chan, struct fb_bitfield *bf)
{
chan &= 0xffff;
chan >>= 16 - bf->length;
return chan << bf->offset;
}
static LIST_HEAD(ipu_outputs);
static LIST_HEAD(ipu_fbs);
int ipu_register_output(struct ipu_output *ouput)
{
list_add_tail(&ouput->list, &ipu_outputs);
return 0;
}
static int ipu_register_fb(struct ipufb_info *ipufb)
{
list_add_tail(&ipufb->list, &ipu_fbs);
return 0;
}
int ipu_crtc_mode_set(struct ipufb_info *fbi,
struct fb_videomode *mode,
int x, int y)
{
struct fb_info *info = &fbi->info;
int ret;
struct ipu_di_signal_cfg sig_cfg = {};
u32 out_pixel_fmt;
dev_info(fbi->dev, "%s: mode->xres: %d\n", __func__,
mode->xres);
dev_info(fbi->dev, "%s: mode->yres: %d\n", __func__,
mode->yres);
out_pixel_fmt = fbi->output->out_pixel_fmt;
if (mode->sync & FB_SYNC_HOR_HIGH_ACT)
sig_cfg.Hsync_pol = 1;
if (mode->sync & FB_SYNC_VERT_HIGH_ACT)
sig_cfg.Vsync_pol = 1;
sig_cfg.enable_pol = 1;
sig_cfg.clk_pol = 1;
sig_cfg.width = mode->xres;
sig_cfg.height = mode->yres;
sig_cfg.pixel_fmt = out_pixel_fmt;
sig_cfg.h_start_width = mode->left_margin;
sig_cfg.h_sync_width = mode->hsync_len;
sig_cfg.h_end_width = mode->right_margin;
sig_cfg.v_start_width = mode->upper_margin;
sig_cfg.v_sync_width = mode->vsync_len;
sig_cfg.v_end_width = mode->lower_margin;
sig_cfg.pixelclock = PICOS2KHZ(mode->pixclock) * 1000UL;
sig_cfg.clkflags = fbi->output->di_clkflags;
sig_cfg.v_to_h_sync = 0;
sig_cfg.hsync_pin = 2;
sig_cfg.vsync_pin = 3;
ret = ipu_dc_init_sync(fbi->dc, fbi->di, sig_cfg.interlaced,
out_pixel_fmt, mode->xres);
if (ret) {
dev_err(fbi->dev,
"initializing display controller failed with %d\n",
ret);
return ret;
}
ret = ipu_di_init_sync_panel(fbi->di, &sig_cfg);
if (ret) {
dev_err(fbi->dev,
"initializing panel failed with %d\n", ret);
return ret;
}
ret = ipu_plane_mode_set(fbi->plane[0], mode, info, DRM_FORMAT_XRGB8888,
0, 0, mode->xres, mode->yres,
x, y, mode->xres, mode->yres);
if (ret) {
dev_err(fbi->dev, "initialising plane failed with %s\n", strerror(-ret));
return ret;
}
ret = ipu_di_enable(fbi->di);
if (ret) {
dev_err(fbi->dev, "enabling di failed with %s\n", strerror(-ret));
return ret;
}
ipu_dc_enable_channel(fbi->dc);
ipu_plane_enable(fbi->plane[0]);
return 0;
}
static void ipufb_enable_controller(struct fb_info *info)
{
struct ipufb_info *fbi = container_of(info, struct ipufb_info, info);
struct ipu_output *output = fbi->output;
if (output->ops->prepare)
output->ops->prepare(output, info->mode, fbi->id);
ipu_crtc_mode_set(fbi, info->mode, 0, 0);
if (output->ops->enable)
output->ops->enable(output, info->mode, fbi->id);
}
static void ipufb_disable_controller(struct fb_info *info)
{
struct ipufb_info *fbi = container_of(info, struct ipufb_info, info);
struct ipu_output *output = fbi->output;
if (output->ops->disable)
output->ops->disable(output);
ipu_plane_disable(fbi->plane[0]);
ipu_dc_disable_channel(fbi->dc);
ipu_di_disable(fbi->di);
if (output->ops->unprepare)
output->ops->unprepare(output);
}
static int ipufb_activate_var(struct fb_info *info)
{
struct ipufb_info *fbi = container_of(info, struct ipufb_info, info);
info->line_length = info->xres * (info->bits_per_pixel >> 3);
fbi->info.screen_base = dma_alloc_coherent(info->line_length * info->yres);
if (!fbi->info.screen_base)
return -ENOMEM;
memset(fbi->info.screen_base, 0, info->line_length * info->yres);
return 0;
}
static struct fb_ops ipufb_ops = {
.fb_enable = ipufb_enable_controller,
.fb_disable = ipufb_disable_controller,
.fb_activate_var = ipufb_activate_var,
};
static struct ipufb_info *ipu_output_find_di(struct ipu_output *output)
{
struct ipufb_info *ipufb;
list_for_each_entry(ipufb, &ipu_fbs, list) {
if (!(output->ipu_mask & (1 << ipufb->id)))
continue;
if (ipufb->output)
continue;
return ipufb;
}
return NULL;
}
static int ipu_init(void)
{
struct ipu_output *output;
struct ipufb_info *ipufb;
int ret;
list_for_each_entry(output, &ipu_outputs, list) {
pr_info("found output: %s\n", output->name);
ipufb = ipu_output_find_di(output);
if (!ipufb) {
pr_info("no di found for output %s\n", output->name);
continue;
}
pr_info("using di %s for output %s\n", ipufb->name, output->name);
ipufb->output = output;
ipufb->info.edid_i2c_adapter = output->edid_i2c_adapter;
if (output->modes) {
ipufb->info.modes.modes = output->modes->modes;
ipufb->info.modes.num_modes = output->modes->num_modes;
}
ret = register_framebuffer(&ipufb->info);
if (ret < 0) {
dev_err(ipufb->dev, "failed to register framebuffer\n");
return ret;
}
}
return 0;
}
late_initcall(ipu_init);
static int ipu_get_resources(struct ipufb_info *fbi,
struct ipu_client_platformdata *pdata)
{
struct ipu_soc *ipu = fbi->dev->parent->priv;
int ret;
int dp = -EINVAL;
fbi->dc = ipu_dc_get(ipu, pdata->dc);
if (IS_ERR(fbi->dc)) {
ret = PTR_ERR(fbi->dc);
goto err_out;
}
fbi->di = ipu_di_get(ipu, pdata->di);
if (IS_ERR(fbi->di)) {
ret = PTR_ERR(fbi->di);
goto err_out;
}
if (pdata->dp >= 0)
dp = IPU_DP_FLOW_SYNC_BG;
fbi->plane[0] = ipu_plane_init(ipu, pdata->dma[0], dp);
ret = ipu_plane_get_resources(fbi->plane[0]);
if (ret) {
dev_err(fbi->dev, "getting plane 0 resources failed with %d.\n",
ret);
goto err_out;
}
return 0;
err_out:
return ret;
}
static int ipufb_probe(struct device_d *dev)
{
struct ipufb_info *fbi;
struct fb_info *info;
int ret, ipuid;
struct ipu_client_platformdata *pdata = dev->platform_data;
struct ipu_rgb *ipu_rgb;
fbi = xzalloc(sizeof(*fbi));
info = &fbi->info;
ipuid = of_alias_get_id(dev->parent->device_node, "ipu");
fbi->name = asprintf("ipu%d-di%d", ipuid + 1, pdata->di);
fbi->id = ipuid * 2 + pdata->di;
fbi->dev = dev;
info->priv = fbi;
info->fbops = &ipufb_ops;
ipu_rgb = drm_fourcc_to_rgb(DRM_FORMAT_RGB888);
info->bits_per_pixel = 32;
info->red = ipu_rgb->red;
info->green = ipu_rgb->green;
info->blue = ipu_rgb->blue;
info->transp = ipu_rgb->transp;
dev_dbg(dev, "i.MX Framebuffer driver\n");
ret = ipu_get_resources(fbi, pdata);
if (ret)
return ret;
ret = ipu_register_fb(fbi);
return ret;
}
static void ipufb_remove(struct device_d *dev)
{
}
static struct driver_d ipufb_driver = {
.name = "imx-ipuv3-crtc",
.probe = ipufb_probe,
.remove = ipufb_remove,
};
device_platform_driver(ipufb_driver);