/*
* SuperH Mobile LCDC Framebuffer
*
* Copyright (c) 2008 Magnus Damm
*
* 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/delay.h>
#include <linux/mm.h>
#include <linux/fb.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <video/sh_mobile_lcdc.h>
#include <asm/atomic.h>
#define PALETTE_NR 16
struct sh_mobile_lcdc_priv;
struct sh_mobile_lcdc_chan {
struct sh_mobile_lcdc_priv *lcdc;
unsigned long *reg_offs;
unsigned long ldmt1r_value;
unsigned long enabled; /* ME and SE in LDCNT2R */
struct sh_mobile_lcdc_chan_cfg cfg;
u32 pseudo_palette[PALETTE_NR];
struct fb_info info;
dma_addr_t dma_handle;
struct fb_deferred_io defio;
unsigned long frame_end;
wait_queue_head_t frame_end_wait;
};
struct sh_mobile_lcdc_priv {
void __iomem *base;
int irq;
#ifdef CONFIG_HAVE_CLK
atomic_t clk_usecnt;
struct clk *dot_clk;
struct clk *clk;
#endif
unsigned long lddckr;
struct sh_mobile_lcdc_chan ch[2];
};
/* shared registers */
#define _LDDCKR 0x410
#define _LDDCKSTPR 0x414
#define _LDINTR 0x468
#define _LDSR 0x46c
#define _LDCNT1R 0x470
#define _LDCNT2R 0x474
#define _LDDDSR 0x47c
#define _LDDWD0R 0x800
#define _LDDRDR 0x840
#define _LDDWAR 0x900
#define _LDDRAR 0x904
/* per-channel registers */
enum { LDDCKPAT1R, LDDCKPAT2R, LDMT1R, LDMT2R, LDMT3R, LDDFR, LDSM1R,
LDSM2R, LDSA1R, LDMLSR, LDHCNR, LDHSYNR, LDVLNR, LDVSYNR, LDPMR };
static unsigned long lcdc_offs_mainlcd[] = {
[LDDCKPAT1R] = 0x400,
[LDDCKPAT2R] = 0x404,
[LDMT1R] = 0x418,
[LDMT2R] = 0x41c,
[LDMT3R] = 0x420,
[LDDFR] = 0x424,
[LDSM1R] = 0x428,
[LDSM2R] = 0x42c,
[LDSA1R] = 0x430,
[LDMLSR] = 0x438,
[LDHCNR] = 0x448,
[LDHSYNR] = 0x44c,
[LDVLNR] = 0x450,
[LDVSYNR] = 0x454,
[LDPMR] = 0x460,
};
static unsigned long lcdc_offs_sublcd[] = {
[LDDCKPAT1R] = 0x408,
[LDDCKPAT2R] = 0x40c,
[LDMT1R] = 0x600,
[LDMT2R] = 0x604,
[LDMT3R] = 0x608,
[LDDFR] = 0x60c,
[LDSM1R] = 0x610,
[LDSM2R] = 0x614,
[LDSA1R] = 0x618,
[LDMLSR] = 0x620,
[LDHCNR] = 0x624,
[LDHSYNR] = 0x628,
[LDVLNR] = 0x62c,
[LDVSYNR] = 0x630,
[LDPMR] = 0x63c,
};
#define START_LCDC 0x00000001
#define LCDC_RESET 0x00000100
#define DISPLAY_BEU 0x00000008
#define LCDC_ENABLE 0x00000001
#define LDINTR_FE 0x00000400
#define LDINTR_FS 0x00000004
static void lcdc_write_chan(struct sh_mobile_lcdc_chan *chan,
int reg_nr, unsigned long data)
{
iowrite32(data, chan->lcdc->base + chan->reg_offs[reg_nr]);
}
static unsigned long lcdc_read_chan(struct sh_mobile_lcdc_chan *chan,
int reg_nr)
{
return ioread32(chan->lcdc->base + chan->reg_offs[reg_nr]);
}
static void lcdc_write(struct sh_mobile_lcdc_priv *priv,
unsigned long reg_offs, unsigned long data)
{
iowrite32(data, priv->base + reg_offs);
}
static unsigned long lcdc_read(struct sh_mobile_lcdc_priv *priv,
unsigned long reg_offs)
{
return ioread32(priv->base + reg_offs);
}
static void lcdc_wait_bit(struct sh_mobile_lcdc_priv *priv,
unsigned long reg_offs,
unsigned long mask, unsigned long until)
{
while ((lcdc_read(priv, reg_offs) & mask) != until)
cpu_relax();
}
static int lcdc_chan_is_sublcd(struct sh_mobile_lcdc_chan *chan)
{
return chan->cfg.chan == LCDC_CHAN_SUBLCD;
}
static void lcdc_sys_write_index(void *handle, unsigned long data)
{
struct sh_mobile_lcdc_chan *ch = handle;
lcdc_write(ch->lcdc, _LDDWD0R, data | 0x10000000);
lcdc_wait_bit(ch->lcdc, _LDSR, 2, 0);
lcdc_write(ch->lcdc, _LDDWAR, 1 | (lcdc_chan_is_sublcd(ch) ? 2 : 0));
}
static void lcdc_sys_write_data(void *handle, unsigned long data)
{
struct sh_mobile_lcdc_chan