9
0
Fork 0

video: Add i.MX IPUv3 support

Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
This commit is contained in:
Sascha Hauer 2014-03-14 10:14:58 +01:00
parent 0a4dece780
commit e5af227899
14 changed files with 3905 additions and 0 deletions

View File

@ -77,6 +77,8 @@ config DRIVER_VIDEO_BCM2835
help
Add support for the BCM2835/VideoCore frame buffer device.
source drivers/video/imx-ipu-v3/Kconfig
config DRIVER_VIDEO_SIMPLEFB
bool "Simple framebuffer support"
depends on OFTREE

View File

@ -13,3 +13,4 @@ obj-$(CONFIG_DRIVER_VIDEO_SDL) += sdl.o
obj-$(CONFIG_DRIVER_VIDEO_OMAP) += omap.o
obj-$(CONFIG_DRIVER_VIDEO_BCM2835) += bcm2835.o
obj-$(CONFIG_DRIVER_VIDEO_SIMPLEFB) += simplefb.o
obj-$(CONFIG_DRIVER_VIDEO_IMX_IPUV3) += imx-ipu-v3/

View File

@ -0,0 +1,8 @@
config DRIVER_VIDEO_IMX_IPUV3
bool "i.MX IPUv3 driver"
help
Support the IPUv3 found on Freescale i.MX51/53/6 SoCs
if DRIVER_VIDEO_IMX_IPUV3
endif

View File

@ -0,0 +1,3 @@
obj-$(CONFIG_DRIVER_VIDEO_IMX_IPUV3) += ipu-common.o ipu-dmfc.o ipu-di.o
obj-$(CONFIG_DRIVER_VIDEO_IMX_IPUV3) += ipu-dp.o ipuv3-plane.o ipufb.o
obj-$(CONFIG_DRIVER_VIDEO_IMX_IPUV3) += ipu-dc.o

View File

@ -0,0 +1,344 @@
/*
* Copyright 2005-2009 Freescale Semiconductor, Inc.
*
* The code contained herein is licensed under the GNU Lesser General
* Public License. You may obtain a copy of the GNU Lesser General
* Public License Version 2.1 or later at the following locations:
*
* http://www.opensource.org/licenses/lgpl-license.html
* http://www.gnu.org/copyleft/lgpl.html
*/
#ifndef __DRM_IPU_H__
#define __DRM_IPU_H__
#include <io.h>
#include <fb.h>
#include <video/fourcc.h>
struct ipu_soc;
enum ipuv3_type {
IPUV3EX,
IPUV3M,
IPUV3H,
};
void ipuwritel(const char *unit, uint32_t value, void __iomem *reg);
uint32_t ipureadl(void __iomem *reg);
/*
* Bitfield of Display Interface signal polarities.
*/
struct ipu_di_signal_cfg {
unsigned datamask_en:1;
unsigned interlaced:1;
unsigned odd_field_first:1;
unsigned clksel_en:1;
unsigned clkidle_en:1;
unsigned data_pol:1; /* true = inverted */
unsigned clk_pol:1; /* true = rising edge */
unsigned enable_pol:1;
unsigned Hsync_pol:1; /* true = active high */
unsigned Vsync_pol:1;
u16 width;
u16 height;
u32 pixel_fmt;
u16 h_start_width;
u16 h_sync_width;
u16 h_end_width;
u16 v_start_width;
u16 v_sync_width;
u16 v_end_width;
u32 v_to_h_sync;
unsigned long pixelclock;
#define IPU_DI_CLKMODE_SYNC (1 << 0)
#define IPU_DI_CLKMODE_EXT (1 << 1)
#define IPU_DI_CLKMODE_NON_FRACTIONAL (1 << 2)
unsigned long clkflags;
u8 hsync_pin;
u8 vsync_pin;
};
enum ipu_color_space {
IPUV3_COLORSPACE_RGB,
IPUV3_COLORSPACE_YUV,
IPUV3_COLORSPACE_UNKNOWN,
};
struct ipuv3_channel;
enum ipu_channel_irq {
IPU_IRQ_EOF = 0,
IPU_IRQ_NFACK = 64,
IPU_IRQ_NFB4EOF = 128,
IPU_IRQ_EOS = 192,
};
int ipu_idmac_channel_irq(struct ipu_soc *ipu, struct ipuv3_channel *channel,
enum ipu_channel_irq irq);
#define IPU_IRQ_DP_SF_START (448 + 2)
#define IPU_IRQ_DP_SF_END (448 + 3)
#define IPU_IRQ_BG_SF_END IPU_IRQ_DP_SF_END,
#define IPU_IRQ_DC_FC_0 (448 + 8)
#define IPU_IRQ_DC_FC_1 (448 + 9)
#define IPU_IRQ_DC_FC_2 (448 + 10)
#define IPU_IRQ_DC_FC_3 (448 + 11)
#define IPU_IRQ_DC_FC_4 (448 + 12)
#define IPU_IRQ_DC_FC_6 (448 + 13)
#define IPU_IRQ_VSYNC_PRE_0 (448 + 14)
#define IPU_IRQ_VSYNC_PRE_1 (448 + 15)
/*
* IPU Image DMA Controller (idmac) functions
*/
struct ipuv3_channel *ipu_idmac_get(struct ipu_soc *ipu, unsigned channel);
void ipu_idmac_put(struct ipuv3_channel *);
int ipu_idmac_enable_channel(struct ipuv3_channel *channel);
int ipu_idmac_disable_channel(struct ipuv3_channel *channel);
int ipu_idmac_wait_busy(struct ipuv3_channel *channel, int ms);
void ipu_idmac_set_double_buffer(struct ipuv3_channel *channel,
bool doublebuffer);
void ipu_idmac_select_buffer(struct ipuv3_channel *channel, u32 buf_num);
/*
* IPU Display Controller (dc) functions
*/
struct ipu_dc;
struct ipu_di;
struct ipu_dc *ipu_dc_get(struct ipu_soc *ipu, int channel);
void ipu_dc_put(struct ipu_dc *dc);
int ipu_dc_init_sync(struct ipu_dc *dc, struct ipu_di *di, bool interlaced,
u32 pixel_fmt, u32 width);
void ipu_dc_enable_channel(struct ipu_dc *dc);
void ipu_dc_disable_channel(struct ipu_dc *dc);
/*
* IPU Display Interface (di) functions
*/
struct ipu_di *ipu_di_get(struct ipu_soc *ipu, int disp);
void ipu_di_put(struct ipu_di *);
int ipu_di_disable(struct ipu_di *);
int ipu_di_enable(struct ipu_di *);
int ipu_di_get_num(struct ipu_di *);
int ipu_di_init_sync_panel(struct ipu_di *, struct ipu_di_signal_cfg *sig);
/*
* IPU Display Multi FIFO Controller (dmfc) functions
*/
struct dmfc_channel;
int ipu_dmfc_enable_channel(struct dmfc_channel *dmfc);
void ipu_dmfc_disable_channel(struct dmfc_channel *dmfc);
int ipu_dmfc_alloc_bandwidth(struct dmfc_channel *dmfc,
unsigned long bandwidth_mbs, int burstsize);
void ipu_dmfc_free_bandwidth(struct dmfc_channel *dmfc);
int ipu_dmfc_init_channel(struct dmfc_channel *dmfc, int width);
struct dmfc_channel *ipu_dmfc_get(struct ipu_soc *ipu, int ipuv3_channel);
void ipu_dmfc_put(struct dmfc_channel *dmfc);
/*
* IPU Display Processor (dp) functions
*/
#define IPU_DP_FLOW_SYNC_BG 0
#define IPU_DP_FLOW_SYNC_FG 1
#define IPU_DP_FLOW_ASYNC0_BG 2
#define IPU_DP_FLOW_ASYNC0_FG 3
#define IPU_DP_FLOW_ASYNC1_BG 4
#define IPU_DP_FLOW_ASYNC1_FG 5
struct ipu_dp *ipu_dp_get(struct ipu_soc *ipu, unsigned int flow);
void ipu_dp_put(struct ipu_dp *);
int ipu_dp_enable_channel(struct ipu_dp *dp);
void ipu_dp_disable_channel(struct ipu_dp *dp);
int ipu_dp_setup_channel(struct ipu_dp *dp,
enum ipu_color_space in, enum ipu_color_space out);
int ipu_dp_set_window_pos(struct ipu_dp *, u16 x_pos, u16 y_pos);
int ipu_dp_set_global_alpha(struct ipu_dp *dp, bool enable, u8 alpha,
bool bg_chan);
#define IPU_CPMEM_WORD(word, ofs, size) ((((word) * 160 + (ofs)) << 8) | (size))
#define IPU_FIELD_UBO IPU_CPMEM_WORD(0, 46, 22)
#define IPU_FIELD_VBO IPU_CPMEM_WORD(0, 68, 22)
#define IPU_FIELD_IOX IPU_CPMEM_WORD(0, 90, 4)
#define IPU_FIELD_RDRW IPU_CPMEM_WORD(0, 94, 1)
#define IPU_FIELD_SO IPU_CPMEM_WORD(0, 113, 1)
#define IPU_FIELD_SLY IPU_CPMEM_WORD(1, 102, 14)
#define IPU_FIELD_SLUV IPU_CPMEM_WORD(1, 128, 14)
#define IPU_FIELD_XV IPU_CPMEM_WORD(0, 0, 10)
#define IPU_FIELD_YV IPU_CPMEM_WORD(0, 10, 9)
#define IPU_FIELD_XB IPU_CPMEM_WORD(0, 19, 13)
#define IPU_FIELD_YB IPU_CPMEM_WORD(0, 32, 12)
#define IPU_FIELD_NSB_B IPU_CPMEM_WORD(0, 44, 1)
#define IPU_FIELD_CF IPU_CPMEM_WORD(0, 45, 1)
#define IPU_FIELD_SX IPU_CPMEM_WORD(0, 46, 12)
#define IPU_FIELD_SY IPU_CPMEM_WORD(0, 58, 11)
#define IPU_FIELD_NS IPU_CPMEM_WORD(0, 69, 10)
#define IPU_FIELD_SDX IPU_CPMEM_WORD(0, 79, 7)
#define IPU_FIELD_SM IPU_CPMEM_WORD(0, 86, 10)
#define IPU_FIELD_SCC IPU_CPMEM_WORD(0, 96, 1)
#define IPU_FIELD_SCE IPU_CPMEM_WORD(0, 97, 1)
#define IPU_FIELD_SDY IPU_CPMEM_WORD(0, 98, 7)
#define IPU_FIELD_SDRX IPU_CPMEM_WORD(0, 105, 1)
#define IPU_FIELD_SDRY IPU_CPMEM_WORD(0, 106, 1)
#define IPU_FIELD_BPP IPU_CPMEM_WORD(0, 107, 3)
#define IPU_FIELD_DEC_SEL IPU_CPMEM_WORD(0, 110, 2)
#define IPU_FIELD_DIM IPU_CPMEM_WORD(0, 112, 1)
#define IPU_FIELD_BNDM IPU_CPMEM_WORD(0, 114, 3)
#define IPU_FIELD_BM IPU_CPMEM_WORD(0, 117, 2)
#define IPU_FIELD_ROT IPU_CPMEM_WORD(0, 119, 1)
#define IPU_FIELD_HF IPU_CPMEM_WORD(0, 120, 1)
#define IPU_FIELD_VF IPU_CPMEM_WORD(0, 121, 1)
#define IPU_FIELD_THE IPU_CPMEM_WORD(0, 122, 1)
#define IPU_FIELD_CAP IPU_CPMEM_WORD(0, 123, 1)
#define IPU_FIELD_CAE IPU_CPMEM_WORD(0, 124, 1)
#define IPU_FIELD_FW IPU_CPMEM_WORD(0, 125, 13)
#define IPU_FIELD_FH IPU_CPMEM_WORD(0, 138, 12)
#define IPU_FIELD_EBA0 IPU_CPMEM_WORD(1, 0, 29)
#define IPU_FIELD_EBA1 IPU_CPMEM_WORD(1, 29, 29)
#define IPU_FIELD_ILO IPU_CPMEM_WORD(1, 58, 20)
#define IPU_FIELD_NPB IPU_CPMEM_WORD(1, 78, 7)
#define IPU_FIELD_PFS IPU_CPMEM_WORD(1, 85, 4)
#define IPU_FIELD_ALU IPU_CPMEM_WORD(1, 89, 1)
#define IPU_FIELD_ALBM IPU_CPMEM_WORD(1, 90, 3)
#define IPU_FIELD_ID IPU_CPMEM_WORD(1, 93, 2)
#define IPU_FIELD_TH IPU_CPMEM_WORD(1, 95, 7)
#define IPU_FIELD_SL IPU_CPMEM_WORD(1, 102, 14)
#define IPU_FIELD_WID0 IPU_CPMEM_WORD(1, 116, 3)
#define IPU_FIELD_WID1 IPU_CPMEM_WORD(1, 119, 3)
#define IPU_FIELD_WID2 IPU_CPMEM_WORD(1, 122, 3)
#define IPU_FIELD_WID3 IPU_CPMEM_WORD(1, 125, 3)
#define IPU_FIELD_OFS0 IPU_CPMEM_WORD(1, 128, 5)
#define IPU_FIELD_OFS1 IPU_CPMEM_WORD(1, 133, 5)
#define IPU_FIELD_OFS2 IPU_CPMEM_WORD(1, 138, 5)
#define IPU_FIELD_OFS3 IPU_CPMEM_WORD(1, 143, 5)
#define IPU_FIELD_SXYS IPU_CPMEM_WORD(1, 148, 1)
#define IPU_FIELD_CRE IPU_CPMEM_WORD(1, 149, 1)
#define IPU_FIELD_DEC_SEL2 IPU_CPMEM_WORD(1, 150, 1)
struct ipu_cpmem_word {
u32 data[5];
u32 res[3];
};
struct ipu_ch_param {
struct ipu_cpmem_word word[2];
};
void ipu_ch_param_write_field(struct ipu_ch_param __iomem *base, u32 wbs, u32 v);
u32 ipu_ch_param_read_field(struct ipu_ch_param __iomem *base, u32 wbs);
struct ipu_ch_param __iomem *ipu_get_cpmem(struct ipuv3_channel *channel);
void ipu_ch_param_dump(struct ipu_ch_param __iomem *p);
static inline void ipu_ch_param_zero(struct ipu_ch_param __iomem *p)
{
int i;
void __iomem *base = p;
for (i = 0; i < sizeof(*p) / sizeof(u32); i++)
ipuwritel("chp", 0, base + i * sizeof(u32));
}
static inline void ipu_cpmem_set_buffer(struct ipu_ch_param __iomem *p,
int bufnum, dma_addr_t buf)
{
if (bufnum)
ipu_ch_param_write_field(p, IPU_FIELD_EBA1, buf >> 3);
else
ipu_ch_param_write_field(p, IPU_FIELD_EBA0, buf >> 3);
}
static inline void ipu_cpmem_set_resolution(struct ipu_ch_param __iomem *p,
int xres, int yres)
{
ipu_ch_param_write_field(p, IPU_FIELD_FW, xres - 1);
ipu_ch_param_write_field(p, IPU_FIELD_FH, yres - 1);
}
static inline void ipu_cpmem_set_stride(struct ipu_ch_param __iomem *p,
int stride)
{
ipu_ch_param_write_field(p, IPU_FIELD_SLY, stride - 1);
}
void ipu_cpmem_set_high_priority(struct ipuv3_channel *channel);
struct ipu_rgb {
struct fb_bitfield red;
struct fb_bitfield green;
struct fb_bitfield blue;
struct fb_bitfield transp;
int bits_per_pixel;
};
struct ipu_rgb *drm_fourcc_to_rgb(u32 drm_fourcc);
int ipu_cpmem_set_format_passthrough(struct ipu_ch_param __iomem *p,
int width);
int ipu_cpmem_set_format_rgb(struct ipu_ch_param __iomem *,
const struct ipu_rgb *rgb);
static inline void ipu_cpmem_interlaced_scan(struct ipu_ch_param *p,
int stride)
{
ipu_ch_param_write_field(p, IPU_FIELD_SO, 1);
ipu_ch_param_write_field(p, IPU_FIELD_ILO, stride / 8);
ipu_ch_param_write_field(p, IPU_FIELD_SLY, (stride * 2) - 1);
};
void ipu_cpmem_set_yuv_planar(struct ipu_ch_param __iomem *p, u32 pixel_format,
int stride, int height);
void ipu_cpmem_set_yuv_interleaved(struct ipu_ch_param __iomem *p,
u32 pixel_format);
void ipu_cpmem_set_yuv_planar_full(struct ipu_ch_param __iomem *p,
u32 pixel_format, int stride, int u_offset, int v_offset);
int ipu_cpmem_set_fmt(struct ipu_ch_param __iomem *cpmem, u32 pixelformat);
enum ipu_color_space ipu_drm_fourcc_to_colorspace(u32 drm_fourcc);
enum ipu_color_space ipu_pixelformat_to_colorspace(u32 pixelformat);
static inline void ipu_cpmem_set_burstsize(struct ipu_ch_param __iomem *p,
int burstsize)
{
ipu_ch_param_write_field(p, IPU_FIELD_NPB, burstsize - 1);
};
struct ipu_client_platformdata {
int di;
int dc;
int dp;
int dmfc;
int dma[2];
struct device_node *device_node;
};
struct ipu_output;
struct ipu_output_ops {
int (*prepare)(struct ipu_output *ipu_video_output, struct fb_videomode *mode, int di);
int (*enable)(struct ipu_output *ipu_video_output, struct fb_videomode *mode, int di);
int (*disable)(struct ipu_output *ipu_video_output);
int (*unprepare)(struct ipu_output *ipu_video_output);
};
struct ipu_output {
struct ipu_output_ops *ops;
struct list_head list;
unsigned int di_clkflags;
uint32_t out_pixel_fmt;
struct i2c_adapter *edid_i2c_adapter;
struct display_timings *modes;
char *name;
int ipu_mask;
};
int ipu_register_output(struct ipu_output *ouput);
#endif /* __DRM_IPU_H__ */

View File

@ -0,0 +1,836 @@
/*
* Copyright (c) 2010 Sascha Hauer <s.hauer@pengutronix.de>
* Copyright (C) 2005-2009 Freescale Semiconductor, Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include <common.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <clock.h>
#include <driver.h>
#include <init.h>
#include <asm/mmu.h>
#include <mach/generic.h>
#include <mach/imx6-regs.h>
#include "imx-ipu-v3.h"
#include "ipu-prv.h"
void ipuwritel(const char *unit, uint32_t value, void __iomem *reg)
{
pr_debug("w: %s 0x%08lx -> 0x%08x\n", unit, (unsigned long)reg & 0xfff, value);
writel(value, reg);
}
uint32_t ipureadl(void __iomem *reg)
{
uint32_t val;
val = readl(reg);
pr_debug("r: 0x%p -> 0x%08x\n", reg - 0x02600000, val);
return val;
}
static inline u32 ipu_cm_read(struct ipu_soc *ipu, unsigned offset)
{
return ipureadl(ipu->cm_reg + offset);
}
static inline void ipu_cm_write(struct ipu_soc *ipu, u32 value, unsigned offset)
{
ipuwritel("cm", value, ipu->cm_reg + offset);
}
static inline u32 ipu_idmac_read(struct ipu_soc *ipu, unsigned offset)
{
return ipureadl(ipu->idmac_reg + offset);
}
static inline void ipu_idmac_write(struct ipu_soc *ipu, u32 value,
unsigned offset)
{
ipuwritel("idmac", value, ipu->idmac_reg + offset);
}
void ipu_srm_dp_sync_update(struct ipu_soc *ipu)
{
u32 val;
val = ipu_cm_read(ipu, IPU_SRM_PRI2);
val |= 0x8;
ipu_cm_write(ipu, val, IPU_SRM_PRI2);
}
EXPORT_SYMBOL_GPL(ipu_srm_dp_sync_update);
struct ipu_ch_param __iomem *ipu_get_cpmem(struct ipuv3_channel *channel)
{
struct ipu_soc *ipu = channel->ipu;
return ipu->cpmem_base + channel->num;
}
EXPORT_SYMBOL_GPL(ipu_get_cpmem);
void ipu_cpmem_set_high_priority(struct ipuv3_channel *channel)
{
struct ipu_soc *ipu = channel->ipu;
struct ipu_ch_param __iomem *p = ipu_get_cpmem(channel);
u32 val;
if (ipu->ipu_type == IPUV3EX)
ipu_ch_param_write_field(p, IPU_FIELD_ID, 1);
val = ipu_idmac_read(ipu, IDMAC_CHA_PRI(channel->num));
val |= 1 << (channel->num % 32);
ipu_idmac_write(ipu, val, IDMAC_CHA_PRI(channel->num));
};
EXPORT_SYMBOL_GPL(ipu_cpmem_set_high_priority);
void ipu_ch_param_write_field(struct ipu_ch_param __iomem *base, u32 wbs, u32 v)
{
u32 bit = (wbs >> 8) % 160;
u32 size = wbs & 0xff;
u32 word = (wbs >> 8) / 160;
u32 i = bit / 32;
u32 ofs = bit % 32;
u32 mask = (1 << size) - 1;
u32 val;
pr_debug("%s %d %d %d 0x%08x\n", __func__, word, bit , size, v);
val = ipureadl(&base->word[word].data[i]);
val &= ~(mask << ofs);
val |= v << ofs;
ipuwritel("chp", val, &base->word[word].data[i]);
if ((bit + size - 1) / 32 > i) {
val = ipureadl(&base->word[word].data[i + 1]);
val &= ~(mask >> (ofs ? (32 - ofs) : 0));
val |= v >> (ofs ? (32 - ofs) : 0);
ipuwritel("chp", val, &base->word[word].data[i + 1]);
}
}
EXPORT_SYMBOL_GPL(ipu_ch_param_write_field);
u32 ipu_ch_param_read_field(struct ipu_ch_param __iomem *base, u32 wbs)
{
u32 bit = (wbs >> 8) % 160;
u32 size = wbs & 0xff;
u32 word = (wbs >> 8) / 160;
u32 i = bit / 32;
u32 ofs = bit % 32;
u32 mask = (1 << size) - 1;
u32 val = 0;
pr_debug("%s %d %d %d\n", __func__, word, bit , size);
val = (ipureadl(&base->word[word].data[i]) >> ofs) & mask;
if ((bit + size - 1) / 32 > i) {
u32 tmp;
tmp = ipureadl(&base->word[word].data[i + 1]);
tmp &= mask >> (ofs ? (32 - ofs) : 0);
val |= tmp << (ofs ? (32 - ofs) : 0);
}
return val;
}
EXPORT_SYMBOL_GPL(ipu_ch_param_read_field);
int ipu_cpmem_set_format_rgb(struct ipu_ch_param __iomem *p,
const struct ipu_rgb *rgb)
{
int bpp = 0, npb = 0, ro, go, bo, to;
ro = rgb->bits_per_pixel - rgb->red.length - rgb->red.offset;
go = rgb->bits_per_pixel - rgb->green.length - rgb->green.offset;
bo = rgb->bits_per_pixel - rgb->blue.length - rgb->blue.offset;
to = rgb->bits_per_pixel - rgb->transp.length - rgb->transp.offset;
ipu_ch_param_write_field(p, IPU_FIELD_WID0, rgb->red.length - 1);
ipu_ch_param_write_field(p, IPU_FIELD_OFS0, ro);
ipu_ch_param_write_field(p, IPU_FIELD_WID1, rgb->green.length - 1);
ipu_ch_param_write_field(p, IPU_FIELD_OFS1, go);
ipu_ch_param_write_field(p, IPU_FIELD_WID2, rgb->blue.length - 1);
ipu_ch_param_write_field(p, IPU_FIELD_OFS2, bo);
if (rgb->transp.length) {
ipu_ch_param_write_field(p, IPU_FIELD_WID3,
rgb->transp.length - 1);
ipu_ch_param_write_field(p, IPU_FIELD_OFS3, to);
} else {
ipu_ch_param_write_field(p, IPU_FIELD_WID3, 7);
ipu_ch_param_write_field(p, IPU_FIELD_OFS3,
rgb->bits_per_pixel);
}
switch (rgb->bits_per_pixel) {
case 32:
bpp = 0;
npb = 15;
break;
case 24:
bpp = 1;
npb = 19;
break;
case 16:
bpp = 3;
npb = 31;
break;
case 8:
bpp = 5;
npb = 63;
break;
default:
return -EINVAL;
}
ipu_ch_param_write_field(p, IPU_FIELD_BPP, bpp);
ipu_ch_param_write_field(p, IPU_FIELD_NPB, npb);
ipu_ch_param_write_field(p, IPU_FIELD_PFS, 7); /* rgb mode */
return 0;
}
EXPORT_SYMBOL_GPL(ipu_cpmem_set_format_rgb);
static struct ipu_rgb def_rgb_32 = {
.red = { .offset = 16, .length = 8, },
.green = { .offset = 8, .length = 8, },
.blue = { .offset = 0, .length = 8, },
.transp = { .offset = 24, .length = 8, },
.bits_per_pixel = 32,
};
static struct ipu_rgb def_bgr_32 = {
.red = { .offset = 0, .length = 8, },
.green = { .offset = 8, .length = 8, },
.blue = { .offset = 16, .length = 8, },
.transp = { .offset = 24, .length = 8, },
.bits_per_pixel = 32,
};
static struct ipu_rgb def_rgb_24 = {
.red = { .offset = 16, .length = 8, },
.green = { .offset = 8, .length = 8, },
.blue = { .offset = 0, .length = 8, },
.transp = { .offset = 0, .length = 0, },
.bits_per_pixel = 24,
};
static struct ipu_rgb def_bgr_24 = {
.red = { .offset = 0, .length = 8, },
.green = { .offset = 8, .length = 8, },
.blue = { .offset = 16, .length = 8, },
.transp = { .offset = 0, .length = 0, },
.bits_per_pixel = 24,
};
static struct ipu_rgb def_rgb_16 = {
.red = { .offset = 11, .length = 5, },
.green = { .offset = 5, .length = 6, },
.blue = { .offset = 0, .length = 5, },
.transp = { .offset = 0, .length = 0, },
.bits_per_pixel = 16,
};
static struct ipu_rgb def_bgr_16 = {
.red = { .offset = 0, .length = 5, },
.green = { .offset = 5, .length = 6, },
.blue = { .offset = 11, .length = 5, },
.transp = { .offset = 0, .length = 0, },
.bits_per_pixel = 16,
};
struct ipu_rgb *drm_fourcc_to_rgb(u32 drm_fourcc)
{
switch (drm_fourcc) {
case DRM_FORMAT_ABGR8888:
case DRM_FORMAT_XBGR8888:
return &def_bgr_32;
case DRM_FORMAT_ARGB8888:
case DRM_FORMAT_XRGB8888:
return &def_rgb_32;
case DRM_FORMAT_BGR888:
return &def_bgr_24;
case DRM_FORMAT_RGB888:
return &def_rgb_24;
case DRM_FORMAT_RGB565:
return &def_rgb_16;
case DRM_FORMAT_BGR565:
return &def_bgr_16;
default:
return NULL;
}
}
#define Y_OFFSET(pix, x, y) ((x) + pix->width * (y))
#define U_OFFSET(pix, x, y) ((pix->width * pix->height) + \
(pix->width * (y) / 4) + (x) / 2)
#define V_OFFSET(pix, x, y) ((pix->width * pix->height) + \
(pix->width * pix->height / 4) + \
(pix->width * (y) / 4) + (x) / 2)
int ipu_cpmem_set_fmt(struct ipu_ch_param __iomem *cpmem, u32 drm_fourcc)
{
switch (drm_fourcc) {
case DRM_FORMAT_ABGR8888:
case DRM_FORMAT_XBGR8888:
ipu_cpmem_set_format_rgb(cpmem, &def_bgr_32);
break;
case DRM_FORMAT_ARGB8888:
case DRM_FORMAT_XRGB8888:
ipu_cpmem_set_format_rgb(cpmem, &def_rgb_32);
break;
case DRM_FORMAT_BGR888:
ipu_cpmem_set_format_rgb(cpmem, &def_bgr_24);
break;
case DRM_FORMAT_RGB888:
ipu_cpmem_set_format_rgb(cpmem, &def_rgb_24);
break;
case DRM_FORMAT_RGB565:
ipu_cpmem_set_format_rgb(cpmem, &def_rgb_16);
break;
case DRM_FORMAT_BGR565:
ipu_cpmem_set_format_rgb(cpmem, &def_bgr_16);
break;
default:
return -EINVAL;
}
return 0;
}
EXPORT_SYMBOL_GPL(ipu_cpmem_set_fmt);
struct ipuv3_channel *ipu_idmac_get(struct ipu_soc *ipu, unsigned num)
{
struct ipuv3_channel *channel;
dev_dbg(ipu->dev, "%s %d\n", __func__, num);
if (num > 63)
return ERR_PTR(-ENODEV);
mutex_lock(&ipu->channel_lock);
channel = &ipu->channel[num];
if (channel->busy) {
channel = ERR_PTR(-EBUSY);
goto out;
}
channel->busy = true;
channel->num = num;
out:
mutex_unlock(&ipu->channel_lock);
return channel;
}
EXPORT_SYMBOL_GPL(ipu_idmac_get);
void ipu_idmac_put(struct ipuv3_channel *channel)
{
struct ipu_soc *ipu = channel->ipu;
dev_dbg(ipu->dev, "%s %d\n", __func__, channel->num);
mutex_lock(&ipu->channel_lock);
channel->busy = false;
mutex_unlock(&ipu->channel_lock);
}
EXPORT_SYMBOL_GPL(ipu_idmac_put);
#define idma_mask(ch) (1 << (ch & 0x1f))
void ipu_idmac_set_double_buffer(struct ipuv3_channel *channel,
bool doublebuffer)
{
struct ipu_soc *ipu = channel->ipu;
u32 reg;
reg = ipu_cm_read(ipu, IPU_CHA_DB_MODE_SEL(channel->num));
if (doublebuffer)
reg |= idma_mask(channel->num);
else
reg &= ~idma_mask(channel->num);
ipu_cm_write(ipu, reg, IPU_CHA_DB_MODE_SEL(channel->num));
}
EXPORT_SYMBOL_GPL(ipu_idmac_set_double_buffer);
int ipu_module_enable(struct ipu_soc *ipu, u32 mask)
{
u32 val;
val = ipu_cm_read(ipu, IPU_DISP_GEN);
if (mask & IPU_CONF_DI0_EN)
val |= IPU_DI0_COUNTER_RELEASE;
if (mask & IPU_CONF_DI1_EN)
val |= IPU_DI1_COUNTER_RELEASE;
ipu_cm_write(ipu, val, IPU_DISP_GEN);
val = ipu_cm_read(ipu, IPU_CONF);
val |= mask;
ipu_cm_write(ipu, val, IPU_CONF);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_module_enable);
int ipu_module_disable(struct ipu_soc *ipu, u32 mask)
{
u32 val;
val = ipu_cm_read(ipu, IPU_CONF);
val &= ~mask;
ipu_cm_write(ipu, val, IPU_CONF);
val = ipu_cm_read(ipu, IPU_DISP_GEN);
if (mask & IPU_CONF_DI0_EN)
val &= ~IPU_DI0_COUNTER_RELEASE;
if (mask & IPU_CONF_DI1_EN)
val &= ~IPU_DI1_COUNTER_RELEASE;
ipu_cm_write(ipu, val, IPU_DISP_GEN);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_module_disable);
void ipu_idmac_select_buffer(struct ipuv3_channel *channel, u32 buf_num)
{
struct ipu_soc *ipu = channel->ipu;
unsigned int chno = channel->num;
/* Mark buffer as ready. */
if (buf_num == 0)
ipu_cm_write(ipu, idma_mask(chno), IPU_CHA_BUF0_RDY(chno));
else
ipu_cm_write(ipu, idma_mask(chno), IPU_CHA_BUF1_RDY(chno));
}
EXPORT_SYMBOL_GPL(ipu_idmac_select_buffer);
int ipu_idmac_enable_channel(struct ipuv3_channel *channel)
{
struct ipu_soc *ipu = channel->ipu;
u32 val;
val = ipu_idmac_read(ipu, IDMAC_CHA_EN(channel->num));
val |= idma_mask(channel->num);
ipu_idmac_write(ipu, val, IDMAC_CHA_EN(channel->num));
return 0;
}
EXPORT_SYMBOL_GPL(ipu_idmac_enable_channel);
int ipu_idmac_wait_busy(struct ipuv3_channel *channel, int ms)
{
struct ipu_soc *ipu = channel->ipu;
uint64_t start;
start = get_time_ns();
while (ipu_idmac_read(ipu, IDMAC_CHA_BUSY(channel->num)) &
idma_mask(channel->num)) {
if (is_timeout(start, ms * MSECOND))
return -ETIMEDOUT;
}
return 0;
}
EXPORT_SYMBOL_GPL(ipu_idmac_wait_busy);
int ipu_idmac_disable_channel(struct ipuv3_channel *channel)
{
struct ipu_soc *ipu = channel->ipu;
u32 val;
/* Disable DMA channel(s) */
val = ipu_idmac_read(ipu, IDMAC_CHA_EN(channel->num));
val &= ~idma_mask(channel->num);
ipu_idmac_write(ipu, val, IDMAC_CHA_EN(channel->num));
/* Set channel buffers NOT to be ready */
ipu_cm_write(ipu, 0xf0000000, IPU_GPR); /* write one to clear */
if (ipu_cm_read(ipu, IPU_CHA_BUF0_RDY(channel->num)) &
idma_mask(channel->num)) {
ipu_cm_write(ipu, idma_mask(channel->num),
IPU_CHA_BUF0_RDY(channel->num));
}
if (ipu_cm_read(ipu, IPU_CHA_BUF1_RDY(channel->num)) &
idma_mask(channel->num)) {
ipu_cm_write(ipu, idma_mask(channel->num),
IPU_CHA_BUF1_RDY(channel->num));
}
ipu_cm_write(ipu, 0x0, IPU_GPR); /* write one to set */
/* Reset the double buffer */
val = ipu_cm_read(ipu, IPU_CHA_DB_MODE_SEL(channel->num));
val &= ~idma_mask(channel->num);
ipu_cm_write(ipu, val, IPU_CHA_DB_MODE_SEL(channel->num));
return 0;
}
EXPORT_SYMBOL_GPL(ipu_idmac_disable_channel);
static int ipu_memory_reset(struct ipu_soc *ipu)
{
uint64_t start;
ipu_cm_write(ipu, 0x807FFFFF, IPU_MEM_RST);
start = get_time_ns();
while (ipu_cm_read(ipu, IPU_MEM_RST) & 0x80000000) {
if (is_timeout(start, SECOND))
return -ETIMEDOUT;
}
return 0;
}
static int imx6_ipu_reset(struct ipu_soc *ipu)
{
uint32_t val;
int ret;
void __iomem *reg;
reg = (void *)MX6_SRC_BASE_ADDR;
val = ipureadl(reg);
if (ipu->base == (void *)MX6_IPU1_BASE_ADDR)
val |= (1 << 3);
else
val |= (1 << 12);
ipuwritel("reset", val, reg);
ret = wait_on_timeout(100 * MSECOND, !(readl(reg) & (1 << 3)));
return ret;
}
struct ipu_devtype {
const char *name;
unsigned long cm_ofs;
unsigned long cpmem_ofs;
unsigned long srm_ofs;
unsigned long tpm_ofs;
unsigned long disp0_ofs;
unsigned long disp1_ofs;
unsigned long dc_tmpl_ofs;
unsigned long vdi_ofs;
enum ipuv3_type type;
int (*reset)(struct ipu_soc *ipu);
};
static struct ipu_devtype ipu_type_imx51 = {
.name = "IPUv3EX",
.cm_ofs = 0x1e000000,
.cpmem_ofs = 0x1f000000,
.srm_ofs = 0x1f040000,
.tpm_ofs = 0x1f060000,
.disp0_ofs = 0x1e040000,
.disp1_ofs = 0x1e048000,
.dc_tmpl_ofs = 0x1f080000,
.vdi_ofs = 0x1e068000,
.type = IPUV3EX,
};
static struct ipu_devtype ipu_type_imx53 = {
.name = "IPUv3M",
.cm_ofs = 0x06000000,
.cpmem_ofs = 0x07000000,
.srm_ofs = 0x07040000,
.tpm_ofs = 0x07060000,
.disp0_ofs = 0x06040000,
.disp1_ofs = 0x06048000,
.dc_tmpl_ofs = 0x07080000,
.vdi_ofs = 0x06068000,
.type = IPUV3M,
};
static struct ipu_devtype ipu_type_imx6q = {
.name = "IPUv3H",
.cm_ofs = 0x00200000,
.cpmem_ofs = 0x00300000,
.srm_ofs = 0x00340000,
.tpm_ofs = 0x00360000,
.disp0_ofs = 0x00240000,
.disp1_ofs = 0x00248000,
.dc_tmpl_ofs = 0x00380000,
.vdi_ofs = 0x00268000,
.type = IPUV3H,
.reset = imx6_ipu_reset,
};
static struct of_device_id imx_ipu_dt_ids[] = {
{ .compatible = "fsl,imx51-ipu", .data = (unsigned long)&ipu_type_imx51, },
{ .compatible = "fsl,imx53-ipu", .data = (unsigned long)&ipu_type_imx53, },
{ .compatible = "fsl,imx6q-ipu", .data = (unsigned long)&ipu_type_imx6q, },
{ /* sentinel */ }
};
static int ipu_submodules_init(struct ipu_soc *ipu,
struct device_d *dev, void __iomem *ipu_base,
struct clk *ipu_clk)
{
char *unit;
int ret;
const struct ipu_devtype *devtype = ipu->devtype;
ret = ipu_di_init(ipu, dev, 0, ipu_base + devtype->disp0_ofs,
IPU_CONF_DI0_EN, ipu_clk);
if (ret) {
unit = "di0";
goto err_di_0;
}
ret = ipu_di_init(ipu, dev, 1, ipu_base + devtype->disp1_ofs,
IPU_CONF_DI1_EN, ipu_clk);
if (ret) {
unit = "di1";
goto err_di_1;
}
ret = ipu_dc_init(ipu, dev, ipu_base + devtype->cm_ofs +
IPU_CM_DC_REG_OFS, ipu_base + devtype->dc_tmpl_ofs);
if (ret) {
unit = "dc_template";
goto err_dc;
}
ret = ipu_dmfc_init(ipu, dev, ipu_base +
devtype->cm_ofs + IPU_CM_DMFC_REG_OFS, ipu_clk);
if (ret) {
unit = "dmfc";
goto err_dmfc;
}
ret = ipu_dp_init(ipu, dev, ipu_base + devtype->srm_ofs);
if (ret) {
unit = "dp";
goto err_dp;
}
return 0;
err_dp:
ipu_dmfc_exit(ipu);
err_dmfc:
ipu_dc_exit(ipu);
err_dc:
ipu_di_exit(ipu, 1);
err_di_1:
ipu_di_exit(ipu, 0);
err_di_0:
dev_err(dev, "init %s failed with %d\n", unit, ret);
return ret;
}
static void ipu_submodules_exit(struct ipu_soc *ipu)
{
ipu_dp_exit(ipu);
ipu_dmfc_exit(ipu);
ipu_dc_exit(ipu);
ipu_di_exit(ipu, 1);
ipu_di_exit(ipu, 0);
}
struct ipu_platform_reg {
struct ipu_client_platformdata pdata;
const char *name;
};
static struct ipu_platform_reg client_reg[] = {
{
.pdata = {
.di = 0,
.dc = 5,
.dp = IPU_DP_FLOW_SYNC_BG,
.dma[0] = IPUV3_CHANNEL_MEM_BG_SYNC,
.dma[1] = IPUV3_CHANNEL_MEM_FG_SYNC,
},
.name = "imx-ipuv3-crtc",
}, {
.pdata = {
.di = 1,
.dc = 1,
.dp = -EINVAL,
.dma[0] = IPUV3_CHANNEL_MEM_DC_SYNC,
.dma[1] = -EINVAL,
},
.name = "imx-ipuv3-crtc",
},
};
static int ipu_client_id;
static int ipu_add_subdevice_pdata(struct device_d *ipu_dev,
struct ipu_platform_reg *reg)
{
struct device_d *dev;
int ret;
dev = device_alloc(reg->name, ipu_client_id++);
dev->parent = ipu_dev;
device_add_data(dev, &reg->pdata, sizeof(reg->pdata));
((struct ipu_client_platformdata *)dev->platform_data)->device_node = ipu_dev->device_node;
ret = platform_device_register(dev);
return ret;
}
static int ipu_add_client_devices(struct ipu_soc *ipu)
{
int ret;
int i;
for (i = 0; i < ARRAY_SIZE(client_reg); i++) {
struct ipu_platform_reg *reg = &client_reg[i];
ret = ipu_add_subdevice_pdata(ipu->dev, reg);
if (ret)
goto err_register;
}
return 0;
err_register:
return ret;
}
static int ipu_probe(struct device_d *dev)
{
struct ipu_soc *ipu;
void __iomem *ipu_base;
int i, ret;
const struct ipu_devtype *devtype;
ret = dev_get_drvdata(dev, (unsigned long *)&devtype);
if (ret)
return ret;
ipu_base = dev_request_mem_region(dev, 0);
if (!ipu_base)
return -EBUSY;
ipu = xzalloc(sizeof(*ipu));
ipu->base = ipu_base;
for (i = 0; i < 64; i++)
ipu->channel[i].ipu = ipu;
ipu->devtype = devtype;
ipu->ipu_type = devtype->type;
dev_dbg(dev, "cm_reg: 0x%p\n",
ipu_base + devtype->cm_ofs);
dev_dbg(dev, "idmac: 0x%p\n",
ipu_base + devtype->cm_ofs + IPU_CM_IDMAC_REG_OFS);
dev_dbg(dev, "cpmem: 0x%p\n",
ipu_base + devtype->cpmem_ofs);
dev_dbg(dev, "disp0: 0x%p\n",
ipu_base + devtype->disp0_ofs);
dev_dbg(dev, "disp1: 0x%p\n",
ipu_base + devtype->disp1_ofs);
dev_dbg(dev, "srm: 0x%p\n",
ipu_base + devtype->srm_ofs);
dev_dbg(dev, "tpm: 0x%p\n",
ipu_base + devtype->tpm_ofs);
dev_dbg(dev, "dc: 0x%p\n",
ipu_base + devtype->cm_ofs + IPU_CM_DC_REG_OFS);
dev_dbg(dev, "ic: 0x%p\n",
ipu_base + devtype->cm_ofs + IPU_CM_IC_REG_OFS);
dev_dbg(dev, "dmfc: 0x%p\n",
ipu_base + devtype->cm_ofs + IPU_CM_DMFC_REG_OFS);
dev_dbg(dev, "vdi: 0x%p\n",
ipu_base + devtype->vdi_ofs);
ipu->cm_reg = ipu_base + devtype->cm_ofs, PAGE_SIZE;
ipu->idmac_reg = ipu_base + devtype->cm_ofs + IPU_CM_IDMAC_REG_OFS;
ipu->cpmem_base = ipu_base + devtype->cpmem_ofs;
ipu->clk = clk_get(dev, "bus");
if (IS_ERR(ipu->clk)) {
ret = PTR_ERR(ipu->clk);
dev_err(dev, "clk_get failed with %d", ret);
return ret;
}
dev->priv = ipu;
ret = clk_enable(ipu->clk);
if (ret)
return ret;
ipu->dev = dev;
ret = devtype->reset(ipu);
if (ret) {
dev_err(dev, "failed to reset: %d\n", ret);
goto out_failed_reset;
}
ret = ipu_memory_reset(ipu);
if (ret)
goto out_failed_reset;
/* Set MCU_T to divide MCU access window into 2 */
ipu_cm_write(ipu, 0x00400000L | (IPU_MCU_T_DEFAULT << 18),
IPU_DISP_GEN);
ret = ipu_submodules_init(ipu, dev, ipu_base, ipu->clk);
if (ret)
goto failed_submodules_init;
ret = ipu_add_client_devices(ipu);
if (ret) {
dev_err(dev, "adding client devices failed with %d\n",
ret);
goto failed_add_clients;
}
dev_info(dev, "%s probed\n", devtype->name);
return 0;
failed_add_clients:
ipu_submodules_exit(ipu);
failed_submodules_init:
out_failed_reset:
clk_disable(ipu->clk);
return ret;
}
static struct driver_d imx_ipu_driver = {
.name = "imx-ipuv3",
.of_compatible = imx_ipu_dt_ids,
.probe = ipu_probe,
};
device_platform_driver(imx_ipu_driver);
MODULE_DESCRIPTION("i.MX IPU v3 driver");
MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,392 @@
/*
* Copyright (c) 2010 Sascha Hauer <s.hauer@pengutronix.de>
* Copyright (C) 2005-2009 Freescale Semiconductor, Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include <common.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <malloc.h>
#include "imx-ipu-v3.h"
#include "ipu-prv.h"
#define DC_MAP_CONF_PTR(n) (0x108 + ((n) & ~0x1) * 2)
#define DC_MAP_CONF_VAL(n) (0x144 + ((n) & ~0x1) * 2)
#define DC_EVT_NF 0
#define DC_EVT_NL 1
#define DC_EVT_EOF 2
#define DC_EVT_NFIELD 3
#define DC_EVT_EOL 4
#define DC_EVT_EOFIELD 5
#define DC_EVT_NEW_ADDR 6
#define DC_EVT_NEW_CHAN 7
#define DC_EVT_NEW_DATA 8
#define DC_EVT_NEW_ADDR_W_0 0
#define DC_EVT_NEW_ADDR_W_1 1
#define DC_EVT_NEW_CHAN_W_0 2
#define DC_EVT_NEW_CHAN_W_1 3
#define DC_EVT_NEW_DATA_W_0 4
#define DC_EVT_NEW_DATA_W_1 5
#define DC_EVT_NEW_ADDR_R_0 6
#define DC_EVT_NEW_ADDR_R_1 7
#define DC_EVT_NEW_CHAN_R_0 8
#define DC_EVT_NEW_CHAN_R_1 9
#define DC_EVT_NEW_DATA_R_0 10
#define DC_EVT_NEW_DATA_R_1 11
#define DC_WR_CH_CONF 0x0
#define DC_WR_CH_ADDR 0x4
#define DC_RL_CH(evt) (8 + ((evt) & ~0x1) * 2)
#define DC_GEN 0xd4
#define DC_DISP_CONF1(disp) (0xd8 + (disp) * 4)
#define DC_DISP_CONF2(disp) (0xe8 + (disp) * 4)
#define DC_STAT 0x1c8
#define WROD(lf) (0x18 | ((lf) << 1))
#define WRG 0x01
#define WCLK 0xc9
#define SYNC_WAVE 0
#define NULL_WAVE (-1)
#define DC_GEN_SYNC_1_6_SYNC (2 << 1)
#define DC_GEN_SYNC_PRIORITY_1 (1 << 7)
#define DC_WR_CH_CONF_WORD_SIZE_8 (0 << 0)
#define DC_WR_CH_CONF_WORD_SIZE_16 (1 << 0)
#define DC_WR_CH_CONF_WORD_SIZE_24 (2 << 0)
#define DC_WR_CH_CONF_WORD_SIZE_32 (3 << 0)
#define DC_WR_CH_CONF_DISP_ID_PARALLEL(i) (((i) & 0x1) << 3)
#define DC_WR_CH_CONF_DISP_ID_SERIAL (2 << 3)
#define DC_WR_CH_CONF_DISP_ID_ASYNC (3 << 4)
#define DC_WR_CH_CONF_FIELD_MODE (1 << 9)
#define DC_WR_CH_CONF_PROG_TYPE_NORMAL (4 << 5)
#define DC_WR_CH_CONF_PROG_TYPE_MASK (7 << 5)
#define DC_WR_CH_CONF_PROG_DI_ID (1 << 2)
#define DC_WR_CH_CONF_PROG_DISP_ID(i) (((i) & 0x1) << 3)
#define IPU_DC_NUM_CHANNELS 10
struct ipu_dc_priv;
enum ipu_dc_map {
IPU_DC_MAP_RGB24,
IPU_DC_MAP_RGB565,
IPU_DC_MAP_GBR24, /* TVEv2 */
IPU_DC_MAP_BGR666,
IPU_DC_MAP_BGR24,
};
struct ipu_dc {
/* The display interface number assigned to this dc channel */
unsigned int di;
void __iomem *base;
struct ipu_dc_priv *priv;
int chno;
bool in_use;
};
struct ipu_dc_priv {
void __iomem *dc_reg;
void __iomem *dc_tmpl_reg;
struct ipu_soc *ipu;
struct device_d *dev;
struct ipu_dc channels[IPU_DC_NUM_CHANNELS];
};
static void dc_link_event(struct ipu_dc *dc, int event, int addr, int priority)
{
u32 reg;
reg = ipureadl(dc->base + DC_RL_CH(event));
reg &= ~(0xffff << (16 * (event & 0x1)));
reg |= ((addr << 8) | priority) << (16 * (event & 0x1));
ipuwritel("dc", reg, dc->base + DC_RL_CH(event));
}
static void dc_write_tmpl(struct ipu_dc *dc, int word, u32 opcode, u32 operand,
int map, int wave, int glue, int sync, int stop)
{
struct ipu_dc_priv *priv = dc->priv;
u32 reg1, reg2;
if (opcode == WCLK) {
reg1 = (operand << 20) & 0xfff00000;
reg2 = operand >> 12 | opcode << 1 | stop << 9;
} else if (opcode == WRG) {
reg1 = sync | glue << 4 | ++wave << 11 | ((operand << 15) & 0xffff8000);
reg2 = operand >> 17 | opcode << 7 | stop << 9;
} else {
reg1 = sync | glue << 4 | ++wave << 11 | ++map << 15 | ((operand << 20) & 0xfff00000);
reg2 = operand >> 12 | opcode << 4 | stop << 9;
}
ipuwritel("dc", reg1, priv->dc_tmpl_reg + word * 8);
ipuwritel("dc", reg2, priv->dc_tmpl_reg + word * 8 + 4);
}
static int ipu_pixfmt_to_map(u32 fmt)
{
switch (fmt) {
case V4L2_PIX_FMT_RGB24:
return IPU_DC_MAP_RGB24;
case V4L2_PIX_FMT_RGB565:
return IPU_DC_MAP_RGB565;
case IPU_PIX_FMT_GBR24:
return IPU_DC_MAP_GBR24;
case V4L2_PIX_FMT_BGR666:
return IPU_DC_MAP_BGR666;
case V4L2_PIX_FMT_BGR24:
return IPU_DC_MAP_BGR24;
default:
return -EINVAL;
}
}
int ipu_dc_init_sync(struct ipu_dc *dc, struct ipu_di *di, bool interlaced,
u32 pixel_fmt, u32 width)
{
struct ipu_dc_priv *priv = dc->priv;
u32 reg = 0;
int map;
dc->di = ipu_di_get_num(di);
map = ipu_pixfmt_to_map(pixel_fmt);
if (map < 0) {
dev_dbg(priv->dev, "IPU_DISP: No MAP\n");
return map;
}
if (interlaced) {
dc_link_event(dc, DC_EVT_NL, 0, 3);
dc_link_event(dc, DC_EVT_EOL, 0, 2);
dc_link_event(dc, DC_EVT_NEW_DATA, 0, 1);
/* Init template microcode */
dc_write_tmpl(dc, 0, WROD(0), 0, map, SYNC_WAVE, 0, 8, 1);
} else {
if (dc->di) {
dc_link_event(dc, DC_EVT_NL, 2, 3);
dc_link_event(dc, DC_EVT_EOL, 3, 2);
dc_link_event(dc, DC_EVT_NEW_DATA, 1, 1);
/* Init template microcode */
dc_write_tmpl(dc, 2, WROD(0), 0, map, SYNC_WAVE, 8, 5, 1);
dc_write_tmpl(dc, 3, WROD(0), 0, map, SYNC_WAVE, 4, 5, 0);
dc_write_tmpl(dc, 4, WRG, 0, map, NULL_WAVE, 0, 0, 1);
dc_write_tmpl(dc, 1, WROD(0), 0, map, SYNC_WAVE, 0, 5, 1);
} else {
dc_link_event(dc, DC_EVT_NL, 5, 3);
dc_link_event(dc, DC_EVT_EOL, 6, 2);
dc_link_event(dc, DC_EVT_NEW_DATA, 8, 1);
/* Init template microcode */
dc_write_tmpl(dc, 5, WROD(0), 0, map, SYNC_WAVE, 8, 5, 1);
dc_write_tmpl(dc, 6, WROD(0), 0, map, SYNC_WAVE, 4, 5, 0);
dc_write_tmpl(dc, 7, WRG, 0, map, NULL_WAVE, 0, 0, 1);
dc_write_tmpl(dc, 8, WROD(0), 0, map, SYNC_WAVE, 0, 5, 1);
}
}
dc_link_event(dc, DC_EVT_NF, 0, 0);
dc_link_event(dc, DC_EVT_NFIELD, 0, 0);
dc_link_event(dc, DC_EVT_EOF, 0, 0);
dc_link_event(dc, DC_EVT_EOFIELD, 0, 0);
dc_link_event(dc, DC_EVT_NEW_CHAN, 0, 0);
dc_link_event(dc, DC_EVT_NEW_ADDR, 0, 0);
reg = ipureadl(dc->base + DC_WR_CH_CONF);
if (interlaced)
reg |= DC_WR_CH_CONF_FIELD_MODE;
else
reg &= ~DC_WR_CH_CONF_FIELD_MODE;
ipuwritel("dc", reg, dc->base + DC_WR_CH_CONF);
ipuwritel("dc", 0x0, dc->base + DC_WR_CH_ADDR);
ipuwritel("dc", width, priv->dc_reg + DC_DISP_CONF2(dc->di));
ipu_module_enable(priv->ipu, IPU_CONF_DC_EN);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_dc_init_sync);
void ipu_dc_enable_channel(struct ipu_dc *dc)
{
int di;
u32 reg;
di = dc->di;
reg = ipureadl(dc->base + DC_WR_CH_CONF);
reg |= DC_WR_CH_CONF_PROG_TYPE_NORMAL;
ipuwritel("dc", reg, dc->base + DC_WR_CH_CONF);
}
EXPORT_SYMBOL_GPL(ipu_dc_enable_channel);
void ipu_dc_disable_channel(struct ipu_dc *dc)
{
struct ipu_dc_priv *priv = dc->priv;
u32 val;
int irq = 0, timeout = 50;
if (dc->chno == 1)
irq = IPU_IRQ_DC_FC_1;
else if (dc->chno == 5)
irq = IPU_IRQ_DP_SF_END;
else
return;
/* should wait for the interrupt here */
mdelay(50);
if (dc->di == 0)
val = 0x00000002;
else
val = 0x00000020;
/* Wait for DC triple buffer to empty */
while ((ipureadl(priv->dc_reg + DC_STAT) & val) != val) {
mdelay(2);
timeout -= 2;
if (timeout <= 0)
break;
}
val = ipureadl(dc->base + DC_WR_CH_CONF);
val &= ~DC_WR_CH_CONF_PROG_TYPE_MASK;
ipuwritel("dc", val, dc->base + DC_WR_CH_CONF);
}
EXPORT_SYMBOL_GPL(ipu_dc_disable_channel);
static void ipu_dc_map_config(struct ipu_dc_priv *priv, enum ipu_dc_map map,
int byte_num, int offset, int mask)
{
int ptr = map * 3 + byte_num;
u32 reg;
reg = ipureadl(priv->dc_reg + DC_MAP_CONF_VAL(ptr));
reg &= ~(0xffff << (16 * (ptr & 0x1)));
reg |= ((offset << 8) | mask) << (16 * (ptr & 0x1));
ipuwritel("dc", reg, priv->dc_reg + DC_MAP_CONF_VAL(ptr));
reg = ipureadl(priv->dc_reg + DC_MAP_CONF_PTR(map));
reg &= ~(0x1f << ((16 * (map & 0x1)) + (5 * byte_num)));
reg |= ptr << ((16 * (map & 0x1)) + (5 * byte_num));
ipuwritel("dc", reg, priv->dc_reg + DC_MAP_CONF_PTR(map));
}
static void ipu_dc_map_clear(struct ipu_dc_priv *priv, int map)
{
u32 reg = ipureadl(priv->dc_reg + DC_MAP_CONF_PTR(map));
ipuwritel("dc", reg & ~(0xffff << (16 * (map & 0x1))),
priv->dc_reg + DC_MAP_CONF_PTR(map));
}
struct ipu_dc *ipu_dc_get(struct ipu_soc *ipu, int channel)
{
struct ipu_dc_priv *priv = ipu->dc_priv;
struct ipu_dc *dc;
if (channel >= IPU_DC_NUM_CHANNELS)
return ERR_PTR(-ENODEV);
dc = &priv->channels[channel];
if (dc->in_use)
return ERR_PTR(-EBUSY);
dc->in_use = true;
return dc;
}
EXPORT_SYMBOL_GPL(ipu_dc_get);
void ipu_dc_put(struct ipu_dc *dc)
{
dc->in_use = false;
}
EXPORT_SYMBOL_GPL(ipu_dc_put);
int ipu_dc_init(struct ipu_soc *ipu, struct device_d *dev,
void __iomem *base, void __iomem *template_base)
{
struct ipu_dc_priv *priv;
static int channel_offsets[] = { 0, 0x1c, 0x38, 0x54, 0x58, 0x5c,
0x78, 0, 0x94, 0xb4};
int i;
priv = xzalloc(sizeof(*priv));
priv->dev = dev;
priv->ipu = ipu;
priv->dc_reg = base;
priv->dc_tmpl_reg = template_base;
for (i = 0; i < IPU_DC_NUM_CHANNELS; i++) {
priv->channels[i].chno = i;
priv->channels[i].priv = priv;
priv->channels[i].base = priv->dc_reg + channel_offsets[i];
}
ipuwritel("dc", DC_WR_CH_CONF_WORD_SIZE_24 | DC_WR_CH_CONF_DISP_ID_PARALLEL(1) |
DC_WR_CH_CONF_PROG_DI_ID,
priv->channels[1].base + DC_WR_CH_CONF);
ipuwritel("dc", DC_WR_CH_CONF_WORD_SIZE_24 | DC_WR_CH_CONF_DISP_ID_PARALLEL(0),
priv->channels[5].base + DC_WR_CH_CONF);
ipuwritel("dc", DC_GEN_SYNC_1_6_SYNC | DC_GEN_SYNC_PRIORITY_1, priv->dc_reg + DC_GEN);
ipu->dc_priv = priv;
dev_dbg(dev, "DC base: 0x%p template base: 0x%p\n",
base, template_base);
/* rgb24 */
ipu_dc_map_clear(priv, IPU_DC_MAP_RGB24);
ipu_dc_map_config(priv, IPU_DC_MAP_RGB24, 0, 7, 0xff); /* blue */
ipu_dc_map_config(priv, IPU_DC_MAP_RGB24, 1, 15, 0xff); /* green */
ipu_dc_map_config(priv, IPU_DC_MAP_RGB24, 2, 23, 0xff); /* red */
/* rgb565 */
ipu_dc_map_clear(priv, IPU_DC_MAP_RGB565);
ipu_dc_map_config(priv, IPU_DC_MAP_RGB565, 0, 4, 0xf8); /* blue */
ipu_dc_map_config(priv, IPU_DC_MAP_RGB565, 1, 10, 0xfc); /* green */
ipu_dc_map_config(priv, IPU_DC_MAP_RGB565, 2, 15, 0xf8); /* red */
/* gbr24 */
ipu_dc_map_clear(priv, IPU_DC_MAP_GBR24);
ipu_dc_map_config(priv, IPU_DC_MAP_GBR24, 2, 15, 0xff); /* green */
ipu_dc_map_config(priv, IPU_DC_MAP_GBR24, 1, 7, 0xff); /* blue */
ipu_dc_map_config(priv, IPU_DC_MAP_GBR24, 0, 23, 0xff); /* red */
/* bgr666 */
ipu_dc_map_clear(priv, IPU_DC_MAP_BGR666);
ipu_dc_map_config(priv, IPU_DC_MAP_BGR666, 0, 5, 0xfc); /* blue */
ipu_dc_map_config(priv, IPU_DC_MAP_BGR666, 1, 11, 0xfc); /* green */
ipu_dc_map_config(priv, IPU_DC_MAP_BGR666, 2, 17, 0xfc); /* red */
/* bgr24 */
ipu_dc_map_clear(priv, IPU_DC_MAP_BGR24);
ipu_dc_map_config(priv, IPU_DC_MAP_BGR24, 2, 7, 0xff); /* red */
ipu_dc_map_config(priv, IPU_DC_MAP_BGR24, 1, 15, 0xff); /* green */
ipu_dc_map_config(priv, IPU_DC_MAP_BGR24, 0, 23, 0xff); /* blue */
return 0;
}
void ipu_dc_exit(struct ipu_soc *ipu)
{
}

View File

@ -0,0 +1,762 @@
/*
* Copyright (c) 2010 Sascha Hauer <s.hauer@pengutronix.de>
* Copyright (C) 2005-2009 Freescale Semiconductor, Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include <common.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <asm-generic/div64.h>
#include <malloc.h>
#include "imx-ipu-v3.h"
#include "ipu-prv.h"
struct ipu_di {
void __iomem *base;
int id;
u32 module;
struct clk *clk_di; /* display input clock */
struct clk *clk_ipu; /* IPU bus clock */
struct clk clk_di_pixel; /* resulting pixel clock */
char *clk_name;
const char *di_parent_names[2];
bool inuse;
unsigned long clkflags;
struct ipu_soc *ipu;
};
struct di_sync_config {
int run_count;
int run_src;
int offset_count;
int offset_src;
int repeat_count;
int cnt_clr_src;
int cnt_polarity_gen_en;
int cnt_polarity_clr_src;
int cnt_polarity_trigger_src;
int cnt_up;
int cnt_down;
};
enum di_pins {
DI_PIN11 = 0,
DI_PIN12 = 1,
DI_PIN13 = 2,
DI_PIN14 = 3,
DI_PIN15 = 4,
DI_PIN16 = 5,
DI_PIN17 = 6,
DI_PIN_CS = 7,
DI_PIN_SER_CLK = 0,
DI_PIN_SER_RS = 1,
};
enum di_sync_wave {
DI_SYNC_NONE = 0,
DI_SYNC_CLK = 1,
DI_SYNC_INT_HSYNC = 2,
DI_SYNC_HSYNC = 3,
DI_SYNC_VSYNC = 4,
DI_SYNC_DE = 6,
};
#define SYNC_WAVE 0
#define DI_GENERAL 0x0000
#define DI_BS_CLKGEN0 0x0004
#define DI_BS_CLKGEN1 0x0008
#define DI_SW_GEN0(gen) (0x000c + 4 * ((gen) - 1))
#define DI_SW_GEN1(gen) (0x0030 + 4 * ((gen) - 1))
#define DI_STP_REP(gen) (0x0148 + 4 * (((gen) - 1)/2))
#define DI_SYNC_AS_GEN 0x0054
#define DI_DW_GEN(gen) (0x0058 + 4 * (gen))
#define DI_DW_SET(gen, set) (0x0088 + 4 * ((gen) + 0xc * (set)))
#define DI_SER_CONF 0x015c
#define DI_SSC 0x0160
#define DI_POL 0x0164
#define DI_AW0 0x0168
#define DI_AW1 0x016c
#define DI_SCR_CONF 0x0170
#define DI_STAT 0x0174
#define DI_SW_GEN0_RUN_COUNT(x) ((x) << 19)
#define DI_SW_GEN0_RUN_SRC(x) ((x) << 16)
#define DI_SW_GEN0_OFFSET_COUNT(x) ((x) << 3)
#define DI_SW_GEN0_OFFSET_SRC(x) ((x) << 0)
#define DI_SW_GEN1_CNT_POL_GEN_EN(x) ((x) << 29)
#define DI_SW_GEN1_CNT_CLR_SRC(x) ((x) << 25)
#define DI_SW_GEN1_CNT_POL_TRIGGER_SRC(x) ((x) << 12)
#define DI_SW_GEN1_CNT_POL_CLR_SRC(x) ((x) << 9)
#define DI_SW_GEN1_CNT_DOWN(x) ((x) << 16)
#define DI_SW_GEN1_CNT_UP(x) (x)
#define DI_SW_GEN1_AUTO_RELOAD (0x10000000)
#define DI_DW_GEN_ACCESS_SIZE_OFFSET 24
#define DI_DW_GEN_COMPONENT_SIZE_OFFSET 16
#define DI_GEN_POLARITY_1 (1 << 0)
#define DI_GEN_POLARITY_2 (1 << 1)
#define DI_GEN_POLARITY_3 (1 << 2)
#define DI_GEN_POLARITY_4 (1 << 3)
#define DI_GEN_POLARITY_5 (1 << 4)
#define DI_GEN_POLARITY_6 (1 << 5)
#define DI_GEN_POLARITY_7 (1 << 6)
#define DI_GEN_POLARITY_8 (1 << 7)
#define DI_GEN_POLARITY_DISP_CLK (1 << 17)
#define DI_GEN_DI_CLK_EXT (1 << 20)
#define DI_GEN_DI_VSYNC_EXT (1 << 21)
#define DI_POL_DRDY_DATA_POLARITY (1 << 7)
#define DI_POL_DRDY_POLARITY_15 (1 << 4)
#define DI_VSYNC_SEL_OFFSET 13
static inline u32 ipu_di_read(struct ipu_di *di, unsigned offset)
{
return readl(di->base + offset);
}
static inline void ipu_di_write(struct ipu_di *di, u32 value, unsigned offset)
{
ipuwritel("di", value, di->base + offset);
}
static int ipu_di_clk_calc_div(unsigned long inrate, unsigned long outrate)
{
u64 tmp = inrate;
int div;
tmp *= 16;
do_div(tmp, outrate);
div = tmp;
if (div < 0x10)
div = 0x10;
#ifdef WTF_IS_THIS
/*
* Freescale has this in their Kernel. It is neither clear what
* it does nor why it does it
*/
if (div & 0x10)
div &= ~0x7;
else {
/* Round up divider if it gets us closer to desired pix clk */
if ((div & 0xC) == 0xC) {
div += 0x10;
div &= ~0xF;
}
}
#endif
return div;
}
static unsigned long clk_di_recalc_rate(struct clk *clk,
unsigned long parent_rate)
{
struct ipu_di *di = container_of(clk, struct ipu_di, clk_di_pixel);
unsigned long outrate;
u32 div = ipu_di_read(di, DI_BS_CLKGEN0);
if (div < 0x10)
div = 0x10;
outrate = (parent_rate / div) * 16;
return outrate;
}
static long clk_di_round_rate(struct clk *clk, unsigned long rate,
unsigned long *prate)
{
struct ipu_di *di = container_of(clk, struct ipu_di, clk_di_pixel);
unsigned long outrate;
int div;
u32 val;
div = ipu_di_clk_calc_div(*prate, rate);
outrate = (*prate / div) * 16;
val = ipu_di_read(di, DI_GENERAL);
if (!(val & DI_GEN_DI_CLK_EXT) && outrate > *prate / 2)
outrate = *prate / 2;
dev_dbg(di->ipu->dev,
"%s: inrate: %ld div: 0x%08x outrate: %ld wanted: %ld\n",
__func__, *prate, div, outrate, rate);
return outrate;
}
static int clk_di_set_rate(struct clk *clk, unsigned long rate,
unsigned long parent_rate)
{
struct ipu_di *di = container_of(clk, struct ipu_di, clk_di_pixel);
int div;
u32 clkgen0;
clkgen0 = ipu_di_read(di, DI_BS_CLKGEN0) & ~0xfff;
div = ipu_di_clk_calc_div(parent_rate, rate);
ipu_di_write(di, clkgen0 | div, DI_BS_CLKGEN0);
dev_dbg(di->ipu->dev, "%s: inrate: %ld desired: %ld div: 0x%08x\n",
__func__, parent_rate, rate, div);
return 0;
}
static int clk_di_get_parent(struct clk *clk)
{
struct ipu_di *di = container_of(clk, struct ipu_di, clk_di_pixel);
u32 val;
val = ipu_di_read(di, DI_GENERAL);
return val & DI_GEN_DI_CLK_EXT ? 1 : 0;
}
static int clk_di_set_parent(struct clk *clk, u8 index)
{
struct ipu_di *di = container_of(clk, struct ipu_di, clk_di_pixel);
u32 val;
val = ipu_di_read(di, DI_GENERAL);
if (index)
val |= DI_GEN_DI_CLK_EXT;
else
val &= ~DI_GEN_DI_CLK_EXT;
ipu_di_write(di, val, DI_GENERAL);
return 0;
}
static struct clk_ops clk_di_ops = {
.round_rate = clk_di_round_rate,
.set_rate = clk_di_set_rate,
.recalc_rate = clk_di_recalc_rate,
.set_parent = clk_di_set_parent,
.get_parent = clk_di_get_parent,
};
static void ipu_di_data_wave_config(struct ipu_di *di,
int wave_gen,
int access_size, int component_size)
{
u32 reg;
reg = (access_size << DI_DW_GEN_ACCESS_SIZE_OFFSET) |
(component_size << DI_DW_GEN_COMPONENT_SIZE_OFFSET);
ipu_di_write(di, reg, DI_DW_GEN(wave_gen));
}
static void ipu_di_data_pin_config(struct ipu_di *di, int wave_gen, int di_pin,
int set, int up, int down)
{
u32 reg;
reg = ipu_di_read(di, DI_DW_GEN(wave_gen));
reg &= ~(0x3 << (di_pin * 2));
reg |= set << (di_pin * 2);
ipu_di_write(di, reg, DI_DW_GEN(wave_gen));
ipu_di_write(di, (down << 16) | up, DI_DW_SET(wave_gen, set));
}
static void ipu_di_sync_config(struct ipu_di *di, struct di_sync_config *config,
int start, int count)
{
u32 reg;
int i;
for (i = 0; i < count; i++) {
struct di_sync_config *c = &config[i];
int wave_gen = start + i + 1;
if ((c->run_count >= 0x1000) || (c->offset_count >= 0x1000) ||
(c->repeat_count >= 0x1000) ||
(c->cnt_up >= 0x400) ||
(c->cnt_down >= 0x400)) {
dev_err(di->ipu->dev, "DI%d counters out of range.\n",
di->id);
return;
}
reg = DI_SW_GEN0_RUN_COUNT(c->run_count) |
DI_SW_GEN0_RUN_SRC(c->run_src) |
DI_SW_GEN0_OFFSET_COUNT(c->offset_count) |
DI_SW_GEN0_OFFSET_SRC(c->offset_src);
ipu_di_write(di, reg, DI_SW_GEN0(wave_gen));
reg = DI_SW_GEN1_CNT_POL_GEN_EN(c->cnt_polarity_gen_en) |
DI_SW_GEN1_CNT_CLR_SRC(c->cnt_clr_src) |
DI_SW_GEN1_CNT_POL_TRIGGER_SRC(
c->cnt_polarity_trigger_src) |
DI_SW_GEN1_CNT_POL_CLR_SRC(c->cnt_polarity_clr_src) |
DI_SW_GEN1_CNT_DOWN(c->cnt_down) |
DI_SW_GEN1_CNT_UP(c->cnt_up);
/* Enable auto reload */
if (c->repeat_count == 0)
reg |= DI_SW_GEN1_AUTO_RELOAD;
ipu_di_write(di, reg, DI_SW_GEN1(wave_gen));
reg = ipu_di_read(di, DI_STP_REP(wave_gen));
reg &= ~(0xffff << (16 * ((wave_gen - 1) & 0x1)));
reg |= c->repeat_count << (16 * ((wave_gen - 1) & 0x1));
ipu_di_write(di, reg, DI_STP_REP(wave_gen));
}
}
static void ipu_di_sync_config_interlaced(struct ipu_di *di,
struct ipu_di_signal_cfg *sig)
{
u32 h_total = sig->width + sig->h_sync_width +
sig->h_start_width + sig->h_end_width;
u32 v_total = sig->height + sig->v_sync_width +
sig->v_start_width + sig->v_end_width;
u32 reg;
struct di_sync_config cfg[] = {
{
.run_count = h_total / 2 - 1,
.run_src = DI_SYNC_CLK,
}, {
.run_count = h_total - 11,
.run_src = DI_SYNC_CLK,
.cnt_down = 4,
}, {
.run_count = v_total * 2 - 1,
.run_src = DI_SYNC_INT_HSYNC,
.offset_count = 1,
.offset_src = DI_SYNC_INT_HSYNC,
.cnt_down = 4,
}, {
.run_count = v_total / 2 - 1,
.run_src = DI_SYNC_HSYNC,
.offset_count = sig->v_start_width,
.offset_src = DI_SYNC_HSYNC,
.repeat_count = 2,
.cnt_clr_src = DI_SYNC_VSYNC,
}, {
.run_src = DI_SYNC_HSYNC,
.repeat_count = sig->height / 2,
.cnt_clr_src = 4,
}, {
.run_count = v_total - 1,
.run_src = DI_SYNC_HSYNC,
}, {
.run_count = v_total / 2 - 1,
.run_src = DI_SYNC_HSYNC,
.offset_count = 9,
.offset_src = DI_SYNC_HSYNC,
.repeat_count = 2,
.cnt_clr_src = DI_SYNC_VSYNC,
}, {
.run_src = DI_SYNC_CLK,
.offset_count = sig->h_start_width,
.offset_src = DI_SYNC_CLK,
.repeat_count = sig->width,
.cnt_clr_src = 5,
}, {
.run_count = v_total - 1,
.run_src = DI_SYNC_INT_HSYNC,
.offset_count = v_total / 2,
.offset_src = DI_SYNC_INT_HSYNC,
.cnt_clr_src = DI_SYNC_HSYNC,
.cnt_down = 4,
}
};
ipu_di_sync_config(di, cfg, 0, ARRAY_SIZE(cfg));
/* set gentime select and tag sel */
reg = ipu_di_read(di, DI_SW_GEN1(9));
reg &= 0x1FFFFFFF;
reg |= (3 - 1) << 29 | 0x00008000;
ipu_di_write(di, reg, DI_SW_GEN1(9));
ipu_di_write(di, v_total / 2 - 1, DI_SCR_CONF);
}
static void ipu_di_sync_config_noninterlaced(struct ipu_di *di,
struct ipu_di_signal_cfg *sig, int div)
{
u32 h_total = sig->width + sig->h_sync_width + sig->h_start_width +
sig->h_end_width;
u32 v_total = sig->height + sig->v_sync_width + sig->v_start_width +
sig->v_end_width;
struct di_sync_config cfg[] = {
{
/* 1: INT_HSYNC */
.run_count = h_total - 1,
.run_src = DI_SYNC_CLK,
} , {
/* PIN2: HSYNC */
.run_count = h_total - 1,
.run_src = DI_SYNC_CLK,
.offset_count = div * sig->v_to_h_sync,
.offset_src = DI_SYNC_CLK,
.cnt_polarity_gen_en = 1,
.cnt_polarity_trigger_src = DI_SYNC_CLK,
.cnt_down = sig->h_sync_width * 2,
} , {
/* PIN3: VSYNC */
.run_count = v_total - 1,
.run_src = DI_SYNC_INT_HSYNC,
.cnt_polarity_gen_en = 1,
.cnt_polarity_trigger_src = DI_SYNC_INT_HSYNC,
.cnt_down = sig->v_sync_width * 2,
} , {
/* 4: Line Active */
.run_src = DI_SYNC_HSYNC,
.offset_count = sig->v_sync_width + sig->v_start_width,
.offset_src = DI_SYNC_HSYNC,
.repeat_count = sig->height,
.cnt_clr_src = DI_SYNC_VSYNC,
} , {
/* 5: Pixel Active, referenced by DC */
.run_src = DI_SYNC_CLK,
.offset_count = sig->h_sync_width + sig->h_start_width,
.offset_src = DI_SYNC_CLK,
.repeat_count = sig->width,
.cnt_clr_src = 5, /* Line Active */
} , {
/* unused */
} , {
/* unused */
} , {
/* unused */
} , {
/* unused */
},
};
/* can't use #7 and #8 for line active and pixel active counters */
struct di_sync_config cfg_vga[] = {
{
/* 1: INT_HSYNC */
.run_count = h_total - 1,
.run_src = DI_SYNC_CLK,
} , {
/* 2: VSYNC */
.run_count = v_total - 1,
.run_src = DI_SYNC_INT_HSYNC,
} , {
/* 3: Line Active */
.run_src = DI_SYNC_INT_HSYNC,
.offset_count = sig->v_sync_width + sig->v_start_width,
.offset_src = DI_SYNC_INT_HSYNC,
.repeat_count = sig->height,
.cnt_clr_src = 3 /* VSYNC */,
} , {
/* PIN4: HSYNC for VGA via TVEv2 on TQ MBa53 */
.run_count = h_total - 1,
.run_src = DI_SYNC_CLK,
.offset_count = div * sig->v_to_h_sync + 18, /* magic value from Freescale TVE driver */
.offset_src = DI_SYNC_CLK,
.cnt_polarity_gen_en = 1,
.cnt_polarity_trigger_src = DI_SYNC_CLK,
.cnt_down = sig->h_sync_width * 2,
} , {
/* 5: Pixel Active signal to DC */
.run_src = DI_SYNC_CLK,
.offset_count = sig->h_sync_width + sig->h_start_width,
.offset_src = DI_SYNC_CLK,
.repeat_count = sig->width,
.cnt_clr_src = 4, /* Line Active */
} , {
/* PIN6: VSYNC for VGA via TVEv2 on TQ MBa53 */
.run_count = v_total - 1,
.run_src = DI_SYNC_INT_HSYNC,
.offset_count = 1, /* magic value from Freescale TVE driver */
.offset_src = DI_SYNC_INT_HSYNC,
.cnt_polarity_gen_en = 1,
.cnt_polarity_trigger_src = DI_SYNC_INT_HSYNC,
.cnt_down = sig->v_sync_width * 2,
} , {
/* PIN4: HSYNC for VGA via TVEv2 on i.MX53-QSB */
.run_count = h_total - 1,
.run_src = DI_SYNC_CLK,
.offset_count = div * sig->v_to_h_sync + 18, /* magic value from Freescale TVE driver */
.offset_src = DI_SYNC_CLK,
.cnt_polarity_gen_en = 1,
.cnt_polarity_trigger_src = DI_SYNC_CLK,
.cnt_down = sig->h_sync_width * 2,
} , {
/* PIN6: VSYNC for VGA via TVEv2 on i.MX53-QSB */
.run_count = v_total - 1,
.run_src = DI_SYNC_INT_HSYNC,
.offset_count = 1, /* magic value from Freescale TVE driver */
.offset_src = DI_SYNC_INT_HSYNC,
.cnt_polarity_gen_en = 1,
.cnt_polarity_trigger_src = DI_SYNC_INT_HSYNC,
.cnt_down = sig->v_sync_width * 2,
} , {
/* unused */
},
};
ipu_di_write(di, v_total - 1, DI_SCR_CONF);
if (sig->hsync_pin == 2 && sig->vsync_pin == 3)
ipu_di_sync_config(di, cfg, 0, ARRAY_SIZE(cfg));
else
ipu_di_sync_config(di, cfg_vga, 0, ARRAY_SIZE(cfg_vga));
}
int ipu_di_init_sync_panel(struct ipu_di *di, struct ipu_di_signal_cfg *sig)
{
u32 reg;
u32 di_gen, vsync_cnt;
u32 div;
u32 h_total, v_total;
int ret;
unsigned long round;
struct clk *parent;
dev_dbg(di->ipu->dev, "disp %d: panel size = %d x %d, pixelclock = %ld\n",
di->id, sig->width, sig->height, sig->pixelclock);
if ((sig->v_sync_width == 0) || (sig->h_sync_width == 0))
return -EINVAL;
if (sig->clkflags & IPU_DI_CLKMODE_EXT)
parent = di->clk_di;
else
parent = di->clk_ipu;
ret = clk_set_parent(&di->clk_di_pixel, parent);
if (ret) {
dev_err(di->ipu->dev,
"setting pixel clock to parent %s failed with %d\n",
parent->name, ret);
return ret;
}
if (sig->clkflags & IPU_DI_CLKMODE_SYNC) {
ret = clk_set_rate(clk_get_parent(parent), sig->pixelclock);
round = sig->pixelclock;
} else if (sig->clkflags & IPU_DI_CLKMODE_NON_FRACTIONAL) {
unsigned div;
round = clk_get_rate(parent);
div = DIV_ROUND_CLOSEST(round, sig->pixelclock);
round = round / div;
} else {
round = sig->pixelclock;
}
ret = clk_set_rate(&di->clk_di_pixel, round);
h_total = sig->width + sig->h_sync_width + sig->h_start_width +
sig->h_end_width;
v_total = sig->height + sig->v_sync_width + sig->v_start_width +
sig->v_end_width;
div = ipu_di_read(di, DI_BS_CLKGEN0) & 0xfff;
div = div / 16; /* Now divider is integer portion */
/* Setup pixel clock timing */
/* Down time is half of period */
ipu_di_write(di, (div << 16), DI_BS_CLKGEN1);
ipu_di_data_wave_config(di, SYNC_WAVE, div - 1, div - 1);
ipu_di_data_pin_config(di, SYNC_WAVE, DI_PIN15, 3, 0, div * 2);
di_gen = ipu_di_read(di, DI_GENERAL) & DI_GEN_DI_CLK_EXT;
di_gen |= DI_GEN_DI_VSYNC_EXT;
if (sig->interlaced) {
ipu_di_sync_config_interlaced(di, sig);
/* set y_sel = 1 */
di_gen |= 0x10000000;
di_gen |= DI_GEN_POLARITY_5;
di_gen |= DI_GEN_POLARITY_8;
vsync_cnt = 7;
if (sig->Hsync_pol)
di_gen |= DI_GEN_POLARITY_3;
if (sig->Vsync_pol)
di_gen |= DI_GEN_POLARITY_2;
} else {
ipu_di_sync_config_noninterlaced(di, sig, div);
vsync_cnt = 3;
if (di->id == 1)
/*
* TODO: change only for TVEv2, parallel display
* uses pin 2 / 3
*/
if (!(sig->hsync_pin == 2 && sig->vsync_pin == 3))
vsync_cnt = 6;
if (sig->Hsync_pol) {
if (sig->hsync_pin == 2)
di_gen |= DI_GEN_POLARITY_2;
else if (sig->hsync_pin == 4)
di_gen |= DI_GEN_POLARITY_4;
else if (sig->hsync_pin == 7)
di_gen |= DI_GEN_POLARITY_7;
}
if (sig->Vsync_pol) {
if (sig->vsync_pin == 3)
di_gen |= DI_GEN_POLARITY_3;
else if (sig->vsync_pin == 6)
di_gen |= DI_GEN_POLARITY_6;
else if (sig->vsync_pin == 8)
di_gen |= DI_GEN_POLARITY_8;
}
}
if (!sig->clk_pol)
di_gen |= DI_GEN_POLARITY_DISP_CLK;
ipu_di_write(di, di_gen, DI_GENERAL);
ipu_di_write(di, (--vsync_cnt << DI_VSYNC_SEL_OFFSET) | 0x00000002,
DI_SYNC_AS_GEN);
reg = ipu_di_read(di, DI_POL);
reg &= ~(DI_POL_DRDY_DATA_POLARITY | DI_POL_DRDY_POLARITY_15);
if (sig->enable_pol)
reg |= DI_POL_DRDY_POLARITY_15;
if (sig->data_pol)
reg |= DI_POL_DRDY_DATA_POLARITY;
ipu_di_write(di, reg, DI_POL);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_di_init_sync_panel);
int ipu_di_enable(struct ipu_di *di)
{
int ret = clk_enable(&di->clk_di_pixel);
if (ret)
return ret;
ipu_module_enable(di->ipu, di->module);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_di_enable);
int ipu_di_disable(struct ipu_di *di)
{
ipu_module_disable(di->ipu, di->module);
clk_disable(&di->clk_di_pixel);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_di_disable);
int ipu_di_get_num(struct ipu_di *di)
{
return di->id;
}
EXPORT_SYMBOL_GPL(ipu_di_get_num);
struct ipu_di *ipu_di_get(struct ipu_soc *ipu, int disp)
{
struct ipu_di *di;
if (disp > 1)
return ERR_PTR(-EINVAL);
di = ipu->di_priv[disp];
if (di->inuse) {
di = ERR_PTR(-EBUSY);
goto out;
}
di->inuse = true;
out:
return di;
}
EXPORT_SYMBOL_GPL(ipu_di_get);
void ipu_di_put(struct ipu_di *di)
{
di->inuse = false;
}
EXPORT_SYMBOL_GPL(ipu_di_put);
int ipu_di_init(struct ipu_soc *ipu, struct device_d *dev, int id,
void __iomem *base,
u32 module, struct clk *clk_ipu)
{
struct ipu_di *di;
int ret;
if (id > 1)
return -ENODEV;
di = xzalloc(sizeof(*di));
ipu->di_priv[id] = di;
di->clk_di = clk_get(dev, id ? "di1" : "di0");
if (IS_ERR(di->clk_di))
return PTR_ERR(di->clk_di);
di->module = module;
di->id = id;
di->clk_ipu = clk_ipu;
di->base = base;
di->di_parent_names[0] = di->clk_ipu->name;
di->di_parent_names[1] = di->clk_di->name;
ipu_di_write(di, 0x10, DI_BS_CLKGEN0);
di->clk_di_pixel.parent_names = di->di_parent_names;
di->clk_name = asprintf("%s_di%d_pixel",
dev_name(dev), id);
if (!di->clk_name)
return -ENOMEM;
di->clk_di_pixel.ops = &clk_di_ops;
di->clk_di_pixel.num_parents = 2;
di->clk_di_pixel.name = di->clk_name;
ret = clk_register(&di->clk_di_pixel);
if (ret)
goto failed_clk_register;
dev_dbg(dev, "DI%d base: 0x%p\n", id, base);
di->inuse = false;
di->ipu = ipu;
return 0;
failed_clk_register:
free(di->clk_name);
return ret;
}
void ipu_di_exit(struct ipu_soc *ipu, int id)
{
}

View File

@ -0,0 +1,397 @@
/*
* Copyright (c) 2010 Sascha Hauer <s.hauer@pengutronix.de>
* Copyright (C) 2005-2009 Freescale Semiconductor, Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include <common.h>
#include <linux/err.h>
#include <linux/clk.h>
#include "imx-ipu-v3.h"
#include "ipu-prv.h"
#define DMFC_RD_CHAN 0x0000
#define DMFC_WR_CHAN 0x0004
#define DMFC_WR_CHAN_DEF 0x0008
#define DMFC_DP_CHAN 0x000c
#define DMFC_DP_CHAN_DEF 0x0010
#define DMFC_GENERAL1 0x0014
#define DMFC_GENERAL2 0x0018
#define DMFC_IC_CTRL 0x001c
#define DMFC_STAT 0x0020
#define DMFC_WR_CHAN_1_28 0
#define DMFC_WR_CHAN_2_41 8
#define DMFC_WR_CHAN_1C_42 16
#define DMFC_WR_CHAN_2C_43 24
#define DMFC_DP_CHAN_5B_23 0
#define DMFC_DP_CHAN_5F_27 8
#define DMFC_DP_CHAN_6B_24 16
#define DMFC_DP_CHAN_6F_29 24
#define DMFC_FIFO_SIZE_64 (3 << 3)
#define DMFC_FIFO_SIZE_128 (2 << 3)
#define DMFC_FIFO_SIZE_256 (1 << 3)
#define DMFC_FIFO_SIZE_512 (0 << 3)
#define DMFC_SEGMENT(x) ((x & 0x7) << 0)
#define DMFC_BURSTSIZE_128 (0 << 6)
#define DMFC_BURSTSIZE_64 (1 << 6)
#define DMFC_BURSTSIZE_32 (2 << 6)
#define DMFC_BURSTSIZE_16 (3 << 6)
struct dmfc_channel_data {
int ipu_channel;
unsigned long channel_reg;
unsigned long shift;
unsigned eot_shift;
unsigned max_fifo_lines;
};
static const struct dmfc_channel_data dmfcdata[] = {
{
.ipu_channel = IPUV3_CHANNEL_MEM_BG_SYNC,
.channel_reg = DMFC_DP_CHAN,
.shift = DMFC_DP_CHAN_5B_23,
.eot_shift = 20,
.max_fifo_lines = 3,
}, {
.ipu_channel = 24,
.channel_reg = DMFC_DP_CHAN,
.shift = DMFC_DP_CHAN_6B_24,
.eot_shift = 22,
.max_fifo_lines = 1,
}, {
.ipu_channel = IPUV3_CHANNEL_MEM_FG_SYNC,
.channel_reg = DMFC_DP_CHAN,
.shift = DMFC_DP_CHAN_5F_27,
.eot_shift = 21,
.max_fifo_lines = 2,
}, {
.ipu_channel = IPUV3_CHANNEL_MEM_DC_SYNC,
.channel_reg = DMFC_WR_CHAN,
.shift = DMFC_WR_CHAN_1_28,
.eot_shift = 16,
.max_fifo_lines = 2,
}, {
.ipu_channel = 29,
.channel_reg = DMFC_DP_CHAN,
.shift = DMFC_DP_CHAN_6F_29,
.eot_shift = 23,
.max_fifo_lines = 1,
},
};
#define DMFC_NUM_CHANNELS ARRAY_SIZE(dmfcdata)
struct ipu_dmfc_priv;
struct dmfc_channel {
unsigned slots;
unsigned slotmask;
unsigned segment;
int burstsize;
struct ipu_soc *ipu;
struct ipu_dmfc_priv *priv;
const struct dmfc_channel_data *data;
};
struct ipu_dmfc_priv {
struct ipu_soc *ipu;
struct device_d *dev;
struct dmfc_channel channels[DMFC_NUM_CHANNELS];
unsigned long bandwidth_per_slot;
void __iomem *base;
int use_count;
};
int ipu_dmfc_enable_channel(struct dmfc_channel *dmfc)
{
struct ipu_dmfc_priv *priv = dmfc->priv;
if (!priv->use_count)
ipu_module_enable(priv->ipu, IPU_CONF_DMFC_EN);
priv->use_count++;
return 0;
}
EXPORT_SYMBOL_GPL(ipu_dmfc_enable_channel);
void ipu_dmfc_disable_channel(struct dmfc_channel *dmfc)
{
struct ipu_dmfc_priv *priv = dmfc->priv;
priv->use_count--;
if (!priv->use_count)
ipu_module_disable(priv->ipu, IPU_CONF_DMFC_EN);
if (priv->use_count < 0)
priv->use_count = 0;
}
EXPORT_SYMBOL_GPL(ipu_dmfc_disable_channel);
static int ipu_dmfc_setup_channel(struct dmfc_channel *dmfc, int slots,
int segment, int burstsize)
{
struct ipu_dmfc_priv *priv = dmfc->priv;
u32 val, field;
dev_dbg(priv->dev,
"dmfc: using %d slots starting from segment %d for IPU channel %d\n",
slots, segment, dmfc->data->ipu_channel);
if (!dmfc)
return -EINVAL;
switch (slots) {
case 1:
field = DMFC_FIFO_SIZE_64;
break;
case 2:
field = DMFC_FIFO_SIZE_128;
break;
case 4:
field = DMFC_FIFO_SIZE_256;
break;
case 8:
field = DMFC_FIFO_SIZE_512;
break;
default:
return -EINVAL;
}
switch (burstsize) {
case 16:
field |= DMFC_BURSTSIZE_16;
break;
case 32:
field |= DMFC_BURSTSIZE_32;
break;
case 64:
field |= DMFC_BURSTSIZE_64;
break;
case 128:
field |= DMFC_BURSTSIZE_128;
break;
}
field |= DMFC_SEGMENT(segment);
val = ipureadl(priv->base + dmfc->data->channel_reg);
val &= ~(0xff << dmfc->data->shift);
val |= field << dmfc->data->shift;
ipuwritel("dmfc", val, priv->base + dmfc->data->channel_reg);
dmfc->slots = slots;
dmfc->segment = segment;
dmfc->burstsize = burstsize;
dmfc->slotmask = ((1 << slots) - 1) << segment;
return 0;
}
static int dmfc_bandwidth_to_slots(struct ipu_dmfc_priv *priv,
unsigned long bandwidth)
{
int slots = 1;
while (slots * priv->bandwidth_per_slot < bandwidth)
slots *= 2;
return slots;
}
static int dmfc_find_slots(struct ipu_dmfc_priv *priv, int slots)
{
unsigned slotmask_need, slotmask_used = 0;
int i, segment = 0;
slotmask_need = (1 << slots) - 1;
for (i = 0; i < DMFC_NUM_CHANNELS; i++)
slotmask_used |= priv->channels[i].slotmask;
while (slotmask_need <= 0xff) {
if (!(slotmask_used & slotmask_need))
return segment;
slotmask_need <<= 1;
segment++;
}
return -EBUSY;
}
void ipu_dmfc_free_bandwidth(struct dmfc_channel *dmfc)
{
struct ipu_dmfc_priv *priv = dmfc->priv;
int i;
dev_dbg(priv->dev, "dmfc: freeing %d slots starting from segment %d\n",
dmfc->slots, dmfc->segment);
if (!dmfc->slots)
return;
dmfc->slotmask = 0;
dmfc->slots = 0;
dmfc->segment = 0;
for (i = 0; i < DMFC_NUM_CHANNELS; i++)
priv->channels[i].slotmask = 0;
for (i = 0; i < DMFC_NUM_CHANNELS; i++) {
if (priv->channels[i].slots > 0) {
priv->channels[i].segment =
dmfc_find_slots(priv, priv->channels[i].slots);
priv->channels[i].slotmask =
((1 << priv->channels[i].slots) - 1) <<
priv->channels[i].segment;
}
}
for (i = 0; i < DMFC_NUM_CHANNELS; i++) {
if (priv->channels[i].slots > 0)
ipu_dmfc_setup_channel(&priv->channels[i],
priv->channels[i].slots,
priv->channels[i].segment,
priv->channels[i].burstsize);
}
}
EXPORT_SYMBOL_GPL(ipu_dmfc_free_bandwidth);
int ipu_dmfc_alloc_bandwidth(struct dmfc_channel *dmfc,
unsigned long bandwidth_pixel_per_second, int burstsize)
{
struct ipu_dmfc_priv *priv = dmfc->priv;
int slots = dmfc_bandwidth_to_slots(priv, bandwidth_pixel_per_second);
int segment = -1, ret = 0;
dev_dbg(priv->dev, "dmfc: trying to allocate %ldMpixel/s for IPU channel %d\n",
bandwidth_pixel_per_second / 1000000,
dmfc->data->ipu_channel);
ipu_dmfc_free_bandwidth(dmfc);
if (slots > 8) {
ret = -EBUSY;
goto out;
}
/* For the MEM_BG channel, first try to allocate twice the slots */
if (dmfc->data->ipu_channel == IPUV3_CHANNEL_MEM_BG_SYNC)
segment = dmfc_find_slots(priv, slots * 2);
else if (slots < 2)
/* Always allocate at least 128*4 bytes (2 slots) */
slots = 2;
if (segment >= 0)
slots *= 2;
else
segment = dmfc_find_slots(priv, slots);
if (segment < 0) {
ret = -EBUSY;
goto out;
}
ipu_dmfc_setup_channel(dmfc, slots, segment, burstsize);
out:
return ret;
}
EXPORT_SYMBOL_GPL(ipu_dmfc_alloc_bandwidth);
int ipu_dmfc_init_channel(struct dmfc_channel *dmfc, int width)
{
struct ipu_dmfc_priv *priv = dmfc->priv;
u32 dmfc_gen1;
dmfc_gen1 = ipureadl(priv->base + DMFC_GENERAL1);
if ((dmfc->slots * 64 * 4) / width > dmfc->data->max_fifo_lines)
dmfc_gen1 |= 1 << dmfc->data->eot_shift;
else
dmfc_gen1 &= ~(1 << dmfc->data->eot_shift);
ipuwritel("dmfc", dmfc_gen1, priv->base + DMFC_GENERAL1);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_dmfc_init_channel);
struct dmfc_channel *ipu_dmfc_get(struct ipu_soc *ipu, int ipu_channel)
{
struct ipu_dmfc_priv *priv = ipu->dmfc_priv;
int i;
for (i = 0; i < DMFC_NUM_CHANNELS; i++)
if (dmfcdata[i].ipu_channel == ipu_channel)
return &priv->channels[i];
return ERR_PTR(-ENODEV);
}
EXPORT_SYMBOL_GPL(ipu_dmfc_get);
void ipu_dmfc_put(struct dmfc_channel *dmfc)
{
ipu_dmfc_free_bandwidth(dmfc);
}
EXPORT_SYMBOL_GPL(ipu_dmfc_put);
int ipu_dmfc_init(struct ipu_soc *ipu, struct device_d *dev, void __iomem *base,
struct clk *ipu_clk)
{
struct ipu_dmfc_priv *priv;
int i;
priv = xzalloc(sizeof(*priv));
priv->base = base;
priv->dev = dev;
priv->ipu = ipu;
ipu->dmfc_priv = priv;
for (i = 0; i < DMFC_NUM_CHANNELS; i++) {
priv->channels[i].priv = priv;
priv->channels[i].ipu = ipu;
priv->channels[i].data = &dmfcdata[i];
}
ipuwritel("dmfc", 0x0, priv->base + DMFC_WR_CHAN);
ipuwritel("dmfc", 0x0, priv->base + DMFC_DP_CHAN);
/*
* We have a total bandwidth of clkrate * 4pixel divided
* into 8 slots.
*/
priv->bandwidth_per_slot = clk_get_rate(ipu_clk) * 4 / 8;
dev_dbg(dev, "dmfc: 8 slots with %ldMpixel/s bandwidth each\n",
priv->bandwidth_per_slot / 1000000);
ipuwritel("dmfc", 0x202020f6, priv->base + DMFC_WR_CHAN_DEF);
ipuwritel("dmfc", 0x2020f6f6, priv->base + DMFC_DP_CHAN_DEF);
ipuwritel("dmfc", 0x00000003, priv->base + DMFC_GENERAL1);
return 0;
}
void ipu_dmfc_exit(struct ipu_soc *ipu)
{
}

View File

@ -0,0 +1,313 @@
/*
* Copyright (c) 2010 Sascha Hauer <s.hauer@pengutronix.de>
* Copyright (C) 2005-2009 Freescale Semiconductor, Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include <common.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <malloc.h>
#include "imx-ipu-v3.h"
#include "ipu-prv.h"
#define DP_SYNC 0
#define DP_ASYNC0 0x60
#define DP_ASYNC1 0xBC
#define DP_COM_CONF 0x0
#define DP_GRAPH_WIND_CTRL 0x0004
#define DP_FG_POS 0x0008
#define DP_CSC_A_0 0x0044
#define DP_CSC_A_1 0x0048
#define DP_CSC_A_2 0x004C
#define DP_CSC_A_3 0x0050
#define DP_CSC_0 0x0054
#define DP_CSC_1 0x0058
#define DP_COM_CONF_FG_EN (1 << 0)
#define DP_COM_CONF_GWSEL (1 << 1)
#define DP_COM_CONF_GWAM (1 << 2)
#define DP_COM_CONF_GWCKE (1 << 3)
#define DP_COM_CONF_CSC_DEF_MASK (3 << 8)
#define DP_COM_CONF_CSC_DEF_OFFSET 8
#define DP_COM_CONF_CSC_DEF_FG (3 << 8)
#define DP_COM_CONF_CSC_DEF_BG (2 << 8)
#define DP_COM_CONF_CSC_DEF_BOTH (1 << 8)
#define IPUV3_NUM_FLOWS 3
struct ipu_dp_priv;
struct ipu_dp {
u32 flow;
bool in_use;
bool foreground;
enum ipu_color_space in_cs;
};
struct ipu_flow {
struct ipu_dp foreground;
struct ipu_dp background;
enum ipu_color_space out_cs;
void __iomem *base;
struct ipu_dp_priv *priv;
};
struct ipu_dp_priv {
struct ipu_soc *ipu;
struct device_d *dev;
void __iomem *base;
struct ipu_flow flow[IPUV3_NUM_FLOWS];
int use_count;
};
static u32 ipu_dp_flow_base[] = {DP_SYNC, DP_ASYNC0, DP_ASYNC1};
static inline struct ipu_flow *to_flow(struct ipu_dp *dp)
{
if (dp->foreground)
return container_of(dp, struct ipu_flow, foreground);
else
return container_of(dp, struct ipu_flow, background);
}
int ipu_dp_set_global_alpha(struct ipu_dp *dp, bool enable,
u8 alpha, bool bg_chan)
{
struct ipu_flow *flow = to_flow(dp);
struct ipu_dp_priv *priv = flow->priv;
u32 reg;
reg = ipureadl(flow->base + DP_COM_CONF);
if (bg_chan)
reg &= ~DP_COM_CONF_GWSEL;
else
reg |= DP_COM_CONF_GWSEL;
ipuwritel("dp", reg, flow->base + DP_COM_CONF);
if (enable) {
reg = ipureadl(flow->base + DP_GRAPH_WIND_CTRL) & 0x00FFFFFFL;
ipuwritel("dp", reg | ((u32) alpha << 24),
flow->base + DP_GRAPH_WIND_CTRL);
reg = ipureadl(flow->base + DP_COM_CONF);
ipuwritel("dp", reg | DP_COM_CONF_GWAM, flow->base + DP_COM_CONF);
} else {
reg = ipureadl(flow->base + DP_COM_CONF);
ipuwritel("dp", reg & ~DP_COM_CONF_GWAM, flow->base + DP_COM_CONF);
}
ipu_srm_dp_sync_update(priv->ipu);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_dp_set_global_alpha);
int ipu_dp_set_window_pos(struct ipu_dp *dp, u16 x_pos, u16 y_pos)
{
struct ipu_flow *flow = to_flow(dp);
struct ipu_dp_priv *priv = flow->priv;
ipuwritel("dp", (x_pos << 16) | y_pos, flow->base + DP_FG_POS);
ipu_srm_dp_sync_update(priv->ipu);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_dp_set_window_pos);
static void ipu_dp_csc_init(struct ipu_flow *flow,
enum ipu_color_space in,
enum ipu_color_space out,
u32 place)
{
u32 reg;
reg = ipureadl(flow->base + DP_COM_CONF);
reg &= ~DP_COM_CONF_CSC_DEF_MASK;
if (in == out) {
ipuwritel("dp", reg, flow->base + DP_COM_CONF);
return;
}
if (in == IPUV3_COLORSPACE_RGB && out == IPUV3_COLORSPACE_YUV) {
ipuwritel("dp", 0x099 | (0x12d << 16), flow->base + DP_CSC_A_0);
ipuwritel("dp", 0x03a | (0x3a9 << 16), flow->base + DP_CSC_A_1);
ipuwritel("dp", 0x356 | (0x100 << 16), flow->base + DP_CSC_A_2);
ipuwritel("dp", 0x100 | (0x329 << 16), flow->base + DP_CSC_A_3);
ipuwritel("dp", 0x3d6 | (0x0000 << 16) | (2 << 30),
flow->base + DP_CSC_0);
ipuwritel("dp", 0x200 | (2 << 14) | (0x200 << 16) | (2 << 30),
flow->base + DP_CSC_1);
} else {
ipuwritel("dp", 0x095 | (0x000 << 16), flow->base + DP_CSC_A_0);
ipuwritel("dp", 0x0cc | (0x095 << 16), flow->base + DP_CSC_A_1);
ipuwritel("dp", 0x3ce | (0x398 << 16), flow->base + DP_CSC_A_2);
ipuwritel("dp", 0x095 | (0x0ff << 16), flow->base + DP_CSC_A_3);
ipuwritel("dp", 0x000 | (0x3e42 << 16) | (1 << 30),
flow->base + DP_CSC_0);
ipuwritel("dp", 0x10a | (1 << 14) | (0x3dd6 << 16) | (1 << 30),
flow->base + DP_CSC_1);
}
reg |= place;
ipuwritel("dp", reg, flow->base + DP_COM_CONF);
}
int ipu_dp_setup_channel(struct ipu_dp *dp,
enum ipu_color_space in,
enum ipu_color_space out)
{
struct ipu_flow *flow = to_flow(dp);
struct ipu_dp_priv *priv = flow->priv;
dp->in_cs = in;
if (!dp->foreground)
flow->out_cs = out;
if (flow->foreground.in_cs == flow->background.in_cs) {
/*
* foreground and background are of same colorspace, put
* colorspace converter after combining unit.
*/
ipu_dp_csc_init(flow, flow->foreground.in_cs, flow->out_cs,
DP_COM_CONF_CSC_DEF_BOTH);
} else {
if (flow->foreground.in_cs == flow->out_cs)
/*
* foreground identical to output, apply color
* conversion on background
*/
ipu_dp_csc_init(flow, flow->background.in_cs,
flow->out_cs, DP_COM_CONF_CSC_DEF_BG);
else
ipu_dp_csc_init(flow, flow->foreground.in_cs,
flow->out_cs, DP_COM_CONF_CSC_DEF_FG);
}
ipu_srm_dp_sync_update(priv->ipu);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_dp_setup_channel);
int ipu_dp_enable_channel(struct ipu_dp *dp)
{
struct ipu_flow *flow = to_flow(dp);
struct ipu_dp_priv *priv = flow->priv;
if (!priv->use_count)
ipu_module_enable(priv->ipu, IPU_CONF_DP_EN);
priv->use_count++;
if (dp->foreground) {
u32 reg;
reg = ipureadl(flow->base + DP_COM_CONF);
reg |= DP_COM_CONF_FG_EN;
ipuwritel("dp", reg, flow->base + DP_COM_CONF);
ipu_srm_dp_sync_update(priv->ipu);
}
return 0;
}
EXPORT_SYMBOL_GPL(ipu_dp_enable_channel);
void ipu_dp_disable_channel(struct ipu_dp *dp)
{
struct ipu_flow *flow = to_flow(dp);
struct ipu_dp_priv *priv = flow->priv;
priv->use_count--;
if (dp->foreground) {
u32 reg, csc;
reg = ipureadl(flow->base + DP_COM_CONF);
csc = reg & DP_COM_CONF_CSC_DEF_MASK;
if (csc == DP_COM_CONF_CSC_DEF_FG)
reg &= ~DP_COM_CONF_CSC_DEF_MASK;
reg &= ~DP_COM_CONF_FG_EN;
ipuwritel("dp", reg, flow->base + DP_COM_CONF);
ipuwritel("dp", 0, flow->base + DP_FG_POS);
ipu_srm_dp_sync_update(priv->ipu);
}
if (!priv->use_count)
ipu_module_disable(priv->ipu, IPU_CONF_DP_EN);
if (priv->use_count < 0)
priv->use_count = 0;
}
EXPORT_SYMBOL_GPL(ipu_dp_disable_channel);
struct ipu_dp *ipu_dp_get(struct ipu_soc *ipu, unsigned int flow)
{
struct ipu_dp_priv *priv = ipu->dp_priv;
struct ipu_dp *dp;
if ((flow >> 1) >= IPUV3_NUM_FLOWS)
return ERR_PTR(-EINVAL);
if (flow & 1)
dp = &priv->flow[flow >> 1].foreground;
else
dp = &priv->flow[flow >> 1].background;
if (dp->in_use)
return ERR_PTR(-EBUSY);
dp->in_use = true;
return dp;
}
EXPORT_SYMBOL_GPL(ipu_dp_get);
void ipu_dp_put(struct ipu_dp *dp)
{
dp->in_use = false;
}
EXPORT_SYMBOL_GPL(ipu_dp_put);
int ipu_dp_init(struct ipu_soc *ipu, struct device_d *dev, void __iomem *base)
{
struct ipu_dp_priv *priv;
int i;
priv = xzalloc(sizeof(*priv));
priv->dev = dev;
priv->ipu = ipu;
ipu->dp_priv = priv;
priv->base = base;
for (i = 0; i < IPUV3_NUM_FLOWS; i++) {
priv->flow[i].foreground.foreground = true;
priv->flow[i].base = priv->base + ipu_dp_flow_base[i];
priv->flow[i].priv = priv;
}
return 0;
}
void ipu_dp_exit(struct ipu_soc *ipu)
{
}

View File

@ -0,0 +1,204 @@
/*
* Copyright (c) 2010 Sascha Hauer <s.hauer@pengutronix.de>
* Copyright (C) 2005-2009 Freescale Semiconductor, Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#ifndef __IPU_PRV_H__
#define __IPU_PRV_H__
struct ipu_soc;
#include "imx-ipu-v3.h"
#define IPU_PIX_FMT_GBR24 v4l2_fourcc('G', 'B', 'R', '3')
#define IPUV3_CHANNEL_CSI0 0
#define IPUV3_CHANNEL_CSI1 1
#define IPUV3_CHANNEL_CSI2 2
#define IPUV3_CHANNEL_CSI3 3
#define IPUV3_CHANNEL_MEM_BG_SYNC 23
#define IPUV3_CHANNEL_MEM_FG_SYNC 27
#define IPUV3_CHANNEL_MEM_DC_SYNC 28
#define IPUV3_CHANNEL_MEM_FG_SYNC_ALPHA 31
#define IPUV3_CHANNEL_MEM_DC_ASYNC 41
#define IPUV3_CHANNEL_ROT_ENC_MEM 45
#define IPUV3_CHANNEL_ROT_VF_MEM 46
#define IPUV3_CHANNEL_ROT_PP_MEM 47
#define IPUV3_CHANNEL_ROT_ENC_MEM_OUT 48
#define IPUV3_CHANNEL_ROT_VF_MEM_OUT 49
#define IPUV3_CHANNEL_ROT_PP_MEM_OUT 50
#define IPUV3_CHANNEL_MEM_BG_SYNC_ALPHA 51
#define IPU_MCU_T_DEFAULT 8
#define IPU_CM_IDMAC_REG_OFS 0x00008000
#define IPU_CM_IC_REG_OFS 0x00020000
#define IPU_CM_IRT_REG_OFS 0x00028000
#define IPU_CM_CSI0_REG_OFS 0x00030000
#define IPU_CM_CSI1_REG_OFS 0x00038000
#define IPU_CM_SMFC_REG_OFS 0x00050000
#define IPU_CM_DC_REG_OFS 0x00058000
#define IPU_CM_DMFC_REG_OFS 0x00060000
/* Register addresses */
/* IPU Common registers */
#define IPU_CM_REG(offset) (offset)
#define IPU_CONF IPU_CM_REG(0)
#define IPU_SRM_PRI1 IPU_CM_REG(0x00a0)
#define IPU_SRM_PRI2 IPU_CM_REG(0x00a4)
#define IPU_FS_PROC_FLOW1 IPU_CM_REG(0x00a8)
#define IPU_FS_PROC_FLOW2 IPU_CM_REG(0x00ac)
#define IPU_FS_PROC_FLOW3 IPU_CM_REG(0x00b0)
#define IPU_FS_DISP_FLOW1 IPU_CM_REG(0x00b4)
#define IPU_FS_DISP_FLOW2 IPU_CM_REG(0x00b8)
#define IPU_SKIP IPU_CM_REG(0x00bc)
#define IPU_DISP_ALT_CONF IPU_CM_REG(0x00c0)
#define IPU_DISP_GEN IPU_CM_REG(0x00c4)
#define IPU_DISP_ALT1 IPU_CM_REG(0x00c8)
#define IPU_DISP_ALT2 IPU_CM_REG(0x00cc)
#define IPU_DISP_ALT3 IPU_CM_REG(0x00d0)
#define IPU_DISP_ALT4 IPU_CM_REG(0x00d4)
#define IPU_SNOOP IPU_CM_REG(0x00d8)
#define IPU_MEM_RST IPU_CM_REG(0x00dc)
#define IPU_PM IPU_CM_REG(0x00e0)
#define IPU_GPR IPU_CM_REG(0x00e4)
#define IPU_CHA_DB_MODE_SEL(ch) IPU_CM_REG(0x0150 + 4 * ((ch) / 32))
#define IPU_ALT_CHA_DB_MODE_SEL(ch) IPU_CM_REG(0x0168 + 4 * ((ch) / 32))
#define IPU_CHA_CUR_BUF(ch) IPU_CM_REG(0x023C + 4 * ((ch) / 32))
#define IPU_ALT_CUR_BUF0 IPU_CM_REG(0x0244)
#define IPU_ALT_CUR_BUF1 IPU_CM_REG(0x0248)
#define IPU_SRM_STAT IPU_CM_REG(0x024C)
#define IPU_PROC_TASK_STAT IPU_CM_REG(0x0250)
#define IPU_DISP_TASK_STAT IPU_CM_REG(0x0254)
#define IPU_CHA_BUF0_RDY(ch) IPU_CM_REG(0x0268 + 4 * ((ch) / 32))
#define IPU_CHA_BUF1_RDY(ch) IPU_CM_REG(0x0270 + 4 * ((ch) / 32))
#define IPU_ALT_CHA_BUF0_RDY(ch) IPU_CM_REG(0x0278 + 4 * ((ch) / 32))
#define IPU_ALT_CHA_BUF1_RDY(ch) IPU_CM_REG(0x0280 + 4 * ((ch) / 32))
#define IPU_INT_CTRL(n) IPU_CM_REG(0x003C + 4 * (n))
#define IPU_INT_STAT(n) IPU_CM_REG(0x0200 + 4 * (n))
#define IPU_DI0_COUNTER_RELEASE (1 << 24)
#define IPU_DI1_COUNTER_RELEASE (1 << 25)
#define IPU_IDMAC_REG(offset) (offset)
#define IDMAC_CONF IPU_IDMAC_REG(0x0000)
#define IDMAC_CHA_EN(ch) IPU_IDMAC_REG(0x0004 + 4 * ((ch) / 32))
#define IDMAC_SEP_ALPHA IPU_IDMAC_REG(0x000c)
#define IDMAC_ALT_SEP_ALPHA IPU_IDMAC_REG(0x0010)
#define IDMAC_CHA_PRI(ch) IPU_IDMAC_REG(0x0014 + 4 * ((ch) / 32))
#define IDMAC_WM_EN(ch) IPU_IDMAC_REG(0x001c + 4 * ((ch) / 32))
#define IDMAC_CH_LOCK_EN_1 IPU_IDMAC_REG(0x0024)
#define IDMAC_CH_LOCK_EN_2 IPU_IDMAC_REG(0x0028)
#define IDMAC_SUB_ADDR_0 IPU_IDMAC_REG(0x002c)
#define IDMAC_SUB_ADDR_1 IPU_IDMAC_REG(0x0030)
#define IDMAC_SUB_ADDR_2 IPU_IDMAC_REG(0x0034)
#define IDMAC_BAND_EN(ch) IPU_IDMAC_REG(0x0040 + 4 * ((ch) / 32))
#define IDMAC_CHA_BUSY(ch) IPU_IDMAC_REG(0x0100 + 4 * ((ch) / 32))
#define IPU_NUM_IRQS (32 * 15)
enum ipu_modules {
IPU_CONF_CSI0_EN = (1 << 0),
IPU_CONF_CSI1_EN = (1 << 1),
IPU_CONF_IC_EN = (1 << 2),
IPU_CONF_ROT_EN = (1 << 3),
IPU_CONF_ISP_EN = (1 << 4),
IPU_CONF_DP_EN = (1 << 5),
IPU_CONF_DI0_EN = (1 << 6),
IPU_CONF_DI1_EN = (1 << 7),
IPU_CONF_SMFC_EN = (1 << 8),
IPU_CONF_DC_EN = (1 << 9),
IPU_CONF_DMFC_EN = (1 << 10),
IPU_CONF_VDI_EN = (1 << 12),
IPU_CONF_IDMAC_DIS = (1 << 22),
IPU_CONF_IC_DMFC_SEL = (1 << 25),
IPU_CONF_IC_DMFC_SYNC = (1 << 26),
IPU_CONF_VDI_DMFC_SYNC = (1 << 27),
IPU_CONF_CSI0_DATA_SOURCE = (1 << 28),
IPU_CONF_CSI1_DATA_SOURCE = (1 << 29),
IPU_CONF_IC_INPUT = (1 << 30),
IPU_CONF_CSI_SEL = (1 << 31),
};
struct ipuv3_channel {
unsigned int num;
bool enabled;
bool busy;
struct ipu_soc *ipu;
};
struct ipu_dc_priv;
struct ipu_dmfc_priv;
struct ipu_di;
struct ipu_devtype;
struct ipu_soc {
struct device_d *dev;
const struct ipu_devtype *devtype;
enum ipuv3_type ipu_type;
spinlock_t lock;
struct mutex channel_lock;
void __iomem *base;
void __iomem *cm_reg;
void __iomem *idmac_reg;
struct ipu_ch_param __iomem *cpmem_base;
int usecount;
struct clk *clk;
struct ipuv3_channel channel[64];
int irq_sync;
int irq_err;
struct irq_domain *domain;
struct ipu_dc_priv *dc_priv;
struct ipu_dp_priv *dp_priv;
struct ipu_dmfc_priv *dmfc_priv;
struct ipu_di *di_priv[2];
};
void ipu_srm_dp_sync_update(struct ipu_soc *ipu);
int ipu_module_enable(struct ipu_soc *ipu, u32 mask);
int ipu_module_disable(struct ipu_soc *ipu, u32 mask);
int ipu_di_init(struct ipu_soc *ipu, struct device_d *dev, int id,
void __iomem *base, u32 module, struct clk *ipu_clk);
void ipu_di_exit(struct ipu_soc *ipu, int id);
int ipu_dmfc_init(struct ipu_soc *ipu, struct device_d *dev, void __iomem *base,
struct clk *ipu_clk);
void ipu_dmfc_exit(struct ipu_soc *ipu);
int ipu_dp_init(struct ipu_soc *ipu, struct device_d *dev, void __iomem *base);
void ipu_dp_exit(struct ipu_soc *ipu);
int ipu_dc_init(struct ipu_soc *ipu, struct device_d *dev, void __iomem *base,
void __iomem *template_base);
void ipu_dc_exit(struct ipu_soc *ipu);
int ipu_cpmem_init(struct ipu_soc *ipu, struct device_d *dev, void __iomem *base);
void ipu_cpmem_exit(struct ipu_soc *ipu);
#endif /* __IPU_PRV_H__ */

View File

@ -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);

View File

@ -0,0 +1,239 @@
/*
* i.MX IPUv3 DP Overlay Planes
*
* Copyright (C) 2013 Philipp Zabel, Pengutronix
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <common.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <malloc.h>
#include <fb.h>
#include "ipuv3-plane.h"
#include "imx-ipu-v3.h"
static int calc_vref(struct fb_videomode *mode)
{
unsigned long htotal, vtotal;
htotal = mode->left_margin + mode->right_margin + mode->xres + mode->hsync_len;
vtotal = mode->upper_margin + mode->lower_margin + mode->yres + mode->vsync_len;
return DIV_ROUND_UP(PICOS2KHZ(mode->pixclock) * 1000, vtotal * htotal);
}
static inline int calc_bandwidth(int width, int height, unsigned int vref)
{
return width * height * vref;
}
int ipu_plane_set_base(struct ipu_plane *ipu_plane, struct fb_info *info,
int x, int y)
{
struct ipu_ch_param __iomem *cpmem;
pr_debug("phys = 0x%p, x = %d, y = %d, line_length = %d\n",
info->screen_base, x, y, info->line_length);
cpmem = ipu_get_cpmem(ipu_plane->ipu_ch);
ipu_cpmem_set_stride(cpmem, info->line_length);
ipu_cpmem_set_buffer(cpmem, 0, (unsigned long)info->screen_base +
info->line_length * y + x);
ipu_cpmem_set_buffer(cpmem, 1, (unsigned long)info->screen_base +
info->line_length * y + x);
return 0;
}
int ipu_plane_mode_set(struct ipu_plane *ipu_plane,
struct fb_videomode *mode,
struct fb_info *info, u32 pixel_format,
int crtc_x, int crtc_y,
unsigned int crtc_w, unsigned int crtc_h,
uint32_t src_x, uint32_t src_y,
uint32_t src_w, uint32_t src_h)
{
struct ipu_ch_param __iomem *cpmem;
struct device_d *dev = &info->dev;
int ret;
/* no scaling */
if (src_w != crtc_w || src_h != crtc_h)
return -EINVAL;
/* clip to crtc bounds */
if (crtc_x < 0) {
if (-crtc_x > crtc_w)
return -EINVAL;
src_x += -crtc_x;
src_w -= -crtc_x;
crtc_w -= -crtc_x;
crtc_x = 0;
}
if (crtc_y < 0) {
if (-crtc_y > crtc_h)
return -EINVAL;
src_y += -crtc_y;
src_h -= -crtc_y;
crtc_h -= -crtc_y;
crtc_y = 0;
}
if (crtc_x + crtc_w > mode->xres) {
if (crtc_x > mode->xres)
return -EINVAL;
crtc_w = mode->xres - crtc_x;
src_w = crtc_w;
}
if (crtc_y + crtc_h > mode->yres) {
if (crtc_y > mode->yres)
return -EINVAL;
crtc_h = mode->yres - crtc_y;
src_h = crtc_h;
}
/* full plane minimum width is 13 pixels */
if (crtc_w < 13 && (ipu_plane->dp_flow != IPU_DP_FLOW_SYNC_FG))
return -EINVAL;
if (crtc_h < 2)
return -EINVAL;
switch (ipu_plane->dp_flow) {
case IPU_DP_FLOW_SYNC_BG:
ret = ipu_dp_setup_channel(ipu_plane->dp,
IPUV3_COLORSPACE_RGB,
IPUV3_COLORSPACE_RGB);
if (ret) {
dev_err(dev,
"initializing display processor failed with %d\n",
ret);
return ret;
}
ipu_dp_set_global_alpha(ipu_plane->dp, 1, 0, 1);
break;
case IPU_DP_FLOW_SYNC_FG:
ipu_dp_setup_channel(ipu_plane->dp,
IPUV3_COLORSPACE_RGB,
IPUV3_COLORSPACE_UNKNOWN);
ipu_dp_set_window_pos(ipu_plane->dp, crtc_x, crtc_y);
break;
}
ret = ipu_dmfc_init_channel(ipu_plane->dmfc, crtc_w);
if (ret) {
dev_err(dev, "initializing dmfc channel failed with %d\n", ret);
return ret;
}
ret = ipu_dmfc_alloc_bandwidth(ipu_plane->dmfc,
calc_bandwidth(crtc_w, crtc_h,
calc_vref(mode)), 64);
if (ret) {
dev_err(dev, "allocating dmfc bandwidth failed with %d\n", ret);
return ret;
}
cpmem = ipu_get_cpmem(ipu_plane->ipu_ch);
ipu_ch_param_zero(cpmem);
ipu_cpmem_set_resolution(cpmem, src_w, src_h);
ret = ipu_cpmem_set_fmt(cpmem, pixel_format);
if (ret < 0) {
dev_err(dev, "unsupported pixel format 0x%08x\n",
pixel_format);
return ret;
}
ipu_cpmem_set_high_priority(ipu_plane->ipu_ch);
ret = ipu_plane_set_base(ipu_plane, info, src_x, src_y);
if (ret < 0)
return ret;
return 0;
}
void ipu_plane_put_resources(struct ipu_plane *ipu_plane)
{
if (!IS_ERR_OR_NULL(ipu_plane->dp))
ipu_dp_put(ipu_plane->dp);
if (!IS_ERR_OR_NULL(ipu_plane->dmfc))
ipu_dmfc_put(ipu_plane->dmfc);
if (!IS_ERR_OR_NULL(ipu_plane->ipu_ch))
ipu_idmac_put(ipu_plane->ipu_ch);
}
int ipu_plane_get_resources(struct ipu_plane *ipu_plane)
{
int ret;
ipu_plane->ipu_ch = ipu_idmac_get(ipu_plane->ipu, ipu_plane->dma);
if (IS_ERR(ipu_plane->ipu_ch)) {
ret = PTR_ERR(ipu_plane->ipu_ch);
pr_err("failed to get idmac channel: %d\n", ret);
return ret;
}
ipu_plane->dmfc = ipu_dmfc_get(ipu_plane->ipu, ipu_plane->dma);
if (IS_ERR(ipu_plane->dmfc)) {
ret = PTR_ERR(ipu_plane->dmfc);
pr_err("failed to get dmfc: ret %d\n", ret);
goto err_out;
}
if (ipu_plane->dp_flow >= 0) {
ipu_plane->dp = ipu_dp_get(ipu_plane->ipu, ipu_plane->dp_flow);
if (IS_ERR(ipu_plane->dp)) {
ret = PTR_ERR(ipu_plane->dp);
pr_err("failed to get dp flow: %d\n", ret);
goto err_out;
}
}
return 0;
err_out:
ipu_plane_put_resources(ipu_plane);
return ret;
}
void ipu_plane_enable(struct ipu_plane *ipu_plane)
{
ipu_dmfc_enable_channel(ipu_plane->dmfc);
ipu_idmac_enable_channel(ipu_plane->ipu_ch);
if (ipu_plane->dp)
ipu_dp_enable_channel(ipu_plane->dp);
ipu_plane->enabled = true;
}
void ipu_plane_disable(struct ipu_plane *ipu_plane)
{
ipu_plane->enabled = false;
ipu_idmac_wait_busy(ipu_plane->ipu_ch, 50);
if (ipu_plane->dp)
ipu_dp_disable_channel(ipu_plane->dp);
ipu_idmac_disable_channel(ipu_plane->ipu_ch);
ipu_dmfc_disable_channel(ipu_plane->dmfc);
}
struct ipu_plane *ipu_plane_init(struct ipu_soc *ipu,
int dma, int dp)
{
struct ipu_plane *ipu_plane;
ipu_plane = xzalloc(sizeof(*ipu_plane));
ipu_plane->ipu = ipu;
ipu_plane->dma = dma;
ipu_plane->dp_flow = dp;
return ipu_plane;
}

View File

@ -0,0 +1,51 @@
#ifndef __IPUV3_PLANE_H__
#define __IPUV3_PLANE_H__
struct drm_plane;
struct drm_device;
struct ipu_soc;
struct drm_crtc;
struct drm_framebuffer;
struct ipuv3_channel;
struct dmfc_channel;
struct ipu_dp;
struct ipu_plane {
struct ipu_soc *ipu;
struct ipuv3_channel *ipu_ch;
struct dmfc_channel *dmfc;
struct ipu_dp *dp;
int dma;
int dp_flow;
int x;
int y;
bool enabled;
};
struct ipu_plane *ipu_plane_init(struct ipu_soc *ipu,
int dma, int dp);
/* Init IDMAC, DMFC, DP */
int ipu_plane_mode_set(struct ipu_plane *ipu_plane,
struct fb_videomode *mode,
struct fb_info *info, u32 pixel_format,
int crtc_x, int crtc_y,
unsigned int crtc_w, unsigned int crtc_h,
uint32_t src_x, uint32_t src_y,
uint32_t src_w, uint32_t src_h);
void ipu_plane_enable(struct ipu_plane *plane);
void ipu_plane_disable(struct ipu_plane *plane);
int ipu_plane_set_base(struct ipu_plane *ipu_plane, struct fb_info *info,
int x, int y);
int ipu_plane_get_resources(struct ipu_plane *plane);
void ipu_plane_put_resources(struct ipu_plane *plane);
int ipu_plane_irq(struct ipu_plane *plane);
#endif