blob: 52cdee7cd450e336fa87bd046727c33afccc842e [file] [log] [blame]
/* linux/drivers/video/s3c-fb.c
*
* Copyright 2008 Openmoko Inc.
* Copyright 2008-2010 Simtec Electronics
* Ben Dooks <ben@simtec.co.uk>
* http://armlinux.simtec.co.uk/
*
* Samsung SoC Framebuffer driver
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software FoundatIon.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/clk.h>
#include <linux/fb.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/interrupt.h>
#include <linux/pm_runtime.h>
#include <linux/delay.h>
#include <linux/kthread.h>
#include <media/v4l2-device.h>
#include <media/exynos_mc.h>
#include <mach/exynos5_bus.h>
#include <mach/map.h>
#include <plat/regs-fb-v4.h>
#include <plat/fb.h>
#include <linux/dma-buf.h>
#include <linux/exynos_ion.h>
#include <linux/ion.h>
#include <linux/highmem.h>
#include <linux/memblock.h>
#include <linux/sw_sync.h>
#include <plat/devs.h>
#include <plat/iovmm.h>
#include <plat/sysmmu.h>
#include <mach/sysmmu.h>
#include <video/adf.h>
#include <video/adf_client.h>
#include <video/adf_fbdev.h>
#include <video/adf_format.h>
#include <video/adf_memblock.h>
#ifdef CONFIG_DEBUG_FS
#include <linux/debugfs.h>
#endif
/* note, the previous use of <mach/regs-fb.h> to get platform specific data
* has been replaced by using the platform device name to pick the correct
* configuration data for the system.
*/
#ifdef CONFIG_FB_S3C_DEBUG_REGWRITE
#undef writel
#define writel(v, r) do { \
printk(KERN_DEBUG "%s: %08x => %p\n", __func__, (unsigned int)v, r); \
__raw_writel(v, r); \
} while (0)
#endif /* FB_S3C_DEBUG_REGWRITE */
#define VSYNC_TIMEOUT_MSEC 50
#define MAX_BW_PER_WINDOW (2560 * 1600 * 4 * 60)
struct s3c_fb;
extern struct ion_device *ion_exynos;
#define VALID_BPP(x) (1 << ((x) - 1))
#define OSD_BASE(win, variant) ((variant).osd + ((win) * (variant).osd_stride))
#define VIDOSD_A(win, variant) (OSD_BASE(win, variant) + 0x00)
#define VIDOSD_B(win, variant) (OSD_BASE(win, variant) + 0x04)
#define VIDOSD_C(win, variant) (OSD_BASE(win, variant) + 0x08)
#define VIDOSD_D(win, variant) (OSD_BASE(win, variant) + 0x0C)
/**
* struct s3c_fb_variant - fb variant information
* @is_2443: Set if S3C2443/S3C2416 style hardware.
* @nr_windows: The number of windows.
* @vidtcon: The base for the VIDTCONx registers
* @wincon: The base for the WINxCON registers.
* @winmap: The base for the WINxMAP registers.
* @keycon: The abse for the WxKEYCON registers.
* @buf_start: Offset of buffer start registers.
* @buf_size: Offset of buffer size registers.
* @buf_end: Offset of buffer end registers.
* @osd: The base for the OSD registers.
* @palette: Address of palette memory, or 0 if none.
* @has_prtcon: Set if has PRTCON register.
* @has_shadowcon: Set if has SHADOWCON register.
* @has_blendcon: Set if has BLENDCON register.
* @has_alphacon: Set if has VIDWALPHA register.
* @has_clksel: Set if VIDCON0 register has CLKSEL bit.
* @has_fixvclk: Set if VIDCON1 register has FIXVCLK bits.
*/
struct s3c_fb_variant {
unsigned int is_2443:1;
unsigned short nr_windows;
unsigned int vidtcon;
unsigned short wincon;
unsigned short winmap;
unsigned short keycon;
unsigned short buf_start;
unsigned short buf_end;
unsigned short buf_size;
unsigned short osd;
unsigned short osd_stride;
unsigned short palette[S3C_FB_MAX_WIN];
unsigned int has_prtcon:1;
unsigned int has_shadowcon:1;
unsigned int has_blendcon:1;
unsigned int has_alphacon:1;
unsigned int has_clksel:1;
unsigned int has_fixvclk:1;
};
/**
* struct s3c_fb_win_variant
* @has_osd_c: Set if has OSD C register.
* @has_osd_d: Set if has OSD D register.
* @has_osd_alpha: Set if can change alpha transparency for a window.
* @palette_sz: Size of palette in entries.
* @palette_16bpp: Set if palette is 16bits wide.
* @osd_size_off: If != 0, supports setting up OSD for a window; the appropriate
* register is located at the given offset from OSD_BASE.
* @valid_bpp: 1 bit per BPP setting to show valid bits-per-pixel.
*
* valid_bpp bit x is set if (x+1)BPP is supported.
*/
struct s3c_fb_win_variant {
unsigned int has_osd_c:1;
unsigned int has_osd_d:1;
unsigned int has_osd_alpha:1;
unsigned int palette_16bpp:1;
unsigned short osd_size_off;
unsigned short palette_sz;
u32 valid_bpp;
};
/**
* struct s3c_fb_driverdata - per-device type driver data for init time.
* @variant: The variant information for this driver.
* @win: The window information for each window.
*/
struct s3c_fb_driverdata {
struct s3c_fb_variant variant;
struct s3c_fb_win_variant *win[S3C_FB_MAX_WIN];
};
struct s3c_reg_data {
u32 shadowcon;
u32 wincon[S3C_FB_MAX_WIN];
u32 win_rgborder[S3C_FB_MAX_WIN];
u32 winmap[S3C_FB_MAX_WIN];
u32 vidosd_a[S3C_FB_MAX_WIN];
u32 vidosd_b[S3C_FB_MAX_WIN];
u32 vidosd_c[S3C_FB_MAX_WIN];
u32 vidosd_d[S3C_FB_MAX_WIN];
u32 vidw_alpha0[S3C_FB_MAX_WIN];
u32 vidw_alpha1[S3C_FB_MAX_WIN];
u32 blendeq[S3C_FB_MAX_WIN - 1];
u32 vidw_buf_start[S3C_FB_MAX_WIN];
u32 vidw_buf_end[S3C_FB_MAX_WIN];
u32 vidw_buf_size[S3C_FB_MAX_WIN];
dma_addr_t dma_addr[S3C_FB_MAX_WIN];
};
/**
* struct s3c_fb_win - per window private data for each framebuffer.
* @windata: The platform data supplied for the window configuration.
* @parent: The hardware that this window is part of.
* @varint: The variant information for this window.
* @index: The window number of this window.
*/
struct s3c_fb_win {
struct s3c_fb_pd_win *windata;
struct s3c_fb *parent;
struct s3c_fb_win_variant variant;
unsigned int index;
};
#ifdef CONFIG_DEBUG_FS
#define S3C_FB_DEBUG_FIFO_TIMESTAMPS 32
#define S3C_FB_DEBUG_REGS_SIZE 0x0280
struct s3c_fb_debug {
ktime_t fifo_timestamps[S3C_FB_DEBUG_FIFO_TIMESTAMPS];
unsigned int num_timestamps;
unsigned int first_timestamp;
u8 regs_at_underflow[S3C_FB_DEBUG_REGS_SIZE];
};
#endif
/**
* struct s3c_fb - overall hardware state of the hardware
* @slock: The spinlock protection for this data sturcture.
* @dev: The device that we bound to, for printing, etc.
* @bus_clk: The clk (hclk) feeding our interface and possibly pixclk.
* @lcd_clk: The clk (sclk) feeding pixclk.
* @regs: The mapped hardware registers.
* @variant: Variant information for this hardware.
* @pdata: The platform configuration data passed with the device.
* @windows: The hardware windows that have been claimed.
* @irq_no: IRQ line number
*/
struct s3c_fb {
struct adf_device base;
struct adf_overlay_engine eng;
struct adf_interface intf;
struct adf_fbdev fbdev;
spinlock_t slock;
struct device *dev;
struct clk *bus_clk;
struct clk *lcd_clk;
void __iomem *regs;
struct s3c_fb_variant variant;
struct s3c_fb_platdata *pdata;
struct s3c_fb_win *windows[S3C_FB_MAX_WIN];
int irq_no;
struct ion_client *fb_ion_client;
#ifdef CONFIG_DEBUG_FS
struct dentry *debug_dentry;
struct s3c_fb_debug debug_data;
#endif
struct exynos5_bus_mif_handle *fb_mif_handle;
struct exynos5_bus_int_handle *fb_int_handle;
struct s3c_reg_data *deferred_update;
};
#define adf_device_to_s3c_fb(p) \
container_of((p), struct s3c_fb, base)
#define adf_overlay_engine_to_s3c_fb(p) \
container_of((p), struct s3c_fb, eng)
#define adf_interface_to_s3c_fb(p) \
container_of((p), struct s3c_fb, intf)
static void s3c_fb_dump_registers(struct s3c_fb *sfb)
{
#ifdef CONFIG_FB_EXYNOS_FIMD_V8
pr_err("dumping registers\n");
print_hex_dump(KERN_ERR, "", DUMP_PREFIX_ADDRESS, 32, 4, sfb->regs,
0x0280, false);
pr_err("...\n");
print_hex_dump(KERN_ERR, "", DUMP_PREFIX_ADDRESS, 32, 4,
sfb->regs + SHD_VIDW_BUF_START(0), 0x74, false);
pr_err("...\n");
print_hex_dump(KERN_ERR, "", DUMP_PREFIX_ADDRESS, 32, 4,
sfb->regs + 0x20000, 0x20, false);
#endif
}
static bool s3c_fb_validate_x_alignment(struct s3c_fb *sfb, int x, u32 w,
u32 bits_per_pixel)
{
uint8_t pixel_alignment = 32 / bits_per_pixel;
if (x % pixel_alignment) {
dev_err(sfb->dev, "left X coordinate not properly aligned to %u-pixel boundary (bpp = %u, x = %u)\n",
pixel_alignment, bits_per_pixel, x);
return 0;
}
if ((x + w) % pixel_alignment) {
dev_err(sfb->dev, "right X coordinate not properly aligned to %u-pixel boundary (bpp = %u, x = %u, w = %u)\n",
pixel_alignment, bits_per_pixel, x, w);
return 0;
}
return 1;
}
/**
* s3c_fb_calc_pixclk() - calculate the divider to create the pixel clock.
* @sfb: The hardware state.
* @pixclock: The pixel clock wanted, in kHz.
*
* Given the specified pixel clock, work out the necessary divider to get
* close to the output frequency.
*/
static int s3c_fb_calc_pixclk(struct s3c_fb *sfb, unsigned int pixclk)
{
unsigned long clk;
if (sfb->variant.has_clksel)
clk = clk_get_rate(sfb->bus_clk);
else
clk = clk_get_rate(sfb->lcd_clk);
return clk / pixclk / 1000;
}
/**
* shadow_protect_win() - disable updating values from shadow registers at vsync
*
* @win: window to protect registers for
* @protect: 1 to protect (disable updates)
*/
static void shadow_protect_win(struct s3c_fb_win *win, bool protect)
{
struct s3c_fb *sfb = win->parent;
u32 reg;
if (protect) {
if (sfb->variant.has_prtcon) {
writel(PRTCON_PROTECT, sfb->regs + PRTCON);
} else if (sfb->variant.has_shadowcon) {
reg = readl(sfb->regs + SHADOWCON);
writel(reg | SHADOWCON_WINx_PROTECT(win->index),
sfb->regs + SHADOWCON);
}
} else {
if (sfb->variant.has_prtcon) {
writel(0, sfb->regs + PRTCON);
} else if (sfb->variant.has_shadowcon) {
reg = readl(sfb->regs + SHADOWCON);
writel(reg & ~SHADOWCON_WINx_PROTECT(win->index),
sfb->regs + SHADOWCON);
}
}
}
static inline u32 vidw_buf_size(u32 xres, u32 line_length, u32 bits_per_pixel)
{
u32 pagewidth = (xres * bits_per_pixel) >> 3;
return VIDW_BUF_SIZE_OFFSET(line_length - pagewidth) |
VIDW_BUF_SIZE_PAGEWIDTH(pagewidth) |
VIDW_BUF_SIZE_OFFSET_E(line_length - pagewidth) |
VIDW_BUF_SIZE_PAGEWIDTH_E(pagewidth);
}
static inline u32 vidosd_a(int x, int y)
{
return VIDOSDxA_TOPLEFT_X(x) |
VIDOSDxA_TOPLEFT_Y(y) |
VIDOSDxA_TOPLEFT_X_E(x) |
VIDOSDxA_TOPLEFT_Y_E(y);
}
static inline u32 vidosd_b(int x, int y, u32 xres, u32 yres)
{
return VIDOSDxB_BOTRIGHT_X(x + xres - 1) |
VIDOSDxB_BOTRIGHT_Y(y + yres - 1) |
VIDOSDxB_BOTRIGHT_X_E(x + xres - 1) |
VIDOSDxB_BOTRIGHT_Y_E(y + yres - 1);
}
static inline u32 vidosd_c(u8 r0, u8 g0, u8 b0, u8 r1, u8 g1, u8 b1)
{
return VIDOSDxC_ALPHA0_R_H(r0) |
VIDOSDxC_ALPHA0_G_H(g0) |
VIDOSDxC_ALPHA0_B_H(b0) |
VIDOSDxC_ALPHA1_R_H(r1) |
VIDOSDxC_ALPHA1_G_H(g1) |
VIDOSDxC_ALPHA1_B_H(b1);
}
static inline u32 vidw_alpha(bool has_osd_alpha, u8 r, u8 g, u8 b)
{
if (has_osd_alpha)
return VIDWxALPHAx_R_L(r) |
VIDWxALPHAx_G_L(g) |
VIDWxALPHAx_B_L(b);
else
return VIDWxALPHAx_R(r) |
VIDWxALPHAx_G(g) |
VIDWxALPHAx_B(b);
}
static inline u32 wincon(u32 bits_per_pixel, u32 transp_length, u32 red_length)
{
u32 data = 0;
switch (bits_per_pixel) {
case 1:
data |= WINCON0_BPPMODE_1BPP;
data |= WINCONx_BITSWP;
data |= WINCONx_BURSTLEN_4WORD;
break;
case 2:
data |= WINCON0_BPPMODE_2BPP;
data |= WINCONx_BITSWP;
data |= WINCONx_BURSTLEN_8WORD;
break;
case 4:
data |= WINCON0_BPPMODE_4BPP;
data |= WINCONx_BITSWP;
data |= WINCONx_BURSTLEN_8WORD;
break;
case 8:
if (transp_length != 0)
data |= WINCON1_BPPMODE_8BPP_1232;
else
data |= WINCON0_BPPMODE_8BPP_PALETTE;
data |= WINCONx_BURSTLEN_8WORD;
data |= WINCONx_BYTSWP;
break;
case 16:
if (transp_length == 1)
data |= WINCON1_BPPMODE_16BPP_A1555
| WINCON1_BLD_PIX;
else if (transp_length == 4)
data |= WINCON1_BPPMODE_16BPP_A4444
| WINCON1_BLD_PIX;
else
data |= WINCON0_BPPMODE_16BPP_565;
data |= WINCONx_HAWSWP;
data |= WINCONx_BURSTLEN_16WORD;
break;
case 24:
case 32:
if (red_length == 6) {
if (transp_length != 0)
data |= WINCON1_BPPMODE_19BPP_A1666;
else
data |= WINCON1_BPPMODE_18BPP_666;
} else if (transp_length == 1)
data |= WINCON1_BPPMODE_25BPP_A1888
| WINCON1_BLD_PIX;
else if ((transp_length == 4) ||
(transp_length == 8))
data |= WINCON1_BPPMODE_28BPP_A4888
| WINCON1_BLD_PIX;
else
data |= WINCON0_BPPMODE_24BPP_888;
data |= WINCONx_WSWP;
data |= WINCONx_BURSTLEN_16WORD;
break;
}
if (transp_length != 1)
data |= WINCON1_ALPHA_SEL;
return data;
}
static inline u32 blendeq(enum s3c_fb_blending blending, u8 transp_length)
{
u8 a, b;
if (transp_length == 1 && blending == S3C_FB_BLENDING_PREMULT)
blending = S3C_FB_BLENDING_COVERAGE;
switch (blending) {
case S3C_FB_BLENDING_NONE:
a = BLENDEQ_COEF_ONE;
b = BLENDEQ_COEF_ZERO;
break;
case S3C_FB_BLENDING_PREMULT:
a = BLENDEQ_COEF_ONE;
b = BLENDEQ_COEF_ONE_MINUS_ALPHA_A;
break;
case S3C_FB_BLENDING_COVERAGE:
a = BLENDEQ_COEF_ALPHA_A;
b = BLENDEQ_COEF_ONE_MINUS_ALPHA_A;
break;
default:
return 0;
}
return BLENDEQ_A_FUNC(a) |
BLENDEQ_B_FUNC(b) |
BLENDEQ_P_FUNC(BLENDEQ_COEF_ZERO) |
BLENDEQ_Q_FUNC(BLENDEQ_COEF_ZERO);
}
static int s3c_fb_modeset(struct adf_interface *intf,
struct drm_mode_modeinfo *win_mode)
{
struct s3c_fb *sfb = adf_interface_to_s3c_fb(intf);
int clkdiv = s3c_fb_calc_pixclk(sfb, win_mode->clock);
u32 data = sfb->pdata->vidcon0;
data &= ~(VIDCON0_CLKVAL_F_MASK | VIDCON0_CLKDIR);
if (clkdiv > 1)
data |= VIDCON0_CLKVAL_F(clkdiv-1) | VIDCON0_CLKDIR;
else
data &= ~VIDCON0_CLKDIR;
/* write the timing data to the panel */
if (sfb->variant.is_2443)
data |= (1 << 5);
data |= VIDCON0_ENVID | VIDCON0_ENVID_F;
writel(data, sfb->regs + VIDCON0);
data = readl(sfb->regs + VIDCON2);
data &= ~(VIDCON2_RGB_ORDER_E_MASK | VIDCON2_RGB_ORDER_O_MASK);
data |= VIDCON2_RGB_ORDER_E_BGR | VIDCON2_RGB_ORDER_O_BGR;
writel(data, sfb->regs + VIDCON2);
/* Set alpha value width */
if (sfb->variant.has_blendcon) {
data = readl(sfb->regs + BLENDCON);
data &= ~BLENDCON_NEW_MASK;
data |= BLENDCON_NEW_8BIT_ALPHA_VALUE;
writel(data, sfb->regs + BLENDCON);
}
data = VIDTCON0_VBPD(win_mode->vtotal - win_mode->vsync_end - 1)
| VIDTCON0_VFPD(win_mode->vsync_start -
win_mode->vdisplay - 1)
| VIDTCON0_VSPW(win_mode->vsync_end -
win_mode->vsync_start - 1);
writel(data, sfb->regs + sfb->variant.vidtcon);
data = VIDTCON1_HBPD(win_mode->htotal - win_mode->hsync_end - 1)
| VIDTCON1_HFPD(win_mode->hsync_start -
win_mode->hdisplay - 1)
| VIDTCON1_HSPW(win_mode->hsync_end -
win_mode->hsync_start - 1);
/* VIDTCON1 */
writel(data, sfb->regs + sfb->variant.vidtcon + 4);
data = VIDTCON2_LINEVAL(win_mode->vdisplay - 1)
| VIDTCON2_HOZVAL(win_mode->hdisplay - 1)
| VIDTCON2_LINEVAL_E(win_mode->vdisplay - 1)
| VIDTCON2_HOZVAL_E(win_mode->hdisplay - 1);
/* VIDTCON2 */
writel(data, sfb->regs + sfb->variant.vidtcon + 8);
return 0;
}
static unsigned int s3c_fb_calc_bandwidth(u32 w, u32 h, u32 bits_per_pixel, int fps)
{
unsigned int bw = w * h;
bw *= DIV_ROUND_UP(bits_per_pixel, 8);
bw *= fps;
return bw;
}
static int s3c_fb_screen_size(struct adf_interface *intf, u16 *width_mm,
u16 *height_mm)
{
struct s3c_fb *sfb = adf_interface_to_s3c_fb(intf);
*width_mm = sfb->pdata->win[0]->width;
*width_mm = sfb->pdata->win[0]->height;
return 0;
}
static int s3c_fb_enable(struct s3c_fb *sfb);
static int s3c_fb_disable(struct s3c_fb *sfb);
/**
* s3c_fb_blank() - blank or unblank the given window
* @blank_mode: The blank state from FB_BLANK_*
* @info: The framebuffer to blank.
*
* Framebuffer layer request to change the power state.
*/
static int s3c_fb_blank(struct adf_interface *intf, u8 dpms_state)
{
struct s3c_fb *sfb = adf_interface_to_s3c_fb(intf);
int ret = 0;
dev_dbg(sfb->dev, "dpms state %d\n", dpms_state);
pm_runtime_get_sync(sfb->dev);
switch (dpms_state) {
case DRM_MODE_DPMS_OFF:
ret = s3c_fb_disable(sfb);
break;
case DRM_MODE_DPMS_ON:
ret = s3c_fb_enable(sfb);
break;
default:
ret = -EINVAL;
}
pm_runtime_put_sync(sfb->dev);
if (ret == 0) {
struct fb_event event;
int fb_state = (dpms_state == DRM_MODE_DPMS_OFF) ?
FB_BLANK_POWERDOWN : FB_BLANK_UNBLANK;
event.info = NULL;
event.data = &fb_state;
fb_notifier_call_chain(FB_EVENT_BLANK, &event);
}
return ret;
}
/**
* s3c_fb_enable_irq() - enable framebuffer interrupts
* @sfb: main hardware state
*/
static void s3c_fb_enable_irq(struct s3c_fb *sfb)
{
void __iomem *regs = sfb->regs;
u32 irq_ctrl_reg;
pm_runtime_get_sync(sfb->dev);
irq_ctrl_reg = readl(regs + VIDINTCON0);
irq_ctrl_reg |= VIDINTCON0_INT_ENABLE;
irq_ctrl_reg |= VIDINTCON0_INT_FRAME;
#ifdef CONFIG_DEBUG_FS
irq_ctrl_reg &= ~VIDINTCON0_FIFOLEVEL_MASK;
irq_ctrl_reg |= VIDINTCON0_FIFOLEVEL_EMPTY;
irq_ctrl_reg |= VIDINTCON0_INT_FIFO;
irq_ctrl_reg |= VIDINTCON0_FIFIOSEL_WINDOW0;
irq_ctrl_reg |= VIDINTCON0_FIFIOSEL_WINDOW1;
irq_ctrl_reg |= VIDINTCON0_FIFIOSEL_WINDOW2;
irq_ctrl_reg |= VIDINTCON0_FIFIOSEL_WINDOW3;
irq_ctrl_reg |= VIDINTCON0_FIFIOSEL_WINDOW4;
#endif
irq_ctrl_reg &= ~VIDINTCON0_FRAMESEL0_MASK;
irq_ctrl_reg |= VIDINTCON0_FRAMESEL0_VSYNC;
irq_ctrl_reg &= ~VIDINTCON0_FRAMESEL1_MASK;
irq_ctrl_reg |= VIDINTCON0_FRAMESEL1_NONE;
writel(irq_ctrl_reg, regs + VIDINTCON0);
pm_runtime_put_sync(sfb->dev);
}
/**
* s3c_fb_disable_irq() - disable framebuffer interrupts
* @sfb: main hardware state
*/
static void s3c_fb_disable_irq(struct s3c_fb *sfb)
{
void __iomem *regs = sfb->regs;
u32 irq_ctrl_reg;
pm_runtime_get_sync(sfb->dev);
irq_ctrl_reg = readl(regs + VIDINTCON0);
#ifdef CONFIG_DEBUG_FS
irq_ctrl_reg &= VIDINTCON0_INT_FIFO;
#endif
irq_ctrl_reg &= ~VIDINTCON0_INT_FRAME;
irq_ctrl_reg &= ~VIDINTCON0_INT_ENABLE;
writel(irq_ctrl_reg, regs + VIDINTCON0);
pm_runtime_put_sync(sfb->dev);
}
static void s3c_fb_set_vsync(struct adf_interface *intf, bool enabled)
{
struct s3c_fb *sfb = adf_interface_to_s3c_fb(intf);
if (enabled)
s3c_fb_enable_irq(sfb);
else
s3c_fb_disable_irq(sfb);
}
static bool s3c_fb_supports_event(struct adf_obj *obj, enum adf_event_type type)
{
return obj->type == ADF_OBJ_INTERFACE && type == ADF_EVENT_VSYNC;
}
static void s3c_fb_set_event(struct adf_obj *obj, enum adf_event_type type,
bool enabled)
{
s3c_fb_set_vsync(adf_obj_to_interface(obj), enabled);
}
/**
* sfb_fb_log_fifo_underflow_locked - log a FIFO underflow event. Caller must
* hold the driver's spin lock while calling.
*
* @sfb: main hardware state
* @timestamp: timestamp of the FIFO underflow event
*/
void s3c_fb_log_fifo_underflow_locked(struct s3c_fb *sfb, ktime_t timestamp)
{
#ifdef CONFIG_DEBUG_FS
unsigned int idx = sfb->debug_data.num_timestamps %
S3C_FB_DEBUG_FIFO_TIMESTAMPS;
sfb->debug_data.fifo_timestamps[idx] = timestamp;
sfb->debug_data.num_timestamps++;
if (sfb->debug_data.num_timestamps > S3C_FB_DEBUG_FIFO_TIMESTAMPS) {
sfb->debug_data.first_timestamp++;
sfb->debug_data.first_timestamp %= S3C_FB_DEBUG_FIFO_TIMESTAMPS;
}
memcpy(sfb->debug_data.regs_at_underflow, sfb->regs,
sizeof(sfb->debug_data.regs_at_underflow));
#endif
}
static irqreturn_t s3c_fb_irq(int irq, void *dev_id)
{
struct s3c_fb *sfb = dev_id;
void __iomem *regs = sfb->regs;
u32 irq_sts_reg;
ktime_t timestamp = ktime_get();
irq_sts_reg = readl(regs + VIDINTCON1);
if (irq_sts_reg & VIDINTCON1_INT_FRAME) {
/* VSYNC interrupt, accept it */
writel(VIDINTCON1_INT_FRAME, regs + VIDINTCON1);
adf_vsync_notify(&sfb->intf, timestamp);
}
if (irq_sts_reg & VIDINTCON1_INT_FIFO) {
writel(VIDINTCON1_INT_FIFO, regs + VIDINTCON1);
spin_lock(&sfb->slock);
s3c_fb_log_fifo_underflow_locked(sfb, timestamp);
spin_unlock(&sfb->slock);
}
return IRQ_HANDLED;
}
static u32 s3c_fb_red_length(u32 format)
{
switch (format) {
case DRM_FORMAT_RGBA8888:
case DRM_FORMAT_RGBX8888:
case DRM_FORMAT_BGRA8888:
return 8;
case DRM_FORMAT_RGBA5551:
return 5;
case DRM_FORMAT_RGB565:
return 5;
default:
pr_warn("s3c-fb: unrecognized pixel format %u\n", format);
return 0;
}
}
static u32 s3c_fb_transp_length(u32 format)
{
switch (format) {
case DRM_FORMAT_RGBA8888:
case DRM_FORMAT_BGRA8888:
return 8;
case DRM_FORMAT_RGBA5551:
return 1;
case DRM_FORMAT_RGBX8888:
case DRM_FORMAT_RGB565:
return 0;
default:
pr_warn("s3c-fb: unrecognized pixel format %u\n", format);
return 0;
}
}
static u32 s3c_fb_rgborder(u32 format)
{
switch (format) {
case DRM_FORMAT_RGBX8888:
case DRM_FORMAT_RGBA8888:
case DRM_FORMAT_RGBA5551:
return WIN_RGB_ORDER_RGB;
case DRM_FORMAT_RGB565:
case DRM_FORMAT_BGRA8888:
return WIN_RGB_ORDER_BGR;
default:
pr_warn("s3c-fb: unrecognized pixel format %u\n", format);
return 0;
}
}
static int s3c_fb_validate_buffer(struct s3c_fb *sfb, struct s3c_fb_win *win,
struct adf_buffer *fb, struct adf_buffer_mapping *mapping,
struct s3c_fb_win_config *win_config, struct s3c_reg_data *regs)
{
unsigned short win_no = win->index;
size_t window_size;
u8 alpha0, alpha1;
u8 bpp = adf_format_bpp(fb->format);
u32 red_length = s3c_fb_red_length(fb->format);
u32 transp_length = s3c_fb_transp_length(fb->format);
if (win_config->blending >= S3C_FB_BLENDING_MAX) {
dev_err(sfb->dev, "unknown blending %u\n",
win_config->blending);
return -EINVAL;
}
if (win_no == 0 && win_config->blending != S3C_FB_BLENDING_NONE) {
dev_err(sfb->dev, "blending not allowed on window 0\n");
return -EINVAL;
}
if (fb->pitch[0] < 128) {
dev_err(sfb->dev, "window must be at least 128 bytes wide (pitch = %u)\n",
fb->pitch[0]);
return -EINVAL;
}
if (!s3c_fb_validate_x_alignment(sfb, win_config->x, fb->w,
bpp)) {
return -EINVAL;
}
window_size = fb->pitch[0] * (fb->h + 1);
if (fb->offset[0] + window_size > fb->dma_bufs[0]->size) {
dev_err(sfb->dev, "window goes past end of buffer (width = %u, height = %u, pitch = %u, offset = %u, buf_size = %u)\n",
fb->w, fb->h, fb->pitch[0], fb->offset[0],
fb->dma_bufs[0]->size);
return -EINVAL;
}
regs->dma_addr[win_no] = iovmm_map(&s5p_device_fimd1.dev,
mapping->sg_tables[0]->sgl, 0, fb->dma_bufs[0]->size);
if (!regs->dma_addr[win_no] || IS_ERR_VALUE(regs->dma_addr[win_no])) {
dev_err(sfb->dev, "iovmm_map() failed: %d\n",
regs->dma_addr[win_no]);
regs->dma_addr[win_no] = 0;
return -ENOMEM;
}
regs->win_rgborder[win_no] = s3c_fb_rgborder(fb->format);
regs->vidw_buf_start[win_no] = regs->dma_addr[win_no] + fb->offset[0];
regs->vidw_buf_end[win_no] = regs->vidw_buf_start[win_no] + window_size;
regs->vidw_buf_size[win_no] = vidw_buf_size(fb->w, fb->pitch[0], bpp);
regs->vidosd_a[win_no] = vidosd_a(win_config->x, win_config->y);
regs->vidosd_b[win_no] = vidosd_b(win_config->x, win_config->y, fb->w,
fb->h);
if (transp_length == 1 &&
win_config->blending == S3C_FB_BLENDING_NONE) {
alpha0 = 0xff;
alpha1 = 0xff;
}
else {
alpha0 = 0;
alpha1 = 0xff;
}
if (win->variant.has_osd_alpha)
regs->vidosd_c[win_no] = vidosd_c(alpha0, alpha0, alpha0,
alpha1, alpha1, alpha1);
regs->vidw_alpha0[win_no] = vidw_alpha(win->variant.has_osd_alpha,
alpha0, alpha0, alpha0);
regs->vidw_alpha1[win_no] = vidw_alpha(win->variant.has_osd_alpha,
alpha1, alpha1, alpha1);
if (win->variant.osd_size_off) {
u32 size = fb->w * fb->h;
if (win->variant.has_osd_alpha)
regs->vidosd_d[win_no] = size;
else
regs->vidosd_c[win_no] = size;
}
regs->shadowcon |= SHADOWCON_CHx_ENABLE(win_no);
regs->wincon[win_no] = wincon(bpp, transp_length, red_length);
if (win_no)
regs->blendeq[win_no - 1] = blendeq(win_config->blending,
transp_length);
return 0;
}
static void s3c_fb_state_free(struct adf_device *dev, void *driver_data)
{
struct s3c_fb *sfb = adf_device_to_s3c_fb(dev);
struct s3c_reg_data *regs = driver_data;
size_t i;
for (i = 0; i < ARRAY_SIZE(regs->dma_addr); i++)
if (regs->dma_addr[i])
iovmm_unmap(sfb->dev, regs->dma_addr[i]);
kfree(regs);
}
static int s3c_fb_validate(struct adf_device *dev, struct adf_post *cfg,
void **driver_data)
{
struct s3c_fb *sfb = adf_device_to_s3c_fb(dev);
struct s3c_fb_win_config_data *win_config;
int ret = 0;
unsigned short i;
struct s3c_reg_data *regs;
unsigned int bw = 0;
if (cfg->n_bufs > sfb->variant.nr_windows) {
dev_err(sfb->dev, "post config has %u buffers, but hardware has only %u windows\n",
cfg->n_bufs, sfb->variant.nr_windows);
return -EINVAL;
}
if (cfg->custom_data_size != sizeof(*win_config)) {
dev_err(sfb->dev, "expected %u bytes of custom post data, got %d bytes\n",
sizeof(*win_config), cfg->custom_data_size);
return -EINVAL;
}
win_config = cfg->custom_data;
regs = kzalloc(sizeof(struct s3c_reg_data), GFP_KERNEL);
if (!regs) {
dev_err(sfb->dev, "could not allocate s3c_reg_data\n");
return -ENOMEM;
}
for (i = 0; i < sfb->variant.nr_windows && !ret; i++) {
struct s3c_fb_win_config *config = &win_config->config[i];
struct s3c_fb_win *win = sfb->windows[i];
struct adf_buffer *buf = NULL;
struct adf_buffer_mapping *mapping;
bool enabled = 0;
u32 color_map = WINxMAP_MAP | WINxMAP_MAP_COLOUR(0);
switch (config->state) {
case S3C_FB_WIN_STATE_DISABLED:
break;
case S3C_FB_WIN_STATE_COLOR:
enabled = 1;
color_map |= WINxMAP_MAP_COLOUR(config->color);
regs->vidosd_a[i] = vidosd_a(config->x, config->y);
regs->vidosd_b[i] = vidosd_b(config->x, config->y,
config->w, config->h);
break;
case S3C_FB_WIN_STATE_BUFFER:
if (config->fb >= cfg->n_bufs) {
dev_err(sfb->dev, "window %u refers to fb %u but config only contains %u bufs\n",
i, config->fb, cfg->n_bufs);
ret = -EINVAL;
break;
}
buf = &cfg->bufs[config->fb];
mapping = &cfg->mappings[config->fb];
ret = s3c_fb_validate_buffer(sfb, win, buf, mapping,
config, regs);
if (!ret) {
enabled = 1;
color_map = 0;
}
break;
default:
dev_warn(sfb->dev, "unrecognized window state %u",
config->state);
ret = -EINVAL;
break;
}
if (enabled)
regs->wincon[i] |= WINCONx_ENWIN;
else
regs->wincon[i] &= ~WINCONx_ENWIN;
regs->winmap[i] = color_map;
if (enabled && buf) {
bw += s3c_fb_calc_bandwidth(buf->w, buf->h,
adf_format_bpp(buf->format),
sfb->intf.current_mode.vrefresh);
}
}
dev_dbg(sfb->dev, "Total BW = %d Mbits, Max BW per window = %d Mbits\n",
bw / (1024 * 1024), MAX_BW_PER_WINDOW / (1024 * 1024));
if (bw > MAX_BW_PER_WINDOW)
exynos5_mif_multiple_windows(true);
else
exynos5_mif_multiple_windows(false);
if (ret)
s3c_fb_state_free(dev, regs);
else
*driver_data = regs;
return ret;
}
static void __s3c_fb_update_regs(struct s3c_fb *sfb, struct s3c_reg_data *regs)
{
unsigned short i;
for (i = 0; i < sfb->variant.nr_windows; i++)
shadow_protect_win(sfb->windows[i], 1);
for (i = 0; i < sfb->variant.nr_windows; i++) {
writel(regs->wincon[i], sfb->regs + WINCON(i));
writel(regs->win_rgborder[i], sfb->regs + WIN_RGB_ORDER(i));
writel(regs->winmap[i], sfb->regs + WINxMAP(i));
writel(regs->vidosd_a[i],
sfb->regs + VIDOSD_A(i, sfb->variant));
writel(regs->vidosd_b[i],
sfb->regs + VIDOSD_B(i, sfb->variant));
if (sfb->windows[i]->variant.has_osd_c)
writel(regs->vidosd_c[i],
sfb->regs + VIDOSD_C(i, sfb->variant));
if (sfb->windows[i]->variant.has_osd_d)
writel(regs->vidosd_d[i],
sfb->regs + VIDOSD_D(i, sfb->variant));
writel(regs->vidw_alpha0[i],
sfb->regs + VIDW_ALPHA0(i));
writel(regs->vidw_alpha1[i],
sfb->regs + VIDW_ALPHA1(i));
writel(regs->vidw_buf_start[i],
sfb->regs + VIDW_BUF_START(i));
writel(regs->vidw_buf_end[i],
sfb->regs + VIDW_BUF_END(i));
writel(regs->vidw_buf_size[i],
sfb->regs + VIDW_BUF_SIZE(i));
if (i)
writel(regs->blendeq[i - 1], sfb->regs + BLENDEQ(i));
}
if (sfb->variant.has_shadowcon)
writel(regs->shadowcon, sfb->regs + SHADOWCON);
for (i = 0; i < sfb->variant.nr_windows; i++)
shadow_protect_win(sfb->windows[i], 0);
}
static void s3c_fb_update_regs(struct s3c_fb *sfb, struct s3c_reg_data *regs)
{
bool wait_for_vsync;
int count = 100;
int i;
pm_runtime_get_sync(sfb->dev);
do {
__s3c_fb_update_regs(sfb, regs);
adf_vsync_wait(&sfb->intf, VSYNC_TIMEOUT_MSEC);
wait_for_vsync = false;
for (i = 0; i < sfb->variant.nr_windows; i++) {
u32 new_start = regs->vidw_buf_start[i];
u32 shadow_start = readl(sfb->regs +
SHD_VIDW_BUF_START(i));
if (unlikely(new_start != shadow_start)) {
wait_for_vsync = true;
break;
}
}
} while (wait_for_vsync && count--);
if (wait_for_vsync) {
pr_err("%s: failed to update registers\n", __func__);
for (i = 0; i < sfb->variant.nr_windows; i++)
pr_err("window %d new value %08x register value %08x\n",
i, regs->vidw_buf_start[i],
readl(sfb->regs + SHD_VIDW_BUF_START(i)));
}
pm_runtime_put_sync(sfb->dev);
}
static void s3c_fb_post(struct adf_device *dev, struct adf_post *post,
void *driver_data)
{
struct s3c_fb *sfb = adf_device_to_s3c_fb(dev);
struct s3c_reg_data *regs = driver_data;
if (sfb->intf.dpms_state == DRM_MODE_DPMS_OFF) {
/* s3c-fb registers can't be updated with the display off, so
defer the actual update until unblank */
sfb->deferred_update = regs;
} else {
sfb->deferred_update = NULL;
s3c_fb_update_regs(sfb, regs);
}
}
/**
* s3c_fb_missing_pixclock() - calculates pixel clock
* @mode: The video mode to change.
*
* Calculate the pixel clock when none has been given through platform data.
*/
static void __devinit s3c_fb_missing_pixclock(struct fb_videomode *mode)
{
u64 pixclk = 1000000000000ULL;
u32 div;
div = mode->left_margin + mode->hsync_len + mode->right_margin +
mode->xres;
div *= mode->upper_margin + mode->vsync_len + mode->lower_margin +
mode->yres;
#if defined(CONFIG_LCD_MIPI_S6E8AB0) /* this define will be delete after mipi lcd supports 60Hz */
div *= mode->refresh ? : 40;
#else
div *= mode->refresh ? : 60;
#endif
do_div(pixclk, div);
mode->pixclock = pixclk;
}
/**
* s3c_fb_alloc_simple_buffer() - allocate display memory for framebuffer window
* @sfb: The base resources for the hardware.
* @win: The window to initialise memory for.
*
* Allocate memory for the given framebuffer.
*/
static int __devinit s3c_fb_alloc_simple_buffer(struct adf_interface *intf,
u16 w, u16 h, u32 format, struct dma_buf **dma_buf, u32 *offset,
u32 *pitch)
{
struct s3c_fb *sfb = adf_interface_to_s3c_fb(intf);
size_t size;
struct ion_handle *handle;
dev_dbg(sfb->dev, "allocating memory for display\n");
*pitch = w * adf_format_bpp(format) / 8;
size = PAGE_ALIGN(*pitch * (h + 1));
dev_dbg(sfb->dev, "want %u bytes\n", size);
handle = ion_alloc(sfb->fb_ion_client, (size_t)size, 0,
EXYNOS_ION_HEAP_EXYNOS_MASK, 0);
if (IS_ERR(handle)) {
dev_err(sfb->dev, "failed to ion_alloc\n");
return PTR_ERR(handle);
}
*dma_buf = ion_share_dma_buf(sfb->fb_ion_client, handle);
ion_free(sfb->fb_ion_client, handle);
if (IS_ERR_OR_NULL(*dma_buf)) {
dev_err(sfb->dev, "ion_share_dma_buf() failed\n");
if (IS_ERR(*dma_buf))
return PTR_ERR(*dma_buf);
else
return -ENOMEM;
}
*offset = 0;
return 0;
}
/**
* s3c_fb_release_win() - release resources for a framebuffer window.
* @win: The window to cleanup the resources for.
*
* Release the resources that where claimed for the hardware window,
* such as the framebuffer instance and any memory claimed for it.
*/
static void s3c_fb_release_win(struct s3c_fb *sfb, struct s3c_fb_win *win)
{
u32 data;
if (sfb->variant.has_shadowcon) {
data = readl(sfb->regs + SHADOWCON);
data &= ~SHADOWCON_CHx_ENABLE(win->index);
data &= ~SHADOWCON_CHx_LOCAL_ENABLE(win->index);
writel(data, sfb->regs + SHADOWCON);
}
kfree(win);
}
/**
* s3c_fb_probe_win() - register an hardware window
* @sfb: The base resources for the hardware
* @variant: The variant information for this window.
* @res: Pointer to where to place the resultant window.
*
* Allocate and do the basic initialisation for one of the hardware's graphics
* windows.
*/
static int __devinit s3c_fb_probe_win(struct s3c_fb *sfb, unsigned int win_no,
struct s3c_fb_win_variant *variant,
struct s3c_fb_win **res)
{
struct s3c_fb_pd_win *windata;
struct s3c_fb_win *win;
dev_dbg(sfb->dev, "probing window %d, variant %p\n", win_no, variant);
windata = sfb->pdata->win[win_no];
WARN_ON(windata->max_bpp == 0);
win = kzalloc(sizeof(*win), GFP_KERNEL);
if (!win)
return -ENOMEM;
*res = win;
win->variant = *variant;
win->parent = sfb;
win->windata = windata;
win->index = win_no;
return 0;
}
/**
* s3c_fb_clear_win() - clear hardware window registers.
* @sfb: The base resources for the hardware.
* @win: The window to process.
*
* Reset the specific window registers to a known state.
*/
static void s3c_fb_clear_win(struct s3c_fb *sfb, int win)
{
void __iomem *regs = sfb->regs;
u32 reg;
writel(0, regs + sfb->variant.wincon + (win * 4));
writel(0, regs + VIDOSD_A(win, sfb->variant));
writel(0, regs + VIDOSD_B(win, sfb->variant));
writel(0, regs + VIDOSD_C(win, sfb->variant));
if (sfb->variant.has_shadowcon) {
reg = readl(sfb->regs + SHADOWCON);
reg &= ~SHADOWCON_CHx_ENABLE(win);
reg &= ~SHADOWCON_WINx_PROTECT(win);
writel(reg, sfb->regs + SHADOWCON);
}
reg = readl(sfb->regs + WINCON(win));
reg &= ~WINCONx_ENWIN;
writel(reg, sfb->regs + WINCON(win));
}
static int s3c_fb_register_mc_subdev_nodes(struct s3c_fb *sfb)
{
int ret;
struct exynos_md *md;
md = (struct exynos_md *)module_name_to_driver_data(MDEV_MODULE_NAME);
if (!md) {
dev_err(sfb->dev, "failed to get output media device\n");
return -ENODEV;
}
/* This function is for exposing sub-devices nodes to user space
* in case of marking with V4L2_SUBDEV_FL_HAS_DEVNODE flag.
*
* And it depends on probe sequence
* because v4l2_dev ptr is shared all of output devices below
*
* probe sequence of output devices
* output media device -> gscaler -> window in fimd
*/
ret = v4l2_device_register_subdev_nodes(&md->v4l2_dev);
if (ret) {
dev_err(sfb->dev, "failed to make nodes for subdev\n");
return ret;
}
dev_dbg(sfb->dev, "Register V4L2 subdev nodes for FIMD\n");
return 0;
}
static int s3c_fb_describe_simple_post(struct adf_interface *intf,
struct adf_buffer *buf, void *data, size_t *size)
{
struct s3c_fb_win_config_data *win_config = data;
win_config->config[0].state = S3C_FB_WIN_STATE_BUFFER;
win_config->config[0].fb = 0;
win_config->config[0].blending = S3C_FB_BLENDING_NONE;
win_config->config[0].x = 0;
win_config->config[0].y = 0;
*size = sizeof(*win_config);
return 0;
}
/*------------------------------------------------------------------ */
int s3c_fb_sysmmu_fault_handler(struct device *dev,
enum exynos_sysmmu_inttype itype, unsigned long pgtable_base,
unsigned long fault_addr)
{
struct platform_device *pdev = to_platform_device(dev);
struct s3c_fb *sfb = platform_get_drvdata(pdev);
pr_err("FIMD1 PAGE FAULT occurred at 0x%lx (Page table base: 0x%lx)\n",
fault_addr, pgtable_base);
s3c_fb_dump_registers(sfb);
pr_err("Generating Kernel OOPS... because it is unrecoverable.\n");
BUG();
return 0;
}
static int __devinit s3c_fb_export_bootloader_logo(struct s3c_fb *sfb,
struct platform_device *pdev,
struct drm_mode_modeinfo *panel_mode, struct adf_buffer *buf)
{
struct resource *res;
struct dma_buf *dma_buf;
int ret = 0;
if (IS_ENABLED(CONFIG_FRAMEBUFFER_CONSOLE))
return -ENODEV;
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
if (!res || !res->start || !resource_size(res)) {
dev_warn(&pdev->dev, "failed to find bootloader framebuffer\n");
return -ENOENT;
}
dma_buf = adf_memblock_export(res->start, resource_size(res), O_RDONLY);
if (IS_ERR(dma_buf)) {
dev_warn(&pdev->dev, "failed to export bootloader logo memblock: %ld\n",
PTR_ERR(dma_buf));
ret = PTR_ERR(dma_buf);
goto err;
}
memset(buf, 0, sizeof(*buf));
buf->overlay_engine = &sfb->eng;
buf->w = panel_mode->hdisplay;
buf->h = panel_mode->vdisplay;
buf->format = DRM_FORMAT_RGBX8888;
buf->dma_bufs[0] = dma_buf;
buf->offset[0] = 0;
buf->pitch[0] = panel_mode->hdisplay * 4;
buf->n_planes = 1;
return 0;
err:
if (memblock_free(res->start, resource_size(res)))
dev_warn(&pdev->dev, "failed to free bootloader framebuffer memblock\n");
return ret;
}
static const u32 fimd_supported_formats[] = {
DRM_FORMAT_RGBA8888,
DRM_FORMAT_RGBX8888,
DRM_FORMAT_RGBA5551,
DRM_FORMAT_RGB565,
DRM_FORMAT_BGRA8888,
};
static const struct adf_device_ops s3c_fb_dev_ops = {
.owner = THIS_MODULE,
.validate = s3c_fb_validate,
.post = s3c_fb_post,
.state_free = s3c_fb_state_free,
};
static const struct adf_overlay_engine_ops s3c_fb_eng_ops = {
.supported_formats = fimd_supported_formats,
.n_supported_formats = ARRAY_SIZE(fimd_supported_formats),
};
static const struct adf_interface_ops s3c_fb_intf_ops = {
.base = {
.supports_event = s3c_fb_supports_event,
.set_event = s3c_fb_set_event,
},
.blank = s3c_fb_blank,
.alloc_simple_buffer = s3c_fb_alloc_simple_buffer,
.describe_simple_post = s3c_fb_describe_simple_post,
.modeset = s3c_fb_modeset,
.screen_size = s3c_fb_screen_size,
};
static struct fb_ops s3c_fb_ops = {
.owner = THIS_MODULE,
.fb_open = adf_fbdev_open,
.fb_release = adf_fbdev_release,
.fb_check_var = adf_fbdev_check_var,
.fb_set_par = adf_fbdev_set_par,
.fb_blank = adf_fbdev_blank,
.fb_pan_display = adf_fbdev_pan_display,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
.fb_mmap = adf_fbdev_mmap,
};
#ifdef CONFIG_DEBUG_FS
static int s3c_fb_debugfs_show(struct seq_file *f, void *offset)
{
struct s3c_fb *sfb = f->private;
struct s3c_fb_debug *debug_data = kzalloc(sizeof(struct s3c_fb_debug),
GFP_KERNEL);
if (!debug_data) {
seq_printf(f, "kmalloc() failed; can't generate file\n");
return -ENOMEM;
}
spin_lock(&sfb->slock);
memcpy(debug_data, &sfb->debug_data, sizeof(sfb->debug_data));
spin_unlock(&sfb->slock);
seq_printf(f, "%u FIFO underflows\n", debug_data->num_timestamps);
if (debug_data->num_timestamps) {
unsigned int i;
unsigned int temp = S3C_FB_DEBUG_FIFO_TIMESTAMPS;
unsigned int n = min(debug_data->num_timestamps, temp);
seq_printf(f, "Last %u FIFO underflow timestamps:\n", n);
for (i = 0; i < n; i++) {
unsigned int idx = (debug_data->first_timestamp + i) %
S3C_FB_DEBUG_FIFO_TIMESTAMPS;
ktime_t timestamp = debug_data->fifo_timestamps[idx];
seq_printf(f, "\t%lld\n", ktime_to_ns(timestamp));
}
seq_printf(f, "Registers at time of last underflow:\n");
for (i = 0; i < S3C_FB_DEBUG_REGS_SIZE; i += 32) {
unsigned char buf[128];
hex_dump_to_buffer(debug_data->regs_at_underflow + i,
32, 32, 4, buf,
sizeof(buf), false);
seq_printf(f, "%.8x: %s\n", i, buf);
}
}
kfree(debug_data);
return 0;
}
static int s3c_fb_debugfs_open(struct inode *inode, struct file *file)
{
return single_open(file, s3c_fb_debugfs_show, inode->i_private);
}
static const struct file_operations s3c_fb_debugfs_fops = {
.owner = THIS_MODULE,
.open = s3c_fb_debugfs_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int s3c_fb_debugfs_init(struct s3c_fb *sfb)
{
sfb->debug_dentry = debugfs_create_file("s3c-fb", S_IRUGO, NULL, sfb,
&s3c_fb_debugfs_fops);
if (IS_ERR_OR_NULL(sfb->debug_dentry)) {
sfb->debug_dentry = NULL;
dev_err(sfb->dev, "debugfs_create_file() failed\n");
return -EIO;
}
return 0;
}
static void s3c_fb_debugfs_cleanup(struct s3c_fb *sfb)
{
if (sfb->debug_dentry)
debugfs_remove(sfb->debug_dentry);
}
#else
static int s3c_fb_debugfs_init(struct s3c_fb *sfb) { return 0; }
static void s3c_fb_debugfs_cleanup(struct s3c_fb *sfb) { }
#endif
static int __devinit s3c_fb_probe(struct platform_device *pdev)
{
const struct platform_device_id *platid;
struct s3c_fb_driverdata *fbdrv;
struct device *dev = &pdev->dev;
struct s3c_fb_platdata *pd;
struct s3c_fb *sfb;
struct resource *res;
struct drm_mode_modeinfo panel_mode;
struct adf_buffer bootloader_logo;
struct s3c_fb_pd_win *windata;
int win;
int default_win;
int ret = 0;
u32 reg;
platid = platform_get_device_id(pdev);
fbdrv = (struct s3c_fb_driverdata *)platid->driver_data;
if (fbdrv->variant.nr_windows > S3C_FB_MAX_WIN) {
dev_err(dev, "too many windows, cannot attach\n");
return -EINVAL;
}
pd = pdev->dev.platform_data;
if (!pd) {
dev_err(dev, "no platform data specified\n");
return -EINVAL;
}
sfb = devm_kzalloc(dev, sizeof(struct s3c_fb), GFP_KERNEL);
if (!sfb) {
dev_err(dev, "no memory for framebuffers\n");
return -ENOMEM;
}
dev_dbg(dev, "allocate new framebuffer %p\n", sfb);
sfb->dev = dev;
sfb->pdata = pd;
sfb->variant = fbdrv->variant;
ret = s3c_fb_debugfs_init(sfb);
if (ret)
dev_warn(dev, "failed to initialize debugfs entry\n");
sfb->bus_clk = clk_get(dev, "lcd");
if (IS_ERR(sfb->bus_clk)) {
dev_err(dev, "failed to get bus clock\n");
return PTR_ERR(sfb->bus_clk);
}
clk_enable(sfb->bus_clk);
if (!sfb->variant.has_clksel) {
sfb->lcd_clk = clk_get(dev, "sclk_fimd");
if (IS_ERR(sfb->lcd_clk)) {
dev_err(dev, "failed to get lcd clock\n");
ret = PTR_ERR(sfb->lcd_clk);
goto err_bus_clk;
}
clk_enable(sfb->lcd_clk);
}
pm_runtime_enable(sfb->dev);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(dev, "failed to find registers\n");
ret = -ENOENT;
goto err_lcd_clk;
}
sfb->regs = devm_request_and_ioremap(dev, res);
if (!sfb->regs) {
dev_err(dev, "failed to map registers\n");
ret = -ENXIO;
goto err_lcd_clk;
}
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!res) {
dev_err(dev, "failed to acquire irq resource\n");
ret = -ENOENT;
goto err_lcd_clk;
}
sfb->irq_no = res->start;
ret = devm_request_irq(dev, sfb->irq_no, s3c_fb_irq,
0, "s3c_fb", sfb);
if (ret) {
dev_err(dev, "irq request failed\n");
goto err_lcd_clk;
}
dev_dbg(dev, "got resources (regs %p), probing windows\n", sfb->regs);
platform_set_drvdata(pdev, sfb);
pm_runtime_get_sync(sfb->dev);
/* setup gpio and output polarity controls */
pd->setup_gpio();
writel(pd->vidcon1, sfb->regs + VIDCON1);
/* set video clock running at under-run */
if (sfb->variant.has_fixvclk) {
reg = readl(sfb->regs + VIDCON1);
reg &= ~VIDCON1_VCLK_MASK;
reg |= VIDCON1_VCLK_RUN;
writel(reg, sfb->regs + VIDCON1);
}
/* zero all windows before we do anything */
for (win = 0; win < fbdrv->variant.nr_windows; win++)
if (win != pd->default_win)
s3c_fb_clear_win(sfb, win);
/* initialise colour key controls */
for (win = 0; win < (fbdrv->variant.nr_windows - 1); win++) {
void __iomem *regs = sfb->regs + sfb->variant.keycon;
regs += (win * 8);
writel(0xffffff, regs + WKEYCON0);
writel(0xffffff, regs + WKEYCON1);
}
sfb->fb_ion_client = ion_client_create(ion_exynos, "fimd");
if (IS_ERR(sfb->fb_ion_client)) {
dev_err(sfb->dev, "failed to ion_client_create\n");
goto err_pm_runtime;
}
/* setup vmm */
platform_set_sysmmu(&SYSMMU_PLATDEV(fimd1).dev, &s5p_device_fimd1.dev);
exynos_sysmmu_set_fault_handler(&s5p_device_fimd1.dev,
s3c_fb_sysmmu_fault_handler);
default_win = sfb->pdata->default_win;
for (win = 0; win < fbdrv->variant.nr_windows; win++) {
if (!pd->win[win])
continue;
if (!pd->win[win]->win_mode.pixclock)
s3c_fb_missing_pixclock(&pd->win[win]->win_mode);
}
/* use platform specified window as the basis for the lcd timings */
adf_modeinfo_from_fb_videomode(&pd->win[default_win]->win_mode,
&panel_mode);
s3c_fb_modeset(&sfb->intf, &panel_mode);
/* we have the register setup, start allocating framebuffers */
for (win = 0; win < fbdrv->variant.nr_windows; win++) {
ret = s3c_fb_probe_win(sfb, win, fbdrv->win[win],
&sfb->windows[win]);
if (ret < 0) {
dev_err(dev, "failed to create window %d\n", win);
for (; win >= 0; win--)
s3c_fb_release_win(sfb, sfb->windows[win]);
goto err_pm_runtime;
}
}
ret = s3c_fb_register_mc_subdev_nodes(sfb);
if (ret) {
dev_err(sfb->dev, "failed to register s3c_fb mc subdev node\n");
goto err_pm_runtime;
}
#ifdef CONFIG_S5P_DP
writel(DPCLKCON_ENABLE, sfb->regs + DPCLKCON);
#endif
platform_set_drvdata(pdev, sfb);
ret = adf_device_init(&sfb->base, dev, &s3c_fb_dev_ops, dev_name(dev));
if (ret < 0)
goto err_device_init;
ret = adf_overlay_engine_init(&sfb->eng, &sfb->base, &s3c_fb_eng_ops,
"FIMD");
if (ret < 0)
goto err_engine_init;
ret = adf_interface_init(&sfb->intf, &sfb->base, ADF_INTF_eDP, 0,
ADF_INTF_FLAG_PRIMARY, &s3c_fb_intf_ops, "FIMD");
if (ret < 0)
goto err_interface_init;
sfb->intf.dpms_state = DRM_MODE_DPMS_ON;
ret = adf_attachment_allow(&sfb->base, &sfb->eng, &sfb->intf);
if (ret < 0)
goto err_attachment_init;
memcpy(&sfb->intf.current_mode, &panel_mode, sizeof(panel_mode));
ret = adf_hotplug_notify_connected(&sfb->intf, &panel_mode, 1);
if (ret < 0)
goto err_attachment_init;
ret = s3c_fb_export_bootloader_logo(sfb, pdev, &panel_mode,
&bootloader_logo);
if (ret < 0) {
s3c_fb_clear_win(sfb, default_win);
} else {
struct sync_fence *fence = adf_interface_simple_post(&sfb->intf,
&bootloader_logo);
if (IS_ERR(fence)) {
ret = PTR_ERR(fence);
dev_warn(sfb->dev, "failed to post bootloader logo: %d\n",
ret);
s3c_fb_clear_win(sfb, default_win);
} else {
sync_fence_put(fence);
}
dma_buf_put(bootloader_logo.dma_bufs[0]);
}
adf_vsync_wait(&sfb->intf, 0);
ret = iovmm_activate(&s5p_device_fimd1.dev);
if (ret < 0) {
dev_err(sfb->dev, "failed to activate vmm\n");
goto err_attachment_init;
}
if (!sfb->fb_mif_handle) {
sfb->fb_mif_handle = exynos5_bus_mif_min(300000);
if (!sfb->fb_mif_handle)
dev_err(sfb->dev, "failed to request min_freq for mif \n");
}
if (!sfb->fb_int_handle) {
sfb->fb_int_handle = exynos5_bus_int_min(160000);
if (!sfb->fb_int_handle)
dev_err(sfb->dev, "failed to request min_freq for int \n");
}
windata = sfb->windows[default_win]->windata;
ret = adf_fbdev_init(&sfb->fbdev, &sfb->intf, &sfb->eng,
windata->virtual_x, windata->virtual_y,
DRM_FORMAT_RGBX8888, &s3c_fb_ops, "%s", dev_name(dev));
if (ret < 0)
goto err_attachment_init;
dev_info(sfb->dev, "window %d: fb %s\n", default_win,
sfb->base.base.name);
return 0;
err_attachment_init:
adf_interface_destroy(&sfb->intf);
err_interface_init:
adf_overlay_engine_destroy(&sfb->eng);
err_engine_init:
adf_device_destroy(&sfb->base);
err_device_init:
if (sfb->fb_mif_handle)
exynos5_bus_mif_put(sfb->fb_mif_handle);
if (sfb->fb_int_handle)
exynos5_bus_int_put(sfb->fb_int_handle);
iovmm_deactivate(&s5p_device_fimd1.dev);
err_pm_runtime:
pm_runtime_put_sync(sfb->dev);
err_lcd_clk:
pm_runtime_disable(sfb->dev);
if (!sfb->variant.has_clksel) {
clk_disable(sfb->lcd_clk);
clk_put(sfb->lcd_clk);
}
err_bus_clk:
clk_disable(sfb->bus_clk);
clk_put(sfb->bus_clk);
return ret;
}
/**
* s3c_fb_remove() - Cleanup on module finalisation
* @pdev: The platform device we are bound to.
*
* Shutdown and then release all the resources that the driver allocated
* on initialisation.
*/
static int __devexit s3c_fb_remove(struct platform_device *pdev)
{
struct s3c_fb *sfb = platform_get_drvdata(pdev);
int win;
pm_runtime_get_sync(sfb->dev);
adf_fbdev_destroy(&sfb->fbdev);
adf_interface_destroy(&sfb->intf);
adf_overlay_engine_destroy(&sfb->eng);
adf_device_destroy(&sfb->base);
for (win = 0; win < S3C_FB_MAX_WIN; win++)
s3c_fb_release_win(sfb, sfb->windows[win]);
if (!sfb->variant.has_clksel) {
clk_disable(sfb->lcd_clk);
clk_put(sfb->lcd_clk);
}
clk_disable(sfb->bus_clk);
clk_put(sfb->bus_clk);
s3c_fb_debugfs_cleanup(sfb);
pm_runtime_put_sync(sfb->dev);
pm_runtime_disable(sfb->dev);
if (sfb->fb_mif_handle)
exynos5_bus_mif_put(sfb->fb_mif_handle);
if (sfb->fb_int_handle)
exynos5_bus_int_put(sfb->fb_int_handle);
return 0;
}
static int s3c_fb_disable(struct s3c_fb *sfb)
{
u32 vidcon0;
if (sfb->pdata->backlight_off)
sfb->pdata->backlight_off();
if (sfb->pdata->lcd_off)
sfb->pdata->lcd_off();
vidcon0 = readl(sfb->regs + VIDCON0);
/* see the note in the framebuffer datasheet about
* why you cannot take both of these bits down at the
* same time. */
if (vidcon0 & VIDCON0_ENVID) {
vidcon0 |= VIDCON0_ENVID;
vidcon0 &= ~VIDCON0_ENVID_F;
writel(vidcon0, sfb->regs + VIDCON0);
} else
dev_warn(sfb->dev, "ENVID not set while disabling fb");
if (!sfb->variant.has_clksel)
clk_disable(sfb->lcd_clk);
clk_disable(sfb->bus_clk);
iovmm_deactivate(&s5p_device_fimd1.dev);
pm_runtime_put_sync(sfb->dev);
exynos5_mif_multiple_windows(false);
return 0;
}
/**
* s3c_fb_enable() - Enable the main LCD output
* @sfb: The main framebuffer state.
*/
static int s3c_fb_enable(struct s3c_fb *sfb)
{
struct s3c_fb_platdata *pd = sfb->pdata;
int win_no;
int ret;
u32 reg;
pm_runtime_get_sync(sfb->dev);
clk_enable(sfb->bus_clk);
if (!sfb->variant.has_clksel)
clk_enable(sfb->lcd_clk);
/* setup gpio and output polarity controls */
pd->setup_gpio();
writel(pd->vidcon1, sfb->regs + VIDCON1);
writel(REG_CLKGATE_MODE_NON_CLOCK_GATE,
sfb->regs + REG_CLKGATE_MODE);
/* set video clock running at under-run */
if (sfb->variant.has_fixvclk) {
reg = readl(sfb->regs + VIDCON1);
reg &= ~VIDCON1_VCLK_MASK;
reg |= VIDCON1_VCLK_RUN;
writel(reg, sfb->regs + VIDCON1);
}
if (sfb->deferred_update) {
__s3c_fb_update_regs(sfb, sfb->deferred_update);
sfb->deferred_update = NULL;
} else {
/* zero all windows before we do anything */
for (win_no = 0; win_no < sfb->variant.nr_windows; win_no++)
s3c_fb_clear_win(sfb, win_no);
}
s3c_fb_modeset(&sfb->intf, &sfb->intf.current_mode);
ret = iovmm_activate(&s5p_device_fimd1.dev);
if (ret < 0) {
dev_err(sfb->dev, "failed to reactivate vmm\n");
return ret;
}
#ifdef CONFIG_S5P_DP
writel(DPCLKCON_ENABLE, sfb->regs + DPCLKCON);
#endif
reg = readl(sfb->regs + VIDCON0);
reg |= VIDCON0_ENVID | VIDCON0_ENVID_F;
writel(reg, sfb->regs + VIDCON0);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int s3c_fb_suspend(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct s3c_fb *sfb = platform_get_drvdata(pdev);
int ret = 0;
if (sfb->intf.dpms_state == DRM_MODE_DPMS_ON) {
dev_warn(dev, "LCD output on while entering suspend\n");
ret = -EBUSY;
}
return ret;
}
static int s3c_fb_resume(struct device *dev)
{
return 0;
}
#endif
#ifdef CONFIG_PM_RUNTIME
static int s3c_fb_runtime_suspend(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct s3c_fb *sfb = platform_get_drvdata(pdev);
if (!sfb->variant.has_clksel)
clk_disable(sfb->lcd_clk);
clk_disable(sfb->bus_clk);
if (sfb->fb_mif_handle) {
if(exynos5_bus_mif_put(sfb->fb_mif_handle))
dev_err(sfb->dev, "failed to free min_freq for mif \n");
sfb->fb_mif_handle = NULL;
}
if (sfb->fb_int_handle) {
if(exynos5_bus_int_put(sfb->fb_int_handle))
dev_err(sfb->dev, "failed to free min_freq for int \n");
sfb->fb_int_handle = NULL;
}
return 0;
}
static int s3c_fb_runtime_resume(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct s3c_fb *sfb = platform_get_drvdata(pdev);
struct s3c_fb_platdata *pd = sfb->pdata;
if (!sfb->fb_mif_handle) {
sfb->fb_mif_handle = exynos5_bus_mif_min(300000);
if (!sfb->fb_mif_handle)
dev_err(sfb->dev, "failed to request min_freq for mif \n");
}
if (!sfb->fb_int_handle) {
sfb->fb_int_handle = exynos5_bus_int_min(160000);
if (!sfb->fb_int_handle)
dev_err(sfb->dev, "failed to request min_freq for int \n");
}
clk_enable(sfb->bus_clk);
if (!sfb->variant.has_clksel)
clk_enable(sfb->lcd_clk);
/* setup gpio and output polarity controls */
pd->setup_gpio();
writel(pd->vidcon1, sfb->regs + VIDCON1);
return 0;
}
#endif
#define VALID_BPP124 (VALID_BPP(1) | VALID_BPP(2) | VALID_BPP(4))
#define VALID_BPP1248 (VALID_BPP124 | VALID_BPP(8))
static struct s3c_fb_win_variant s3c_fb_data_64xx_wins[] = {
[0] = {
.has_osd_c = 1,
.osd_size_off = 0x8,
.palette_sz = 256,
.valid_bpp = (VALID_BPP1248 | VALID_BPP(16) |
VALID_BPP(18) | VALID_BPP(24)),
},
[1] = {
.has_osd_c = 1,
.has_osd_d = 1,
.osd_size_off = 0xc,
.has_osd_alpha = 1,
.palette_sz = 256,
.valid_bpp = (VALID_BPP1248 | VALID_BPP(16) |
VALID_BPP(18) | VALID_BPP(19) |
VALID_BPP(24) | VALID_BPP(25) |
VALID_BPP(28)),
},
[2] = {
.has_osd_c = 1,
.has_osd_d = 1,
.osd_size_off = 0xc,
.has_osd_alpha = 1,
.palette_sz = 16,
.palette_16bpp = 1,
.valid_bpp = (VALID_BPP1248 | VALID_BPP(16) |
VALID_BPP(18) | VALID_BPP(19) |
VALID_BPP(24) | VALID_BPP(25) |
VALID_BPP(28)),
},
[3] = {
.has_osd_c = 1,
.has_osd_alpha = 1,
.palette_sz = 16,
.palette_16bpp = 1,
.valid_bpp = (VALID_BPP124 | VALID_BPP(16) |
VALID_BPP(18) | VALID_BPP(19) |
VALID_BPP(24) | VALID_BPP(25) |
VALID_BPP(28)),
},
[4] = {
.has_osd_c = 1,
.has_osd_alpha = 1,
.palette_sz = 4,
.palette_16bpp = 1,
.valid_bpp = (VALID_BPP(1) | VALID_BPP(2) |
VALID_BPP(16) | VALID_BPP(18) |
VALID_BPP(19) | VALID_BPP(24) |
VALID_BPP(25) | VALID_BPP(28)),
},
};
static struct s3c_fb_win_variant s3c_fb_data_s5p_wins[] = {
[0] = {
.has_osd_c = 1,
.osd_size_off = 0x8,
.palette_sz = 256,
.valid_bpp = (VALID_BPP1248 | VALID_BPP(13) |
VALID_BPP(15) | VALID_BPP(16) |
VALID_BPP(18) | VALID_BPP(19) |
VALID_BPP(24) | VALID_BPP(25) |
VALID_BPP(32)),
},
[1] = {
.has_osd_c = 1,
.has_osd_d = 1,
.osd_size_off = 0xc,
.has_osd_alpha = 1,
.palette_sz = 256,
.valid_bpp = (VALID_BPP1248 | VALID_BPP(13) |
VALID_BPP(15) | VALID_BPP(16) |
VALID_BPP(18) | VALID_BPP(19) |
VALID_BPP(24) | VALID_BPP(25) |
VALID_BPP(32)),
},
[2] = {
.has_osd_c = 1,
.has_osd_d = 1,
.osd_size_off = 0xc,
.has_osd_alpha = 1,
.palette_sz = 256,
.valid_bpp = (VALID_BPP1248 | VALID_BPP(13) |
VALID_BPP(15) | VALID_BPP(16) |
VALID_BPP(18) | VALID_BPP(19) |
VALID_BPP(24) | VALID_BPP(25) |
VALID_BPP(32)),
},
[3] = {
.has_osd_c = 1,
.has_osd_alpha = 1,
.palette_sz = 256,
.valid_bpp = (VALID_BPP1248 | VALID_BPP(13) |
VALID_BPP(15) | VALID_BPP(16) |
VALID_BPP(18) | VALID_BPP(19) |
VALID_BPP(24) | VALID_BPP(25) |
VALID_BPP(32)),
},
[4] = {
.has_osd_c = 1,
.has_osd_alpha = 1,
.palette_sz = 256,
.valid_bpp = (VALID_BPP1248 | VALID_BPP(13) |
VALID_BPP(15) | VALID_BPP(16) |
VALID_BPP(18) | VALID_BPP(19) |
VALID_BPP(24) | VALID_BPP(25) |
VALID_BPP(32)),
},
};
static struct s3c_fb_driverdata s3c_fb_data_64xx = {
.variant = {
.nr_windows = 5,
.vidtcon = VIDTCON0,
.wincon = WINCON(0),
.winmap = WINxMAP(0),
.keycon = WKEYCON,
.osd = VIDOSD_BASE,
.osd_stride = 16,
.buf_start = VIDW_BUF_START(0),
.buf_size = VIDW_BUF_SIZE(0),
.buf_end = VIDW_BUF_END(0),
.palette = {
[0] = 0x400,
[1] = 0x800,
[2] = 0x300,
[3] = 0x320,
[4] = 0x340,
},
.has_prtcon = 1,
.has_clksel = 1,
},
.win[0] = &s3c_fb_data_64xx_wins[0],
.win[1] = &s3c_fb_data_64xx_wins[1],
.win[2] = &s3c_fb_data_64xx_wins[2],
.win[3] = &s3c_fb_data_64xx_wins[3],
.win[4] = &s3c_fb_data_64xx_wins[4],
};
static struct s3c_fb_driverdata s3c_fb_data_s5pc100 = {
.variant = {
.nr_windows = 5,
.vidtcon = VIDTCON0,
.wincon = WINCON(0),
.winmap = WINxMAP(0),
.keycon = WKEYCON,
.osd = VIDOSD_BASE,
.osd_stride = 16,
.buf_start = VIDW_BUF_START(0),
.buf_size = VIDW_BUF_SIZE(0),
.buf_end = VIDW_BUF_END(0),
.palette = {
[0] = 0x2400,
[1] = 0x2800,
[2] = 0x2c00,
[3] = 0x3000,
[4] = 0x3400,
},
.has_prtcon = 1,
.has_blendcon = 1,
.has_alphacon = 1,
.has_clksel = 1,
},
.win[0] = &s3c_fb_data_s5p_wins[0],
.win[1] = &s3c_fb_data_s5p_wins[1],
.win[2] = &s3c_fb_data_s5p_wins[2],
.win[3] = &s3c_fb_data_s5p_wins[3],
.win[4] = &s3c_fb_data_s5p_wins[4],
};
static struct s3c_fb_driverdata s3c_fb_data_s5pv210 = {
.variant = {
.nr_windows = 5,
.vidtcon = VIDTCON0,
.wincon = WINCON(0),
.winmap = WINxMAP(0),
.keycon = WKEYCON,
.osd = VIDOSD_BASE,
.osd_stride = 16,
.buf_start = VIDW_BUF_START(0),
.buf_size = VIDW_BUF_SIZE(0),
.buf_end = VIDW_BUF_END(0),
.palette = {
[0] = 0x2400,
[1] = 0x2800,
[2] = 0x2c00,
[3] = 0x3000,
[4] = 0x3400,
},
.has_shadowcon = 1,
.has_blendcon = 1,
.has_alphacon = 1,
.has_clksel = 1,
.has_fixvclk = 1,
},
.win[0] = &s3c_fb_data_s5p_wins[0],
.win[1] = &s3c_fb_data_s5p_wins[1],
.win[2] = &s3c_fb_data_s5p_wins[2],
.win[3] = &s3c_fb_data_s5p_wins[3],
.win[4] = &s3c_fb_data_s5p_wins[4],
};
static struct s3c_fb_driverdata s3c_fb_data_exynos4 = {
.variant = {
.nr_windows = 5,
.vidtcon = VIDTCON0,
.wincon = WINCON(0),
.winmap = WINxMAP(0),
.keycon = WKEYCON,
.osd = VIDOSD_BASE,
.osd_stride = 16,
.buf_start = VIDW_BUF_START(0),
.buf_size = VIDW_BUF_SIZE(0),
.buf_end = VIDW_BUF_END(0),
.palette = {
[0] = 0x2400,
[1] = 0x2800,
[2] = 0x2c00,
[3] = 0x3000,
[4] = 0x3400,
},
.has_shadowcon = 1,
.has_blendcon = 1,
.has_alphacon = 1,
.has_fixvclk = 1,
},
.win[0] = &s3c_fb_data_s5p_wins[0],
.win[1] = &s3c_fb_data_s5p_wins[1],
.win[2] = &s3c_fb_data_s5p_wins[2],
.win[3] = &s3c_fb_data_s5p_wins[3],
.win[4] = &s3c_fb_data_s5p_wins[4],
};
static struct s3c_fb_driverdata s3c_fb_data_exynos5 = {
.variant = {
.nr_windows = 5,
.vidtcon = VIDTCON0,
.wincon = WINCON(0),
.winmap = WINxMAP(0),
.keycon = WKEYCON,
.osd = VIDOSD_BASE,
.osd_stride = 16,
.buf_start = VIDW_BUF_START(0),
.buf_size = VIDW_BUF_SIZE(0),
.buf_end = VIDW_BUF_END(0),
.palette = {
[0] = 0x2400,
[1] = 0x2800,
[2] = 0x2c00,
[3] = 0x3000,
[4] = 0x3400,
},
.has_shadowcon = 1,
.has_blendcon = 1,
.has_alphacon = 1,
.has_fixvclk = 1,
},
.win[0] = &s3c_fb_data_s5p_wins[0],
.win[1] = &s3c_fb_data_s5p_wins[1],
.win[2] = &s3c_fb_data_s5p_wins[2],
.win[3] = &s3c_fb_data_s5p_wins[3],
.win[4] = &s3c_fb_data_s5p_wins[4],
};
/* S3C2443/S3C2416 style hardware */
static struct s3c_fb_driverdata s3c_fb_data_s3c2443 = {
.variant = {
.nr_windows = 2,
.is_2443 = 1,
.vidtcon = 0x08,
.wincon = 0x14,
.winmap = 0xd0,
.keycon = 0xb0,
.osd = 0x28,
.osd_stride = 12,
.buf_start = 0x64,
.buf_size = 0x94,
.buf_end = 0x7c,
.palette = {
[0] = 0x400,
[1] = 0x800,
},
.has_clksel = 1,
},
.win[0] = &(struct s3c_fb_win_variant) {
.palette_sz = 256,
.valid_bpp = VALID_BPP1248 | VALID_BPP(16) | VALID_BPP(24),
},
.win[1] = &(struct s3c_fb_win_variant) {
.has_osd_c = 1,
.has_osd_alpha = 1,
.palette_sz = 256,
.valid_bpp = (VALID_BPP1248 | VALID_BPP(16) |
VALID_BPP(18) | VALID_BPP(19) |
VALID_BPP(24) | VALID_BPP(25) |
VALID_BPP(28)),
},
};
static struct s3c_fb_driverdata s3c_fb_data_s5p64x0 = {
.variant = {
.nr_windows = 3,
.vidtcon = VIDTCON0,
.wincon = WINCON(0),
.winmap = WINxMAP(0),
.keycon = WKEYCON,
.osd = VIDOSD_BASE,
.osd_stride = 16,
.buf_start = VIDW_BUF_START(0),
.buf_size = VIDW_BUF_SIZE(0),
.buf_end = VIDW_BUF_END(0),
.palette = {
[0] = 0x2400,
[1] = 0x2800,
[2] = 0x2c00,
},
.has_blendcon = 1,
.has_fixvclk = 1,
},
.win[0] = &s3c_fb_data_s5p_wins[0],
.win[1] = &s3c_fb_data_s5p_wins[1],
.win[2] = &s3c_fb_data_s5p_wins[2],
};
static struct platform_device_id s3c_fb_driver_ids[] = {
{
.name = "s3c-fb",
.driver_data = (unsigned long)&s3c_fb_data_64xx,
}, {
.name = "s5pc100-fb",
.driver_data = (unsigned long)&s3c_fb_data_s5pc100,
}, {
.name = "s5pv210-fb",
.driver_data = (unsigned long)&s3c_fb_data_s5pv210,
}, {
.name = "exynos4-fb",
.driver_data = (unsigned long)&s3c_fb_data_exynos4,
}, {
.name = "exynos5-fb",
.driver_data = (unsigned long)&s3c_fb_data_exynos5,
}, {
.name = "s3c2443-fb",
.driver_data = (unsigned long)&s3c_fb_data_s3c2443,
}, {
.name = "s5p64x0-fb",
.driver_data = (unsigned long)&s3c_fb_data_s5p64x0,
},
{},
};
MODULE_DEVICE_TABLE(platform, s3c_fb_driver_ids);
static const struct dev_pm_ops s3cfb_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(s3c_fb_suspend, s3c_fb_resume)
SET_RUNTIME_PM_OPS(s3c_fb_runtime_suspend, s3c_fb_runtime_resume,
NULL)
};
static struct platform_driver s3c_fb_driver = {
.probe = s3c_fb_probe,
.remove = __devexit_p(s3c_fb_remove),
.id_table = s3c_fb_driver_ids,
.driver = {
.name = "s3c-fb",
.owner = THIS_MODULE,
.pm = &s3cfb_pm_ops,
},
};
static int __init s3c_fb_init(void)
{
return platform_driver_register(&s3c_fb_driver);
}
static void __exit s3c_fb_cleanup(void)
{
platform_driver_unregister(&s3c_fb_driver);
}
late_initcall(s3c_fb_init);
module_exit(s3c_fb_cleanup);
MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
MODULE_DESCRIPTION("Samsung S3C SoC Framebuffer driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:s3c-fb");