📄 sh_mobile_lcdcfb.c
字号:
/* * 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 16struct 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;};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 0x00000004static 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 *ch = handle; lcdc_write(ch->lcdc, _LDDWD0R, data | 0x11000000); lcdc_wait_bit(ch->lcdc, _LDSR, 2, 0); lcdc_write(ch->lcdc, _LDDWAR, 1 | (lcdc_chan_is_sublcd(ch) ? 2 : 0));}static unsigned long lcdc_sys_read_data(void *handle){ struct sh_mobile_lcdc_chan *ch = handle; lcdc_write(ch->lcdc, _LDDRDR, 0x01000000); lcdc_wait_bit(ch->lcdc, _LDSR, 2, 0); lcdc_write(ch->lcdc, _LDDRAR, 1 | (lcdc_chan_is_sublcd(ch) ? 2 : 0)); udelay(1); return lcdc_read(ch->lcdc, _LDDRDR) & 0xffff;}struct sh_mobile_lcdc_sys_bus_ops sh_mobile_lcdc_sys_bus_ops = { lcdc_sys_write_index, lcdc_sys_write_data, lcdc_sys_read_data,};#ifdef CONFIG_HAVE_CLKstatic void sh_mobile_lcdc_clk_on(struct sh_mobile_lcdc_priv *priv){ if (atomic_inc_and_test(&priv->clk_usecnt)) { clk_enable(priv->clk); if (priv->dot_clk) clk_enable(priv->dot_clk); }}static void sh_mobile_lcdc_clk_off(struct sh_mobile_lcdc_priv *priv){ if (atomic_sub_return(1, &priv->clk_usecnt) == -1) { if (priv->dot_clk) clk_disable(priv->dot_clk); clk_disable(priv->clk); }}#elsestatic void sh_mobile_lcdc_clk_on(struct sh_mobile_lcdc_priv *priv) {}static void sh_mobile_lcdc_clk_off(struct sh_mobile_lcdc_priv *priv) {}#endifstatic void sh_mobile_lcdc_deferred_io(struct fb_info *info, struct list_head *pagelist){ struct sh_mobile_lcdc_chan *ch = info->par; /* enable clocks before accessing hardware */ sh_mobile_lcdc_clk_on(ch->lcdc); /* trigger panel update */ lcdc_write_chan(ch, LDSM2R, 1);}static void sh_mobile_lcdc_deferred_io_touch(struct fb_info *info){ struct fb_deferred_io *fbdefio = info->fbdefio; if (fbdefio) schedule_delayed_work(&info->deferred_work, fbdefio->delay);}static irqreturn_t sh_mobile_lcdc_irq(int irq, void *data){ struct sh_mobile_lcdc_priv *priv = data; unsigned long tmp; /* acknowledge interrupt */ tmp = lcdc_read(priv, _LDINTR); tmp &= 0xffffff00; /* mask in high 24 bits */ tmp |= 0x000000ff ^ LDINTR_FS; /* status in low 8 */ lcdc_write(priv, _LDINTR, tmp); /* disable clocks */ sh_mobile_lcdc_clk_off(priv); return IRQ_HANDLED;}static void sh_mobile_lcdc_start_stop(struct sh_mobile_lcdc_priv *priv, int start){ unsigned long tmp = lcdc_read(priv, _LDCNT2R); int k; /* start or stop the lcdc */ if (start) lcdc_write(priv, _LDCNT2R, tmp | START_LCDC); else lcdc_write(priv, _LDCNT2R, tmp & ~START_LCDC); /* wait until power is applied/stopped on all channels */ for (k = 0; k < ARRAY_SIZE(priv->ch); k++) if (lcdc_read(priv, _LDCNT2R) & priv->ch[k].enabled) while (1) { tmp = lcdc_read_chan(&priv->ch[k], LDPMR) & 3; if (start && tmp == 3) break; if (!start && tmp == 0) break; cpu_relax(); } if (!start) lcdc_write(priv, _LDDCKSTPR, 1); /* stop dotclock */}static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv){ struct sh_mobile_lcdc_chan *ch; struct fb_videomode *lcd_cfg; struct sh_mobile_lcdc_board_cfg *board_cfg; unsigned long tmp; int k, m; int ret = 0; /* enable clocks before accessing the hardware */ for (k = 0; k < ARRAY_SIZE(priv->ch); k++) if (priv->ch[k].enabled) sh_mobile_lcdc_clk_on(priv); /* reset */ lcdc_write(priv, _LDCNT2R, lcdc_read(priv, _LDCNT2R) | LCDC_RESET); lcdc_wait_bit(priv, _LDCNT2R, LCDC_RESET, 0); /* enable LCDC channels */ tmp = lcdc_read(priv, _LDCNT2R); tmp |= priv->ch[0].enabled; tmp |= priv->ch[1].enabled; lcdc_write(priv, _LDCNT2R, tmp); /* read data from external memory, avoid using the BEU for now */ lcdc_write(priv, _LDCNT2R, lcdc_read(priv, _LDCNT2R) & ~DISPLAY_BEU); /* stop the lcdc first */ sh_mobile_lcdc_start_stop(priv, 0); /* configure clocks */ tmp = priv->lddckr; for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { ch = &priv->ch[k]; if (!priv->ch[k].enabled) continue; m = ch->cfg.clock_divider; if (!m) continue; if (m == 1) m = 1 << 6; tmp |= m << (lcdc_chan_is_sublcd(ch) ? 8 : 0); lcdc_write_chan(ch, LDDCKPAT1R, 0x00000000); lcdc_write_chan(ch, LDDCKPAT2R, (1 << (m/2)) - 1); } lcdc_write(priv, _LDDCKR, tmp); /* start dotclock again */ lcdc_write(priv, _LDDCKSTPR, 0); lcdc_wait_bit(priv, _LDDCKSTPR, ~0, 0); /* interrupts are disabled to begin with */ lcdc_write(priv, _LDINTR, 0); for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { ch = &priv->ch[k]; lcd_cfg = &ch->cfg.lcd_cfg; if (!ch->enabled) continue; tmp = ch->ldmt1r_value; tmp |= (lcd_cfg->sync & FB_SYNC_VERT_HIGH_ACT) ? 0 : 1 << 28; tmp |= (lcd_cfg->sync & FB_SYNC_HOR_HIGH_ACT) ? 0 : 1 << 27; tmp |= (ch->cfg.flags & LCDC_FLAGS_DWPOL) ? 1 << 26 : 0; tmp |= (ch->cfg.flags & LCDC_FLAGS_DIPOL) ? 1 << 25 : 0; tmp |= (ch->cfg.flags & LCDC_FLAGS_DAPOL) ? 1 << 24 : 0; tmp |= (ch->cfg.flags & LCDC_FLAGS_HSCNT) ? 1 << 17 : 0; tmp |= (ch->cfg.flags & LCDC_FLAGS_DWCNT) ? 1 << 16 : 0; lcdc_write_chan(ch, LDMT1R, tmp); /* setup SYS bus */ lcdc_write_chan(ch, LDMT2R, ch->cfg.sys_bus_cfg.ldmt2r); lcdc_write_chan(ch, LDMT3R, ch->cfg.sys_bus_cfg.ldmt3r); /* horizontal configuration */ tmp = lcd_cfg->xres + lcd_cfg->hsync_len; tmp += lcd_cfg->left_margin; tmp += lcd_cfg->right_margin; tmp /= 8; /* HTCN */ tmp |= (lcd_cfg->xres / 8) << 16; /* HDCN */ lcdc_write_chan(ch, LDHCNR, tmp); tmp = lcd_cfg->xres; tmp += lcd_cfg->right_margin; tmp /= 8; /* HSYNP */ tmp |= (lcd_cfg->hsync_len / 8) << 16; /* HSYNW */ lcdc_write_chan(ch, LDHSYNR, tmp); /* power supply */ lcdc_write_chan(ch, LDPMR, 0); /* vertical configuration */ tmp = lcd_cfg->yres + lcd_cfg->vsync_len; tmp += lcd_cfg->upper_margin; tmp += lcd_cfg->lower_margin; /* VTLN */ tmp |= lcd_cfg->yres << 16; /* VDLN */ lcdc_write_chan(ch, LDVLNR, tmp); tmp = lcd_cfg->yres; tmp += lcd_cfg->lower_margin; /* VSYNP */ tmp |= lcd_cfg->vsync_len << 16; /* VSYNW */ lcdc_write_chan(ch, LDVSYNR, tmp); board_cfg = &ch->cfg.board_cfg; if (board_cfg->setup_sys) ret = board_cfg->setup_sys(board_cfg->board_data, ch, &sh_mobile_lcdc_sys_bus_ops); if (ret) return ret; } /* word and long word swap */ lcdc_write(priv, _LDDDSR, lcdc_read(priv, _LDDDSR) | 6); for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { ch = &priv->ch[k]; if (!priv->ch[k].enabled) continue; /* set bpp format in PKF[4:0] */ tmp = lcdc_read_chan(ch, LDDFR); tmp &= ~(0x0001001f); tmp |= (priv->ch[k].info.var.bits_per_pixel == 16) ? 3 : 0; lcdc_write_chan(ch, LDDFR, tmp); /* point out our frame buffer */ lcdc_write_chan(ch, LDSA1R, ch->info.fix.smem_start); /* set line size */ lcdc_write_chan(ch, LDMLSR, ch->info.fix.line_length); /* setup deferred io if SYS bus */ tmp = ch->cfg.sys_bus_cfg.deferred_io_msec; if (ch->ldmt1r_value & (1 << 12) && tmp) { ch->defio.deferred_io = sh_mobile_lcdc_deferred_io; ch->defio.delay = msecs_to_jiffies(tmp); ch->info.fbdefio = &ch->defio; fb_deferred_io_init(&ch->info); /* one-shot mode */ lcdc_write_chan(ch, LDSM1R, 1); /* enable "Frame End Interrupt Enable" bit */ lcdc_write(priv, _LDINTR, LDINTR_FE); } else { /* continuous read mode */ lcdc_write_chan(ch, LDSM1R, 0); } } /* display output */ lcdc_write(priv, _LDCNT1R, LCDC_ENABLE); /* start the lcdc */ sh_mobile_lcdc_start_stop(priv, 1); /* tell the board code to enable the panel */ for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { ch = &priv->ch[k]; board_cfg = &ch->cfg.board_cfg; if (board_cfg->display_on) board_cfg->display_on(board_cfg->board_data); } return 0;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -