Linux Audio

Check our new training course

Real-Time Linux with PREEMPT_RT training

Feb 18-20, 2025
Register
Loading...
  1// SPDX-License-Identifier: GPL-2.0
  2/*
  3 * SH7760/SH7763 LCDC Framebuffer driver.
  4 *
  5 * (c) 2006-2008 MSC Vertriebsges.m.b.H.,
  6 *             Manuel Lauss <mano@roarinelk.homelinux.net>
  7 * (c) 2008 Nobuhiro Iwamatsu <iwamatsu.nobuhiro@renesas.com>
  8 *
  9 * PLEASE HAVE A LOOK AT Documentation/fb/sh7760fb.rst!
 10 *
 11 * Thanks to Siegfried Schaefer <s.schaefer at schaefer-edv.de>
 12 *     for his original source and testing!
 13 *
 14 * sh7760_setcolreg get from drivers/video/sh_mobile_lcdcfb.c
 15 */
 16
 17#include <linux/completion.h>
 18#include <linux/delay.h>
 19#include <linux/dma-mapping.h>
 20#include <linux/fb.h>
 21#include <linux/interrupt.h>
 22#include <linux/io.h>
 23#include <linux/kernel.h>
 24#include <linux/module.h>
 25#include <linux/platform_device.h>
 26#include <linux/slab.h>
 27
 28#include <asm/sh7760fb.h>
 29
 30struct sh7760fb_par {
 31	void __iomem *base;
 32	int irq;
 33
 34	struct sh7760fb_platdata *pd;	/* display information */
 35
 36	dma_addr_t fbdma;	/* physical address */
 37
 38	int rot;		/* rotation enabled? */
 39
 40	u32 pseudo_palette[16];
 41
 42	struct platform_device *dev;
 43	struct resource *ioarea;
 44	struct completion vsync;	/* vsync irq event */
 45};
 46
 47static irqreturn_t sh7760fb_irq(int irq, void *data)
 48{
 49	struct completion *c = data;
 50
 51	complete(c);
 52
 53	return IRQ_HANDLED;
 54}
 55
 56/* wait_for_lps - wait until power supply has reached a certain state. */
 57static int wait_for_lps(struct sh7760fb_par *par, int val)
 58{
 59	int i = 100;
 60	while (--i && ((ioread16(par->base + LDPMMR) & 3) != val))
 61		msleep(1);
 62
 63	if (i <= 0)
 64		return -ETIMEDOUT;
 65
 66	return 0;
 67}
 68
 69/* en/disable the LCDC */
 70static int sh7760fb_blank(int blank, struct fb_info *info)
 71{
 72	struct sh7760fb_par *par = info->par;
 73	struct sh7760fb_platdata *pd = par->pd;
 74	unsigned short cntr = ioread16(par->base + LDCNTR);
 75	unsigned short intr = ioread16(par->base + LDINTR);
 76	int lps;
 77
 78	if (blank == FB_BLANK_UNBLANK) {
 79		intr |= VINT_START;
 80		cntr = LDCNTR_DON2 | LDCNTR_DON;
 81		lps = 3;
 82	} else {
 83		intr &= ~VINT_START;
 84		cntr = LDCNTR_DON2;
 85		lps = 0;
 86	}
 87
 88	if (pd->blank)
 89		pd->blank(blank);
 90
 91	iowrite16(intr, par->base + LDINTR);
 92	iowrite16(cntr, par->base + LDCNTR);
 93
 94	return wait_for_lps(par, lps);
 95}
 96
 97static int sh7760_setcolreg (u_int regno,
 98	u_int red, u_int green, u_int blue,
 99	u_int transp, struct fb_info *info)
100{
101	u32 *palette = info->pseudo_palette;
102
103	if (regno >= 16)
104		return -EINVAL;
105
106	/* only FB_VISUAL_TRUECOLOR supported */
107
108	red >>= 16 - info->var.red.length;
109	green >>= 16 - info->var.green.length;
110	blue >>= 16 - info->var.blue.length;
111	transp >>= 16 - info->var.transp.length;
112
113	palette[regno] = (red << info->var.red.offset) |
114		(green << info->var.green.offset) |
115		(blue << info->var.blue.offset) |
116		(transp << info->var.transp.offset);
117
118	return 0;
119}
120
121static int sh7760fb_get_color_info(struct fb_info *info,
122				   u16 lddfr, int *bpp, int *gray)
123{
124	int lbpp, lgray;
125
126	lgray = lbpp = 0;
127
128	switch (lddfr & LDDFR_COLOR_MASK) {
129	case LDDFR_1BPP_MONO:
130		lgray = 1;
131		lbpp = 1;
132		break;
133	case LDDFR_2BPP_MONO:
134		lgray = 1;
135		lbpp = 2;
136		break;
137	case LDDFR_4BPP_MONO:
138		lgray = 1;
139		fallthrough;
140	case LDDFR_4BPP:
141		lbpp = 4;
142		break;
143	case LDDFR_6BPP_MONO:
144		lgray = 1;
145		fallthrough;
146	case LDDFR_8BPP:
147		lbpp = 8;
148		break;
149	case LDDFR_16BPP_RGB555:
150	case LDDFR_16BPP_RGB565:
151		lbpp = 16;
152		lgray = 0;
153		break;
154	default:
155		fb_dbg(info, "unsupported LDDFR bit depth.\n");
156		return -EINVAL;
157	}
158
159	if (bpp)
160		*bpp = lbpp;
161	if (gray)
162		*gray = lgray;
163
164	return 0;
165}
166
167static int sh7760fb_check_var(struct fb_var_screeninfo *var,
168			      struct fb_info *info)
169{
170	struct fb_fix_screeninfo *fix = &info->fix;
171	struct sh7760fb_par *par = info->par;
172	int ret, bpp;
173
174	/* get color info from register value */
175	ret = sh7760fb_get_color_info(info, par->pd->lddfr, &bpp, NULL);
176	if (ret)
177		return ret;
178
179	var->bits_per_pixel = bpp;
180
181	if ((var->grayscale) && (var->bits_per_pixel == 1))
182		fix->visual = FB_VISUAL_MONO10;
183	else if (var->bits_per_pixel >= 15)
184		fix->visual = FB_VISUAL_TRUECOLOR;
185	else
186		fix->visual = FB_VISUAL_PSEUDOCOLOR;
187
188	/* TODO: add some more validation here */
189	return 0;
190}
191
192/*
193 * sh7760fb_set_par - set videomode.
194 *
195 * NOTE: The rotation, grayscale and DSTN codepaths are
196 *     totally untested!
197 */
198static int sh7760fb_set_par(struct fb_info *info)
199{
200	struct sh7760fb_par *par = info->par;
201	struct fb_videomode *vm = par->pd->def_mode;
202	unsigned long sbase, dstn_off, ldsarl, stride;
203	unsigned short hsynp, hsynw, htcn, hdcn;
204	unsigned short vsynp, vsynw, vtln, vdln;
205	unsigned short lddfr, ldmtr;
206	int ret, bpp, gray;
207
208	par->rot = par->pd->rotate;
209
210	/* rotate only works with xres <= 320 */
211	if (par->rot && (vm->xres > 320)) {
212		fb_dbg(info, "rotation disabled due to display size\n");
213		par->rot = 0;
214	}
215
216	/* calculate LCDC reg vals from display parameters */
217	hsynp = vm->right_margin + vm->xres;
218	hsynw = vm->hsync_len;
219	htcn = vm->left_margin + hsynp + hsynw;
220	hdcn = vm->xres;
221	vsynp = vm->lower_margin + vm->yres;
222	vsynw = vm->vsync_len;
223	vtln = vm->upper_margin + vsynp + vsynw;
224	vdln = vm->yres;
225
226	/* get color info from register value */
227	ret = sh7760fb_get_color_info(info, par->pd->lddfr, &bpp, &gray);
228	if (ret)
229		return ret;
230
231	fb_dbg(info, "%dx%d %dbpp %s (orientation %s)\n", hdcn,
232		vdln, bpp, gray ? "grayscale" : "color",
233		par->rot ? "rotated" : "normal");
234
235#ifdef CONFIG_CPU_LITTLE_ENDIAN
236	lddfr = par->pd->lddfr | (1 << 8);
237#else
238	lddfr = par->pd->lddfr & ~(1 << 8);
239#endif
240
241	ldmtr = par->pd->ldmtr;
242
243	if (!(vm->sync & FB_SYNC_HOR_HIGH_ACT))
244		ldmtr |= LDMTR_CL1POL;
245	if (!(vm->sync & FB_SYNC_VERT_HIGH_ACT))
246		ldmtr |= LDMTR_FLMPOL;
247
248	/* shut down LCDC before changing display parameters */
249	sh7760fb_blank(FB_BLANK_POWERDOWN, info);
250
251	iowrite16(par->pd->ldickr, par->base + LDICKR);	/* pixclock */
252	iowrite16(ldmtr, par->base + LDMTR);	/* polarities */
253	iowrite16(lddfr, par->base + LDDFR);	/* color/depth */
254	iowrite16((par->rot ? 1 << 13 : 0), par->base + LDSMR);	/* rotate */
255	iowrite16(par->pd->ldpmmr, par->base + LDPMMR);	/* Power Management */
256	iowrite16(par->pd->ldpspr, par->base + LDPSPR);	/* Power Supply Ctrl */
257
258	/* display resolution */
259	iowrite16(((htcn >> 3) - 1) | (((hdcn >> 3) - 1) << 8),
260		  par->base + LDHCNR);
261	iowrite16(vdln - 1, par->base + LDVDLNR);
262	iowrite16(vtln - 1, par->base + LDVTLNR);
263	/* h/v sync signals */
264	iowrite16((vsynp - 1) | ((vsynw - 1) << 12), par->base + LDVSYNR);
265	iowrite16(((hsynp >> 3) - 1) | (((hsynw >> 3) - 1) << 12),
266		  par->base + LDHSYNR);
267	/* AC modulation sig */
268	iowrite16(par->pd->ldaclnr, par->base + LDACLNR);
269
270	stride = (par->rot) ? vtln : hdcn;
271	if (!gray)
272		stride *= (bpp + 7) >> 3;
273	else {
274		if (bpp == 1)
275			stride >>= 3;
276		else if (bpp == 2)
277			stride >>= 2;
278		else if (bpp == 4)
279			stride >>= 1;
280		/* 6 bpp == 8 bpp */
281	}
282
283	/* if rotated, stride must be power of 2 */
284	if (par->rot) {
285		unsigned long bit = 1 << 31;
286		while (bit) {
287			if (stride & bit)
288				break;
289			bit >>= 1;
290		}
291		if (stride & ~bit)
292			stride = bit << 1;	/* not P-o-2, round up */
293	}
294	iowrite16(stride, par->base + LDLAOR);
295
296	/* set display mem start address */
297	sbase = (unsigned long)par->fbdma;
298	if (par->rot)
299		sbase += (hdcn - 1) * stride;
300
301	iowrite32(sbase, par->base + LDSARU);
302
303	/*
304	 * for DSTN need to set address for lower half.
305	 * I (mlau) don't know which address to set it to,
306	 * so I guessed at (stride * yres/2).
307	 */
308	if (((ldmtr & 0x003f) >= LDMTR_DSTN_MONO_8) &&
309	    ((ldmtr & 0x003f) <= LDMTR_DSTN_COLOR_16)) {
310
311		fb_dbg(info, " ***** DSTN untested! *****\n");
312
313		dstn_off = stride;
314		if (par->rot)
315			dstn_off *= hdcn >> 1;
316		else
317			dstn_off *= vdln >> 1;
318
319		ldsarl = sbase + dstn_off;
320	} else
321		ldsarl = 0;
322
323	iowrite32(ldsarl, par->base + LDSARL);	/* mem for lower half of DSTN */
324
325	info->fix.line_length = stride;
326
327	sh7760fb_check_var(&info->var, info);
328
329	sh7760fb_blank(FB_BLANK_UNBLANK, info);	/* panel on! */
330
331	fb_dbg(info, "hdcn  : %6d htcn  : %6d\n", hdcn, htcn);
332	fb_dbg(info, "hsynw : %6d hsynp : %6d\n", hsynw, hsynp);
333	fb_dbg(info, "vdln  : %6d vtln  : %6d\n", vdln, vtln);
334	fb_dbg(info, "vsynw : %6d vsynp : %6d\n", vsynw, vsynp);
335	fb_dbg(info, "clksrc: %6d clkdiv: %6d\n",
336		(par->pd->ldickr >> 12) & 3, par->pd->ldickr & 0x1f);
337	fb_dbg(info, "ldpmmr: 0x%04x ldpspr: 0x%04x\n", par->pd->ldpmmr,
338		par->pd->ldpspr);
339	fb_dbg(info, "ldmtr : 0x%04x lddfr : 0x%04x\n", ldmtr, lddfr);
340	fb_dbg(info, "ldlaor: %ld\n", stride);
341	fb_dbg(info, "ldsaru: 0x%08lx ldsarl: 0x%08lx\n", sbase, ldsarl);
342
343	return 0;
344}
345
346static const struct fb_ops sh7760fb_ops = {
347	.owner = THIS_MODULE,
348	FB_DEFAULT_IOMEM_OPS,
349	.fb_blank = sh7760fb_blank,
350	.fb_check_var = sh7760fb_check_var,
351	.fb_setcolreg = sh7760_setcolreg,
352	.fb_set_par = sh7760fb_set_par,
353};
354
355static void sh7760fb_free_mem(struct fb_info *info)
356{
357	struct sh7760fb_par *par = info->par;
358
359	if (!info->screen_base)
360		return;
361
362	dma_free_coherent(info->device, info->screen_size,
363			  info->screen_base, par->fbdma);
364
365	par->fbdma = 0;
366	info->screen_base = NULL;
367	info->screen_size = 0;
368}
369
370/* allocate the framebuffer memory. This memory must be in Area3,
371 * (dictated by the DMA engine) and contiguous, at a 512 byte boundary.
372 */
373static int sh7760fb_alloc_mem(struct fb_info *info)
374{
375	struct sh7760fb_par *par = info->par;
376	void *fbmem;
377	unsigned long vram;
378	int ret, bpp;
379
380	if (info->screen_base)
381		return 0;
382
383	/* get color info from register value */
384	ret = sh7760fb_get_color_info(info, par->pd->lddfr, &bpp, NULL);
385	if (ret) {
386		printk(KERN_ERR "colinfo\n");
387		return ret;
388	}
389
390	/* min VRAM: xres_min = 16, yres_min = 1, bpp = 1: 2byte -> 1 page
391	   max VRAM: xres_max = 1024, yres_max = 1024, bpp = 16: 2MB */
392
393	vram = info->var.xres * info->var.yres;
394	if (info->var.grayscale) {
395		if (bpp == 1)
396			vram >>= 3;
397		else if (bpp == 2)
398			vram >>= 2;
399		else if (bpp == 4)
400			vram >>= 1;
401	} else if (bpp > 8)
402		vram *= 2;
403	if ((vram < 1) || (vram > 1024 * 2048)) {
404		fb_dbg(info, "too much VRAM required. Check settings\n");
405		return -ENODEV;
406	}
407
408	if (vram < PAGE_SIZE)
409		vram = PAGE_SIZE;
410
411	fbmem = dma_alloc_coherent(info->device, vram, &par->fbdma, GFP_KERNEL);
412	if (!fbmem)
413		return -ENOMEM;
414
415	if ((par->fbdma & SH7760FB_DMA_MASK) != SH7760FB_DMA_MASK) {
416		dma_free_coherent(info->device, vram, fbmem, par->fbdma);
417		dev_err(info->device, "kernel gave me memory at 0x%08lx, which is"
418			"unusable for the LCDC\n", (unsigned long)par->fbdma);
419		return -ENOMEM;
420	}
421
422	info->screen_base = fbmem;
423	info->screen_size = vram;
424	info->fix.smem_start = (unsigned long)info->screen_base;
425	info->fix.smem_len = info->screen_size;
426
427	return 0;
428}
429
430static int sh7760fb_probe(struct platform_device *pdev)
431{
432	struct fb_info *info;
433	struct resource *res;
434	struct sh7760fb_par *par;
435	int ret;
436
437	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
438	if (unlikely(res == NULL)) {
439		dev_err(&pdev->dev, "invalid resource\n");
440		return -EINVAL;
441	}
442
443	info = framebuffer_alloc(sizeof(struct sh7760fb_par), &pdev->dev);
444	if (!info)
445		return -ENOMEM;
446
447	par = info->par;
448	par->dev = pdev;
449
450	par->pd = pdev->dev.platform_data;
451	if (!par->pd) {
452		dev_dbg(&pdev->dev, "no display setup data!\n");
453		ret = -ENODEV;
454		goto out_fb;
455	}
456
457	par->ioarea = request_mem_region(res->start,
458					 resource_size(res), pdev->name);
459	if (!par->ioarea) {
460		dev_err(&pdev->dev, "mmio area busy\n");
461		ret = -EBUSY;
462		goto out_fb;
463	}
464
465	par->base = ioremap(res->start, resource_size(res));
466	if (!par->base) {
467		dev_err(&pdev->dev, "cannot remap\n");
468		ret = -ENODEV;
469		goto out_res;
470	}
471
472	iowrite16(0, par->base + LDINTR);	/* disable vsync irq */
473	par->irq = platform_get_irq(pdev, 0);
474	if (par->irq >= 0) {
475		ret = request_irq(par->irq, sh7760fb_irq, 0,
476				  "sh7760-lcdc", &par->vsync);
477		if (ret) {
478			dev_err(&pdev->dev, "cannot grab IRQ\n");
479			par->irq = -ENXIO;
480		} else
481			disable_irq_nosync(par->irq);
482	}
483
484	fb_videomode_to_var(&info->var, par->pd->def_mode);
485
486	ret = sh7760fb_alloc_mem(info);
487	if (ret) {
488		dev_dbg(info->device, "framebuffer memory allocation failed!\n");
489		goto out_unmap;
490	}
491
492	info->pseudo_palette = par->pseudo_palette;
493
494	/* fixup color register bitpositions. These are fixed by hardware */
495	info->var.red.offset = 11;
496	info->var.red.length = 5;
497	info->var.red.msb_right = 0;
498
499	info->var.green.offset = 5;
500	info->var.green.length = 6;
501	info->var.green.msb_right = 0;
502
503	info->var.blue.offset = 0;
504	info->var.blue.length = 5;
505	info->var.blue.msb_right = 0;
506
507	info->var.transp.offset = 0;
508	info->var.transp.length = 0;
509	info->var.transp.msb_right = 0;
510
511	strcpy(info->fix.id, "sh7760-lcdc");
512
513	/* set the DON2 bit now, before cmap allocation, as it will randomize
514	 * palette memory.
515	 */
516	iowrite16(LDCNTR_DON2, par->base + LDCNTR);
517	info->fbops = &sh7760fb_ops;
518
519	ret = fb_alloc_cmap(&info->cmap, 256, 0);
520	if (ret) {
521		dev_dbg(&pdev->dev, "Unable to allocate cmap memory\n");
522		goto out_mem;
523	}
524
525	ret = register_framebuffer(info);
526	if (ret < 0) {
527		dev_dbg(&pdev->dev, "cannot register fb!\n");
528		goto out_cmap;
529	}
530	platform_set_drvdata(pdev, info);
531
532	printk(KERN_INFO "%s: memory at phys 0x%08lx-0x%08lx, size %ld KiB\n",
533	       pdev->name,
534	       (unsigned long)par->fbdma,
535	       (unsigned long)(par->fbdma + info->screen_size - 1),
536	       info->screen_size >> 10);
537
538	return 0;
539
540out_cmap:
541	sh7760fb_blank(FB_BLANK_POWERDOWN, info);
542	fb_dealloc_cmap(&info->cmap);
543out_mem:
544	sh7760fb_free_mem(info);
545out_unmap:
546	if (par->irq >= 0)
547		free_irq(par->irq, &par->vsync);
548	iounmap(par->base);
549out_res:
550	release_mem_region(res->start, resource_size(res));
551out_fb:
552	framebuffer_release(info);
553	return ret;
554}
555
556static void sh7760fb_remove(struct platform_device *dev)
557{
558	struct fb_info *info = platform_get_drvdata(dev);
559	struct sh7760fb_par *par = info->par;
560
561	sh7760fb_blank(FB_BLANK_POWERDOWN, info);
562	unregister_framebuffer(info);
563	fb_dealloc_cmap(&info->cmap);
564	sh7760fb_free_mem(info);
565	if (par->irq >= 0)
566		free_irq(par->irq, &par->vsync);
567	iounmap(par->base);
568	release_mem_region(par->ioarea->start, resource_size(par->ioarea));
569	framebuffer_release(info);
570}
571
572static struct platform_driver sh7760_lcdc_driver = {
573	.driver = {
574		   .name = "sh7760-lcdc",
575		   },
576	.probe = sh7760fb_probe,
577	.remove = sh7760fb_remove,
578};
579
580module_platform_driver(sh7760_lcdc_driver);
581
582MODULE_AUTHOR("Nobuhiro Iwamatsu, Manuel Lauss");
583MODULE_DESCRIPTION("FBdev for SH7760/63 integrated LCD Controller");
584MODULE_LICENSE("GPL v2");