blob: cef814562daf8ef9a18d20a3802f8c71962a5482 [file] [log] [blame]
/*
* PDP Framebuffer
*
* Copyright (c) 2008-2012 Imagination Technologies Ltd.
* Parts Copyright (C) 2009 Nokia Corporation
* (custom ISR code derived from drivers/video/omap2/dss/dispc.c)
*
* 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.
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/fb.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/uaccess.h>
#include <linux/clk.h>
#include <linux/console.h>
#include <linux/slab.h>
#include <asm/irq.h>
#include <video/pdpfb.h>
#include "pdpfb.h"
#include "pdpfb_regs.h"
#include "pdpfb_gfx.h"
#include "pdpfb_vid.h"
/* Physical address and length of video memory */
static ulong videomem_base;
module_param(videomem_base, ulong, 0);
static ulong videomem_len;
module_param(videomem_len, ulong, 0);
/* Physical address and length of graphics plane framebuffer */
#ifndef PDP_SHARED_BASE
static ulong gfx_videomem_base;
module_param(gfx_videomem_base, ulong, 0);
#endif
static ulong gfx_videomem_len = CONFIG_FB_PDP_GFX_VIDEOMEM;
module_param(gfx_videomem_len, ulong, 0);
/* Physical address and length of video plane framebuffer */
#ifdef CONFIG_FB_PDP_VID
#ifndef PDP_SHARED_BASE
static ulong vid_videomem_base;
module_param(vid_videomem_base, ulong, 0);
#endif
static ulong vid_videomem_len = CONFIG_FB_PDP_VID_VIDEOMEM;
module_param(vid_videomem_len, ulong, 0);
#endif
/* report number of VBLANKs taken to update LUT */
/*#define LUT_UPD_REPORT_PERF*/
/* Groups of registers that may need updating on VEVENT */
#define PDP_UPDATE_SYNC 0x00000001
#define PDP_UPDATE_CLUT 0x00000002
struct pdpfb_lut {
spinlock_t lock;
u32 palette[PDP_PALETTE_NR]; /* hardware specific format */
#ifdef CONFIG_FB_PDP_QUEUE_CLUT
u16 count; /* number of items in queue */
u8 position; /* next index to update */
u8 queue[PDP_PALETTE_NR]; /* circular queue */
u8 rqueue[PDP_PALETTE_NR]; /* reverse of queue */
#else
u16 min, max; /* first and last item to update */
#endif
};
struct pdpfb_update_sync {
u32 hsync1, hsync2, hsync3;
u32 hde_ctrl;
u32 str1blend2;
};
struct pdpfb_updates {
spinlock_t lock;
u32 updates; /* PDP_UPDATE_* */
struct pdpfb_update_sync sync; /* PDP_UPDATE_SYNC */
};
struct pdpfb_isr_data {
pdpfb_isr_t isr;
void *arg;
u32 mask;
};
#define PDPFB_MAX_NR_ISRS 8
#ifdef CONFIG_FB_PDP_VID
#define PDPFB_STREAM_NR 2
#else
#define PDPFB_STREAM_NR 1
#endif
struct pdpfb_rgb_fmt {
struct fb_bitfield r, g, b;
};
#define PDPFB_MEMPOOLF_KERNEL 0x1 /* Kernel allocated memory */
struct pdpfb_mem_pool {
unsigned long phys_base;
void *base;
unsigned long size;
unsigned int flags;
unsigned long allocated;
};
static char *pdpfb_mem_pool_names[] = {
[PDPFB_MEMPOOL_MEM] = "videomem",
[PDPFB_MEMPOOL_GFXMEM] = "gfx videomem",
[PDPFB_MEMPOOL_VIDMEM] = "vid videomem",
[PDPFB_MEMPOOL_USER] = "usermem",
};
enum pdpfb_power_state {
PDPFB_POWER_NA = -1,
PDPFB_POWER_BACKLIT = FB_BLANK_UNBLANK,
PDPFB_POWER_PANEL_POWERED = FB_BLANK_NORMAL,
PDPFB_POWER_SYNC_ENABLED = FB_BLANK_VSYNC_SUSPEND,
PDPFB_POWER_PANEL_ENABLED = FB_BLANK_HSYNC_SUSPEND,
PDPFB_POWER_FULLY_OFF = FB_BLANK_POWERDOWN,
PDPFB_POWER_MAX
};
struct pdpfb_priv {
void __iomem *base;
struct pdpfb_mem_pool pools[PDPFB_MEMPOOL_NR_POOLS];
unsigned int irq;
struct clk *pdp_clk;
struct clk *pixel_clk;
int pdp_clk_en;
int pixel_clk_en;
struct device *device;
struct fb_videomode mode;
u32 pseudo_palette[PDP_PALETTE_NR];
enum pdpfb_power_state power;
struct pdpfb_updates upd;
struct pdpfb_lut lut;
struct pdpfb_rgb_fmt palette_fmt;
u32 bgnd_col;
struct pdpfb_stream *streams[PDPFB_STREAM_NR];
spinlock_t irq_lock;
int isr_disable;
struct pdpfb_isr_data registered_isr[PDPFB_MAX_NR_ISRS];
unsigned int vsync_count;
int final_blank;
unsigned int reconfiguring:1;
unsigned int no_blank_emit:1;
};
/* The one and only */
static struct pdpfb_priv *pdpfb_priv;
static int pdpfb_init_mempool(struct pdpfb_priv *priv, int type,
unsigned long phys, unsigned long len);
static void pdpfb_pdp_clk_enable(struct pdpfb_priv *priv)
{
if (!priv->pdp_clk_en) {
clk_enable(priv->pdp_clk);
priv->pdp_clk_en = 1;
}
}
static void pdpfb_pdp_clk_disable(struct pdpfb_priv *priv)
{
if (priv->pdp_clk_en) {
clk_disable(priv->pdp_clk);
priv->pdp_clk_en = 0;
}
}
static void pdpfb_pixel_clk_enable(struct pdpfb_priv *priv)
{
if (!priv->pixel_clk_en) {
clk_prepare_enable(priv->pixel_clk);
priv->pixel_clk_en = 1;
}
}
static void pdpfb_pixel_clk_disable(struct pdpfb_priv *priv)
{
if (priv->pixel_clk_en) {
clk_disable_unprepare(priv->pixel_clk);
priv->pixel_clk_en = 0;
}
}
/* Register access */
#define PDUMP_TAG "pdpfb pdump: "
#ifdef CONFIG_FB_PDP_PDUMP_V1
#define PDUMP_WR_FMT "WRW :REG:%08x %08x\n"
#define PDUMP_RD_FMT "RDW :REG:%08x\n"
#endif
#ifdef CONFIG_FB_PDP_PDUMP_V2
#define PDUMP_WR_FMT "WRW :REGMET_PDP:%#x %#x\n"
#define PDUMP_RD_FMT "RDW :REGMET_PDP:%#x\n"
#endif
void pdpfb_write(struct pdpfb_priv *priv,
unsigned int reg_offs, unsigned int data)
{
#ifdef CONFIG_FB_PDP_PDUMP
printk(PDUMP_TAG PDUMP_WR_FMT,
reg_offs, data);
#endif
iowrite32(data, priv->base + reg_offs);
}
unsigned int pdpfb_read(struct pdpfb_priv *priv,
unsigned int reg_offs)
{
#ifdef CONFIG_FB_PDP_PDUMP
printk(PDUMP_TAG PDUMP_RD_FMT,
reg_offs);
#endif
return ioread32(priv->base + reg_offs);
}
/* LUT queue */
static void pdpfb_lut_init(struct pdpfb_priv *priv)
{
spin_lock_init(&priv->lut.lock);
#ifdef CONFIG_FB_PDP_QUEUE_CLUT
priv->lut.count = 0;
#else
priv->lut.min = PDP_PALETTE_NR;
priv->lut.max = 0;
#endif
}
/*
* lut_num_entries: Get number of LUT entries to write.
* lut_upd_entries: Write LUT updates. priv->lut.lock must be held.
*/
#ifdef CONFIG_FB_PDP_QUEUE_CLUT
static inline int _pdpfb_lut_num_entries(struct pdpfb_priv *priv)
{
return priv->lut.count;
}
static inline int _pdpfb_lut_upd(struct pdpfb_priv *priv)
{
int i = priv->lut.count;
u8 pos = priv->lut.position;
/* pos intentionally wraps around */
for (; i; --i, ++pos) {
u8 id = priv->lut.queue[pos];
u32 val = priv->lut.palette[id];
pdpfb_write(priv, PDP_PALETTE1,
id << PDP_PALETTE1_LUTADDR_OFFSET);
pdpfb_write(priv, PDP_PALETTE2, val);
if (unlikely(val != pdpfb_read(priv, PDP_PALETTE2)))
break;
}
priv->lut.count = i;
priv->lut.position = pos;
return i;
}
static inline void _pdpfb_queue_lut_upd(struct pdpfb_priv *priv,
u8 id, u32 palette_rgb)
{
u8 position = priv->lut.position;
u16 count = priv->lut.count;
u8 index = priv->lut.rqueue[id];
u8 relindex = index - position;
priv->lut.palette[id] = palette_rgb;
/* last queue index not pending, or referring to different id */
if (likely(relindex >= count || priv->lut.queue[index] != id)) {
priv->lut.rqueue[id] = index = position + count;
priv->lut.queue[index] = id;
priv->lut.count = count+1;
}
}
#else /*CONFIG_FB_PDP_QUEUE_CLUT*/
static inline int _pdpfb_lut_num_entries(struct pdpfb_priv *priv)
{
if (priv->lut.max < priv->lut.min)
return 0;
return 1 + priv->lut.max - priv->lut.min;
}
static inline int _pdpfb_lut_upd(struct pdpfb_priv *priv)
{
int i;
for (i = priv->lut.min; i <= priv->lut.max; ++i) {
pdpfb_write(priv, PDP_PALETTE1,
i << PDP_PALETTE1_LUTADDR_OFFSET);
pdpfb_write(priv, PDP_PALETTE2, priv->lut.palette[i]);
}
priv->lut.min = PDP_PALETTE_NR;
priv->lut.max = 0;
/* read any PDP register, without this we get flickering artifacts */
pdpfb_read(priv, PDP_SIGNAT);
return 0;
}
static inline void _pdpfb_queue_lut_upd(struct pdpfb_priv *priv,
u8 id, u32 palette_rgb)
{
priv->lut.palette[id] = palette_rgb;
if (id < priv->lut.min)
priv->lut.min = id;
if (id > priv->lut.max)
priv->lut.max = id;
}
#endif /*CONFIG_FB_PDP_QUEUE_CLUT*/
/*
* Write any queued LUT updates (for use in VBLANK).
* Must not hold priv->upd.lock going into this function.
*/
static void pdpfb_lut_upd(struct pdpfb_priv *priv)
{
int new_count;
#ifdef LUT_UPD_REPORT_PERF
int init_count;
#endif
/* Don't bother updating LUT unless it's switched on */
if (!GET_FIELD(pdpfb_read(priv, PDP_STR1SURF), PDP_STR1SURF_USELUT))
return;
spin_lock(&priv->lut.lock);
#ifdef LUT_UPD_REPORT_PERF
init_count = _pdpfb_lut_num_entries(priv);
#endif
new_count = _pdpfb_lut_upd(priv);
if (!new_count) {
/* Updating CLUT finished */
spin_lock(&priv->upd.lock);
priv->upd.updates &= ~PDP_UPDATE_CLUT;
spin_unlock(&priv->upd.lock);
}
spin_unlock(&priv->lut.lock);
#ifdef LUT_UPD_REPORT_PERF
if (new_count != init_count) {
static unsigned int vevent_count;
static unsigned int luti_count;
++vevent_count;
luti_count += init_count-new_count;
if (!new_count) {
dev_dbg(priv->device,
"updated %u LUTs in %u VEVENTs (%u each)\n",
luti_count,
vevent_count,
luti_count / vevent_count);
vevent_count = 0;
luti_count = 0;
}
}
#endif
}
static void pdpfb_sync_upd(struct pdpfb_priv *priv)
{
pdpfb_write(priv, PDP_HSYNC1, priv->upd.sync.hsync1);
pdpfb_write(priv, PDP_HSYNC2, priv->upd.sync.hsync2);
pdpfb_write(priv, PDP_HSYNC3, priv->upd.sync.hsync3);
pdpfb_write(priv, PDP_HDECTRL, priv->upd.sync.hde_ctrl);
priv->upd.updates &= ~PDP_UPDATE_SYNC;
}
/* triggered on vevent0 in interrupt context */
static void pdpfb_interrupt_upd(void *arg, u32 mask)
{
struct pdpfb_priv *priv = (struct pdpfb_priv *)arg;
/* Update as many colour lookup table entries as possible */
if (priv->upd.updates & PDP_UPDATE_CLUT)
pdpfb_lut_upd(priv);
spin_lock(&priv->upd.lock);
/* Update fields that would cause artifacts elsewhere */
if (priv->upd.updates & PDP_UPDATE_SYNC)
pdpfb_sync_upd(priv);
/* Disable interrupt if no more updates */
if (!priv->upd.updates)
pdpfb_unregister_isr(pdpfb_interrupt_upd, arg,
PDPFB_IRQ_VEVENT0);
spin_unlock(&priv->upd.lock);
}
static inline void _pdpfb_start_queue_lut_upd(struct pdpfb_priv *priv,
unsigned long *flags)
{
spin_lock_irqsave(&priv->lut.lock, *flags);
}
/* must correspond to _pdpfb_start_queue_lut_upd */
static inline void _pdpfb_end_queue_lut_upd(struct pdpfb_priv *priv,
unsigned long *flags)
{
/* Start updating CLUT */
spin_lock(&priv->upd.lock); /* interrupts are already disabled */
/* If not in lut mode, delay update until it's enabled */
if (GET_FIELD(pdpfb_read(priv, PDP_STR1SURF), PDP_STR1SURF_USELUT)) {
priv->upd.updates |= PDP_UPDATE_CLUT;
pdpfb_register_isr(pdpfb_interrupt_upd, priv,
PDPFB_IRQ_VEVENT0);
}
spin_unlock(&priv->upd.lock);
spin_unlock_irqrestore(&priv->lut.lock, *flags);
}
void pdpfb_enable_palette(struct pdpfb_priv *priv)
{
unsigned long flags;
/* restart any pending palette updates */
spin_lock_irqsave(&priv->lut.lock, flags);
if (_pdpfb_lut_num_entries(priv)) {
spin_lock(&priv->upd.lock);
priv->upd.updates |= PDP_UPDATE_CLUT;
pdpfb_register_isr(pdpfb_interrupt_upd, priv,
PDPFB_IRQ_VEVENT0);
spin_unlock(&priv->upd.lock);
}
spin_unlock_irqrestore(&priv->lut.lock, flags);
}
void pdpfb_disable_palette(struct pdpfb_priv *priv)
{
unsigned long flags;
/* stop any pending palette updates */
spin_lock_irqsave(&priv->lut.lock, flags);
if (_pdpfb_lut_num_entries(priv)) {
spin_lock(&priv->upd.lock);
priv->upd.updates &= ~PDP_UPDATE_CLUT;
if (!priv->upd.updates)
pdpfb_unregister_isr(pdpfb_interrupt_upd, priv,
PDPFB_IRQ_VEVENT0);
spin_unlock(&priv->upd.lock);
}
spin_unlock_irqrestore(&priv->lut.lock, flags);
}
static void pdpfb_ident(struct pdpfb_priv *priv)
{
unsigned int coreid;
unsigned int corerev;
coreid = pdpfb_read(priv, PDP_CORE_ID);
corerev = pdpfb_read(priv, PDP_CORE_REV);
dev_info(priv->device,
"PDP id: %#x revision: %d.%d.%d\n", coreid,
(corerev >> 16) & 0xff,
(corerev >> 8) & 0xff,
corerev & 0xff);
}
/* Handler for all PDP interrupts */
static irqreturn_t pdpfb_interrupt(int irq, void *dev_id)
{
struct pdpfb_priv *priv = dev_id;
u32 int_stat;
int i;
struct pdpfb_isr_data *isr_data;
struct pdpfb_isr_data registered_isr[PDPFB_MAX_NR_ISRS];
int_stat = pdpfb_read(priv, PDP_INTSTAT);
pdpfb_write(priv, PDP_INTCLR, int_stat);
spin_lock(&priv->irq_lock);
if (priv->isr_disable) {
spin_unlock(&priv->irq_lock);
return IRQ_HANDLED;
}
/* make a copy and unlock, so that isrs can unregister themselves */
memcpy(registered_isr, priv->registered_isr,
sizeof(registered_isr));
spin_unlock(&priv->irq_lock);
for (i = 0; i < PDPFB_MAX_NR_ISRS; i++) {
isr_data = &registered_isr[i];
if (!isr_data->isr)
continue;
if (isr_data->mask & int_stat)
isr_data->isr(isr_data->arg, int_stat);
}
return IRQ_HANDLED;
}
/* priv->irq_lock has to be locked by the caller */
static void _pdpfb_set_irqs(struct pdpfb_priv *priv)
{
u32 mask;
u32 old_mask;
int i;
struct pdpfb_isr_data *isr_data;
if (priv->isr_disable) {
pdpfb_write(priv, PDP_INTENAB, 0);
return;
}
mask = 0;
for (i = 0; i < PDPFB_MAX_NR_ISRS; i++) {
isr_data = &priv->registered_isr[i];
if (isr_data->isr == NULL)
continue;
mask |= isr_data->mask;
}
clk_enable(priv->pdp_clk);
old_mask = pdpfb_read(priv, PDP_INTENAB);
/* clear the irqstatus for newly enabled irqs */
pdpfb_write(priv, PDP_INTCLR, (mask ^ old_mask) & mask);
pdpfb_write(priv, PDP_INTENAB, mask);
clk_disable(priv->pdp_clk);
}
static void pdpfb_disable_isr(struct pdpfb_priv *priv)
{
unsigned long flags;
spin_lock_irqsave(&priv->irq_lock, flags);
priv->isr_disable = 1;
_pdpfb_set_irqs(priv);
spin_unlock_irqrestore(&priv->irq_lock, flags);
}
static void pdpfb_enable_isr(struct pdpfb_priv *priv)
{
unsigned long flags;
spin_lock_irqsave(&priv->irq_lock, flags);
priv->isr_disable = 0;
_pdpfb_set_irqs(priv);
spin_unlock_irqrestore(&priv->irq_lock, flags);
}
int pdpfb_register_isr(pdpfb_isr_t isr, void *arg, u32 mask)
{
struct pdpfb_priv *priv = pdpfb_priv;
int i;
int ret;
unsigned long flags;
struct pdpfb_isr_data *isr_data;
if (!priv)
return -ENODEV;
if (isr == NULL)
return -EINVAL;
spin_lock_irqsave(&priv->irq_lock, flags);
/* check for duplicate entry */
for (i = 0; i < PDPFB_MAX_NR_ISRS; i++) {
isr_data = &priv->registered_isr[i];
if (isr_data->isr == isr && isr_data->arg == arg &&
isr_data->mask == mask) {
ret = -EINVAL;
goto err;
}
}
isr_data = NULL;
ret = -EBUSY;
for (i = 0; i < PDPFB_MAX_NR_ISRS; i++) {
isr_data = &priv->registered_isr[i];
if (isr_data->isr != NULL)
continue;
isr_data->isr = isr;
isr_data->arg = arg;
isr_data->mask = mask;
ret = 0;
break;
}
_pdpfb_set_irqs(priv);
spin_unlock_irqrestore(&priv->irq_lock, flags);
return 0;
err:
spin_unlock_irqrestore(&priv->irq_lock, flags);
return ret;
}
EXPORT_SYMBOL(pdpfb_register_isr);
int pdpfb_unregister_isr(pdpfb_isr_t isr, void *arg, u32 mask)
{
struct pdpfb_priv *priv = pdpfb_priv;
int i;
unsigned long flags;
int ret = -EINVAL;
struct pdpfb_isr_data *isr_data;
if (!priv)
return -ENODEV;
spin_lock_irqsave(&priv->irq_lock, flags);
for (i = 0; i < PDPFB_MAX_NR_ISRS; i++) {
isr_data = &priv->registered_isr[i];
if (isr_data->isr != isr || isr_data->arg != arg ||
isr_data->mask != mask)
continue;
/* found the correct isr */
isr_data->isr = NULL;
isr_data->arg = NULL;
isr_data->mask = 0;
ret = 0;
break;
}
if (ret == 0)
_pdpfb_set_irqs(priv);
spin_unlock_irqrestore(&priv->irq_lock, flags);
return ret;
}
EXPORT_SYMBOL(pdpfb_unregister_isr);
static void pdpfb_irq_wait_handler(void *data, u32 mask)
{
complete((struct completion *)data);
}
int pdpfb_wait_for_irq_timeout(u32 irqmask, unsigned long timeout)
{
int r;
DECLARE_COMPLETION_ONSTACK(completion);
r = pdpfb_register_isr(pdpfb_irq_wait_handler, &completion, irqmask);
if (r)
return r;
timeout = wait_for_completion_timeout(&completion, timeout);
pdpfb_unregister_isr(pdpfb_irq_wait_handler, &completion, irqmask);
if (timeout == 0)
return -ETIMEDOUT;
if (timeout == -ERESTARTSYS)
return -ERESTARTSYS;
return 0;
}
int pdpfb_wait_for_irq_interruptible_timeout(u32 irqmask,
unsigned long timeout)
{
int r;
DECLARE_COMPLETION_ONSTACK(completion);
r = pdpfb_register_isr(pdpfb_irq_wait_handler, &completion, irqmask);
if (r)
return r;
timeout = wait_for_completion_interruptible_timeout(&completion,
timeout);
pdpfb_unregister_isr(pdpfb_irq_wait_handler, &completion, irqmask);
if (timeout == 0)
return -ETIMEDOUT;
if (timeout == -ERESTARTSYS)
return -ERESTARTSYS;
return 0;
}
int pdpfb_wait_vsync(void)
{
unsigned long timeout = msecs_to_jiffies(500);
u32 irq = PDPFB_IRQ_VEVENT0;
return pdpfb_wait_for_irq_interruptible_timeout(irq, timeout);
}
EXPORT_SYMBOL(pdpfb_wait_vsync);
#ifndef CONFIG_FB_PDP_PDUMP
static void pdpfb_vsync_isr(void *arg, u32 mask)
{
struct pdpfb_priv *priv = (struct pdpfb_priv *)arg;
++priv->vsync_count;
}
#endif
struct pdp_info *pdpfb_get_platform_data(struct pdpfb_priv *priv)
{
return priv->device->platform_data;
}
u32 *pdpfb_get_pseudo_palette(struct pdpfb_priv *priv)
{
return priv->pseudo_palette;
}
enum pdpfb_display_mode {
PDP_FULLY_POWERED_DOWN,
PDP_PARTIALLY_POWERED_DOWN,
PDP_ENABLED
};
static void pdpfb_set_display_enabled(struct pdpfb_priv *priv,
enum pdpfb_display_mode mode)
{
struct pdp_info *pdata = priv->device->platform_data;
u32 sync_ctrl;
u32 op_mask;
int powerdn = 0;
if (mode == PDP_FULLY_POWERED_DOWN && pdata->sync_cfg.force_vsyncs)
mode = PDP_PARTIALLY_POWERED_DOWN;
switch (mode) {
case PDP_FULLY_POWERED_DOWN:
sync_ctrl = pdpfb_read(priv, PDP_SYNCCTRL);
SET_FIELD(sync_ctrl, PDP_SYNCCTRL_SYNCACTIVE, 0);
SET_FIELD(sync_ctrl, PDP_SYNCCTRL_POWERDN, 1);
pdpfb_write(priv, PDP_SYNCCTRL, sync_ctrl);
pdpfb_disable_isr(priv);
if (pdata->hwops.set_screen_power)
pdata->hwops.set_screen_power(0);
pdpfb_pixel_clk_disable(priv);
break;
case PDP_PARTIALLY_POWERED_DOWN:
powerdn = 1;
case PDP_ENABLED:
op_mask = pdpfb_read(priv, PDP_OPMASK);
SET_FIELD(op_mask, PDP_OPMASK_MASKR, 0x00);
SET_FIELD(op_mask, PDP_OPMASK_MASKG, 0x00);
SET_FIELD(op_mask, PDP_OPMASK_MASKB, 0x00);
SET_FIELD(op_mask, PDP_OPMASK_MASKLEVEL, 0);
pdpfb_write(priv, PDP_OPMASK, op_mask);
sync_ctrl = pdpfb_read(priv, PDP_SYNCCTRL);
SET_FIELD(sync_ctrl, PDP_SYNCCTRL_UPDCTRL, 0);
SET_FIELD(sync_ctrl, PDP_SYNCCTRL_SYNCACTIVE, 1);
SET_FIELD(sync_ctrl, PDP_SYNCCTRL_POWERDN, powerdn);
pdpfb_write(priv, PDP_SYNCCTRL, sync_ctrl);
pdpfb_pixel_clk_enable(priv);
if (pdata->hwops.set_screen_power)
pdata->hwops.set_screen_power(!powerdn);
pdpfb_enable_isr(priv);
break;
default:
dev_err(priv->device,
"Unrecognised pdpfb_set_display_enabled parameter %d\n",
mode);
}
}
void pdpfb_update_margins(struct pdpfb_priv *priv,
struct pdpfb_stream *stream)
{
struct pdp_info *pdata = priv->device->platform_data;
struct fb_info *info = &stream->info;
int xres, yres, w, h;
int xgap[2];
int ygap[2];
xres = info->var.xres;
yres = info->var.yres;
w = (stream->geom.w ? stream->geom.w : xres);
h = (stream->geom.h ? stream->geom.h : yres);
xgap[0] = stream->geom.x;
ygap[0] = stream->geom.y;
/* we can't really handle the case of a plane positioned offscreen */
xgap[1] = max(0, (int)priv->mode.xres - w - xgap[0]);
ygap[1] = max(0, (int)priv->mode.yres - h - ygap[0]);
/* physical size of plane */
info->var.width = pdata->lcd_size_cfg.width * w / priv->mode.xres;
info->var.height = pdata->lcd_size_cfg.height * h / priv->mode.yres;
/* measurements in units of plane pixels */
info->var.pixclock = priv->mode.pixclock * w / xres * h / yres;
info->var.hsync_len = priv->mode.hsync_len * xres / w;
info->var.vsync_len = priv->mode.vsync_len * yres / h;
info->var.left_margin = (priv->mode.left_margin + xgap[0])
* xres / w;
info->var.upper_margin = (priv->mode.upper_margin + ygap[0])
* yres / h;
info->var.right_margin = (priv->mode.right_margin + xgap[1])
* xres / w;
info->var.lower_margin = (priv->mode.lower_margin + ygap[1])
* yres / h;
}
static void pdpfb_update_all_margins(struct pdpfb_priv *priv)
{
int i;
for (i = 0; i < PDPFB_STREAM_NR; ++i) {
struct pdpfb_stream *stream = priv->streams[i];
if (!stream || !stream->probed)
continue;
pdpfb_update_margins(priv, stream);
}
}
/* update variables but not registers */
static void pdpfb_update_refresh_rate(struct pdpfb_priv *priv)
{
u32 ht, vt;
unsigned long pix_freq;
if (priv->mode.pixclock) {
pix_freq = 1000*PICOS2KHZ(priv->mode.pixclock);
} else {
ht = priv->mode.hsync_len
+ priv->mode.left_margin
+ priv->mode.xres
+ priv->mode.right_margin;
vt = priv->mode.vsync_len
+ priv->mode.upper_margin
+ priv->mode.yres
+ priv->mode.lower_margin;
pix_freq = ht*vt*priv->mode.refresh;
}
clk_set_rate(priv->pixel_clk, pix_freq);
clk_prepare_enable(priv->pixel_clk);
pix_freq = clk_get_rate(priv->pixel_clk);
clk_disable_unprepare(priv->pixel_clk);
priv->mode.pixclock = KHZ2PICOS(pix_freq/1000);
pdpfb_update_all_margins(priv);
}
/*
* Updates pixel clock if it's changed (pixclock is pS).
* Returns 1 if changed.
*/
int pdpfb_update_pixclock(struct pdpfb_priv *priv, unsigned int pixclock)
{
if (pixclock != priv->mode.pixclock) {
priv->mode.pixclock = pixclock;
pdpfb_update_refresh_rate(priv);
return 1;
}
return 0;
}
unsigned long pdpfb_get_line_length(int xres_virtual, int bpp)
{
#define LINELEN_ALIGNMENT_BYTES 16 /* in bytes, must be power of 2 */
#define LINELEN_ALIGNMENT (LINELEN_ALIGNMENT_BYTES << 3)
u_long length;
/* round up to line length alignment */
length = xres_virtual * bpp;
length = (length + (LINELEN_ALIGNMENT-1)) & -LINELEN_ALIGNMENT;
length >>= 3;
return length;
}
static void pdpfb_soft_reset(struct pdpfb_priv *priv)
{
/*
* We intentionally include the video plane even if support for it is
* not compiled in. This is because it may already have been enabled by
* the bootloader and we don't want a green plane on top of the
* graphics plane.
*/
static const unsigned int strctrls[] = {
PDP_STR1CTRL,
PDP_STR2CTRL,
};
int i;
u32 sync_ctrl, ctrl;
sync_ctrl = pdpfb_read(priv, PDP_SYNCCTRL);
SET_FIELD(sync_ctrl, PDP_SYNCCTRL_DISPRST, 1);
pdpfb_write(priv, PDP_SYNCCTRL, sync_ctrl);
SET_FIELD(sync_ctrl, PDP_SYNCCTRL_DISPRST, 0);
pdpfb_write(priv, PDP_SYNCCTRL, sync_ctrl);
/* disable all the streams */
for (i = 0; i < ARRAY_SIZE(strctrls); ++i) {
ctrl = pdpfb_read(priv, strctrls[i]);
SET_FIELD(ctrl, PDP_STRXCTRL_STREAMEN, 0);
SET_FIELD(ctrl, PDP_STRXCTRL_BLENDPOS, i);
pdpfb_write(priv, strctrls[i], ctrl);
}
}
static void pdpfb_configure_display(struct pdpfb_priv *priv)
{
struct pdp_info *pdata = priv->device->platform_data;
u32 sync_ctrl;
u32 mem_ctrl;
sync_ctrl = pdpfb_read(priv, PDP_SYNCCTRL);
SET_FIELD(sync_ctrl, PDP_SYNCCTRL_CLKPOL, pdata->sync_cfg.clock_pol);
SET_FIELD(sync_ctrl, PDP_SYNCCTRL_VSPOL,
0 == (priv->mode.sync & FB_SYNC_VERT_HIGH_ACT));
SET_FIELD(sync_ctrl, PDP_SYNCCTRL_HSPOL,
0 == (priv->mode.sync & FB_SYNC_HOR_HIGH_ACT));
SET_FIELD(sync_ctrl, PDP_SYNCCTRL_BLNKPOL, pdata->sync_cfg.blank_pol);
SET_FIELD(sync_ctrl, PDP_SYNCCTRL_VSDIS, pdata->sync_cfg.vsync_dis);
SET_FIELD(sync_ctrl, PDP_SYNCCTRL_HSDIS, pdata->sync_cfg.hsync_dis);
SET_FIELD(sync_ctrl, PDP_SYNCCTRL_BLNKDIS, pdata->sync_cfg.blank_dis);
SET_FIELD(sync_ctrl, PDP_SYNCCTRL_VSSLAVE, pdata->sync_cfg.sync_slave);
SET_FIELD(sync_ctrl, PDP_SYNCCTRL_HSSLAVE, pdata->sync_cfg.sync_slave);
pdpfb_write(priv, PDP_SYNCCTRL, sync_ctrl);
mem_ctrl = pdpfb_read(priv, PDP_MEMCTRL);
SET_FIELD(mem_ctrl, PDP_MEMCTRL_MEMREFRESH,
PDP_MEMCTRL_MEMREFRESH_BOTH);
pdpfb_write(priv, PDP_MEMCTRL, mem_ctrl);
}
#ifdef PDP_CKEY_BY_BLENDPOS
const static struct pdpfb_stream_regs pdpfb_ckey_regs[] = {
{
.blend = PDP_STR1BLEND,
.blend2 = PDP_STR1BLEND2,
.ctrl = PDP_STR1CTRL,
},
{
.blend = PDP_STR2BLEND,
.blend2 = PDP_STR2BLEND2,
.ctrl = PDP_STR2CTRL,
},
};
#define CKEY_REGS(STREAM, BLENDPOS) (pdpfb_ckey_regs[(BLENDPOS)])
#else
#define CKEY_REGS(STREAM, BLENDPOS) ((STREAM)->regs)
#endif
static void pdpfb_configure_streams(struct pdpfb_priv *priv)
{
u32 ctrl, posn, blend, blend2;
u32 en, i;
pdpfb_write(priv, PDP_BGNDCOL, priv->bgnd_col);
for (i = 0; i < PDPFB_STREAM_NR; ++i) {
struct pdpfb_stream *stream = priv->streams[i];
const struct pdpfb_stream_regs *ckey_regs;
if (!stream)
continue;
if (stream->probed && stream->ops.configure) {
en = !stream->ops.configure(priv) && stream->enable;
if (en && stream->ops.configure_addr)
stream->ops.configure_addr(priv);
} else {
en = 0;
}
if (en) {
/* stream position on screen */
posn = pdpfb_read(priv, stream->regs.posn);
SET_FIELD(posn, PDP_STRXPOSN_XSTART, stream->geom.x);
SET_FIELD(posn, PDP_STRXPOSN_YSTART, stream->geom.y);
pdpfb_write(priv, stream->regs.posn, posn);
}
/* set up blending */
ctrl = pdpfb_read(priv, stream->regs.ctrl);
SET_FIELD(ctrl, PDP_STRXCTRL_STREAMEN, en);
SET_FIELD(ctrl, PDP_STRXCTRL_BLENDMODE, stream->blend_mode);
SET_FIELD(ctrl, PDP_STRXCTRL_BLENDPOS, i);
pdpfb_write(priv, stream->regs.ctrl, ctrl);
blend = pdpfb_read(priv, stream->regs.blend);
SET_FIELD(blend, PDP_STRXBLEND_GLOBALALPHA,
stream->global_alpha);
pdpfb_write(priv, stream->regs.blend, blend);
/* set up colour keying */
ckey_regs = &CKEY_REGS(stream, i);
ctrl = pdpfb_read(priv, ckey_regs->ctrl);
SET_FIELD(ctrl, PDP_STRXCTRL_CKEYEN, stream->ckey_en);
SET_FIELD(ctrl, PDP_STRXCTRL_CKEYSRC, stream->ckey_src);
pdpfb_write(priv, ckey_regs->ctrl, ctrl);
blend = pdpfb_read(priv, ckey_regs->blend);
SET_FIELD(blend, PDP_STRXBLEND_COLKEY, stream->ckey.ckey);
pdpfb_write(priv, ckey_regs->blend, blend);
blend2 = pdpfb_read(priv, ckey_regs->blend2);
SET_FIELD(blend2, PDP_STRXBLEND2_COLKEYMASK, stream->ckey.mask);
pdpfb_write(priv, ckey_regs->blend2, blend2);
}
}
/* Extract the current sync setup from the hardware */
int pdpfb_extract_sync(struct pdpfb_priv *priv)
{
u32 hsync1, hde_ctrl;
u32 vsync1, vde_ctrl;
u16 vbps, vt, vdes, vdef;
u16 hbps, ht, hdes, hdef;
/* read vsync */
vsync1 = pdpfb_read(priv, PDP_VSYNC1);
vbps = GET_FIELD(vsync1, PDP_VSYNC1_VBPS);
vt = GET_FIELD(vsync1, PDP_VSYNC1_VT);
vde_ctrl = pdpfb_read(priv, PDP_VDECTRL);
vdes = GET_FIELD(vde_ctrl, PDP_VDECTRL_VDES);
vdef = GET_FIELD(vde_ctrl, PDP_VDECTRL_VDEF);
if (!vbps || vdes <= vbps || vdef <= vdes || vt <= vdef)
return -EINVAL;
/* read hsync */
hsync1 = pdpfb_read(priv, PDP_HSYNC1);
hbps = GET_FIELD(hsync1, PDP_HSYNC1_HBPS);
ht = GET_FIELD(hsync1, PDP_HSYNC1_HT);
hde_ctrl = pdpfb_read(priv, PDP_HDECTRL);
hdes = GET_FIELD(hde_ctrl, PDP_HDECTRL_HDES);
hdef = GET_FIELD(hde_ctrl, PDP_HDECTRL_HDEF);
if (!hbps || hdes <= hbps || hdef <= hdes || ht <= hdef)
return -EINVAL;
/* calculate timings */
priv->mode.vsync_len = vbps;
priv->mode.upper_margin = vdes - vbps;
priv->mode.yres = vdef - vdes;
priv->mode.lower_margin = vt - vdef;
priv->mode.hsync_len = hbps;
priv->mode.left_margin = hdes - hbps;
priv->mode.xres = hdef - hdes;
priv->mode.right_margin = ht - hdef;
return 0;
}
/* Set up sync registers to position the frame buffers on the screen */
int pdpfb_configure_sync(struct pdpfb_priv *priv)
{
struct pdpfb_update_sync *sync = &priv->upd.sync;
unsigned long flags;
u32 sync_ctrl;
u32 vsync1, vsync2, vsync3;
u32 vevent;
u32 vde_ctrl;
u16 hbps, ht, hfps, hrbs, has, hlbs;
u16 vbps, vt, vtbs, vfps, vas, vbbs;
u16 vevent_vevent, vevent_vfetch;
u16 hdes, hdef;
u16 vdes, vdef;
int border[2][2] = { {3, 3}, {3, 3} };
hbps = priv->mode.hsync_len;
hdes = hbps + priv->mode.left_margin;
has = hdes;
hlbs = has - border[0][0];
hdef = hdes + priv->mode.xres;
hrbs = hdef;
hfps = hrbs + border[0][1];
ht = hrbs + priv->mode.right_margin;
vbps = priv->mode.vsync_len;
vdes = vbps + priv->mode.upper_margin;
vas = vdes;
vtbs = vas - border[1][0];
vdef = vdes + priv->mode.yres;
vbbs = vdef;
vfps = vbbs + border[1][1];
vt = vbbs + priv->mode.lower_margin;
vevent_vevent = 0;
vevent_vfetch = vbps;
/* write registers that shouldn't cause artifacts */
vsync1 = PLACE_FIELD(PDP_VSYNC1_VBPS, vbps)
| PLACE_FIELD(PDP_VSYNC1_VT, vt);
vsync2 = PLACE_FIELD(PDP_VSYNC2_VAS, vas)
| PLACE_FIELD(PDP_VSYNC2_VTBS, vtbs);
vsync3 = PLACE_FIELD(PDP_VSYNC3_VFPS, vfps)
| PLACE_FIELD(PDP_VSYNC3_VBBS, vbbs);
pdpfb_write(priv, PDP_VSYNC1, vsync1);
pdpfb_write(priv, PDP_VSYNC2, vsync2);
pdpfb_write(priv, PDP_VSYNC3, vsync3);
vevent = PLACE_FIELD(PDP_VEVENT_VEVENT, vevent_vevent)
| PLACE_FIELD(PDP_VEVENT_VFETCH, vevent_vfetch);
vde_ctrl = PLACE_FIELD(PDP_VDECTRL_VDES, vdes)
| PLACE_FIELD(PDP_VDECTRL_VDEF, vdef);
pdpfb_write(priv, PDP_VEVENT, vevent);
pdpfb_write(priv, PDP_VDECTRL, vde_ctrl);
/* write registers that would cause artifacts on next VBLANK */
spin_lock_irqsave(&priv->upd.lock, flags);
sync->hsync1 = PLACE_FIELD(PDP_HSYNC1_HBPS, hbps)
| PLACE_FIELD(PDP_HSYNC1_HT, ht);
sync->hsync2 = PLACE_FIELD(PDP_HSYNC2_HAS, has)
| PLACE_FIELD(PDP_HSYNC2_HLBS, hlbs);
sync->hsync3 = PLACE_FIELD(PDP_HSYNC3_HFPS, hfps)
| PLACE_FIELD(PDP_HSYNC3_HRBS, hrbs);
sync->hde_ctrl = PLACE_FIELD(PDP_HDECTRL_HDES, hdes)
| PLACE_FIELD(PDP_HDECTRL_HDEF, hdef);
sync_ctrl = pdpfb_read(priv, PDP_SYNCCTRL);
if (GET_FIELD(sync_ctrl, PDP_SYNCCTRL_SYNCACTIVE)) {
/* sync enabled, apply on next VSYNC */
priv->upd.updates |= PDP_UPDATE_SYNC;
pdpfb_register_isr(pdpfb_interrupt_upd, priv,
PDPFB_IRQ_VEVENT0);
} else {
/* sync disabled, apply immediately */
pdpfb_sync_upd(priv);
}
spin_unlock_irqrestore(&priv->upd.lock, flags);
return 0;
}
static void pdpfb_emit_blank_event(struct pdpfb_priv *priv)
{
struct fb_event event;
int blank = priv->power;
int i;
/* skip blank events if no_blank_emit flag is set */
if (priv->no_blank_emit)
return;
/* skip the final blank if we know it'll be emitted anyway */
if (priv->final_blank == blank)
return;
event.data = &blank;
for (i = 0; i < PDPFB_STREAM_NR; ++i) {
struct pdpfb_stream *stream = priv->streams[i];
int flags;
if (!stream)
continue;
event.info = &stream->info;
/* prevent fbcon responding badly to the blank events */
flags = event.info->flags;
event.info->flags |= FBINFO_MISC_USEREVENT;
fb_notifier_call_chain(FB_EVENT_BLANK, &event);
event.info->flags = flags;
}
}
/* FULLY_OFF -> PANEL_ENABLED */
static void pdpfb_power_panel_en(struct pdpfb_priv *priv)
{
dev_dbg(priv->device, "panel_en\n");
pdpfb_soft_reset(priv);
pdpfb_write(priv, PDP_INTCLR, 0xFFFFFFFF);
pdpfb_emit_blank_event(priv);
}
/* PANEL_ENABLED -> FULLY_OFF */
static void pdpfb_power_panel_dis(struct pdpfb_priv *priv)
{
dev_dbg(priv->device, "panel_dis\n");
pdpfb_emit_blank_event(priv);
}
/* PANEL_ENABLED -> SYNC_ENABLED */
static void pdpfb_power_sync_en(struct pdpfb_priv *priv)
{
dev_dbg(priv->device, "sync_en\n");
pdpfb_update_refresh_rate(priv);
pdpfb_configure_display(priv);
pdpfb_configure_sync(priv);
pdpfb_set_display_enabled(priv, PDP_PARTIALLY_POWERED_DOWN);
pdpfb_emit_blank_event(priv);
}
/* SYNC_ENABLED -> PANEL_ENABLED */
static void pdpfb_power_sync_dis(struct pdpfb_priv *priv)
{
dev_dbg(priv->device, "sync_dis\n");
pdpfb_set_display_enabled(priv, PDP_FULLY_POWERED_DOWN);
pdpfb_emit_blank_event(priv);
}
/* SYNC_ENABLED -> PANEL_POWERED */
static void pdpfb_power_panelpwr_en(struct pdpfb_priv *priv)
{
dev_dbg(priv->device, "panelpwr_en\n");
pdpfb_emit_blank_event(priv);
pdpfb_configure_streams(priv);
}
/* PANEL_POWERED -> SYNC_ENABLED */
static void pdpfb_power_panelpwr_dis(struct pdpfb_priv *priv)
{
dev_dbg(priv->device, "panelpwr_dis\n");
pdpfb_emit_blank_event(priv);
}
/* PANEL_POWERED -> BACKLIT */
static void pdpfb_power_bl_en(struct pdpfb_priv *priv)
{
dev_dbg(priv->device, "bl_en\n");
pdpfb_set_display_enabled(priv, PDP_ENABLED);
pdpfb_emit_blank_event(priv);
}
/* BACKLIT -> PANEL_POWERED */
static void pdpfb_power_bl_dis(struct pdpfb_priv *priv)
{
dev_dbg(priv->device, "bl_dis\n");
pdpfb_emit_blank_event(priv);
/* enable blanking after vsync to avoid tearing */
pdpfb_wait_vsync();
pdpfb_set_display_enabled(priv, PDP_PARTIALLY_POWERED_DOWN);
}
/* power transition functions */
typedef void (*pdpfb_power_change)(struct pdpfb_priv *priv);
static const pdpfb_power_change pdpfb_power_fn[PDPFB_POWER_MAX][2] = {
[PDPFB_POWER_BACKLIT]
= { pdpfb_power_bl_en, pdpfb_power_bl_dis },
[PDPFB_POWER_PANEL_POWERED]
= { pdpfb_power_panelpwr_en, pdpfb_power_panelpwr_dis },
[PDPFB_POWER_SYNC_ENABLED]
= { pdpfb_power_sync_en, pdpfb_power_sync_dis },
[PDPFB_POWER_PANEL_ENABLED]
= { pdpfb_power_panel_en, pdpfb_power_panel_dis },
};
/* move to prestate (lower power) then to state (higher power) */
static int pdpfb_reconfigure(struct pdpfb_priv *priv,
enum pdpfb_power_state prestate,
enum pdpfb_power_state state)
{
WARN_CONSOLE_UNLOCKED();
if (prestate >= PDPFB_POWER_MAX || state <= PDPFB_POWER_NA)
return -EINVAL;
/* ignore blank events that we emitted ourselves */
if (priv->reconfiguring)
return -EBUSY;
priv->reconfiguring = 1;
while (priv->power < prestate) {
++priv->power;
pdpfb_power_fn[priv->power - 1][1](priv);
}
while (priv->power > state) {
--priv->power;
pdpfb_power_fn[priv->power][0](priv);
}
priv->reconfiguring = 0;
return 0;
}
/* move to a different power state */
static int pdpfb_configure(struct pdpfb_priv *priv,
enum pdpfb_power_state state)
{
return pdpfb_reconfigure(priv, state, state);
}
void pdpfb_set_mode(struct pdpfb_priv *priv, struct fb_var_screeninfo *var)
{
/*
* Blanking events confuse fbcon, and aren't strictly necessary when
* we're just changing mode.
*/
priv->no_blank_emit = 1;
if (var->activate & FB_ACTIVATE_FORCE) {
fb_var_to_videomode(&priv->mode, var);
pdpfb_reconfigure(priv, PDPFB_POWER_PANEL_ENABLED, priv->power);
} else {
struct fb_videomode old_mode = priv->mode;
fb_var_to_videomode(&priv->mode, var);
if (!fb_mode_is_equal(&priv->mode, &old_mode))
pdpfb_reconfigure(priv, PDPFB_POWER_PANEL_ENABLED,
priv->power);
else
pdpfb_configure_streams(priv);
}
priv->no_blank_emit = 0;
}
static inline u32 _pdpfb_convert_palette(struct fb_bitfield *red_bits,
struct fb_bitfield *green_bits,
struct fb_bitfield *blue_bits,
u_int red, u_int green, u_int blue)
{
u32 rgb;
rgb = MOVE_FIELD(red, 0, 16,
red_bits->offset,
red_bits->length);
rgb |= MOVE_FIELD(green, 0, 16,
green_bits->offset,
green_bits->length);
rgb |= MOVE_FIELD(blue, 0, 16,
blue_bits->offset,
blue_bits->length);
return rgb;
}
static inline void _pdpfb_set_pseudo_palette(struct pdpfb_priv *priv,
struct fb_info *info, u_int regno,
u_int red, u_int green,
u_int blue, u_int trans)
{
priv->pseudo_palette[regno] = _pdpfb_convert_palette(
&info->var.red, &info->var.green, &info->var.blue,
red, green, blue);
}
static inline u32 _pdpfb_hw_palette(struct pdpfb_priv *priv, u_int regno,
u_int red, u_int green,
u_int blue, u_int trans)
{
return _pdpfb_convert_palette(&priv->palette_fmt.r,
&priv->palette_fmt.g,
&priv->palette_fmt.b,
red, green, blue);
}
int pdpfb_setcolreg(u_int regno,
u_int red, u_int green, u_int blue,
u_int trans, struct fb_info *info)
{
struct pdpfb_priv *priv;
u32 palette_rgb;
unsigned long flags;
if (regno > 255)
return -EINVAL;
priv = dev_get_drvdata(info->device);
/* update pseudo palette used by fbcon */
_pdpfb_set_pseudo_palette(priv, info, regno, red, green, blue, trans);
/* queue hardware palette update */
palette_rgb = _pdpfb_hw_palette(priv, regno, red, green, blue, trans);
_pdpfb_start_queue_lut_upd(priv, &flags);
_pdpfb_queue_lut_upd(priv, regno, palette_rgb);
_pdpfb_end_queue_lut_upd(priv, &flags);
return 0;
}
int pdpfb_setcmap(struct fb_cmap *cmap, struct fb_info *info)
{
struct pdpfb_priv *priv;
int i, start;
u16 *red, *green, *blue, *transp;
u_int hred, hgreen, hblue, htransp = 0xffff;
unsigned long flags;
u32 palette_rgb;
priv = dev_get_drvdata(info->device);
red = cmap->red;
green = cmap->green;
blue = cmap->blue;
transp = cmap->transp;
start = cmap->start;
if (start + cmap->len > PDP_PALETTE_NR)
return -EINVAL;
_pdpfb_start_queue_lut_upd(priv, &flags);
for (i = 0; i < cmap->len; i++) {
hred = *red++;
hgreen = *green++;
hblue = *blue++;
if (transp)
htransp = *transp++;
/* update pseudo palette used by fbcon */
_pdpfb_set_pseudo_palette(priv, info, i,
hred, hgreen, hblue, htransp);
/* queue hardware palette update */
palette_rgb = _pdpfb_hw_palette(priv, i,
hred, hgreen, hblue, htransp);
_pdpfb_queue_lut_upd(priv, i, palette_rgb);
}
_pdpfb_end_queue_lut_upd(priv, &flags);
return 0;
}
int pdpfb_blank(int blank, struct fb_info *info)
{
struct pdpfb_priv *priv = dev_get_drvdata(info->device);
int err, prev_final_blank;
prev_final_blank = priv->final_blank;
priv->final_blank = blank;
err = pdpfb_configure(priv, blank);
priv->final_blank = prev_final_blank;
return err;
}
static void pdpfb_str_get_vblank(struct pdpfb_priv *priv,
struct pdpfb_stream *stream,
struct fb_vblank *vblank)
{
int line_no, geomy, geomh;
line_no = pdpfb_read(priv, PDP_LINESTAT);
line_no = GET_FIELD(line_no, PDP_LINESTAT_LINENO_STAT);
geomy = priv->mode.vsync_len + priv->mode.upper_margin + stream->geom.y;
geomh = stream->geom.h;
if (!geomh)
geomh = stream->info.var.yres;
memset(vblank, 0, sizeof(*vblank));
vblank->flags = FB_VBLANK_HAVE_VBLANK |
FB_VBLANK_HAVE_COUNT |
FB_VBLANK_HAVE_VCOUNT |
FB_VBLANK_HAVE_VSYNC;
if (line_no < geomy ||
line_no >= geomy + geomh)
vblank->flags |= FB_VBLANK_VBLANKING;
if (line_no < priv->mode.vsync_len)
vblank->flags |= FB_VBLANK_VSYNCING;
vblank->count = priv->vsync_count;
vblank->vcount = line_no * stream->info.var.yres / geomh;
}
static int pdpfb_str_get_plane_pos(struct pdpfb_priv *priv,
struct pdpfb_stream *stream)
{
int i;
for (i = 0; i < PDPFB_STREAM_NR; ++i) {
if (priv->streams[i] == stream)
return i;
}
return -EINVAL;
}
/* returns new position, or negative on error */
static int pdpfb_str_set_plane_pos(struct pdpfb_priv *priv,
struct pdpfb_stream *stream,
int pos)
{
int old_pos = pdpfb_str_get_plane_pos(priv, stream);
if (unlikely(old_pos < 0))
return old_pos;
if (unlikely(pos < 0 || pos >= PDPFB_STREAM_NR))
return -EINVAL;
if (pos == old_pos)
return pos;
if (pos > old_pos) {
do {
priv->streams[old_pos] = priv->streams[old_pos+1];
++old_pos;
} while (pos > old_pos);
} else if (pos < old_pos) {
do {
priv->streams[old_pos] = priv->streams[old_pos-1];
--old_pos;
} while (pos < old_pos);
}
priv->streams[pos] = stream;
pdpfb_configure_streams(priv);
return pos;
}
int pdpfb_str_ioctl(struct pdpfb_priv *priv, struct pdpfb_stream *stream,
unsigned int cmd, unsigned long arg)
{
void __user *argp = (void __user *)arg;
struct pdpfb_geom geom;
struct pdpfb_ckey ckey;
struct fb_vblank vblank;
int plane_pos;
int error;
u32 val;
#ifdef CONFIG_FB_PDP_USERMEM
struct pdpfb_usermem usermem;
struct pdp_info *pdata;
int i, ret;
#endif
switch (cmd) {
/* get blanking info */
case FBIOGET_VBLANK:
pdpfb_str_get_vblank(priv, stream, &vblank);
if (copy_to_user(argp, &vblank, sizeof(vblank)))
return -EFAULT;
return 0;
/* wait for vsync */
case FBIO_WAITFORVSYNC:
if (get_user(val, (u32 __user *)arg))
return -EFAULT;
if (val != 0)
return -ENODEV;
return pdpfb_wait_vsync();
/* get background colour */
case PDPIO_GETBGND:
if (copy_to_user(argp, &priv->bgnd_col, sizeof(priv->bgnd_col)))
return -EFAULT;
return 0;
/* set background colour */
case PDPIO_SETBGND:
if (copy_from_user(&val, argp, sizeof(val)))
return -EFAULT;
priv->bgnd_col = val & 0xFFFFFF;
pdpfb_configure_streams(priv);
return 0;
/* get screen geometry */
case PDPIO_GETSCRGEOM:
geom.x = geom.y = 0;
geom.w = priv->mode.xres;
geom.h = priv->mode.yres;
if (copy_to_user(argp, &geom, sizeof(geom)))
return -EFAULT;
return 0;
/* get stream enable */
case PDPIO_GETEN:
val = stream->enable;
if (copy_to_user(argp, &val, sizeof(val)))
return -EFAULT;
return 0;
/* set stream enable */
case PDPIO_SETEN:
if (copy_from_user(&val, argp, sizeof(val)))
return -EFAULT;
stream->enable = (0 != val);
pdpfb_configure_streams(priv);
return 0;
/* get stream plane position */
case PDPIO_GETPLANEPOS:
plane_pos = pdpfb_str_get_plane_pos(priv, stream);
if (unlikely(plane_pos < 0))
return -EINVAL;
if (copy_to_user(argp, &plane_pos, sizeof(plane_pos)))
return -EFAULT;
return 0;
/* set stream plane position */
case PDPIO_SETPLANEPOS:
if (copy_from_user(&plane_pos, argp, sizeof(plane_pos)))
return -EFAULT;
plane_pos = pdpfb_str_set_plane_pos(priv, stream, plane_pos);
if (plane_pos < 0)
return plane_pos;
if (copy_to_user(argp, &plane_pos, sizeof(plane_pos)))
return -EFAULT;
return 0;
/* get stream geometry */
case PDPIO_GETGEOM:
geom = stream->geom;
if (copy_to_user(argp, &geom, sizeof(geom)))
return -EFAULT;
return 0;
/* set stream geometry */
case PDPIO_SETGEOM:
if (copy_from_user(&geom, argp, sizeof(geom)))
return -EFAULT;
if (geom.x > 2047 || geom.y > 2047)
return -EINVAL;
if (stream->ops.check_geom) {
error = stream->ops.check_geom(priv, &geom);
if (error)
return -EINVAL;
} else {
geom.w = 0;
geom.h = 0;
}
stream->geom = geom;
if (stream->ops.set_geom)
stream->ops.set_geom(priv);
pdpfb_configure_streams(priv);
pdpfb_update_margins(priv, stream);
if (copy_to_user(argp, &geom, sizeof(geom)))
return -EFAULT;
return 0;
/* get stream colour key mode */
case PDPIO_GETCKEYMODE:
if (!stream->ckey_en)
val = PDP_CKEYMODE_DISABLE;
else if (stream->ckey_src == PDP_STRXCTRL_CKEYSRC_PREV)
val = PDP_CKEYMODE_PREVIOUS;
else if (stream->ckey_src == PDP_STRXCTRL_CKEYSRC_CUR)
val = PDP_CKEYMODE_CURRENT;
if (copy_to_user(argp, &val, sizeof(val)))
return -EFAULT;
return 0;
/* set stream colour key mode */
case PDPIO_SETCKEYMODE:
if (copy_from_user(&val, argp, sizeof(val)))
return -EFAULT;
switch (val) {
case PDP_CKEYMODE_DISABLE:
stream->ckey_en = 0;
break;
case PDP_CKEYMODE_PREVIOUS:
stream->ckey_en = 1;
stream->ckey_src = PDP_STRXCTRL_CKEYSRC_PREV;
break;
case PDP_CKEYMODE_CURRENT:
stream->ckey_en = 1;
stream->ckey_src = PDP_STRXCTRL_CKEYSRC_CUR;
break;
default:
return -EINVAL;
}
pdpfb_configure_streams(priv);
return 0;
/* get stream colour key */
case PDPIO_GETCKEY:
if (copy_to_user(argp, &stream->ckey, sizeof(stream->ckey)))
return -EFAULT;
return 0;
/* set stream colour key */
case PDPIO_SETCKEY:
if (copy_from_user(&ckey, argp, sizeof(ckey)))
return -EFAULT;
ckey.mask = ckey.mask & 0xFFFFFF;
ckey.ckey = ckey.ckey & ckey.mask;
stream->ckey = ckey;
if (stream->ckey_en)
pdpfb_configure_streams(priv);
if (copy_to_user(argp, &stream->ckey, sizeof(stream->ckey)))
return -EFAULT;
return 0;
/* get stream blending mode */
case PDPIO_GETBLENDMODE:
val = stream->blend_mode;
if (copy_to_user(argp, &val, sizeof(val)))
return -EFAULT;
return 0;
/* set stream blending mode */
case PDPIO_SETBLENDMODE:
if (copy_from_user(&val, argp, sizeof(val)))
return -EFAULT;
if (val > PDP_BLENDMODE_PIXEL)
return -EINVAL;
stream->blend_mode = val;
pdpfb_configure_streams(priv);
return 0;
/* gset stream global alpha */
case PDPIO_GETGALPHA:
val = stream->global_alpha;
if (copy_to_user(argp, &val, sizeof(val)))
return -EFAULT;
return 0;
/* set stream global alpha */
case PDPIO_SETGALPHA:
if (copy_from_user(&val, argp, sizeof(val)))
return -EFAULT;
val &= 0xFF;
stream->global_alpha = val;
if (stream->blend_mode != PDP_BLENDMODE_NOALPHA)
pdpfb_configure_streams(priv);
if (copy_to_user(argp, &val, sizeof(val)))
return -EFAULT;
return 0;
/* set user provided memory */
case PDPIO_SETUSERMEM:
#ifdef CONFIG_FB_PDP_USERMEM
if (copy_from_user(&usermem, argp, sizeof(usermem)))
return -EFAULT;
if (usermem.flags & PDP_USERMEM_ALLPLANES) {
pdpfb_init_mempool(priv, PDPFB_MEMPOOL_USER,
usermem.phys, usermem.len);
#ifdef PDP_SHARED_BASE
pdata = pdpfb_get_platform_data(priv);
pdata->hwops.set_shared_base(usermem.phys);
#endif
ret = 0;
for (i = PDPFB_STREAM_NR - 1; i >= 0; i--) {
struct pdpfb_stream *stream = priv->streams[i];
if (!stream || !stream->probed)
continue;
pdpfb_str_videomem_free(stream);
}
for (i = 0; i < PDPFB_STREAM_NR; i++) {
struct pdpfb_stream *stream = priv->streams[i];
if (!stream || !stream->probed)
continue;
error = pdpfb_str_videomem_alloc(priv, stream);
if (error && !ret)
ret = error;
}
#ifdef PDP_SHARED_BASE
if (!ret)
return 0;
/*
* Allocation from usermem failed, the best we can do
* is to reset the usermem pool and attempt allocations
* again.
*/
for (i = PDPFB_STREAM_NR - 1; i >= 0; i--) {
struct pdpfb_stream *stream = priv->streams[i];
if (!stream || !stream->probed)
continue;
pdpfb_str_videomem_free(stream);
}
priv->pools[PDPFB_MEMPOOL_USER].size = 0;
priv->pools[PDPFB_MEMPOOL_USER].flags = 0;
priv->pools[PDPFB_MEMPOOL_USER].phys_base = 0;
priv->pools[PDPFB_MEMPOOL_USER].base = NULL;
pdata->hwops.set_shared_base(
priv->pools[PDPFB_MEMPOOL_MEM].phys_base);
for (i = 0; i < PDPFB_STREAM_NR; i++) {
struct pdpfb_stream *stream = priv->streams[i];
if (!stream || !stream->probed)
continue;
pdpfb_str_videomem_alloc(priv, stream);
}
#endif
return -ENOMEM;
}
#ifdef PDP_SHARED_BASE
/* with a shared base we can only affect all planes */
return -EINVAL;
#else
stream->videomem = (void *)usermem.phys;
stream->videomem_len = usermem.len;
stream->mem_pool = PDPFB_MEMPOOL_USERPLANE;
stream->info.screen_base = (char __force __iomem *)stream->videomem;
stream->info.fix.smem_start = usermem.phys;
stream->info.fix.smem_len = usermem.len;
return 0;
#endif
#else /* !CONFIG_FB_PDP_USERMEM */
return -EINVAL;
#endif /* !CONFIG_FB_PDP_USERMEM */
default:
return -ENOTTY;
}
return 0;
}
void pdpfb_str_videomem_free(struct pdpfb_stream *stream)
{
struct fb_info *info = &stream->info;
struct pdpfb_mem_pool *pool;
if (stream->mem_pool == PDPFB_MEMPOOL_KERNEL) {
kfree(stream->videomem);
} else if (stream->mem_pool < PDPFB_MEMPOOL_NR_POOLS) {
pool = &pdpfb_priv->pools[stream->mem_pool];
if ((unsigned long)pool->base + pool->allocated ==
(unsigned long)stream->videomem + stream->videomem_len) {
pool->allocated -= stream->videomem_len;
} else {
dev_warn(pdpfb_priv->device,
"free losing memory in pool '%s'",
pdpfb_mem_pool_names[stream->mem_pool]);
}
}
stream->videomem = NULL;
info->screen_base = NULL;
info->fix.smem_start = 0;
info->fix.smem_len = 0;
stream->mem_pool = PDPFB_MEMPOOL_NONE;
}
static int pdpfb_pool_alloc(struct pdpfb_mem_pool *pool,
struct pdpfb_stream *stream,
unsigned long len)
{
struct fb_info *info = &stream->info;
unsigned long offset;
unsigned long len_aligned = (len + PAGE_SIZE - 1) & PAGE_MASK;
offset = pool->allocated;
if (offset + len > pool->size)
return -ENOMEM;
pool->allocated += len_aligned;
stream->videomem = (void *)((unsigned long)pool->base + offset);
#ifdef PDP_SHARED_BASE
stream->videomem_offset = offset;
#endif
stream->videomem_len = len_aligned;
info->screen_base = (char __force __iomem *)stream->videomem;
info->fix.smem_start = pool->phys_base + offset;
info->fix.smem_len = len;
return 0;
}
int pdpfb_str_videomem_alloc(struct pdpfb_priv *priv,
struct pdpfb_stream *stream)
{
struct fb_info *info = &stream->info;
void *videomem = NULL;
unsigned long len;
#ifdef CONFIG_FB_PDP_VID
if (stream->mem_pool == PDPFB_MEMPOOL_GFXMEM)
len = gfx_videomem_len;
else
len = vid_videomem_len;
#else
len = gfx_videomem_len;
#endif
if (!len)
return 0;
/* Try user memory pool */
if (priv->pools[PDPFB_MEMPOOL_USER].base) {
if (!pdpfb_pool_alloc(&priv->pools[PDPFB_MEMPOOL_USER],
stream, len)) {
stream->mem_pool = PDPFB_MEMPOOL_USER;
return 0;
}
#ifdef PDP_SHARED_BASE
goto err_nomem;
#endif
}
/* Try default memory pool (depends on stream type) */
if (stream->mem_pool < PDPFB_MEMPOOL_NR_POOLS &&
priv->pools[stream->mem_pool].base) {
if (pdpfb_pool_alloc(&priv->pools[stream->mem_pool], stream,
priv->pools[stream->mem_pool].size))
goto err_nomem;
return 0;
}
/* Try combined memory pool */
if (priv->pools[PDPFB_MEMPOOL_MEM].base) {
if (pdpfb_pool_alloc(&priv->pools[PDPFB_MEMPOOL_MEM],
stream, len))
goto err_nomem;
stream->mem_pool = PDPFB_MEMPOOL_MEM;
return 0;
}
/* Finally try kernel allocated memory */
videomem = kzalloc(len, GFP_KERNEL);
if (!videomem)
goto err_nomem;
stream->mem_pool = PDPFB_MEMPOOL_KERNEL;
stream->videomem = videomem;
stream->videomem_len = len;
info->screen_base = (char __force __iomem *)videomem;
info->fix.smem_start = __pa(videomem);
info->fix.smem_len = stream->videomem_len;
return 0;
err_nomem:
dev_err(priv->device, "unable to allocate video memory\n");
return -ENOMEM;
}
static int pdpfb_probe_streams(struct pdpfb_priv *priv,
struct platform_device *pdev)
{
int i;
int error;
for (i = 0; i < PDPFB_STREAM_NR; ++i) {
struct pdpfb_stream *stream = priv->streams[i];
if (!stream)
continue;
stream->probed = 1;
error = stream->ops.probe(priv, pdev, &priv->mode);
if (error)
stream->probed = 0;
if (error == -ENOMEM)
dev_err(priv->device,
"unable to initialise fb %d, "
"not enough video memory\n",
i);
}
return 0;
}
static void pdpfb_remove_streams(struct pdpfb_priv *priv,
struct platform_device *pdev)
{
int i;
for (i = 0; i < PDPFB_STREAM_NR; ++i) {
struct pdpfb_stream *stream = priv->streams[i];
if (stream && stream->probed)
stream->ops.remove(priv, pdev);
}
}
static int pdpfb_stop(struct pdpfb_priv *priv)
{
pdpfb_configure(priv, PDPFB_POWER_FULLY_OFF);
return 0;
}
static void pdpfb_probe_caps(struct pdpfb_priv *priv)
{
u32 palette_bpc = ((PDP_REV >= 0x010001) ? 8 : 6);
priv->palette_fmt.r.length
= priv->palette_fmt.g.length
= priv->palette_fmt.b.length
= palette_bpc;
priv->palette_fmt.b.offset = 0;
priv->palette_fmt.g.offset = palette_bpc;
priv->palette_fmt.r.offset = palette_bpc*2;
}
#ifdef PDP_SHARED_BASE
static int pdpfb_init_mem(struct pdpfb_priv *priv,
struct pdp_info *pdata)
{
struct pdpfb_mem_pool *pool;
void *videomem;
unsigned long len;
if (unlikely(!pdata->hwops.set_shared_base)) {
dev_err(priv->device, "no set_shared_base in platform data\n");
return -EINVAL;
}
/* If fixed memory pool already exists, don't alloc from kernel mem. */
pool = &priv->pools[PDPFB_MEMPOOL_MEM];
if (pool->base) {
pdata->hwops.set_shared_base(pool->phys_base);
return 0;
}
len = ((gfx_videomem_len + PAGE_SIZE - 1) & PAGE_MASK)
#ifdef CONFIG_FB_PDP_VID
+ ((vid_videomem_len + PAGE_SIZE - 1) & PAGE_MASK)
#endif
;
videomem = kzalloc(len, GFP_KERNEL);
if (!videomem) {
dev_err(priv->device, "unable to allocate video memory\n");
return -ENOMEM;
}
pool->base = videomem;
pool->phys_base = __pa(videomem);
pool->size = len;
pool->flags = PDPFB_MEMPOOLF_KERNEL;
pdata->hwops.set_shared_base(pool->phys_base);
return 0;
}
#else
static inline int pdpfb_init_mem(struct pdpfb_priv *priv,
struct pdp_info *pdata)
{
return 0;
}
#endif
static void pdpfb_free_pools(struct pdpfb_priv *priv)
{
int i;
for (i = 0; i < PDPFB_MEMPOOL_NR_POOLS; ++i) {
if (!priv->pools[i].base)
continue;
if (priv->pools[i].flags & PDPFB_MEMPOOLF_KERNEL)
kfree(priv->pools[i].base);
else
iounmap((void __force __iomem *)priv->pools[i].base);
}
}
static int pdpfb_init_mempool(struct pdpfb_priv *priv, int type,
unsigned long phys, unsigned long len)
{
struct pdpfb_mem_pool *pool = &priv->pools[type];
/* First come first served */
if (unlikely(pool->base))
return -EBUSY;
pool->base = (void __force *)ioremap_cached(phys, len);
if (unlikely(!pool->base)) {
dev_err(priv->device, "cannot ioremap %s @0x%08lx:0x%lx\n",
pdpfb_mem_pool_names[type], phys, len);
return -ENOMEM;
}
dev_info(priv->device, "%s @0x%08lx:0x%lx (ioremapped to 0x%p)\n",
pdpfb_mem_pool_names[type],
phys, len, pool->base);
priv->pools[type].phys_base = phys;
priv->pools[type].size = len;
priv->pools[type].flags = 0;
/* Clear the memory */
memset(priv->pools[type].base, 0, priv->pools[type].size);
return 0;
}
static int pdpfb_probe_params(struct platform_device *pdev,
struct pdpfb_priv *priv)
{
if (videomem_base > 0 && videomem_len > 0)
pdpfb_init_mempool(priv, PDPFB_MEMPOOL_MEM,
videomem_base, videomem_len);
#ifndef PDP_SHARED_BASE
if (gfx_videomem_base > 0 && gfx_videomem_len > 0)
pdpfb_init_mempool(priv, PDPFB_MEMPOOL_GFXMEM,
gfx_videomem_base, gfx_videomem_len);
#ifdef CONFIG_FB_PDP_VID
if (vid_videomem_base > 0 && vid_videomem_len > 0)
pdpfb_init_mempool(priv, PDPFB_MEMPOOL_VIDMEM,
vid_videomem_base, vid_videomem_len);
#endif
#endif
return 0;
}
static int pdpfb_probe_state(struct pdpfb_priv *priv)
{
u32 sync_ctrl;
unsigned long pix_freq;
/* try and determine the current state */
sync_ctrl = pdpfb_read(priv, PDP_SYNCCTRL);
if (!GET_FIELD(sync_ctrl, PDP_SYNCCTRL_SYNCACTIVE))
goto done;
/* sync is active, what about the pixel clock? */
pix_freq = clk_get_rate(priv->pixel_clk);
if (!pix_freq)
goto done;
dev_dbg(priv->device, "initial pixel frequency = %lu Hz\n", pix_freq);
/* pixel clock active, try and extract the sync timings */
if (pdpfb_extract_sync(priv))
goto done;
dev_dbg(priv->device, "initial mode %ux%u sync=(%u,%u) margins=(%u,%u),(%u,%u)\n",
priv->mode.xres, priv->mode.yres,
priv->mode.vsync_len, priv->mode.hsync_len,
priv->mode.upper_margin, priv->mode.left_margin,
priv->mode.lower_margin, priv->mode.right_margin);
priv->mode.pixclock = KHZ2PICOS(pix_freq/1000);
pdpfb_pixel_clk_enable(priv);
if (GET_FIELD(sync_ctrl, PDP_SYNCCTRL_POWERDN))
priv->power = PDPFB_POWER_SYNC_ENABLED;
else
priv->power = PDPFB_POWER_BACKLIT;
done:
dev_dbg(priv->device, "initial power = %d\n", priv->power);
return 0;
}
static int pdpfb_probe_mems(struct platform_device *pdev,
struct pdpfb_priv *priv)
{
struct resource *res_mem;
int i;
int type;
unsigned long len;
for (i = 0; ; ++i) {
res_mem = platform_get_resource(pdev, IORESOURCE_MEM, i);
if (!res_mem)
break;
type = res_mem->flags & IORESOURCE_BITS;
len = res_mem->end - res_mem->start;
if (type < PDPFB_MEMPOOL_NR_POOLS) {
/* Videomem pool */
#ifdef PDP_SHARED_BASE
if (unlikely(type != PDPFB_MEMPOOL_MEM))
continue;
#endif
#ifndef CONFIG_FB_PDP_VID
if (unlikely(type == PDPFB_MEMPOOL_VIDMEM))
continue;
#endif
pdpfb_init_mempool(priv, type,
res_mem->start, len);
} else if (type == PDPFB_IORES_PDP) {
/* PDP register region */
if (priv->base) {
dev_warn(&pdev->dev,
"multiple pdp register ioresources\n");
continue;
}
priv->base = ioremap_nocache(res_mem->start, len);
if (!priv->base)
return -ENOMEM;
}
}
return 0;
}
static int pdpfb_probe(struct platform_device *pdev)
{
struct pdpfb_priv *priv;
struct pdp_info *pdata;
struct resource *res_irq;
int error;
if (!pdev->dev.platform_data) {
dev_err(&pdev->dev, "no platform data defined\n");
error = -EINVAL;
goto err0;
}
res_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (res_irq == NULL) {
dev_err(&pdev->dev, "cannot find IRQ resource\n");
error = -ENOENT;
goto err0;
}
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv) {
dev_err(&pdev->dev, "cannot allocate device data\n");
error = -ENOMEM;
goto err0;
}
spin_lock_init(&priv->upd.lock);
spin_lock_init(&priv->irq_lock);
pdpfb_lut_init(priv);
platform_set_drvdata(pdev, priv);
pdata = pdev->dev.platform_data;
priv->mode = pdata->lcd_cfg;
priv->power = PDPFB_POWER_FULLY_OFF;
priv->final_blank = -1;
priv->device = &pdev->dev;
error = pdpfb_probe_params(pdev, priv);
if (error)
goto err1;
error = pdpfb_probe_mems(pdev, priv);
if (error)
goto err1;
if (!priv->base) {
dev_err(&pdev->dev, "no pdp register ioresource\n");
error = -ENOMEM;
goto err1;
}
priv->pdp_clk = clk_get(priv->device, "pdp");
if (IS_ERR(priv->pdp_clk)) {
dev_err(&pdev->dev, "could not get pdp clock resource\n");
error = PTR_ERR(priv->pdp_clk);
goto err1;
}
clk_prepare(priv->pdp_clk);
priv->pixel_clk = clk_get(priv->device, "pixel");
if (IS_ERR(priv->pixel_clk)) {
dev_err(&pdev->dev, "could not get pixel clock resource\n");
error = PTR_ERR(priv->pixel_clk);
goto err2;
}
pdpfb_pdp_clk_enable(priv);
pdpfb_probe_state(priv);
error = pdpfb_init_mem(priv, pdata);
if (error)
goto err3;
priv->irq = res_irq->start;
error = request_irq(priv->irq, pdpfb_interrupt, 0, "pdp", priv);
if (error) {
dev_err(&pdev->dev, "cannot register IRQ %d (%d)\n",
priv->irq,
error);
error = -EIO;
goto err3;
}
pdpfb_probe_caps(priv);
pdpfb_priv = priv;
/* don't bother counting vsync interrupts with Pdump to reduce noise */
#ifndef CONFIG_FB_PDP_PDUMP
pdpfb_register_isr(pdpfb_vsync_isr, priv, PDPFB_IRQ_VEVENT0);
#endif
priv->streams[0] = pdpfb_gfx_get_stream();
priv->streams[0]->mode_master = pdata->lcd_size_cfg.dynamic_mode;
#ifdef CONFIG_FB_PDP_VID
priv->streams[1] = pdpfb_vid_get_stream();
#endif
/*
* We need to be able to work out the pixel clock that we can achieve
* prior to registering the framebuffer, else fbcon can try and set the
* mode and reset the frequency.
*/
pdpfb_update_refresh_rate(priv);
if (pdpfb_probe_streams(priv, pdev))
goto err4;
/*
* If sync is already enabled, update each stream's margins since it
* won't get done by pdpfb_configure
*/
if (priv->power <= PDPFB_POWER_SYNC_ENABLED)
pdpfb_update_all_margins(priv);
/* Turn everything on in the right sequence */
console_lock();
pdpfb_configure(priv, PDPFB_POWER_BACKLIT);
console_unlock();
_pdpfb_set_irqs(priv);
pdpfb_ident(priv);
return 0;
err4:
#ifndef CONFIG_FB_PDP_PDUMP
pdpfb_unregister_isr(pdpfb_vsync_isr, priv, PDPFB_IRQ_VEVENT0);
#endif
pdpfb_priv = NULL;
free_irq(priv->irq, priv);
err3:
pdpfb_pixel_clk_disable(priv);
pdpfb_pdp_clk_disable(priv);
clk_put(priv->pixel_clk);
err2:
clk_unprepare(priv->pdp_clk);
clk_put(priv->pdp_clk);
err1:
if (priv->base)
iounmap(priv->base);
pdpfb_free_pools(priv);
kfree(priv);
err0:
return error;
}
static int pdpfb_remove(struct platform_device *pdev)
{
struct pdpfb_priv *priv = platform_get_drvdata(pdev);
console_lock();
pdpfb_stop(priv);
console_unlock();
pdpfb_pdp_clk_disable(priv);
#ifndef CONFIG_FB_PDP_PDUMP
pdpfb_unregister_isr(pdpfb_vsync_isr, priv, PDPFB_IRQ_VEVENT0);
#endif
pdpfb_priv = NULL;
pdpfb_remove_streams(priv, pdev);
free_irq(priv->irq, priv);
clk_put(priv->pixel_clk);
clk_unprepare(priv->pdp_clk);
clk_put(priv->pdp_clk);
if (priv->base)
iounmap(priv->base);
pdpfb_free_pools(priv);
kfree(priv);
return 0;
}
static void pdpfb_shutdown(struct platform_device *pdev)
{
struct pdpfb_priv *priv = platform_get_drvdata(pdev);
console_lock();
pdpfb_stop(priv);
console_unlock();
}
#ifdef CONFIG_PM
static int pdpfb_suspend(struct platform_device *pdev, pm_message_t state)
{
struct pdpfb_priv *priv = platform_get_drvdata(pdev);
int i;
console_lock();
for (i = 0; i < PDPFB_STREAM_NR; ++i) {
struct pdpfb_stream *stream = priv->streams[i];
if (!stream || !stream->probed)
continue;
fb_set_suspend(&stream->info, 1);
}
pdpfb_stop(priv);
pdpfb_pdp_clk_disable(priv);
console_unlock();
return 0;
}
static int pdpfb_resume(struct platform_device *pdev)
{
struct pdpfb_priv *priv = platform_get_drvdata(pdev);
#ifdef PDP_SHARED_BASE
struct pdp_info *pdata = priv->device->platform_data;
#endif
int i;
console_lock();
pdpfb_pdp_clk_enable(priv);
#ifdef PDP_SHARED_BASE
/* restore shared base pointer */
if (pdata->hwops.set_shared_base) {
struct pdpfb_mem_pool *pool;
if (priv->pools[PDPFB_MEMPOOL_USER].base)
pool = &priv->pools[PDPFB_MEMPOOL_USER];
else
pool = &priv->pools[PDPFB_MEMPOOL_MEM];
pdata->hwops.set_shared_base(pool->phys_base);
}
#endif
pdpfb_configure(priv, PDPFB_POWER_BACKLIT);
for (i = 0; i < PDPFB_STREAM_NR; ++i) {
struct pdpfb_stream *stream = priv->streams[i];
if (!stream || !stream->probed)
continue;
fb_set_suspend(&stream->info, 0);
}
console_unlock();
return 0;
}
#else
#define pdpfb_suspend NULL
#define pdpfb_resume NULL
#endif /* CONFIG_PM */
static struct platform_driver pdpfb_driver = {
.driver = {
.name = "pdpfb",
.owner = THIS_MODULE,
},
.probe = pdpfb_probe,
.remove = pdpfb_remove,
.shutdown = pdpfb_shutdown,
.suspend = pdpfb_suspend,
.resume = pdpfb_resume,
};
static int pdpfb_init(void)
{
return platform_driver_register(&pdpfb_driver);
}
static void pdpfb_exit(void)
{
platform_driver_unregister(&pdpfb_driver);
}
module_init(pdpfb_init);
module_exit(pdpfb_exit);
MODULE_DESCRIPTION("PDP Framebuffer driver");
MODULE_AUTHOR("Will Newton <will.newton@imgtec.com>");
MODULE_AUTHOR("James Hogan <james.hogan@imgtec.com>");
MODULE_LICENSE("GPL v2");