diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2009-09-18 12:43:09 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2009-09-18 12:43:09 -0400 |
commit | 515b696b282f856c3ad1679ccd658120faa387d0 (patch) | |
tree | d9d7c1185c396617f128ca23463062308d11393b /drivers/video | |
parent | fa877c71e2136bd682b45022c96d5e073ced9f58 (diff) | |
parent | 064a16dc41be879d12bd5de5d2f9d38d890e0ee7 (diff) |
Merge git://git.kernel.org/pub/scm/linux/kernel/git/lethal/sh-2.6
* git://git.kernel.org/pub/scm/linux/kernel/git/lethal/sh-2.6: (262 commits)
sh: mach-ecovec24: Add user debug switch support
sh: Kill off unused se_skipped in alignment trap notification code.
sh: Wire up HAVE_SYSCALL_TRACEPOINTS.
video: sh_mobile_lcdcfb: use both register sets for display panning
video: sh_mobile_lcdcfb: implement display panning
sh: Fix up sh7705 flush_dcache_page() build.
sh: kfr2r09: document the PLL/FLL <-> RF relationship.
sh: mach-ecovec24: need asm/clock.h.
sh: mach-ecovec24: deassert usb irq on boot.
sh: Add KEYSC support for EcoVec24
sh: add kycr2_delay for sh_keysc
sh: cpufreq: Include CPU id in info messages.
sh: multi-evt support for SH-X3 proto CPU.
sh: clkfwk: remove bogus set_bus_parent() from SH7709.
sh: Fix the indication point of the liquid crystal of AP-325RXA(AP3300)
sh: Add EcoVec24 romImage defconfig
sh: USB disable process is needed if romImage boot for EcoVec24
sh: EcoVec24: add HIZA setting for LED
sh: EcoVec24: write MAC address in boot
sh: Add romImage support for EcoVec24
...
Diffstat (limited to 'drivers/video')
-rw-r--r-- | drivers/video/Kconfig | 2 | ||||
-rw-r--r-- | drivers/video/sh_mobile_lcdcfb.c | 292 |
2 files changed, 222 insertions, 72 deletions
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index cef3e1d9b92e..11af4cb8924e 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig | |||
@@ -1869,7 +1869,7 @@ config FB_W100 | |||
1869 | 1869 | ||
1870 | config FB_SH_MOBILE_LCDC | 1870 | config FB_SH_MOBILE_LCDC |
1871 | tristate "SuperH Mobile LCDC framebuffer support" | 1871 | tristate "SuperH Mobile LCDC framebuffer support" |
1872 | depends on FB && SUPERH | 1872 | depends on FB && SUPERH && HAVE_CLK |
1873 | select FB_SYS_FILLRECT | 1873 | select FB_SYS_FILLRECT |
1874 | select FB_SYS_COPYAREA | 1874 | select FB_SYS_COPYAREA |
1875 | select FB_SYS_IMAGEBLIT | 1875 | select FB_SYS_IMAGEBLIT |
diff --git a/drivers/video/sh_mobile_lcdcfb.c b/drivers/video/sh_mobile_lcdcfb.c index 07f22b625632..3ad5157f9899 100644 --- a/drivers/video/sh_mobile_lcdcfb.c +++ b/drivers/video/sh_mobile_lcdcfb.c | |||
@@ -14,6 +14,7 @@ | |||
14 | #include <linux/mm.h> | 14 | #include <linux/mm.h> |
15 | #include <linux/fb.h> | 15 | #include <linux/fb.h> |
16 | #include <linux/clk.h> | 16 | #include <linux/clk.h> |
17 | #include <linux/pm_runtime.h> | ||
17 | #include <linux/platform_device.h> | 18 | #include <linux/platform_device.h> |
18 | #include <linux/dma-mapping.h> | 19 | #include <linux/dma-mapping.h> |
19 | #include <linux/interrupt.h> | 20 | #include <linux/interrupt.h> |
@@ -22,35 +23,8 @@ | |||
22 | #include <asm/atomic.h> | 23 | #include <asm/atomic.h> |
23 | 24 | ||
24 | #define PALETTE_NR 16 | 25 | #define PALETTE_NR 16 |
25 | 26 | #define SIDE_B_OFFSET 0x1000 | |
26 | struct sh_mobile_lcdc_priv; | 27 | #define MIRROR_OFFSET 0x2000 |
27 | struct sh_mobile_lcdc_chan { | ||
28 | struct sh_mobile_lcdc_priv *lcdc; | ||
29 | unsigned long *reg_offs; | ||
30 | unsigned long ldmt1r_value; | ||
31 | unsigned long enabled; /* ME and SE in LDCNT2R */ | ||
32 | struct sh_mobile_lcdc_chan_cfg cfg; | ||
33 | u32 pseudo_palette[PALETTE_NR]; | ||
34 | struct fb_info *info; | ||
35 | dma_addr_t dma_handle; | ||
36 | struct fb_deferred_io defio; | ||
37 | struct scatterlist *sglist; | ||
38 | unsigned long frame_end; | ||
39 | wait_queue_head_t frame_end_wait; | ||
40 | }; | ||
41 | |||
42 | struct sh_mobile_lcdc_priv { | ||
43 | void __iomem *base; | ||
44 | int irq; | ||
45 | #ifdef CONFIG_HAVE_CLK | ||
46 | atomic_t clk_usecnt; | ||
47 | struct clk *dot_clk; | ||
48 | struct clk *clk; | ||
49 | #endif | ||
50 | unsigned long lddckr; | ||
51 | struct sh_mobile_lcdc_chan ch[2]; | ||
52 | int started; | ||
53 | }; | ||
54 | 28 | ||
55 | /* shared registers */ | 29 | /* shared registers */ |
56 | #define _LDDCKR 0x410 | 30 | #define _LDDCKR 0x410 |
@@ -59,17 +33,30 @@ struct sh_mobile_lcdc_priv { | |||
59 | #define _LDSR 0x46c | 33 | #define _LDSR 0x46c |
60 | #define _LDCNT1R 0x470 | 34 | #define _LDCNT1R 0x470 |
61 | #define _LDCNT2R 0x474 | 35 | #define _LDCNT2R 0x474 |
36 | #define _LDRCNTR 0x478 | ||
62 | #define _LDDDSR 0x47c | 37 | #define _LDDDSR 0x47c |
63 | #define _LDDWD0R 0x800 | 38 | #define _LDDWD0R 0x800 |
64 | #define _LDDRDR 0x840 | 39 | #define _LDDRDR 0x840 |
65 | #define _LDDWAR 0x900 | 40 | #define _LDDWAR 0x900 |
66 | #define _LDDRAR 0x904 | 41 | #define _LDDRAR 0x904 |
67 | 42 | ||
43 | /* shared registers and their order for context save/restore */ | ||
44 | static int lcdc_shared_regs[] = { | ||
45 | _LDDCKR, | ||
46 | _LDDCKSTPR, | ||
47 | _LDINTR, | ||
48 | _LDDDSR, | ||
49 | _LDCNT1R, | ||
50 | _LDCNT2R, | ||
51 | }; | ||
52 | #define NR_SHARED_REGS ARRAY_SIZE(lcdc_shared_regs) | ||
53 | |||
68 | /* per-channel registers */ | 54 | /* per-channel registers */ |
69 | enum { LDDCKPAT1R, LDDCKPAT2R, LDMT1R, LDMT2R, LDMT3R, LDDFR, LDSM1R, | 55 | enum { LDDCKPAT1R, LDDCKPAT2R, LDMT1R, LDMT2R, LDMT3R, LDDFR, LDSM1R, |
70 | LDSM2R, LDSA1R, LDMLSR, LDHCNR, LDHSYNR, LDVLNR, LDVSYNR, LDPMR }; | 56 | LDSM2R, LDSA1R, LDMLSR, LDHCNR, LDHSYNR, LDVLNR, LDVSYNR, LDPMR, |
57 | NR_CH_REGS }; | ||
71 | 58 | ||
72 | static unsigned long lcdc_offs_mainlcd[] = { | 59 | static unsigned long lcdc_offs_mainlcd[NR_CH_REGS] = { |
73 | [LDDCKPAT1R] = 0x400, | 60 | [LDDCKPAT1R] = 0x400, |
74 | [LDDCKPAT2R] = 0x404, | 61 | [LDDCKPAT2R] = 0x404, |
75 | [LDMT1R] = 0x418, | 62 | [LDMT1R] = 0x418, |
@@ -87,7 +74,7 @@ static unsigned long lcdc_offs_mainlcd[] = { | |||
87 | [LDPMR] = 0x460, | 74 | [LDPMR] = 0x460, |
88 | }; | 75 | }; |
89 | 76 | ||
90 | static unsigned long lcdc_offs_sublcd[] = { | 77 | static unsigned long lcdc_offs_sublcd[NR_CH_REGS] = { |
91 | [LDDCKPAT1R] = 0x408, | 78 | [LDDCKPAT1R] = 0x408, |
92 | [LDDCKPAT2R] = 0x40c, | 79 | [LDDCKPAT2R] = 0x40c, |
93 | [LDMT1R] = 0x600, | 80 | [LDMT1R] = 0x600, |
@@ -110,12 +97,80 @@ static unsigned long lcdc_offs_sublcd[] = { | |||
110 | #define DISPLAY_BEU 0x00000008 | 97 | #define DISPLAY_BEU 0x00000008 |
111 | #define LCDC_ENABLE 0x00000001 | 98 | #define LCDC_ENABLE 0x00000001 |
112 | #define LDINTR_FE 0x00000400 | 99 | #define LDINTR_FE 0x00000400 |
100 | #define LDINTR_VSE 0x00000200 | ||
101 | #define LDINTR_VEE 0x00000100 | ||
113 | #define LDINTR_FS 0x00000004 | 102 | #define LDINTR_FS 0x00000004 |
103 | #define LDINTR_VSS 0x00000002 | ||
104 | #define LDINTR_VES 0x00000001 | ||
105 | #define LDRCNTR_SRS 0x00020000 | ||
106 | #define LDRCNTR_SRC 0x00010000 | ||
107 | #define LDRCNTR_MRS 0x00000002 | ||
108 | #define LDRCNTR_MRC 0x00000001 | ||
109 | |||
110 | struct sh_mobile_lcdc_priv; | ||
111 | struct sh_mobile_lcdc_chan { | ||
112 | struct sh_mobile_lcdc_priv *lcdc; | ||
113 | unsigned long *reg_offs; | ||
114 | unsigned long ldmt1r_value; | ||
115 | unsigned long enabled; /* ME and SE in LDCNT2R */ | ||
116 | struct sh_mobile_lcdc_chan_cfg cfg; | ||
117 | u32 pseudo_palette[PALETTE_NR]; | ||
118 | unsigned long saved_ch_regs[NR_CH_REGS]; | ||
119 | struct fb_info *info; | ||
120 | dma_addr_t dma_handle; | ||
121 | struct fb_deferred_io defio; | ||
122 | struct scatterlist *sglist; | ||
123 | unsigned long frame_end; | ||
124 | unsigned long pan_offset; | ||
125 | unsigned long new_pan_offset; | ||
126 | wait_queue_head_t frame_end_wait; | ||
127 | }; | ||
128 | |||
129 | struct sh_mobile_lcdc_priv { | ||
130 | void __iomem *base; | ||
131 | int irq; | ||
132 | atomic_t hw_usecnt; | ||
133 | struct device *dev; | ||
134 | struct clk *dot_clk; | ||
135 | unsigned long lddckr; | ||
136 | struct sh_mobile_lcdc_chan ch[2]; | ||
137 | unsigned long saved_shared_regs[NR_SHARED_REGS]; | ||
138 | int started; | ||
139 | }; | ||
140 | |||
141 | static bool banked(int reg_nr) | ||
142 | { | ||
143 | switch (reg_nr) { | ||
144 | case LDMT1R: | ||
145 | case LDMT2R: | ||
146 | case LDMT3R: | ||
147 | case LDDFR: | ||
148 | case LDSM1R: | ||
149 | case LDSA1R: | ||
150 | case LDMLSR: | ||
151 | case LDHCNR: | ||
152 | case LDHSYNR: | ||
153 | case LDVLNR: | ||
154 | case LDVSYNR: | ||
155 | return true; | ||
156 | } | ||
157 | return false; | ||
158 | } | ||
114 | 159 | ||
115 | static void lcdc_write_chan(struct sh_mobile_lcdc_chan *chan, | 160 | static void lcdc_write_chan(struct sh_mobile_lcdc_chan *chan, |
116 | int reg_nr, unsigned long data) | 161 | int reg_nr, unsigned long data) |
117 | { | 162 | { |
118 | iowrite32(data, chan->lcdc->base + chan->reg_offs[reg_nr]); | 163 | iowrite32(data, chan->lcdc->base + chan->reg_offs[reg_nr]); |
164 | if (banked(reg_nr)) | ||
165 | iowrite32(data, chan->lcdc->base + chan->reg_offs[reg_nr] + | ||
166 | SIDE_B_OFFSET); | ||
167 | } | ||
168 | |||
169 | static void lcdc_write_chan_mirror(struct sh_mobile_lcdc_chan *chan, | ||
170 | int reg_nr, unsigned long data) | ||
171 | { | ||
172 | iowrite32(data, chan->lcdc->base + chan->reg_offs[reg_nr] + | ||
173 | MIRROR_OFFSET); | ||
119 | } | 174 | } |
120 | 175 | ||
121 | static unsigned long lcdc_read_chan(struct sh_mobile_lcdc_chan *chan, | 176 | static unsigned long lcdc_read_chan(struct sh_mobile_lcdc_chan *chan, |
@@ -156,6 +211,7 @@ static void lcdc_sys_write_index(void *handle, unsigned long data) | |||
156 | lcdc_write(ch->lcdc, _LDDWD0R, data | 0x10000000); | 211 | lcdc_write(ch->lcdc, _LDDWD0R, data | 0x10000000); |
157 | lcdc_wait_bit(ch->lcdc, _LDSR, 2, 0); | 212 | lcdc_wait_bit(ch->lcdc, _LDSR, 2, 0); |
158 | lcdc_write(ch->lcdc, _LDDWAR, 1 | (lcdc_chan_is_sublcd(ch) ? 2 : 0)); | 213 | lcdc_write(ch->lcdc, _LDDWAR, 1 | (lcdc_chan_is_sublcd(ch) ? 2 : 0)); |
214 | lcdc_wait_bit(ch->lcdc, _LDSR, 2, 0); | ||
159 | } | 215 | } |
160 | 216 | ||
161 | static void lcdc_sys_write_data(void *handle, unsigned long data) | 217 | static void lcdc_sys_write_data(void *handle, unsigned long data) |
@@ -165,6 +221,7 @@ static void lcdc_sys_write_data(void *handle, unsigned long data) | |||
165 | lcdc_write(ch->lcdc, _LDDWD0R, data | 0x11000000); | 221 | lcdc_write(ch->lcdc, _LDDWD0R, data | 0x11000000); |
166 | lcdc_wait_bit(ch->lcdc, _LDSR, 2, 0); | 222 | lcdc_wait_bit(ch->lcdc, _LDSR, 2, 0); |
167 | lcdc_write(ch->lcdc, _LDDWAR, 1 | (lcdc_chan_is_sublcd(ch) ? 2 : 0)); | 223 | lcdc_write(ch->lcdc, _LDDWAR, 1 | (lcdc_chan_is_sublcd(ch) ? 2 : 0)); |
224 | lcdc_wait_bit(ch->lcdc, _LDSR, 2, 0); | ||
168 | } | 225 | } |
169 | 226 | ||
170 | static unsigned long lcdc_sys_read_data(void *handle) | 227 | static unsigned long lcdc_sys_read_data(void *handle) |
@@ -175,8 +232,9 @@ static unsigned long lcdc_sys_read_data(void *handle) | |||
175 | lcdc_wait_bit(ch->lcdc, _LDSR, 2, 0); | 232 | lcdc_wait_bit(ch->lcdc, _LDSR, 2, 0); |
176 | lcdc_write(ch->lcdc, _LDDRAR, 1 | (lcdc_chan_is_sublcd(ch) ? 2 : 0)); | 233 | lcdc_write(ch->lcdc, _LDDRAR, 1 | (lcdc_chan_is_sublcd(ch) ? 2 : 0)); |
177 | udelay(1); | 234 | udelay(1); |
235 | lcdc_wait_bit(ch->lcdc, _LDSR, 2, 0); | ||
178 | 236 | ||
179 | return lcdc_read(ch->lcdc, _LDDRDR) & 0xffff; | 237 | return lcdc_read(ch->lcdc, _LDDRDR) & 0x3ffff; |
180 | } | 238 | } |
181 | 239 | ||
182 | struct sh_mobile_lcdc_sys_bus_ops sh_mobile_lcdc_sys_bus_ops = { | 240 | struct sh_mobile_lcdc_sys_bus_ops sh_mobile_lcdc_sys_bus_ops = { |
@@ -185,11 +243,10 @@ struct sh_mobile_lcdc_sys_bus_ops sh_mobile_lcdc_sys_bus_ops = { | |||
185 | lcdc_sys_read_data, | 243 | lcdc_sys_read_data, |
186 | }; | 244 | }; |
187 | 245 | ||
188 | #ifdef CONFIG_HAVE_CLK | ||
189 | static void sh_mobile_lcdc_clk_on(struct sh_mobile_lcdc_priv *priv) | 246 | static void sh_mobile_lcdc_clk_on(struct sh_mobile_lcdc_priv *priv) |
190 | { | 247 | { |
191 | if (atomic_inc_and_test(&priv->clk_usecnt)) { | 248 | if (atomic_inc_and_test(&priv->hw_usecnt)) { |
192 | clk_enable(priv->clk); | 249 | pm_runtime_get_sync(priv->dev); |
193 | if (priv->dot_clk) | 250 | if (priv->dot_clk) |
194 | clk_enable(priv->dot_clk); | 251 | clk_enable(priv->dot_clk); |
195 | } | 252 | } |
@@ -197,16 +254,12 @@ static void sh_mobile_lcdc_clk_on(struct sh_mobile_lcdc_priv *priv) | |||
197 | 254 | ||
198 | static void sh_mobile_lcdc_clk_off(struct sh_mobile_lcdc_priv *priv) | 255 | static void sh_mobile_lcdc_clk_off(struct sh_mobile_lcdc_priv *priv) |
199 | { | 256 | { |
200 | if (atomic_sub_return(1, &priv->clk_usecnt) == -1) { | 257 | if (atomic_sub_return(1, &priv->hw_usecnt) == -1) { |
201 | if (priv->dot_clk) | 258 | if (priv->dot_clk) |
202 | clk_disable(priv->dot_clk); | 259 | clk_disable(priv->dot_clk); |
203 | clk_disable(priv->clk); | 260 | pm_runtime_put(priv->dev); |
204 | } | 261 | } |
205 | } | 262 | } |
206 | #else | ||
207 | static void sh_mobile_lcdc_clk_on(struct sh_mobile_lcdc_priv *priv) {} | ||
208 | static void sh_mobile_lcdc_clk_off(struct sh_mobile_lcdc_priv *priv) {} | ||
209 | #endif | ||
210 | 263 | ||
211 | static int sh_mobile_lcdc_sginit(struct fb_info *info, | 264 | static int sh_mobile_lcdc_sginit(struct fb_info *info, |
212 | struct list_head *pagelist) | 265 | struct list_head *pagelist) |
@@ -255,30 +308,52 @@ static irqreturn_t sh_mobile_lcdc_irq(int irq, void *data) | |||
255 | struct sh_mobile_lcdc_priv *priv = data; | 308 | struct sh_mobile_lcdc_priv *priv = data; |
256 | struct sh_mobile_lcdc_chan *ch; | 309 | struct sh_mobile_lcdc_chan *ch; |
257 | unsigned long tmp; | 310 | unsigned long tmp; |
311 | unsigned long ldintr; | ||
258 | int is_sub; | 312 | int is_sub; |
259 | int k; | 313 | int k; |
260 | 314 | ||
261 | /* acknowledge interrupt */ | 315 | /* acknowledge interrupt */ |
262 | tmp = lcdc_read(priv, _LDINTR); | 316 | ldintr = tmp = lcdc_read(priv, _LDINTR); |
263 | tmp &= 0xffffff00; /* mask in high 24 bits */ | 317 | /* |
264 | tmp |= 0x000000ff ^ LDINTR_FS; /* status in low 8 */ | 318 | * disable further VSYNC End IRQs, preserve all other enabled IRQs, |
319 | * write 0 to bits 0-6 to ack all triggered IRQs. | ||
320 | */ | ||
321 | tmp &= 0xffffff00 & ~LDINTR_VEE; | ||
265 | lcdc_write(priv, _LDINTR, tmp); | 322 | lcdc_write(priv, _LDINTR, tmp); |
266 | 323 | ||
267 | /* figure out if this interrupt is for main or sub lcd */ | 324 | /* figure out if this interrupt is for main or sub lcd */ |
268 | is_sub = (lcdc_read(priv, _LDSR) & (1 << 10)) ? 1 : 0; | 325 | is_sub = (lcdc_read(priv, _LDSR) & (1 << 10)) ? 1 : 0; |
269 | 326 | ||
270 | /* wake up channel and disable clocks*/ | 327 | /* wake up channel and disable clocks */ |
271 | for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { | 328 | for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { |
272 | ch = &priv->ch[k]; | 329 | ch = &priv->ch[k]; |
273 | 330 | ||
274 | if (!ch->enabled) | 331 | if (!ch->enabled) |
275 | continue; | 332 | continue; |
276 | 333 | ||
277 | if (is_sub == lcdc_chan_is_sublcd(ch)) { | 334 | /* Frame Start */ |
278 | ch->frame_end = 1; | 335 | if (ldintr & LDINTR_FS) { |
279 | wake_up(&ch->frame_end_wait); | 336 | if (is_sub == lcdc_chan_is_sublcd(ch)) { |
337 | ch->frame_end = 1; | ||
338 | wake_up(&ch->frame_end_wait); | ||
280 | 339 | ||
281 | sh_mobile_lcdc_clk_off(priv); | 340 | sh_mobile_lcdc_clk_off(priv); |
341 | } | ||
342 | } | ||
343 | |||
344 | /* VSYNC End */ | ||
345 | if (ldintr & LDINTR_VES) { | ||
346 | unsigned long ldrcntr = lcdc_read(priv, _LDRCNTR); | ||
347 | /* Set the source address for the next refresh */ | ||
348 | lcdc_write_chan_mirror(ch, LDSA1R, ch->dma_handle + | ||
349 | ch->new_pan_offset); | ||
350 | if (lcdc_chan_is_sublcd(ch)) | ||
351 | lcdc_write(ch->lcdc, _LDRCNTR, | ||
352 | ldrcntr ^ LDRCNTR_SRS); | ||
353 | else | ||
354 | lcdc_write(ch->lcdc, _LDRCNTR, | ||
355 | ldrcntr ^ LDRCNTR_MRS); | ||
356 | ch->pan_offset = ch->new_pan_offset; | ||
282 | } | 357 | } |
283 | } | 358 | } |
284 | 359 | ||
@@ -520,7 +595,6 @@ static void sh_mobile_lcdc_stop(struct sh_mobile_lcdc_priv *priv) | |||
520 | board_cfg = &ch->cfg.board_cfg; | 595 | board_cfg = &ch->cfg.board_cfg; |
521 | if (board_cfg->display_off) | 596 | if (board_cfg->display_off) |
522 | board_cfg->display_off(board_cfg->board_data); | 597 | board_cfg->display_off(board_cfg->board_data); |
523 | |||
524 | } | 598 | } |
525 | 599 | ||
526 | /* stop the lcdc */ | 600 | /* stop the lcdc */ |
@@ -579,9 +653,6 @@ static int sh_mobile_lcdc_setup_clocks(struct platform_device *pdev, | |||
579 | int clock_source, | 653 | int clock_source, |
580 | struct sh_mobile_lcdc_priv *priv) | 654 | struct sh_mobile_lcdc_priv *priv) |
581 | { | 655 | { |
582 | #ifdef CONFIG_HAVE_CLK | ||
583 | char clk_name[8]; | ||
584 | #endif | ||
585 | char *str; | 656 | char *str; |
586 | int icksel; | 657 | int icksel; |
587 | 658 | ||
@@ -595,25 +666,21 @@ static int sh_mobile_lcdc_setup_clocks(struct platform_device *pdev, | |||
595 | 666 | ||
596 | priv->lddckr = icksel << 16; | 667 | priv->lddckr = icksel << 16; |
597 | 668 | ||
598 | #ifdef CONFIG_HAVE_CLK | ||
599 | atomic_set(&priv->clk_usecnt, -1); | ||
600 | snprintf(clk_name, sizeof(clk_name), "lcdc%d", pdev->id); | ||
601 | priv->clk = clk_get(&pdev->dev, clk_name); | ||
602 | if (IS_ERR(priv->clk)) { | ||
603 | dev_err(&pdev->dev, "cannot get clock \"%s\"\n", clk_name); | ||
604 | return PTR_ERR(priv->clk); | ||
605 | } | ||
606 | |||
607 | if (str) { | 669 | if (str) { |
608 | priv->dot_clk = clk_get(&pdev->dev, str); | 670 | priv->dot_clk = clk_get(&pdev->dev, str); |
609 | if (IS_ERR(priv->dot_clk)) { | 671 | if (IS_ERR(priv->dot_clk)) { |
610 | dev_err(&pdev->dev, "cannot get dot clock %s\n", str); | 672 | dev_err(&pdev->dev, "cannot get dot clock %s\n", str); |
611 | clk_put(priv->clk); | ||
612 | return PTR_ERR(priv->dot_clk); | 673 | return PTR_ERR(priv->dot_clk); |
613 | } | 674 | } |
614 | } | 675 | } |
615 | #endif | 676 | atomic_set(&priv->hw_usecnt, -1); |
616 | 677 | ||
678 | /* Runtime PM support involves two step for this driver: | ||
679 | * 1) Enable Runtime PM | ||
680 | * 2) Force Runtime PM Resume since hardware is accessed from probe() | ||
681 | */ | ||
682 | pm_runtime_enable(priv->dev); | ||
683 | pm_runtime_resume(priv->dev); | ||
617 | return 0; | 684 | return 0; |
618 | } | 685 | } |
619 | 686 | ||
@@ -646,6 +713,9 @@ static struct fb_fix_screeninfo sh_mobile_lcdc_fix = { | |||
646 | .type = FB_TYPE_PACKED_PIXELS, | 713 | .type = FB_TYPE_PACKED_PIXELS, |
647 | .visual = FB_VISUAL_TRUECOLOR, | 714 | .visual = FB_VISUAL_TRUECOLOR, |
648 | .accel = FB_ACCEL_NONE, | 715 | .accel = FB_ACCEL_NONE, |
716 | .xpanstep = 0, | ||
717 | .ypanstep = 1, | ||
718 | .ywrapstep = 0, | ||
649 | }; | 719 | }; |
650 | 720 | ||
651 | static void sh_mobile_lcdc_fillrect(struct fb_info *info, | 721 | static void sh_mobile_lcdc_fillrect(struct fb_info *info, |
@@ -669,13 +739,38 @@ static void sh_mobile_lcdc_imageblit(struct fb_info *info, | |||
669 | sh_mobile_lcdc_deferred_io_touch(info); | 739 | sh_mobile_lcdc_deferred_io_touch(info); |
670 | } | 740 | } |
671 | 741 | ||
742 | static int sh_mobile_fb_pan_display(struct fb_var_screeninfo *var, | ||
743 | struct fb_info *info) | ||
744 | { | ||
745 | struct sh_mobile_lcdc_chan *ch = info->par; | ||
746 | |||
747 | if (info->var.xoffset == var->xoffset && | ||
748 | info->var.yoffset == var->yoffset) | ||
749 | return 0; /* No change, do nothing */ | ||
750 | |||
751 | ch->new_pan_offset = (var->yoffset * info->fix.line_length) + | ||
752 | (var->xoffset * (info->var.bits_per_pixel / 8)); | ||
753 | |||
754 | if (ch->new_pan_offset != ch->pan_offset) { | ||
755 | unsigned long ldintr; | ||
756 | ldintr = lcdc_read(ch->lcdc, _LDINTR); | ||
757 | ldintr |= LDINTR_VEE; | ||
758 | lcdc_write(ch->lcdc, _LDINTR, ldintr); | ||
759 | sh_mobile_lcdc_deferred_io_touch(info); | ||
760 | } | ||
761 | |||
762 | return 0; | ||
763 | } | ||
764 | |||
672 | static struct fb_ops sh_mobile_lcdc_ops = { | 765 | static struct fb_ops sh_mobile_lcdc_ops = { |
766 | .owner = THIS_MODULE, | ||
673 | .fb_setcolreg = sh_mobile_lcdc_setcolreg, | 767 | .fb_setcolreg = sh_mobile_lcdc_setcolreg, |
674 | .fb_read = fb_sys_read, | 768 | .fb_read = fb_sys_read, |
675 | .fb_write = fb_sys_write, | 769 | .fb_write = fb_sys_write, |
676 | .fb_fillrect = sh_mobile_lcdc_fillrect, | 770 | .fb_fillrect = sh_mobile_lcdc_fillrect, |
677 | .fb_copyarea = sh_mobile_lcdc_copyarea, | 771 | .fb_copyarea = sh_mobile_lcdc_copyarea, |
678 | .fb_imageblit = sh_mobile_lcdc_imageblit, | 772 | .fb_imageblit = sh_mobile_lcdc_imageblit, |
773 | .fb_pan_display = sh_mobile_fb_pan_display, | ||
679 | }; | 774 | }; |
680 | 775 | ||
681 | static int sh_mobile_lcdc_set_bpp(struct fb_var_screeninfo *var, int bpp) | 776 | static int sh_mobile_lcdc_set_bpp(struct fb_var_screeninfo *var, int bpp) |
@@ -731,9 +826,59 @@ static int sh_mobile_lcdc_resume(struct device *dev) | |||
731 | return sh_mobile_lcdc_start(platform_get_drvdata(pdev)); | 826 | return sh_mobile_lcdc_start(platform_get_drvdata(pdev)); |
732 | } | 827 | } |
733 | 828 | ||
829 | static int sh_mobile_lcdc_runtime_suspend(struct device *dev) | ||
830 | { | ||
831 | struct platform_device *pdev = to_platform_device(dev); | ||
832 | struct sh_mobile_lcdc_priv *p = platform_get_drvdata(pdev); | ||
833 | struct sh_mobile_lcdc_chan *ch; | ||
834 | int k, n; | ||
835 | |||
836 | /* save per-channel registers */ | ||
837 | for (k = 0; k < ARRAY_SIZE(p->ch); k++) { | ||
838 | ch = &p->ch[k]; | ||
839 | if (!ch->enabled) | ||
840 | continue; | ||
841 | for (n = 0; n < NR_CH_REGS; n++) | ||
842 | ch->saved_ch_regs[n] = lcdc_read_chan(ch, n); | ||
843 | } | ||
844 | |||
845 | /* save shared registers */ | ||
846 | for (n = 0; n < NR_SHARED_REGS; n++) | ||
847 | p->saved_shared_regs[n] = lcdc_read(p, lcdc_shared_regs[n]); | ||
848 | |||
849 | /* turn off LCDC hardware */ | ||
850 | lcdc_write(p, _LDCNT1R, 0); | ||
851 | return 0; | ||
852 | } | ||
853 | |||
854 | static int sh_mobile_lcdc_runtime_resume(struct device *dev) | ||
855 | { | ||
856 | struct platform_device *pdev = to_platform_device(dev); | ||
857 | struct sh_mobile_lcdc_priv *p = platform_get_drvdata(pdev); | ||
858 | struct sh_mobile_lcdc_chan *ch; | ||
859 | int k, n; | ||
860 | |||
861 | /* restore per-channel registers */ | ||
862 | for (k = 0; k < ARRAY_SIZE(p->ch); k++) { | ||
863 | ch = &p->ch[k]; | ||
864 | if (!ch->enabled) | ||
865 | continue; | ||
866 | for (n = 0; n < NR_CH_REGS; n++) | ||
867 | lcdc_write_chan(ch, n, ch->saved_ch_regs[n]); | ||
868 | } | ||
869 | |||
870 | /* restore shared registers */ | ||
871 | for (n = 0; n < NR_SHARED_REGS; n++) | ||
872 | lcdc_write(p, lcdc_shared_regs[n], p->saved_shared_regs[n]); | ||
873 | |||
874 | return 0; | ||
875 | } | ||
876 | |||
734 | static struct dev_pm_ops sh_mobile_lcdc_dev_pm_ops = { | 877 | static struct dev_pm_ops sh_mobile_lcdc_dev_pm_ops = { |
735 | .suspend = sh_mobile_lcdc_suspend, | 878 | .suspend = sh_mobile_lcdc_suspend, |
736 | .resume = sh_mobile_lcdc_resume, | 879 | .resume = sh_mobile_lcdc_resume, |
880 | .runtime_suspend = sh_mobile_lcdc_runtime_suspend, | ||
881 | .runtime_resume = sh_mobile_lcdc_runtime_resume, | ||
737 | }; | 882 | }; |
738 | 883 | ||
739 | static int sh_mobile_lcdc_remove(struct platform_device *pdev); | 884 | static int sh_mobile_lcdc_remove(struct platform_device *pdev); |
@@ -778,6 +923,7 @@ static int __init sh_mobile_lcdc_probe(struct platform_device *pdev) | |||
778 | } | 923 | } |
779 | 924 | ||
780 | priv->irq = i; | 925 | priv->irq = i; |
926 | priv->dev = &pdev->dev; | ||
781 | platform_set_drvdata(pdev, priv); | 927 | platform_set_drvdata(pdev, priv); |
782 | pdata = pdev->dev.platform_data; | 928 | pdata = pdev->dev.platform_data; |
783 | 929 | ||
@@ -792,6 +938,8 @@ static int __init sh_mobile_lcdc_probe(struct platform_device *pdev) | |||
792 | goto err1; | 938 | goto err1; |
793 | } | 939 | } |
794 | init_waitqueue_head(&priv->ch[i].frame_end_wait); | 940 | init_waitqueue_head(&priv->ch[i].frame_end_wait); |
941 | priv->ch[j].pan_offset = 0; | ||
942 | priv->ch[j].new_pan_offset = 0; | ||
795 | 943 | ||
796 | switch (pdata->ch[i].chan) { | 944 | switch (pdata->ch[i].chan) { |
797 | case LCDC_CHAN_MAINLCD: | 945 | case LCDC_CHAN_MAINLCD: |
@@ -834,7 +982,9 @@ static int __init sh_mobile_lcdc_probe(struct platform_device *pdev) | |||
834 | info = priv->ch[i].info; | 982 | info = priv->ch[i].info; |
835 | info->fbops = &sh_mobile_lcdc_ops; | 983 | info->fbops = &sh_mobile_lcdc_ops; |
836 | info->var.xres = info->var.xres_virtual = cfg->lcd_cfg.xres; | 984 | info->var.xres = info->var.xres_virtual = cfg->lcd_cfg.xres; |
837 | info->var.yres = info->var.yres_virtual = cfg->lcd_cfg.yres; | 985 | info->var.yres = cfg->lcd_cfg.yres; |
986 | /* Default Y virtual resolution is 2x panel size */ | ||
987 | info->var.yres_virtual = info->var.yres * 2; | ||
838 | info->var.width = cfg->lcd_size_cfg.width; | 988 | info->var.width = cfg->lcd_size_cfg.width; |
839 | info->var.height = cfg->lcd_size_cfg.height; | 989 | info->var.height = cfg->lcd_size_cfg.height; |
840 | info->var.activate = FB_ACTIVATE_NOW; | 990 | info->var.activate = FB_ACTIVATE_NOW; |
@@ -844,7 +994,8 @@ static int __init sh_mobile_lcdc_probe(struct platform_device *pdev) | |||
844 | 994 | ||
845 | info->fix = sh_mobile_lcdc_fix; | 995 | info->fix = sh_mobile_lcdc_fix; |
846 | info->fix.line_length = cfg->lcd_cfg.xres * (cfg->bpp / 8); | 996 | info->fix.line_length = cfg->lcd_cfg.xres * (cfg->bpp / 8); |
847 | info->fix.smem_len = info->fix.line_length * cfg->lcd_cfg.yres; | 997 | info->fix.smem_len = info->fix.line_length * |
998 | info->var.yres_virtual; | ||
848 | 999 | ||
849 | buf = dma_alloc_coherent(&pdev->dev, info->fix.smem_len, | 1000 | buf = dma_alloc_coherent(&pdev->dev, info->fix.smem_len, |
850 | &priv->ch[i].dma_handle, GFP_KERNEL); | 1001 | &priv->ch[i].dma_handle, GFP_KERNEL); |
@@ -947,11 +1098,10 @@ static int sh_mobile_lcdc_remove(struct platform_device *pdev) | |||
947 | framebuffer_release(info); | 1098 | framebuffer_release(info); |
948 | } | 1099 | } |
949 | 1100 | ||
950 | #ifdef CONFIG_HAVE_CLK | ||
951 | if (priv->dot_clk) | 1101 | if (priv->dot_clk) |
952 | clk_put(priv->dot_clk); | 1102 | clk_put(priv->dot_clk); |
953 | clk_put(priv->clk); | 1103 | |
954 | #endif | 1104 | pm_runtime_disable(priv->dev); |
955 | 1105 | ||
956 | if (priv->base) | 1106 | if (priv->base) |
957 | iounmap(priv->base); | 1107 | iounmap(priv->base); |