diff options
Diffstat (limited to 'drivers/video/imx-ipu-v3/ipufb.c')
-rw-r--r-- | drivers/video/imx-ipu-v3/ipufb.c | 353 |
1 files changed, 353 insertions, 0 deletions
diff --git a/drivers/video/imx-ipu-v3/ipufb.c b/drivers/video/imx-ipu-v3/ipufb.c new file mode 100644 index 0000000000..14a099e14a --- /dev/null +++ b/drivers/video/imx-ipu-v3/ipufb.c @@ -0,0 +1,353 @@ +/* + * 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); |