blob: d3013cd9f976966330cd7b501e86e934f1493794 [file] [log] [blame]
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07001/*
2 * SuperH Mobile LCDC Framebuffer
3 *
4 * Copyright (c) 2008 Magnus Damm
5 *
6 * This file is subject to the terms and conditions of the GNU General Public
7 * License. See the file "COPYING" in the main directory of this archive
8 * for more details.
9 */
10
Laurent Pinchartf1f60b52011-09-07 11:09:26 +020011#include <linux/atomic.h>
12#include <linux/backlight.h>
Magnus Dammcfb4f5d2008-07-23 21:31:24 -070013#include <linux/clk.h>
Laurent Pinchartf1f60b52011-09-07 11:09:26 +020014#include <linux/console.h>
Laurent Pinchartc5deac32011-12-12 18:43:16 +010015#include <linux/ctype.h>
Magnus Dammcfb4f5d2008-07-23 21:31:24 -070016#include <linux/dma-mapping.h>
Laurent Pinchartf1f60b52011-09-07 11:09:26 +020017#include <linux/delay.h>
18#include <linux/gpio.h>
19#include <linux/init.h>
Magnus Damm85645572008-12-19 15:34:41 +090020#include <linux/interrupt.h>
Laurent Pinchartf1f60b52011-09-07 11:09:26 +020021#include <linux/ioctl.h>
22#include <linux/kernel.h>
23#include <linux/mm.h>
24#include <linux/module.h>
25#include <linux/platform_device.h>
26#include <linux/pm_runtime.h>
27#include <linux/slab.h>
Laurent Pinchartedd153a2011-12-13 14:02:28 +010028#include <linux/videodev2.h>
Paul Mundt1c6a3072009-07-01 06:50:31 +000029#include <linux/vmalloc.h>
Laurent Pinchartf1f60b52011-09-07 11:09:26 +020030
Paul Mundt225c9a82008-10-01 16:24:32 +090031#include <video/sh_mobile_lcdc.h>
Laurent Pinchart8a209742011-07-13 12:13:47 +020032#include <video/sh_mobile_meram.h>
Magnus Dammcfb4f5d2008-07-23 21:31:24 -070033
Guennadi Liakhovetski6de9edd2010-09-03 07:20:23 +000034#include "sh_mobile_lcdcfb.h"
35
Laurent Pinchartc5deac32011-12-12 18:43:16 +010036/* ----------------------------------------------------------------------------
37 * Overlay register definitions
38 */
39
40#define LDBCR 0xb00
41#define LDBCR_UPC(n) (1 << ((n) + 16))
42#define LDBCR_UPF(n) (1 << ((n) + 8))
43#define LDBCR_UPD(n) (1 << ((n) + 0))
44#define LDBnBSIFR(n) (0xb20 + (n) * 0x20 + 0x00)
45#define LDBBSIFR_EN (1 << 31)
46#define LDBBSIFR_VS (1 << 29)
47#define LDBBSIFR_BRSEL (1 << 28)
48#define LDBBSIFR_MX (1 << 27)
49#define LDBBSIFR_MY (1 << 26)
50#define LDBBSIFR_CV3 (3 << 24)
51#define LDBBSIFR_CV2 (2 << 24)
52#define LDBBSIFR_CV1 (1 << 24)
53#define LDBBSIFR_CV0 (0 << 24)
54#define LDBBSIFR_CV_MASK (3 << 24)
55#define LDBBSIFR_LAY_MASK (0xff << 16)
56#define LDBBSIFR_LAY_SHIFT 16
57#define LDBBSIFR_ROP3_MASK (0xff << 16)
58#define LDBBSIFR_ROP3_SHIFT 16
59#define LDBBSIFR_AL_PL8 (3 << 14)
60#define LDBBSIFR_AL_PL1 (2 << 14)
61#define LDBBSIFR_AL_PK (1 << 14)
62#define LDBBSIFR_AL_1 (0 << 14)
63#define LDBBSIFR_AL_MASK (3 << 14)
64#define LDBBSIFR_SWPL (1 << 10)
65#define LDBBSIFR_SWPW (1 << 9)
66#define LDBBSIFR_SWPB (1 << 8)
67#define LDBBSIFR_RY (1 << 7)
68#define LDBBSIFR_CHRR_420 (2 << 0)
69#define LDBBSIFR_CHRR_422 (1 << 0)
70#define LDBBSIFR_CHRR_444 (0 << 0)
71#define LDBBSIFR_RPKF_ARGB32 (0x00 << 0)
72#define LDBBSIFR_RPKF_RGB16 (0x03 << 0)
73#define LDBBSIFR_RPKF_RGB24 (0x0b << 0)
74#define LDBBSIFR_RPKF_MASK (0x1f << 0)
75#define LDBnBSSZR(n) (0xb20 + (n) * 0x20 + 0x04)
76#define LDBBSSZR_BVSS_MASK (0xfff << 16)
77#define LDBBSSZR_BVSS_SHIFT 16
78#define LDBBSSZR_BHSS_MASK (0xfff << 0)
79#define LDBBSSZR_BHSS_SHIFT 0
80#define LDBnBLOCR(n) (0xb20 + (n) * 0x20 + 0x08)
81#define LDBBLOCR_CVLC_MASK (0xfff << 16)
82#define LDBBLOCR_CVLC_SHIFT 16
83#define LDBBLOCR_CHLC_MASK (0xfff << 0)
84#define LDBBLOCR_CHLC_SHIFT 0
85#define LDBnBSMWR(n) (0xb20 + (n) * 0x20 + 0x0c)
86#define LDBBSMWR_BSMWA_MASK (0xffff << 16)
87#define LDBBSMWR_BSMWA_SHIFT 16
88#define LDBBSMWR_BSMW_MASK (0xffff << 0)
89#define LDBBSMWR_BSMW_SHIFT 0
90#define LDBnBSAYR(n) (0xb20 + (n) * 0x20 + 0x10)
91#define LDBBSAYR_FG1A_MASK (0xff << 24)
92#define LDBBSAYR_FG1A_SHIFT 24
93#define LDBBSAYR_FG1R_MASK (0xff << 16)
94#define LDBBSAYR_FG1R_SHIFT 16
95#define LDBBSAYR_FG1G_MASK (0xff << 8)
96#define LDBBSAYR_FG1G_SHIFT 8
97#define LDBBSAYR_FG1B_MASK (0xff << 0)
98#define LDBBSAYR_FG1B_SHIFT 0
99#define LDBnBSACR(n) (0xb20 + (n) * 0x20 + 0x14)
100#define LDBBSACR_FG2A_MASK (0xff << 24)
101#define LDBBSACR_FG2A_SHIFT 24
102#define LDBBSACR_FG2R_MASK (0xff << 16)
103#define LDBBSACR_FG2R_SHIFT 16
104#define LDBBSACR_FG2G_MASK (0xff << 8)
105#define LDBBSACR_FG2G_SHIFT 8
106#define LDBBSACR_FG2B_MASK (0xff << 0)
107#define LDBBSACR_FG2B_SHIFT 0
108#define LDBnBSAAR(n) (0xb20 + (n) * 0x20 + 0x18)
109#define LDBBSAAR_AP_MASK (0xff << 24)
110#define LDBBSAAR_AP_SHIFT 24
111#define LDBBSAAR_R_MASK (0xff << 16)
112#define LDBBSAAR_R_SHIFT 16
113#define LDBBSAAR_GY_MASK (0xff << 8)
114#define LDBBSAAR_GY_SHIFT 8
115#define LDBBSAAR_B_MASK (0xff << 0)
116#define LDBBSAAR_B_SHIFT 0
117#define LDBnBPPCR(n) (0xb20 + (n) * 0x20 + 0x1c)
118#define LDBBPPCR_AP_MASK (0xff << 24)
119#define LDBBPPCR_AP_SHIFT 24
120#define LDBBPPCR_R_MASK (0xff << 16)
121#define LDBBPPCR_R_SHIFT 16
122#define LDBBPPCR_GY_MASK (0xff << 8)
123#define LDBBPPCR_GY_SHIFT 8
124#define LDBBPPCR_B_MASK (0xff << 0)
125#define LDBBPPCR_B_SHIFT 0
126#define LDBnBBGCL(n) (0xb10 + (n) * 0x04)
127#define LDBBBGCL_BGA_MASK (0xff << 24)
128#define LDBBBGCL_BGA_SHIFT 24
129#define LDBBBGCL_BGR_MASK (0xff << 16)
130#define LDBBBGCL_BGR_SHIFT 16
131#define LDBBBGCL_BGG_MASK (0xff << 8)
132#define LDBBBGCL_BGG_SHIFT 8
133#define LDBBBGCL_BGB_MASK (0xff << 0)
134#define LDBBBGCL_BGB_SHIFT 0
135
Phil Edworthya6f15ad2009-09-15 12:00:30 +0000136#define SIDE_B_OFFSET 0x1000
137#define MIRROR_OFFSET 0x2000
Magnus Dammcfb4f5d2008-07-23 21:31:24 -0700138
Guennadi Liakhovetskid2ecbab2010-11-04 11:06:06 +0000139#define MAX_XRES 1920
140#define MAX_YRES 1080
Magnus Dammcfb4f5d2008-07-23 21:31:24 -0700141
Laurent Pinchartc5deac32011-12-12 18:43:16 +0100142enum sh_mobile_lcdc_overlay_mode {
143 LCDC_OVERLAY_BLEND,
144 LCDC_OVERLAY_ROP3,
145};
146
147/*
148 * struct sh_mobile_lcdc_overlay - LCDC display overlay
149 *
150 * @channel: LCDC channel this overlay belongs to
151 * @cfg: Overlay configuration
152 * @info: Frame buffer device
153 * @index: Overlay index (0-3)
154 * @base: Overlay registers base address
155 * @enabled: True if the overlay is enabled
156 * @mode: Overlay blending mode (alpha blend or ROP3)
157 * @alpha: Global alpha blending value (0-255, for alpha blending mode)
158 * @rop3: Raster operation (for ROP3 mode)
159 * @fb_mem: Frame buffer virtual memory address
160 * @fb_size: Frame buffer size in bytes
161 * @dma_handle: Frame buffer DMA address
162 * @base_addr_y: Overlay base address (RGB or luma component)
163 * @base_addr_c: Overlay base address (chroma component)
Laurent Pincharta4aa25f2012-03-15 18:15:37 +0100164 * @pan_y_offset: Panning linear offset in bytes (luma component)
Laurent Pinchartc5deac32011-12-12 18:43:16 +0100165 * @format: Current pixelf format
166 * @xres: Horizontal visible resolution
167 * @xres_virtual: Horizontal total resolution
168 * @yres: Vertical visible resolution
169 * @yres_virtual: Vertical total resolution
170 * @pitch: Overlay line pitch
171 * @pos_x: Horizontal overlay position
172 * @pos_y: Vertical overlay position
173 */
174struct sh_mobile_lcdc_overlay {
175 struct sh_mobile_lcdc_chan *channel;
176
177 const struct sh_mobile_lcdc_overlay_cfg *cfg;
178 struct fb_info *info;
179
180 unsigned int index;
181 unsigned long base;
182
183 bool enabled;
184 enum sh_mobile_lcdc_overlay_mode mode;
185 unsigned int alpha;
186 unsigned int rop3;
187
188 void *fb_mem;
189 unsigned long fb_size;
190
191 dma_addr_t dma_handle;
192 unsigned long base_addr_y;
193 unsigned long base_addr_c;
Laurent Pincharta4aa25f2012-03-15 18:15:37 +0100194 unsigned long pan_y_offset;
Laurent Pinchartc5deac32011-12-12 18:43:16 +0100195
196 const struct sh_mobile_lcdc_format_info *format;
197 unsigned int xres;
198 unsigned int xres_virtual;
199 unsigned int yres;
200 unsigned int yres_virtual;
201 unsigned int pitch;
202 int pos_x;
203 int pos_y;
204};
205
Laurent Pinchartf1f60b52011-09-07 11:09:26 +0200206struct sh_mobile_lcdc_priv {
207 void __iomem *base;
208 int irq;
209 atomic_t hw_usecnt;
210 struct device *dev;
211 struct clk *dot_clk;
212 unsigned long lddckr;
Laurent Pinchartc5deac32011-12-12 18:43:16 +0100213
Laurent Pinchartf1f60b52011-09-07 11:09:26 +0200214 struct sh_mobile_lcdc_chan ch[2];
Laurent Pinchartc5deac32011-12-12 18:43:16 +0100215 struct sh_mobile_lcdc_overlay overlays[4];
216
Laurent Pinchartf1f60b52011-09-07 11:09:26 +0200217 struct notifier_block notifier;
218 int started;
219 int forced_fourcc; /* 2 channel LCDC must share fourcc setting */
220 struct sh_mobile_meram_info *meram_dev;
221};
222
223/* -----------------------------------------------------------------------------
224 * Registers access
225 */
226
Magnus Damm0246c472009-08-14 10:49:08 +0000227static unsigned long lcdc_offs_mainlcd[NR_CH_REGS] = {
Magnus Dammcfb4f5d2008-07-23 21:31:24 -0700228 [LDDCKPAT1R] = 0x400,
229 [LDDCKPAT2R] = 0x404,
230 [LDMT1R] = 0x418,
231 [LDMT2R] = 0x41c,
232 [LDMT3R] = 0x420,
233 [LDDFR] = 0x424,
234 [LDSM1R] = 0x428,
Magnus Damm85645572008-12-19 15:34:41 +0900235 [LDSM2R] = 0x42c,
Magnus Dammcfb4f5d2008-07-23 21:31:24 -0700236 [LDSA1R] = 0x430,
Damian Hobson-Garcia53b50312011-02-24 05:47:13 +0000237 [LDSA2R] = 0x434,
Magnus Dammcfb4f5d2008-07-23 21:31:24 -0700238 [LDMLSR] = 0x438,
239 [LDHCNR] = 0x448,
240 [LDHSYNR] = 0x44c,
241 [LDVLNR] = 0x450,
242 [LDVSYNR] = 0x454,
243 [LDPMR] = 0x460,
Guennadi Liakhovetski6011bde2010-07-21 10:13:21 +0000244 [LDHAJR] = 0x4a0,
Magnus Dammcfb4f5d2008-07-23 21:31:24 -0700245};
246
Magnus Damm0246c472009-08-14 10:49:08 +0000247static unsigned long lcdc_offs_sublcd[NR_CH_REGS] = {
Magnus Dammcfb4f5d2008-07-23 21:31:24 -0700248 [LDDCKPAT1R] = 0x408,
249 [LDDCKPAT2R] = 0x40c,
250 [LDMT1R] = 0x600,
251 [LDMT2R] = 0x604,
252 [LDMT3R] = 0x608,
253 [LDDFR] = 0x60c,
254 [LDSM1R] = 0x610,
Magnus Damm85645572008-12-19 15:34:41 +0900255 [LDSM2R] = 0x614,
Magnus Dammcfb4f5d2008-07-23 21:31:24 -0700256 [LDSA1R] = 0x618,
257 [LDMLSR] = 0x620,
258 [LDHCNR] = 0x624,
259 [LDHSYNR] = 0x628,
260 [LDVLNR] = 0x62c,
261 [LDVSYNR] = 0x630,
262 [LDPMR] = 0x63c,
263};
264
Phil Edworthya6f15ad2009-09-15 12:00:30 +0000265static bool banked(int reg_nr)
266{
267 switch (reg_nr) {
268 case LDMT1R:
269 case LDMT2R:
270 case LDMT3R:
271 case LDDFR:
272 case LDSM1R:
273 case LDSA1R:
Damian Hobson-Garcia53b50312011-02-24 05:47:13 +0000274 case LDSA2R:
Phil Edworthya6f15ad2009-09-15 12:00:30 +0000275 case LDMLSR:
276 case LDHCNR:
277 case LDHSYNR:
278 case LDVLNR:
279 case LDVSYNR:
280 return true;
281 }
282 return false;
283}
284
Laurent Pinchartf1f60b52011-09-07 11:09:26 +0200285static int lcdc_chan_is_sublcd(struct sh_mobile_lcdc_chan *chan)
286{
Laurent Pinchartb5ef9672011-11-22 00:56:58 +0100287 return chan->cfg->chan == LCDC_CHAN_SUBLCD;
Laurent Pinchartf1f60b52011-09-07 11:09:26 +0200288}
289
Magnus Dammcfb4f5d2008-07-23 21:31:24 -0700290static void lcdc_write_chan(struct sh_mobile_lcdc_chan *chan,
291 int reg_nr, unsigned long data)
292{
293 iowrite32(data, chan->lcdc->base + chan->reg_offs[reg_nr]);
Phil Edworthya6f15ad2009-09-15 12:00:30 +0000294 if (banked(reg_nr))
295 iowrite32(data, chan->lcdc->base + chan->reg_offs[reg_nr] +
296 SIDE_B_OFFSET);
297}
298
299static void lcdc_write_chan_mirror(struct sh_mobile_lcdc_chan *chan,
300 int reg_nr, unsigned long data)
301{
302 iowrite32(data, chan->lcdc->base + chan->reg_offs[reg_nr] +
303 MIRROR_OFFSET);
Magnus Dammcfb4f5d2008-07-23 21:31:24 -0700304}
305
306static unsigned long lcdc_read_chan(struct sh_mobile_lcdc_chan *chan,
307 int reg_nr)
308{
309 return ioread32(chan->lcdc->base + chan->reg_offs[reg_nr]);
310}
311
Laurent Pinchartc5deac32011-12-12 18:43:16 +0100312static void lcdc_write_overlay(struct sh_mobile_lcdc_overlay *ovl,
313 int reg, unsigned long data)
314{
315 iowrite32(data, ovl->channel->lcdc->base + reg);
316 iowrite32(data, ovl->channel->lcdc->base + reg + SIDE_B_OFFSET);
317}
318
Magnus Dammcfb4f5d2008-07-23 21:31:24 -0700319static void lcdc_write(struct sh_mobile_lcdc_priv *priv,
320 unsigned long reg_offs, unsigned long data)
321{
322 iowrite32(data, priv->base + reg_offs);
323}
324
325static unsigned long lcdc_read(struct sh_mobile_lcdc_priv *priv,
326 unsigned long reg_offs)
327{
328 return ioread32(priv->base + reg_offs);
329}
330
331static void lcdc_wait_bit(struct sh_mobile_lcdc_priv *priv,
332 unsigned long reg_offs,
333 unsigned long mask, unsigned long until)
334{
335 while ((lcdc_read(priv, reg_offs) & mask) != until)
336 cpu_relax();
337}
338
Laurent Pinchartf1f60b52011-09-07 11:09:26 +0200339/* -----------------------------------------------------------------------------
340 * Clock management
341 */
342
343static void sh_mobile_lcdc_clk_on(struct sh_mobile_lcdc_priv *priv)
Magnus Dammcfb4f5d2008-07-23 21:31:24 -0700344{
Laurent Pinchartf1f60b52011-09-07 11:09:26 +0200345 if (atomic_inc_and_test(&priv->hw_usecnt)) {
346 if (priv->dot_clk)
Laurent Pinchartdeccd242013-10-28 23:49:29 +0100347 clk_prepare_enable(priv->dot_clk);
Laurent Pinchartf1f60b52011-09-07 11:09:26 +0200348 pm_runtime_get_sync(priv->dev);
349 if (priv->meram_dev && priv->meram_dev->pdev)
350 pm_runtime_get_sync(&priv->meram_dev->pdev->dev);
351 }
Magnus Dammcfb4f5d2008-07-23 21:31:24 -0700352}
353
Laurent Pinchartf1f60b52011-09-07 11:09:26 +0200354static void sh_mobile_lcdc_clk_off(struct sh_mobile_lcdc_priv *priv)
355{
356 if (atomic_sub_return(1, &priv->hw_usecnt) == -1) {
357 if (priv->meram_dev && priv->meram_dev->pdev)
358 pm_runtime_put_sync(&priv->meram_dev->pdev->dev);
359 pm_runtime_put(priv->dev);
360 if (priv->dot_clk)
Laurent Pinchartdeccd242013-10-28 23:49:29 +0100361 clk_disable_unprepare(priv->dot_clk);
Laurent Pinchartf1f60b52011-09-07 11:09:26 +0200362 }
363}
364
Laurent Pinchart0a7f17a2011-09-07 16:02:31 +0200365static int sh_mobile_lcdc_setup_clocks(struct sh_mobile_lcdc_priv *priv,
366 int clock_source)
Laurent Pinchartf1f60b52011-09-07 11:09:26 +0200367{
Laurent Pinchart4774c122011-09-07 15:47:07 +0200368 struct clk *clk;
Laurent Pinchartf1f60b52011-09-07 11:09:26 +0200369 char *str;
370
371 switch (clock_source) {
372 case LCDC_CLK_BUS:
373 str = "bus_clk";
374 priv->lddckr = LDDCKR_ICKSEL_BUS;
375 break;
376 case LCDC_CLK_PERIPHERAL:
377 str = "peripheral_clk";
378 priv->lddckr = LDDCKR_ICKSEL_MIPI;
379 break;
380 case LCDC_CLK_EXTERNAL:
381 str = NULL;
382 priv->lddckr = LDDCKR_ICKSEL_HDMI;
383 break;
384 default:
385 return -EINVAL;
386 }
387
Laurent Pinchart4774c122011-09-07 15:47:07 +0200388 if (str == NULL)
389 return 0;
390
Laurent Pinchart0a7f17a2011-09-07 16:02:31 +0200391 clk = clk_get(priv->dev, str);
Laurent Pinchart4774c122011-09-07 15:47:07 +0200392 if (IS_ERR(clk)) {
Laurent Pinchart0a7f17a2011-09-07 16:02:31 +0200393 dev_err(priv->dev, "cannot get dot clock %s\n", str);
Laurent Pinchart4774c122011-09-07 15:47:07 +0200394 return PTR_ERR(clk);
Laurent Pinchartf1f60b52011-09-07 11:09:26 +0200395 }
396
Laurent Pinchart4774c122011-09-07 15:47:07 +0200397 priv->dot_clk = clk;
Laurent Pinchartf1f60b52011-09-07 11:09:26 +0200398 return 0;
399}
400
401/* -----------------------------------------------------------------------------
Laurent Pinchart37c5dcc2011-09-09 15:45:43 +0200402 * Display, panel and deferred I/O
Laurent Pinchartf1f60b52011-09-07 11:09:26 +0200403 */
404
Magnus Dammcfb4f5d2008-07-23 21:31:24 -0700405static void lcdc_sys_write_index(void *handle, unsigned long data)
406{
407 struct sh_mobile_lcdc_chan *ch = handle;
408
Laurent Pinchartce1c0b02011-07-13 12:13:47 +0200409 lcdc_write(ch->lcdc, _LDDWD0R, data | LDDWDxR_WDACT);
410 lcdc_wait_bit(ch->lcdc, _LDSR, LDSR_AS, 0);
411 lcdc_write(ch->lcdc, _LDDWAR, LDDWAR_WA |
412 (lcdc_chan_is_sublcd(ch) ? 2 : 0));
413 lcdc_wait_bit(ch->lcdc, _LDSR, LDSR_AS, 0);
Magnus Dammcfb4f5d2008-07-23 21:31:24 -0700414}
415
416static void lcdc_sys_write_data(void *handle, unsigned long data)
417{
418 struct sh_mobile_lcdc_chan *ch = handle;
419
Laurent Pinchartce1c0b02011-07-13 12:13:47 +0200420 lcdc_write(ch->lcdc, _LDDWD0R, data | LDDWDxR_WDACT | LDDWDxR_RSW);
421 lcdc_wait_bit(ch->lcdc, _LDSR, LDSR_AS, 0);
422 lcdc_write(ch->lcdc, _LDDWAR, LDDWAR_WA |
423 (lcdc_chan_is_sublcd(ch) ? 2 : 0));
424 lcdc_wait_bit(ch->lcdc, _LDSR, LDSR_AS, 0);
Magnus Dammcfb4f5d2008-07-23 21:31:24 -0700425}
426
427static unsigned long lcdc_sys_read_data(void *handle)
428{
429 struct sh_mobile_lcdc_chan *ch = handle;
430
Laurent Pinchartce1c0b02011-07-13 12:13:47 +0200431 lcdc_write(ch->lcdc, _LDDRDR, LDDRDR_RSR);
432 lcdc_wait_bit(ch->lcdc, _LDSR, LDSR_AS, 0);
433 lcdc_write(ch->lcdc, _LDDRAR, LDDRAR_RA |
434 (lcdc_chan_is_sublcd(ch) ? 2 : 0));
Magnus Dammcfb4f5d2008-07-23 21:31:24 -0700435 udelay(1);
Laurent Pinchartce1c0b02011-07-13 12:13:47 +0200436 lcdc_wait_bit(ch->lcdc, _LDSR, LDSR_AS, 0);
Magnus Dammcfb4f5d2008-07-23 21:31:24 -0700437
Laurent Pinchartce1c0b02011-07-13 12:13:47 +0200438 return lcdc_read(ch->lcdc, _LDDRDR) & LDDRDR_DRD_MASK;
Magnus Dammcfb4f5d2008-07-23 21:31:24 -0700439}
440
Laurent Pinchartd38d8402012-08-13 13:56:46 +0200441static struct sh_mobile_lcdc_sys_bus_ops sh_mobile_lcdc_sys_bus_ops = {
Magnus Dammcfb4f5d2008-07-23 21:31:24 -0700442 lcdc_sys_write_index,
443 lcdc_sys_write_data,
444 lcdc_sys_read_data,
445};
446
Paul Mundt1c6a3072009-07-01 06:50:31 +0000447static int sh_mobile_lcdc_sginit(struct fb_info *info,
448 struct list_head *pagelist)
449{
450 struct sh_mobile_lcdc_chan *ch = info->par;
Laurent Pinchart58f03d92011-11-30 23:07:30 +0100451 unsigned int nr_pages_max = ch->fb_size >> PAGE_SHIFT;
Paul Mundt1c6a3072009-07-01 06:50:31 +0000452 struct page *page;
453 int nr_pages = 0;
454
455 sg_init_table(ch->sglist, nr_pages_max);
456
457 list_for_each_entry(page, pagelist, lru)
458 sg_set_page(&ch->sglist[nr_pages++], page, PAGE_SIZE, 0);
459
460 return nr_pages;
461}
462
Magnus Damm85645572008-12-19 15:34:41 +0900463static void sh_mobile_lcdc_deferred_io(struct fb_info *info,
464 struct list_head *pagelist)
465{
466 struct sh_mobile_lcdc_chan *ch = info->par;
Laurent Pinchartb5ef9672011-11-22 00:56:58 +0100467 const struct sh_mobile_lcdc_panel_cfg *panel = &ch->cfg->panel_cfg;
Magnus Damm85645572008-12-19 15:34:41 +0900468
469 /* enable clocks before accessing hardware */
470 sh_mobile_lcdc_clk_on(ch->lcdc);
471
Paul Mundt5c1a56b2009-11-04 15:59:04 +0900472 /*
473 * It's possible to get here without anything on the pagelist via
474 * sh_mobile_lcdc_deferred_io_touch() or via a userspace fsync()
475 * invocation. In the former case, the acceleration routines are
476 * stepped in to when using the framebuffer console causing the
477 * workqueue to be scheduled without any dirty pages on the list.
478 *
479 * Despite this, a panel update is still needed given that the
480 * acceleration routines have their own methods for writing in
481 * that still need to be updated.
482 *
483 * The fsync() and empty pagelist case could be optimized for,
484 * but we don't bother, as any application exhibiting such
485 * behaviour is fundamentally broken anyways.
486 */
487 if (!list_empty(pagelist)) {
488 unsigned int nr_pages = sh_mobile_lcdc_sginit(info, pagelist);
Paul Mundt1c6a3072009-07-01 06:50:31 +0000489
Paul Mundt5c1a56b2009-11-04 15:59:04 +0900490 /* trigger panel update */
Laurent Pincharte8363142011-11-29 14:37:35 +0100491 dma_map_sg(ch->lcdc->dev, ch->sglist, nr_pages, DMA_TO_DEVICE);
Laurent Pinchartafaad832011-09-11 22:59:04 +0200492 if (panel->start_transfer)
493 panel->start_transfer(ch, &sh_mobile_lcdc_sys_bus_ops);
Laurent Pinchartce1c0b02011-07-13 12:13:47 +0200494 lcdc_write_chan(ch, LDSM2R, LDSM2R_OSTRG);
Laurent Pincharte8363142011-11-29 14:37:35 +0100495 dma_unmap_sg(ch->lcdc->dev, ch->sglist, nr_pages,
496 DMA_TO_DEVICE);
Magnus Dammef61aae2009-12-07 14:20:06 +0000497 } else {
Laurent Pinchartafaad832011-09-11 22:59:04 +0200498 if (panel->start_transfer)
499 panel->start_transfer(ch, &sh_mobile_lcdc_sys_bus_ops);
Laurent Pinchartce1c0b02011-07-13 12:13:47 +0200500 lcdc_write_chan(ch, LDSM2R, LDSM2R_OSTRG);
Magnus Dammef61aae2009-12-07 14:20:06 +0000501 }
Magnus Damm85645572008-12-19 15:34:41 +0900502}
503
504static void sh_mobile_lcdc_deferred_io_touch(struct fb_info *info)
505{
506 struct fb_deferred_io *fbdefio = info->fbdefio;
507
508 if (fbdefio)
509 schedule_delayed_work(&info->deferred_work, fbdefio->delay);
510}
511
Laurent Pinchart37c5dcc2011-09-09 15:45:43 +0200512static void sh_mobile_lcdc_display_on(struct sh_mobile_lcdc_chan *ch)
513{
Laurent Pinchartb5ef9672011-11-22 00:56:58 +0100514 const struct sh_mobile_lcdc_panel_cfg *panel = &ch->cfg->panel_cfg;
Laurent Pinchart37c5dcc2011-09-09 15:45:43 +0200515
Laurent Pinchart9a2985e2011-09-11 22:59:04 +0200516 if (ch->tx_dev) {
Laurent Pinchart458981c2011-11-28 23:19:59 +0100517 int ret;
518
519 ret = ch->tx_dev->ops->display_on(ch->tx_dev);
520 if (ret < 0)
Laurent Pinchart9a2985e2011-09-11 22:59:04 +0200521 return;
Laurent Pinchart458981c2011-11-28 23:19:59 +0100522
523 if (ret == SH_MOBILE_LCDC_DISPLAY_DISCONNECTED)
524 ch->info->state = FBINFO_STATE_SUSPENDED;
Laurent Pinchart9a2985e2011-09-11 22:59:04 +0200525 }
526
Laurent Pinchart37c5dcc2011-09-09 15:45:43 +0200527 /* HDMI must be enabled before LCDC configuration */
Laurent Pinchartafaad832011-09-11 22:59:04 +0200528 if (panel->display_on)
529 panel->display_on();
Laurent Pinchart37c5dcc2011-09-09 15:45:43 +0200530}
531
532static void sh_mobile_lcdc_display_off(struct sh_mobile_lcdc_chan *ch)
533{
Laurent Pinchartb5ef9672011-11-22 00:56:58 +0100534 const struct sh_mobile_lcdc_panel_cfg *panel = &ch->cfg->panel_cfg;
Laurent Pinchart37c5dcc2011-09-09 15:45:43 +0200535
Laurent Pinchartafaad832011-09-11 22:59:04 +0200536 if (panel->display_off)
537 panel->display_off();
Laurent Pinchart9a2985e2011-09-11 22:59:04 +0200538
539 if (ch->tx_dev)
540 ch->tx_dev->ops->display_off(ch->tx_dev);
Laurent Pinchart37c5dcc2011-09-09 15:45:43 +0200541}
542
Laurent Pinchartecd29942011-09-18 14:14:46 +0200543static bool
544sh_mobile_lcdc_must_reconfigure(struct sh_mobile_lcdc_chan *ch,
Laurent Pincharte0c86012011-11-29 01:05:47 +0100545 const struct fb_videomode *new_mode)
Laurent Pinchartecd29942011-09-18 14:14:46 +0200546{
Laurent Pinchartecd29942011-09-18 14:14:46 +0200547 dev_dbg(ch->info->dev, "Old %ux%u, new %ux%u\n",
Laurent Pinchart2d045592011-11-29 13:42:48 +0100548 ch->display.mode.xres, ch->display.mode.yres,
549 new_mode->xres, new_mode->yres);
Laurent Pinchartecd29942011-09-18 14:14:46 +0200550
Laurent Pincharte0c86012011-11-29 01:05:47 +0100551 /* It can be a different monitor with an equal video-mode */
Laurent Pinchart2d045592011-11-29 13:42:48 +0100552 if (fb_mode_is_equal(&ch->display.mode, new_mode))
Laurent Pinchartecd29942011-09-18 14:14:46 +0200553 return false;
Laurent Pinchartecd29942011-09-18 14:14:46 +0200554
555 dev_dbg(ch->info->dev, "Switching %u -> %u lines\n",
Laurent Pinchart2d045592011-11-29 13:42:48 +0100556 ch->display.mode.yres, new_mode->yres);
557 ch->display.mode = *new_mode;
Laurent Pinchartecd29942011-09-18 14:14:46 +0200558
559 return true;
560}
561
Laurent Pinchartd7ad3342011-11-22 00:56:58 +0100562static int sh_mobile_lcdc_check_var(struct fb_var_screeninfo *var,
563 struct fb_info *info);
Laurent Pinchartecd29942011-09-18 14:14:46 +0200564
565static int sh_mobile_lcdc_display_notify(struct sh_mobile_lcdc_chan *ch,
566 enum sh_mobile_lcdc_entity_event event,
Laurent Pincharte0c86012011-11-29 01:05:47 +0100567 const struct fb_videomode *mode,
568 const struct fb_monspecs *monspec)
Laurent Pinchartecd29942011-09-18 14:14:46 +0200569{
570 struct fb_info *info = ch->info;
Laurent Pincharte0c86012011-11-29 01:05:47 +0100571 struct fb_var_screeninfo var;
Laurent Pinchartecd29942011-09-18 14:14:46 +0200572 int ret = 0;
573
574 switch (event) {
575 case SH_MOBILE_LCDC_EVENT_DISPLAY_CONNECT:
576 /* HDMI plug in */
Gu Zheng3a41c5d2013-11-05 18:00:57 +0800577 console_lock();
Laurent Pinchartecd29942011-09-18 14:14:46 +0200578 if (lock_fb_info(info)) {
Gu Zheng3a41c5d2013-11-05 18:00:57 +0800579
Laurent Pinchartecd29942011-09-18 14:14:46 +0200580
Laurent Pinchart2d045592011-11-29 13:42:48 +0100581 ch->display.width = monspec->max_x * 10;
582 ch->display.height = monspec->max_y * 10;
Laurent Pincharte0c86012011-11-29 01:05:47 +0100583
584 if (!sh_mobile_lcdc_must_reconfigure(ch, mode) &&
Laurent Pinchartecd29942011-09-18 14:14:46 +0200585 info->state == FBINFO_STATE_RUNNING) {
586 /* First activation with the default monitor.
587 * Just turn on, if we run a resume here, the
588 * logo disappears.
589 */
Laurent Pinchart856e8df2012-08-13 13:56:46 +0200590 info->var.width = ch->display.width;
591 info->var.height = ch->display.height;
Laurent Pinchartecd29942011-09-18 14:14:46 +0200592 sh_mobile_lcdc_display_on(ch);
593 } else {
594 /* New monitor or have to wake up */
595 fb_set_suspend(info, 0);
596 }
597
Gu Zheng3a41c5d2013-11-05 18:00:57 +0800598
Laurent Pinchartecd29942011-09-18 14:14:46 +0200599 unlock_fb_info(info);
600 }
Gu Zheng3a41c5d2013-11-05 18:00:57 +0800601 console_unlock();
Laurent Pinchartecd29942011-09-18 14:14:46 +0200602 break;
603
604 case SH_MOBILE_LCDC_EVENT_DISPLAY_DISCONNECT:
605 /* HDMI disconnect */
Gu Zheng3a41c5d2013-11-05 18:00:57 +0800606 console_lock();
Laurent Pinchartecd29942011-09-18 14:14:46 +0200607 if (lock_fb_info(info)) {
Laurent Pinchartecd29942011-09-18 14:14:46 +0200608 fb_set_suspend(info, 1);
Laurent Pinchartecd29942011-09-18 14:14:46 +0200609 unlock_fb_info(info);
610 }
Gu Zheng3a41c5d2013-11-05 18:00:57 +0800611 console_unlock();
Laurent Pinchartecd29942011-09-18 14:14:46 +0200612 break;
613
614 case SH_MOBILE_LCDC_EVENT_DISPLAY_MODE:
615 /* Validate a proposed new mode */
Laurent Pincharte0c86012011-11-29 01:05:47 +0100616 fb_videomode_to_var(&var, mode);
617 var.bits_per_pixel = info->var.bits_per_pixel;
618 var.grayscale = info->var.grayscale;
Laurent Pinchartd7ad3342011-11-22 00:56:58 +0100619 ret = sh_mobile_lcdc_check_var(&var, info);
Laurent Pinchartecd29942011-09-18 14:14:46 +0200620 break;
621 }
622
623 return ret;
624}
625
Laurent Pinchartf1f60b52011-09-07 11:09:26 +0200626/* -----------------------------------------------------------------------------
627 * Format helpers
628 */
629
Laurent Pinchart105784b2011-11-29 15:58:10 +0100630struct sh_mobile_lcdc_format_info {
631 u32 fourcc;
632 unsigned int bpp;
633 bool yuv;
634 u32 lddfr;
635};
636
637static const struct sh_mobile_lcdc_format_info sh_mobile_format_infos[] = {
638 {
639 .fourcc = V4L2_PIX_FMT_RGB565,
640 .bpp = 16,
641 .yuv = false,
642 .lddfr = LDDFR_PKF_RGB16,
643 }, {
644 .fourcc = V4L2_PIX_FMT_BGR24,
645 .bpp = 24,
646 .yuv = false,
647 .lddfr = LDDFR_PKF_RGB24,
648 }, {
649 .fourcc = V4L2_PIX_FMT_BGR32,
650 .bpp = 32,
651 .yuv = false,
652 .lddfr = LDDFR_PKF_ARGB32,
653 }, {
654 .fourcc = V4L2_PIX_FMT_NV12,
655 .bpp = 12,
656 .yuv = true,
657 .lddfr = LDDFR_CC | LDDFR_YF_420,
658 }, {
659 .fourcc = V4L2_PIX_FMT_NV21,
660 .bpp = 12,
661 .yuv = true,
662 .lddfr = LDDFR_CC | LDDFR_YF_420,
663 }, {
664 .fourcc = V4L2_PIX_FMT_NV16,
665 .bpp = 16,
666 .yuv = true,
667 .lddfr = LDDFR_CC | LDDFR_YF_422,
668 }, {
669 .fourcc = V4L2_PIX_FMT_NV61,
670 .bpp = 16,
671 .yuv = true,
672 .lddfr = LDDFR_CC | LDDFR_YF_422,
673 }, {
674 .fourcc = V4L2_PIX_FMT_NV24,
675 .bpp = 24,
676 .yuv = true,
677 .lddfr = LDDFR_CC | LDDFR_YF_444,
678 }, {
679 .fourcc = V4L2_PIX_FMT_NV42,
680 .bpp = 24,
681 .yuv = true,
682 .lddfr = LDDFR_CC | LDDFR_YF_444,
683 },
684};
685
686static const struct sh_mobile_lcdc_format_info *
687sh_mobile_format_info(u32 fourcc)
688{
689 unsigned int i;
690
691 for (i = 0; i < ARRAY_SIZE(sh_mobile_format_infos); ++i) {
692 if (sh_mobile_format_infos[i].fourcc == fourcc)
693 return &sh_mobile_format_infos[i];
694 }
695
696 return NULL;
697}
698
Laurent Pinchartf1f60b52011-09-07 11:09:26 +0200699static int sh_mobile_format_fourcc(const struct fb_var_screeninfo *var)
700{
701 if (var->grayscale > 1)
702 return var->grayscale;
703
704 switch (var->bits_per_pixel) {
705 case 16:
706 return V4L2_PIX_FMT_RGB565;
707 case 24:
708 return V4L2_PIX_FMT_BGR24;
709 case 32:
710 return V4L2_PIX_FMT_BGR32;
711 default:
712 return 0;
713 }
714}
715
716static int sh_mobile_format_is_fourcc(const struct fb_var_screeninfo *var)
717{
718 return var->grayscale > 1;
719}
720
Laurent Pinchartf1f60b52011-09-07 11:09:26 +0200721/* -----------------------------------------------------------------------------
722 * Start, stop and IRQ
723 */
724
Magnus Damm85645572008-12-19 15:34:41 +0900725static irqreturn_t sh_mobile_lcdc_irq(int irq, void *data)
726{
727 struct sh_mobile_lcdc_priv *priv = data;
Magnus Damm2feb0752009-03-13 15:36:55 +0000728 struct sh_mobile_lcdc_chan *ch;
Phil Edworthy9dd38812009-09-15 12:00:18 +0000729 unsigned long ldintr;
Magnus Damm2feb0752009-03-13 15:36:55 +0000730 int is_sub;
731 int k;
Magnus Damm85645572008-12-19 15:34:41 +0900732
Laurent Pinchartdc486652011-07-13 12:13:47 +0200733 /* Acknowledge interrupts and disable further VSYNC End IRQs. */
734 ldintr = lcdc_read(priv, _LDINTR);
735 lcdc_write(priv, _LDINTR, (ldintr ^ LDINTR_STATUS_MASK) & ~LDINTR_VEE);
Magnus Damm85645572008-12-19 15:34:41 +0900736
Magnus Damm2feb0752009-03-13 15:36:55 +0000737 /* figure out if this interrupt is for main or sub lcd */
Laurent Pinchartce1c0b02011-07-13 12:13:47 +0200738 is_sub = (lcdc_read(priv, _LDSR) & LDSR_MSS) ? 1 : 0;
Magnus Damm2feb0752009-03-13 15:36:55 +0000739
Phil Edworthy9dd38812009-09-15 12:00:18 +0000740 /* wake up channel and disable clocks */
Magnus Damm2feb0752009-03-13 15:36:55 +0000741 for (k = 0; k < ARRAY_SIZE(priv->ch); k++) {
742 ch = &priv->ch[k];
743
744 if (!ch->enabled)
745 continue;
746
Laurent Pinchartdc486652011-07-13 12:13:47 +0200747 /* Frame End */
Phil Edworthy9dd38812009-09-15 12:00:18 +0000748 if (ldintr & LDINTR_FS) {
749 if (is_sub == lcdc_chan_is_sublcd(ch)) {
750 ch->frame_end = 1;
751 wake_up(&ch->frame_end_wait);
Magnus Damm2feb0752009-03-13 15:36:55 +0000752
Phil Edworthy9dd38812009-09-15 12:00:18 +0000753 sh_mobile_lcdc_clk_off(priv);
754 }
755 }
756
757 /* VSYNC End */
Phil Edworthy40331b22010-02-15 13:57:49 +0000758 if (ldintr & LDINTR_VES)
759 complete(&ch->vsync_completion);
Magnus Damm2feb0752009-03-13 15:36:55 +0000760 }
761
Magnus Damm85645572008-12-19 15:34:41 +0900762 return IRQ_HANDLED;
763}
764
Laurent Pinchartd7ad3342011-11-22 00:56:58 +0100765static int sh_mobile_lcdc_wait_for_vsync(struct sh_mobile_lcdc_chan *ch)
Laurent Pinchart49766772011-11-30 23:07:30 +0100766{
767 unsigned long ldintr;
768 int ret;
769
770 /* Enable VSync End interrupt and be careful not to acknowledge any
771 * pending interrupt.
772 */
773 ldintr = lcdc_read(ch->lcdc, _LDINTR);
774 ldintr |= LDINTR_VEE | LDINTR_STATUS_MASK;
775 lcdc_write(ch->lcdc, _LDINTR, ldintr);
776
777 ret = wait_for_completion_interruptible_timeout(&ch->vsync_completion,
778 msecs_to_jiffies(100));
779 if (!ret)
780 return -ETIMEDOUT;
781
782 return 0;
783}
784
Magnus Dammcfb4f5d2008-07-23 21:31:24 -0700785static void sh_mobile_lcdc_start_stop(struct sh_mobile_lcdc_priv *priv,
786 int start)
787{
788 unsigned long tmp = lcdc_read(priv, _LDCNT2R);
789 int k;
790
791 /* start or stop the lcdc */
792 if (start)
Laurent Pinchartce1c0b02011-07-13 12:13:47 +0200793 lcdc_write(priv, _LDCNT2R, tmp | LDCNT2R_DO);
Magnus Dammcfb4f5d2008-07-23 21:31:24 -0700794 else
Laurent Pinchartce1c0b02011-07-13 12:13:47 +0200795 lcdc_write(priv, _LDCNT2R, tmp & ~LDCNT2R_DO);
Magnus Dammcfb4f5d2008-07-23 21:31:24 -0700796
797 /* wait until power is applied/stopped on all channels */
798 for (k = 0; k < ARRAY_SIZE(priv->ch); k++)
799 if (lcdc_read(priv, _LDCNT2R) & priv->ch[k].enabled)
800 while (1) {
Laurent Pinchartce1c0b02011-07-13 12:13:47 +0200801 tmp = lcdc_read_chan(&priv->ch[k], LDPMR)
802 & LDPMR_LPS;
803 if (start && tmp == LDPMR_LPS)
Magnus Dammcfb4f5d2008-07-23 21:31:24 -0700804 break;
805 if (!start && tmp == 0)
806 break;
807 cpu_relax();
808 }
809
810 if (!start)
811 lcdc_write(priv, _LDDCKSTPR, 1); /* stop dotclock */
812}
813
Guennadi Liakhovetski6011bde2010-07-21 10:13:21 +0000814static void sh_mobile_lcdc_geometry(struct sh_mobile_lcdc_chan *ch)
815{
Laurent Pinchart2d045592011-11-29 13:42:48 +0100816 const struct fb_var_screeninfo *var = &ch->info->var;
817 const struct fb_videomode *mode = &ch->display.mode;
Guennadi Liakhovetski1c120de2010-09-03 07:20:27 +0000818 unsigned long h_total, hsync_pos, display_h_total;
Guennadi Liakhovetski6011bde2010-07-21 10:13:21 +0000819 u32 tmp;
820
821 tmp = ch->ldmt1r_value;
Laurent Pinchartce1c0b02011-07-13 12:13:47 +0200822 tmp |= (var->sync & FB_SYNC_VERT_HIGH_ACT) ? 0 : LDMT1R_VPOL;
823 tmp |= (var->sync & FB_SYNC_HOR_HIGH_ACT) ? 0 : LDMT1R_HPOL;
Laurent Pinchartb5ef9672011-11-22 00:56:58 +0100824 tmp |= (ch->cfg->flags & LCDC_FLAGS_DWPOL) ? LDMT1R_DWPOL : 0;
825 tmp |= (ch->cfg->flags & LCDC_FLAGS_DIPOL) ? LDMT1R_DIPOL : 0;
826 tmp |= (ch->cfg->flags & LCDC_FLAGS_DAPOL) ? LDMT1R_DAPOL : 0;
827 tmp |= (ch->cfg->flags & LCDC_FLAGS_HSCNT) ? LDMT1R_HSCNT : 0;
828 tmp |= (ch->cfg->flags & LCDC_FLAGS_DWCNT) ? LDMT1R_DWCNT : 0;
Guennadi Liakhovetski6011bde2010-07-21 10:13:21 +0000829 lcdc_write_chan(ch, LDMT1R, tmp);
830
831 /* setup SYS bus */
Laurent Pinchartb5ef9672011-11-22 00:56:58 +0100832 lcdc_write_chan(ch, LDMT2R, ch->cfg->sys_bus_cfg.ldmt2r);
833 lcdc_write_chan(ch, LDMT3R, ch->cfg->sys_bus_cfg.ldmt3r);
Guennadi Liakhovetski6011bde2010-07-21 10:13:21 +0000834
835 /* horizontal configuration */
Laurent Pinchart2d045592011-11-29 13:42:48 +0100836 h_total = mode->xres + mode->hsync_len + mode->left_margin
837 + mode->right_margin;
Guennadi Liakhovetski6011bde2010-07-21 10:13:21 +0000838 tmp = h_total / 8; /* HTCN */
Laurent Pinchart58f03d92011-11-30 23:07:30 +0100839 tmp |= (min(mode->xres, ch->xres) / 8) << 16; /* HDCN */
Guennadi Liakhovetski6011bde2010-07-21 10:13:21 +0000840 lcdc_write_chan(ch, LDHCNR, tmp);
841
Laurent Pinchart2d045592011-11-29 13:42:48 +0100842 hsync_pos = mode->xres + mode->right_margin;
Guennadi Liakhovetski6011bde2010-07-21 10:13:21 +0000843 tmp = hsync_pos / 8; /* HSYNP */
Laurent Pinchart2d045592011-11-29 13:42:48 +0100844 tmp |= (mode->hsync_len / 8) << 16; /* HSYNW */
Guennadi Liakhovetski6011bde2010-07-21 10:13:21 +0000845 lcdc_write_chan(ch, LDHSYNR, tmp);
846
847 /* vertical configuration */
Laurent Pinchart2d045592011-11-29 13:42:48 +0100848 tmp = mode->yres + mode->vsync_len + mode->upper_margin
849 + mode->lower_margin; /* VTLN */
Laurent Pinchart58f03d92011-11-30 23:07:30 +0100850 tmp |= min(mode->yres, ch->yres) << 16; /* VDLN */
Guennadi Liakhovetski6011bde2010-07-21 10:13:21 +0000851 lcdc_write_chan(ch, LDVLNR, tmp);
852
Laurent Pinchart2d045592011-11-29 13:42:48 +0100853 tmp = mode->yres + mode->lower_margin; /* VSYNP */
854 tmp |= mode->vsync_len << 16; /* VSYNW */
Guennadi Liakhovetski6011bde2010-07-21 10:13:21 +0000855 lcdc_write_chan(ch, LDVSYNR, tmp);
856
857 /* Adjust horizontal synchronisation for HDMI */
Laurent Pinchart2d045592011-11-29 13:42:48 +0100858 display_h_total = mode->xres + mode->hsync_len + mode->left_margin
859 + mode->right_margin;
860 tmp = ((mode->xres & 7) << 24) | ((display_h_total & 7) << 16)
861 | ((mode->hsync_len & 7) << 8) | (hsync_pos & 7);
Guennadi Liakhovetski6011bde2010-07-21 10:13:21 +0000862 lcdc_write_chan(ch, LDHAJR, tmp);
Kuninori Morimoto9beb09f2013-03-04 20:44:39 -0800863 lcdc_write_chan_mirror(ch, LDHAJR, tmp);
Guennadi Liakhovetski6011bde2010-07-21 10:13:21 +0000864}
865
Laurent Pinchartc5deac32011-12-12 18:43:16 +0100866static void sh_mobile_lcdc_overlay_setup(struct sh_mobile_lcdc_overlay *ovl)
867{
868 u32 format = 0;
869
870 if (!ovl->enabled) {
871 lcdc_write(ovl->channel->lcdc, LDBCR, LDBCR_UPC(ovl->index));
872 lcdc_write_overlay(ovl, LDBnBSIFR(ovl->index), 0);
873 lcdc_write(ovl->channel->lcdc, LDBCR,
874 LDBCR_UPF(ovl->index) | LDBCR_UPD(ovl->index));
875 return;
876 }
877
878 ovl->base_addr_y = ovl->dma_handle;
Laurent Pincharta4aa25f2012-03-15 18:15:37 +0100879 ovl->base_addr_c = ovl->dma_handle
880 + ovl->xres_virtual * ovl->yres_virtual;
Laurent Pinchartc5deac32011-12-12 18:43:16 +0100881
882 switch (ovl->mode) {
883 case LCDC_OVERLAY_BLEND:
884 format = LDBBSIFR_EN | (ovl->alpha << LDBBSIFR_LAY_SHIFT);
885 break;
886
887 case LCDC_OVERLAY_ROP3:
888 format = LDBBSIFR_EN | LDBBSIFR_BRSEL
889 | (ovl->rop3 << LDBBSIFR_ROP3_SHIFT);
890 break;
891 }
892
893 switch (ovl->format->fourcc) {
894 case V4L2_PIX_FMT_RGB565:
895 case V4L2_PIX_FMT_NV21:
896 case V4L2_PIX_FMT_NV61:
897 case V4L2_PIX_FMT_NV42:
898 format |= LDBBSIFR_SWPL | LDBBSIFR_SWPW;
899 break;
900 case V4L2_PIX_FMT_BGR24:
901 case V4L2_PIX_FMT_NV12:
902 case V4L2_PIX_FMT_NV16:
903 case V4L2_PIX_FMT_NV24:
904 format |= LDBBSIFR_SWPL | LDBBSIFR_SWPW | LDBBSIFR_SWPB;
905 break;
906 case V4L2_PIX_FMT_BGR32:
907 default:
908 format |= LDBBSIFR_SWPL;
909 break;
910 }
911
912 switch (ovl->format->fourcc) {
913 case V4L2_PIX_FMT_RGB565:
914 format |= LDBBSIFR_AL_1 | LDBBSIFR_RY | LDBBSIFR_RPKF_RGB16;
915 break;
916 case V4L2_PIX_FMT_BGR24:
917 format |= LDBBSIFR_AL_1 | LDBBSIFR_RY | LDBBSIFR_RPKF_RGB24;
918 break;
919 case V4L2_PIX_FMT_BGR32:
920 format |= LDBBSIFR_AL_PK | LDBBSIFR_RY | LDDFR_PKF_ARGB32;
921 break;
922 case V4L2_PIX_FMT_NV12:
923 case V4L2_PIX_FMT_NV21:
924 format |= LDBBSIFR_AL_1 | LDBBSIFR_CHRR_420;
925 break;
926 case V4L2_PIX_FMT_NV16:
927 case V4L2_PIX_FMT_NV61:
928 format |= LDBBSIFR_AL_1 | LDBBSIFR_CHRR_422;
929 break;
930 case V4L2_PIX_FMT_NV24:
931 case V4L2_PIX_FMT_NV42:
932 format |= LDBBSIFR_AL_1 | LDBBSIFR_CHRR_444;
933 break;
934 }
935
936 lcdc_write(ovl->channel->lcdc, LDBCR, LDBCR_UPC(ovl->index));
937
938 lcdc_write_overlay(ovl, LDBnBSIFR(ovl->index), format);
939
940 lcdc_write_overlay(ovl, LDBnBSSZR(ovl->index),
941 (ovl->yres << LDBBSSZR_BVSS_SHIFT) |
942 (ovl->xres << LDBBSSZR_BHSS_SHIFT));
943 lcdc_write_overlay(ovl, LDBnBLOCR(ovl->index),
944 (ovl->pos_y << LDBBLOCR_CVLC_SHIFT) |
945 (ovl->pos_x << LDBBLOCR_CHLC_SHIFT));
946 lcdc_write_overlay(ovl, LDBnBSMWR(ovl->index),
947 ovl->pitch << LDBBSMWR_BSMW_SHIFT);
948
949 lcdc_write_overlay(ovl, LDBnBSAYR(ovl->index), ovl->base_addr_y);
950 lcdc_write_overlay(ovl, LDBnBSACR(ovl->index), ovl->base_addr_c);
951
952 lcdc_write(ovl->channel->lcdc, LDBCR,
953 LDBCR_UPF(ovl->index) | LDBCR_UPD(ovl->index));
954}
955
Laurent Pinchart9a217e32011-07-13 12:13:47 +0200956/*
Laurent Pinchartd7ad3342011-11-22 00:56:58 +0100957 * __sh_mobile_lcdc_start - Configure and start the LCDC
Laurent Pinchart9a217e32011-07-13 12:13:47 +0200958 * @priv: LCDC device
959 *
960 * Configure all enabled channels and start the LCDC device. All external
961 * devices (clocks, MERAM, panels, ...) are not touched by this function.
962 */
963static void __sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv)
Magnus Dammcfb4f5d2008-07-23 21:31:24 -0700964{
965 struct sh_mobile_lcdc_chan *ch;
Magnus Dammcfb4f5d2008-07-23 21:31:24 -0700966 unsigned long tmp;
Laurent Pinchart9a217e32011-07-13 12:13:47 +0200967 int k, m;
Magnus Dammcfb4f5d2008-07-23 21:31:24 -0700968
Laurent Pinchart9a217e32011-07-13 12:13:47 +0200969 /* Enable LCDC channels. Read data from external memory, avoid using the
970 * BEU for now.
971 */
972 lcdc_write(priv, _LDCNT2R, priv->ch[0].enabled | priv->ch[1].enabled);
Magnus Damm85645572008-12-19 15:34:41 +0900973
Laurent Pinchart9a217e32011-07-13 12:13:47 +0200974 /* Stop the LCDC first and disable all interrupts. */
Magnus Dammcfb4f5d2008-07-23 21:31:24 -0700975 sh_mobile_lcdc_start_stop(priv, 0);
Laurent Pinchart9a217e32011-07-13 12:13:47 +0200976 lcdc_write(priv, _LDINTR, 0);
Magnus Dammcfb4f5d2008-07-23 21:31:24 -0700977
Laurent Pinchart9a217e32011-07-13 12:13:47 +0200978 /* Configure power supply, dot clocks and start them. */
Magnus Dammcfb4f5d2008-07-23 21:31:24 -0700979 tmp = priv->lddckr;
980 for (k = 0; k < ARRAY_SIZE(priv->ch); k++) {
981 ch = &priv->ch[k];
Laurent Pinchart9a217e32011-07-13 12:13:47 +0200982 if (!ch->enabled)
Magnus Dammcfb4f5d2008-07-23 21:31:24 -0700983 continue;
984
Laurent Pinchart9a217e32011-07-13 12:13:47 +0200985 /* Power supply */
986 lcdc_write_chan(ch, LDPMR, 0);
987
Laurent Pinchartb5ef9672011-11-22 00:56:58 +0100988 m = ch->cfg->clock_divider;
Magnus Dammcfb4f5d2008-07-23 21:31:24 -0700989 if (!m)
990 continue;
991
Laurent Pinchart505c7de52011-07-13 12:13:47 +0200992 /* FIXME: sh7724 can only use 42, 48, 54 and 60 for the divider
993 * denominator.
994 */
995 lcdc_write_chan(ch, LDDCKPAT1R, 0);
996 lcdc_write_chan(ch, LDDCKPAT2R, (1 << (m/2)) - 1);
997
Magnus Dammcfb4f5d2008-07-23 21:31:24 -0700998 if (m == 1)
Laurent Pinchartce1c0b02011-07-13 12:13:47 +0200999 m = LDDCKR_MOSEL;
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07001000 tmp |= m << (lcdc_chan_is_sublcd(ch) ? 8 : 0);
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07001001 }
1002
1003 lcdc_write(priv, _LDDCKR, tmp);
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07001004 lcdc_write(priv, _LDDCKSTPR, 0);
1005 lcdc_wait_bit(priv, _LDDCKSTPR, ~0, 0);
1006
Laurent Pinchart9a217e32011-07-13 12:13:47 +02001007 /* Setup geometry, format, frame buffer memory and operation mode. */
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07001008 for (k = 0; k < ARRAY_SIZE(priv->ch); k++) {
1009 ch = &priv->ch[k];
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07001010 if (!ch->enabled)
1011 continue;
1012
Guennadi Liakhovetski6011bde2010-07-21 10:13:21 +00001013 sh_mobile_lcdc_geometry(ch);
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07001014
Laurent Pinchartfc9e78e2011-11-29 16:05:36 +01001015 tmp = ch->format->lddfr;
Laurent Pinchartedd153a2011-12-13 14:02:28 +01001016
Laurent Pinchartfc9e78e2011-11-29 16:05:36 +01001017 if (ch->format->yuv) {
Laurent Pinchart58f03d92011-11-30 23:07:30 +01001018 switch (ch->colorspace) {
Laurent Pinchartedd153a2011-12-13 14:02:28 +01001019 case V4L2_COLORSPACE_REC709:
1020 tmp |= LDDFR_CF1;
Damian Hobson-Garcia53b50312011-02-24 05:47:13 +00001021 break;
Laurent Pinchartedd153a2011-12-13 14:02:28 +01001022 case V4L2_COLORSPACE_JPEG:
1023 tmp |= LDDFR_CF0;
Damian Hobson-Garcia53b50312011-02-24 05:47:13 +00001024 break;
1025 }
Magnus Damm417d4822011-01-05 10:21:00 +00001026 }
Laurent Pinchart9a217e32011-07-13 12:13:47 +02001027
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07001028 lcdc_write_chan(ch, LDDFR, tmp);
Laurent Pinchart72c04af2012-05-18 10:58:26 +02001029 lcdc_write_chan(ch, LDMLSR, ch->line_size);
Laurent Pinchart9a217e32011-07-13 12:13:47 +02001030 lcdc_write_chan(ch, LDSA1R, ch->base_addr_y);
Laurent Pinchartfc9e78e2011-11-29 16:05:36 +01001031 if (ch->format->yuv)
Laurent Pinchart9a217e32011-07-13 12:13:47 +02001032 lcdc_write_chan(ch, LDSA2R, ch->base_addr_c);
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07001033
Laurent Pinchart9a217e32011-07-13 12:13:47 +02001034 /* When using deferred I/O mode, configure the LCDC for one-shot
1035 * operation and enable the frame end interrupt. Otherwise use
1036 * continuous read mode.
1037 */
1038 if (ch->ldmt1r_value & LDMT1R_IFM &&
Laurent Pinchartb5ef9672011-11-22 00:56:58 +01001039 ch->cfg->sys_bus_cfg.deferred_io_msec) {
Laurent Pinchart9a217e32011-07-13 12:13:47 +02001040 lcdc_write_chan(ch, LDSM1R, LDSM1R_OS);
1041 lcdc_write(priv, _LDINTR, LDINTR_FE);
1042 } else {
1043 lcdc_write_chan(ch, LDSM1R, 0);
1044 }
1045 }
Damian7caa4342011-05-18 11:10:07 +00001046
Laurent Pinchart9a217e32011-07-13 12:13:47 +02001047 /* Word and long word swap. */
Laurent Pinchartfc9e78e2011-11-29 16:05:36 +01001048 switch (priv->ch[0].format->fourcc) {
Laurent Pinchartedd153a2011-12-13 14:02:28 +01001049 case V4L2_PIX_FMT_RGB565:
1050 case V4L2_PIX_FMT_NV21:
1051 case V4L2_PIX_FMT_NV61:
1052 case V4L2_PIX_FMT_NV42:
1053 tmp = LDDDSR_LS | LDDDSR_WS;
1054 break;
1055 case V4L2_PIX_FMT_BGR24:
1056 case V4L2_PIX_FMT_NV12:
1057 case V4L2_PIX_FMT_NV16:
1058 case V4L2_PIX_FMT_NV24:
Laurent Pinchart9a217e32011-07-13 12:13:47 +02001059 tmp = LDDDSR_LS | LDDDSR_WS | LDDDSR_BS;
Laurent Pinchartedd153a2011-12-13 14:02:28 +01001060 break;
1061 case V4L2_PIX_FMT_BGR32:
1062 default:
1063 tmp = LDDDSR_LS;
1064 break;
Laurent Pinchart9a217e32011-07-13 12:13:47 +02001065 }
1066 lcdc_write(priv, _LDDDSR, tmp);
Damian7caa4342011-05-18 11:10:07 +00001067
Laurent Pinchart9a217e32011-07-13 12:13:47 +02001068 /* Enable the display output. */
1069 lcdc_write(priv, _LDCNT1R, LDCNT1R_DE);
1070 sh_mobile_lcdc_start_stop(priv, 1);
1071 priv->started = 1;
1072}
Damian7caa4342011-05-18 11:10:07 +00001073
Laurent Pinchart9a217e32011-07-13 12:13:47 +02001074static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv)
1075{
1076 struct sh_mobile_meram_info *mdev = priv->meram_dev;
Laurent Pinchart9a217e32011-07-13 12:13:47 +02001077 struct sh_mobile_lcdc_chan *ch;
1078 unsigned long tmp;
1079 int ret;
1080 int k;
1081
1082 /* enable clocks before accessing the hardware */
1083 for (k = 0; k < ARRAY_SIZE(priv->ch); k++) {
1084 if (priv->ch[k].enabled)
1085 sh_mobile_lcdc_clk_on(priv);
1086 }
1087
1088 /* reset */
1089 lcdc_write(priv, _LDCNT2R, lcdc_read(priv, _LDCNT2R) | LDCNT2R_BR);
1090 lcdc_wait_bit(priv, _LDCNT2R, LDCNT2R_BR, 0);
1091
1092 for (k = 0; k < ARRAY_SIZE(priv->ch); k++) {
Laurent Pinchartb5ef9672011-11-22 00:56:58 +01001093 const struct sh_mobile_lcdc_panel_cfg *panel;
Laurent Pinchart9a217e32011-07-13 12:13:47 +02001094
Laurent Pinchart37c5dcc2011-09-09 15:45:43 +02001095 ch = &priv->ch[k];
Laurent Pinchart9a217e32011-07-13 12:13:47 +02001096 if (!ch->enabled)
1097 continue;
1098
Laurent Pinchartb5ef9672011-11-22 00:56:58 +01001099 panel = &ch->cfg->panel_cfg;
Laurent Pinchartafaad832011-09-11 22:59:04 +02001100 if (panel->setup_sys) {
1101 ret = panel->setup_sys(ch, &sh_mobile_lcdc_sys_bus_ops);
Laurent Pinchart9a217e32011-07-13 12:13:47 +02001102 if (ret)
1103 return ret;
1104 }
1105 }
1106
1107 /* Compute frame buffer base address and pitch for each channel. */
1108 for (k = 0; k < ARRAY_SIZE(priv->ch); k++) {
Laurent Pinchart9a217e32011-07-13 12:13:47 +02001109 int pixelformat;
Laurent Pinchart4a2371772012-03-15 12:40:47 +01001110 void *cache;
Laurent Pinchart9a217e32011-07-13 12:13:47 +02001111
1112 ch = &priv->ch[k];
1113 if (!ch->enabled)
1114 continue;
1115
Laurent Pinchart58f03d92011-11-30 23:07:30 +01001116 ch->base_addr_y = ch->dma_handle;
Laurent Pincharta4aa25f2012-03-15 18:15:37 +01001117 ch->base_addr_c = ch->dma_handle
1118 + ch->xres_virtual * ch->yres_virtual;
Laurent Pinchart72c04af2012-05-18 10:58:26 +02001119 ch->line_size = ch->pitch;
Laurent Pinchart9a217e32011-07-13 12:13:47 +02001120
1121 /* Enable MERAM if possible. */
Laurent Pinchart6e729b42012-03-15 12:40:47 +01001122 if (mdev == NULL || ch->cfg->meram_cfg == NULL)
Laurent Pinchart9a217e32011-07-13 12:13:47 +02001123 continue;
1124
Laurent Pinchart4a2371772012-03-15 12:40:47 +01001125 /* Free the allocated MERAM cache. */
1126 if (ch->cache) {
Laurent Pinchart6e729b42012-03-15 12:40:47 +01001127 sh_mobile_meram_cache_free(mdev, ch->cache);
Laurent Pinchart4a2371772012-03-15 12:40:47 +01001128 ch->cache = NULL;
Damian7caa4342011-05-18 11:10:07 +00001129 }
1130
Laurent Pinchartfc9e78e2011-11-29 16:05:36 +01001131 switch (ch->format->fourcc) {
Laurent Pinchartedd153a2011-12-13 14:02:28 +01001132 case V4L2_PIX_FMT_NV12:
1133 case V4L2_PIX_FMT_NV21:
1134 case V4L2_PIX_FMT_NV16:
1135 case V4L2_PIX_FMT_NV61:
Laurent Pinchart9a217e32011-07-13 12:13:47 +02001136 pixelformat = SH_MOBILE_MERAM_PF_NV;
Laurent Pinchartedd153a2011-12-13 14:02:28 +01001137 break;
1138 case V4L2_PIX_FMT_NV24:
1139 case V4L2_PIX_FMT_NV42:
1140 pixelformat = SH_MOBILE_MERAM_PF_NV24;
1141 break;
1142 case V4L2_PIX_FMT_RGB565:
1143 case V4L2_PIX_FMT_BGR24:
1144 case V4L2_PIX_FMT_BGR32:
1145 default:
1146 pixelformat = SH_MOBILE_MERAM_PF_RGB;
1147 break;
1148 }
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07001149
Laurent Pinchart6e729b42012-03-15 12:40:47 +01001150 cache = sh_mobile_meram_cache_alloc(mdev, ch->cfg->meram_cfg,
Laurent Pinchartb5ef9672011-11-22 00:56:58 +01001151 ch->pitch, ch->yres, pixelformat,
Laurent Pinchart72c04af2012-05-18 10:58:26 +02001152 &ch->line_size);
Laurent Pinchart4a2371772012-03-15 12:40:47 +01001153 if (!IS_ERR(cache)) {
Laurent Pinchart6e729b42012-03-15 12:40:47 +01001154 sh_mobile_meram_cache_update(mdev, cache,
Laurent Pinchart97d16fe2011-11-22 00:56:58 +01001155 ch->base_addr_y, ch->base_addr_c,
1156 &ch->base_addr_y, &ch->base_addr_c);
Laurent Pinchart4a2371772012-03-15 12:40:47 +01001157 ch->cache = cache;
Laurent Pinchart97d16fe2011-11-22 00:56:58 +01001158 }
Laurent Pinchart9a217e32011-07-13 12:13:47 +02001159 }
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07001160
Laurent Pinchartc5deac32011-12-12 18:43:16 +01001161 for (k = 0; k < ARRAY_SIZE(priv->overlays); ++k) {
1162 struct sh_mobile_lcdc_overlay *ovl = &priv->overlays[k];
1163 sh_mobile_lcdc_overlay_setup(ovl);
1164 }
1165
Laurent Pinchart9a217e32011-07-13 12:13:47 +02001166 /* Start the LCDC. */
1167 __sh_mobile_lcdc_start(priv);
1168
1169 /* Setup deferred I/O, tell the board code to enable the panels, and
1170 * turn backlight on.
1171 */
1172 for (k = 0; k < ARRAY_SIZE(priv->ch); k++) {
1173 ch = &priv->ch[k];
1174 if (!ch->enabled)
1175 continue;
1176
Laurent Pinchartb5ef9672011-11-22 00:56:58 +01001177 tmp = ch->cfg->sys_bus_cfg.deferred_io_msec;
Laurent Pinchartce1c0b02011-07-13 12:13:47 +02001178 if (ch->ldmt1r_value & LDMT1R_IFM && tmp) {
Magnus Damm85645572008-12-19 15:34:41 +09001179 ch->defio.deferred_io = sh_mobile_lcdc_deferred_io;
1180 ch->defio.delay = msecs_to_jiffies(tmp);
Paul Mundte33afdd2009-07-07 11:24:32 +09001181 ch->info->fbdefio = &ch->defio;
1182 fb_deferred_io_init(ch->info);
Magnus Damm85645572008-12-19 15:34:41 +09001183 }
Magnus Damm21bc1f02009-08-15 02:53:16 +00001184
Laurent Pinchart37c5dcc2011-09-09 15:45:43 +02001185 sh_mobile_lcdc_display_on(ch);
Alexandre Courbot3b0fd9d2011-02-16 03:49:01 +00001186
1187 if (ch->bl) {
1188 ch->bl->props.power = FB_BLANK_UNBLANK;
1189 backlight_update_status(ch->bl);
1190 }
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07001191 }
1192
1193 return 0;
1194}
1195
1196static void sh_mobile_lcdc_stop(struct sh_mobile_lcdc_priv *priv)
1197{
1198 struct sh_mobile_lcdc_chan *ch;
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07001199 int k;
1200
Magnus Damm2feb0752009-03-13 15:36:55 +00001201 /* clean up deferred io and ask board code to disable panel */
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07001202 for (k = 0; k < ARRAY_SIZE(priv->ch); k++) {
1203 ch = &priv->ch[k];
Magnus Damm21bc1f02009-08-15 02:53:16 +00001204 if (!ch->enabled)
1205 continue;
Magnus Damm2feb0752009-03-13 15:36:55 +00001206
1207 /* deferred io mode:
1208 * flush frame, and wait for frame end interrupt
1209 * clean up deferred io and enable clock
1210 */
Guennadi Liakhovetski5ef6b502010-09-03 07:19:57 +00001211 if (ch->info && ch->info->fbdefio) {
Magnus Damm2feb0752009-03-13 15:36:55 +00001212 ch->frame_end = 0;
Paul Mundte33afdd2009-07-07 11:24:32 +09001213 schedule_delayed_work(&ch->info->deferred_work, 0);
Magnus Damm2feb0752009-03-13 15:36:55 +00001214 wait_event(ch->frame_end_wait, ch->frame_end);
Paul Mundte33afdd2009-07-07 11:24:32 +09001215 fb_deferred_io_cleanup(ch->info);
1216 ch->info->fbdefio = NULL;
Magnus Damm2feb0752009-03-13 15:36:55 +00001217 sh_mobile_lcdc_clk_on(priv);
1218 }
1219
Alexandre Courbot3b0fd9d2011-02-16 03:49:01 +00001220 if (ch->bl) {
1221 ch->bl->props.power = FB_BLANK_POWERDOWN;
1222 backlight_update_status(ch->bl);
1223 }
1224
Laurent Pinchart37c5dcc2011-09-09 15:45:43 +02001225 sh_mobile_lcdc_display_off(ch);
Damian7caa4342011-05-18 11:10:07 +00001226
Laurent Pinchart4a2371772012-03-15 12:40:47 +01001227 /* Free the MERAM cache. */
1228 if (ch->cache) {
Laurent Pinchart6e729b42012-03-15 12:40:47 +01001229 sh_mobile_meram_cache_free(priv->meram_dev, ch->cache);
Laurent Pinchart40af1eb2013-11-27 01:38:43 +01001230 ch->cache = NULL;
Damian7caa4342011-05-18 11:10:07 +00001231 }
1232
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07001233 }
1234
1235 /* stop the lcdc */
Magnus Damm8e9bb192009-05-20 14:34:43 +00001236 if (priv->started) {
1237 sh_mobile_lcdc_start_stop(priv, 0);
1238 priv->started = 0;
1239 }
Magnus Dammb51339f2008-10-31 20:23:26 +09001240
Magnus Damm85645572008-12-19 15:34:41 +09001241 /* stop clocks */
1242 for (k = 0; k < ARRAY_SIZE(priv->ch); k++)
1243 if (priv->ch[k].enabled)
1244 sh_mobile_lcdc_clk_off(priv);
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07001245}
1246
Laurent Pinchartc5deac32011-12-12 18:43:16 +01001247static int __sh_mobile_lcdc_check_var(struct fb_var_screeninfo *var,
1248 struct fb_info *info)
1249{
1250 if (var->xres > MAX_XRES || var->yres > MAX_YRES)
1251 return -EINVAL;
1252
1253 /* Make sure the virtual resolution is at least as big as the visible
1254 * resolution.
1255 */
1256 if (var->xres_virtual < var->xres)
1257 var->xres_virtual = var->xres;
1258 if (var->yres_virtual < var->yres)
1259 var->yres_virtual = var->yres;
1260
1261 if (sh_mobile_format_is_fourcc(var)) {
1262 const struct sh_mobile_lcdc_format_info *format;
1263
1264 format = sh_mobile_format_info(var->grayscale);
1265 if (format == NULL)
1266 return -EINVAL;
1267 var->bits_per_pixel = format->bpp;
1268
1269 /* Default to RGB and JPEG color-spaces for RGB and YUV formats
1270 * respectively.
1271 */
1272 if (!format->yuv)
1273 var->colorspace = V4L2_COLORSPACE_SRGB;
1274 else if (var->colorspace != V4L2_COLORSPACE_REC709)
1275 var->colorspace = V4L2_COLORSPACE_JPEG;
1276 } else {
1277 if (var->bits_per_pixel <= 16) { /* RGB 565 */
1278 var->bits_per_pixel = 16;
1279 var->red.offset = 11;
1280 var->red.length = 5;
1281 var->green.offset = 5;
1282 var->green.length = 6;
1283 var->blue.offset = 0;
1284 var->blue.length = 5;
1285 var->transp.offset = 0;
1286 var->transp.length = 0;
1287 } else if (var->bits_per_pixel <= 24) { /* RGB 888 */
1288 var->bits_per_pixel = 24;
1289 var->red.offset = 16;
1290 var->red.length = 8;
1291 var->green.offset = 8;
1292 var->green.length = 8;
1293 var->blue.offset = 0;
1294 var->blue.length = 8;
1295 var->transp.offset = 0;
1296 var->transp.length = 0;
1297 } else if (var->bits_per_pixel <= 32) { /* RGBA 888 */
1298 var->bits_per_pixel = 32;
1299 var->red.offset = 16;
1300 var->red.length = 8;
1301 var->green.offset = 8;
1302 var->green.length = 8;
1303 var->blue.offset = 0;
1304 var->blue.length = 8;
1305 var->transp.offset = 24;
1306 var->transp.length = 8;
1307 } else
1308 return -EINVAL;
1309
1310 var->red.msb_right = 0;
1311 var->green.msb_right = 0;
1312 var->blue.msb_right = 0;
1313 var->transp.msb_right = 0;
1314 }
1315
1316 /* Make sure we don't exceed our allocated memory. */
1317 if (var->xres_virtual * var->yres_virtual * var->bits_per_pixel / 8 >
1318 info->fix.smem_len)
1319 return -EINVAL;
1320
1321 return 0;
1322}
1323
Laurent Pinchartf1f60b52011-09-07 11:09:26 +02001324/* -----------------------------------------------------------------------------
Laurent Pinchartc5deac32011-12-12 18:43:16 +01001325 * Frame buffer operations - Overlays
1326 */
1327
1328static ssize_t
1329overlay_alpha_show(struct device *dev, struct device_attribute *attr, char *buf)
1330{
1331 struct fb_info *info = dev_get_drvdata(dev);
1332 struct sh_mobile_lcdc_overlay *ovl = info->par;
1333
1334 return scnprintf(buf, PAGE_SIZE, "%u\n", ovl->alpha);
1335}
1336
1337static ssize_t
1338overlay_alpha_store(struct device *dev, struct device_attribute *attr,
1339 const char *buf, size_t count)
1340{
1341 struct fb_info *info = dev_get_drvdata(dev);
1342 struct sh_mobile_lcdc_overlay *ovl = info->par;
1343 unsigned int alpha;
1344 char *endp;
1345
1346 alpha = simple_strtoul(buf, &endp, 10);
1347 if (isspace(*endp))
1348 endp++;
1349
1350 if (endp - buf != count)
1351 return -EINVAL;
1352
1353 if (alpha > 255)
1354 return -EINVAL;
1355
1356 if (ovl->alpha != alpha) {
1357 ovl->alpha = alpha;
1358
1359 if (ovl->mode == LCDC_OVERLAY_BLEND && ovl->enabled)
1360 sh_mobile_lcdc_overlay_setup(ovl);
1361 }
1362
1363 return count;
1364}
1365
1366static ssize_t
1367overlay_mode_show(struct device *dev, struct device_attribute *attr, char *buf)
1368{
1369 struct fb_info *info = dev_get_drvdata(dev);
1370 struct sh_mobile_lcdc_overlay *ovl = info->par;
1371
1372 return scnprintf(buf, PAGE_SIZE, "%u\n", ovl->mode);
1373}
1374
1375static ssize_t
1376overlay_mode_store(struct device *dev, struct device_attribute *attr,
1377 const char *buf, size_t count)
1378{
1379 struct fb_info *info = dev_get_drvdata(dev);
1380 struct sh_mobile_lcdc_overlay *ovl = info->par;
1381 unsigned int mode;
1382 char *endp;
1383
1384 mode = simple_strtoul(buf, &endp, 10);
1385 if (isspace(*endp))
1386 endp++;
1387
1388 if (endp - buf != count)
1389 return -EINVAL;
1390
1391 if (mode != LCDC_OVERLAY_BLEND && mode != LCDC_OVERLAY_ROP3)
1392 return -EINVAL;
1393
1394 if (ovl->mode != mode) {
1395 ovl->mode = mode;
1396
1397 if (ovl->enabled)
1398 sh_mobile_lcdc_overlay_setup(ovl);
1399 }
1400
1401 return count;
1402}
1403
1404static ssize_t
1405overlay_position_show(struct device *dev, struct device_attribute *attr,
1406 char *buf)
1407{
1408 struct fb_info *info = dev_get_drvdata(dev);
1409 struct sh_mobile_lcdc_overlay *ovl = info->par;
1410
1411 return scnprintf(buf, PAGE_SIZE, "%d,%d\n", ovl->pos_x, ovl->pos_y);
1412}
1413
1414static ssize_t
1415overlay_position_store(struct device *dev, struct device_attribute *attr,
1416 const char *buf, size_t count)
1417{
1418 struct fb_info *info = dev_get_drvdata(dev);
1419 struct sh_mobile_lcdc_overlay *ovl = info->par;
1420 char *endp;
1421 int pos_x;
1422 int pos_y;
1423
1424 pos_x = simple_strtol(buf, &endp, 10);
1425 if (*endp != ',')
1426 return -EINVAL;
1427
1428 pos_y = simple_strtol(endp + 1, &endp, 10);
1429 if (isspace(*endp))
1430 endp++;
1431
1432 if (endp - buf != count)
1433 return -EINVAL;
1434
1435 if (ovl->pos_x != pos_x || ovl->pos_y != pos_y) {
1436 ovl->pos_x = pos_x;
1437 ovl->pos_y = pos_y;
1438
1439 if (ovl->enabled)
1440 sh_mobile_lcdc_overlay_setup(ovl);
1441 }
1442
1443 return count;
1444}
1445
1446static ssize_t
1447overlay_rop3_show(struct device *dev, struct device_attribute *attr, char *buf)
1448{
1449 struct fb_info *info = dev_get_drvdata(dev);
1450 struct sh_mobile_lcdc_overlay *ovl = info->par;
1451
1452 return scnprintf(buf, PAGE_SIZE, "%u\n", ovl->rop3);
1453}
1454
1455static ssize_t
1456overlay_rop3_store(struct device *dev, struct device_attribute *attr,
1457 const char *buf, size_t count)
1458{
1459 struct fb_info *info = dev_get_drvdata(dev);
1460 struct sh_mobile_lcdc_overlay *ovl = info->par;
1461 unsigned int rop3;
1462 char *endp;
1463
1464 rop3 = !!simple_strtoul(buf, &endp, 10);
1465 if (isspace(*endp))
1466 endp++;
1467
1468 if (endp - buf != count)
1469 return -EINVAL;
1470
1471 if (rop3 > 255)
1472 return -EINVAL;
1473
1474 if (ovl->rop3 != rop3) {
1475 ovl->rop3 = rop3;
1476
1477 if (ovl->mode == LCDC_OVERLAY_ROP3 && ovl->enabled)
1478 sh_mobile_lcdc_overlay_setup(ovl);
1479 }
1480
1481 return count;
1482}
1483
1484static const struct device_attribute overlay_sysfs_attrs[] = {
1485 __ATTR(ovl_alpha, S_IRUGO|S_IWUSR,
1486 overlay_alpha_show, overlay_alpha_store),
1487 __ATTR(ovl_mode, S_IRUGO|S_IWUSR,
1488 overlay_mode_show, overlay_mode_store),
1489 __ATTR(ovl_position, S_IRUGO|S_IWUSR,
1490 overlay_position_show, overlay_position_store),
1491 __ATTR(ovl_rop3, S_IRUGO|S_IWUSR,
1492 overlay_rop3_show, overlay_rop3_store),
1493};
1494
1495static const struct fb_fix_screeninfo sh_mobile_lcdc_overlay_fix = {
1496 .id = "SH Mobile LCDC",
1497 .type = FB_TYPE_PACKED_PIXELS,
1498 .visual = FB_VISUAL_TRUECOLOR,
1499 .accel = FB_ACCEL_NONE,
Laurent Pinchart15dede82012-07-18 17:09:04 +02001500 .xpanstep = 1,
Laurent Pinchartc5deac32011-12-12 18:43:16 +01001501 .ypanstep = 1,
1502 .ywrapstep = 0,
1503 .capabilities = FB_CAP_FOURCC,
1504};
1505
1506static int sh_mobile_lcdc_overlay_pan(struct fb_var_screeninfo *var,
1507 struct fb_info *info)
1508{
1509 struct sh_mobile_lcdc_overlay *ovl = info->par;
1510 unsigned long base_addr_y;
1511 unsigned long base_addr_c;
Laurent Pincharta4aa25f2012-03-15 18:15:37 +01001512 unsigned long y_offset;
Laurent Pinchartc5deac32011-12-12 18:43:16 +01001513 unsigned long c_offset;
1514
Laurent Pincharta4aa25f2012-03-15 18:15:37 +01001515 if (!ovl->format->yuv) {
1516 y_offset = (var->yoffset * ovl->xres_virtual + var->xoffset)
1517 * ovl->format->bpp / 8;
1518 c_offset = 0;
1519 } else {
1520 unsigned int xsub = ovl->format->bpp < 24 ? 2 : 1;
1521 unsigned int ysub = ovl->format->bpp < 16 ? 2 : 1;
Laurent Pinchartc5deac32011-12-12 18:43:16 +01001522
Laurent Pincharta4aa25f2012-03-15 18:15:37 +01001523 y_offset = var->yoffset * ovl->xres_virtual + var->xoffset;
1524 c_offset = var->yoffset / ysub * ovl->xres_virtual * 2 / xsub
1525 + var->xoffset * 2 / xsub;
1526 }
1527
1528 /* If the Y offset hasn't changed, the C offset hasn't either. There's
1529 * nothing to do in that case.
1530 */
1531 if (y_offset == ovl->pan_y_offset)
1532 return 0;
Laurent Pinchartc5deac32011-12-12 18:43:16 +01001533
1534 /* Set the source address for the next refresh */
Laurent Pincharta4aa25f2012-03-15 18:15:37 +01001535 base_addr_y = ovl->dma_handle + y_offset;
1536 base_addr_c = ovl->dma_handle + ovl->xres_virtual * ovl->yres_virtual
1537 + c_offset;
Laurent Pinchartc5deac32011-12-12 18:43:16 +01001538
1539 ovl->base_addr_y = base_addr_y;
Laurent Pincharta4aa25f2012-03-15 18:15:37 +01001540 ovl->base_addr_c = base_addr_c;
1541 ovl->pan_y_offset = y_offset;
Laurent Pinchartc5deac32011-12-12 18:43:16 +01001542
Laurent Pinchart8be7c662012-07-19 02:29:52 +02001543 lcdc_write(ovl->channel->lcdc, LDBCR, LDBCR_UPC(ovl->index));
1544
Laurent Pinchartc5deac32011-12-12 18:43:16 +01001545 lcdc_write_overlay(ovl, LDBnBSAYR(ovl->index), ovl->base_addr_y);
1546 lcdc_write_overlay(ovl, LDBnBSACR(ovl->index), ovl->base_addr_c);
1547
Laurent Pinchart8be7c662012-07-19 02:29:52 +02001548 lcdc_write(ovl->channel->lcdc, LDBCR,
1549 LDBCR_UPF(ovl->index) | LDBCR_UPD(ovl->index));
1550
Laurent Pinchartc5deac32011-12-12 18:43:16 +01001551 return 0;
1552}
1553
1554static int sh_mobile_lcdc_overlay_ioctl(struct fb_info *info, unsigned int cmd,
1555 unsigned long arg)
1556{
1557 struct sh_mobile_lcdc_overlay *ovl = info->par;
1558
1559 switch (cmd) {
1560 case FBIO_WAITFORVSYNC:
1561 return sh_mobile_lcdc_wait_for_vsync(ovl->channel);
1562
1563 default:
1564 return -ENOIOCTLCMD;
1565 }
1566}
1567
1568static int sh_mobile_lcdc_overlay_check_var(struct fb_var_screeninfo *var,
1569 struct fb_info *info)
1570{
1571 return __sh_mobile_lcdc_check_var(var, info);
1572}
1573
1574static int sh_mobile_lcdc_overlay_set_par(struct fb_info *info)
1575{
1576 struct sh_mobile_lcdc_overlay *ovl = info->par;
1577
1578 ovl->format =
1579 sh_mobile_format_info(sh_mobile_format_fourcc(&info->var));
1580
1581 ovl->xres = info->var.xres;
1582 ovl->xres_virtual = info->var.xres_virtual;
1583 ovl->yres = info->var.yres;
1584 ovl->yres_virtual = info->var.yres_virtual;
1585
1586 if (ovl->format->yuv)
Laurent Pinchart16ca21c2012-07-18 16:29:20 +02001587 ovl->pitch = info->var.xres_virtual;
Laurent Pinchartc5deac32011-12-12 18:43:16 +01001588 else
Laurent Pinchart16ca21c2012-07-18 16:29:20 +02001589 ovl->pitch = info->var.xres_virtual * ovl->format->bpp / 8;
Laurent Pinchartc5deac32011-12-12 18:43:16 +01001590
1591 sh_mobile_lcdc_overlay_setup(ovl);
1592
1593 info->fix.line_length = ovl->pitch;
1594
1595 if (sh_mobile_format_is_fourcc(&info->var)) {
1596 info->fix.type = FB_TYPE_FOURCC;
1597 info->fix.visual = FB_VISUAL_FOURCC;
1598 } else {
1599 info->fix.type = FB_TYPE_PACKED_PIXELS;
1600 info->fix.visual = FB_VISUAL_TRUECOLOR;
1601 }
1602
1603 return 0;
1604}
1605
1606/* Overlay blanking. Disable the overlay when blanked. */
1607static int sh_mobile_lcdc_overlay_blank(int blank, struct fb_info *info)
1608{
1609 struct sh_mobile_lcdc_overlay *ovl = info->par;
1610
1611 ovl->enabled = !blank;
1612 sh_mobile_lcdc_overlay_setup(ovl);
1613
1614 /* Prevent the backlight from receiving a blanking event by returning
1615 * a non-zero value.
1616 */
1617 return 1;
1618}
1619
Hideki EIRAKUbf10a532012-08-16 19:13:20 +09001620static int
1621sh_mobile_lcdc_overlay_mmap(struct fb_info *info, struct vm_area_struct *vma)
1622{
1623 struct sh_mobile_lcdc_overlay *ovl = info->par;
1624
1625 return dma_mmap_coherent(ovl->channel->lcdc->dev, vma, ovl->fb_mem,
1626 ovl->dma_handle, ovl->fb_size);
1627}
1628
Laurent Pinchartc5deac32011-12-12 18:43:16 +01001629static struct fb_ops sh_mobile_lcdc_overlay_ops = {
1630 .owner = THIS_MODULE,
1631 .fb_read = fb_sys_read,
1632 .fb_write = fb_sys_write,
1633 .fb_fillrect = sys_fillrect,
1634 .fb_copyarea = sys_copyarea,
1635 .fb_imageblit = sys_imageblit,
1636 .fb_blank = sh_mobile_lcdc_overlay_blank,
1637 .fb_pan_display = sh_mobile_lcdc_overlay_pan,
1638 .fb_ioctl = sh_mobile_lcdc_overlay_ioctl,
1639 .fb_check_var = sh_mobile_lcdc_overlay_check_var,
1640 .fb_set_par = sh_mobile_lcdc_overlay_set_par,
Hideki EIRAKUbf10a532012-08-16 19:13:20 +09001641 .fb_mmap = sh_mobile_lcdc_overlay_mmap,
Laurent Pinchartc5deac32011-12-12 18:43:16 +01001642};
1643
1644static void
1645sh_mobile_lcdc_overlay_fb_unregister(struct sh_mobile_lcdc_overlay *ovl)
1646{
1647 struct fb_info *info = ovl->info;
1648
1649 if (info == NULL || info->dev == NULL)
1650 return;
1651
1652 unregister_framebuffer(ovl->info);
1653}
1654
Greg Kroah-Hartman48c68c42012-12-21 13:07:39 -08001655static int
Laurent Pinchartc5deac32011-12-12 18:43:16 +01001656sh_mobile_lcdc_overlay_fb_register(struct sh_mobile_lcdc_overlay *ovl)
1657{
1658 struct sh_mobile_lcdc_priv *lcdc = ovl->channel->lcdc;
1659 struct fb_info *info = ovl->info;
1660 unsigned int i;
1661 int ret;
1662
1663 if (info == NULL)
1664 return 0;
1665
1666 ret = register_framebuffer(info);
1667 if (ret < 0)
1668 return ret;
1669
1670 dev_info(lcdc->dev, "registered %s/overlay %u as %dx%d %dbpp.\n",
1671 dev_name(lcdc->dev), ovl->index, info->var.xres,
1672 info->var.yres, info->var.bits_per_pixel);
1673
1674 for (i = 0; i < ARRAY_SIZE(overlay_sysfs_attrs); ++i) {
1675 ret = device_create_file(info->dev, &overlay_sysfs_attrs[i]);
1676 if (ret < 0)
1677 return ret;
1678 }
1679
1680 return 0;
1681}
1682
1683static void
1684sh_mobile_lcdc_overlay_fb_cleanup(struct sh_mobile_lcdc_overlay *ovl)
1685{
1686 struct fb_info *info = ovl->info;
1687
1688 if (info == NULL || info->device == NULL)
1689 return;
1690
1691 framebuffer_release(info);
1692}
1693
Greg Kroah-Hartman48c68c42012-12-21 13:07:39 -08001694static int
Laurent Pinchartc5deac32011-12-12 18:43:16 +01001695sh_mobile_lcdc_overlay_fb_init(struct sh_mobile_lcdc_overlay *ovl)
1696{
1697 struct sh_mobile_lcdc_priv *priv = ovl->channel->lcdc;
1698 struct fb_var_screeninfo *var;
1699 struct fb_info *info;
1700
1701 /* Allocate and initialize the frame buffer device. */
1702 info = framebuffer_alloc(0, priv->dev);
1703 if (info == NULL) {
1704 dev_err(priv->dev, "unable to allocate fb_info\n");
1705 return -ENOMEM;
1706 }
1707
1708 ovl->info = info;
1709
1710 info->flags = FBINFO_FLAG_DEFAULT;
1711 info->fbops = &sh_mobile_lcdc_overlay_ops;
1712 info->device = priv->dev;
1713 info->screen_base = ovl->fb_mem;
1714 info->par = ovl;
1715
1716 /* Initialize fixed screen information. Restrict pan to 2 lines steps
1717 * for NV12 and NV21.
1718 */
1719 info->fix = sh_mobile_lcdc_overlay_fix;
1720 snprintf(info->fix.id, sizeof(info->fix.id),
1721 "SH Mobile LCDC Overlay %u", ovl->index);
1722 info->fix.smem_start = ovl->dma_handle;
1723 info->fix.smem_len = ovl->fb_size;
1724 info->fix.line_length = ovl->pitch;
1725
1726 if (ovl->format->yuv)
1727 info->fix.visual = FB_VISUAL_FOURCC;
1728 else
1729 info->fix.visual = FB_VISUAL_TRUECOLOR;
1730
Laurent Pinchart15dede82012-07-18 17:09:04 +02001731 switch (ovl->format->fourcc) {
Laurent Pinchart15dede82012-07-18 17:09:04 +02001732 case V4L2_PIX_FMT_NV12:
1733 case V4L2_PIX_FMT_NV21:
Laurent Pinchartac33a202012-07-26 14:36:55 +02001734 info->fix.ypanstep = 2;
1735 case V4L2_PIX_FMT_NV16:
1736 case V4L2_PIX_FMT_NV61:
Laurent Pinchart15dede82012-07-18 17:09:04 +02001737 info->fix.xpanstep = 2;
1738 }
Laurent Pinchartc5deac32011-12-12 18:43:16 +01001739
1740 /* Initialize variable screen information. */
1741 var = &info->var;
1742 memset(var, 0, sizeof(*var));
1743 var->xres = ovl->xres;
1744 var->yres = ovl->yres;
1745 var->xres_virtual = ovl->xres_virtual;
1746 var->yres_virtual = ovl->yres_virtual;
1747 var->activate = FB_ACTIVATE_NOW;
1748
1749 /* Use the legacy API by default for RGB formats, and the FOURCC API
1750 * for YUV formats.
1751 */
1752 if (!ovl->format->yuv)
1753 var->bits_per_pixel = ovl->format->bpp;
1754 else
1755 var->grayscale = ovl->format->fourcc;
1756
1757 return sh_mobile_lcdc_overlay_check_var(var, info);
1758}
1759
1760/* -----------------------------------------------------------------------------
1761 * Frame buffer operations - main frame buffer
Laurent Pinchartf1f60b52011-09-07 11:09:26 +02001762 */
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07001763
1764static int sh_mobile_lcdc_setcolreg(u_int regno,
1765 u_int red, u_int green, u_int blue,
1766 u_int transp, struct fb_info *info)
1767{
1768 u32 *palette = info->pseudo_palette;
1769
1770 if (regno >= PALETTE_NR)
1771 return -EINVAL;
1772
1773 /* only FB_VISUAL_TRUECOLOR supported */
1774
1775 red >>= 16 - info->var.red.length;
1776 green >>= 16 - info->var.green.length;
1777 blue >>= 16 - info->var.blue.length;
1778 transp >>= 16 - info->var.transp.length;
1779
1780 palette[regno] = (red << info->var.red.offset) |
1781 (green << info->var.green.offset) |
1782 (blue << info->var.blue.offset) |
1783 (transp << info->var.transp.offset);
1784
1785 return 0;
1786}
1787
Laurent Pinchart3281e542011-11-22 00:56:58 +01001788static const struct fb_fix_screeninfo sh_mobile_lcdc_fix = {
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07001789 .id = "SH Mobile LCDC",
1790 .type = FB_TYPE_PACKED_PIXELS,
1791 .visual = FB_VISUAL_TRUECOLOR,
1792 .accel = FB_ACCEL_NONE,
Laurent Pinchart15dede82012-07-18 17:09:04 +02001793 .xpanstep = 1,
Phil Edworthy9dd38812009-09-15 12:00:18 +00001794 .ypanstep = 1,
1795 .ywrapstep = 0,
Laurent Pinchartedd153a2011-12-13 14:02:28 +01001796 .capabilities = FB_CAP_FOURCC,
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07001797};
1798
Magnus Damm85645572008-12-19 15:34:41 +09001799static void sh_mobile_lcdc_fillrect(struct fb_info *info,
1800 const struct fb_fillrect *rect)
1801{
1802 sys_fillrect(info, rect);
1803 sh_mobile_lcdc_deferred_io_touch(info);
1804}
1805
1806static void sh_mobile_lcdc_copyarea(struct fb_info *info,
1807 const struct fb_copyarea *area)
1808{
1809 sys_copyarea(info, area);
1810 sh_mobile_lcdc_deferred_io_touch(info);
1811}
1812
1813static void sh_mobile_lcdc_imageblit(struct fb_info *info,
1814 const struct fb_image *image)
1815{
1816 sys_imageblit(info, image);
1817 sh_mobile_lcdc_deferred_io_touch(info);
1818}
1819
Laurent Pinchartd7ad3342011-11-22 00:56:58 +01001820static int sh_mobile_lcdc_pan(struct fb_var_screeninfo *var,
1821 struct fb_info *info)
Phil Edworthy9dd38812009-09-15 12:00:18 +00001822{
1823 struct sh_mobile_lcdc_chan *ch = info->par;
Phil Edworthy92e1f9a2010-02-11 10:24:25 +00001824 struct sh_mobile_lcdc_priv *priv = ch->lcdc;
1825 unsigned long ldrcntr;
Laurent Pincharta4aa25f2012-03-15 18:15:37 +01001826 unsigned long base_addr_y, base_addr_c;
1827 unsigned long y_offset;
Damian Hobson-Garcia53b50312011-02-24 05:47:13 +00001828 unsigned long c_offset;
Phil Edworthy9dd38812009-09-15 12:00:18 +00001829
Laurent Pincharta4aa25f2012-03-15 18:15:37 +01001830 if (!ch->format->yuv) {
1831 y_offset = (var->yoffset * ch->xres_virtual + var->xoffset)
1832 * ch->format->bpp / 8;
1833 c_offset = 0;
1834 } else {
1835 unsigned int xsub = ch->format->bpp < 24 ? 2 : 1;
1836 unsigned int ysub = ch->format->bpp < 16 ? 2 : 1;
Phil Edworthy9dd38812009-09-15 12:00:18 +00001837
Laurent Pincharta4aa25f2012-03-15 18:15:37 +01001838 y_offset = var->yoffset * ch->xres_virtual + var->xoffset;
1839 c_offset = var->yoffset / ysub * ch->xres_virtual * 2 / xsub
1840 + var->xoffset * 2 / xsub;
1841 }
Phil Edworthy92e1f9a2010-02-11 10:24:25 +00001842
Laurent Pincharta4aa25f2012-03-15 18:15:37 +01001843 /* If the Y offset hasn't changed, the C offset hasn't either. There's
1844 * nothing to do in that case.
1845 */
1846 if (y_offset == ch->pan_y_offset)
1847 return 0;
Phil Edworthy92e1f9a2010-02-11 10:24:25 +00001848
1849 /* Set the source address for the next refresh */
Laurent Pincharta4aa25f2012-03-15 18:15:37 +01001850 base_addr_y = ch->dma_handle + y_offset;
1851 base_addr_c = ch->dma_handle + ch->xres_virtual * ch->yres_virtual
1852 + c_offset;
Damian Hobson-Garcia53b50312011-02-24 05:47:13 +00001853
Laurent Pinchart6e729b42012-03-15 12:40:47 +01001854 if (ch->cache)
1855 sh_mobile_meram_cache_update(priv->meram_dev, ch->cache,
1856 base_addr_y, base_addr_c,
1857 &base_addr_y, &base_addr_c);
Damian Hobson-Garcia53b50312011-02-24 05:47:13 +00001858
Laurent Pinchart49d79ba2011-07-13 12:13:47 +02001859 ch->base_addr_y = base_addr_y;
1860 ch->base_addr_c = base_addr_c;
Laurent Pincharta4aa25f2012-03-15 18:15:37 +01001861 ch->pan_y_offset = y_offset;
Laurent Pinchart49d79ba2011-07-13 12:13:47 +02001862
1863 lcdc_write_chan_mirror(ch, LDSA1R, base_addr_y);
Laurent Pinchart58f03d92011-11-30 23:07:30 +01001864 if (ch->format->yuv)
Laurent Pinchart49d79ba2011-07-13 12:13:47 +02001865 lcdc_write_chan_mirror(ch, LDSA2R, base_addr_c);
1866
Laurent Pincharta4aa25f2012-03-15 18:15:37 +01001867 ldrcntr = lcdc_read(priv, _LDRCNTR);
Phil Edworthy92e1f9a2010-02-11 10:24:25 +00001868 if (lcdc_chan_is_sublcd(ch))
1869 lcdc_write(ch->lcdc, _LDRCNTR, ldrcntr ^ LDRCNTR_SRS);
1870 else
1871 lcdc_write(ch->lcdc, _LDRCNTR, ldrcntr ^ LDRCNTR_MRS);
1872
Phil Edworthy92e1f9a2010-02-11 10:24:25 +00001873
1874 sh_mobile_lcdc_deferred_io_touch(info);
Phil Edworthy9dd38812009-09-15 12:00:18 +00001875
1876 return 0;
1877}
1878
Laurent Pinchartd7ad3342011-11-22 00:56:58 +01001879static int sh_mobile_lcdc_ioctl(struct fb_info *info, unsigned int cmd,
1880 unsigned long arg)
Phil Edworthy40331b22010-02-15 13:57:49 +00001881{
Laurent Pinchartd7ad3342011-11-22 00:56:58 +01001882 struct sh_mobile_lcdc_chan *ch = info->par;
Phil Edworthy40331b22010-02-15 13:57:49 +00001883 int retval;
1884
1885 switch (cmd) {
1886 case FBIO_WAITFORVSYNC:
Laurent Pinchartd7ad3342011-11-22 00:56:58 +01001887 retval = sh_mobile_lcdc_wait_for_vsync(ch);
Phil Edworthy40331b22010-02-15 13:57:49 +00001888 break;
1889
1890 default:
1891 retval = -ENOIOCTLCMD;
1892 break;
1893 }
1894 return retval;
1895}
1896
Guennadi Liakhovetskidd210502010-09-14 14:48:54 +00001897static void sh_mobile_fb_reconfig(struct fb_info *info)
1898{
1899 struct sh_mobile_lcdc_chan *ch = info->par;
Laurent Pinchart2d045592011-11-29 13:42:48 +01001900 struct fb_var_screeninfo var;
1901 struct fb_videomode mode;
Guennadi Liakhovetskidd210502010-09-14 14:48:54 +00001902 struct fb_event event;
1903 int evnt = FB_EVENT_MODE_CHANGE_ALL;
1904
1905 if (ch->use_count > 1 || (ch->use_count == 1 && !info->fbcon_par))
1906 /* More framebuffer users are active */
1907 return;
1908
Laurent Pinchart2d045592011-11-29 13:42:48 +01001909 fb_var_to_videomode(&mode, &info->var);
Guennadi Liakhovetskidd210502010-09-14 14:48:54 +00001910
Laurent Pinchart2d045592011-11-29 13:42:48 +01001911 if (fb_mode_is_equal(&ch->display.mode, &mode))
Guennadi Liakhovetskidd210502010-09-14 14:48:54 +00001912 return;
1913
1914 /* Display has been re-plugged, framebuffer is free now, reconfigure */
Laurent Pinchart2d045592011-11-29 13:42:48 +01001915 var = info->var;
1916 fb_videomode_to_var(&var, &ch->display.mode);
1917 var.width = ch->display.width;
1918 var.height = ch->display.height;
1919 var.activate = FB_ACTIVATE_NOW;
1920
1921 if (fb_set_var(info, &var) < 0)
Guennadi Liakhovetskidd210502010-09-14 14:48:54 +00001922 /* Couldn't reconfigure, hopefully, can continue as before */
1923 return;
1924
Guennadi Liakhovetskidd210502010-09-14 14:48:54 +00001925 /*
1926 * fb_set_var() calls the notifier change internally, only if
1927 * FBINFO_MISC_USEREVENT flag is set. Since we do not want to fake a
1928 * user event, we have to call the chain ourselves.
1929 */
1930 event.info = info;
Laurent Pinchart2d045592011-11-29 13:42:48 +01001931 event.data = &ch->display.mode;
Guennadi Liakhovetskidd210502010-09-14 14:48:54 +00001932 fb_notifier_call_chain(evnt, &event);
1933}
1934
1935/*
1936 * Locking: both .fb_release() and .fb_open() are called with info->lock held if
1937 * user == 1, or with console sem held, if user == 0.
1938 */
Laurent Pinchartd7ad3342011-11-22 00:56:58 +01001939static int sh_mobile_lcdc_release(struct fb_info *info, int user)
Guennadi Liakhovetskidd210502010-09-14 14:48:54 +00001940{
1941 struct sh_mobile_lcdc_chan *ch = info->par;
1942
1943 mutex_lock(&ch->open_lock);
1944 dev_dbg(info->dev, "%s(): %d users\n", __func__, ch->use_count);
1945
1946 ch->use_count--;
1947
1948 /* Nothing to reconfigure, when called from fbcon */
1949 if (user) {
Torben Hohnac751ef2011-01-25 15:07:35 -08001950 console_lock();
Guennadi Liakhovetskidd210502010-09-14 14:48:54 +00001951 sh_mobile_fb_reconfig(info);
Torben Hohnac751ef2011-01-25 15:07:35 -08001952 console_unlock();
Guennadi Liakhovetskidd210502010-09-14 14:48:54 +00001953 }
1954
1955 mutex_unlock(&ch->open_lock);
1956
1957 return 0;
1958}
1959
Laurent Pinchartd7ad3342011-11-22 00:56:58 +01001960static int sh_mobile_lcdc_open(struct fb_info *info, int user)
Guennadi Liakhovetskidd210502010-09-14 14:48:54 +00001961{
1962 struct sh_mobile_lcdc_chan *ch = info->par;
1963
1964 mutex_lock(&ch->open_lock);
1965 ch->use_count++;
1966
1967 dev_dbg(info->dev, "%s(): %d users\n", __func__, ch->use_count);
1968 mutex_unlock(&ch->open_lock);
1969
1970 return 0;
1971}
1972
Laurent Pinchartd7ad3342011-11-22 00:56:58 +01001973static int sh_mobile_lcdc_check_var(struct fb_var_screeninfo *var,
1974 struct fb_info *info)
Guennadi Liakhovetskidd210502010-09-14 14:48:54 +00001975{
1976 struct sh_mobile_lcdc_chan *ch = info->par;
Magnus Damm417d4822011-01-05 10:21:00 +00001977 struct sh_mobile_lcdc_priv *p = ch->lcdc;
Laurent Pinchart03862192011-08-31 13:00:53 +02001978 unsigned int best_dist = (unsigned int)-1;
1979 unsigned int best_xres = 0;
1980 unsigned int best_yres = 0;
1981 unsigned int i;
Laurent Pinchartc5deac32011-12-12 18:43:16 +01001982 int ret;
Laurent Pinchart03862192011-08-31 13:00:53 +02001983
1984 /* If board code provides us with a list of available modes, make sure
1985 * we use one of them. Find the mode closest to the requested one. The
1986 * distance between two modes is defined as the size of the
1987 * non-overlapping parts of the two rectangles.
1988 */
Laurent Pinchartb5ef9672011-11-22 00:56:58 +01001989 for (i = 0; i < ch->cfg->num_modes; ++i) {
1990 const struct fb_videomode *mode = &ch->cfg->lcd_modes[i];
Laurent Pinchart03862192011-08-31 13:00:53 +02001991 unsigned int dist;
1992
1993 /* We can only round up. */
1994 if (var->xres > mode->xres || var->yres > mode->yres)
1995 continue;
1996
1997 dist = var->xres * var->yres + mode->xres * mode->yres
1998 - 2 * min(var->xres, mode->xres)
1999 * min(var->yres, mode->yres);
2000
2001 if (dist < best_dist) {
2002 best_xres = mode->xres;
2003 best_yres = mode->yres;
2004 best_dist = dist;
2005 }
Guennadi Liakhovetskidd210502010-09-14 14:48:54 +00002006 }
Magnus Damm417d4822011-01-05 10:21:00 +00002007
Laurent Pinchart03862192011-08-31 13:00:53 +02002008 /* If no available mode can be used, return an error. */
Laurent Pinchartb5ef9672011-11-22 00:56:58 +01002009 if (ch->cfg->num_modes != 0) {
Laurent Pinchart03862192011-08-31 13:00:53 +02002010 if (best_dist == (unsigned int)-1)
2011 return -EINVAL;
2012
2013 var->xres = best_xres;
2014 var->yres = best_yres;
2015 }
2016
Laurent Pinchartc5deac32011-12-12 18:43:16 +01002017 ret = __sh_mobile_lcdc_check_var(var, info);
2018 if (ret < 0)
2019 return ret;
Laurent Pinchart03862192011-08-31 13:00:53 +02002020
Laurent Pinchartedd153a2011-12-13 14:02:28 +01002021 /* only accept the forced_fourcc for dual channel configurations */
2022 if (p->forced_fourcc &&
2023 p->forced_fourcc != sh_mobile_format_fourcc(var))
Magnus Damm417d4822011-01-05 10:21:00 +00002024 return -EINVAL;
2025
Guennadi Liakhovetskidd210502010-09-14 14:48:54 +00002026 return 0;
2027}
Phil Edworthy40331b22010-02-15 13:57:49 +00002028
Laurent Pinchartd7ad3342011-11-22 00:56:58 +01002029static int sh_mobile_lcdc_set_par(struct fb_info *info)
Laurent Pincharted5bebf2011-08-31 13:00:54 +02002030{
2031 struct sh_mobile_lcdc_chan *ch = info->par;
2032 int ret;
2033
2034 sh_mobile_lcdc_stop(ch->lcdc);
Laurent Pinchart91fba482011-08-31 13:00:56 +02002035
Laurent Pinchartfc9e78e2011-11-29 16:05:36 +01002036 ch->format = sh_mobile_format_info(sh_mobile_format_fourcc(&info->var));
Laurent Pinchart58f03d92011-11-30 23:07:30 +01002037 ch->colorspace = info->var.colorspace;
2038
2039 ch->xres = info->var.xres;
2040 ch->xres_virtual = info->var.xres_virtual;
2041 ch->yres = info->var.yres;
2042 ch->yres_virtual = info->var.yres_virtual;
2043
2044 if (ch->format->yuv)
Laurent Pinchart16ca21c2012-07-18 16:29:20 +02002045 ch->pitch = info->var.xres_virtual;
Laurent Pinchart58f03d92011-11-30 23:07:30 +01002046 else
Laurent Pinchart16ca21c2012-07-18 16:29:20 +02002047 ch->pitch = info->var.xres_virtual * ch->format->bpp / 8;
Laurent Pinchartfc9e78e2011-11-29 16:05:36 +01002048
Laurent Pincharted5bebf2011-08-31 13:00:54 +02002049 ret = sh_mobile_lcdc_start(ch->lcdc);
Laurent Pinchart58f03d92011-11-30 23:07:30 +01002050 if (ret < 0)
Laurent Pincharted5bebf2011-08-31 13:00:54 +02002051 dev_err(info->dev, "%s: unable to restart LCDC\n", __func__);
Laurent Pinchart58f03d92011-11-30 23:07:30 +01002052
2053 info->fix.line_length = ch->pitch;
Laurent Pincharted5bebf2011-08-31 13:00:54 +02002054
Laurent Pinchartedd153a2011-12-13 14:02:28 +01002055 if (sh_mobile_format_is_fourcc(&info->var)) {
2056 info->fix.type = FB_TYPE_FOURCC;
2057 info->fix.visual = FB_VISUAL_FOURCC;
2058 } else {
2059 info->fix.type = FB_TYPE_PACKED_PIXELS;
2060 info->fix.visual = FB_VISUAL_TRUECOLOR;
2061 }
2062
Laurent Pincharted5bebf2011-08-31 13:00:54 +02002063 return ret;
2064}
2065
Alexandre Courbot8857b9a2011-02-23 08:36:30 +00002066/*
2067 * Screen blanking. Behavior is as follows:
2068 * FB_BLANK_UNBLANK: screen unblanked, clocks enabled
2069 * FB_BLANK_NORMAL: screen blanked, clocks enabled
2070 * FB_BLANK_VSYNC,
2071 * FB_BLANK_HSYNC,
2072 * FB_BLANK_POWEROFF: screen blanked, clocks disabled
2073 */
2074static int sh_mobile_lcdc_blank(int blank, struct fb_info *info)
2075{
2076 struct sh_mobile_lcdc_chan *ch = info->par;
2077 struct sh_mobile_lcdc_priv *p = ch->lcdc;
2078
2079 /* blank the screen? */
2080 if (blank > FB_BLANK_UNBLANK && ch->blank_status == FB_BLANK_UNBLANK) {
2081 struct fb_fillrect rect = {
Laurent Pinchart58f03d92011-11-30 23:07:30 +01002082 .width = ch->xres,
2083 .height = ch->yres,
Alexandre Courbot8857b9a2011-02-23 08:36:30 +00002084 };
2085 sh_mobile_lcdc_fillrect(info, &rect);
2086 }
2087 /* turn clocks on? */
2088 if (blank <= FB_BLANK_NORMAL && ch->blank_status > FB_BLANK_NORMAL) {
2089 sh_mobile_lcdc_clk_on(p);
2090 }
2091 /* turn clocks off? */
2092 if (blank > FB_BLANK_NORMAL && ch->blank_status <= FB_BLANK_NORMAL) {
2093 /* make sure the screen is updated with the black fill before
2094 * switching the clocks off. one vsync is not enough since
2095 * blanking may occur in the middle of a refresh. deferred io
2096 * mode will reenable the clocks and update the screen in time,
2097 * so it does not need this. */
2098 if (!info->fbdefio) {
Laurent Pinchartd7ad3342011-11-22 00:56:58 +01002099 sh_mobile_lcdc_wait_for_vsync(ch);
2100 sh_mobile_lcdc_wait_for_vsync(ch);
Alexandre Courbot8857b9a2011-02-23 08:36:30 +00002101 }
2102 sh_mobile_lcdc_clk_off(p);
2103 }
2104
2105 ch->blank_status = blank;
2106 return 0;
2107}
2108
Hideki EIRAKUbf10a532012-08-16 19:13:20 +09002109static int
2110sh_mobile_lcdc_mmap(struct fb_info *info, struct vm_area_struct *vma)
2111{
2112 struct sh_mobile_lcdc_chan *ch = info->par;
2113
2114 return dma_mmap_coherent(ch->lcdc->dev, vma, ch->fb_mem,
2115 ch->dma_handle, ch->fb_size);
2116}
2117
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07002118static struct fb_ops sh_mobile_lcdc_ops = {
Phil Edworthy9dd38812009-09-15 12:00:18 +00002119 .owner = THIS_MODULE,
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07002120 .fb_setcolreg = sh_mobile_lcdc_setcolreg,
Magnus Damm2540c112008-12-17 17:29:49 +09002121 .fb_read = fb_sys_read,
2122 .fb_write = fb_sys_write,
Magnus Damm85645572008-12-19 15:34:41 +09002123 .fb_fillrect = sh_mobile_lcdc_fillrect,
2124 .fb_copyarea = sh_mobile_lcdc_copyarea,
2125 .fb_imageblit = sh_mobile_lcdc_imageblit,
Alexandre Courbot8857b9a2011-02-23 08:36:30 +00002126 .fb_blank = sh_mobile_lcdc_blank,
Laurent Pinchartd7ad3342011-11-22 00:56:58 +01002127 .fb_pan_display = sh_mobile_lcdc_pan,
2128 .fb_ioctl = sh_mobile_lcdc_ioctl,
2129 .fb_open = sh_mobile_lcdc_open,
2130 .fb_release = sh_mobile_lcdc_release,
2131 .fb_check_var = sh_mobile_lcdc_check_var,
2132 .fb_set_par = sh_mobile_lcdc_set_par,
Hideki EIRAKUbf10a532012-08-16 19:13:20 +09002133 .fb_mmap = sh_mobile_lcdc_mmap,
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07002134};
2135
Laurent Pincharta67f3792011-11-29 14:37:35 +01002136static void
2137sh_mobile_lcdc_channel_fb_unregister(struct sh_mobile_lcdc_chan *ch)
2138{
2139 if (ch->info && ch->info->dev)
2140 unregister_framebuffer(ch->info);
2141}
2142
Greg Kroah-Hartman48c68c42012-12-21 13:07:39 -08002143static int
Laurent Pincharta67f3792011-11-29 14:37:35 +01002144sh_mobile_lcdc_channel_fb_register(struct sh_mobile_lcdc_chan *ch)
2145{
2146 struct fb_info *info = ch->info;
2147 int ret;
2148
2149 if (info->fbdefio) {
2150 ch->sglist = vmalloc(sizeof(struct scatterlist) *
2151 ch->fb_size >> PAGE_SHIFT);
2152 if (!ch->sglist) {
2153 dev_err(ch->lcdc->dev, "cannot allocate sglist\n");
2154 return -ENOMEM;
2155 }
2156 }
2157
2158 info->bl_dev = ch->bl;
2159
2160 ret = register_framebuffer(info);
2161 if (ret < 0)
2162 return ret;
2163
2164 dev_info(ch->lcdc->dev, "registered %s/%s as %dx%d %dbpp.\n",
Laurent Pinchartb5ef9672011-11-22 00:56:58 +01002165 dev_name(ch->lcdc->dev), (ch->cfg->chan == LCDC_CHAN_MAINLCD) ?
Laurent Pincharta67f3792011-11-29 14:37:35 +01002166 "mainlcd" : "sublcd", info->var.xres, info->var.yres,
2167 info->var.bits_per_pixel);
2168
2169 /* deferred io mode: disable clock to save power */
2170 if (info->fbdefio || info->state == FBINFO_STATE_SUSPENDED)
2171 sh_mobile_lcdc_clk_off(ch->lcdc);
2172
2173 return ret;
2174}
2175
2176static void
2177sh_mobile_lcdc_channel_fb_cleanup(struct sh_mobile_lcdc_chan *ch)
2178{
2179 struct fb_info *info = ch->info;
2180
2181 if (!info || !info->device)
2182 return;
2183
Markus Elfringf8582752014-11-22 16:51:31 +01002184 vfree(ch->sglist);
Laurent Pincharta67f3792011-11-29 14:37:35 +01002185
2186 fb_dealloc_cmap(&info->cmap);
2187 framebuffer_release(info);
2188}
2189
Greg Kroah-Hartman48c68c42012-12-21 13:07:39 -08002190static int
Laurent Pincharta67f3792011-11-29 14:37:35 +01002191sh_mobile_lcdc_channel_fb_init(struct sh_mobile_lcdc_chan *ch,
Laurent Pinchart352d6132012-08-13 13:56:46 +02002192 const struct fb_videomode *modes,
Laurent Pincharta67f3792011-11-29 14:37:35 +01002193 unsigned int num_modes)
2194{
2195 struct sh_mobile_lcdc_priv *priv = ch->lcdc;
2196 struct fb_var_screeninfo *var;
2197 struct fb_info *info;
2198 int ret;
2199
2200 /* Allocate and initialize the frame buffer device. Create the modes
2201 * list and allocate the color map.
2202 */
2203 info = framebuffer_alloc(0, priv->dev);
2204 if (info == NULL) {
2205 dev_err(priv->dev, "unable to allocate fb_info\n");
2206 return -ENOMEM;
2207 }
2208
2209 ch->info = info;
2210
2211 info->flags = FBINFO_FLAG_DEFAULT;
2212 info->fbops = &sh_mobile_lcdc_ops;
2213 info->device = priv->dev;
2214 info->screen_base = ch->fb_mem;
2215 info->pseudo_palette = &ch->pseudo_palette;
2216 info->par = ch;
2217
Laurent Pinchart352d6132012-08-13 13:56:46 +02002218 fb_videomode_to_modelist(modes, num_modes, &info->modelist);
Laurent Pincharta67f3792011-11-29 14:37:35 +01002219
2220 ret = fb_alloc_cmap(&info->cmap, PALETTE_NR, 0);
2221 if (ret < 0) {
2222 dev_err(priv->dev, "unable to allocate cmap\n");
2223 return ret;
2224 }
2225
2226 /* Initialize fixed screen information. Restrict pan to 2 lines steps
2227 * for NV12 and NV21.
2228 */
2229 info->fix = sh_mobile_lcdc_fix;
2230 info->fix.smem_start = ch->dma_handle;
2231 info->fix.smem_len = ch->fb_size;
Laurent Pinchart58f03d92011-11-30 23:07:30 +01002232 info->fix.line_length = ch->pitch;
2233
2234 if (ch->format->yuv)
2235 info->fix.visual = FB_VISUAL_FOURCC;
2236 else
2237 info->fix.visual = FB_VISUAL_TRUECOLOR;
2238
Laurent Pinchart15dede82012-07-18 17:09:04 +02002239 switch (ch->format->fourcc) {
Laurent Pinchart15dede82012-07-18 17:09:04 +02002240 case V4L2_PIX_FMT_NV12:
2241 case V4L2_PIX_FMT_NV21:
Laurent Pinchartac33a202012-07-26 14:36:55 +02002242 info->fix.ypanstep = 2;
2243 case V4L2_PIX_FMT_NV16:
2244 case V4L2_PIX_FMT_NV61:
Laurent Pinchart15dede82012-07-18 17:09:04 +02002245 info->fix.xpanstep = 2;
2246 }
Laurent Pincharta67f3792011-11-29 14:37:35 +01002247
2248 /* Initialize variable screen information using the first mode as
Laurent Pinchartbd5f2c62012-07-18 16:59:16 +02002249 * default.
Laurent Pincharta67f3792011-11-29 14:37:35 +01002250 */
2251 var = &info->var;
Laurent Pinchart352d6132012-08-13 13:56:46 +02002252 fb_videomode_to_var(var, modes);
Laurent Pinchart856e8df2012-08-13 13:56:46 +02002253 var->width = ch->display.width;
2254 var->height = ch->display.height;
Laurent Pinchartbd5f2c62012-07-18 16:59:16 +02002255 var->xres_virtual = ch->xres_virtual;
2256 var->yres_virtual = ch->yres_virtual;
Laurent Pincharta67f3792011-11-29 14:37:35 +01002257 var->activate = FB_ACTIVATE_NOW;
2258
2259 /* Use the legacy API by default for RGB formats, and the FOURCC API
2260 * for YUV formats.
2261 */
2262 if (!ch->format->yuv)
2263 var->bits_per_pixel = ch->format->bpp;
2264 else
2265 var->grayscale = ch->format->fourcc;
2266
Laurent Pinchartd7ad3342011-11-22 00:56:58 +01002267 ret = sh_mobile_lcdc_check_var(var, info);
Laurent Pincharta67f3792011-11-29 14:37:35 +01002268 if (ret)
2269 return ret;
2270
Laurent Pincharta67f3792011-11-29 14:37:35 +01002271 return 0;
2272}
2273
Laurent Pinchartf1f60b52011-09-07 11:09:26 +02002274/* -----------------------------------------------------------------------------
2275 * Backlight
2276 */
2277
Alexandre Courbot3b0fd9d2011-02-16 03:49:01 +00002278static int sh_mobile_lcdc_update_bl(struct backlight_device *bdev)
2279{
2280 struct sh_mobile_lcdc_chan *ch = bl_get_data(bdev);
Alexandre Courbot3b0fd9d2011-02-16 03:49:01 +00002281 int brightness = bdev->props.brightness;
2282
2283 if (bdev->props.power != FB_BLANK_UNBLANK ||
2284 bdev->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK))
2285 brightness = 0;
2286
Laurent Pinchart656d4f32012-08-15 18:10:03 +02002287 ch->bl_brightness = brightness;
Laurent Pinchartb5ef9672011-11-22 00:56:58 +01002288 return ch->cfg->bl_info.set_brightness(brightness);
Alexandre Courbot3b0fd9d2011-02-16 03:49:01 +00002289}
2290
2291static int sh_mobile_lcdc_get_brightness(struct backlight_device *bdev)
2292{
2293 struct sh_mobile_lcdc_chan *ch = bl_get_data(bdev);
Alexandre Courbot3b0fd9d2011-02-16 03:49:01 +00002294
Laurent Pinchart656d4f32012-08-15 18:10:03 +02002295 return ch->bl_brightness;
Alexandre Courbot3b0fd9d2011-02-16 03:49:01 +00002296}
2297
2298static int sh_mobile_lcdc_check_fb(struct backlight_device *bdev,
2299 struct fb_info *info)
2300{
2301 return (info->bl_dev == bdev);
2302}
2303
2304static struct backlight_ops sh_mobile_lcdc_bl_ops = {
2305 .options = BL_CORE_SUSPENDRESUME,
2306 .update_status = sh_mobile_lcdc_update_bl,
2307 .get_brightness = sh_mobile_lcdc_get_brightness,
2308 .check_fb = sh_mobile_lcdc_check_fb,
2309};
2310
2311static struct backlight_device *sh_mobile_lcdc_bl_probe(struct device *parent,
2312 struct sh_mobile_lcdc_chan *ch)
2313{
2314 struct backlight_device *bl;
2315
Laurent Pinchartb5ef9672011-11-22 00:56:58 +01002316 bl = backlight_device_register(ch->cfg->bl_info.name, parent, ch,
Alexandre Courbot3b0fd9d2011-02-16 03:49:01 +00002317 &sh_mobile_lcdc_bl_ops, NULL);
Dan Carpenterbeee1f22011-03-21 15:03:13 +00002318 if (IS_ERR(bl)) {
2319 dev_err(parent, "unable to register backlight device: %ld\n",
2320 PTR_ERR(bl));
Alexandre Courbot3b0fd9d2011-02-16 03:49:01 +00002321 return NULL;
2322 }
2323
Laurent Pinchartb5ef9672011-11-22 00:56:58 +01002324 bl->props.max_brightness = ch->cfg->bl_info.max_brightness;
Alexandre Courbot3b0fd9d2011-02-16 03:49:01 +00002325 bl->props.brightness = bl->props.max_brightness;
2326 backlight_update_status(bl);
2327
2328 return bl;
2329}
2330
2331static void sh_mobile_lcdc_bl_remove(struct backlight_device *bdev)
2332{
2333 backlight_device_unregister(bdev);
2334}
2335
Laurent Pinchartf1f60b52011-09-07 11:09:26 +02002336/* -----------------------------------------------------------------------------
2337 * Power management
2338 */
2339
Magnus Damm2feb0752009-03-13 15:36:55 +00002340static int sh_mobile_lcdc_suspend(struct device *dev)
2341{
2342 struct platform_device *pdev = to_platform_device(dev);
2343
2344 sh_mobile_lcdc_stop(platform_get_drvdata(pdev));
2345 return 0;
2346}
2347
2348static int sh_mobile_lcdc_resume(struct device *dev)
2349{
2350 struct platform_device *pdev = to_platform_device(dev);
2351
2352 return sh_mobile_lcdc_start(platform_get_drvdata(pdev));
2353}
2354
Magnus Damm0246c472009-08-14 10:49:08 +00002355static int sh_mobile_lcdc_runtime_suspend(struct device *dev)
2356{
2357 struct platform_device *pdev = to_platform_device(dev);
Laurent Pinchart2427bb22011-07-13 12:13:47 +02002358 struct sh_mobile_lcdc_priv *priv = platform_get_drvdata(pdev);
Magnus Damm0246c472009-08-14 10:49:08 +00002359
2360 /* turn off LCDC hardware */
Laurent Pinchart2427bb22011-07-13 12:13:47 +02002361 lcdc_write(priv, _LDCNT1R, 0);
2362
Magnus Damm0246c472009-08-14 10:49:08 +00002363 return 0;
2364}
2365
2366static int sh_mobile_lcdc_runtime_resume(struct device *dev)
2367{
2368 struct platform_device *pdev = to_platform_device(dev);
Laurent Pinchart2427bb22011-07-13 12:13:47 +02002369 struct sh_mobile_lcdc_priv *priv = platform_get_drvdata(pdev);
Magnus Damm0246c472009-08-14 10:49:08 +00002370
Laurent Pinchart2427bb22011-07-13 12:13:47 +02002371 __sh_mobile_lcdc_start(priv);
Magnus Damm0246c472009-08-14 10:49:08 +00002372
2373 return 0;
2374}
2375
Alexey Dobriyan47145212009-12-14 18:00:08 -08002376static const struct dev_pm_ops sh_mobile_lcdc_dev_pm_ops = {
Magnus Damm2feb0752009-03-13 15:36:55 +00002377 .suspend = sh_mobile_lcdc_suspend,
2378 .resume = sh_mobile_lcdc_resume,
Magnus Damm0246c472009-08-14 10:49:08 +00002379 .runtime_suspend = sh_mobile_lcdc_runtime_suspend,
2380 .runtime_resume = sh_mobile_lcdc_runtime_resume,
Magnus Damm2feb0752009-03-13 15:36:55 +00002381};
2382
Laurent Pinchartf1f60b52011-09-07 11:09:26 +02002383/* -----------------------------------------------------------------------------
2384 * Framebuffer notifier
2385 */
2386
Guennadi Liakhovetski6de9edd2010-09-03 07:20:23 +00002387/* locking: called with info->lock held */
Guennadi Liakhovetski6011bde2010-07-21 10:13:21 +00002388static int sh_mobile_lcdc_notify(struct notifier_block *nb,
2389 unsigned long action, void *data)
2390{
2391 struct fb_event *event = data;
2392 struct fb_info *info = event->info;
2393 struct sh_mobile_lcdc_chan *ch = info->par;
Guennadi Liakhovetski6011bde2010-07-21 10:13:21 +00002394
2395 if (&ch->lcdc->notifier != nb)
Guennadi Liakhovetskibaf16372010-09-03 07:20:08 +00002396 return NOTIFY_DONE;
Guennadi Liakhovetski6011bde2010-07-21 10:13:21 +00002397
2398 dev_dbg(info->dev, "%s(): action = %lu, data = %p\n",
2399 __func__, action, event->data);
2400
2401 switch(action) {
2402 case FB_EVENT_SUSPEND:
Laurent Pinchart37c5dcc2011-09-09 15:45:43 +02002403 sh_mobile_lcdc_display_off(ch);
Guennadi Liakhovetskiafe417c2010-09-03 07:20:39 +00002404 sh_mobile_lcdc_stop(ch->lcdc);
Guennadi Liakhovetski6011bde2010-07-21 10:13:21 +00002405 break;
2406 case FB_EVENT_RESUME:
Guennadi Liakhovetskidd210502010-09-14 14:48:54 +00002407 mutex_lock(&ch->open_lock);
2408 sh_mobile_fb_reconfig(info);
2409 mutex_unlock(&ch->open_lock);
Guennadi Liakhovetski6011bde2010-07-21 10:13:21 +00002410
Laurent Pinchart37c5dcc2011-09-09 15:45:43 +02002411 sh_mobile_lcdc_display_on(ch);
Guennadi Liakhovetskiebe5e122011-05-05 16:33:40 +00002412 sh_mobile_lcdc_start(ch->lcdc);
Guennadi Liakhovetski6011bde2010-07-21 10:13:21 +00002413 }
2414
Guennadi Liakhovetskibaf16372010-09-03 07:20:08 +00002415 return NOTIFY_OK;
Guennadi Liakhovetski6011bde2010-07-21 10:13:21 +00002416}
2417
Laurent Pinchartf1f60b52011-09-07 11:09:26 +02002418/* -----------------------------------------------------------------------------
2419 * Probe/remove and driver init/exit
2420 */
2421
Greg Kroah-Hartman48c68c42012-12-21 13:07:39 -08002422static const struct fb_videomode default_720p = {
Laurent Pinchartf1f60b52011-09-07 11:09:26 +02002423 .name = "HDMI 720p",
2424 .xres = 1280,
2425 .yres = 720,
2426
2427 .left_margin = 220,
2428 .right_margin = 110,
2429 .hsync_len = 40,
2430
2431 .upper_margin = 20,
2432 .lower_margin = 5,
2433 .vsync_len = 5,
2434
2435 .pixclock = 13468,
2436 .refresh = 60,
2437 .sync = FB_SYNC_VERT_HIGH_ACT | FB_SYNC_HOR_HIGH_ACT,
2438};
2439
Laurent Pinchartb4bee692011-08-31 13:00:57 +02002440static int sh_mobile_lcdc_remove(struct platform_device *pdev)
2441{
2442 struct sh_mobile_lcdc_priv *priv = platform_get_drvdata(pdev);
Laurent Pinchartc5deac32011-12-12 18:43:16 +01002443 unsigned int i;
Laurent Pinchartb4bee692011-08-31 13:00:57 +02002444
2445 fb_unregister_client(&priv->notifier);
2446
Laurent Pinchartc5deac32011-12-12 18:43:16 +01002447 for (i = 0; i < ARRAY_SIZE(priv->overlays); i++)
2448 sh_mobile_lcdc_overlay_fb_unregister(&priv->overlays[i]);
Laurent Pinchartb4bee692011-08-31 13:00:57 +02002449 for (i = 0; i < ARRAY_SIZE(priv->ch); i++)
Laurent Pincharta67f3792011-11-29 14:37:35 +01002450 sh_mobile_lcdc_channel_fb_unregister(&priv->ch[i]);
Laurent Pinchartb4bee692011-08-31 13:00:57 +02002451
2452 sh_mobile_lcdc_stop(priv);
2453
Laurent Pinchartc5deac32011-12-12 18:43:16 +01002454 for (i = 0; i < ARRAY_SIZE(priv->overlays); i++) {
2455 struct sh_mobile_lcdc_overlay *ovl = &priv->overlays[i];
2456
2457 sh_mobile_lcdc_overlay_fb_cleanup(ovl);
2458
2459 if (ovl->fb_mem)
2460 dma_free_coherent(&pdev->dev, ovl->fb_size,
2461 ovl->fb_mem, ovl->dma_handle);
2462 }
2463
Laurent Pinchartb4bee692011-08-31 13:00:57 +02002464 for (i = 0; i < ARRAY_SIZE(priv->ch); i++) {
Laurent Pinchart9a2985e2011-09-11 22:59:04 +02002465 struct sh_mobile_lcdc_chan *ch = &priv->ch[i];
Laurent Pinchartb4bee692011-08-31 13:00:57 +02002466
Laurent Pincharte34d0bb2011-09-18 12:21:17 +02002467 if (ch->tx_dev) {
2468 ch->tx_dev->lcdc = NULL;
Laurent Pinchartb5ef9672011-11-22 00:56:58 +01002469 module_put(ch->cfg->tx_dev->dev.driver->owner);
Laurent Pincharte34d0bb2011-09-18 12:21:17 +02002470 }
Laurent Pinchart9a2985e2011-09-11 22:59:04 +02002471
Laurent Pincharta67f3792011-11-29 14:37:35 +01002472 sh_mobile_lcdc_channel_fb_cleanup(ch);
Laurent Pinchartb4bee692011-08-31 13:00:57 +02002473
Laurent Pincharta67f3792011-11-29 14:37:35 +01002474 if (ch->fb_mem)
2475 dma_free_coherent(&pdev->dev, ch->fb_size,
2476 ch->fb_mem, ch->dma_handle);
Laurent Pinchartb4bee692011-08-31 13:00:57 +02002477 }
2478
2479 for (i = 0; i < ARRAY_SIZE(priv->ch); i++) {
Laurent Pinchart0c75c4e2012-03-15 18:34:05 +01002480 struct sh_mobile_lcdc_chan *ch = &priv->ch[i];
2481
2482 if (ch->bl)
2483 sh_mobile_lcdc_bl_remove(ch->bl);
2484 mutex_destroy(&ch->open_lock);
Laurent Pinchartb4bee692011-08-31 13:00:57 +02002485 }
2486
Laurent Pinchart4774c122011-09-07 15:47:07 +02002487 if (priv->dot_clk) {
2488 pm_runtime_disable(&pdev->dev);
Laurent Pinchartb4bee692011-08-31 13:00:57 +02002489 clk_put(priv->dot_clk);
Laurent Pinchart4774c122011-09-07 15:47:07 +02002490 }
Laurent Pinchartb4bee692011-08-31 13:00:57 +02002491
2492 if (priv->base)
2493 iounmap(priv->base);
2494
2495 if (priv->irq)
2496 free_irq(priv->irq, priv);
2497 kfree(priv);
2498 return 0;
2499}
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07002500
Greg Kroah-Hartman48c68c42012-12-21 13:07:39 -08002501static int sh_mobile_lcdc_check_interface(struct sh_mobile_lcdc_chan *ch)
Laurent Pinchartf1f60b52011-09-07 11:09:26 +02002502{
Laurent Pinchartb5ef9672011-11-22 00:56:58 +01002503 int interface_type = ch->cfg->interface_type;
Laurent Pinchartf1f60b52011-09-07 11:09:26 +02002504
2505 switch (interface_type) {
2506 case RGB8:
2507 case RGB9:
2508 case RGB12A:
2509 case RGB12B:
2510 case RGB16:
2511 case RGB18:
2512 case RGB24:
2513 case SYS8A:
2514 case SYS8B:
2515 case SYS8C:
2516 case SYS8D:
2517 case SYS9:
2518 case SYS12:
2519 case SYS16A:
2520 case SYS16B:
2521 case SYS16C:
2522 case SYS18:
2523 case SYS24:
2524 break;
2525 default:
2526 return -EINVAL;
2527 }
2528
2529 /* SUBLCD only supports SYS interface */
2530 if (lcdc_chan_is_sublcd(ch)) {
2531 if (!(interface_type & LDMT1R_IFM))
2532 return -EINVAL;
2533
2534 interface_type &= ~LDMT1R_IFM;
2535 }
2536
2537 ch->ldmt1r_value = interface_type;
2538 return 0;
2539}
2540
Greg Kroah-Hartman48c68c42012-12-21 13:07:39 -08002541static int
Laurent Pinchart07073302012-08-13 13:56:46 +02002542sh_mobile_lcdc_overlay_init(struct sh_mobile_lcdc_overlay *ovl)
Laurent Pinchartc5deac32011-12-12 18:43:16 +01002543{
2544 const struct sh_mobile_lcdc_format_info *format;
Laurent Pinchart07073302012-08-13 13:56:46 +02002545 struct device *dev = ovl->channel->lcdc->dev;
Laurent Pinchartc5deac32011-12-12 18:43:16 +01002546 int ret;
2547
2548 if (ovl->cfg->fourcc == 0)
2549 return 0;
2550
2551 /* Validate the format. */
2552 format = sh_mobile_format_info(ovl->cfg->fourcc);
2553 if (format == NULL) {
Laurent Pinchart07073302012-08-13 13:56:46 +02002554 dev_err(dev, "Invalid FOURCC %08x\n", ovl->cfg->fourcc);
Laurent Pinchartc5deac32011-12-12 18:43:16 +01002555 return -EINVAL;
2556 }
2557
2558 ovl->enabled = false;
2559 ovl->mode = LCDC_OVERLAY_BLEND;
2560 ovl->alpha = 255;
2561 ovl->rop3 = 0;
2562 ovl->pos_x = 0;
2563 ovl->pos_y = 0;
2564
2565 /* The default Y virtual resolution is twice the panel size to allow for
2566 * double-buffering.
2567 */
2568 ovl->format = format;
2569 ovl->xres = ovl->cfg->max_xres;
2570 ovl->xres_virtual = ovl->xres;
2571 ovl->yres = ovl->cfg->max_yres;
2572 ovl->yres_virtual = ovl->yres * 2;
2573
2574 if (!format->yuv)
Laurent Pinchart16ca21c2012-07-18 16:29:20 +02002575 ovl->pitch = ovl->xres_virtual * format->bpp / 8;
Laurent Pinchartc5deac32011-12-12 18:43:16 +01002576 else
Laurent Pinchart16ca21c2012-07-18 16:29:20 +02002577 ovl->pitch = ovl->xres_virtual;
Laurent Pinchartc5deac32011-12-12 18:43:16 +01002578
2579 /* Allocate frame buffer memory. */
2580 ovl->fb_size = ovl->cfg->max_xres * ovl->cfg->max_yres
2581 * format->bpp / 8 * 2;
Laurent Pinchart07073302012-08-13 13:56:46 +02002582 ovl->fb_mem = dma_alloc_coherent(dev, ovl->fb_size, &ovl->dma_handle,
2583 GFP_KERNEL);
Laurent Pinchartc5deac32011-12-12 18:43:16 +01002584 if (!ovl->fb_mem) {
Laurent Pinchart07073302012-08-13 13:56:46 +02002585 dev_err(dev, "unable to allocate buffer\n");
Laurent Pinchartc5deac32011-12-12 18:43:16 +01002586 return -ENOMEM;
2587 }
2588
2589 ret = sh_mobile_lcdc_overlay_fb_init(ovl);
2590 if (ret < 0)
2591 return ret;
2592
2593 return 0;
2594}
2595
Greg Kroah-Hartman48c68c42012-12-21 13:07:39 -08002596static int
Laurent Pinchart07073302012-08-13 13:56:46 +02002597sh_mobile_lcdc_channel_init(struct sh_mobile_lcdc_chan *ch)
Laurent Pinchart3ce05592011-08-31 13:00:58 +02002598{
Laurent Pinchart105784b2011-11-29 15:58:10 +01002599 const struct sh_mobile_lcdc_format_info *format;
Laurent Pinchartb5ef9672011-11-22 00:56:58 +01002600 const struct sh_mobile_lcdc_chan_cfg *cfg = ch->cfg;
Laurent Pinchart07073302012-08-13 13:56:46 +02002601 struct device *dev = ch->lcdc->dev;
Laurent Pinchart3ce05592011-08-31 13:00:58 +02002602 const struct fb_videomode *max_mode;
2603 const struct fb_videomode *mode;
Laurent Pincharta67f3792011-11-29 14:37:35 +01002604 unsigned int num_modes;
Laurent Pinchart3ce05592011-08-31 13:00:58 +02002605 unsigned int max_size;
Laurent Pincharta67f3792011-11-29 14:37:35 +01002606 unsigned int i;
Laurent Pinchart3ce05592011-08-31 13:00:58 +02002607
Laurent Pincharta67472a2011-08-31 13:00:59 +02002608 mutex_init(&ch->open_lock);
Laurent Pinchartecd29942011-09-18 14:14:46 +02002609 ch->notify = sh_mobile_lcdc_display_notify;
Laurent Pincharta67472a2011-08-31 13:00:59 +02002610
Laurent Pinchart105784b2011-11-29 15:58:10 +01002611 /* Validate the format. */
2612 format = sh_mobile_format_info(cfg->fourcc);
2613 if (format == NULL) {
Laurent Pinchart07073302012-08-13 13:56:46 +02002614 dev_err(dev, "Invalid FOURCC %08x.\n", cfg->fourcc);
Laurent Pinchart105784b2011-11-29 15:58:10 +01002615 return -EINVAL;
2616 }
2617
Laurent Pinchart3ce05592011-08-31 13:00:58 +02002618 /* Iterate through the modes to validate them and find the highest
2619 * resolution.
2620 */
2621 max_mode = NULL;
2622 max_size = 0;
2623
Laurent Pinchart93ff2592011-11-29 14:33:41 +01002624 for (i = 0, mode = cfg->lcd_modes; i < cfg->num_modes; i++, mode++) {
Laurent Pinchart3ce05592011-08-31 13:00:58 +02002625 unsigned int size = mode->yres * mode->xres;
2626
Laurent Pinchartedd153a2011-12-13 14:02:28 +01002627 /* NV12/NV21 buffers must have even number of lines */
2628 if ((cfg->fourcc == V4L2_PIX_FMT_NV12 ||
2629 cfg->fourcc == V4L2_PIX_FMT_NV21) && (mode->yres & 0x1)) {
Laurent Pinchart07073302012-08-13 13:56:46 +02002630 dev_err(dev, "yres must be multiple of 2 for "
Laurent Pinchart0a7f17a2011-09-07 16:02:31 +02002631 "YCbCr420 mode.\n");
Laurent Pinchart3ce05592011-08-31 13:00:58 +02002632 return -EINVAL;
2633 }
2634
2635 if (size > max_size) {
2636 max_mode = mode;
2637 max_size = size;
2638 }
2639 }
2640
2641 if (!max_size)
2642 max_size = MAX_XRES * MAX_YRES;
2643 else
Laurent Pinchart07073302012-08-13 13:56:46 +02002644 dev_dbg(dev, "Found largest videomode %ux%u\n",
Laurent Pinchart3ce05592011-08-31 13:00:58 +02002645 max_mode->xres, max_mode->yres);
2646
Laurent Pinchart93ff2592011-11-29 14:33:41 +01002647 if (cfg->lcd_modes == NULL) {
Laurent Pinchart3ce05592011-08-31 13:00:58 +02002648 mode = &default_720p;
Laurent Pinchart93ff2592011-11-29 14:33:41 +01002649 num_modes = 1;
Laurent Pinchart3ce05592011-08-31 13:00:58 +02002650 } else {
Laurent Pinchart93ff2592011-11-29 14:33:41 +01002651 mode = cfg->lcd_modes;
2652 num_modes = cfg->num_modes;
Laurent Pinchart3ce05592011-08-31 13:00:58 +02002653 }
2654
Laurent Pinchartbd5f2c62012-07-18 16:59:16 +02002655 /* Use the first mode as default. The default Y virtual resolution is
2656 * twice the panel size to allow for double-buffering.
2657 */
Laurent Pinchart58f03d92011-11-30 23:07:30 +01002658 ch->format = format;
2659 ch->xres = mode->xres;
2660 ch->xres_virtual = mode->xres;
2661 ch->yres = mode->yres;
2662 ch->yres_virtual = mode->yres * 2;
2663
2664 if (!format->yuv) {
2665 ch->colorspace = V4L2_COLORSPACE_SRGB;
Laurent Pinchart16ca21c2012-07-18 16:29:20 +02002666 ch->pitch = ch->xres_virtual * format->bpp / 8;
Laurent Pinchart58f03d92011-11-30 23:07:30 +01002667 } else {
2668 ch->colorspace = V4L2_COLORSPACE_REC709;
Laurent Pinchart16ca21c2012-07-18 16:29:20 +02002669 ch->pitch = ch->xres_virtual;
Laurent Pinchart58f03d92011-11-30 23:07:30 +01002670 }
2671
Laurent Pincharta67f3792011-11-29 14:37:35 +01002672 ch->display.width = cfg->panel_cfg.width;
2673 ch->display.height = cfg->panel_cfg.height;
2674 ch->display.mode = *mode;
2675
2676 /* Allocate frame buffer memory. */
2677 ch->fb_size = max_size * format->bpp / 8 * 2;
Laurent Pinchart07073302012-08-13 13:56:46 +02002678 ch->fb_mem = dma_alloc_coherent(dev, ch->fb_size, &ch->dma_handle,
Laurent Pincharta67f3792011-11-29 14:37:35 +01002679 GFP_KERNEL);
2680 if (ch->fb_mem == NULL) {
Laurent Pinchart07073302012-08-13 13:56:46 +02002681 dev_err(dev, "unable to allocate buffer\n");
Laurent Pincharta67f3792011-11-29 14:37:35 +01002682 return -ENOMEM;
2683 }
Laurent Pinchart3ce05592011-08-31 13:00:58 +02002684
Laurent Pinchart13f80ee2011-11-29 01:46:12 +01002685 /* Initialize the transmitter device if present. */
2686 if (cfg->tx_dev) {
2687 if (!cfg->tx_dev->dev.driver ||
2688 !try_module_get(cfg->tx_dev->dev.driver->owner)) {
Laurent Pinchart07073302012-08-13 13:56:46 +02002689 dev_warn(dev, "unable to get transmitter device\n");
Laurent Pinchart13f80ee2011-11-29 01:46:12 +01002690 return -EINVAL;
2691 }
2692 ch->tx_dev = platform_get_drvdata(cfg->tx_dev);
2693 ch->tx_dev->lcdc = ch;
2694 ch->tx_dev->def_mode = *mode;
2695 }
2696
Laurent Pincharta67f3792011-11-29 14:37:35 +01002697 return sh_mobile_lcdc_channel_fb_init(ch, mode, num_modes);
Laurent Pinchart3ce05592011-08-31 13:00:58 +02002698}
2699
Greg Kroah-Hartman48c68c42012-12-21 13:07:39 -08002700static int sh_mobile_lcdc_probe(struct platform_device *pdev)
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07002701{
Guennadi Liakhovetski01ac25b2010-09-03 07:20:00 +00002702 struct sh_mobile_lcdc_info *pdata = pdev->dev.platform_data;
Laurent Pinchart3ce05592011-08-31 13:00:58 +02002703 struct sh_mobile_lcdc_priv *priv;
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07002704 struct resource *res;
Laurent Pinchart3ce05592011-08-31 13:00:58 +02002705 int num_channels;
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07002706 int error;
Laurent Pinchart3ce05592011-08-31 13:00:58 +02002707 int i;
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07002708
Guennadi Liakhovetski01ac25b2010-09-03 07:20:00 +00002709 if (!pdata) {
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07002710 dev_err(&pdev->dev, "no platform data defined\n");
Guennadi Liakhovetski8bed9052010-04-30 16:07:00 +00002711 return -EINVAL;
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07002712 }
2713
2714 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
Magnus Damm85645572008-12-19 15:34:41 +09002715 i = platform_get_irq(pdev, 0);
2716 if (!res || i < 0) {
2717 dev_err(&pdev->dev, "cannot get platform resources\n");
Guennadi Liakhovetski8bed9052010-04-30 16:07:00 +00002718 return -ENOENT;
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07002719 }
2720
2721 priv = kzalloc(sizeof(*priv), GFP_KERNEL);
2722 if (!priv) {
2723 dev_err(&pdev->dev, "cannot allocate device data\n");
Guennadi Liakhovetski8bed9052010-04-30 16:07:00 +00002724 return -ENOMEM;
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07002725 }
2726
Laurent Pinchart4774c122011-09-07 15:47:07 +02002727 priv->dev = &pdev->dev;
2728 priv->meram_dev = pdata->meram_dev;
Guennadi Liakhovetski8bed9052010-04-30 16:07:00 +00002729 platform_set_drvdata(pdev, priv);
2730
Yong Zhangf8798cc2011-09-22 16:59:16 +08002731 error = request_irq(i, sh_mobile_lcdc_irq, 0,
Kay Sievers7ad33e72009-03-24 16:38:21 -07002732 dev_name(&pdev->dev), priv);
Magnus Damm85645572008-12-19 15:34:41 +09002733 if (error) {
2734 dev_err(&pdev->dev, "unable to request irq\n");
2735 goto err1;
2736 }
2737
2738 priv->irq = i;
Guennadi Liakhovetski5ef6b502010-09-03 07:19:57 +00002739 atomic_set(&priv->hw_usecnt, -1);
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07002740
Laurent Pinchart3ce05592011-08-31 13:00:58 +02002741 for (i = 0, num_channels = 0; i < ARRAY_SIZE(pdata->ch); i++) {
2742 struct sh_mobile_lcdc_chan *ch = priv->ch + num_channels;
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07002743
Guennadi Liakhovetski01ac25b2010-09-03 07:20:00 +00002744 ch->lcdc = priv;
Laurent Pinchartb5ef9672011-11-22 00:56:58 +01002745 ch->cfg = &pdata->ch[i];
Guennadi Liakhovetski01ac25b2010-09-03 07:20:00 +00002746
2747 error = sh_mobile_lcdc_check_interface(ch);
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07002748 if (error) {
2749 dev_err(&pdev->dev, "unsupported interface type\n");
2750 goto err1;
2751 }
Guennadi Liakhovetski01ac25b2010-09-03 07:20:00 +00002752 init_waitqueue_head(&ch->frame_end_wait);
2753 init_completion(&ch->vsync_completion);
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07002754
Alexandre Courbot3b0fd9d2011-02-16 03:49:01 +00002755 /* probe the backlight is there is one defined */
Laurent Pinchartb5ef9672011-11-22 00:56:58 +01002756 if (ch->cfg->bl_info.max_brightness)
Alexandre Courbot3b0fd9d2011-02-16 03:49:01 +00002757 ch->bl = sh_mobile_lcdc_bl_probe(&pdev->dev, ch);
2758
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07002759 switch (pdata->ch[i].chan) {
2760 case LCDC_CHAN_MAINLCD:
Laurent Pinchartce1c0b02011-07-13 12:13:47 +02002761 ch->enabled = LDCNT2R_ME;
Guennadi Liakhovetski01ac25b2010-09-03 07:20:00 +00002762 ch->reg_offs = lcdc_offs_mainlcd;
Laurent Pinchart3ce05592011-08-31 13:00:58 +02002763 num_channels++;
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07002764 break;
2765 case LCDC_CHAN_SUBLCD:
Laurent Pinchartce1c0b02011-07-13 12:13:47 +02002766 ch->enabled = LDCNT2R_SE;
Guennadi Liakhovetski01ac25b2010-09-03 07:20:00 +00002767 ch->reg_offs = lcdc_offs_sublcd;
Laurent Pinchart3ce05592011-08-31 13:00:58 +02002768 num_channels++;
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07002769 break;
2770 }
2771 }
2772
Laurent Pinchart3ce05592011-08-31 13:00:58 +02002773 if (!num_channels) {
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07002774 dev_err(&pdev->dev, "no channels defined\n");
2775 error = -EINVAL;
2776 goto err1;
2777 }
2778
Laurent Pinchartedd153a2011-12-13 14:02:28 +01002779 /* for dual channel LCDC (MAIN + SUB) force shared format setting */
Laurent Pinchart3ce05592011-08-31 13:00:58 +02002780 if (num_channels == 2)
Laurent Pinchartedd153a2011-12-13 14:02:28 +01002781 priv->forced_fourcc = pdata->ch[0].fourcc;
Magnus Damm417d4822011-01-05 10:21:00 +00002782
Guennadi Liakhovetskidba6f382010-06-30 09:26:35 +00002783 priv->base = ioremap_nocache(res->start, resource_size(res));
2784 if (!priv->base)
2785 goto err1;
2786
Laurent Pinchart0a7f17a2011-09-07 16:02:31 +02002787 error = sh_mobile_lcdc_setup_clocks(priv, pdata->clock_source);
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07002788 if (error) {
2789 dev_err(&pdev->dev, "unable to setup clocks\n");
2790 goto err1;
2791 }
2792
Laurent Pinchart4774c122011-09-07 15:47:07 +02002793 /* Enable runtime PM. */
2794 pm_runtime_enable(&pdev->dev);
Damian7caa4342011-05-18 11:10:07 +00002795
Laurent Pinchart3ce05592011-08-31 13:00:58 +02002796 for (i = 0; i < num_channels; i++) {
Laurent Pinchart07073302012-08-13 13:56:46 +02002797 struct sh_mobile_lcdc_chan *ch = &priv->ch[i];
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07002798
Laurent Pinchart07073302012-08-13 13:56:46 +02002799 error = sh_mobile_lcdc_channel_init(ch);
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07002800 if (error)
Laurent Pinchart3ce05592011-08-31 13:00:58 +02002801 goto err1;
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07002802 }
2803
Laurent Pinchartc5deac32011-12-12 18:43:16 +01002804 for (i = 0; i < ARRAY_SIZE(pdata->overlays); i++) {
2805 struct sh_mobile_lcdc_overlay *ovl = &priv->overlays[i];
2806
2807 ovl->cfg = &pdata->overlays[i];
2808 ovl->channel = &priv->ch[0];
2809
Laurent Pinchart07073302012-08-13 13:56:46 +02002810 error = sh_mobile_lcdc_overlay_init(ovl);
Laurent Pinchartc5deac32011-12-12 18:43:16 +01002811 if (error)
2812 goto err1;
2813 }
2814
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07002815 error = sh_mobile_lcdc_start(priv);
2816 if (error) {
2817 dev_err(&pdev->dev, "unable to start hardware\n");
2818 goto err1;
2819 }
2820
Laurent Pinchart3ce05592011-08-31 13:00:58 +02002821 for (i = 0; i < num_channels; i++) {
Paul Mundt1c6a3072009-07-01 06:50:31 +00002822 struct sh_mobile_lcdc_chan *ch = priv->ch + i;
Paul Mundt1c6a3072009-07-01 06:50:31 +00002823
Laurent Pincharta67f3792011-11-29 14:37:35 +01002824 error = sh_mobile_lcdc_channel_fb_register(ch);
2825 if (error)
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07002826 goto err1;
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07002827 }
2828
Laurent Pinchartc5deac32011-12-12 18:43:16 +01002829 for (i = 0; i < ARRAY_SIZE(pdata->overlays); i++) {
2830 struct sh_mobile_lcdc_overlay *ovl = &priv->overlays[i];
2831
2832 error = sh_mobile_lcdc_overlay_fb_register(ovl);
2833 if (error)
2834 goto err1;
2835 }
2836
Guennadi Liakhovetski6011bde2010-07-21 10:13:21 +00002837 /* Failure ignored */
2838 priv->notifier.notifier_call = sh_mobile_lcdc_notify;
2839 fb_register_client(&priv->notifier);
2840
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07002841 return 0;
Guennadi Liakhovetski8bed9052010-04-30 16:07:00 +00002842err1:
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07002843 sh_mobile_lcdc_remove(pdev);
Guennadi Liakhovetski8bed9052010-04-30 16:07:00 +00002844
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07002845 return error;
2846}
2847
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07002848static struct platform_driver sh_mobile_lcdc_driver = {
2849 .driver = {
2850 .name = "sh_mobile_lcdc_fb",
Magnus Damm2feb0752009-03-13 15:36:55 +00002851 .pm = &sh_mobile_lcdc_dev_pm_ops,
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07002852 },
2853 .probe = sh_mobile_lcdc_probe,
2854 .remove = sh_mobile_lcdc_remove,
2855};
2856
Axel Lin4277f2c2011-11-26 10:25:54 +08002857module_platform_driver(sh_mobile_lcdc_driver);
Magnus Dammcfb4f5d2008-07-23 21:31:24 -07002858
2859MODULE_DESCRIPTION("SuperH Mobile LCDC Framebuffer driver");
2860MODULE_AUTHOR("Magnus Damm <damm@opensource.se>");
2861MODULE_LICENSE("GPL v2");