Blame view

drivers/video/sh_mobile_lcdcfb.c 45.5 KB
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  /*
   * SuperH Mobile LCDC Framebuffer
   *
   * Copyright (c) 2008 Magnus Damm
   *
   * This file is subject to the terms and conditions of the GNU General Public
   * License.  See the file "COPYING" in the main directory of this archive
   * for more details.
   */
  
  #include <linux/kernel.h>
  #include <linux/init.h>
  #include <linux/delay.h>
  #include <linux/mm.h>
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
15
  #include <linux/clk.h>
0246c4712   Magnus Damm   video: Runtime PM...
16
  #include <linux/pm_runtime.h>
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
17
18
  #include <linux/platform_device.h>
  #include <linux/dma-mapping.h>
8564557a0   Magnus Damm   video: sh_mobile_...
19
  #include <linux/interrupt.h>
edd153a3e   Laurent Pinchart   fbdev: sh_mobile_...
20
  #include <linux/videodev2.h>
1c6a307a5   Paul Mundt   sh: LCDC dcache f...
21
  #include <linux/vmalloc.h>
40331b21f   Phil Edworthy   video: sh_mobile_...
22
  #include <linux/ioctl.h>
5a0e3ad6a   Tejun Heo   include cleanup: ...
23
  #include <linux/slab.h>
dd210503b   Guennadi Liakhovetski   fbdev: sh_mobile_...
24
  #include <linux/console.h>
3b0fd9d75   Alexandre Courbot   fbdev: sh_mobile_...
25
26
  #include <linux/backlight.h>
  #include <linux/gpio.h>
355b200ba   Paul Gortmaker   video: Add module...
27
  #include <linux/module.h>
225c9a8d1   Paul Mundt   video: sh_mobile_...
28
  #include <video/sh_mobile_lcdc.h>
8a20974f0   Laurent Pinchart   fbdev: sh_mobile_...
29
  #include <video/sh_mobile_meram.h>
60063497a   Arun Sharma   atomic: use <linu...
30
  #include <linux/atomic.h>
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
31

6de9edd5b   Guennadi Liakhovetski   fbdev: sh_mobile_...
32
  #include "sh_mobile_lcdcfb.h"
a6f15ade9   Phil Edworthy   video: sh_mobile_...
33
34
  #define SIDE_B_OFFSET 0x1000
  #define MIRROR_OFFSET 0x2000
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
35

d2ecbab59   Guennadi Liakhovetski   fbdev: sh_mobile_...
36
37
  #define MAX_XRES 1920
  #define MAX_YRES 1080
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
38

0246c4712   Magnus Damm   video: Runtime PM...
39
  static unsigned long lcdc_offs_mainlcd[NR_CH_REGS] = {
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
40
41
42
43
44
45
46
  	[LDDCKPAT1R] = 0x400,
  	[LDDCKPAT2R] = 0x404,
  	[LDMT1R] = 0x418,
  	[LDMT2R] = 0x41c,
  	[LDMT3R] = 0x420,
  	[LDDFR] = 0x424,
  	[LDSM1R] = 0x428,
8564557a0   Magnus Damm   video: sh_mobile_...
47
  	[LDSM2R] = 0x42c,
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
48
  	[LDSA1R] = 0x430,
53b503143   Damian Hobson-Garcia   fbdev: sh_mobile_...
49
  	[LDSA2R] = 0x434,
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
50
51
52
53
54
55
  	[LDMLSR] = 0x438,
  	[LDHCNR] = 0x448,
  	[LDHSYNR] = 0x44c,
  	[LDVLNR] = 0x450,
  	[LDVSYNR] = 0x454,
  	[LDPMR] = 0x460,
6011bdeaa   Guennadi Liakhovetski   fbdev: sh-mobile:...
56
  	[LDHAJR] = 0x4a0,
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
57
  };
0246c4712   Magnus Damm   video: Runtime PM...
58
  static unsigned long lcdc_offs_sublcd[NR_CH_REGS] = {
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
59
60
61
62
63
64
65
  	[LDDCKPAT1R] = 0x408,
  	[LDDCKPAT2R] = 0x40c,
  	[LDMT1R] = 0x600,
  	[LDMT2R] = 0x604,
  	[LDMT3R] = 0x608,
  	[LDDFR] = 0x60c,
  	[LDSM1R] = 0x610,
8564557a0   Magnus Damm   video: sh_mobile_...
66
  	[LDSM2R] = 0x614,
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
67
68
69
70
71
72
73
74
  	[LDSA1R] = 0x618,
  	[LDMLSR] = 0x620,
  	[LDHCNR] = 0x624,
  	[LDHSYNR] = 0x628,
  	[LDVLNR] = 0x62c,
  	[LDVSYNR] = 0x630,
  	[LDPMR] = 0x63c,
  };
c44f9f76d   Guennadi Liakhovetski   fbdev: sh_mobile_...
75
76
77
78
  static const struct fb_videomode default_720p = {
  	.name = "HDMI 720p",
  	.xres = 1280,
  	.yres = 720,
5ae0cf82d   Guennadi Liakhovetski   fbdev: sh_mobile_...
79
80
81
  	.left_margin = 220,
  	.right_margin = 110,
  	.hsync_len = 40,
c44f9f76d   Guennadi Liakhovetski   fbdev: sh_mobile_...
82
83
84
85
86
87
  
  	.upper_margin = 20,
  	.lower_margin = 5,
  	.vsync_len = 5,
  
  	.pixclock = 13468,
5ae0cf82d   Guennadi Liakhovetski   fbdev: sh_mobile_...
88
  	.refresh = 60,
c44f9f76d   Guennadi Liakhovetski   fbdev: sh_mobile_...
89
  	.sync = FB_SYNC_VERT_HIGH_ACT | FB_SYNC_HOR_HIGH_ACT,
0246c4712   Magnus Damm   video: Runtime PM...
90
91
92
93
94
95
96
97
98
99
  };
  
  struct sh_mobile_lcdc_priv {
  	void __iomem *base;
  	int irq;
  	atomic_t hw_usecnt;
  	struct device *dev;
  	struct clk *dot_clk;
  	unsigned long lddckr;
  	struct sh_mobile_lcdc_chan ch[2];
6011bdeaa   Guennadi Liakhovetski   fbdev: sh-mobile:...
100
  	struct notifier_block notifier;
0246c4712   Magnus Damm   video: Runtime PM...
101
  	int started;
edd153a3e   Laurent Pinchart   fbdev: sh_mobile_...
102
  	int forced_fourcc; /* 2 channel LCDC must share fourcc setting */
7caa4342c   Damian Hobson-Garcia   sh_mobile_meram: ...
103
  	struct sh_mobile_meram_info *meram_dev;
0246c4712   Magnus Damm   video: Runtime PM...
104
  };
a6f15ade9   Phil Edworthy   video: sh_mobile_...
105
106
107
108
109
110
111
112
113
  static bool banked(int reg_nr)
  {
  	switch (reg_nr) {
  	case LDMT1R:
  	case LDMT2R:
  	case LDMT3R:
  	case LDDFR:
  	case LDSM1R:
  	case LDSA1R:
53b503143   Damian Hobson-Garcia   fbdev: sh_mobile_...
114
  	case LDSA2R:
a6f15ade9   Phil Edworthy   video: sh_mobile_...
115
116
117
118
119
120
121
122
123
  	case LDMLSR:
  	case LDHCNR:
  	case LDHSYNR:
  	case LDVLNR:
  	case LDVSYNR:
  		return true;
  	}
  	return false;
  }
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
124
125
126
127
  static void lcdc_write_chan(struct sh_mobile_lcdc_chan *chan,
  			    int reg_nr, unsigned long data)
  {
  	iowrite32(data, chan->lcdc->base + chan->reg_offs[reg_nr]);
a6f15ade9   Phil Edworthy   video: sh_mobile_...
128
129
130
131
132
133
134
135
136
137
  	if (banked(reg_nr))
  		iowrite32(data, chan->lcdc->base + chan->reg_offs[reg_nr] +
  			  SIDE_B_OFFSET);
  }
  
  static void lcdc_write_chan_mirror(struct sh_mobile_lcdc_chan *chan,
  			    int reg_nr, unsigned long data)
  {
  	iowrite32(data, chan->lcdc->base + chan->reg_offs[reg_nr] +
  		  MIRROR_OFFSET);
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
  }
  
  static unsigned long lcdc_read_chan(struct sh_mobile_lcdc_chan *chan,
  				    int reg_nr)
  {
  	return ioread32(chan->lcdc->base + chan->reg_offs[reg_nr]);
  }
  
  static void lcdc_write(struct sh_mobile_lcdc_priv *priv,
  		       unsigned long reg_offs, unsigned long data)
  {
  	iowrite32(data, priv->base + reg_offs);
  }
  
  static unsigned long lcdc_read(struct sh_mobile_lcdc_priv *priv,
  			       unsigned long reg_offs)
  {
  	return ioread32(priv->base + reg_offs);
  }
  
  static void lcdc_wait_bit(struct sh_mobile_lcdc_priv *priv,
  			  unsigned long reg_offs,
  			  unsigned long mask, unsigned long until)
  {
  	while ((lcdc_read(priv, reg_offs) & mask) != until)
  		cpu_relax();
  }
  
  static int lcdc_chan_is_sublcd(struct sh_mobile_lcdc_chan *chan)
  {
  	return chan->cfg.chan == LCDC_CHAN_SUBLCD;
  }
  
  static void lcdc_sys_write_index(void *handle, unsigned long data)
  {
  	struct sh_mobile_lcdc_chan *ch = handle;
ce1c0b087   Laurent Pinchart   fbdev: sh_mobile_...
174
175
176
177
178
  	lcdc_write(ch->lcdc, _LDDWD0R, data | LDDWDxR_WDACT);
  	lcdc_wait_bit(ch->lcdc, _LDSR, LDSR_AS, 0);
  	lcdc_write(ch->lcdc, _LDDWAR, LDDWAR_WA |
  		   (lcdc_chan_is_sublcd(ch) ? 2 : 0));
  	lcdc_wait_bit(ch->lcdc, _LDSR, LDSR_AS, 0);
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
179
180
181
182
183
  }
  
  static void lcdc_sys_write_data(void *handle, unsigned long data)
  {
  	struct sh_mobile_lcdc_chan *ch = handle;
ce1c0b087   Laurent Pinchart   fbdev: sh_mobile_...
184
185
186
187
188
  	lcdc_write(ch->lcdc, _LDDWD0R, data | LDDWDxR_WDACT | LDDWDxR_RSW);
  	lcdc_wait_bit(ch->lcdc, _LDSR, LDSR_AS, 0);
  	lcdc_write(ch->lcdc, _LDDWAR, LDDWAR_WA |
  		   (lcdc_chan_is_sublcd(ch) ? 2 : 0));
  	lcdc_wait_bit(ch->lcdc, _LDSR, LDSR_AS, 0);
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
189
190
191
192
193
  }
  
  static unsigned long lcdc_sys_read_data(void *handle)
  {
  	struct sh_mobile_lcdc_chan *ch = handle;
ce1c0b087   Laurent Pinchart   fbdev: sh_mobile_...
194
195
196
197
  	lcdc_write(ch->lcdc, _LDDRDR, LDDRDR_RSR);
  	lcdc_wait_bit(ch->lcdc, _LDSR, LDSR_AS, 0);
  	lcdc_write(ch->lcdc, _LDDRAR, LDDRAR_RA |
  		   (lcdc_chan_is_sublcd(ch) ? 2 : 0));
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
198
  	udelay(1);
ce1c0b087   Laurent Pinchart   fbdev: sh_mobile_...
199
  	lcdc_wait_bit(ch->lcdc, _LDSR, LDSR_AS, 0);
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
200

ce1c0b087   Laurent Pinchart   fbdev: sh_mobile_...
201
  	return lcdc_read(ch->lcdc, _LDDRDR) & LDDRDR_DRD_MASK;
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
202
203
204
205
206
207
208
  }
  
  struct sh_mobile_lcdc_sys_bus_ops sh_mobile_lcdc_sys_bus_ops = {
  	lcdc_sys_write_index,
  	lcdc_sys_write_data,
  	lcdc_sys_read_data,
  };
edd153a3e   Laurent Pinchart   fbdev: sh_mobile_...
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
  static int sh_mobile_format_fourcc(const struct fb_var_screeninfo *var)
  {
  	if (var->grayscale > 1)
  		return var->grayscale;
  
  	switch (var->bits_per_pixel) {
  	case 16:
  		return V4L2_PIX_FMT_RGB565;
  	case 24:
  		return V4L2_PIX_FMT_BGR24;
  	case 32:
  		return V4L2_PIX_FMT_BGR32;
  	default:
  		return 0;
  	}
  }
  
  static int sh_mobile_format_is_fourcc(const struct fb_var_screeninfo *var)
  {
  	return var->grayscale > 1;
  }
  
  static bool sh_mobile_format_is_yuv(const struct fb_var_screeninfo *var)
  {
  	if (var->grayscale <= 1)
  		return false;
  
  	switch (var->grayscale) {
  	case V4L2_PIX_FMT_NV12:
  	case V4L2_PIX_FMT_NV21:
  	case V4L2_PIX_FMT_NV16:
  	case V4L2_PIX_FMT_NV61:
  	case V4L2_PIX_FMT_NV24:
  	case V4L2_PIX_FMT_NV42:
  		return true;
  
  	default:
  		return false;
  	}
  }
8564557a0   Magnus Damm   video: sh_mobile_...
249
250
  static void sh_mobile_lcdc_clk_on(struct sh_mobile_lcdc_priv *priv)
  {
0246c4712   Magnus Damm   video: Runtime PM...
251
  	if (atomic_inc_and_test(&priv->hw_usecnt)) {
8564557a0   Magnus Damm   video: sh_mobile_...
252
253
  		if (priv->dot_clk)
  			clk_enable(priv->dot_clk);
f1ad90da5   Laurent Pinchart   fbdev: sh_mobile_...
254
  		pm_runtime_get_sync(priv->dev);
ec19b9e0f   Damian Hobson-Garcia   fbdev: sh_mobile_...
255
256
  		if (priv->meram_dev && priv->meram_dev->pdev)
  			pm_runtime_get_sync(&priv->meram_dev->pdev->dev);
8564557a0   Magnus Damm   video: sh_mobile_...
257
258
259
260
261
  	}
  }
  
  static void sh_mobile_lcdc_clk_off(struct sh_mobile_lcdc_priv *priv)
  {
0246c4712   Magnus Damm   video: Runtime PM...
262
  	if (atomic_sub_return(1, &priv->hw_usecnt) == -1) {
ec19b9e0f   Damian Hobson-Garcia   fbdev: sh_mobile_...
263
264
  		if (priv->meram_dev && priv->meram_dev->pdev)
  			pm_runtime_put_sync(&priv->meram_dev->pdev->dev);
0246c4712   Magnus Damm   video: Runtime PM...
265
  		pm_runtime_put(priv->dev);
f1ad90da5   Laurent Pinchart   fbdev: sh_mobile_...
266
267
  		if (priv->dot_clk)
  			clk_disable(priv->dot_clk);
8564557a0   Magnus Damm   video: sh_mobile_...
268
269
  	}
  }
8564557a0   Magnus Damm   video: sh_mobile_...
270

1c6a307a5   Paul Mundt   sh: LCDC dcache f...
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
  static int sh_mobile_lcdc_sginit(struct fb_info *info,
  				  struct list_head *pagelist)
  {
  	struct sh_mobile_lcdc_chan *ch = info->par;
  	unsigned int nr_pages_max = info->fix.smem_len >> PAGE_SHIFT;
  	struct page *page;
  	int nr_pages = 0;
  
  	sg_init_table(ch->sglist, nr_pages_max);
  
  	list_for_each_entry(page, pagelist, lru)
  		sg_set_page(&ch->sglist[nr_pages++], page, PAGE_SIZE, 0);
  
  	return nr_pages;
  }
8564557a0   Magnus Damm   video: sh_mobile_...
286
287
288
289
  static void sh_mobile_lcdc_deferred_io(struct fb_info *info,
  				       struct list_head *pagelist)
  {
  	struct sh_mobile_lcdc_chan *ch = info->par;
ef61aae4d   Magnus Damm   sh: add a start_t...
290
  	struct sh_mobile_lcdc_board_cfg	*bcfg = &ch->cfg.board_cfg;
8564557a0   Magnus Damm   video: sh_mobile_...
291
292
293
  
  	/* enable clocks before accessing hardware */
  	sh_mobile_lcdc_clk_on(ch->lcdc);
5c1a56b5f   Paul Mundt   video: sh_mobile_...
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
  	/*
  	 * It's possible to get here without anything on the pagelist via
  	 * sh_mobile_lcdc_deferred_io_touch() or via a userspace fsync()
  	 * invocation. In the former case, the acceleration routines are
  	 * stepped in to when using the framebuffer console causing the
  	 * workqueue to be scheduled without any dirty pages on the list.
  	 *
  	 * Despite this, a panel update is still needed given that the
  	 * acceleration routines have their own methods for writing in
  	 * that still need to be updated.
  	 *
  	 * The fsync() and empty pagelist case could be optimized for,
  	 * but we don't bother, as any application exhibiting such
  	 * behaviour is fundamentally broken anyways.
  	 */
  	if (!list_empty(pagelist)) {
  		unsigned int nr_pages = sh_mobile_lcdc_sginit(info, pagelist);
  
  		/* trigger panel update */
  		dma_map_sg(info->dev, ch->sglist, nr_pages, DMA_TO_DEVICE);
ef61aae4d   Magnus Damm   sh: add a start_t...
314
315
316
  		if (bcfg->start_transfer)
  			bcfg->start_transfer(bcfg->board_data, ch,
  					     &sh_mobile_lcdc_sys_bus_ops);
ce1c0b087   Laurent Pinchart   fbdev: sh_mobile_...
317
  		lcdc_write_chan(ch, LDSM2R, LDSM2R_OSTRG);
5c1a56b5f   Paul Mundt   video: sh_mobile_...
318
  		dma_unmap_sg(info->dev, ch->sglist, nr_pages, DMA_TO_DEVICE);
ef61aae4d   Magnus Damm   sh: add a start_t...
319
320
321
322
  	} else {
  		if (bcfg->start_transfer)
  			bcfg->start_transfer(bcfg->board_data, ch,
  					     &sh_mobile_lcdc_sys_bus_ops);
ce1c0b087   Laurent Pinchart   fbdev: sh_mobile_...
323
  		lcdc_write_chan(ch, LDSM2R, LDSM2R_OSTRG);
ef61aae4d   Magnus Damm   sh: add a start_t...
324
  	}
8564557a0   Magnus Damm   video: sh_mobile_...
325
326
327
328
329
330
331
332
333
334
335
336
337
  }
  
  static void sh_mobile_lcdc_deferred_io_touch(struct fb_info *info)
  {
  	struct fb_deferred_io *fbdefio = info->fbdefio;
  
  	if (fbdefio)
  		schedule_delayed_work(&info->deferred_work, fbdefio->delay);
  }
  
  static irqreturn_t sh_mobile_lcdc_irq(int irq, void *data)
  {
  	struct sh_mobile_lcdc_priv *priv = data;
2feb075a3   Magnus Damm   video: sh_mobile_...
338
  	struct sh_mobile_lcdc_chan *ch;
9dd38819c   Phil Edworthy   video: sh_mobile_...
339
  	unsigned long ldintr;
2feb075a3   Magnus Damm   video: sh_mobile_...
340
341
  	int is_sub;
  	int k;
8564557a0   Magnus Damm   video: sh_mobile_...
342

dc48665fa   Laurent Pinchart   fbdev: sh_mobile_...
343
344
345
  	/* Acknowledge interrupts and disable further VSYNC End IRQs. */
  	ldintr = lcdc_read(priv, _LDINTR);
  	lcdc_write(priv, _LDINTR, (ldintr ^ LDINTR_STATUS_MASK) & ~LDINTR_VEE);
8564557a0   Magnus Damm   video: sh_mobile_...
346

2feb075a3   Magnus Damm   video: sh_mobile_...
347
  	/* figure out if this interrupt is for main or sub lcd */
ce1c0b087   Laurent Pinchart   fbdev: sh_mobile_...
348
  	is_sub = (lcdc_read(priv, _LDSR) & LDSR_MSS) ? 1 : 0;
2feb075a3   Magnus Damm   video: sh_mobile_...
349

9dd38819c   Phil Edworthy   video: sh_mobile_...
350
  	/* wake up channel and disable clocks */
2feb075a3   Magnus Damm   video: sh_mobile_...
351
352
353
354
355
  	for (k = 0; k < ARRAY_SIZE(priv->ch); k++) {
  		ch = &priv->ch[k];
  
  		if (!ch->enabled)
  			continue;
dc48665fa   Laurent Pinchart   fbdev: sh_mobile_...
356
  		/* Frame End */
9dd38819c   Phil Edworthy   video: sh_mobile_...
357
358
359
360
  		if (ldintr & LDINTR_FS) {
  			if (is_sub == lcdc_chan_is_sublcd(ch)) {
  				ch->frame_end = 1;
  				wake_up(&ch->frame_end_wait);
2feb075a3   Magnus Damm   video: sh_mobile_...
361

9dd38819c   Phil Edworthy   video: sh_mobile_...
362
363
364
365
366
  				sh_mobile_lcdc_clk_off(priv);
  			}
  		}
  
  		/* VSYNC End */
40331b21f   Phil Edworthy   video: sh_mobile_...
367
368
  		if (ldintr & LDINTR_VES)
  			complete(&ch->vsync_completion);
2feb075a3   Magnus Damm   video: sh_mobile_...
369
  	}
8564557a0   Magnus Damm   video: sh_mobile_...
370
371
  	return IRQ_HANDLED;
  }
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
372
373
374
375
376
377
378
379
  static void sh_mobile_lcdc_start_stop(struct sh_mobile_lcdc_priv *priv,
  				      int start)
  {
  	unsigned long tmp = lcdc_read(priv, _LDCNT2R);
  	int k;
  
  	/* start or stop the lcdc */
  	if (start)
ce1c0b087   Laurent Pinchart   fbdev: sh_mobile_...
380
  		lcdc_write(priv, _LDCNT2R, tmp | LDCNT2R_DO);
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
381
  	else
ce1c0b087   Laurent Pinchart   fbdev: sh_mobile_...
382
  		lcdc_write(priv, _LDCNT2R, tmp & ~LDCNT2R_DO);
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
383
384
385
386
387
  
  	/* wait until power is applied/stopped on all channels */
  	for (k = 0; k < ARRAY_SIZE(priv->ch); k++)
  		if (lcdc_read(priv, _LDCNT2R) & priv->ch[k].enabled)
  			while (1) {
ce1c0b087   Laurent Pinchart   fbdev: sh_mobile_...
388
389
390
  				tmp = lcdc_read_chan(&priv->ch[k], LDPMR)
  				    & LDPMR_LPS;
  				if (start && tmp == LDPMR_LPS)
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
391
392
393
394
395
396
397
398
399
  					break;
  				if (!start && tmp == 0)
  					break;
  				cpu_relax();
  			}
  
  	if (!start)
  		lcdc_write(priv, _LDDCKSTPR, 1); /* stop dotclock */
  }
6011bdeaa   Guennadi Liakhovetski   fbdev: sh-mobile:...
400
401
  static void sh_mobile_lcdc_geometry(struct sh_mobile_lcdc_chan *ch)
  {
1c120deb6   Guennadi Liakhovetski   fbdev: sh_mobile_...
402
403
  	struct fb_var_screeninfo *var = &ch->info->var, *display_var = &ch->display_var;
  	unsigned long h_total, hsync_pos, display_h_total;
6011bdeaa   Guennadi Liakhovetski   fbdev: sh-mobile:...
404
405
406
  	u32 tmp;
  
  	tmp = ch->ldmt1r_value;
ce1c0b087   Laurent Pinchart   fbdev: sh_mobile_...
407
408
409
410
411
412
413
  	tmp |= (var->sync & FB_SYNC_VERT_HIGH_ACT) ? 0 : LDMT1R_VPOL;
  	tmp |= (var->sync & FB_SYNC_HOR_HIGH_ACT) ? 0 : LDMT1R_HPOL;
  	tmp |= (ch->cfg.flags & LCDC_FLAGS_DWPOL) ? LDMT1R_DWPOL : 0;
  	tmp |= (ch->cfg.flags & LCDC_FLAGS_DIPOL) ? LDMT1R_DIPOL : 0;
  	tmp |= (ch->cfg.flags & LCDC_FLAGS_DAPOL) ? LDMT1R_DAPOL : 0;
  	tmp |= (ch->cfg.flags & LCDC_FLAGS_HSCNT) ? LDMT1R_HSCNT : 0;
  	tmp |= (ch->cfg.flags & LCDC_FLAGS_DWCNT) ? LDMT1R_DWCNT : 0;
6011bdeaa   Guennadi Liakhovetski   fbdev: sh-mobile:...
414
415
416
417
418
419
420
  	lcdc_write_chan(ch, LDMT1R, tmp);
  
  	/* setup SYS bus */
  	lcdc_write_chan(ch, LDMT2R, ch->cfg.sys_bus_cfg.ldmt2r);
  	lcdc_write_chan(ch, LDMT3R, ch->cfg.sys_bus_cfg.ldmt3r);
  
  	/* horizontal configuration */
1c120deb6   Guennadi Liakhovetski   fbdev: sh_mobile_...
421
422
  	h_total = display_var->xres + display_var->hsync_len +
  		display_var->left_margin + display_var->right_margin;
6011bdeaa   Guennadi Liakhovetski   fbdev: sh-mobile:...
423
  	tmp = h_total / 8; /* HTCN */
1c120deb6   Guennadi Liakhovetski   fbdev: sh_mobile_...
424
  	tmp |= (min(display_var->xres, var->xres) / 8) << 16; /* HDCN */
6011bdeaa   Guennadi Liakhovetski   fbdev: sh-mobile:...
425
  	lcdc_write_chan(ch, LDHCNR, tmp);
1c120deb6   Guennadi Liakhovetski   fbdev: sh_mobile_...
426
  	hsync_pos = display_var->xres + display_var->right_margin;
6011bdeaa   Guennadi Liakhovetski   fbdev: sh-mobile:...
427
  	tmp = hsync_pos / 8; /* HSYNP */
1c120deb6   Guennadi Liakhovetski   fbdev: sh_mobile_...
428
  	tmp |= (display_var->hsync_len / 8) << 16; /* HSYNW */
6011bdeaa   Guennadi Liakhovetski   fbdev: sh-mobile:...
429
430
431
  	lcdc_write_chan(ch, LDHSYNR, tmp);
  
  	/* vertical configuration */
1c120deb6   Guennadi Liakhovetski   fbdev: sh_mobile_...
432
433
434
  	tmp = display_var->yres + display_var->vsync_len +
  		display_var->upper_margin + display_var->lower_margin; /* VTLN */
  	tmp |= min(display_var->yres, var->yres) << 16; /* VDLN */
6011bdeaa   Guennadi Liakhovetski   fbdev: sh-mobile:...
435
  	lcdc_write_chan(ch, LDVLNR, tmp);
1c120deb6   Guennadi Liakhovetski   fbdev: sh_mobile_...
436
437
  	tmp = display_var->yres + display_var->lower_margin; /* VSYNP */
  	tmp |= display_var->vsync_len << 16; /* VSYNW */
6011bdeaa   Guennadi Liakhovetski   fbdev: sh-mobile:...
438
439
440
  	lcdc_write_chan(ch, LDVSYNR, tmp);
  
  	/* Adjust horizontal synchronisation for HDMI */
1c120deb6   Guennadi Liakhovetski   fbdev: sh_mobile_...
441
442
443
444
445
  	display_h_total = display_var->xres + display_var->hsync_len +
  		display_var->left_margin + display_var->right_margin;
  	tmp = ((display_var->xres & 7) << 24) |
  		((display_h_total & 7) << 16) |
  		((display_var->hsync_len & 7) << 8) |
41e583c22   Kuninori Morimoto   fbdev: sh_mobile_...
446
  		(hsync_pos & 7);
6011bdeaa   Guennadi Liakhovetski   fbdev: sh-mobile:...
447
448
  	lcdc_write_chan(ch, LDHAJR, tmp);
  }
9a217e344   Laurent Pinchart   fbdev: sh_mobile_...
449
450
451
452
453
454
455
456
  /*
   * __sh_mobile_lcdc_start - Configure and tart the LCDC
   * @priv: LCDC device
   *
   * Configure all enabled channels and start the LCDC device. All external
   * devices (clocks, MERAM, panels, ...) are not touched by this function.
   */
  static void __sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv)
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
457
458
  {
  	struct sh_mobile_lcdc_chan *ch;
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
459
  	unsigned long tmp;
9a217e344   Laurent Pinchart   fbdev: sh_mobile_...
460
  	int k, m;
8564557a0   Magnus Damm   video: sh_mobile_...
461

9a217e344   Laurent Pinchart   fbdev: sh_mobile_...
462
463
464
465
  	/* Enable LCDC channels. Read data from external memory, avoid using the
  	 * BEU for now.
  	 */
  	lcdc_write(priv, _LDCNT2R, priv->ch[0].enabled | priv->ch[1].enabled);
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
466

9a217e344   Laurent Pinchart   fbdev: sh_mobile_...
467
  	/* Stop the LCDC first and disable all interrupts. */
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
468
  	sh_mobile_lcdc_start_stop(priv, 0);
9a217e344   Laurent Pinchart   fbdev: sh_mobile_...
469
  	lcdc_write(priv, _LDINTR, 0);
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
470

9a217e344   Laurent Pinchart   fbdev: sh_mobile_...
471
  	/* Configure power supply, dot clocks and start them. */
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
472
473
474
  	tmp = priv->lddckr;
  	for (k = 0; k < ARRAY_SIZE(priv->ch); k++) {
  		ch = &priv->ch[k];
9a217e344   Laurent Pinchart   fbdev: sh_mobile_...
475
  		if (!ch->enabled)
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
476
  			continue;
9a217e344   Laurent Pinchart   fbdev: sh_mobile_...
477
478
  		/* Power supply */
  		lcdc_write_chan(ch, LDPMR, 0);
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
479
480
481
  		m = ch->cfg.clock_divider;
  		if (!m)
  			continue;
505c7de51   Laurent Pinchart   fbdev: sh_mobile_...
482
483
484
485
486
  		/* FIXME: sh7724 can only use 42, 48, 54 and 60 for the divider
  		 * denominator.
  		 */
  		lcdc_write_chan(ch, LDDCKPAT1R, 0);
  		lcdc_write_chan(ch, LDDCKPAT2R, (1 << (m/2)) - 1);
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
487
  		if (m == 1)
ce1c0b087   Laurent Pinchart   fbdev: sh_mobile_...
488
  			m = LDDCKR_MOSEL;
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
489
  		tmp |= m << (lcdc_chan_is_sublcd(ch) ? 8 : 0);
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
490
491
492
  	}
  
  	lcdc_write(priv, _LDDCKR, tmp);
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
493
494
  	lcdc_write(priv, _LDDCKSTPR, 0);
  	lcdc_wait_bit(priv, _LDDCKSTPR, ~0, 0);
9a217e344   Laurent Pinchart   fbdev: sh_mobile_...
495
  	/* Setup geometry, format, frame buffer memory and operation mode. */
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
496
497
  	for (k = 0; k < ARRAY_SIZE(priv->ch); k++) {
  		ch = &priv->ch[k];
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
498
499
  		if (!ch->enabled)
  			continue;
6011bdeaa   Guennadi Liakhovetski   fbdev: sh-mobile:...
500
  		sh_mobile_lcdc_geometry(ch);
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
501

edd153a3e   Laurent Pinchart   fbdev: sh_mobile_...
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
  		switch (sh_mobile_format_fourcc(&ch->info->var)) {
  		case V4L2_PIX_FMT_RGB565:
  			tmp = LDDFR_PKF_RGB16;
  			break;
  		case V4L2_PIX_FMT_BGR24:
  			tmp = LDDFR_PKF_RGB24;
  			break;
  		case V4L2_PIX_FMT_BGR32:
  			tmp = LDDFR_PKF_ARGB32;
  			break;
  		case V4L2_PIX_FMT_NV12:
  		case V4L2_PIX_FMT_NV21:
  			tmp = LDDFR_CC | LDDFR_YF_420;
  			break;
  		case V4L2_PIX_FMT_NV16:
  		case V4L2_PIX_FMT_NV61:
  			tmp = LDDFR_CC | LDDFR_YF_422;
  			break;
  		case V4L2_PIX_FMT_NV24:
  		case V4L2_PIX_FMT_NV42:
  			tmp = LDDFR_CC | LDDFR_YF_444;
  			break;
  		}
  
  		if (sh_mobile_format_is_yuv(&ch->info->var)) {
  			switch (ch->info->var.colorspace) {
  			case V4L2_COLORSPACE_REC709:
  				tmp |= LDDFR_CF1;
53b503143   Damian Hobson-Garcia   fbdev: sh_mobile_...
530
  				break;
edd153a3e   Laurent Pinchart   fbdev: sh_mobile_...
531
532
  			case V4L2_COLORSPACE_JPEG:
  				tmp |= LDDFR_CF0;
53b503143   Damian Hobson-Garcia   fbdev: sh_mobile_...
533
534
  				break;
  			}
417d48274   Magnus Damm   fbdev: sh_mobile_...
535
  		}
7caa4342c   Damian Hobson-Garcia   sh_mobile_meram: ...
536

9a217e344   Laurent Pinchart   fbdev: sh_mobile_...
537
538
539
  		lcdc_write_chan(ch, LDDFR, tmp);
  		lcdc_write_chan(ch, LDMLSR, ch->pitch);
  		lcdc_write_chan(ch, LDSA1R, ch->base_addr_y);
edd153a3e   Laurent Pinchart   fbdev: sh_mobile_...
540
  		if (sh_mobile_format_is_yuv(&ch->info->var))
9a217e344   Laurent Pinchart   fbdev: sh_mobile_...
541
  			lcdc_write_chan(ch, LDSA2R, ch->base_addr_c);
7caa4342c   Damian Hobson-Garcia   sh_mobile_meram: ...
542

9a217e344   Laurent Pinchart   fbdev: sh_mobile_...
543
544
545
546
547
548
549
550
551
552
553
554
  		/* When using deferred I/O mode, configure the LCDC for one-shot
  		 * operation and enable the frame end interrupt. Otherwise use
  		 * continuous read mode.
  		 */
  		if (ch->ldmt1r_value & LDMT1R_IFM &&
  		    ch->cfg.sys_bus_cfg.deferred_io_msec) {
  			lcdc_write_chan(ch, LDSM1R, LDSM1R_OS);
  			lcdc_write(priv, _LDINTR, LDINTR_FE);
  		} else {
  			lcdc_write_chan(ch, LDSM1R, 0);
  		}
  	}
7caa4342c   Damian Hobson-Garcia   sh_mobile_meram: ...
555

9a217e344   Laurent Pinchart   fbdev: sh_mobile_...
556
  	/* Word and long word swap. */
edd153a3e   Laurent Pinchart   fbdev: sh_mobile_...
557
558
559
560
561
562
563
564
565
566
567
  	switch (sh_mobile_format_fourcc(&priv->ch[0].info->var)) {
  	case V4L2_PIX_FMT_RGB565:
  	case V4L2_PIX_FMT_NV21:
  	case V4L2_PIX_FMT_NV61:
  	case V4L2_PIX_FMT_NV42:
  		tmp = LDDDSR_LS | LDDDSR_WS;
  		break;
  	case V4L2_PIX_FMT_BGR24:
  	case V4L2_PIX_FMT_NV12:
  	case V4L2_PIX_FMT_NV16:
  	case V4L2_PIX_FMT_NV24:
9a217e344   Laurent Pinchart   fbdev: sh_mobile_...
568
  		tmp = LDDDSR_LS | LDDDSR_WS | LDDDSR_BS;
edd153a3e   Laurent Pinchart   fbdev: sh_mobile_...
569
570
571
572
573
  		break;
  	case V4L2_PIX_FMT_BGR32:
  	default:
  		tmp = LDDDSR_LS;
  		break;
9a217e344   Laurent Pinchart   fbdev: sh_mobile_...
574
575
  	}
  	lcdc_write(priv, _LDDDSR, tmp);
7caa4342c   Damian Hobson-Garcia   sh_mobile_meram: ...
576

9a217e344   Laurent Pinchart   fbdev: sh_mobile_...
577
578
579
580
581
  	/* Enable the display output. */
  	lcdc_write(priv, _LDCNT1R, LDCNT1R_DE);
  	sh_mobile_lcdc_start_stop(priv, 1);
  	priv->started = 1;
  }
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
582

9a217e344   Laurent Pinchart   fbdev: sh_mobile_...
583
584
585
586
587
588
589
590
  static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv)
  {
  	struct sh_mobile_meram_info *mdev = priv->meram_dev;
  	struct sh_mobile_lcdc_board_cfg	*board_cfg;
  	struct sh_mobile_lcdc_chan *ch;
  	unsigned long tmp;
  	int ret;
  	int k;
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
591

9a217e344   Laurent Pinchart   fbdev: sh_mobile_...
592
593
594
595
596
  	/* enable clocks before accessing the hardware */
  	for (k = 0; k < ARRAY_SIZE(priv->ch); k++) {
  		if (priv->ch[k].enabled)
  			sh_mobile_lcdc_clk_on(priv);
  	}
8564557a0   Magnus Damm   video: sh_mobile_...
597

9a217e344   Laurent Pinchart   fbdev: sh_mobile_...
598
599
600
  	/* reset */
  	lcdc_write(priv, _LDCNT2R, lcdc_read(priv, _LDCNT2R) | LDCNT2R_BR);
  	lcdc_wait_bit(priv, _LDCNT2R, LDCNT2R_BR, 0);
8564557a0   Magnus Damm   video: sh_mobile_...
601

9a217e344   Laurent Pinchart   fbdev: sh_mobile_...
602
603
  	for (k = 0; k < ARRAY_SIZE(priv->ch); k++) {
  		ch = &priv->ch[k];
8564557a0   Magnus Damm   video: sh_mobile_...
604

9a217e344   Laurent Pinchart   fbdev: sh_mobile_...
605
606
607
608
609
610
611
612
613
  		if (!ch->enabled)
  			continue;
  
  		board_cfg = &ch->cfg.board_cfg;
  		if (board_cfg->setup_sys) {
  			ret = board_cfg->setup_sys(board_cfg->board_data, ch,
  						   &sh_mobile_lcdc_sys_bus_ops);
  			if (ret)
  				return ret;
8564557a0   Magnus Damm   video: sh_mobile_...
614
  		}
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
615
  	}
9a217e344   Laurent Pinchart   fbdev: sh_mobile_...
616
617
618
619
  	/* Compute frame buffer base address and pitch for each channel. */
  	for (k = 0; k < ARRAY_SIZE(priv->ch); k++) {
  		struct sh_mobile_meram_cfg *cfg;
  		int pixelformat;
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
620

9a217e344   Laurent Pinchart   fbdev: sh_mobile_...
621
622
623
  		ch = &priv->ch[k];
  		if (!ch->enabled)
  			continue;
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
624

9a217e344   Laurent Pinchart   fbdev: sh_mobile_...
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
  		ch->base_addr_y = ch->info->fix.smem_start;
  		ch->base_addr_c = ch->base_addr_y
  				+ ch->info->var.xres
  				* ch->info->var.yres_virtual;
  		ch->pitch = ch->info->fix.line_length;
  
  		/* Enable MERAM if possible. */
  		cfg = ch->cfg.meram_cfg;
  		if (mdev == NULL || mdev->ops == NULL || cfg == NULL)
  			continue;
  
  		/* we need to de-init configured ICBs before we can
  		 * re-initialize them.
  		 */
  		if (ch->meram_enabled) {
  			mdev->ops->meram_unregister(mdev, cfg);
  			ch->meram_enabled = 0;
  		}
edd153a3e   Laurent Pinchart   fbdev: sh_mobile_...
643
644
645
646
647
  		switch (sh_mobile_format_fourcc(&ch->info->var)) {
  		case V4L2_PIX_FMT_NV12:
  		case V4L2_PIX_FMT_NV21:
  		case V4L2_PIX_FMT_NV16:
  		case V4L2_PIX_FMT_NV61:
9a217e344   Laurent Pinchart   fbdev: sh_mobile_...
648
  			pixelformat = SH_MOBILE_MERAM_PF_NV;
edd153a3e   Laurent Pinchart   fbdev: sh_mobile_...
649
650
651
652
653
654
655
656
657
658
659
660
  			break;
  		case V4L2_PIX_FMT_NV24:
  		case V4L2_PIX_FMT_NV42:
  			pixelformat = SH_MOBILE_MERAM_PF_NV24;
  			break;
  		case V4L2_PIX_FMT_RGB565:
  		case V4L2_PIX_FMT_BGR24:
  		case V4L2_PIX_FMT_BGR32:
  		default:
  			pixelformat = SH_MOBILE_MERAM_PF_RGB;
  			break;
  		}
9a217e344   Laurent Pinchart   fbdev: sh_mobile_...
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
  
  		ret = mdev->ops->meram_register(mdev, cfg, ch->pitch,
  					ch->info->var.yres, pixelformat,
  					ch->base_addr_y, ch->base_addr_c,
  					&ch->base_addr_y, &ch->base_addr_c,
  					&ch->pitch);
  		if (!ret)
  			ch->meram_enabled = 1;
  	}
  
  	/* Start the LCDC. */
  	__sh_mobile_lcdc_start(priv);
  
  	/* Setup deferred I/O, tell the board code to enable the panels, and
  	 * turn backlight on.
  	 */
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
677
678
  	for (k = 0; k < ARRAY_SIZE(priv->ch); k++) {
  		ch = &priv->ch[k];
21bc1f024   Magnus Damm   sh: skip disabled...
679
680
  		if (!ch->enabled)
  			continue;
9a217e344   Laurent Pinchart   fbdev: sh_mobile_...
681
682
683
684
685
686
687
  		tmp = ch->cfg.sys_bus_cfg.deferred_io_msec;
  		if (ch->ldmt1r_value & LDMT1R_IFM && tmp) {
  			ch->defio.deferred_io = sh_mobile_lcdc_deferred_io;
  			ch->defio.delay = msecs_to_jiffies(tmp);
  			ch->info->fbdefio = &ch->defio;
  			fb_deferred_io_init(ch->info);
  		}
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
688
  		board_cfg = &ch->cfg.board_cfg;
247f99386   Alexandre Courbot   fbdev: sh_mobile_...
689
  		if (board_cfg->display_on && try_module_get(board_cfg->owner)) {
c24393981   Guennadi Liakhovetski   sh: add a paramet...
690
  			board_cfg->display_on(board_cfg->board_data, ch->info);
6de9edd5b   Guennadi Liakhovetski   fbdev: sh_mobile_...
691
692
  			module_put(board_cfg->owner);
  		}
3b0fd9d75   Alexandre Courbot   fbdev: sh_mobile_...
693
694
695
696
697
  
  		if (ch->bl) {
  			ch->bl->props.power = FB_BLANK_UNBLANK;
  			backlight_update_status(ch->bl);
  		}
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
698
699
700
701
702
703
704
705
706
707
  	}
  
  	return 0;
  }
  
  static void sh_mobile_lcdc_stop(struct sh_mobile_lcdc_priv *priv)
  {
  	struct sh_mobile_lcdc_chan *ch;
  	struct sh_mobile_lcdc_board_cfg	*board_cfg;
  	int k;
2feb075a3   Magnus Damm   video: sh_mobile_...
708
  	/* clean up deferred io and ask board code to disable panel */
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
709
710
  	for (k = 0; k < ARRAY_SIZE(priv->ch); k++) {
  		ch = &priv->ch[k];
21bc1f024   Magnus Damm   sh: skip disabled...
711
712
  		if (!ch->enabled)
  			continue;
8564557a0   Magnus Damm   video: sh_mobile_...
713

2feb075a3   Magnus Damm   video: sh_mobile_...
714
715
716
717
  		/* deferred io mode:
  		 * flush frame, and wait for frame end interrupt
  		 * clean up deferred io and enable clock
  		 */
5ef6b505d   Guennadi Liakhovetski   fbdev: sh_mobile_...
718
  		if (ch->info && ch->info->fbdefio) {
2feb075a3   Magnus Damm   video: sh_mobile_...
719
  			ch->frame_end = 0;
e33afddca   Paul Mundt   video: sh_mobile_...
720
  			schedule_delayed_work(&ch->info->deferred_work, 0);
2feb075a3   Magnus Damm   video: sh_mobile_...
721
  			wait_event(ch->frame_end_wait, ch->frame_end);
e33afddca   Paul Mundt   video: sh_mobile_...
722
723
  			fb_deferred_io_cleanup(ch->info);
  			ch->info->fbdefio = NULL;
2feb075a3   Magnus Damm   video: sh_mobile_...
724
  			sh_mobile_lcdc_clk_on(priv);
8564557a0   Magnus Damm   video: sh_mobile_...
725
  		}
2feb075a3   Magnus Damm   video: sh_mobile_...
726

3b0fd9d75   Alexandre Courbot   fbdev: sh_mobile_...
727
728
729
730
  		if (ch->bl) {
  			ch->bl->props.power = FB_BLANK_POWERDOWN;
  			backlight_update_status(ch->bl);
  		}
2feb075a3   Magnus Damm   video: sh_mobile_...
731
  		board_cfg = &ch->cfg.board_cfg;
247f99386   Alexandre Courbot   fbdev: sh_mobile_...
732
  		if (board_cfg->display_off && try_module_get(board_cfg->owner)) {
2feb075a3   Magnus Damm   video: sh_mobile_...
733
  			board_cfg->display_off(board_cfg->board_data);
6de9edd5b   Guennadi Liakhovetski   fbdev: sh_mobile_...
734
735
  			module_put(board_cfg->owner);
  		}
7caa4342c   Damian Hobson-Garcia   sh_mobile_meram: ...
736
737
738
739
740
741
742
743
744
745
  
  		/* disable the meram */
  		if (ch->meram_enabled) {
  			struct sh_mobile_meram_cfg *cfg;
  			struct sh_mobile_meram_info *mdev;
  			cfg = ch->cfg.meram_cfg;
  			mdev = priv->meram_dev;
  			mdev->ops->meram_unregister(mdev, cfg);
  			ch->meram_enabled = 0;
  		}
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
746
747
748
  	}
  
  	/* stop the lcdc */
8e9bb19ef   Magnus Damm   video: stop sh_mo...
749
750
751
752
  	if (priv->started) {
  		sh_mobile_lcdc_start_stop(priv, 0);
  		priv->started = 0;
  	}
b51339fff   Magnus Damm   sh: sh_mobile lcd...
753

8564557a0   Magnus Damm   video: sh_mobile_...
754
755
756
757
  	/* stop clocks */
  	for (k = 0; k < ARRAY_SIZE(priv->ch); k++)
  		if (priv->ch[k].enabled)
  			sh_mobile_lcdc_clk_off(priv);
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
758
759
760
761
  }
  
  static int sh_mobile_lcdc_check_interface(struct sh_mobile_lcdc_chan *ch)
  {
ce1c0b087   Laurent Pinchart   fbdev: sh_mobile_...
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
  	int interface_type = ch->cfg.interface_type;
  
  	switch (interface_type) {
  	case RGB8:
  	case RGB9:
  	case RGB12A:
  	case RGB12B:
  	case RGB16:
  	case RGB18:
  	case RGB24:
  	case SYS8A:
  	case SYS8B:
  	case SYS8C:
  	case SYS8D:
  	case SYS9:
  	case SYS12:
  	case SYS16A:
  	case SYS16B:
  	case SYS16C:
  	case SYS18:
  	case SYS24:
  		break;
  	default:
  		return -EINVAL;
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
786
787
788
789
  	}
  
  	/* SUBLCD only supports SYS interface */
  	if (lcdc_chan_is_sublcd(ch)) {
ce1c0b087   Laurent Pinchart   fbdev: sh_mobile_...
790
791
792
793
  		if (!(interface_type & LDMT1R_IFM))
  			return -EINVAL;
  
  		interface_type &= ~LDMT1R_IFM;
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
794
  	}
ce1c0b087   Laurent Pinchart   fbdev: sh_mobile_...
795
  	ch->ldmt1r_value = interface_type;
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
796
  	return 0;
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
797
  }
b51339fff   Magnus Damm   sh: sh_mobile lcd...
798
799
  static int sh_mobile_lcdc_setup_clocks(struct platform_device *pdev,
  				       int clock_source,
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
800
801
802
  				       struct sh_mobile_lcdc_priv *priv)
  {
  	char *str;
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
803
804
  
  	switch (clock_source) {
ce1c0b087   Laurent Pinchart   fbdev: sh_mobile_...
805
806
807
808
809
810
811
812
813
814
815
816
  	case LCDC_CLK_BUS:
  		str = "bus_clk";
  		priv->lddckr = LDDCKR_ICKSEL_BUS;
  		break;
  	case LCDC_CLK_PERIPHERAL:
  		str = "peripheral_clk";
  		priv->lddckr = LDDCKR_ICKSEL_MIPI;
  		break;
  	case LCDC_CLK_EXTERNAL:
  		str = NULL;
  		priv->lddckr = LDDCKR_ICKSEL_HDMI;
  		break;
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
817
818
819
  	default:
  		return -EINVAL;
  	}
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
820
  	if (str) {
b51339fff   Magnus Damm   sh: sh_mobile lcd...
821
822
823
824
  		priv->dot_clk = clk_get(&pdev->dev, str);
  		if (IS_ERR(priv->dot_clk)) {
  			dev_err(&pdev->dev, "cannot get dot clock %s
  ", str);
b51339fff   Magnus Damm   sh: sh_mobile lcd...
825
  			return PTR_ERR(priv->dot_clk);
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
826
  		}
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
827
  	}
0246c4712   Magnus Damm   video: Runtime PM...
828
829
830
831
832
  
  	/* Runtime PM support involves two step for this driver:
  	 * 1) Enable Runtime PM
  	 * 2) Force Runtime PM Resume since hardware is accessed from probe()
  	 */
8bed90557   Guennadi Liakhovetski   sh: fix a number ...
833
  	priv->dev = &pdev->dev;
0246c4712   Magnus Damm   video: Runtime PM...
834
835
  	pm_runtime_enable(priv->dev);
  	pm_runtime_resume(priv->dev);
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
  	return 0;
  }
  
  static int sh_mobile_lcdc_setcolreg(u_int regno,
  				    u_int red, u_int green, u_int blue,
  				    u_int transp, struct fb_info *info)
  {
  	u32 *palette = info->pseudo_palette;
  
  	if (regno >= PALETTE_NR)
  		return -EINVAL;
  
  	/* only FB_VISUAL_TRUECOLOR supported */
  
  	red >>= 16 - info->var.red.length;
  	green >>= 16 - info->var.green.length;
  	blue >>= 16 - info->var.blue.length;
  	transp >>= 16 - info->var.transp.length;
  
  	palette[regno] = (red << info->var.red.offset) |
  	  (green << info->var.green.offset) |
  	  (blue << info->var.blue.offset) |
  	  (transp << info->var.transp.offset);
  
  	return 0;
  }
  
  static struct fb_fix_screeninfo sh_mobile_lcdc_fix  = {
  	.id =		"SH Mobile LCDC",
  	.type =		FB_TYPE_PACKED_PIXELS,
  	.visual =	FB_VISUAL_TRUECOLOR,
  	.accel =	FB_ACCEL_NONE,
9dd38819c   Phil Edworthy   video: sh_mobile_...
868
869
870
  	.xpanstep =	0,
  	.ypanstep =	1,
  	.ywrapstep =	0,
edd153a3e   Laurent Pinchart   fbdev: sh_mobile_...
871
  	.capabilities =	FB_CAP_FOURCC,
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
872
  };
8564557a0   Magnus Damm   video: sh_mobile_...
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
  static void sh_mobile_lcdc_fillrect(struct fb_info *info,
  				    const struct fb_fillrect *rect)
  {
  	sys_fillrect(info, rect);
  	sh_mobile_lcdc_deferred_io_touch(info);
  }
  
  static void sh_mobile_lcdc_copyarea(struct fb_info *info,
  				    const struct fb_copyarea *area)
  {
  	sys_copyarea(info, area);
  	sh_mobile_lcdc_deferred_io_touch(info);
  }
  
  static void sh_mobile_lcdc_imageblit(struct fb_info *info,
  				     const struct fb_image *image)
  {
  	sys_imageblit(info, image);
  	sh_mobile_lcdc_deferred_io_touch(info);
  }
9dd38819c   Phil Edworthy   video: sh_mobile_...
893
894
895
896
  static int sh_mobile_fb_pan_display(struct fb_var_screeninfo *var,
  				     struct fb_info *info)
  {
  	struct sh_mobile_lcdc_chan *ch = info->par;
92e1f9a7e   Phil Edworthy   video: sh_mobile_...
897
898
899
  	struct sh_mobile_lcdc_priv *priv = ch->lcdc;
  	unsigned long ldrcntr;
  	unsigned long new_pan_offset;
53b503143   Damian Hobson-Garcia   fbdev: sh_mobile_...
900
901
  	unsigned long base_addr_y, base_addr_c;
  	unsigned long c_offset;
edd153a3e   Laurent Pinchart   fbdev: sh_mobile_...
902
  	bool yuv = sh_mobile_format_is_yuv(&info->var);
92e1f9a7e   Phil Edworthy   video: sh_mobile_...
903

edd153a3e   Laurent Pinchart   fbdev: sh_mobile_...
904
  	if (!yuv)
dc1d5adab   Laurent Pinchart   fbdev: sh_mobile_...
905
906
  		new_pan_offset = var->yoffset * info->fix.line_length
  			       + var->xoffset * (info->var.bits_per_pixel / 8);
53b503143   Damian Hobson-Garcia   fbdev: sh_mobile_...
907
  	else
dc1d5adab   Laurent Pinchart   fbdev: sh_mobile_...
908
909
  		new_pan_offset = var->yoffset * info->fix.line_length
  			       + var->xoffset;
9dd38819c   Phil Edworthy   video: sh_mobile_...
910

92e1f9a7e   Phil Edworthy   video: sh_mobile_...
911
  	if (new_pan_offset == ch->pan_offset)
9dd38819c   Phil Edworthy   video: sh_mobile_...
912
  		return 0;	/* No change, do nothing */
92e1f9a7e   Phil Edworthy   video: sh_mobile_...
913
  	ldrcntr = lcdc_read(priv, _LDRCNTR);
9dd38819c   Phil Edworthy   video: sh_mobile_...
914

92e1f9a7e   Phil Edworthy   video: sh_mobile_...
915
  	/* Set the source address for the next refresh */
53b503143   Damian Hobson-Garcia   fbdev: sh_mobile_...
916
  	base_addr_y = ch->dma_handle + new_pan_offset;
edd153a3e   Laurent Pinchart   fbdev: sh_mobile_...
917
  	if (yuv) {
53b503143   Damian Hobson-Garcia   fbdev: sh_mobile_...
918
  		/* Set y offset */
dc1d5adab   Laurent Pinchart   fbdev: sh_mobile_...
919
920
921
922
923
  		c_offset = var->yoffset * info->fix.line_length
  			 * (info->var.bits_per_pixel - 8) / 8;
  		base_addr_c = ch->dma_handle
  			    + info->var.xres * info->var.yres_virtual
  			    + c_offset;
53b503143   Damian Hobson-Garcia   fbdev: sh_mobile_...
924
  		/* Set x offset */
edd153a3e   Laurent Pinchart   fbdev: sh_mobile_...
925
  		if (sh_mobile_format_fourcc(&info->var) == V4L2_PIX_FMT_NV24)
53b503143   Damian Hobson-Garcia   fbdev: sh_mobile_...
926
927
928
  			base_addr_c += 2 * var->xoffset;
  		else
  			base_addr_c += var->xoffset;
49d79ba2e   Laurent Pinchart   fbdev: sh_mobile_...
929
  	}
53b503143   Damian Hobson-Garcia   fbdev: sh_mobile_...
930

49d79ba2e   Laurent Pinchart   fbdev: sh_mobile_...
931
  	if (ch->meram_enabled) {
7caa4342c   Damian Hobson-Garcia   sh_mobile_meram: ...
932
933
  		struct sh_mobile_meram_cfg *cfg;
  		struct sh_mobile_meram_info *mdev;
7caa4342c   Damian Hobson-Garcia   sh_mobile_meram: ...
934
935
936
937
938
939
  		int ret;
  
  		cfg = ch->cfg.meram_cfg;
  		mdev = priv->meram_dev;
  		ret = mdev->ops->meram_update(mdev, cfg,
  					base_addr_y, base_addr_c,
49d79ba2e   Laurent Pinchart   fbdev: sh_mobile_...
940
  					&base_addr_y, &base_addr_c);
7caa4342c   Damian Hobson-Garcia   sh_mobile_meram: ...
941
942
  		if (ret)
  			return ret;
49d79ba2e   Laurent Pinchart   fbdev: sh_mobile_...
943
  	}
7caa4342c   Damian Hobson-Garcia   sh_mobile_meram: ...
944

49d79ba2e   Laurent Pinchart   fbdev: sh_mobile_...
945
946
  	ch->base_addr_y = base_addr_y;
  	ch->base_addr_c = base_addr_c;
7caa4342c   Damian Hobson-Garcia   sh_mobile_meram: ...
947

49d79ba2e   Laurent Pinchart   fbdev: sh_mobile_...
948
  	lcdc_write_chan_mirror(ch, LDSA1R, base_addr_y);
edd153a3e   Laurent Pinchart   fbdev: sh_mobile_...
949
  	if (yuv)
49d79ba2e   Laurent Pinchart   fbdev: sh_mobile_...
950
  		lcdc_write_chan_mirror(ch, LDSA2R, base_addr_c);
53b503143   Damian Hobson-Garcia   fbdev: sh_mobile_...
951

92e1f9a7e   Phil Edworthy   video: sh_mobile_...
952
953
954
955
956
957
958
959
  	if (lcdc_chan_is_sublcd(ch))
  		lcdc_write(ch->lcdc, _LDRCNTR, ldrcntr ^ LDRCNTR_SRS);
  	else
  		lcdc_write(ch->lcdc, _LDRCNTR, ldrcntr ^ LDRCNTR_MRS);
  
  	ch->pan_offset = new_pan_offset;
  
  	sh_mobile_lcdc_deferred_io_touch(info);
9dd38819c   Phil Edworthy   video: sh_mobile_...
960
961
962
  
  	return 0;
  }
40331b21f   Phil Edworthy   video: sh_mobile_...
963
964
965
966
967
  static int sh_mobile_wait_for_vsync(struct fb_info *info)
  {
  	struct sh_mobile_lcdc_chan *ch = info->par;
  	unsigned long ldintr;
  	int ret;
dc48665fa   Laurent Pinchart   fbdev: sh_mobile_...
968
969
970
  	/* Enable VSync End interrupt and be careful not to acknowledge any
  	 * pending interrupt.
  	 */
40331b21f   Phil Edworthy   video: sh_mobile_...
971
  	ldintr = lcdc_read(ch->lcdc, _LDINTR);
dc48665fa   Laurent Pinchart   fbdev: sh_mobile_...
972
  	ldintr |= LDINTR_VEE | LDINTR_STATUS_MASK;
40331b21f   Phil Edworthy   video: sh_mobile_...
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
  	lcdc_write(ch->lcdc, _LDINTR, ldintr);
  
  	ret = wait_for_completion_interruptible_timeout(&ch->vsync_completion,
  							msecs_to_jiffies(100));
  	if (!ret)
  		return -ETIMEDOUT;
  
  	return 0;
  }
  
  static int sh_mobile_ioctl(struct fb_info *info, unsigned int cmd,
  		       unsigned long arg)
  {
  	int retval;
  
  	switch (cmd) {
  	case FBIO_WAITFORVSYNC:
  		retval = sh_mobile_wait_for_vsync(info);
  		break;
  
  	default:
  		retval = -ENOIOCTLCMD;
  		break;
  	}
  	return retval;
  }
dd210503b   Guennadi Liakhovetski   fbdev: sh_mobile_...
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
  static void sh_mobile_fb_reconfig(struct fb_info *info)
  {
  	struct sh_mobile_lcdc_chan *ch = info->par;
  	struct fb_videomode mode1, mode2;
  	struct fb_event event;
  	int evnt = FB_EVENT_MODE_CHANGE_ALL;
  
  	if (ch->use_count > 1 || (ch->use_count == 1 && !info->fbcon_par))
  		/* More framebuffer users are active */
  		return;
  
  	fb_var_to_videomode(&mode1, &ch->display_var);
  	fb_var_to_videomode(&mode2, &info->var);
  
  	if (fb_mode_is_equal(&mode1, &mode2))
  		return;
  
  	/* Display has been re-plugged, framebuffer is free now, reconfigure */
  	if (fb_set_var(info, &ch->display_var) < 0)
  		/* Couldn't reconfigure, hopefully, can continue as before */
  		return;
dd210503b   Guennadi Liakhovetski   fbdev: sh_mobile_...
1020
1021
1022
1023
1024
1025
  	/*
  	 * fb_set_var() calls the notifier change internally, only if
  	 * FBINFO_MISC_USEREVENT flag is set. Since we do not want to fake a
  	 * user event, we have to call the chain ourselves.
  	 */
  	event.info = info;
cc267ec5d   Arnd Hannemann   fbdev: sh_mobile_...
1026
  	event.data = &mode1;
dd210503b   Guennadi Liakhovetski   fbdev: sh_mobile_...
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
  	fb_notifier_call_chain(evnt, &event);
  }
  
  /*
   * Locking: both .fb_release() and .fb_open() are called with info->lock held if
   * user == 1, or with console sem held, if user == 0.
   */
  static int sh_mobile_release(struct fb_info *info, int user)
  {
  	struct sh_mobile_lcdc_chan *ch = info->par;
  
  	mutex_lock(&ch->open_lock);
  	dev_dbg(info->dev, "%s(): %d users
  ", __func__, ch->use_count);
  
  	ch->use_count--;
  
  	/* Nothing to reconfigure, when called from fbcon */
  	if (user) {
ac751efa6   Torben Hohn   console: rename a...
1046
  		console_lock();
dd210503b   Guennadi Liakhovetski   fbdev: sh_mobile_...
1047
  		sh_mobile_fb_reconfig(info);
ac751efa6   Torben Hohn   console: rename a...
1048
  		console_unlock();
dd210503b   Guennadi Liakhovetski   fbdev: sh_mobile_...
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
  	}
  
  	mutex_unlock(&ch->open_lock);
  
  	return 0;
  }
  
  static int sh_mobile_open(struct fb_info *info, int user)
  {
  	struct sh_mobile_lcdc_chan *ch = info->par;
  
  	mutex_lock(&ch->open_lock);
  	ch->use_count++;
  
  	dev_dbg(info->dev, "%s(): %d users
  ", __func__, ch->use_count);
  	mutex_unlock(&ch->open_lock);
  
  	return 0;
  }
  
  static int sh_mobile_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
  {
  	struct sh_mobile_lcdc_chan *ch = info->par;
417d48274   Magnus Damm   fbdev: sh_mobile_...
1073
  	struct sh_mobile_lcdc_priv *p = ch->lcdc;
038621944   Laurent Pinchart   fbdev: sh_mobile_...
1074
1075
1076
1077
  	unsigned int best_dist = (unsigned int)-1;
  	unsigned int best_xres = 0;
  	unsigned int best_yres = 0;
  	unsigned int i;
dd210503b   Guennadi Liakhovetski   fbdev: sh_mobile_...
1078

038621944   Laurent Pinchart   fbdev: sh_mobile_...
1079
  	if (var->xres > MAX_XRES || var->yres > MAX_YRES)
dd210503b   Guennadi Liakhovetski   fbdev: sh_mobile_...
1080
  		return -EINVAL;
038621944   Laurent Pinchart   fbdev: sh_mobile_...
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
  
  	/* If board code provides us with a list of available modes, make sure
  	 * we use one of them. Find the mode closest to the requested one. The
  	 * distance between two modes is defined as the size of the
  	 * non-overlapping parts of the two rectangles.
  	 */
  	for (i = 0; i < ch->cfg.num_cfg; ++i) {
  		const struct fb_videomode *mode = &ch->cfg.lcd_cfg[i];
  		unsigned int dist;
  
  		/* We can only round up. */
  		if (var->xres > mode->xres || var->yres > mode->yres)
  			continue;
  
  		dist = var->xres * var->yres + mode->xres * mode->yres
  		     - 2 * min(var->xres, mode->xres)
  			 * min(var->yres, mode->yres);
  
  		if (dist < best_dist) {
  			best_xres = mode->xres;
  			best_yres = mode->yres;
  			best_dist = dist;
  		}
dd210503b   Guennadi Liakhovetski   fbdev: sh_mobile_...
1104
  	}
417d48274   Magnus Damm   fbdev: sh_mobile_...
1105

038621944   Laurent Pinchart   fbdev: sh_mobile_...
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
  	/* If no available mode can be used, return an error. */
  	if (ch->cfg.num_cfg != 0) {
  		if (best_dist == (unsigned int)-1)
  			return -EINVAL;
  
  		var->xres = best_xres;
  		var->yres = best_yres;
  	}
  
  	/* Make sure the virtual resolution is at least as big as the visible
  	 * resolution.
  	 */
  	if (var->xres_virtual < var->xres)
  		var->xres_virtual = var->xres;
  	if (var->yres_virtual < var->yres)
  		var->yres_virtual = var->yres;
edd153a3e   Laurent Pinchart   fbdev: sh_mobile_...
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
  	if (sh_mobile_format_is_fourcc(var)) {
  		switch (var->grayscale) {
  		case V4L2_PIX_FMT_NV12:
  		case V4L2_PIX_FMT_NV21:
  			var->bits_per_pixel = 12;
  			break;
  		case V4L2_PIX_FMT_RGB565:
  		case V4L2_PIX_FMT_NV16:
  		case V4L2_PIX_FMT_NV61:
  			var->bits_per_pixel = 16;
  			break;
  		case V4L2_PIX_FMT_BGR24:
  		case V4L2_PIX_FMT_NV24:
  		case V4L2_PIX_FMT_NV42:
  			var->bits_per_pixel = 24;
  			break;
  		case V4L2_PIX_FMT_BGR32:
  			var->bits_per_pixel = 32;
  			break;
  		default:
  			return -EINVAL;
  		}
  
  		/* Default to RGB and JPEG color-spaces for RGB and YUV formats
  		 * respectively.
  		 */
  		if (!sh_mobile_format_is_yuv(var))
  			var->colorspace = V4L2_COLORSPACE_SRGB;
  		else if (var->colorspace != V4L2_COLORSPACE_REC709)
  			var->colorspace = V4L2_COLORSPACE_JPEG;
  	} else {
  		if (var->bits_per_pixel <= 16) {		/* RGB 565 */
  			var->bits_per_pixel = 16;
  			var->red.offset = 11;
  			var->red.length = 5;
  			var->green.offset = 5;
  			var->green.length = 6;
  			var->blue.offset = 0;
  			var->blue.length = 5;
  			var->transp.offset = 0;
  			var->transp.length = 0;
  		} else if (var->bits_per_pixel <= 24) {		/* RGB 888 */
  			var->bits_per_pixel = 24;
  			var->red.offset = 16;
  			var->red.length = 8;
  			var->green.offset = 8;
  			var->green.length = 8;
  			var->blue.offset = 0;
  			var->blue.length = 8;
  			var->transp.offset = 0;
  			var->transp.length = 0;
  		} else if (var->bits_per_pixel <= 32) {		/* RGBA 888 */
  			var->bits_per_pixel = 32;
  			var->red.offset = 16;
  			var->red.length = 8;
  			var->green.offset = 8;
  			var->green.length = 8;
  			var->blue.offset = 0;
  			var->blue.length = 8;
  			var->transp.offset = 24;
  			var->transp.length = 8;
  		} else
  			return -EINVAL;
417d48274   Magnus Damm   fbdev: sh_mobile_...
1185

edd153a3e   Laurent Pinchart   fbdev: sh_mobile_...
1186
1187
1188
1189
1190
  		var->red.msb_right = 0;
  		var->green.msb_right = 0;
  		var->blue.msb_right = 0;
  		var->transp.msb_right = 0;
  	}
038621944   Laurent Pinchart   fbdev: sh_mobile_...
1191
1192
1193
1194
1195
  
  	/* Make sure we don't exceed our allocated memory. */
  	if (var->xres_virtual * var->yres_virtual * var->bits_per_pixel / 8 >
  	    info->fix.smem_len)
  		return -EINVAL;
edd153a3e   Laurent Pinchart   fbdev: sh_mobile_...
1196
1197
1198
  	/* only accept the forced_fourcc for dual channel configurations */
  	if (p->forced_fourcc &&
  	    p->forced_fourcc != sh_mobile_format_fourcc(var))
417d48274   Magnus Damm   fbdev: sh_mobile_...
1199
  		return -EINVAL;
417d48274   Magnus Damm   fbdev: sh_mobile_...
1200

dd210503b   Guennadi Liakhovetski   fbdev: sh_mobile_...
1201
1202
  	return 0;
  }
40331b21f   Phil Edworthy   video: sh_mobile_...
1203

ed5bebf29   Laurent Pinchart   fbdev: sh_mobile_...
1204
1205
1206
  static int sh_mobile_set_par(struct fb_info *info)
  {
  	struct sh_mobile_lcdc_chan *ch = info->par;
91fba48d5   Laurent Pinchart   fbdev: sh_mobile_...
1207
  	u32 line_length = info->fix.line_length;
ed5bebf29   Laurent Pinchart   fbdev: sh_mobile_...
1208
1209
1210
  	int ret;
  
  	sh_mobile_lcdc_stop(ch->lcdc);
91fba48d5   Laurent Pinchart   fbdev: sh_mobile_...
1211

edd153a3e   Laurent Pinchart   fbdev: sh_mobile_...
1212
  	if (sh_mobile_format_is_yuv(&info->var))
91fba48d5   Laurent Pinchart   fbdev: sh_mobile_...
1213
1214
1215
1216
  		info->fix.line_length = info->var.xres;
  	else
  		info->fix.line_length = info->var.xres
  				      * info->var.bits_per_pixel / 8;
ed5bebf29   Laurent Pinchart   fbdev: sh_mobile_...
1217
  	ret = sh_mobile_lcdc_start(ch->lcdc);
91fba48d5   Laurent Pinchart   fbdev: sh_mobile_...
1218
  	if (ret < 0) {
ed5bebf29   Laurent Pinchart   fbdev: sh_mobile_...
1219
1220
  		dev_err(info->dev, "%s: unable to restart LCDC
  ", __func__);
91fba48d5   Laurent Pinchart   fbdev: sh_mobile_...
1221
1222
  		info->fix.line_length = line_length;
  	}
ed5bebf29   Laurent Pinchart   fbdev: sh_mobile_...
1223

edd153a3e   Laurent Pinchart   fbdev: sh_mobile_...
1224
1225
1226
1227
1228
1229
1230
  	if (sh_mobile_format_is_fourcc(&info->var)) {
  		info->fix.type = FB_TYPE_FOURCC;
  		info->fix.visual = FB_VISUAL_FOURCC;
  	} else {
  		info->fix.type = FB_TYPE_PACKED_PIXELS;
  		info->fix.visual = FB_VISUAL_TRUECOLOR;
  	}
ed5bebf29   Laurent Pinchart   fbdev: sh_mobile_...
1231
1232
  	return ret;
  }
8857b9aa7   Alexandre Courbot   fbdev: sh_mobile_...
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
  /*
   * Screen blanking. Behavior is as follows:
   * FB_BLANK_UNBLANK: screen unblanked, clocks enabled
   * FB_BLANK_NORMAL: screen blanked, clocks enabled
   * FB_BLANK_VSYNC,
   * FB_BLANK_HSYNC,
   * FB_BLANK_POWEROFF: screen blanked, clocks disabled
   */
  static int sh_mobile_lcdc_blank(int blank, struct fb_info *info)
  {
  	struct sh_mobile_lcdc_chan *ch = info->par;
  	struct sh_mobile_lcdc_priv *p = ch->lcdc;
  
  	/* blank the screen? */
  	if (blank > FB_BLANK_UNBLANK && ch->blank_status == FB_BLANK_UNBLANK) {
  		struct fb_fillrect rect = {
  			.width = info->var.xres,
  			.height = info->var.yres,
  		};
  		sh_mobile_lcdc_fillrect(info, &rect);
  	}
  	/* turn clocks on? */
  	if (blank <= FB_BLANK_NORMAL && ch->blank_status > FB_BLANK_NORMAL) {
  		sh_mobile_lcdc_clk_on(p);
  	}
  	/* turn clocks off? */
  	if (blank > FB_BLANK_NORMAL && ch->blank_status <= FB_BLANK_NORMAL) {
  		/* make sure the screen is updated with the black fill before
  		 * switching the clocks off. one vsync is not enough since
  		 * blanking may occur in the middle of a refresh. deferred io
  		 * mode will reenable the clocks and update the screen in time,
  		 * so it does not need this. */
  		if (!info->fbdefio) {
  			sh_mobile_wait_for_vsync(info);
  			sh_mobile_wait_for_vsync(info);
  		}
  		sh_mobile_lcdc_clk_off(p);
  	}
  
  	ch->blank_status = blank;
  	return 0;
  }
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
1275
  static struct fb_ops sh_mobile_lcdc_ops = {
9dd38819c   Phil Edworthy   video: sh_mobile_...
1276
  	.owner          = THIS_MODULE,
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
1277
  	.fb_setcolreg	= sh_mobile_lcdc_setcolreg,
2540c111e   Magnus Damm   sh_mobile_lcdc: u...
1278
1279
  	.fb_read        = fb_sys_read,
  	.fb_write       = fb_sys_write,
8564557a0   Magnus Damm   video: sh_mobile_...
1280
1281
1282
  	.fb_fillrect	= sh_mobile_lcdc_fillrect,
  	.fb_copyarea	= sh_mobile_lcdc_copyarea,
  	.fb_imageblit	= sh_mobile_lcdc_imageblit,
8857b9aa7   Alexandre Courbot   fbdev: sh_mobile_...
1283
  	.fb_blank	= sh_mobile_lcdc_blank,
9dd38819c   Phil Edworthy   video: sh_mobile_...
1284
  	.fb_pan_display = sh_mobile_fb_pan_display,
40331b21f   Phil Edworthy   video: sh_mobile_...
1285
  	.fb_ioctl       = sh_mobile_ioctl,
dd210503b   Guennadi Liakhovetski   fbdev: sh_mobile_...
1286
1287
1288
  	.fb_open	= sh_mobile_open,
  	.fb_release	= sh_mobile_release,
  	.fb_check_var	= sh_mobile_check_var,
ed5bebf29   Laurent Pinchart   fbdev: sh_mobile_...
1289
  	.fb_set_par	= sh_mobile_set_par,
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
1290
  };
3b0fd9d75   Alexandre Courbot   fbdev: sh_mobile_...
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
  static int sh_mobile_lcdc_update_bl(struct backlight_device *bdev)
  {
  	struct sh_mobile_lcdc_chan *ch = bl_get_data(bdev);
  	struct sh_mobile_lcdc_board_cfg *cfg = &ch->cfg.board_cfg;
  	int brightness = bdev->props.brightness;
  
  	if (bdev->props.power != FB_BLANK_UNBLANK ||
  	    bdev->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK))
  		brightness = 0;
  
  	return cfg->set_brightness(cfg->board_data, brightness);
  }
  
  static int sh_mobile_lcdc_get_brightness(struct backlight_device *bdev)
  {
  	struct sh_mobile_lcdc_chan *ch = bl_get_data(bdev);
  	struct sh_mobile_lcdc_board_cfg *cfg = &ch->cfg.board_cfg;
  
  	return cfg->get_brightness(cfg->board_data);
  }
  
  static int sh_mobile_lcdc_check_fb(struct backlight_device *bdev,
  				   struct fb_info *info)
  {
  	return (info->bl_dev == bdev);
  }
  
  static struct backlight_ops sh_mobile_lcdc_bl_ops = {
  	.options	= BL_CORE_SUSPENDRESUME,
  	.update_status	= sh_mobile_lcdc_update_bl,
  	.get_brightness	= sh_mobile_lcdc_get_brightness,
  	.check_fb	= sh_mobile_lcdc_check_fb,
  };
  
  static struct backlight_device *sh_mobile_lcdc_bl_probe(struct device *parent,
  					       struct sh_mobile_lcdc_chan *ch)
  {
  	struct backlight_device *bl;
  
  	bl = backlight_device_register(ch->cfg.bl_info.name, parent, ch,
  				       &sh_mobile_lcdc_bl_ops, NULL);
beee1f20a   Dan Carpenter   fbdev: sh_mobile_...
1332
1333
1334
1335
  	if (IS_ERR(bl)) {
  		dev_err(parent, "unable to register backlight device: %ld
  ",
  			PTR_ERR(bl));
3b0fd9d75   Alexandre Courbot   fbdev: sh_mobile_...
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
  		return NULL;
  	}
  
  	bl->props.max_brightness = ch->cfg.bl_info.max_brightness;
  	bl->props.brightness = bl->props.max_brightness;
  	backlight_update_status(bl);
  
  	return bl;
  }
  
  static void sh_mobile_lcdc_bl_remove(struct backlight_device *bdev)
  {
  	backlight_device_unregister(bdev);
  }
2feb075a3   Magnus Damm   video: sh_mobile_...
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
  static int sh_mobile_lcdc_suspend(struct device *dev)
  {
  	struct platform_device *pdev = to_platform_device(dev);
  
  	sh_mobile_lcdc_stop(platform_get_drvdata(pdev));
  	return 0;
  }
  
  static int sh_mobile_lcdc_resume(struct device *dev)
  {
  	struct platform_device *pdev = to_platform_device(dev);
  
  	return sh_mobile_lcdc_start(platform_get_drvdata(pdev));
  }
0246c4712   Magnus Damm   video: Runtime PM...
1364
1365
1366
  static int sh_mobile_lcdc_runtime_suspend(struct device *dev)
  {
  	struct platform_device *pdev = to_platform_device(dev);
2427bb241   Laurent Pinchart   fbdev: sh_mobile_...
1367
  	struct sh_mobile_lcdc_priv *priv = platform_get_drvdata(pdev);
0246c4712   Magnus Damm   video: Runtime PM...
1368
1369
  
  	/* turn off LCDC hardware */
2427bb241   Laurent Pinchart   fbdev: sh_mobile_...
1370
  	lcdc_write(priv, _LDCNT1R, 0);
0246c4712   Magnus Damm   video: Runtime PM...
1371
1372
1373
1374
1375
1376
  	return 0;
  }
  
  static int sh_mobile_lcdc_runtime_resume(struct device *dev)
  {
  	struct platform_device *pdev = to_platform_device(dev);
2427bb241   Laurent Pinchart   fbdev: sh_mobile_...
1377
  	struct sh_mobile_lcdc_priv *priv = platform_get_drvdata(pdev);
0246c4712   Magnus Damm   video: Runtime PM...
1378

2427bb241   Laurent Pinchart   fbdev: sh_mobile_...
1379
  	__sh_mobile_lcdc_start(priv);
0246c4712   Magnus Damm   video: Runtime PM...
1380
1381
1382
  
  	return 0;
  }
471452104   Alexey Dobriyan   const: constify r...
1383
  static const struct dev_pm_ops sh_mobile_lcdc_dev_pm_ops = {
2feb075a3   Magnus Damm   video: sh_mobile_...
1384
1385
  	.suspend = sh_mobile_lcdc_suspend,
  	.resume = sh_mobile_lcdc_resume,
0246c4712   Magnus Damm   video: Runtime PM...
1386
1387
  	.runtime_suspend = sh_mobile_lcdc_runtime_suspend,
  	.runtime_resume = sh_mobile_lcdc_runtime_resume,
2feb075a3   Magnus Damm   video: sh_mobile_...
1388
  };
6de9edd5b   Guennadi Liakhovetski   fbdev: sh_mobile_...
1389
  /* locking: called with info->lock held */
6011bdeaa   Guennadi Liakhovetski   fbdev: sh-mobile:...
1390
1391
1392
1393
1394
1395
1396
  static int sh_mobile_lcdc_notify(struct notifier_block *nb,
  				 unsigned long action, void *data)
  {
  	struct fb_event *event = data;
  	struct fb_info *info = event->info;
  	struct sh_mobile_lcdc_chan *ch = info->par;
  	struct sh_mobile_lcdc_board_cfg	*board_cfg = &ch->cfg.board_cfg;
6011bdeaa   Guennadi Liakhovetski   fbdev: sh-mobile:...
1397
1398
  
  	if (&ch->lcdc->notifier != nb)
baf163749   Guennadi Liakhovetski   fbdev: sh_mobile_...
1399
  		return NOTIFY_DONE;
6011bdeaa   Guennadi Liakhovetski   fbdev: sh-mobile:...
1400
1401
1402
1403
1404
1405
1406
  
  	dev_dbg(info->dev, "%s(): action = %lu, data = %p
  ",
  		__func__, action, event->data);
  
  	switch(action) {
  	case FB_EVENT_SUSPEND:
247f99386   Alexandre Courbot   fbdev: sh_mobile_...
1407
  		if (board_cfg->display_off && try_module_get(board_cfg->owner)) {
6011bdeaa   Guennadi Liakhovetski   fbdev: sh-mobile:...
1408
  			board_cfg->display_off(board_cfg->board_data);
6de9edd5b   Guennadi Liakhovetski   fbdev: sh_mobile_...
1409
1410
  			module_put(board_cfg->owner);
  		}
afe417c03   Guennadi Liakhovetski   fbdev: sh_mobile_...
1411
  		sh_mobile_lcdc_stop(ch->lcdc);
6011bdeaa   Guennadi Liakhovetski   fbdev: sh-mobile:...
1412
1413
  		break;
  	case FB_EVENT_RESUME:
dd210503b   Guennadi Liakhovetski   fbdev: sh_mobile_...
1414
1415
1416
  		mutex_lock(&ch->open_lock);
  		sh_mobile_fb_reconfig(info);
  		mutex_unlock(&ch->open_lock);
6011bdeaa   Guennadi Liakhovetski   fbdev: sh-mobile:...
1417
1418
  
  		/* HDMI must be enabled before LCDC configuration */
247f99386   Alexandre Courbot   fbdev: sh_mobile_...
1419
  		if (board_cfg->display_on && try_module_get(board_cfg->owner)) {
dd210503b   Guennadi Liakhovetski   fbdev: sh_mobile_...
1420
  			board_cfg->display_on(board_cfg->board_data, info);
6de9edd5b   Guennadi Liakhovetski   fbdev: sh_mobile_...
1421
  			module_put(board_cfg->owner);
6011bdeaa   Guennadi Liakhovetski   fbdev: sh-mobile:...
1422
  		}
ebe5e12d0   Guennadi Liakhovetski   fbdev: sh_mobile_...
1423
  		sh_mobile_lcdc_start(ch->lcdc);
6011bdeaa   Guennadi Liakhovetski   fbdev: sh-mobile:...
1424
  	}
baf163749   Guennadi Liakhovetski   fbdev: sh_mobile_...
1425
  	return NOTIFY_OK;
6011bdeaa   Guennadi Liakhovetski   fbdev: sh-mobile:...
1426
  }
b4bee692e   Laurent Pinchart   fbdev: sh_mobile_...
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
  static int sh_mobile_lcdc_remove(struct platform_device *pdev)
  {
  	struct sh_mobile_lcdc_priv *priv = platform_get_drvdata(pdev);
  	struct fb_info *info;
  	int i;
  
  	fb_unregister_client(&priv->notifier);
  
  	for (i = 0; i < ARRAY_SIZE(priv->ch); i++)
  		if (priv->ch[i].info && priv->ch[i].info->dev)
  			unregister_framebuffer(priv->ch[i].info);
  
  	sh_mobile_lcdc_stop(priv);
  
  	for (i = 0; i < ARRAY_SIZE(priv->ch); i++) {
  		info = priv->ch[i].info;
  
  		if (!info || !info->device)
  			continue;
  
  		if (priv->ch[i].sglist)
  			vfree(priv->ch[i].sglist);
  
  		if (info->screen_base)
  			dma_free_coherent(&pdev->dev, info->fix.smem_len,
  					  info->screen_base,
  					  priv->ch[i].dma_handle);
  		fb_dealloc_cmap(&info->cmap);
  		framebuffer_release(info);
  	}
  
  	for (i = 0; i < ARRAY_SIZE(priv->ch); i++) {
  		if (priv->ch[i].bl)
  			sh_mobile_lcdc_bl_remove(priv->ch[i].bl);
  	}
  
  	if (priv->dot_clk)
  		clk_put(priv->dot_clk);
  
  	if (priv->dev)
  		pm_runtime_disable(priv->dev);
  
  	if (priv->base)
  		iounmap(priv->base);
  
  	if (priv->irq)
  		free_irq(priv->irq, priv);
  	kfree(priv);
  	return 0;
  }
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
1477

3ce055999   Laurent Pinchart   fbdev: sh_mobile_...
1478
1479
  static int __devinit sh_mobile_lcdc_channel_init(struct sh_mobile_lcdc_chan *ch,
  						 struct device *dev)
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
1480
  {
3ce055999   Laurent Pinchart   fbdev: sh_mobile_...
1481
1482
1483
1484
  	struct sh_mobile_lcdc_chan_cfg *cfg = &ch->cfg;
  	const struct fb_videomode *max_mode;
  	const struct fb_videomode *mode;
  	struct fb_var_screeninfo *var;
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
1485
  	struct fb_info *info;
3ce055999   Laurent Pinchart   fbdev: sh_mobile_...
1486
1487
1488
1489
1490
  	unsigned int max_size;
  	int num_cfg;
  	void *buf;
  	int ret;
  	int i;
a67472ad1   Laurent Pinchart   fbdev: sh_mobile_...
1491
1492
1493
  	mutex_init(&ch->open_lock);
  
  	/* Allocate the frame buffer device. */
3ce055999   Laurent Pinchart   fbdev: sh_mobile_...
1494
1495
1496
1497
1498
1499
1500
1501
  	ch->info = framebuffer_alloc(0, dev);
  	if (!ch->info) {
  		dev_err(dev, "unable to allocate fb_info
  ");
  		return -ENOMEM;
  	}
  
  	info = ch->info;
3ce055999   Laurent Pinchart   fbdev: sh_mobile_...
1502
1503
  	info->fbops = &sh_mobile_lcdc_ops;
  	info->par = ch;
a67472ad1   Laurent Pinchart   fbdev: sh_mobile_...
1504
1505
  	info->pseudo_palette = &ch->pseudo_palette;
  	info->flags = FBINFO_FLAG_DEFAULT;
3ce055999   Laurent Pinchart   fbdev: sh_mobile_...
1506
1507
1508
1509
1510
1511
1512
1513
1514
  
  	/* Iterate through the modes to validate them and find the highest
  	 * resolution.
  	 */
  	max_mode = NULL;
  	max_size = 0;
  
  	for (i = 0, mode = cfg->lcd_cfg; i < cfg->num_cfg; i++, mode++) {
  		unsigned int size = mode->yres * mode->xres;
edd153a3e   Laurent Pinchart   fbdev: sh_mobile_...
1515
1516
1517
  		/* NV12/NV21 buffers must have even number of lines */
  		if ((cfg->fourcc == V4L2_PIX_FMT_NV12 ||
  		     cfg->fourcc == V4L2_PIX_FMT_NV21) && (mode->yres & 0x1)) {
3ce055999   Laurent Pinchart   fbdev: sh_mobile_...
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
  			dev_err(dev, "yres must be multiple of 2 for YCbCr420 "
  				"mode.
  ");
  			return -EINVAL;
  		}
  
  		if (size > max_size) {
  			max_mode = mode;
  			max_size = size;
  		}
  	}
  
  	if (!max_size)
  		max_size = MAX_XRES * MAX_YRES;
  	else
  		dev_dbg(dev, "Found largest videomode %ux%u
  ",
  			max_mode->xres, max_mode->yres);
a67472ad1   Laurent Pinchart   fbdev: sh_mobile_...
1536
  	/* Create the mode list. */
3ce055999   Laurent Pinchart   fbdev: sh_mobile_...
1537
1538
1539
1540
1541
1542
1543
1544
1545
  	if (cfg->lcd_cfg == NULL) {
  		mode = &default_720p;
  		num_cfg = 1;
  	} else {
  		mode = cfg->lcd_cfg;
  		num_cfg = cfg->num_cfg;
  	}
  
  	fb_videomode_to_modelist(mode, num_cfg, &info->modelist);
a67472ad1   Laurent Pinchart   fbdev: sh_mobile_...
1546
1547
1548
1549
1550
  	/* Initialize variable screen information using the first mode as
  	 * default. The default Y virtual resolution is twice the panel size to
  	 * allow for double-buffering.
  	 */
  	var = &info->var;
3ce055999   Laurent Pinchart   fbdev: sh_mobile_...
1551
1552
1553
  	fb_videomode_to_var(var, mode);
  	var->width = cfg->lcd_size_cfg.width;
  	var->height = cfg->lcd_size_cfg.height;
3ce055999   Laurent Pinchart   fbdev: sh_mobile_...
1554
1555
  	var->yres_virtual = var->yres * 2;
  	var->activate = FB_ACTIVATE_NOW;
edd153a3e   Laurent Pinchart   fbdev: sh_mobile_...
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
  	switch (cfg->fourcc) {
  	case V4L2_PIX_FMT_RGB565:
  		var->bits_per_pixel = 16;
  		break;
  	case V4L2_PIX_FMT_BGR24:
  		var->bits_per_pixel = 24;
  		break;
  	case V4L2_PIX_FMT_BGR32:
  		var->bits_per_pixel = 32;
  		break;
  	default:
  		var->grayscale = cfg->fourcc;
  		break;
  	}
  
  	/* Make sure the memory size check won't fail. smem_len is initialized
  	 * later based on var.
  	 */
  	info->fix.smem_len = UINT_MAX;
a67472ad1   Laurent Pinchart   fbdev: sh_mobile_...
1575
  	ret = sh_mobile_check_var(var, info);
3ce055999   Laurent Pinchart   fbdev: sh_mobile_...
1576
1577
  	if (ret)
  		return ret;
edd153a3e   Laurent Pinchart   fbdev: sh_mobile_...
1578
  	max_size = max_size * var->bits_per_pixel / 8 * 2;
a67472ad1   Laurent Pinchart   fbdev: sh_mobile_...
1579
  	/* Allocate frame buffer memory and color map. */
edd153a3e   Laurent Pinchart   fbdev: sh_mobile_...
1580
  	buf = dma_alloc_coherent(dev, max_size, &ch->dma_handle, GFP_KERNEL);
3ce055999   Laurent Pinchart   fbdev: sh_mobile_...
1581
1582
1583
1584
1585
  	if (!buf) {
  		dev_err(dev, "unable to allocate buffer
  ");
  		return -ENOMEM;
  	}
3ce055999   Laurent Pinchart   fbdev: sh_mobile_...
1586
1587
1588
1589
  	ret = fb_alloc_cmap(&info->cmap, PALETTE_NR, 0);
  	if (ret < 0) {
  		dev_err(dev, "unable to allocate cmap
  ");
edd153a3e   Laurent Pinchart   fbdev: sh_mobile_...
1590
  		dma_free_coherent(dev, max_size, buf, ch->dma_handle);
3ce055999   Laurent Pinchart   fbdev: sh_mobile_...
1591
1592
  		return ret;
  	}
edd153a3e   Laurent Pinchart   fbdev: sh_mobile_...
1593
1594
1595
1596
  	/* Initialize fixed screen information. Restrict pan to 2 lines steps
  	 * for NV12 and NV21.
  	 */
  	info->fix = sh_mobile_lcdc_fix;
3ce055999   Laurent Pinchart   fbdev: sh_mobile_...
1597
  	info->fix.smem_start = ch->dma_handle;
edd153a3e   Laurent Pinchart   fbdev: sh_mobile_...
1598
1599
1600
1601
1602
1603
  	info->fix.smem_len = max_size;
  	if (cfg->fourcc == V4L2_PIX_FMT_NV12 ||
  	    cfg->fourcc == V4L2_PIX_FMT_NV21)
  		info->fix.ypanstep = 2;
  
  	if (sh_mobile_format_is_yuv(var)) {
3ce055999   Laurent Pinchart   fbdev: sh_mobile_...
1604
  		info->fix.line_length = var->xres;
edd153a3e   Laurent Pinchart   fbdev: sh_mobile_...
1605
1606
1607
1608
1609
  		info->fix.visual = FB_VISUAL_FOURCC;
  	} else {
  		info->fix.line_length = var->xres * var->bits_per_pixel / 8;
  		info->fix.visual = FB_VISUAL_TRUECOLOR;
  	}
3ce055999   Laurent Pinchart   fbdev: sh_mobile_...
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
  
  	info->screen_base = buf;
  	info->device = dev;
  	ch->display_var = *var;
  
  	return 0;
  }
  
  static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev)
  {
01ac25b59   Guennadi Liakhovetski   fbdev: sh_mobile_...
1620
  	struct sh_mobile_lcdc_info *pdata = pdev->dev.platform_data;
3ce055999   Laurent Pinchart   fbdev: sh_mobile_...
1621
  	struct sh_mobile_lcdc_priv *priv;
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
1622
  	struct resource *res;
3ce055999   Laurent Pinchart   fbdev: sh_mobile_...
1623
  	int num_channels;
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
1624
  	int error;
3ce055999   Laurent Pinchart   fbdev: sh_mobile_...
1625
  	int i;
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
1626

01ac25b59   Guennadi Liakhovetski   fbdev: sh_mobile_...
1627
  	if (!pdata) {
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
1628
1629
  		dev_err(&pdev->dev, "no platform data defined
  ");
8bed90557   Guennadi Liakhovetski   sh: fix a number ...
1630
  		return -EINVAL;
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
1631
1632
1633
  	}
  
  	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
8564557a0   Magnus Damm   video: sh_mobile_...
1634
1635
1636
1637
  	i = platform_get_irq(pdev, 0);
  	if (!res || i < 0) {
  		dev_err(&pdev->dev, "cannot get platform resources
  ");
8bed90557   Guennadi Liakhovetski   sh: fix a number ...
1638
  		return -ENOENT;
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
1639
1640
1641
1642
1643
1644
  	}
  
  	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
  	if (!priv) {
  		dev_err(&pdev->dev, "cannot allocate device data
  ");
8bed90557   Guennadi Liakhovetski   sh: fix a number ...
1645
  		return -ENOMEM;
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
1646
  	}
8bed90557   Guennadi Liakhovetski   sh: fix a number ...
1647
  	platform_set_drvdata(pdev, priv);
f8798ccbe   Yong Zhang   video: irq: Remov...
1648
  	error = request_irq(i, sh_mobile_lcdc_irq, 0,
7ad33e748   Kay Sievers   video: struct dev...
1649
  			    dev_name(&pdev->dev), priv);
8564557a0   Magnus Damm   video: sh_mobile_...
1650
1651
1652
1653
1654
1655
1656
  	if (error) {
  		dev_err(&pdev->dev, "unable to request irq
  ");
  		goto err1;
  	}
  
  	priv->irq = i;
5ef6b505d   Guennadi Liakhovetski   fbdev: sh_mobile_...
1657
  	atomic_set(&priv->hw_usecnt, -1);
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
1658

3ce055999   Laurent Pinchart   fbdev: sh_mobile_...
1659
1660
  	for (i = 0, num_channels = 0; i < ARRAY_SIZE(pdata->ch); i++) {
  		struct sh_mobile_lcdc_chan *ch = priv->ch + num_channels;
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
1661

01ac25b59   Guennadi Liakhovetski   fbdev: sh_mobile_...
1662
1663
  		ch->lcdc = priv;
  		memcpy(&ch->cfg, &pdata->ch[i], sizeof(pdata->ch[i]));
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
1664

01ac25b59   Guennadi Liakhovetski   fbdev: sh_mobile_...
1665
  		error = sh_mobile_lcdc_check_interface(ch);
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
1666
1667
1668
1669
1670
  		if (error) {
  			dev_err(&pdev->dev, "unsupported interface type
  ");
  			goto err1;
  		}
01ac25b59   Guennadi Liakhovetski   fbdev: sh_mobile_...
1671
1672
1673
  		init_waitqueue_head(&ch->frame_end_wait);
  		init_completion(&ch->vsync_completion);
  		ch->pan_offset = 0;
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
1674

3b0fd9d75   Alexandre Courbot   fbdev: sh_mobile_...
1675
1676
1677
  		/* probe the backlight is there is one defined */
  		if (ch->cfg.bl_info.max_brightness)
  			ch->bl = sh_mobile_lcdc_bl_probe(&pdev->dev, ch);
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
1678
1679
  		switch (pdata->ch[i].chan) {
  		case LCDC_CHAN_MAINLCD:
ce1c0b087   Laurent Pinchart   fbdev: sh_mobile_...
1680
  			ch->enabled = LDCNT2R_ME;
01ac25b59   Guennadi Liakhovetski   fbdev: sh_mobile_...
1681
  			ch->reg_offs = lcdc_offs_mainlcd;
3ce055999   Laurent Pinchart   fbdev: sh_mobile_...
1682
  			num_channels++;
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
1683
1684
  			break;
  		case LCDC_CHAN_SUBLCD:
ce1c0b087   Laurent Pinchart   fbdev: sh_mobile_...
1685
  			ch->enabled = LDCNT2R_SE;
01ac25b59   Guennadi Liakhovetski   fbdev: sh_mobile_...
1686
  			ch->reg_offs = lcdc_offs_sublcd;
3ce055999   Laurent Pinchart   fbdev: sh_mobile_...
1687
  			num_channels++;
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
1688
1689
1690
  			break;
  		}
  	}
3ce055999   Laurent Pinchart   fbdev: sh_mobile_...
1691
  	if (!num_channels) {
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
1692
1693
1694
1695
1696
  		dev_err(&pdev->dev, "no channels defined
  ");
  		error = -EINVAL;
  		goto err1;
  	}
edd153a3e   Laurent Pinchart   fbdev: sh_mobile_...
1697
  	/* for dual channel LCDC (MAIN + SUB) force shared format setting */
3ce055999   Laurent Pinchart   fbdev: sh_mobile_...
1698
  	if (num_channels == 2)
edd153a3e   Laurent Pinchart   fbdev: sh_mobile_...
1699
  		priv->forced_fourcc = pdata->ch[0].fourcc;
417d48274   Magnus Damm   fbdev: sh_mobile_...
1700

dba6f385b   Guennadi Liakhovetski   fbdev: sh-mobile-...
1701
1702
1703
  	priv->base = ioremap_nocache(res->start, resource_size(res));
  	if (!priv->base)
  		goto err1;
b51339fff   Magnus Damm   sh: sh_mobile lcd...
1704
  	error = sh_mobile_lcdc_setup_clocks(pdev, pdata->clock_source, priv);
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
1705
1706
1707
1708
1709
  	if (error) {
  		dev_err(&pdev->dev, "unable to setup clocks
  ");
  		goto err1;
  	}
7caa4342c   Damian Hobson-Garcia   sh_mobile_meram: ...
1710
  	priv->meram_dev = pdata->meram_dev;
3ce055999   Laurent Pinchart   fbdev: sh_mobile_...
1711
  	for (i = 0; i < num_channels; i++) {
01ac25b59   Guennadi Liakhovetski   fbdev: sh_mobile_...
1712
  		struct sh_mobile_lcdc_chan *ch = priv->ch + i;
c44f9f76d   Guennadi Liakhovetski   fbdev: sh_mobile_...
1713

3ce055999   Laurent Pinchart   fbdev: sh_mobile_...
1714
  		error = sh_mobile_lcdc_channel_init(ch, &pdev->dev);
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
1715
  		if (error)
3ce055999   Laurent Pinchart   fbdev: sh_mobile_...
1716
  			goto err1;
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
1717
  	}
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
1718
1719
1720
1721
1722
1723
  	error = sh_mobile_lcdc_start(priv);
  	if (error) {
  		dev_err(&pdev->dev, "unable to start hardware
  ");
  		goto err1;
  	}
3ce055999   Laurent Pinchart   fbdev: sh_mobile_...
1724
  	for (i = 0; i < num_channels; i++) {
1c6a307a5   Paul Mundt   sh: LCDC dcache f...
1725
  		struct sh_mobile_lcdc_chan *ch = priv->ch + i;
3ce055999   Laurent Pinchart   fbdev: sh_mobile_...
1726
  		struct fb_info *info = ch->info;
1c6a307a5   Paul Mundt   sh: LCDC dcache f...
1727
1728
  
  		if (info->fbdefio) {
8bed90557   Guennadi Liakhovetski   sh: fix a number ...
1729
  			ch->sglist = vmalloc(sizeof(struct scatterlist) *
1c6a307a5   Paul Mundt   sh: LCDC dcache f...
1730
  					info->fix.smem_len >> PAGE_SHIFT);
8bed90557   Guennadi Liakhovetski   sh: fix a number ...
1731
  			if (!ch->sglist) {
1c6a307a5   Paul Mundt   sh: LCDC dcache f...
1732
1733
1734
1735
1736
  				dev_err(&pdev->dev, "cannot allocate sglist
  ");
  				goto err1;
  			}
  		}
3b0fd9d75   Alexandre Courbot   fbdev: sh_mobile_...
1737
  		info->bl_dev = ch->bl;
1c6a307a5   Paul Mundt   sh: LCDC dcache f...
1738
  		error = register_framebuffer(info);
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
1739
1740
  		if (error < 0)
  			goto err1;
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
1741

edd153a3e   Laurent Pinchart   fbdev: sh_mobile_...
1742
1743
1744
1745
1746
  		dev_info(info->dev, "registered %s/%s as %dx%d %dbpp.
  ",
  			 pdev->name, (ch->cfg.chan == LCDC_CHAN_MAINLCD) ?
  			 "mainlcd" : "sublcd", info->var.xres, info->var.yres,
  			 info->var.bits_per_pixel);
8564557a0   Magnus Damm   video: sh_mobile_...
1747
1748
  
  		/* deferred io mode: disable clock to save power */
6011bdeaa   Guennadi Liakhovetski   fbdev: sh-mobile:...
1749
  		if (info->fbdefio || info->state == FBINFO_STATE_SUSPENDED)
8564557a0   Magnus Damm   video: sh_mobile_...
1750
  			sh_mobile_lcdc_clk_off(priv);
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
1751
  	}
6011bdeaa   Guennadi Liakhovetski   fbdev: sh-mobile:...
1752
1753
1754
  	/* Failure ignored */
  	priv->notifier.notifier_call = sh_mobile_lcdc_notify;
  	fb_register_client(&priv->notifier);
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
1755
  	return 0;
8bed90557   Guennadi Liakhovetski   sh: fix a number ...
1756
  err1:
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
1757
  	sh_mobile_lcdc_remove(pdev);
8bed90557   Guennadi Liakhovetski   sh: fix a number ...
1758

cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
1759
1760
  	return error;
  }
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
1761
1762
1763
1764
  static struct platform_driver sh_mobile_lcdc_driver = {
  	.driver		= {
  		.name		= "sh_mobile_lcdc_fb",
  		.owner		= THIS_MODULE,
2feb075a3   Magnus Damm   video: sh_mobile_...
1765
  		.pm		= &sh_mobile_lcdc_dev_pm_ops,
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
1766
1767
1768
1769
  	},
  	.probe		= sh_mobile_lcdc_probe,
  	.remove		= sh_mobile_lcdc_remove,
  };
4277f2c46   Axel Lin   video: convert dr...
1770
  module_platform_driver(sh_mobile_lcdc_driver);
cfb4f5d17   Magnus Damm   fbdev: SuperH Mob...
1771
1772
1773
1774
  
  MODULE_DESCRIPTION("SuperH Mobile LCDC Framebuffer driver");
  MODULE_AUTHOR("Magnus Damm <damm@opensource.se>");
  MODULE_LICENSE("GPL v2");